From 7cd4befdfaf8327d61c6bc84a8b6597cbe6c0b79 Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Sun, 31 Jul 2022 19:17:44 +0200 Subject: [PATCH] Refactored shift entry deletion: Use event for notification and worklog creation --- config/app.php | 7 +- includes/controller/shifts_controller.php | 19 ++- includes/helper/shift_helper.php | 129 ++++++++++++++++++ includes/includes.php | 1 + includes/mailer/shifts_mailer.php | 28 ---- includes/model/Shifts_model.php | 5 +- includes/model/UserWorkLog_model.php | 39 ------ includes/pages/admin_rooms.php | 18 ++- includes/pages/admin_shifts.php | 16 ++- includes/pages/schedule/ImportSchedule.php | 37 ++++- resources/lang/de_DE/additional.po | 11 ++ resources/lang/en_US/additional.po | 11 ++ .../views/emails/worklog-from-shift.twig | 16 +++ src/Helpers/Shifts.php | 2 +- src/Mail/EngelsystemMailer.php | 2 +- tests/Unit/Helpers/ShiftsTest.php | 65 +++++---- 16 files changed, 297 insertions(+), 109 deletions(-) create mode 100644 includes/helper/shift_helper.php create mode 100644 resources/views/emails/worklog-from-shift.twig diff --git a/config/app.php b/config/app.php index 24775499..0d711eba 100644 --- a/config/app.php +++ b/config/app.php @@ -64,11 +64,16 @@ return [ // a list of // 'Class@method' or 'Class' (which uses @handle), // ['Class', 'method'], - // callable like [$instance, 'method] or 'function' + // callable like [$instance, 'method'] or 'function' // or $function // ] 'news.created' => \Engelsystem\Events\Listener\News::class . '@created', 'oauth2.login' => \Engelsystem\Events\Listener\OAuth2::class . '@login', + + 'shift.entry.deleting' => [ + \Engelsystem\Events\Listener\Shift::class . '@deletedEntryCreateWorklog', + \Engelsystem\Events\Listener\Shift::class . '@deletedEntrySendEmail', + ], ], ]; diff --git a/includes/controller/shifts_controller.php b/includes/controller/shifts_controller.php index 78bf85c8..aee03dca 100644 --- a/includes/controller/shifts_controller.php +++ b/includes/controller/shifts_controller.php @@ -1,9 +1,10 @@ hasPostData('delete')) { - UserWorkLog_from_shift($shift_id); + $room = Room::find($shift['RID']); + foreach ($shift['ShiftEntry'] as $entry) { + $type = AngelType($entry['TID']); + event('shift.entry.deleting', [ + 'user' => User::find($entry['user_id']), + 'start' => Carbon::createFromTimestamp($shift['start']), + 'end' => Carbon::createFromTimestamp($shift['end']), + 'name' => $shift['name'], + 'title' => $shift['title'], + 'type' => $type['name'], + 'room' => $room, + 'freeloaded' => (bool)$entry['freeloaded'], + ]); + } + Shift_delete($shift_id); engelsystem_log( diff --git a/includes/helper/shift_helper.php b/includes/helper/shift_helper.php new file mode 100644 index 00000000..935a584a --- /dev/null +++ b/includes/helper/shift_helper.php @@ -0,0 +1,129 @@ +log = $log; + $this->mailer = $mailer; + } + + /** + * @param User $user + * @param Carbon $start + * @param Carbon $end + * @param string $name + * @param string $title + * @param string $type + * @param Room $room + * @return void + */ + public function deletedEntryCreateWorklog( + User $user, + Carbon $start, + Carbon $end, + string $name, + string $title, + string $type, + Room $room, + bool $freeloaded + ): void { + if ($freeloaded || $start > Carbon::now()) { + return; + } + + $workLog = new Worklog(); + $workLog->user()->associate($user); + $workLog->creator()->associate(auth()->user()); + $workLog->worked_at = $start->copy()->startOfDay(); + $workLog->hours = + (($end->timestamp - $start->timestamp) / 60 / 60) + * Shifts::getNightShiftMultiplier($start, $end); + $workLog->comment = sprintf( + '%s (%s as %s) in %s, %s - %s', + $name, + $title, + $type, + $room->name, + $start->format('Y-m-d H:i'), + $end->format('Y-m-d H:i') + ); + $workLog->save(); + + $this->log->info( + 'Created worklog entry from shift for {user} ({uid}): {worklog})', + ['user' => $workLog->user->name, 'uid' => $workLog->user->id, 'worklog' => $workLog->comment] + ); + } + + /** + * @param User $user + * @param Carbon $start + * @param Carbon $end + * @param string $name + * @param string $title + * @param string $type + * @param Room $room + * @return void + */ + public function deletedEntrySendEmail( + User $user, + Carbon $start, + Carbon $end, + string $name, + string $title, + string $type, + Room $room, + bool $freeloaded + ): void { + if (!$user->settings->email_shiftinfo) { + return; + } + + $subject = 'notification.shift.deleted'; + try { + $this->mailer->sendViewTranslated( + $user, + $subject, + 'emails/worklog-from-shift', + [ + 'name' => $name, + 'title' => $title, + 'start' => $start, + 'end' => $end, + 'room' => $room, + 'freeloaded' => $freeloaded, + 'username' => $user->name, + ] + ); + } catch (TransportException $e) { + $this->log->error( + 'Unable to send email "{title}" to user {user} with {exception}', + ['title' => $subject, 'user' => $user->name, 'exception' => $e] + ); + } + } +} diff --git a/includes/includes.php b/includes/includes.php index 149adf3f..57604d49 100644 --- a/includes/includes.php +++ b/includes/includes.php @@ -59,6 +59,7 @@ $includeFiles = [ __DIR__ . '/../includes/helper/message_helper.php', __DIR__ . '/../includes/helper/email_helper.php', __DIR__ . '/../includes/helper/oauth_helper.php', + __DIR__ . '/../includes/helper/shift_helper.php', __DIR__ . '/../includes/mailer/shifts_mailer.php', __DIR__ . '/../includes/mailer/users_mailer.php', diff --git a/includes/mailer/shifts_mailer.php b/includes/mailer/shifts_mailer.php index c1695315..a2384631 100644 --- a/includes/mailer/shifts_mailer.php +++ b/includes/mailer/shifts_mailer.php @@ -78,34 +78,6 @@ function mail_shift_change($old_shift, $new_shift) } } -/** - * @param array $shift - */ -function mail_shift_delete($shift) -{ - $users = ShiftEntries_by_shift($shift['SID']); - $room = Room::find($shift['RID']); - - $message = __('A Shift you are registered on was deleted:') . "\n"; - - $message .= $shift['name'] . "\n"; - $message .= $shift['title'] . "\n"; - $message .= date('Y-m-d H:i', $shift['start']) . ' - ' . date('H:i', $shift['end']) . "\n"; - $message .= $room->name . "\n"; - - foreach ($users as $user) { - $user = (new User())->forceFill($user); - if ($user->settings->email_shiftinfo) { - $userMessage = $message; - if ($shift['start'] < time() && !$user['freeloaded']) { - $userMessage .= "\n" . __('Since the deleted shift was already done, we added a worklog entry instead, to keep your work hours correct.') . "\n"; - } - - engelsystem_email_to_user($user, __('Your Shift was deleted'), $userMessage, true); - } - } -} - /** * @param User $user * @param array $shift diff --git a/includes/model/Shifts_model.php b/includes/model/Shifts_model.php index 43fc63b7..59acf65e 100644 --- a/includes/model/Shifts_model.php +++ b/includes/model/Shifts_model.php @@ -512,7 +512,6 @@ function Shift_signup_allowed( */ function Shift_delete($shift_id) { - mail_shift_delete(Shift($shift_id)); Db::delete('DELETE FROM `Shifts` WHERE `SID`=?', [$shift_id]); } @@ -657,7 +656,9 @@ function Shift($shift_id) } $shiftsEntry_source = Db::select(' - SELECT `ShiftEntry`.`id`, `ShiftEntry`.`TID` , `ShiftEntry`.`UID` , `ShiftEntry`.`freeloaded`, `users`.`name` AS `username` + SELECT + `ShiftEntry`.`id`, `ShiftEntry`.`TID` , `ShiftEntry`.`UID` , `ShiftEntry`.`freeloaded`, + `users`.`name` AS `username`, `users`.`id` AS `user_id` FROM `ShiftEntry` LEFT JOIN `users` ON (`users`.`id` = `ShiftEntry`.`UID`) WHERE `SID`=?', [$shift_id]); diff --git a/includes/model/UserWorkLog_model.php b/includes/model/UserWorkLog_model.php index 9f444357..e7fa1c62 100644 --- a/includes/model/UserWorkLog_model.php +++ b/includes/model/UserWorkLog_model.php @@ -66,45 +66,6 @@ function UserWorkLog_create(Worklog $worklog) return $result; } -/** - * @param array|int $shift - */ -function UserWorkLog_from_shift($shift) -{ - $shift = is_array($shift) ? $shift : Shift($shift); - if ($shift['start'] > time()) { - return; - } - - $room = Room::find($shift['RID']); - foreach ($shift['ShiftEntry'] as $entry) { - if ($entry['freeloaded']) { - continue; - } - - $type = AngelType($entry['TID']); - - $shiftStart = Carbon::createFromTimestamp($shift['start']); - $shiftEnd = Carbon::createFromTimestamp($shift['end']); - $nightShiftMultiplier = Shifts::getNightShiftMultiplier($shiftStart, $shiftEnd); - - $worklog = UserWorkLog_new($entry['UID']); - $worklog->hours = (($shift['end'] - $shift['start']) / 60 / 60) * $nightShiftMultiplier; - $worklog->comment = sprintf( - '%s (%s as %s) in %s, %s-%s', - $shift['name'], - $shift['title'], - $type['name'], - $room->name, - Carbon::createFromTimestamp($shift['start'])->format(__('m/d/Y h:i a')), - Carbon::createFromTimestamp($shift['end'])->format(__('m/d/Y h:i a')) - ); - $worklog->worked_at = Carbon::createFromTimestamp($shift['start']); - - UserWorkLog_create($worklog); - } -} - /** * New user work log entry * diff --git a/includes/pages/admin_rooms.php b/includes/pages/admin_rooms.php index 653d633a..d16a6a06 100644 --- a/includes/pages/admin_rooms.php +++ b/includes/pages/admin_rooms.php @@ -1,6 +1,8 @@ User::find($entry['user_id']), + 'start' => Carbon::createFromTimestamp($shift['start']), + 'end' => Carbon::createFromTimestamp($shift['end']), + 'name' => $shift['name'], + 'title' => $shift['title'], + 'type' => $type['name'], + 'room' => $room, + 'freeloaded' => (bool)$entry['freeloaded'], + ]); + } } Room_delete($room); diff --git a/includes/pages/admin_shifts.php b/includes/pages/admin_shifts.php index c100053d..2112961a 100644 --- a/includes/pages/admin_shifts.php +++ b/includes/pages/admin_shifts.php @@ -536,7 +536,21 @@ function admin_shifts_history(): string foreach ($shifts as $shift) { $shift = Shift($shift['SID']); - UserWorkLog_from_shift($shift); + $room = Room::find($shift['RID']); + foreach ($shift['ShiftEntry'] as $entry) { + $type = AngelType($entry['TID']); + event('shift.entry.deleting', [ + 'user' => User::find($entry['user_id']), + 'start' => Carbon::createFromTimestamp($shift['start']), + 'end' => Carbon::createFromTimestamp($shift['end']), + 'name' => $shift['name'], + 'title' => $shift['title'], + 'type' => $type['name'], + 'room' => $room, + 'freeloaded' => (bool)$entry['freeloaded'], + ]); + } + shift_delete($shift['SID']); engelsystem_log( diff --git a/includes/pages/schedule/ImportSchedule.php b/includes/pages/schedule/ImportSchedule.php index 27380099..9857561d 100644 --- a/includes/pages/schedule/ImportSchedule.php +++ b/includes/pages/schedule/ImportSchedule.php @@ -4,7 +4,7 @@ declare(strict_types=1); namespace Engelsystem\Controllers\Admin\Schedule; -use Carbon\Carbon; +use Engelsystem\Helpers\Carbon; use DateTimeInterface; use Engelsystem\Controllers\BaseController; use Engelsystem\Controllers\HasUserNotifications; @@ -17,6 +17,7 @@ use Engelsystem\Http\Response; use Engelsystem\Models\Room as RoomModel; use Engelsystem\Models\Shifts\Schedule as ScheduleUrl; use Engelsystem\Models\Shifts\ScheduleShift; +use Engelsystem\Models\User\User; use ErrorException; use GuzzleHttp\Client as GuzzleClient; use Illuminate\Database\Connection as DatabaseConnection; @@ -267,6 +268,7 @@ class ImportSchedule extends BaseController } foreach ($deleteEvents as $event) { + $this->fireDeleteShiftEntryEvents($event); $this->deleteEvent($event); } @@ -289,6 +291,39 @@ class ImportSchedule extends BaseController $this->log('Created schedule room "{room}"', ['room' => $room->getName()]); } + /** + * @param Event $event + */ + protected function fireDeleteShiftEntryEvents(Event $event): void + { + $shiftEntries = $this->db + ->table('ShiftEntry') + ->select([ + 'ShiftTypes.name', 'Shifts.title', 'AngelTypes.name AS type', 'rooms.id AS room_id', + 'Shifts.start', 'Shifts.end', 'ShiftEntry.UID as user_id', 'ShiftEntry.freeloaded' + ]) + ->join('Shifts', 'Shifts.SID', 'ShiftEntry.SID') + ->join('schedule_shift', 'Shifts.SID', 'schedule_shift.shift_id') + ->join('rooms', 'rooms.id', 'Shifts.RID') + ->join('AngelTypes', 'AngelTypes.id', 'ShiftEntry.TID') + ->join('ShiftTypes', 'ShiftTypes.id', 'Shifts.shifttype_id') + ->where('schedule_shift.guid', $event->getGuid()) + ->get(); + + foreach ($shiftEntries as $shiftEntry) { + event('shift.entry.deleting', [ + 'user' => User::find($shiftEntry->user_id), + 'start' => Carbon::createFromTimestamp($shiftEntry->start), + 'end' => Carbon::createFromTimestamp($shiftEntry->end), + 'name' => $shiftEntry->name, + 'title' => $shiftEntry->title, + 'type' => $shiftEntry->type, + 'room' => RoomModel::find($shiftEntry->room_id), + 'freeloaded' => $shiftEntry->freeloaded, + ]); + } + } + /** * @param Event $shift * @param int $shiftTypeId diff --git a/resources/lang/de_DE/additional.po b/resources/lang/de_DE/additional.po index 4407c592..7b7df794 100644 --- a/resources/lang/de_DE/additional.po +++ b/resources/lang/de_DE/additional.po @@ -152,6 +152,17 @@ msgstr "Du wurdest von einem Supporter als %1$s hinzugefügt." msgid "notification.angeltype.added.text" msgstr "Eine Beschreibung findest du unter %2$s" +msgid "notification.shift.deleted" +msgstr "Deine Schicht wurde gelöscht" + +msgid "notification.shift.deleted.introduction" +msgstr "Eine deiner Schichten wurde gelöscht:" + +msgid "notification.shift.deleted.worklog" +msgstr "" +"Da die gelöschte Schicht bereits vergangen ist, " +"haben wir einen entsprechenden Arbeitseinsatz hinzugefügt." + msgid "user.edit.success" msgstr "Benutzer erfolgreich bearbeitet." diff --git a/resources/lang/en_US/additional.po b/resources/lang/en_US/additional.po index 041098ff..ce25afc9 100644 --- a/resources/lang/en_US/additional.po +++ b/resources/lang/en_US/additional.po @@ -150,6 +150,17 @@ msgstr "You have been added as an %1$s by a supporter." msgid "notification.angeltype.added.text" msgstr "You can find a description at %2$s" +msgid "notification.shift.deleted" +msgstr "Your Shift was deleted" + +msgid "notification.shift.deleted.introduction" +msgstr "A Shift you are registered on was deleted:" + +msgid "notification.shift.deleted.worklog" +msgstr "" +"Since the deleted shift was already done, " +"we added a worklog entry instead, to keep your work hours correct." + msgid "user.edit.success" msgstr "User edited successfully." diff --git a/resources/views/emails/worklog-from-shift.twig b/resources/views/emails/worklog-from-shift.twig new file mode 100644 index 00000000..b85239a6 --- /dev/null +++ b/resources/views/emails/worklog-from-shift.twig @@ -0,0 +1,16 @@ +{% extends "emails/mail.twig" %} + +{% block introduction %} +{{ __('notification.shift.deleted.introduction') }} +{% endblock %} + +{% block message %} +{{ name }} +{{ title }} +{{ start.format(__('Y-m-d H:i')) }} - {{ end.format(__('Y-m-d H:i')) }} +{{ room.name }} + +{% if start <= date() and not freeloaded %} +{{ __('notification.shift.deleted.worklog') }} +{% endif %} +{% endblock %} diff --git a/src/Helpers/Shifts.php b/src/Helpers/Shifts.php index 2eb9dce5..c852d25c 100644 --- a/src/Helpers/Shifts.php +++ b/src/Helpers/Shifts.php @@ -2,7 +2,7 @@ namespace Engelsystem\Helpers; -use \Carbon\Carbon; +use Carbon\Carbon; // Should be moved to the shift model if it's available class Shifts diff --git a/src/Mail/EngelsystemMailer.php b/src/Mail/EngelsystemMailer.php index b5785d02..f648337d 100644 --- a/src/Mail/EngelsystemMailer.php +++ b/src/Mail/EngelsystemMailer.php @@ -47,7 +47,7 @@ class EngelsystemMailer extends Mailer ): void { if ($to instanceof User) { $locale = $locale ?: $to->settings->language; - $to = $to->contact->email ? $to->contact->email : $to->email; + $to = $to->contact->email ?: $to->email; } $activeLocale = null; diff --git a/tests/Unit/Helpers/ShiftsTest.php b/tests/Unit/Helpers/ShiftsTest.php index 3c9a6310..8a76cf39 100644 --- a/tests/Unit/Helpers/ShiftsTest.php +++ b/tests/Unit/Helpers/ShiftsTest.php @@ -14,7 +14,7 @@ class ShiftsTest extends TestCase /** * @covers \Engelsystem\Helpers\Shifts::isNightShift */ - public function testIsNightShift() + public function testIsNightShiftDisabled() { $config = new Config(['night_shifts' => [ 'enabled' => false, @@ -29,38 +29,43 @@ class ShiftsTest extends TestCase new Carbon('2042-01-01 04:00'), new Carbon('2042-01-01 05:00') )); + } - $config->set('night_shifts', array_merge($config->get('night_shifts'), ['enabled' => true])); + /** + * @return array[][] + */ + public function nightShiftData(): array + { + // $start, $end, $isNightShift + return [ + // Is night shift + [new Carbon('2042-01-01 04:00'), new Carbon('2042-01-01 05:00'), true], + // Starts as night shift + [new Carbon('2042-01-01 05:45'), new Carbon('2042-01-01 07:00'), true], + // Ends as night shift + [new Carbon('2042-01-01 00:00'), new Carbon('2042-01-01 02:15'), true], + // Too early + [new Carbon('2042-01-01 00:00'), new Carbon('2042-01-01 01:59'), false], + // Too late + [new Carbon('2042-01-01 06:00'), new Carbon('2042-01-01 09:59'), false], + ]; + } - // Is night shift - $this->assertTrue(Shifts::isNightShift( - new Carbon('2042-01-01 04:00'), - new Carbon('2042-01-01 05:00') - )); + /** + * @covers \Engelsystem\Helpers\Shifts::isNightShift + * @dataProvider nightShiftData + */ + public function testIsNightShiftEnabled($start, $end, $isNightShift) + { + $config = new Config(['night_shifts' => [ + 'enabled' => true, + 'start' => 2, + 'end' => 6, + 'multiplier' => 2, + ]]); + $this->app->instance('config', $config); - // Starts as night shift - $this->assertTrue(Shifts::isNightShift( - new Carbon('2042-01-01 05:45'), - new Carbon('2042-01-01 07:00') - )); - - // Ends as night shift - $this->assertTrue(Shifts::isNightShift( - new Carbon('2042-01-01 00:00'), - new Carbon('2042-01-01 02:15') - )); - - // Too early - $this->assertFalse(Shifts::isNightShift( - new Carbon('2042-01-01 00:00'), - new Carbon('2042-01-01 01:59') - )); - - // Too late - $this->assertFalse(Shifts::isNightShift( - new Carbon('2042-01-01 06:00'), - new Carbon('2042-01-01 09:59') - )); + $this->assertEquals($isNightShift, Shifts::isNightShift($start, $end)); } /**