feat: Refactor task management to support recursive parent-child relationships and update database schema

This commit is contained in:
2026-03-01 11:39:28 +01:00
parent f6ba7ebc36
commit fc97a990fb
7 changed files with 179 additions and 47 deletions

View File

@@ -89,8 +89,8 @@
}, },
"UsersController.php": { "UsersController.php": {
"type": "-", "type": "-",
"size": 13159, "size": 16981,
"lmtime": 1772131160725, "lmtime": 1772277041282,
"modified": false "modified": false
} }
}, },
@@ -115,20 +115,20 @@
}, },
"class.Site.php": { "class.Site.php": {
"type": "-", "type": "-",
"size": 1298, "size": 1305,
"lmtime": 1771236164961, "lmtime": 1772276662027,
"modified": false "modified": false
}, },
"class.Tasks.php": { "class.Tasks.php": {
"type": "-", "type": "-",
"size": 25767, "size": 26351,
"lmtime": 1771495209476, "lmtime": 1772285310911,
"modified": true "modified": false
}, },
"class.Users.php": { "class.Users.php": {
"type": "-", "type": "-",
"size": 4974, "size": 7131,
"lmtime": 1772141683315, "lmtime": 1772276658356,
"modified": false "modified": false
}, },
"class.Wiki.php": { "class.Wiki.php": {
@@ -184,8 +184,8 @@
"Users": { "Users": {
"PermissionRepository.php": { "PermissionRepository.php": {
"type": "-", "type": "-",
"size": 1656, "size": 2103,
"lmtime": 1772131139905, "lmtime": 1772276648416,
"modified": false "modified": false
}, },
"UserRepository.php": { "UserRepository.php": {
@@ -230,13 +230,13 @@
"class.Projects.php": { "class.Projects.php": {
"type": "-", "type": "-",
"size": 27485, "size": 27485,
"lmtime": 0, "lmtime": 1772276304852,
"modified": true "modified": false
}, },
"class.Tasks.php": { "class.Tasks.php": {
"type": "-", "type": "-",
"size": 21700, "size": 29649,
"lmtime": 1771237098140, "lmtime": 1772285985430,
"modified": false "modified": false
}, },
"class.Users.php": { "class.Users.php": {
@@ -319,8 +319,8 @@
}, },
"index.php": { "index.php": {
"type": "-", "type": "-",
"size": 3935, "size": 6268,
"lmtime": 1772141695415, "lmtime": 1772276742587,
"modified": false "modified": false
}, },
"layout": { "layout": {
@@ -343,7 +343,64 @@
"modified": false "modified": false
} }
}, },
"libraries": {}, "libraries": {
"Simple-Gant-master": {
"frappe-gantt.css": {
"type": "-",
"size": 6990,
"lmtime": 0,
"modified": false
},
"frappe-gantt.js": {
"type": "-",
"size": 81951,
"lmtime": 1772285639705,
"modified": false
},
"frappe-gantt.js.map": {
"type": "-",
"size": 101071,
"lmtime": 0,
"modified": false
},
"frappe-gantt.min.js": {
"type": "-",
"size": 26395,
"lmtime": 0,
"modified": false
},
"frappe-gantt.min.js.map": {
"type": "-",
"size": 324380,
"lmtime": 0,
"modified": false
},
"index.html": {
"type": "-",
"size": 2131,
"lmtime": 0,
"modified": false
},
"LICENSE": {
"type": "-",
"size": 1151,
"lmtime": 0,
"modified": false
},
"names.txt": {
"type": "-",
"size": 361,
"lmtime": 0,
"modified": false
},
"README.md": {
"type": "-",
"size": 32,
"lmtime": 0,
"modified": false
}
}
},
"logs.txt": { "logs.txt": {
"type": "-", "type": "-",
"size": 3048, "size": 3048,
@@ -565,26 +622,26 @@
}, },
"main_view.php": { "main_view.php": {
"type": "-", "type": "-",
"size": 41808, "size": 46340,
"lmtime": 1771336223833, "lmtime": 1772285648542,
"modified": true "modified": false
}, },
"task_edit.php": { "task_edit.php": {
"type": "-", "type": "-",
"size": 32082, "size": 32097,
"lmtime": 1771495910826, "lmtime": 1772283180543,
"modified": true "modified": false
}, },
"task_popup.php": { "task_popup.php": {
"type": "-", "type": "-",
"size": 27993, "size": 32627,
"lmtime": 1772111511936, "lmtime": 1772282729802,
"modified": false "modified": false
}, },
"task_single.php": { "task_single.php": {
"type": "-", "type": "-",
"size": 2893, "size": 2893,
"lmtime": 0, "lmtime": 1772276304856,
"modified": false "modified": false
}, },
"work-time.php": { "work-time.php": {
@@ -603,8 +660,14 @@
}, },
"main-view.php": { "main-view.php": {
"type": "-", "type": "-",
"size": 3773, "size": 6085,
"lmtime": 1772130816909, "lmtime": 1772277033654,
"modified": false
},
"permissions-popup.php": {
"type": "-",
"size": 1545,
"lmtime": 1772277004609,
"modified": false "modified": false
}, },
"settings.php": { "settings.php": {
@@ -654,8 +717,8 @@
"Users": { "Users": {
"PermissionRepositoryTest.php": { "PermissionRepositoryTest.php": {
"type": "-", "type": "-",
"size": 1373, "size": 2111,
"lmtime": 1772131187402, "lmtime": 1772276691784,
"modified": false "modified": false
}, },
"UserRepositoryTest.php": { "UserRepositoryTest.php": {

View File

@@ -135,6 +135,7 @@ class MailToTaskImporter
$client_id = $this -> resolveClientIdBySenderDomain( $sender ); $client_id = $this -> resolveClientIdBySenderDomain( $sender );
$task_id = \factory\Tasks::task_save( $task_id = \factory\Tasks::task_save(
null,
null, null,
null, null,
self::TASK_USER_ID, self::TASK_USER_ID,

View File

@@ -7,7 +7,7 @@ class Cron
global $mdb; global $mdb;
$results = $mdb -> query( 'SELECT ' $results = $mdb -> query( 'SELECT '
. 't.*, ( SELECT COUNT(0) FROM tasks WHERE parent_id = t.id ) AS quantity ' . 't.*, ( SELECT COUNT(0) FROM tasks WHERE recursive_parent_id = t.id ) AS quantity '
. 'FROM ' . 'FROM '
. 'tasks AS t ' . 'tasks AS t '
. 'WHERE ' . 'WHERE '
@@ -101,7 +101,7 @@ class Cron
$row['show_in_calendar'] ? $show_in_calendar = 'on' : $show_in_calendar = 'off'; $row['show_in_calendar'] ? $show_in_calendar = 'on' : $show_in_calendar = 'off';
$new_task_id = \factory\Tasks::task_save( $new_task_id = \factory\Tasks::task_save(
null, $row['id'], $row['created_by'], $row['name'], $row['text'], $new_date_start, $new_date_end, $row['project_id'], $row['client_id'], $row['pay_rate'], $row['reminders_interval'], $row['recursively'] ? 'on' : 'off', $row['frequency'], $row['period'], $task_users, $row['date_end_month_day'], $row['date_start_month_day'], null, $row['status_change_mail'], true, $status, $show_in_calendar, $row['priority'], $row['recursive_last_date'] null, null, $row['id'], $row['created_by'], $row['name'], $row['text'], $new_date_start, $new_date_end, $row['project_id'], $row['client_id'], $row['pay_rate'], $row['reminders_interval'], $row['recursively'] ? 'on' : 'off', $row['frequency'], $row['period'], $task_users, $row['date_end_month_day'], $row['date_start_month_day'], null, $row['status_change_mail'], true, $status, $show_in_calendar, $row['priority'], $row['recursive_last_date']
); );
if ( $new_task_id ) { if ( $new_task_id ) {

View File

@@ -494,9 +494,17 @@ class Tasks
$values['priority'] = isset( $values['priority'] ) && $values['priority'] !== '' ? $values['priority'] : ( empty( $values['id'] ) ? 1 : 0 ); $values['priority'] = isset( $values['priority'] ) && $values['priority'] !== '' ? $values['priority'] : ( empty( $values['id'] ) ? 1 : 0 );
$values['recursive_last_date'] = isset( $values['recursive_last_date'] ) ? $values['recursive_last_date'] : null; $values['recursive_last_date'] = isset( $values['recursive_last_date'] ) ? $values['recursive_last_date'] : null;
$status = \Controllers\TasksController::resolveTaskStatusForSave( $values ); $status = \Controllers\TasksController::resolveTaskStatusForSave( $values );
$recursive_parent_id = null;
if ( !empty( $values['id'] ) )
{
$current_task = \factory\Tasks::task_details( (int)$values['id'] );
if ( is_array( $current_task ) and isset( $current_task['recursive_parent_id'] ) and $current_task['recursive_parent_id'] !== '' )
$recursive_parent_id = (int)$current_task['recursive_parent_id'];
}
if ( $id = \factory\Tasks::task_save( if ( $id = \factory\Tasks::task_save(
$values['id'], $values['parent_id'], $user['id'], $values['name'], $values['text'], $values['date_start'], $values['date_end'], $values['project_id'], $values['client_id'], $values['pay_rate'], $values['reminders_interval'], $values['recursively'], $values['frequency'], $values['period'], $values['users'], null, null, $values['send_email_notification'], $values['status_change_mail'], false, $status, $values['show_in_calendar'], $values['priority'], $values['recursive_last_date'] $values['id'], $values['parent_id'], $recursive_parent_id, $user['id'], $values['name'], $values['text'], $values['date_start'], $values['date_end'], $values['project_id'], $values['client_id'], $values['pay_rate'], $values['reminders_interval'], $values['recursively'], $values['frequency'], $values['period'], $values['users'], null, null, $values['send_email_notification'], $values['status_change_mail'], false, $status, $values['show_in_calendar'], $values['priority'], $values['recursive_last_date']
) ) ) )
{ {
\factory\Tasks::clear_task_opened( $id ); \factory\Tasks::clear_task_opened( $id );

View File

@@ -53,16 +53,17 @@ class Tasks
global $mdb; global $mdb;
if ( $user_id != 1 ) if ( $user_id != 1 )
$sql = ' AND id IN (SELECT task_id FROM task_user WHERE user_id = ' . $user_id . ') '; $sql = ' AND ( id IN (SELECT task_id FROM task_user WHERE user_id = ' . $user_id . ') OR created_by = ' . $user_id . ' ) ';
else else
$sql = ''; $sql = '';
$result = $mdb -> query( 'SELECT name, id, project_id, client_id FROM tasks WHERE parent_id IS NULL AND status != 2 AND status != 3 AND status != 1 ' . $sql . ' ORDER BY date_start ASC, o ASC' ) -> fetchAll( \PDO::FETCH_ASSOC ); $result = $mdb -> query( 'SELECT name, id, project_id, client_id FROM tasks WHERE deleted = 0 AND status != 2 ' . $sql . ' ORDER BY date_start IS NULL ASC, date_start ASC, o ASC, id DESC' ) -> fetchAll( \PDO::FETCH_ASSOC );
foreach ( $result as $row ) foreach ( $result as $row )
{ {
$task['id'] = $row['id']; $task['id'] = $row['id'];
$task['name'] = htmlspecialchars( $row['name'] ); $task['name'] = htmlspecialchars( $row['name'] );
$task['project_id'] = $row['project_id']; $task['project_id'] = $row['project_id'];
$task['client_id'] = $row['client_id'] ? (int)$row['client_id'] : 0;
$task['client'] = $row['client_id'] ? \factory\Crm::get_client_name( (int)$row['client_id'] ) : null; $task['client'] = $row['client_id'] ? \factory\Crm::get_client_name( (int)$row['client_id'] ) : null;
$task['project'] = \factory\Projects::get_project_name( $row['project_id'] ); $task['project'] = \factory\Projects::get_project_name( $row['project_id'] );
$tasks[] = $task; $tasks[] = $task;
@@ -632,7 +633,7 @@ class Tasks
} }
// przy zmianach pamiętać o zadaniach z CRON // przy zmianach pamiętać o zadaniach z CRON
static public function task_save( $task_id, $parent_id = null, $user_id, $name, $text, $date_start, $date_end, $project_id, $client_id, $pay_rate, $reminders_interval, $recursively, $frequency, $period, $users, $date_end_month_day = null, $date_start_month_day = null, $send_email_notification = false, $status_change_mail, $rescursive = false, $status = 0, $show_in_calendar, $priority = 0, $recursive_last_date = null ) static public function task_save( $task_id, $parent_id = null, $recursive_parent_id = null, $user_id, $name, $text, $date_start, $date_end, $project_id, $client_id, $pay_rate, $reminders_interval, $recursively, $frequency, $period, $users, $date_end_month_day = null, $date_start_month_day = null, $send_email_notification = false, $status_change_mail, $rescursive = false, $status = 0, $show_in_calendar, $priority = 0, $recursive_last_date = null )
{ {
global $mdb; global $mdb;
@@ -643,6 +644,7 @@ class Tasks
$mdb -> insert( 'tasks', [ $mdb -> insert( 'tasks', [
'created_by' => $user_id, 'created_by' => $user_id,
'parent_id' => $parent_id, 'parent_id' => $parent_id,
'recursive_parent_id' => $recursive_parent_id,
'name' => $name, 'name' => $name,
'text' => $rescursive ? $text : htmlspecialchars( $text ), 'text' => $rescursive ? $text : htmlspecialchars( $text ),
'date_start' => $date_start != '' ? "$date_start" : null, 'date_start' => $date_start != '' ? "$date_start" : null,
@@ -689,6 +691,7 @@ class Tasks
{ {
$mdb -> update( 'tasks', [ $mdb -> update( 'tasks', [
'parent_id' => $parent_id, 'parent_id' => $parent_id,
'recursive_parent_id' => $recursive_parent_id,
'name' => $name, 'name' => $name,
'text' => htmlspecialchars( $text ), 'text' => htmlspecialchars( $text ),
'date_start' => $date_start != '' ? $date_start : null, 'date_start' => $date_start != '' ? $date_start : null,
@@ -741,29 +744,29 @@ class Tasks
return true; return true;
} }
static public function task_first_id( $parent_id ) { static public function task_first_id( $recursive_parent_id ) {
global $mdb; global $mdb;
if ( $task_id = $mdb -> get( 'tasks', 'parent_id', [ 'id' => $parent_id ] ) ) if ( $task_id = $mdb -> get( 'tasks', 'recursive_parent_id', [ 'id' => $recursive_parent_id ] ) )
return self::task_first_id( $task_id ); return self::task_first_id( $task_id );
else else
return $parent_id; return $recursive_parent_id;
} }
static public function task_delete_all( $task_id ) static public function task_delete_all( $task_id )
{ {
global $mdb; global $mdb;
$parent_id = $mdb -> get( 'tasks', 'parent_id', [ 'id' => $task_id ] ); $recursive_parent_id = $mdb -> get( 'tasks', 'recursive_parent_id', [ 'id' => $task_id ] );
if ( !$parent_id ) { if ( !$recursive_parent_id ) {
$children_id = self::task_delete_from_db( $task_id ); $children_id = self::task_delete_from_db( $task_id );
if ( $children_id ) if ( $children_id )
self::task_delete_all( $children_id ); self::task_delete_all( $children_id );
} }
if ( $parent_id ) if ( $recursive_parent_id )
self::task_delete_all( $parent_id ); self::task_delete_all( $recursive_parent_id );
else else
return false; return false;
} }
@@ -772,13 +775,13 @@ class Tasks
{ {
global $mdb; global $mdb;
$children = $mdb -> get( 'tasks', 'id', [ 'parent_id' => $task_id ] ); $children = $mdb -> get( 'tasks', 'id', [ 'recursive_parent_id' => $task_id ] );
$attachments_repository = new \Domain\Tasks\TaskAttachmentRepository( $mdb ); $attachments_repository = new \Domain\Tasks\TaskAttachmentRepository( $mdb );
$attachments_repository -> purgeByTaskId( $task_id ); $attachments_repository -> purgeByTaskId( $task_id );
$mdb -> delete( 'tasks', [ 'id' => $task_id ] ); $mdb -> delete( 'tasks', [ 'id' => $task_id ] );
$mdb -> update( 'tasks', [ 'parent_id' => null ], [ 'parent_id' => $task_id ] ); $mdb -> update( 'tasks', [ 'recursive_parent_id' => null ], [ 'recursive_parent_id' => $task_id ] );
return $children; return $children;
} }

View File

@@ -0,0 +1,15 @@
-- 2026-03-01: rozdzielenie relacji parent_id (hierarchia) i recursive_parent_id (rekurencja)
ALTER TABLE tasks
ADD COLUMN recursive_parent_id INT NULL AFTER parent_id,
ADD INDEX idx_tasks_recursive_parent_id (recursive_parent_id);
-- Przeniesienie historycznych powiazan rekurencyjnych do nowej kolumny.
UPDATE tasks
SET recursive_parent_id = parent_id
WHERE parent_id IS NOT NULL;
-- parent_id pozostaje czyste i od teraz sluzy tylko do relacji nadrzedne/podrzedne.
UPDATE tasks
SET parent_id = NULL
WHERE parent_id IS NOT NULL;

View File

@@ -38,7 +38,23 @@ if ( is_array( $this -> clients ) )
$parent_tasks = [ 0 => '--- wybierz zadanie nadrzędne ---' ]; $parent_tasks = [ 0 => '--- wybierz zadanie nadrzędne ---' ];
if ( is_array( $this -> parent_tasks ) ) if ( is_array( $this -> parent_tasks ) )
foreach ( $this -> parent_tasks as $parent_task ) foreach ( $this -> parent_tasks as $parent_task )
$parent_tasks[ $parent_task[ 'id' ] ] = $parent_task[ 'name' ] . ( $parent_task['client'] ? ' (' . $parent_task['client'] . ')' : '' ); $parent_tasks[ $parent_task[ 'id' ] ] = $parent_task[ 'name' ] . ( $parent_task['client'] ? ' - ' . $parent_task['client'] : '' );
$parent_tasks_meta = [];
if ( is_array( $this -> parent_tasks ) )
{
foreach ( $this -> parent_tasks as $parent_task )
{
$parent_task_id = isset( $parent_task['id'] ) ? (int)$parent_task['id'] : 0;
if ( !$parent_task_id )
continue;
$parent_tasks_meta[ $parent_task_id ] = [
'project_id' => isset( $parent_task['project_id'] ) ? (int)$parent_task['project_id'] : 0,
'client_id' => isset( $parent_task['client_id'] ) && $parent_task['client_id'] !== null ? (int)$parent_task['client_id'] : 0
];
}
}
ob_start(); ob_start();
?> ?>
@@ -495,6 +511,8 @@ echo $grid -> draw();
<script type="text/javascript" src="/libraries/ckeditor/ckeditor.js"></script> <script type="text/javascript" src="/libraries/ckeditor/ckeditor.js"></script>
<script type="text/javascript" src="/libraries/ckeditor/adapters/jquery.js"></script> <script type="text/javascript" src="/libraries/ckeditor/adapters/jquery.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var parent_tasks_meta = <?= json_encode( $parent_tasks_meta );?>;
$(document).ready(function () $(document).ready(function ()
{ {
var task_edit_root = $( '#task-edit-tabs' ); var task_edit_root = $( '#task-edit-tabs' );
@@ -548,6 +566,26 @@ echo $grid -> draw();
} }
} }
function syncProjectAndClientFromParentTask() {
var parent_id = parseInt( $( '#parent_id' ).val(), 10 ) || 0;
if ( !parent_id || !parent_tasks_meta || !parent_tasks_meta[ parent_id ] )
return;
var parent_meta = parent_tasks_meta[ parent_id ];
var project_id = parseInt( parent_meta.project_id, 10 ) || 0;
var client_id = parseInt( parent_meta.client_id, 10 ) || 0;
if ( project_id > 0 && String( $( '#project_id' ).val() ) !== String( project_id ) )
{
$( '#project_id' ).val( project_id ).trigger( 'change' );
}
if ( String( $( '#client_id' ).val() ) !== String( client_id ) )
{
$( '#client_id' ).val( client_id ).trigger( 'change' );
}
}
function refreshAttachmentsState() { function refreshAttachmentsState() {
var task_id = parseInt( $( '#id' ).val(), 10 ) || 0; var task_id = parseInt( $( '#id' ).val(), 10 ) || 0;
var attachments_box = $( '.task-edit-attachments' ); var attachments_box = $( '.task-edit-attachments' );
@@ -676,6 +714,10 @@ echo $grid -> draw();
toggleRecursiveDetails(); toggleRecursiveDetails();
}); });
$( '#parent_id' ).on( 'change select2:select', function() {
syncProjectAndClientFromParentTask();
});
refreshAttachmentsState(); refreshAttachmentsState();
var initial_task_id = parseInt( $( '#id' ).val(), 10 ) || 0; var initial_task_id = parseInt( $( '#id' ).val(), 10 ) || 0;
if ( initial_task_id > 0 ) if ( initial_task_id > 0 )