diff --git a/app/.env.development b/app/.env.development index 2002315..24f43d7 100644 --- a/app/.env.development +++ b/app/.env.development @@ -1 +1 @@ -VUE_APP_WAXIOS_URL=https://taskmanager.internal.studiotem.com/api/ \ No newline at end of file +VUE_APP_WAXIOS_URL=http://127.0.0.1:8001/api/ \ No newline at end of file diff --git a/app/.env.production b/app/.env.production index e1febcc..2002315 100644 --- a/app/.env.production +++ b/app/.env.production @@ -1 +1 @@ -VUE_APP_WAXIOS_URL=https://tm.jandl.sk/api/ \ No newline at end of file +VUE_APP_WAXIOS_URL=https://taskmanager.internal.studiotem.com/api/ \ No newline at end of file diff --git a/be/app/Console/Commands/SyncOffice365.php b/be/app/Console/Commands/SyncOffice365.php index 2d7a039..bcc827e 100644 --- a/be/app/Console/Commands/SyncOffice365.php +++ b/be/app/Console/Commands/SyncOffice365.php @@ -2,9 +2,6 @@ namespace App\Console\Commands; -use App\Models\Blocker; -use App\Models\Project; -use App\Models\SubTask; use App\Services\Office365\Office365Service; use Illuminate\Console\Command; @@ -32,9 +29,6 @@ class SyncOffice365 extends Command public function handle() { $events = Office365Service::getCalendarEvents(); - - // TODO: asi prerobit na CreateAndUpdate - // determine if event is blocker or subtask. if the first word in event name is project code, then it is subtask. otherwise it is blocker // but first, remove "Canceled: " from the event name in case of canceled event @@ -46,93 +40,17 @@ class SyncOffice365 extends Command return $event; }, $events); - $blockers_from_office365 = array_filter($events, function ($event) { - $projectCode = explode(' ', $event['subject'])[0]; + $blockers_from_office365 = Office365Service::getBlockers_from_office_365_events($events); + $subtasks_from_office365 = Office365Service::getSubtasks_from_office_365_events($events); - // if project code doesnt exist in database, then it is blocker - return ! Project::where('code', $projectCode)->exists(); - }); + Office365Service::addBlockers($blockers_from_office365); + Office365Service::editBlockers($blockers_from_office365); + Office365Service::removeBlockers($blockers_from_office365); - $subtasks_from_office365 = array_filter($events, function ($event) { - $projectCode = explode(' ', $event['subject'])[0]; - - // if project code exist in database, then it is subtask - return Project::where('code', $projectCode)->exists(); - }); - - // get only new events - the ones that doesnt have any office365_id in blockers table and only events that doesnt have isCancelled => true - - $newBlockers = array_filter($blockers_from_office365, function ($event) { - return ! Blocker::where('office365_id', $event['id'])->exists() && ! $event['isCancelled']; - }); - - $newSubtasks = array_filter($subtasks_from_office365, function ($event) { - return ! SubTask::where('office365_id', $event['id'])->exists() && ! $event['isCancelled']; - }); - - - Office365Service::addBlockers($newBlockers); - Office365Service::addSubtasks($newSubtasks); - - // get edited blockers - the ones, that has different changeKey from our database - $editedBlockers = array_filter($blockers_from_office365, function ($event) { - $blocker = Blocker::where('office365_id', $event['id'])->first(); - - 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 ( - $blockers_from_office365 - ) { - $event = collect($blockers_from_office365)->firstWhere('id', $blocker->office365_id); - - return $event && $event['isCancelled']; - }); - - // TODO dat do service - $removedBlockers->each(function ($blocker) { - $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'); + Office365Service::addSubtasks($subtasks_from_office365); + Office365Service::editSubtasks($subtasks_from_office365); + Office365Service::removeSubtasks($subtasks_from_office365); + + return Command::SUCCESS; } } diff --git a/be/app/Http/Controllers/MasterTaskController.php b/be/app/Http/Controllers/MasterTaskController.php index 5f698c4..ca8c0bf 100644 --- a/be/app/Http/Controllers/MasterTaskController.php +++ b/be/app/Http/Controllers/MasterTaskController.php @@ -122,7 +122,7 @@ class MasterTaskController extends Controller $data['data'] = $request->all()['data']; $data['data']['title'] = $request->all()['data']['name']; $data['data']['estimated_time'] = $request->all()['data']['duration']; - $data['data']['priceqoute_time'] = $request->all()['data']['estimated_duration']; + $data['data']['pricequote_time'] = $request->all()['data']['estimated_duration']; $data['data']['created_by'] = $created_by; foreach ($data['data']['employees'] as $key => $employee) { diff --git a/be/app/Services/Office365/Office365Service.php b/be/app/Services/Office365/Office365Service.php index a939828..dd65afb 100644 --- a/be/app/Services/Office365/Office365Service.php +++ b/be/app/Services/Office365/Office365Service.php @@ -2,6 +2,7 @@ namespace App\Services\Office365; +use App\Mail\Error; use App\Models\Blocker; use App\Models\MasterTask; use App\Models\Project; @@ -9,6 +10,7 @@ use App\Models\SubTask; use App\Models\User; use Carbon\Carbon; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Facades\Mail; class Office365Service { @@ -22,7 +24,7 @@ class Office365Service return $response->json()['value']; } - public static function getAccessToken() + private static function getAccessToken() { $tenantId = env('MICROSOFT_TENANT_ID'); $clientId = env('MICROSOFT_CLIENT_ID'); @@ -40,17 +42,28 @@ class Office365Service ]); if ($response->failed()) { - dd($response->body()); throw new \Exception('Failed to obtain access token: ' . $response->body()); } return $response->json()['access_token']; } - public static function addBlockers(mixed $blockers) + public static function addBlockers(mixed $blockers_from_office365) { - foreach ($blockers as $blocker) { - self::addBlocker($blocker); + // get only new events - the ones that doesnt have any office365_id in blockers table and only events that doesnt have isCancelled => true + + $newBlockers = array_filter($blockers_from_office365, function ($event) { + return ! Blocker::where('office365_id', $event['id'])->exists() && ! $event['isCancelled']; + }); + + foreach ($newBlockers as $blocker) { + try { + self::addBlocker($blocker); + } catch (\Exception $e) { + // log error + Mail::to('feher@studiotem.com')->send(new Error($e)); + \Log::error($e->getMessage()); + } } } @@ -83,10 +96,38 @@ class Office365Service } } - public static function editBlockers(mixed $editedBlockers) + /** + * @param mixed $events + * + * @return mixed + */ + public static function getBlockers_from_office_365_events(mixed $events): mixed { + return array_filter($events, function ($event) { + $projectCode = explode(' ', $event['subject'])[0]; + + // if project code doesnt exist in database, then it is blocker + return ! Project::where('code', $projectCode)->exists(); + }); + } + + public static function editBlockers(mixed $blockers_from_office365) + { + // get edited blockers - the ones, that has different changeKey from our database + $editedBlockers = array_filter($blockers_from_office365, function ($event) { + $blocker = Blocker::where('office365_id', $event['id'])->first(); + + return $blocker && $blocker->office365_change_key !== $event['changeKey']; + }); + foreach ($editedBlockers as $editedBlocker) { - self::editBlocker($editedBlocker); + try { + self::editBlocker($editedBlocker); + } catch (\Exception $e) { + // log error + Mail::to('feher@studiotem.com')->send(new Error($e)); + \Log::error($e->getMessage()); + } } } @@ -94,6 +135,21 @@ class Office365Service { $attendees = $editedBlocker['attendees']; + // ak sa v nasej DB pod blockerom nachadza nejaky user, ktory sa uz nenachadza medzi attendees, tak mu tento blocker vymazeme + $blockersToDelete = Blocker::where('office365_id', $editedBlocker['id'])->get()->filter(function ($blocker) use + ( + $attendees + ) { + $event = collect($attendees)->firstWhere('emailAddress.address', $blocker->user->email); + + return ! $event; + }); + + foreach ($blockersToDelete as $blocker) { + $blocker->delete(); + } + + // pre kazdeho cloveka z eventu, ktory je zaroven userom v TM updatneme blocker foreach ($attendees as $attendee) { $user = User::where('email', $attendee['emailAddress']['address'])->first(); @@ -104,33 +160,60 @@ class Office365Service $blocker = Blocker::where('office365_id', $editedBlocker['id'])->where('user_id', $user->id)->first(); - $blocker->update([ - 'office365_change_key' => $editedBlocker['changeKey'], - 'name' => $editedBlocker['subject'], - 'start' => $editedBlocker['start']['dateTime'], - 'end' => $editedBlocker['end']['dateTime'], - 'updated_at' => Carbon::now()->toDateTimeString(), - ]); - } - } - - public static function addSubtasks(mixed $newSubtasks) - { - foreach ($newSubtasks as $newSubtask) { - $project = Project::where('code', explode(' ', $newSubtask['subject'])[0])->first(); - - // ak existuje viac master taskov, pouzijeme posledny - $mastertask = MasterTask::where('project_id', $project->id)->latest()->first(); - // ak neexistuje master task k danemu projektu, tak sa subtask neprida - if ( ! $mastertask) { - continue; + // ak sa nenachadza blocker, znamena to, ze je to novy user medzi attendees, ktory este nema u nas vytvoreny blocker. tak ho vytvorime + if ( ! $blocker) { + Blocker::create([ + 'office365_id' => $editedBlocker['id'], + 'office365_change_key' => $editedBlocker['changeKey'], + 'user_id' => $user->id, + 'name' => $editedBlocker['subject'], + 'type' => "office365_blocker", + 'start' => $editedBlocker['start']['dateTime'], + 'end' => $editedBlocker['end']['dateTime'], + 'approved' => 1, + 'created_at' => Carbon::now()->toDateTimeString(), + 'updated_at' => Carbon::now()->toDateTimeString() + ] + ); + } else { + $blocker->update([ + 'office365_change_key' => $editedBlocker['changeKey'], + 'name' => $editedBlocker['subject'], + 'start' => $editedBlocker['start']['dateTime'], + 'end' => $editedBlocker['end']['dateTime'], + 'updated_at' => Carbon::now()->toDateTimeString(), + ]); } - - self::addSubtaskToAllInvited($newSubtask, $mastertask); } } - private static function addSubtaskToAllInvited(mixed $newSubtask, mixed $mastertask) + public static function addSubtasks(mixed $subtasks_from_office365) + { + $newSubtasks = array_filter($subtasks_from_office365, function ($event) { + return ! SubTask::where('office365_id', $event['id'])->exists() && ! $event['isCancelled']; + }); + + foreach ($newSubtasks as $newSubtask) { + try { + $project = Project::where('code', explode(' ', $newSubtask['subject'])[0])->first(); + + // ak existuje viac master taskov, pouzijeme posledny + $mastertask = MasterTask::where('project_id', $project->id)->latest()->first(); + // ak neexistuje master task k danemu projektu, tak sa subtask neprida + if ( ! $mastertask) { + continue; + } + + self::addSubtask($newSubtask, $mastertask); + } catch (\Exception $e) { + // log error + Mail::to('feher@studiotem.com')->send(new Error($e)); + \Log::error($e->getMessage()); + } + } + } + + private static function addSubtask(mixed $newSubtask, mixed $mastertask) { $attendees = $newSubtask['attendees']; @@ -148,8 +231,10 @@ class Office365Service } $subtasksToInsert[$i]['user_id'] = $user->id; - // vymazem prve slovo z title, ktore je project code - $subtasksToInsert[$i]['title'] = substr($newSubtask['subject'], + + // vymazem prve slovo z title, ktore je project code. ak title ma len 1 slovo (project code), tak sa ako title vlozi "No title" + $subtasksToInsert[$i]['title'] = ( ! str_contains($newSubtask['subject'], + ' ')) ? 'No title' : substr($newSubtask['subject'], strpos($newSubtask['subject'], ' ') + 1); @@ -185,38 +270,37 @@ class Office365Service $mastertask->recalculateEstimatedTime(); } - public static function finishSubtasks(mixed $subtasks) + /** + * @param mixed $events + * + * @return mixed + */ + public static function getSubtasks_from_office_365_events(mixed $events): mixed { - foreach ($subtasks as $subtask) { - // ukoncenie timeblocks - foreach ($subtask->timeblocks as $timeblock) { - $timeblock->update([ - 'is_finished' => 1, - ]); - } + return array_filter($events, function ($event) { + $projectCode = explode(' ', $event['subject'])[0]; - // pridanie odtrackovaneho casu - $subtask->syncTimesheets([ - [ - 'sub_task_id' => $subtask->id, - 'employee_id' => $subtask->user_id, - 'time' => $subtask->estimated_time, - ], - ]); - - $subtask->update([ - 'is_finished' => 1, - ]); - } - - // manualne musim trignernut tuto operaciu, lebo cez Observer sa to nespusti - ten je nastaveny len na update subtasku - $subtasks->first()->mastertask->recalculateTrackedTime(); + // if project code exist in database, then it is subtask + return Project::where('code', $projectCode)->exists(); + }); } - public static function editSubtasks(mixed $editedSubtasks) + public static function editSubtasks(mixed $subtasks_from_office365) { + // 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']; + }); + foreach ($editedSubtasks as $editedSubtask) { - self::editSubtask($editedSubtask); + try { + self::editSubtask($editedSubtask); + } catch (\Exception $e) { + Mail::to('feher@studiotem.com')->send(new Error($e)); + \Log::error($e->getMessage()); + } } } @@ -225,57 +309,14 @@ class Office365Service $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; - }); + $subtasksToDelete = self::getSubtasksToDeleteFromEditedEvents($editedSubtask['id'], $attendees); foreach ($subtasksToDelete as $subtaskToDelete) { - // vymazanie timeblockov - foreach ($subtaskToDelete->timeblocks as $timeblock) { - $timeblock->delete(); - } - - // vymazanie timesheetov - foreach ($subtaskToDelete->timesheets as $timesheet) { - $timesheet->delete(); - } - - $subtaskToDelete->delete(); + self::removeSubtask($subtaskToDelete); } // 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); + self::createNewSubtaskToNewAttendee($attendees, $editedSubtask); // existujucim ucastnikom sa updatne subtask foreach ($attendees as $attendee) { @@ -321,4 +362,160 @@ class Office365Service // 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(); } + + private static function getSubtasksToDeleteFromEditedEvents( + $id, + mixed $attendees + ): \Illuminate\Support\Collection|array|\LaravelIdea\Helper\App\Models\_IH_SubTask_C { + $subtasksToDelete = SubTask::where('is_finished', 0)->where('office365_id', + $id)->get()->filter(function ($subtask) use ( + $attendees + ) { + $event = collect($attendees)->firstWhere('emailAddress.address', $subtask->user->email); + + return ! $event; + }); + + return $subtasksToDelete; + } + + /** + * @param array|\LaravelIdea\Helper\App\Models\_IH_SubTask_C|\Illuminate\Support\Collection $removedSubtasks + * + * @return void + */ + private static function removeSubtask($subtask + ): void { + // vymazanie timeblockov + foreach ($subtask->timeblocks as $timeblock) { + $timeblock->delete(); + } + + // vymazanie timesheetov + foreach ($subtask->timesheets as $timesheet) { + $timesheet->delete(); + } + + $subtask->delete(); + } + + /** + * @param mixed $attendees + * @param mixed $editedSubtask + * + * @return void + */ + private static function createNewSubtaskToNewAttendee(mixed $attendees, mixed $editedSubtask): void + { + $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::addSubtask($editedSubtaskWithOnlyNewAttendees, $mastertask); + } + + public static function removeBlockers(mixed $blockers_from_office365) + { + // 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 ( + $blockers_from_office365 + ) { + $event = collect($blockers_from_office365)->firstWhere('id', $blocker->office365_id); + + return $event && $event['isCancelled']; + }); + + $removedBlockers->each(function ($blocker) { + try { + $blocker->delete(); + } catch (\Exception $e) { + // log error + Mail::to('feher@studiotem.com')->send(new Error($e)); + \Log::error($e->getMessage()); + } + }); + } + + /** + * @param $id + * @param mixed $attendees + * + * @return SubTask[]|\Illuminate\Support\Collection|\LaravelIdea\Helper\App\Models\_IH_SubTask_C + */ + + public static function removeSubtasks(mixed $subtasks_from_office365) + { + // 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']; + }); + + + foreach ($removedSubtasks as $removed_subtask) { + try { + self::removeSubtask($removed_subtask); + } catch (\Exception $e) { + Mail::to('feher@studiotem.com')->send(new Error($e)); + \Log::error($e->getMessage()); + } + } + } + + public static function finishSubtasks(mixed $subtasks) + { + foreach ($subtasks as $subtask) { + try { + // ukoncenie timeblocks + foreach ($subtask->timeblocks as $timeblock) { + $timeblock->update([ + 'is_finished' => 1, + ]); + } + + // pridanie odtrackovaneho casu + $subtask->syncTimesheets([ + [ + 'sub_task_id' => $subtask->id, + 'employee_id' => $subtask->user_id, + 'time' => $subtask->estimated_time, + ], + ]); + + $subtask->update([ + 'is_finished' => 1, + ]); + } catch (\Exception $e) { + Mail::to('feher@studiotem.com')->send(new Error($e)); + \Log::error($e->getMessage()); + } + } + + // manualne musim trignernut tuto operaciu, lebo cez Observer sa to nespusti - ten je nastaveny len na update subtasku + $subtasks->first()->mastertask->recalculateTrackedTime(); + } } diff --git a/be/routes/web.php b/be/routes/web.php index 79271eb..2f11a72 100644 --- a/be/routes/web.php +++ b/be/routes/web.php @@ -16,10 +16,6 @@ use Illuminate\Support\Facades\Route; Route::get('/', function () { }); -Route::get('/run-office365', function () { - \Artisan::call('office365:sync'); -}); - Route::prefix('be')->group(function () { Route::get('/unauthenticated', function () { return response()->json([ @@ -33,6 +29,11 @@ Route::prefix('be')->group(function () { dd($subtask); }); + Route::get('/run-office365', function () { + \Artisan::call('office365:sync'); + \Artisan::call('office365:office365:finish-subtasks'); + }); + Route::get('/healthcheck', function () { return response()->json([ 'message' => 'All good',