diff --git a/be/app/Console/Commands/SyncOffice365.php b/be/app/Console/Commands/SyncOffice365.php index f48e1f4..2d7a039 100644 --- a/be/app/Console/Commands/SyncOffice365.php +++ b/be/app/Console/Commands/SyncOffice365.php @@ -53,7 +53,7 @@ class SyncOffice365 extends Command return ! Project::where('code', $projectCode)->exists(); }); - $subtasks = array_filter($events, function ($event) { + $subtasks_from_office365 = array_filter($events, function ($event) { $projectCode = explode(' ', $event['subject'])[0]; // if project code exist in database, then it is subtask @@ -66,7 +66,7 @@ class SyncOffice365 extends Command return ! Blocker::where('office365_id', $event['id'])->exists() && ! $event['isCancelled']; }); - $newSubtasks = array_filter($subtasks, function ($event) { + $newSubtasks = array_filter($subtasks_from_office365, function ($event) { return ! SubTask::where('office365_id', $event['id'])->exists() && ! $event['isCancelled']; }); @@ -81,7 +81,15 @@ class SyncOffice365 extends Command return $blocker && $blocker->office365_change_key !== $event['changeKey']; }); + // get edited subtasks - the ones, that has different changeKey from our database + $editedSubtasks = array_filter($subtasks_from_office365, function ($event) { + $subtask = SubTask::where('office365_id', $event['id'])->first(); + + return $subtask && $subtask->office365_change_key !== $event['changeKey']; + }); + Office365Service::editBlockers($editedBlockers); + Office365Service::editSubtasks($editedSubtasks); // get all canceled blockers - the ones, that are in our database and in the response they have isCancelled => true $removedBlockers = Blocker::whereNotNull('office365_id')->get()->filter(function ($blocker) use ( @@ -97,6 +105,34 @@ class SyncOffice365 extends Command $blocker->delete(); }); + // get all canceled subtasks - the ones, that are in our database and in the response they have isCancelled => true + + $removedSubtasks = SubTask::where('is_finished', 0)->whereNotNull('office365_id')->get()->filter(function ( + $subtask + ) use ( + $subtasks_from_office365 + ) { + $event = collect($subtasks_from_office365)->firstWhere('id', $subtask->office365_id); + + return $event && $event['isCancelled']; + }); + + //dd($removedSubtasks); + + foreach ($removedSubtasks as $subtaskToDelete) { + // vymazanie timeblockov + foreach ($subtaskToDelete->timeblocks as $timeblock) { + $timeblock->delete(); + } + + // vymazanie timesheetov + foreach ($subtaskToDelete->timesheets as $timesheet) { + $timesheet->delete(); + } + + $subtaskToDelete->delete(); + } + dd('he'); } } diff --git a/be/app/Listeners/SendTrackedTimeReachingPriceQouteTimeNotification.php b/be/app/Listeners/SendTrackedTimeReachingPriceQouteTimeNotification.php new file mode 100644 index 0000000..0173d58 --- /dev/null +++ b/be/app/Listeners/SendTrackedTimeReachingPriceQouteTimeNotification.php @@ -0,0 +1,54 @@ +mastertask->tracked_time < $event->mastertask->pricequote_time * 0.8 || $event->mastertask->pricequote_time_notification_sent) { + return; + } + + $receiver = User::find($event->mastertask->created_by); + + // pripad ked bol user soft deletnuty + if ( ! $receiver) { + return; + } + + // only send if email wasnt sent before + if ($event->mastertask->pricequote_time_email_sent) { + return; + } + + Mail::to($receiver->email)->send(new \App\Mail\MasterTaskTrackedTimeReachingPricQouteTime($event->mastertask)); + + // updatnem tak, aby mi to nespustilo dalsi event - dostal by som email loop + $event->mastertask->withoutEvents(function () use ($event) { + $event->mastertask->update(['pricequote_time_notification_sent' => 1]); + });; + } +} diff --git a/be/app/Mail/MasterTaskTrackedTimeReachingPricQouteTime.php b/be/app/Mail/MasterTaskTrackedTimeReachingPricQouteTime.php new file mode 100644 index 0000000..eaa590d --- /dev/null +++ b/be/app/Mail/MasterTaskTrackedTimeReachingPricQouteTime.php @@ -0,0 +1,65 @@ +created_by); + + $this->mastertask = $mastertask; + $this->creator = ($creator) ? $creator->firstname . ' ' . $creator->lastname : ''; + } + + /** + * Get the message envelope. + * + * @return \Illuminate\Mail\Mailables\Envelope + */ + public function envelope() + { + return new Envelope( + subject: 'Master Task ' . $this->mastertask->project->code . ' - ' . $this->mastertask->title . ' ma natrackovaných viac ako 80% odhadovaných hodín', + ); + } + + /** + * Get the message content definition. + * + * @return \Illuminate\Mail\Mailables\Content + */ + public function content() + { + return new Content( + view: 'emails.task.master_task_reaching_pricequote_time', + ); + } + + /** + * Get the attachments for the message. + * + * @return array + */ + public function attachments() + { + return []; + } +} diff --git a/be/app/Models/MasterTask.php b/be/app/Models/MasterTask.php index ffcf3c4..ff97999 100644 --- a/be/app/Models/MasterTask.php +++ b/be/app/Models/MasterTask.php @@ -12,6 +12,12 @@ class MasterTask extends Model public $timestamps = true; public $guarded = ['id']; + public static function generateMasterTaskColor() + { + // return random value from array + return collect(['#E9FFE5', '#F6ECD3', '#FDE1FF', '#FFC9C9', '#DADEF8', '#EDFFFE', '#FFDED7'])->random(); + } + public function subtasks() { return $this->hasMany(SubTask::class); @@ -22,11 +28,6 @@ class MasterTask extends Model return $this->belongsTo(Project::class); } - public function attachments() - { - return $this->hasMany(Attachment::class); - } - public function team() { return $this->belongsTo(Team::class); @@ -38,12 +39,12 @@ class MasterTask extends Model $users = $this->subtasks->pluck('user')->unique(); // ak nie je uvedeny ziaden team, tak sa vratia vsetci useri - if (!$this->team){ + if ( ! $this->team) { return $users; } return $users->filter(function ($user) { - return !in_array($this->team->id, $user->teams->pluck('id')->unique()->toArray()); + return ! in_array($this->team->id, $user->teams->pluck('id')->unique()->toArray()); }); } @@ -53,15 +54,9 @@ class MasterTask extends Model return $this->subtasks->pluck('user')->unique(); } - public static function generateMasterTaskColor() - { - // return random value from array - return collect(['#E9FFE5', '#F6ECD3', '#FDE1FF', '#FFC9C9', '#DADEF8', '#EDFFFE', '#FFDED7'])->random(); - } - public function deleteAttachments() { - foreach ( $this->attachments as $attachment ) { + foreach ($this->attachments as $attachment) { $attachment->deleteFromS3(); } @@ -70,6 +65,11 @@ class MasterTask extends Model return true; } + public function attachments() + { + return $this->hasMany(Attachment::class); + } + public function recalculateEstimatedTime() { $this->estimated_time = $this->subtasks->sum('estimated_time'); @@ -80,12 +80,11 @@ class MasterTask extends Model { $trackedTime = 0.0; - foreach ( $this->subtasks as $subtask ) { + foreach ($this->subtasks as $subtask) { $trackedTime += $subtask->timesheets->sum('time'); } $this->tracked_time = $trackedTime; $this->save(); - } } diff --git a/be/app/Providers/EventServiceProvider.php b/be/app/Providers/EventServiceProvider.php index 527e7fe..585fcba 100644 --- a/be/app/Providers/EventServiceProvider.php +++ b/be/app/Providers/EventServiceProvider.php @@ -12,6 +12,7 @@ use App\Listeners\SendEditedMasterTaskNotification; use App\Listeners\SendEditedSubTaskNotification; use App\Listeners\SendFinishedTimeBlockNotification; use App\Listeners\SendNewSubTaskEmailNotification; +use App\Listeners\SendTrackedTimeReachingPriceQouteTimeNotification; use App\Models\MasterTask; use App\Models\SubTask; use App\Models\Timeblock; @@ -21,7 +22,6 @@ use App\Observers\TimeBlockObserver; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; -use Illuminate\Support\Facades\Event; class EventServiceProvider extends ServiceProvider { @@ -31,21 +31,22 @@ class EventServiceProvider extends ServiceProvider * @var array> */ protected $listen = [ - Registered::class => [ + Registered::class => [ SendEmailVerificationNotification::class, ], - SubTaskCreated::class => [ + SubTaskCreated::class => [ SendNewSubTaskEmailNotification::class, //LogEvent::class, ], - SubTaskDeleted::class => [ + SubTaskDeleted::class => [ SendDeletedSubTaskNotification::class ], - SubTaskEdited::class => [ + SubTaskEdited::class => [ SendEditedSubTaskNotification::class ], - MasterTaskEdited::class => [ - SendEditedMasterTaskNotification::class + MasterTaskEdited::class => [ + SendEditedMasterTaskNotification::class, + SendTrackedTimeReachingPriceQouteTimeNotification::class ], TimeBlockFinished::class => [ SendFinishedTimeBlockNotification::class @@ -59,9 +60,9 @@ class EventServiceProvider extends ServiceProvider */ public function boot() { - MasterTask::observe( MasterTaskObserver::class); - SubTask::observe( SubTaskObserver::class); - Timeblock::observe( TimeBlockObserver::class); + MasterTask::observe(MasterTaskObserver::class); + SubTask::observe(SubTaskObserver::class); + Timeblock::observe(TimeBlockObserver::class); } /** diff --git a/be/app/Services/Office365/Office365Service.php b/be/app/Services/Office365/Office365Service.php index 7062131..a939828 100644 --- a/be/app/Services/Office365/Office365Service.php +++ b/be/app/Services/Office365/Office365Service.php @@ -5,6 +5,7 @@ namespace App\Services\Office365; use App\Models\Blocker; use App\Models\MasterTask; use App\Models\Project; +use App\Models\SubTask; use App\Models\User; use Carbon\Carbon; use Illuminate\Support\Facades\Http; @@ -125,11 +126,11 @@ class Office365Service continue; } - self::addSubtask($newSubtask, $mastertask); + self::addSubtaskToAllInvited($newSubtask, $mastertask); } } - private static function addSubtask(mixed $newSubtask, mixed $mastertask) + private static function addSubtaskToAllInvited(mixed $newSubtask, mixed $mastertask) { $attendees = $newSubtask['attendees']; @@ -211,4 +212,113 @@ class Office365Service // manualne musim trignernut tuto operaciu, lebo cez Observer sa to nespusti - ten je nastaveny len na update subtasku $subtasks->first()->mastertask->recalculateTrackedTime(); } + + public static function editSubtasks(mixed $editedSubtasks) + { + foreach ($editedSubtasks as $editedSubtask) { + self::editSubtask($editedSubtask); + } + } + + private static function editSubtask(mixed $editedSubtask) + { + $attendees = $editedSubtask['attendees']; + + // vrati subtasky pre userov, ktori sa uz nenachadzaju v evente, ale v nasej databaze tento subtask maju. ale iba v pripade, ak ich subtasky este nie su ukoncene (is_finished != 1) + + // TODO - zvlast do funkcie + $subtasksToDelete = SubTask::where('is_finished', 0)->where('office365_id', + $editedSubtask['id'])->get()->filter(function ($subtask) use ( + $attendees + ) { + $event = collect($attendees)->firstWhere('emailAddress.address', $subtask->user->email); + + return ! $event; + }); + + foreach ($subtasksToDelete as $subtaskToDelete) { + // vymazanie timeblockov + foreach ($subtaskToDelete->timeblocks as $timeblock) { + $timeblock->delete(); + } + + // vymazanie timesheetov + foreach ($subtaskToDelete->timesheets as $timesheet) { + $timesheet->delete(); + } + + $subtaskToDelete->delete(); + } + + // novym ucastnikom sa klasicky vytvori subtask ako v pripade noveho subtasku. pre 1 subtask existuje iba 1 user. + // TODO - zvlast do funkcie + $newAttendees = array_filter($attendees, function ($attendee) use ($editedSubtask) { + $idsOfAttendees = SubTask::where('office365_id', $editedSubtask['id'])->get()->pluck('user_id')->toArray(); + + $idOfUserFromOffice365 = User::where('email', $attendee['emailAddress']['address'])->first(); + + // if user doesnt exist in our database, we skip it + if ( ! $idOfUserFromOffice365) { + return false; + } + + return ! in_array($idOfUserFromOffice365->id, $idsOfAttendees); + }); + + // nakopiruj subtask tak, ze bude obsahovat iba newAttendees + $editedSubtaskWithOnlyNewAttendees = $editedSubtask; + + $editedSubtaskWithOnlyNewAttendees['attendees'] = $newAttendees; + + $project = Project::where('code', explode(' ', $editedSubtask['subject'])[0])->first(); + + // ak existuje viac master taskov, pouzijeme posledny + $mastertask = MasterTask::where('project_id', $project->id)->latest()->first(); + + self::addSubtaskToAllInvited($editedSubtaskWithOnlyNewAttendees, $mastertask); + + // existujucim ucastnikom sa updatne subtask + foreach ($attendees as $attendee) { + $idOfUserFromOffice365 = User::where('email', $attendee['emailAddress']['address'])->first(); + + // if user doesnt exist in our database, we skip it + if ( ! $idOfUserFromOffice365) { + continue; + } + + $subtask = SubTask::where('office365_id', $editedSubtask['id'])->where('user_id', + $idOfUserFromOffice365->id)->first(); + + if ( ! $subtask) { + continue; + } + + $subtask->update([ + 'office365_change_key' => $editedSubtask['changeKey'], + 'title' => substr($editedSubtask['subject'], strpos($editedSubtask['subject'], ' ') + 1), + 'estimated_time' => round((strtotime($editedSubtask['end']['dateTime']) - strtotime($editedSubtask['start']['dateTime'])) / 3600, + 1), + 'description' => $editedSubtask['body']['content'], + 'deadline' => $editedSubtask['end']['dateTime'], + 'office365_location' => $editedSubtask['location']['displayName'], + ]); + + $oldTimeblock = $subtask->timeblocks->first(); + + // ak sa zmenilo trvanie alebo casy subtasku, tak sa updatne aj timeblock + if ($oldTimeblock['start'] != $editedSubtask['start']['dateTime'] || $oldTimeblock['end'] != $editedSubtask['end']['dateTime']) { + $subtask->syncTimeblocks([ + [ + 'sub_task_id' => $subtask->id, + 'start' => $editedSubtask['start']['dateTime'], + 'end' => $editedSubtask['end']['dateTime'], + 'is_finished' => 0, + ] + ]); + } + } + + // manualne musim trignernut tuto operaciu, lebo cez Observer sa to nespusti - ten je nastaveny len na update subtasku + Subtask::where('office365_id', $editedSubtask['id'])->first()->mastertask->recalculateEstimatedTime(); + } } diff --git a/be/database/migrations/2025_03_18_101925_add_price_qoute_notification_info.php b/be/database/migrations/2025_03_18_101925_add_price_qoute_notification_info.php new file mode 100644 index 0000000..02288fa --- /dev/null +++ b/be/database/migrations/2025_03_18_101925_add_price_qoute_notification_info.php @@ -0,0 +1,29 @@ +tinyInteger('pricequote_time_notification_sent')->after('color')->default(0); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // + } +}; diff --git a/be/resources/views/emails/task/master_task_reaching_pricequote_time.blade.php b/be/resources/views/emails/task/master_task_reaching_pricequote_time.blade.php new file mode 100644 index 0000000..c43c38f --- /dev/null +++ b/be/resources/views/emails/task/master_task_reaching_pricequote_time.blade.php @@ -0,0 +1,10 @@ +Task založil: {{$creator}}
+Job number: {{$mastertask->project->code}}
+Názov Tasku: {{$mastertask->title}}
+Deadline do: {{$mastertask->deadline}}
+Pôvodný odhad: {{$mastertask->pricequote_time}}
+Natrackované hodiny: {{$mastertask->tracked_time}}
+Odkaz na Task: {{ url( env( 'APP_URL_FE' ) . '/#/app/dashboard/projects?mastertask=' . $mastertask->id) }}
+ + +