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": {
"type": "-",
"size": 13159,
"lmtime": 1772131160725,
"size": 16981,
"lmtime": 1772277041282,
"modified": false
}
},
@@ -115,20 +115,20 @@
},
"class.Site.php": {
"type": "-",
"size": 1298,
"lmtime": 1771236164961,
"size": 1305,
"lmtime": 1772276662027,
"modified": false
},
"class.Tasks.php": {
"type": "-",
"size": 25767,
"lmtime": 1771495209476,
"modified": true
"size": 26351,
"lmtime": 1772285310911,
"modified": false
},
"class.Users.php": {
"type": "-",
"size": 4974,
"lmtime": 1772141683315,
"size": 7131,
"lmtime": 1772276658356,
"modified": false
},
"class.Wiki.php": {
@@ -184,8 +184,8 @@
"Users": {
"PermissionRepository.php": {
"type": "-",
"size": 1656,
"lmtime": 1772131139905,
"size": 2103,
"lmtime": 1772276648416,
"modified": false
},
"UserRepository.php": {
@@ -230,13 +230,13 @@
"class.Projects.php": {
"type": "-",
"size": 27485,
"lmtime": 0,
"modified": true
"lmtime": 1772276304852,
"modified": false
},
"class.Tasks.php": {
"type": "-",
"size": 21700,
"lmtime": 1771237098140,
"size": 29649,
"lmtime": 1772285985430,
"modified": false
},
"class.Users.php": {
@@ -319,8 +319,8 @@
},
"index.php": {
"type": "-",
"size": 3935,
"lmtime": 1772141695415,
"size": 6268,
"lmtime": 1772276742587,
"modified": false
},
"layout": {
@@ -343,7 +343,64 @@
"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": {
"type": "-",
"size": 3048,
@@ -565,26 +622,26 @@
},
"main_view.php": {
"type": "-",
"size": 41808,
"lmtime": 1771336223833,
"modified": true
"size": 46340,
"lmtime": 1772285648542,
"modified": false
},
"task_edit.php": {
"type": "-",
"size": 32082,
"lmtime": 1771495910826,
"modified": true
"size": 32097,
"lmtime": 1772283180543,
"modified": false
},
"task_popup.php": {
"type": "-",
"size": 27993,
"lmtime": 1772111511936,
"size": 32627,
"lmtime": 1772282729802,
"modified": false
},
"task_single.php": {
"type": "-",
"size": 2893,
"lmtime": 0,
"lmtime": 1772276304856,
"modified": false
},
"work-time.php": {
@@ -603,8 +660,14 @@
},
"main-view.php": {
"type": "-",
"size": 3773,
"lmtime": 1772130816909,
"size": 6085,
"lmtime": 1772277033654,
"modified": false
},
"permissions-popup.php": {
"type": "-",
"size": 1545,
"lmtime": 1772277004609,
"modified": false
},
"settings.php": {
@@ -654,8 +717,8 @@
"Users": {
"PermissionRepositoryTest.php": {
"type": "-",
"size": 1373,
"lmtime": 1772131187402,
"size": 2111,
"lmtime": 1772276691784,
"modified": false
},
"UserRepositoryTest.php": {

View File

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

View File

@@ -7,7 +7,7 @@ class Cron
global $mdb;
$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 '
. 'tasks AS t '
. 'WHERE '
@@ -101,7 +101,7 @@ class Cron
$row['show_in_calendar'] ? $show_in_calendar = 'on' : $show_in_calendar = 'off';
$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 ) {

View File

@@ -494,9 +494,17 @@ class Tasks
$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;
$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(
$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 );

View File

@@ -53,16 +53,17 @@ class Tasks
global $mdb;
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
$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 )
{
$task['id'] = $row['id'];
$task['name'] = htmlspecialchars( $row['name'] );
$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['project'] = \factory\Projects::get_project_name( $row['project_id'] );
$tasks[] = $task;
@@ -632,7 +633,7 @@ class Tasks
}
// 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;
@@ -643,6 +644,7 @@ class Tasks
$mdb -> insert( 'tasks', [
'created_by' => $user_id,
'parent_id' => $parent_id,
'recursive_parent_id' => $recursive_parent_id,
'name' => $name,
'text' => $rescursive ? $text : htmlspecialchars( $text ),
'date_start' => $date_start != '' ? "$date_start" : null,
@@ -689,6 +691,7 @@ class Tasks
{
$mdb -> update( 'tasks', [
'parent_id' => $parent_id,
'recursive_parent_id' => $recursive_parent_id,
'name' => $name,
'text' => htmlspecialchars( $text ),
'date_start' => $date_start != '' ? $date_start : null,
@@ -741,29 +744,29 @@ class Tasks
return true;
}
static public function task_first_id( $parent_id ) {
static public function task_first_id( $recursive_parent_id ) {
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 );
else
return $parent_id;
return $recursive_parent_id;
}
static public function task_delete_all( $task_id )
{
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 );
if ( $children_id )
self::task_delete_all( $children_id );
}
if ( $parent_id )
self::task_delete_all( $parent_id );
if ( $recursive_parent_id )
self::task_delete_all( $recursive_parent_id );
else
return false;
}
@@ -772,13 +775,13 @@ class Tasks
{
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 -> purgeByTaskId( $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;
}

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 ---' ];
if ( is_array( $this -> parent_tasks ) )
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();
?>
@@ -495,6 +511,8 @@ echo $grid -> draw();
<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">
var parent_tasks_meta = <?= json_encode( $parent_tasks_meta );?>;
$(document).ready(function ()
{
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() {
var task_id = parseInt( $( '#id' ).val(), 10 ) || 0;
var attachments_box = $( '.task-edit-attachments' );
@@ -676,6 +714,10 @@ echo $grid -> draw();
toggleRecursiveDetails();
});
$( '#parent_id' ).on( 'change select2:select', function() {
syncProjectAndClientFromParentTask();
});
refreshAttachmentsState();
var initial_task_id = parseInt( $( '#id' ).val(), 10 ) || 0;
if ( initial_task_id > 0 )