PluginProbe
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration / 1.21
FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration v1.21
2.0.15 2.0.12 2.0.10 2.0.4 2.0.1 2.0.0 1.95.3 1.95.2 1.95 1.91.6 trunk 1.11 1.12 1.13 1.20 1.21 1.22 1.23 1.30 1.31 1.32 1.35 1.40 1.41 1.45 All 41 releases
fluent-boards / app / Services / TaskService.php

TaskService.php in FluentBoards – Project Management, Task Management, Goal Tracking, Kanban Board, and, Team Collaboration 1.21, at app/Services/TaskService.php

737 lines 24.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace FluentBoards\App\Services;
4
5 use FluentBoards\App\Models\NotificationUser;
6 use FluentBoards\App\Services\Constant;
7 use FluentBoardsPro\App\Models\Attachment;
8 use FluentBoards\App\Models\Stage;
9 use FluentBoards\App\Models\Task;
10 use FluentBoards\App\Models\Board;
11 use FluentBoards\App\Models\TaskMeta;
12 use FluentBoards\App\Models\Activity;
13 use FluentBoards\App\Models\BoardTerm;
14 use FluentBoards\Framework\Support\Arr;
15 use FluentRoadmap\App\Models\IdeaReaction;
16
17 class TaskService
18 {
19 public function createTask($data, $boardId)
20 {
21 $board = Board::select('id', 'type')->find($boardId);
22
23 if (!$board) {
24 throw new \Exception(__("Board doesn't exists", 'fluent-boards'));
25 }
26
27 $stage = Stage::find($data['stage_id']);
28 if (!$stage) {
29 throw new \Exception(__("Stage doesn't exists", 'fluent-boards'));
30 }
31
32 $data['status'] = $stage->defaultTaskStatus();
33
34 if ($board->type == 'roadmap') {
35 $current_user = wp_get_current_user();
36 $settingData = array(
37 'integration_type' => 'feature',
38 'logo' => '',
39 'author' => [
40 'email' => $current_user->user_email // email of who posted this feature
41 ],
42 );
43 $data['settings'] = $settingData;
44 $data['type'] = 'roadmap';
45 }
46
47 $providerPosition = Arr::get($data, 'position');
48
49 $data['position'] = $this->getLastPositionOfTasks($stage->id);
50
51 $data['board_id'] = $boardId;
52
53 $data = array_filter($data);
54 $task = (new Task())->createTask($data);
55 if (isset($data['is_template']) && $data['is_template'] == 'yes') {
56 $task->updateMeta(Constant::IS_TASK_TEMPLATE, $data['is_template']);
57 }
58
59 if ($providerPosition) {
60 $task->moveToNewPosition($providerPosition);
61 }
62
63 // $this->taskCreatedAction($task);
64 $this->loadWithRelations($task, ['assignees', 'labels', 'board']);
65
66 return $task;
67 }
68
69 public function loadWithRelations($task, $relations)
70 {
71 if (!is_array($relations)) {
72 return $task;
73 }
74 $task->load($relations); // $relations = ['assignees', 'board'] in this case
75 $task->isOverdue = $task->isOverdue();
76
77 return $task;
78 }
79
80 public function getTasksForBoards($filters = ['overdue', 'upcoming'], $limit = 5, $task_ids = [])
81 {
82 $overDue = $this->getTasksForBoardsByCategory('overdue', $limit, $task_ids);
83 $completed = $this->getTasksForBoardsByCategory('completed', $limit, $task_ids);
84 $upcoming = $this->getTasksForBoardsByCategory('upcoming', $limit, $task_ids);
85
86
87 return [
88 'overdue' => $overDue ?? [],
89 'upcoming' => $upcoming ?? [],
90 'completed' => $completed ?? []
91 ];
92 }
93
94 public function getTasksForBoardsByCategory($category, $limit, $taskIds)
95 {
96 unset($taskQuery);
97 $taskQuery = Task::whereIn('id', $taskIds)
98 ->with(['assignees', 'board', 'stage'])
99 ->whereNull('archived_at')
100 ->where('parent_id', null)
101 ->orderBy('due_at', 'ASC');
102
103 if ('overdue' == $category) {
104 $taskQuery->overdue();
105 } elseif ('upcoming' == $category) {
106 $taskQuery->upcoming();
107 } elseif ('upcoming_no_duedate' == $category) {
108 $taskQuery->whereNull('due_at');
109 } elseif ('completed' == $category) {
110 $taskQuery->where('status', 'closed');
111 } else {
112 return [];
113 }
114
115 $tasks = $taskQuery->take($limit)->get();
116
117 return $tasks->toArray();
118 }
119
120 /*
121 * TODO: Refactor this function - For me.
122 */
123 public function updateTaskProperty($col, $value, $task)
124 {
125 $oldTask = clone $task; // normal assigning won't work here. because objects are passed by reference in php
126 $validColumns = [
127 'board_id',
128 'task_type',
129 'reminder_type',
130 'remind_at',
131 'log_minutes',
132 'settings'
133 ];
134
135 if (in_array($col, $validColumns) && $task->{$col} != $value) {
136 $task->{$col} = $value;
137 $task->save();
138 // do_action('fluent_boards/task_prop_changed', $col, $task, $oldTask);
139 } elseif ('assignees' == $col) {
140 if (is_array($value)) {
141 foreach ($value as $id) {
142 $this->updateAssignee($id, $task);
143 }
144 } else {
145 $this->updateAssignee($value, $task);
146 }
147
148 } elseif ('crm_contact_id' == $col) {
149 $this->updateAssociate($value, $task);
150 } elseif ('archived_at' == $col) {
151 $this->updateArchive($value, $task);
152 } elseif ('status' == $col) {
153 $this->updateStatus($value, $task);
154 } elseif ('parent_id' == $col) {
155 $this->updateParent($value, $task);
156 } elseif ('title' == $col) {
157 $this->updateTitle($col, $value, $task, $oldTask);
158 } elseif ('description' == $col) {
159 $this->updateDescription($col, $value, $task, $oldTask);
160 } elseif ($col == 'due_at') {
161 $this->updateDueDate($value, $task);
162 } elseif ($col == 'started_at') {
163 $this->updateStartedDate($value, $task);
164 } elseif ($col == 'priority') {
165 $this->updatePriority($value, $task);
166 } elseif ($col == 'is_watching') {
167 $this->updateObservationOfCurrentUser($value, $task);
168 } elseif ($col == 'last_completed_at') {
169 $isClosed = $value == 'true' || $value === true;
170 if ($isClosed) {
171 $task = $task->close();
172 } else {
173 $task = $task->reopen();
174 }
175 $task->save();
176 } elseif ($col == 'attachment_count') {
177 $settings = $task->settings;
178 $settings['attachment_count'] = $task->attachments()->count();
179 $task->settings = $settings;
180 $task->save();
181 } elseif ($col == 'subtask_count') {
182 $settings = $task->settings;
183 $subtasksCount = Task::where('parent_id', $task->id)->count();
184 $settings['subtask_count'] = $subtasksCount;
185 $task->settings = $settings;
186 $task->save();
187 } elseif ($col == 'is_template') {
188 if (defined('FLUENT_BOARDS_PRO')) {
189 $task->updateMeta(Constant::IS_TASK_TEMPLATE, $value);
190 }
191 }
192
193 return $task;
194 }
195
196 public function updateAssignee($payloadAssigneeId, $task)
197 {
198 $operation = $task->addOrRemoveAssignee($payloadAssigneeId);
199 $task->load('assignees');
200 $task->updated_at = current_time('mysql');
201
202 $task->save();
203
204 if ($operation == 'added') {
205 if ((new NotificationService())->checkIfEmailEnable($payloadAssigneeId, Constant::BOARD_EMAIL_TASK_ASSIGN, $task->board_id)) {
206 $this->sendMailAfterTaskModify('add_assignee', $payloadAssigneeId, $task->id);
207 }
208 // $assigneeIdsToSendEmail = $this->filterAssigneeToSendEmail($task, $idArray, Constant::BOARD_EMAIL_TASK_ASSIGN);
209 // $this->sendMailAfterAddAssignees($assigneeIdsToSendEmail, $task->id);
210 do_action('fluent_boards/task_assignee_changed', $task, $payloadAssigneeId, $operation);
211 } else {
212 if ((new NotificationService())->checkIfEmailEnable($payloadAssigneeId, Constant::BOARD_EMAIL_REMOVE_FROM_TASK, $task->board_id)) {
213 $this->sendMailAfterTaskModify('remove_assignee', $payloadAssigneeId, $task->id);
214 }
215 do_action('fluent_boards/task_assignee_changed', $task, $payloadAssigneeId, $operation);
216 }
217
218 }
219
220 // public function filterAssigneeToSendEmail($task, $newAssigneeIds, $purpose)
221 // {
222 // $toSendEmail = array();
223 // foreach ($newAssigneeIds as $assigneeId) {
224 // if ((new NotificationService())->checkIfEmailEnabled($task->board_id, $assigneeId, $purpose)) {
225 // $toSendEmail[] = $assigneeId;
226 // }
227 // }
228 // return $toSendEmail;
229 // }
230
231 // public function defaultWatchingTaskByNewUsers($task, $newIds)
232 // {
233 // foreach ($newIds as $newId) {
234 // if (!$task->watchers->contains($newId)) {
235 // $task->watchers()->attach(
236 // $newId,
237 // [
238 // 'object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH,
239 // ]
240 // );
241 // }
242 // }
243 // }
244
245 // public function checkIfAnybodyRemovedFromTask($newAssigneeIds, $oldAssigneeIds, $task)
246 // {
247 // $removedAssignees = array_diff($oldAssigneeIds, $newAssigneeIds);
248 // $this->sendMailAfterTaskModify('removed_from_task', $removedAssignees, $task->id);
249 // dd($removedAssignees);
250 // }
251
252 private function updateAssociate($value, $task)
253 {
254 // if task has no crm contact and got value null then return current task
255 if (($task->crm_contact_id == null || $task->crm_contact_id == 0) && $value == null) {
256 return $task;
257 }
258
259 $oldAssociateId = $task->crm_contact_id;
260 $task->crm_contact_id = $value;
261 $task->save();
262 $task->contact = Task::lead_contact($task->crm_contact_id);
263 do_action('fluent_boards/contact_added_to_task', $task);
264 do_action('fluent_boards/associate_user_add_change_remove_activity', $oldAssociateId, $task->crm_contact_id, $task->id);
265 }
266
267 private function updateArchive($value, $task)
268 {
269 if ($value != null) {
270 $task->position = 0;
271 } else {
272 $task->moveToNewPosition(1);
273 }
274 $task->archived_at = $value == null ? null : current_time('mysql');
275 $task->save();
276 do_action('fluent_boards/board_task_archived', $task);
277 $wathersToSendEmail = (new NotificationService())->filterAssigneeToSendEmail($task->id, Constant::BOARD_EMAIL_TASK_ARCHIVE);
278 $this->sendMailAfterTaskModify('task_archived', $wathersToSendEmail, $task->id);
279 }
280
281 private function updateStatus($value, $task)
282 {
283 if ($value == 'closed') {
284 $task = $task->close();
285 } else {
286 $task = $task->reopen();
287 }
288
289 do_action('fluent_boards/task_completed_activity', $task, $value);
290 }
291
292 private function updateParent($value, $task)
293 {
294 $task->parent_id = $value;
295 $task->save();
296 }
297
298 private function updateTitle($col, $value, $task, $oldTask)
299 {
300 $task->title = $value;
301 $task->save();
302 do_action('fluent_boards/task_content_updated', $task, $col, $oldTask);
303 }
304
305 private function updateDescription($col, $value, $task, $oldTask)
306 {
307 $task->description = $value;
308 $task->save();
309 do_action('fluent_boards/task_content_updated', $task, $col, $oldTask);
310 }
311
312 private function updateDueDate($value, $task)
313 {
314 $oldValue = $task->due_at;
315 $value = $this->filterNullDate($value);
316 $task->due_at = $value;
317 $task->save();
318
319 $task = $task->reopen();
320
321 do_action('fluent_boards/task_date_changed', $task, $oldValue, 'Due Date');
322
323 $wathersToSendEmail = (new NotificationService())->filterAssigneeToSendEmail($task->id, Constant::BOARD_EMAIL_DUE_DATE_CHANGE);
324 $this->sendMailAfterTaskModify('due_date_update', $wathersToSendEmail, $task->id);
325 }
326
327 private function updateStartedDate($value, $task)
328 {
329 $oldValue = $task->started_at;
330 $value = $this->filterNullDate($value);
331 $task->started_at = $value;
332 $task->save();
333
334 do_action('fluent_boards/task_date_changed', $task, $oldValue, 'Start Date');
335
336 }
337
338 private function updatePriority($value, $task)
339 {
340 $oldPriority = $task->priority;
341 $task->priority = $value;
342 $task->save();
343 do_action('fluent_boards/task_priority_changed', $task, $oldPriority);
344 }
345
346 public function updateObservationOfCurrentUser($value, $task)
347 {
348 $currentUserId = get_current_user_id();
349
350 if ($value == 'stop') {
351 $task->watchers()->detach($currentUserId);
352 } else {
353 $task->watchers()->syncWithoutDetaching([$currentUserId => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
354 }
355 $task->updated_at = current_time('mysql');
356 $task->save();
357
358 if ($value == 'stop') {
359 $task->is_watching = false;
360 } else {
361 $task->is_watching = true;
362 }
363 }
364
365 public function taskCoverPhotoUpdate($taskId, $imagePath)
366 {
367 $task = Task::find($taskId);
368 if (!$task) {
369 return null;
370 }
371
372 $settings = unserialize($task->settings);
373
374 $settings['logo'] = $imagePath;
375 $task->settings = serialize($settings);
376 $task->save();
377
378 return $task;
379 }
380
381 public function taskStatusUpdate($taskId, $integrationType)
382 {
383 $task = Task::find($taskId);
384 if (!$task) {
385 return null;
386 }
387
388 $settings = $task->settings;
389 $settings['integration_type'] = $integrationType;
390 $task->settings = serialize($settings);
391 $task->save();
392
393 return $task;
394 }
395
396 public function assignYourselfInTask($boardId, $taskId)
397 {
398 $task = Task::find($taskId);
399 $authUserId = get_current_user_id();
400
401 $boardService = new BoardService();
402 if (!$boardService->isAlreadyMember($boardId, $authUserId)) {
403 $boardService->addMembersInBoard($boardId, $authUserId);
404 }
405
406 $task->addOrRemoveAssignee($authUserId);
407 // when user assign himself then he will be watching that task
408 $task->watchers()->syncWithoutDetaching([$authUserId => ['object_type' => Constant::OBJECT_TYPE_USER_TASK_WATCH]]);
409
410 $task->load('assignees');
411
412 return $task;
413 }
414
415 public function detachYourselfFromTask($boardId, $taskId)
416 {
417 $task = Task::find($taskId);
418 $task->addOrRemoveAssignee(get_current_user_id());
419 $task->load('assignees');
420
421 return $task;
422 }
423
424 public function deleteTask($task)
425 {
426 $deleted = $task->delete();
427
428 if ($deleted) {
429
430 //task assignees watchers removed
431 $task->watchers()->detach();
432 $task->assignees()->detach();
433
434 //removing all task related notifications
435 $notificationIds = $task->notifications->pluck('id');
436 $task->notifications()->delete();
437 NotificationUser::whereIn('notification_id', $notificationIds)->delete();
438
439 //task labels removed
440 $task->labels()->detach();
441
442 //task custom field value
443 $task->customFields()->detach();
444
445 do_action('fluent_boards/task_deleted', $task);
446 TaskMeta::where('task_id', $task->id)->delete();
447 }
448 }
449
450 public function filterNullDate($date)
451 {
452 if ('0000-00-00 00:00:00' == $date || false === strtotime($date)) {
453 return null;
454 }
455 return $date;
456 }
457
458 // this is invoked when task is moved to another board
459
460 /**
461 * @throws \Exception
462 */
463 public function changeBoardByTask($task, $targetBoardId)
464 {
465 if ($task->board_id == $targetBoardId) {
466 return $task;
467 }
468
469 $oldBoard = Board::find($task->board_id);
470
471 $newBoard = Board::find($targetBoardId);
472 if (!$newBoard) {
473 throw new \Exception('Invalid board id', 400);
474 }
475 $task->board_id = $targetBoardId;
476 $task->save();
477 //delete labels of that task because labels have board dependencies
478 $task->labels()->detach();
479
480 do_action('fluent_boards/task_moved_from_board', $task, $oldBoard, $newBoard);
481
482 return $task;
483 }
484
485
486 public function getIdeaVoteStatistics($taskId)
487 {
488 $reactionTypes = [
489 [
490 'label' => 'Upvote',
491 'type' => 'upvote'
492 ],
493 [
494 'label' => 'Downvote',
495 'type' => 'downvote'
496 ]
497 ];
498
499 $reactionCounts = [];
500
501 foreach ($reactionTypes as $reactionType) {
502 $count = IdeaReaction::where('object_id', $taskId)
503 ->where('object_type', 'idea')
504 ->where('type', $reactionType['type'])
505 ->count();
506
507 $reactionCounts[] = [
508 'label' => $reactionType['label'],
509 'type' => $reactionType['type'],
510 'count' => $count
511 ];
512 }
513
514 return $reactionCounts;
515 }
516
517
518 /**
519 * Summary of getArchivedOrCompletedTasks
520 * this function will return completd tasks or archived tasks based on users input and also can search by name
521 * @param mixed $data
522 * @param mixed $taskType
523 * @return mixed
524 * @throws \Exception
525 */
526 public function getArchivedTasks($data, $boardId)
527 {
528 $per_page = isset($data['per_page']) ? $data['per_page'] : 25;
529 $page = isset($data['page']) ? $data['page'] : 1;
530 $tasksQuery = Task::where('board_id', $boardId)->whereNotNull('archived_at');
531 if (isset($data['searchInput'])) {
532 $tasksQuery = $tasksQuery->where('title', 'LIKE', '%' . $data['searchInput'] . '%');
533 }
534
535 // if board_id is not passed then throw an exception
536 if (!$boardId) {
537 throw new \Exception('Board id is required', 'fluent-boards');
538 }
539
540 return $tasksQuery->orderBy('created_at', 'DESC')->with('assignees')->paginate($per_page, ['*'], 'page', $page);
541 }
542
543 public function sendMailAfterTaskModify($column, $assigneeIds, $taskId)
544 {
545 $current_user_id = get_current_user_id();
546 /* this will run in background as soon as possible */
547 /* sending Model or Model Instance won't work here */
548
549 as_enqueue_async_action('fluent_boards/one_time_schedule_send_email_for_'.$column, [$taskId, $assigneeIds, $current_user_id], 'fluent-boards');
550 }
551
552 public function getStageByTask($task_id)
553 {
554 $task = Task::find($task_id);
555 return $task->stage;
556 }
557
558 public function moveTaskToNextStage($task_id)
559 {
560 $task = Task::findOrFail($task_id);
561
562 $oldStage = $task->stage;
563
564 $nextStage = Stage::where('board_id', $task->board_id)
565 ->where('position', '>', $oldStage->position)
566 ->orderBy('position', 'ASC')
567 ->first();
568
569 if (!$nextStage) {
570 return $task;
571 }
572
573 if ($nextStage->defaultTaskStatus() == 'closed' && $task->status != 'closed') {
574 $task->status = 'closed';
575 if (!$task->last_completed_at) {
576 $task->last_completed_at = current_time('mysql');
577 }
578 }
579
580 $task->stage_id = $nextStage->id;
581 $task->save();
582
583 $task->load(['board', 'stage', 'attachments']);
584
585 $task = $this->loadNextStage($task);
586
587 return $task;
588 }
589
590 public function loadNextStage($task)
591 {
592 $stage = $task->stage;
593 $nextStage = Stage::where('board_id', $task->board_id)
594 ->where('position', '>', $stage->position)
595 ->orderBy('position', 'ASC')
596 ->first();
597
598 $task->nextStage = $nextStage ? $nextStage->title : null;
599 return $task;
600 }
601
602 public function getActivities($taskId, $perPage, $filter = 'newest')
603 {
604 $activityQuery = Activity::where('object_id', $taskId)
605 ->where('object_type', Constant::ACTIVITY_TASK);
606 if ($filter == 'newest') {
607 $activityQuery = $activityQuery->latest();
608 } else if ($filter == 'oldest') {
609 $activityQuery = $activityQuery->oldest();
610 }
611 return $activityQuery->with('user')->paginate($perPage);
612 }
613
614 public function getLastOneMinuteUpdatedTasks($boardId, $lastUpdated = null)
615 {
616 if (!$lastUpdated) {
617 $lastUpdated = gmdate('Y-m-d H:i:s', current_time('timestamp') - 60);
618 }
619
620 $tasks = Task::query()
621 ->where([
622 'board_id' => $boardId,
623 'parent_id' => null,
624 ])
625 ->where('updated_at', '>', $lastUpdated)
626 ->with(['assignees', 'labels', 'watchers'])
627 ->orderBy('due_at', 'ASC')
628 ->get();
629
630 foreach ($tasks as $task) {
631 $task->isOverdue = $task->isOverdue();
632 $task->isUpcoming = $task->upcoming();
633 $task->is_watching = $task->isWatching();
634 $task->contact = Task::lead_contact($task->crm_contact_id);
635 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
636 $task->watchers = Helper::sanitizeUserCollections($task->watchers);
637 }
638 return $tasks;
639 }
640
641 public function getLastPositionOfTasks($stage_id)
642 {
643 $lastPosition = Task::query()
644 ->where('stage_id', $stage_id)
645 ->where('parent_id', null)
646 ->whereNull('archived_at')
647 ->orderBy('position', 'desc')
648 ->pluck('position')
649 ->first();
650
651 return $lastPosition + 1;
652 }
653
654 public function getAssociatedTasks($associatedId)
655 {
656 $tasks = Task::query()
657 ->where('crm_contact_id', $associatedId)
658 ->with(['board', 'stage', 'assignees', 'labels', 'watchers',])
659 ->orderBy('due_at', 'ASC')
660 ->get();
661
662 foreach ($tasks as $task) {
663 $task->isOverdue = $task->isOverdue();
664 $task->isUpcoming = $task->upcoming();
665 $task->contact = Task::lead_contact($task->crm_contact_id);
666 $task->is_watching = $task->isWatching();
667
668 $task->assignees = Helper::sanitizeUserCollections($task->assignees);
669 $task->watchers = Helper::sanitizeUserCollections($task->watchers);
670
671 $subTasks = Task::query()
672 ->where('parent_id', $task->id)
673 ->with(['assignees'])
674 ->whereNull('archived_at')
675 ->orderBy('position', 'ASC')
676 ->get();
677
678 foreach ($subTasks as $subTask) {
679 $subTask->assignees = Helper::sanitizeUserCollections($subTask->assignees);
680 }
681
682 $task->subtasks = $subTasks;
683 }
684
685 return $tasks;
686 }
687
688 public function copyTasks($boardId, $stageMap, $newBoard)
689 {
690 $allActiveTasks = Task::where('board_id', $boardId)->whereNull('archived_at')->get();
691 $taskMap = [];
692 foreach ($allActiveTasks as $task) {
693 $newTask = array();
694 $newTask['title'] = $task->title;
695 $newTask['parent_id'] = $task->parent_id ? $taskMap[$task->parent_id] : null;
696 $newTask['description'] = $task->description;
697 $newTask['board_id'] = $newBoard->id;
698 $newTask['stage_id'] = $stageMap[$task->stage_id];
699 $newTask['status'] = $task->status;
700 $newTask['priority'] = $task->priority;
701 $newTask['position'] = $task->position;
702 $newTask['due_at'] = $task->due_at;
703 $newTask = Task::create($newTask);
704 if(!$task->parent_id){
705 $taskMap[$task['id']] = $newTask->id;
706 //duplicate labels to task
707 $labelIds = $task->labels->pluck('id');
708 if($labelIds){
709 $newTask->labels()->attach($labelIds, [
710 'object_type' => Constant::OBJECT_TYPE_TASK_LABEL
711 ]);
712 }
713 } else {
714 $this->subtaskCountUpdate($newTask->parent_id);
715 }
716 }
717
718 //initiate task count
719 $totalTasks = sizeof($allActiveTasks);
720 $board = Board::findOrFail($newBoard->id);
721 $settings = [];
722 $settings['tasks_count'] = $totalTasks;
723 $board->settings = $settings;
724 $board->save();
725 }
726
727 private function subtaskCountUpdate($taskId){
728 $parentTask = Task::findOrFail($taskId);
729 $settings = $parentTask->settings;
730 $settings['subtask_count'] = (int)($settings['subtask_count'] ?? 0) + 1;
731 $parentTask->settings = $settings;
732 $parentTask->save();
733 }
734
735
736 }
737