summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrédéric Guillot <fred@kanboard.net>2014-09-01 19:36:40 -0800
committerFrédéric Guillot <fred@kanboard.net>2014-09-01 19:36:40 -0800
commite6d0658a0eedeb6a641c003d1c492af0f9a7502c (patch)
tree31004fe37246e39e438fb2dc7f1d07996634f1f3
parente4965546543be040da29dc341900fa9511a158be (diff)
Add the possibility to duplicate a task to another project
-rw-r--r--app/Action/TaskDuplicateAnotherProject.php3
-rw-r--r--app/Controller/Task.php33
-rw-r--r--app/Model/SubTask.php53
-rw-r--r--app/Model/Task.php55
-rw-r--r--app/Schema/Mysql.php2
-rw-r--r--app/Schema/Postgres.php2
-rw-r--r--app/Schema/Sqlite.php2
-rw-r--r--app/Templates/task_duplicate_project.php24
-rw-r--r--app/Templates/task_sidebar.php1
-rw-r--r--docs/automatic-actions.markdown9
-rw-r--r--tests/units/Base.php1
-rw-r--r--tests/units/SubtaskTest.php56
-rw-r--r--tests/units/TaskTest.php9
13 files changed, 205 insertions, 45 deletions
diff --git a/app/Action/TaskDuplicateAnotherProject.php b/app/Action/TaskDuplicateAnotherProject.php
index 7ef0f6ab..0f14cbed 100644
--- a/app/Action/TaskDuplicateAnotherProject.php
+++ b/app/Action/TaskDuplicateAnotherProject.php
@@ -73,7 +73,8 @@ class TaskDuplicateAnotherProject extends Base
{
if ($data['column_id'] == $this->getParam('column_id') && $data['project_id'] != $this->getParam('project_id')) {
- $this->task->duplicateToAnotherProject($data['task_id'], $this->getParam('project_id'));
+ $task = $this->task->getById($data['task_id']);
+ $this->task->duplicateToAnotherProject($this->getParam('project_id'), $task);
return true;
}
diff --git a/app/Controller/Task.php b/app/Controller/Task.php
index 6936185f..35ef6965 100644
--- a/app/Controller/Task.php
+++ b/app/Controller/Task.php
@@ -433,6 +433,26 @@ class Task extends Base
*/
public function move()
{
+ $this->toAnotherProject('move');
+ }
+
+ /**
+ * Duplicate a task to another project
+ *
+ * @access public
+ */
+ public function copy()
+ {
+ $this->toAnotherProject('duplicate');
+ }
+
+ /**
+ * Common methods between the actions "move" and "copy"
+ *
+ * @access private
+ */
+ private function toAnotherProject($action)
+ {
$task = $this->getTask();
$values = $task;
$errors = array();
@@ -446,23 +466,24 @@ class Task extends Base
list($valid, $errors) = $this->task->validateProjectModification($values);
if ($valid) {
- if ($this->task->moveToAnotherProject($values['project_id'], $task)) {
- $this->session->flash(t('Task updated successfully.'));
- $this->response->redirect('?controller=task&action=show&task_id='.$values['id']);
+ $task_id = $this->task->{$action.'ToAnotherProject'}($values['project_id'], $task);
+ if ($task_id) {
+ $this->session->flash(t('Task created successfully.'));
+ $this->response->redirect('?controller=task&action=show&task_id='.$task_id);
}
else {
- $this->session->flashError(t('Unable to update your task.'));
+ $this->session->flashError(t('Unable to create your task.'));
}
}
}
- $this->response->html($this->taskLayout('task_move_project', array(
+ $this->response->html($this->taskLayout('task_'.$action.'_project', array(
'values' => $values,
'errors' => $errors,
'task' => $task,
'projects_list' => $projects_list,
'menu' => 'tasks',
- 'title' => t('Move the task to another project')
+ 'title' => t(ucfirst($action).' the task to another project')
)));
}
}
diff --git a/app/Model/SubTask.php b/app/Model/SubTask.php
index 9f2941c5..011c58e7 100644
--- a/app/Model/SubTask.php
+++ b/app/Model/SubTask.php
@@ -121,13 +121,12 @@ class SubTask extends Base
}
/**
- * Create
+ * Prepare data before insert/update
*
* @access public
* @param array $values Form values
- * @return bool
*/
- public function create(array $values)
+ public function prepare(array &$values)
{
if (isset($values['another_subtask'])) {
unset($values['another_subtask']);
@@ -140,7 +139,18 @@ class SubTask extends Base
if (isset($values['time_spent']) && empty($values['time_spent'])) {
$values['time_spent'] = 0;
}
+ }
+ /**
+ * Create
+ *
+ * @access public
+ * @param array $values Form values
+ * @return bool
+ */
+ public function create(array $values)
+ {
+ $this->prepare($values);
$result = $this->db->table(self::TABLE)->save($values);
if ($result) {
@@ -160,14 +170,7 @@ class SubTask extends Base
*/
public function update(array $values)
{
- if (isset($values['time_estimated']) && empty($values['time_estimated'])) {
- $values['time_estimated'] = 0;
- }
-
- if (isset($values['time_spent']) && empty($values['time_spent'])) {
- $values['time_spent'] = 0;
- }
-
+ $this->prepare($values);
$result = $this->db->table(self::TABLE)->eq('id', $values['id'])->save($values);
if ($result) {
@@ -190,6 +193,34 @@ class SubTask extends Base
}
/**
+ * Duplicate all subtasks to another task
+ *
+ * @access public
+ * @param integer $src_task_id Source task id
+ * @param integer $dst_task_id Destination task id
+ * @return bool
+ */
+ public function duplicate($src_task_id, $dst_task_id)
+ {
+ $subtasks = $this->db->table(self::TABLE)
+ ->columns('title', 'time_estimated')
+ ->eq('task_id', $src_task_id)
+ ->findAll();
+
+ foreach ($subtasks as &$subtask) {
+
+ $subtask['task_id'] = $dst_task_id;
+ $subtask['time_spent'] = 0;
+
+ if (! $this->db->table(self::TABLE)->save($subtask)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
* Validate creation/modification
*
* @access public
diff --git a/app/Model/Task.php b/app/Model/Task.php
index 64f3b74c..30c1781b 100644
--- a/app/Model/Task.php
+++ b/app/Model/Task.php
@@ -306,43 +306,54 @@ class Task extends Base
* Duplicate a task to another project (always copy to the first column)
*
* @access public
- * @param integer $task_id Task id
* @param integer $project_id Destination project id
+ * @param array $task Task data
* @return boolean
*/
- public function duplicateToAnotherProject($task_id, $project_id)
+ public function duplicateToAnotherProject($project_id, array $task)
{
$this->db->startTransaction();
- // Get the original task
- $task = $this->getById($task_id);
-
- // Cleanup data
- unset($task['id']);
- unset($task['date_completed']);
-
// Assign new values
- $task['date_creation'] = time();
- $task['owner_id'] = 0;
- $task['category_id'] = 0;
- $task['is_active'] = 1;
- $task['column_id'] = $this->board->getFirstColumn($project_id);
- $task['project_id'] = $project_id;
- $task['position'] = $this->countByColumnId($task['project_id'], $task['column_id']);
+ $values = array();
+ $values['title'] = $task['title'];
+ $values['description'] = $task['description'];
+ $values['date_creation'] = time();
+ $values['date_modification'] = $values['date_creation'];
+ $values['date_due'] = $task['date_due'];
+ $values['color_id'] = $task['color_id'];
+ $values['project_id'] = $project_id;
+ $values['column_id'] = $this->board->getFirstColumn($project_id);
+ $values['owner_id'] = 0;
+ $values['creator_id'] = $task['creator_id'];
+ $values['position'] = $this->countByColumnId($project_id, $values['column_id']);
+ $values['score'] = $task['score'];
+ $values['category_id'] = 0;
+
+ // Check if the assigned user is allowed for the new project
+ if ($task['owner_id'] && $this->project->isUserAllowed($project_id, $task['owner_id'])) {
+ $values['owner_id'] = $task['owner_id'];
+ }
// Save task
- if (! $this->db->table(self::TABLE)->save($task)) {
+ if (! $this->db->table(self::TABLE)->save($values)) {
$this->db->cancelTransaction();
return false;
}
$task_id = $this->db->getConnection()->getLastId();
+ // Duplicate subtasks
+ if (! $this->subTask->duplicate($task['id'], $task_id)) {
+ $this->db->cancelTransaction();
+ return false;
+ }
+
$this->db->closeTransaction();
// Trigger events
- $this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $task);
- $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $task);
+ $this->event->trigger(self::EVENT_CREATE_UPDATE, array('task_id' => $task_id) + $values);
+ $this->event->trigger(self::EVENT_CREATE, array('task_id' => $task_id) + $values);
return $task_id;
}
@@ -584,7 +595,11 @@ class Task extends Base
$values['position'] = $this->countByColumnId($project_id, $values['column_id']);
$values['project_id'] = $project_id;
- return $this->db->table(self::TABLE)->eq('id', $task['id'])->update($values);
+ if ($this->db->table(self::TABLE)->eq('id', $task['id'])->update($values)) {
+ return $task['id'];
+ }
+
+ return false;
}
/**
diff --git a/app/Schema/Mysql.php b/app/Schema/Mysql.php
index ca4bbbae..9b202a69 100644
--- a/app/Schema/Mysql.php
+++ b/app/Schema/Mysql.php
@@ -57,7 +57,7 @@ function version_18($pdo)
status INT DEFAULT 0,
time_estimated INT DEFAULT 0,
time_spent INT DEFAULT 0,
- task_id INT,
+ task_id INT NOT NULL,
user_id INT,
PRIMARY KEY (id),
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
diff --git a/app/Schema/Postgres.php b/app/Schema/Postgres.php
index ac27f786..99774a55 100644
--- a/app/Schema/Postgres.php
+++ b/app/Schema/Postgres.php
@@ -143,7 +143,7 @@ function version_1($pdo)
status SMALLINT DEFAULT 0,
time_estimated INTEGER DEFAULT 0,
time_spent INTEGER DEFAULT 0,
- task_id INTEGER,
+ task_id INTEGER NOT NULL,
user_id INTEGER,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
);
diff --git a/app/Schema/Sqlite.php b/app/Schema/Sqlite.php
index fed9aaf8..21b6ed4d 100644
--- a/app/Schema/Sqlite.php
+++ b/app/Schema/Sqlite.php
@@ -57,7 +57,7 @@ function version_18($pdo)
status INTEGER DEFAULT 0,
time_estimated INTEGER DEFAULT 0,
time_spent INTEGER DEFAULT 0,
- task_id INTEGER,
+ task_id INTEGER NOT NULL,
user_id INTEGER,
FOREIGN KEY(task_id) REFERENCES tasks(id) ON DELETE CASCADE
)"
diff --git a/app/Templates/task_duplicate_project.php b/app/Templates/task_duplicate_project.php
new file mode 100644
index 00000000..86d2114a
--- /dev/null
+++ b/app/Templates/task_duplicate_project.php
@@ -0,0 +1,24 @@
+<div class="page-header">
+ <h2><?= t('Duplicate the task to another project') ?></h2>
+</div>
+
+<?php if (empty($projects_list)): ?>
+ <p class="alert"><?= t('No project') ?></p>
+<?php else: ?>
+
+ <form method="post" action="?controller=task&amp;action=copy&amp;task_id=<?= $task['id'] ?>&amp;project_id=<?= $task['project_id'] ?>" autocomplete="off">
+
+ <?= Helper\form_csrf() ?>
+
+ <?= Helper\form_hidden('id', $values) ?>
+ <?= Helper\form_label(t('Project'), 'project_id') ?>
+ <?= Helper\form_select('project_id', $projects_list, $values, $errors) ?><br/>
+
+ <div class="form-actions">
+ <input type="submit" value="<?= t('Save') ?>" class="btn btn-blue"/>
+ <?= t('or') ?>
+ <a href="?controller=task&amp;action=show&amp;task_id=<?= $task['id'] ?>"><?= t('cancel') ?></a>
+ </div>
+ </form>
+
+<?php endif ?> \ No newline at end of file
diff --git a/app/Templates/task_sidebar.php b/app/Templates/task_sidebar.php
index ba9d7a86..43170aa6 100644
--- a/app/Templates/task_sidebar.php
+++ b/app/Templates/task_sidebar.php
@@ -10,6 +10,7 @@
<li><a href="?controller=file&amp;action=create&amp;task_id=<?= $task['id'] ?>"><?= t('Attach a document') ?></a></li>
<li><a href="?controller=task&amp;action=duplicate&amp;project_id=<?= $task['project_id'] ?>&amp;task_id=<?= $task['id'] ?>"><?= t('Duplicate') ?></a></li>
<li><a href="?controller=task&amp;action=move&amp;project_id=<?= $task['project_id'] ?>&amp;task_id=<?= $task['id'] ?>"><?= t('Move to another project') ?></a></li>
+ <li><a href="?controller=task&amp;action=copy&amp;project_id=<?= $task['project_id'] ?>&amp;task_id=<?= $task['id'] ?>"><?= t('Duplicate to another project') ?></a></li>
<li>
<?php if ($task['is_active'] == 1): ?>
<a href="?controller=task&amp;action=confirmClose&amp;task_id=<?= $task['id'] ?>"><?= t('Close this task') ?></a>
diff --git a/docs/automatic-actions.markdown b/docs/automatic-actions.markdown
index e903e0b1..6d46b053 100644
--- a/docs/automatic-actions.markdown
+++ b/docs/automatic-actions.markdown
@@ -33,6 +33,7 @@ List of available actions
- Assign the task to a specific user
- Assign the task to the person who does the action
- Duplicate the task to another project
+- Move the task to another project
- Assign a color to a specific user
- Assign automatically a color based on a category
- Assign automatically a category based on a color
@@ -68,6 +69,14 @@ Let's say we have two projects "Customer orders" and "Production", once the orde
- Choose the action: **Duplicate the task to another project**
- Define the action parameters: **Column = Validated** and **Project = Production**
+### When a task is moved to the last column, move the exact same task to another project
+
+Let's say we have two projects "Ideas" and "Development", once the idea is validated, swap it to the "Development" project.
+
+- Choose the event: **Move a task to another column**
+- Choose the action: **Move the task to another project**
+- Define the action parameters: **Column = Validated** and **Project = Development**
+
### I want to assign automatically a color to the user Bob
- Choose the event: **Task creation**
diff --git a/tests/units/Base.php b/tests/units/Base.php
index fad5d074..7d8a075f 100644
--- a/tests/units/Base.php
+++ b/tests/units/Base.php
@@ -39,6 +39,7 @@ require_once __DIR__.'/../../app/Model/User.php';
require_once __DIR__.'/../../app/Model/Board.php';
require_once __DIR__.'/../../app/Model/Action.php';
require_once __DIR__.'/../../app/Model/Category.php';
+require_once __DIR__.'/../../app/Model/SubTask.php';
require_once __DIR__.'/../../app/Action/Base.php';
require_once __DIR__.'/../../app/Action/TaskClose.php';
diff --git a/tests/units/SubtaskTest.php b/tests/units/SubtaskTest.php
new file mode 100644
index 00000000..a74ee60a
--- /dev/null
+++ b/tests/units/SubtaskTest.php
@@ -0,0 +1,56 @@
+<?php
+
+require_once __DIR__.'/Base.php';
+
+use Model\Task;
+use Model\SubTask;
+use Model\Project;
+use Model\Category;
+use Model\User;
+
+class SubTaskTest extends Base
+{
+ public function testDuplicate()
+ {
+ $t = new Task($this->registry);
+ $s = new SubTask($this->registry);
+ $p = new Project($this->registry);
+
+ // We create a project
+ $this->assertEquals(1, $p->create(array('name' => 'test1')));
+
+ // We create 2 tasks
+ $this->assertEquals(1, $t->create(array('title' => 'test 1', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1)));
+ $this->assertEquals(2, $t->create(array('title' => 'test 2', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 0)));
+
+ // We create many subtasks for the first task
+ $this->assertEquals(1, $s->create(array('title' => 'subtask #1', 'task_id' => 1, 'time_estimated' => 5, 'time_spent' => 3, 'status' => 1)));
+ $this->assertEquals(2, $s->create(array('title' => 'subtask #2', 'task_id' => 1, 'time_estimated' => 0, 'time_spent' => 0, 'status' => 2, 'user_id' => 1)));
+
+ // We duplicate our subtasks
+ $this->assertTrue($s->duplicate(1, 2));
+ $subtasks = $s->getAll(2);
+
+ $this->assertNotFalse($subtasks);
+ $this->assertNotEmpty($subtasks);
+ $this->assertEquals(2, count($subtasks));
+
+ $this->assertEquals('subtask #1', $subtasks[0]['title']);
+ $this->assertEquals('subtask #2', $subtasks[1]['title']);
+
+ $this->assertEquals(2, $subtasks[0]['task_id']);
+ $this->assertEquals(2, $subtasks[1]['task_id']);
+
+ $this->assertEquals(5, $subtasks[0]['time_estimated']);
+ $this->assertEquals(0, $subtasks[1]['time_estimated']);
+
+ $this->assertEquals(0, $subtasks[0]['time_spent']);
+ $this->assertEquals(0, $subtasks[1]['time_spent']);
+
+ $this->assertEquals(0, $subtasks[0]['status']);
+ $this->assertEquals(0, $subtasks[1]['status']);
+
+ $this->assertEquals(0, $subtasks[0]['user_id']);
+ $this->assertEquals(0, $subtasks[1]['user_id']);
+ }
+}
diff --git a/tests/units/TaskTest.php b/tests/units/TaskTest.php
index 66f82016..064f30b0 100644
--- a/tests/units/TaskTest.php
+++ b/tests/units/TaskTest.php
@@ -170,15 +170,16 @@ class TaskTest extends Base
// We create a task
$this->assertEquals(1, $t->create(array('title' => 'test', 'project_id' => 1, 'column_id' => 1, 'owner_id' => 1, 'category_id' => 1)));
+ $task = $t->getById(1);
// We duplicate our task to the 2nd project
- $this->assertEquals(2, $t->duplicateToAnotherProject(1, 2));
+ $this->assertEquals(2, $t->duplicateToAnotherProject(2, $task));
$this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_CREATE));
// Check the values of the duplicated task
$task = $t->getById(2);
$this->assertNotEmpty($task);
- $this->assertEquals(0, $task['owner_id']);
+ $this->assertEquals(1, $task['owner_id']);
$this->assertEquals(0, $task['category_id']);
$this->assertEquals(2, $task['project_id']);
$this->assertEquals('test', $task['title']);
@@ -204,7 +205,7 @@ class TaskTest extends Base
// We duplicate our task to the 2nd project
$task = $t->getById(1);
- $this->assertTrue($t->moveToAnotherProject(2, $task));
+ $this->assertEquals(1, $t->moveToAnotherProject(2, $task));
//$this->assertTrue($this->registry->event->isEventTriggered(Task::EVENT_CREATE));
// Check the values of the duplicated task
@@ -222,7 +223,7 @@ class TaskTest extends Base
// The owner should be reseted
$task = $t->getById(2);
- $this->assertTrue($t->moveToAnotherProject(2, $task));
+ $this->assertEquals(2, $t->moveToAnotherProject(2, $task));
$task = $t->getById(2);
$this->assertNotEmpty($task);