From bdc62eaac31ac81fc45987ffb489687ded28086f Mon Sep 17 00:00:00 2001 From: frischler <95189570+frischler@users.noreply.github.com> Date: Thu, 8 Dec 2022 17:40:24 +0100 Subject: [PATCH] Worklog Refactoring --- config/routes.php | 16 +- .../controller/user_worklog_controller.php | 217 ---------- includes/helper/message_helper.php | 26 +- includes/includes.php | 2 - includes/model/UserWorkLog_model.php | 72 ---- includes/view/UserWorkLog_view.php | 104 ----- includes/view/User_view.php | 10 +- resources/lang/de_DE/additional.po | 9 + resources/lang/de_DE/default.po | 82 ++-- resources/lang/en_US/additional.po | 9 + resources/lang/en_US/default.po | 24 ++ .../views/admin/user/delete-worklog.twig | 21 + resources/views/admin/user/edit-worklog.twig | 49 +++ resources/views/macros/form.twig | 1 + .../Admin/UserWorkLogController.php | 210 ++++++++++ src/Http/Validation/Validator.php | 1 + src/Middleware/LegacyMiddleware.php | 3 - .../Admin/UserWorkLogControllerTest.php | 394 ++++++++++++++++++ tests/Unit/Http/Validation/ValidatorTest.php | 8 + .../Unit/Middleware/LegacyMiddlewareTest.php | 6 +- 20 files changed, 800 insertions(+), 464 deletions(-) delete mode 100644 includes/controller/user_worklog_controller.php delete mode 100644 includes/view/UserWorkLog_view.php create mode 100644 resources/views/admin/user/delete-worklog.twig create mode 100644 resources/views/admin/user/edit-worklog.twig create mode 100644 src/Controllers/Admin/UserWorkLogController.php create mode 100644 tests/Unit/Controllers/Admin/UserWorkLogControllerTest.php diff --git a/config/routes.php b/config/routes.php index e88f9581..3b388de6 100644 --- a/config/routes.php +++ b/config/routes.php @@ -112,10 +112,24 @@ $route->addGroup( // User $route->addGroup( '/user/{id:\d+}', - // Shirts function (RouteCollector $route) { + // Shirts $route->get('/shirt', 'Admin\\UserShirtController@editShirt'); $route->post('/shirt', 'Admin\\UserShirtController@saveShirt'); + + // Worklogs + $route->get('/worklog', 'Admin\\UserWorkLogController@editWorklog'); + $route->post('/worklog', 'Admin\\UserWorkLogController@saveWorklog'); + $route->get('/worklog/{worklog_id:\d+}', 'Admin\\UserWorkLogController@editWorklog'); + $route->post('/worklog/{worklog_id:\d+}', 'Admin\\UserWorkLogController@saveWorklog'); + $route->get( + '/worklog/{worklog_id:\d+}/delete', + 'Admin\\UserWorkLogController@showDeleteWorklog' + ); + $route->post( + '/worklog/{worklog_id:\d+}/delete', + 'Admin\\UserWorkLogController@deleteWorklog' + ); } ); diff --git a/includes/controller/user_worklog_controller.php b/includes/controller/user_worklog_controller.php deleted file mode 100644 index 6ba99ed4..00000000 --- a/includes/controller/user_worklog_controller.php +++ /dev/null @@ -1,217 +0,0 @@ -input('user_worklog_id')); - if (empty($worklog)) { - throw_redirect(user_link(auth()->user()->id)); - } - $user = $worklog->user; - - if ($request->hasPostData('submit')) { - UserWorkLog_delete($worklog); - - success(__('Work log entry deleted.')); - throw_redirect(user_link($user->id)); - } - - return [ - UserWorkLog_delete_title(), - UserWorkLog_delete_view($user) - ]; -} - -/** - * Edit work log for user. - * - * @return array - */ -function user_worklog_edit_controller() -{ - $request = request(); - $worklog = Worklog::find($request->input('user_worklog_id')); - if (empty($worklog)) { - throw_redirect(user_link(auth()->user()->id)); - } - $user = $worklog->user; - - if ($request->hasPostData('submit')) { - list ($valid, $worklog) = user_worklog_from_request($worklog); - - if ($valid) { - $worklog->save(); - - engelsystem_log(sprintf( - 'Updated work log for %s, %s hours, %s', - User_Nick_render($worklog->user, true), - $worklog->hours, - $worklog->comment - )); - - success(__('Work log entry updated.')); - throw_redirect(user_link($user->id)); - } - } - - return [ - UserWorkLog_edit_title(), - UserWorkLog_edit_view($user, $worklog) - ]; -} - -/** - * Handle form - * - * @param Worklog $worklog - * @return bool[]|Worklog[] [bool $valid, Worklog $worklog] - */ -function user_worklog_from_request(Worklog $worklog) -{ - $request = request(); - - $valid = true; - - $worklog->worked_at = DateTime::createFromFormat('Y-m-d H:i', $request->input('work_timestamp') . ' 00:00'); - if (!$worklog->worked_at) { - $valid = false; - error(__('Please enter work date.')); - } - - $worklog->hours = $request->input('work_hours'); - if (!preg_match("/^\d+(\.\d{0,2})?$/", $request->input('work_hours'))) { - $valid = false; - error(__('Please enter work hours in format ##[.##]')); - } - - $worklog->comment = $request->input('comment'); - if (empty($worklog->comment)) { - $valid = false; - error(__('Please enter a comment.')); - } - - if (mb_strlen($worklog->comment) > 200) { - $valid = false; - error(__('Comment too long.')); - } - - return [ - $valid, - $worklog - ]; -} - -/** - * Add work log entry to user. - * - * @return array - */ -function user_worklog_add_controller() -{ - $request = request(); - $user = User::find($request->input('user_id')); - if (!$user) { - throw_redirect(user_link(auth()->user()->id)); - } - - $worklog = UserWorkLog_new($user->id); - - if ($request->hasPostData('submit')) { - list ($valid, $worklog) = user_worklog_from_request($worklog); - - if ($valid) { - UserWorkLog_create($worklog); - - success(__('Work log entry created.')); - throw_redirect(user_link($user->id)); - } - } - - return [ - UserWorkLog_add_title(), - UserWorkLog_add_view($user, $worklog) - ]; -} - -/** - * Link to work log entry add for given user. - * - * @param User $user - * - * @return string - */ -function user_worklog_add_link(User $user) -{ - return page_link_to('user_worklog', [ - 'action' => 'add', - 'user_id' => $user->id, - ]); -} - -/** - * Link to work log entry edit. - * - * @param Worklog $worklog - * @return string - */ -function user_worklog_edit_link(Worklog $worklog) -{ - return page_link_to('user_worklog', [ - 'action' => 'edit', - 'user_worklog_id' => $worklog->id - ]); -} - -/** - * Link to work log entry delete. - * - * @param Worklog $worklog - * @param array[] $parameters - * @return string - */ -function user_worklog_delete_link(Worklog $worklog, $parameters = []) -{ - return page_link_to('user_worklog', array_merge([ - 'action' => 'delete', - 'user_worklog_id' => $worklog->id - ], $parameters)); -} - -/** - * Work log entry actions - * - * @return array - */ -function user_worklog_controller() -{ - $user = auth()->user(); - - if (!auth()->can('admin_user_worklog')) { - throw_redirect(user_link($user->id)); - } - - $request = request(); - $action = $request->input('action'); - if (!$request->has('action')) { - throw_redirect(user_link($user->id)); - } - - switch ($action) { - case 'add': - return user_worklog_add_controller(); - case 'edit': - return user_worklog_edit_controller(); - case 'delete': - return user_worklog_delete_controller(); - } - - return ['', '']; -} diff --git a/includes/helper/message_helper.php b/includes/helper/message_helper.php index 388a878c..e4058b0b 100644 --- a/includes/helper/message_helper.php +++ b/includes/helper/message_helper.php @@ -2,16 +2,38 @@ /** * Returns messages from session and removes them from the stack - * + * @param bool $includeMessagesFromNewProcedure + * If set, the messages from the new procedure are also included. + * The output will be similar to how it would be with messages.twig. + * @see \Engelsystem\Controllers\HasUserNotifications * @return string */ -function msg() +function msg(bool $includeMessagesFromNewProcedure = false) { $session = session(); $message = $session->get('msg', ''); $session->set('msg', ''); + if ($includeMessagesFromNewProcedure) { + foreach (session()->get('errors', []) as $msg) { + $message .= error(__($msg), true); + } + foreach (session()->get('warnings', []) as $msg) { + $message .= warning(__($msg), true); + } + foreach (session()->get('information', []) as $msg) { + $message .= info(__($msg), true); + } + foreach (session()->get('messages', []) as $msg) { + $message .= success(__($msg), true); + } + + foreach (['errors', 'warnings', 'information', 'messages'] as $type) { + session()->remove($type); + } + } + return $message; } diff --git a/includes/includes.php b/includes/includes.php index ac24dfc4..d7ed25ce 100644 --- a/includes/includes.php +++ b/includes/includes.php @@ -39,7 +39,6 @@ $includeFiles = [ __DIR__ . '/../includes/view/UserDriverLicenses_view.php', __DIR__ . '/../includes/view/UserHintsRenderer.php', __DIR__ . '/../includes/view/User_view.php', - __DIR__ . '/../includes/view/UserWorkLog_view.php', __DIR__ . '/../includes/controller/angeltypes_controller.php', __DIR__ . '/../includes/controller/event_config_controller.php', @@ -51,7 +50,6 @@ $includeFiles = [ __DIR__ . '/../includes/controller/users_controller.php', __DIR__ . '/../includes/controller/user_angeltypes_controller.php', __DIR__ . '/../includes/controller/user_driver_licenses_controller.php', - __DIR__ . '/../includes/controller/user_worklog_controller.php', __DIR__ . '/../includes/helper/legacy_helper.php', __DIR__ . '/../includes/helper/message_helper.php', diff --git a/includes/model/UserWorkLog_model.php b/includes/model/UserWorkLog_model.php index d564e7b7..a15c7969 100644 --- a/includes/model/UserWorkLog_model.php +++ b/includes/model/UserWorkLog_model.php @@ -21,75 +21,3 @@ function UserWorkLogsForUser($userId, Carbon $sinceTime = null) return $worklogs->get(); } - -/** - * Delete a work log entry. - * - * @param Worklog $worklog - * @return int - */ -function UserWorkLog_delete(Worklog $worklog) -{ - $result = $worklog->delete(); - - engelsystem_log(sprintf( - 'Delete work log for %s, %s hours, %s', - User_Nick_render($worklog->user, true), - $worklog->hours, - $worklog->comment - )); - - return $result; -} - -/** - * Create a new work log entry - * - * @param Worklog $worklog - * @return bool - */ -function UserWorkLog_create(Worklog $worklog) -{ - $user = auth()->user(); - $worklog->creator()->associate($user); - $result = $worklog->save(); - - engelsystem_log(sprintf( - 'Added work log entry for %s, %s hours, %s', - User_Nick_render($worklog->user, true), - $worklog->hours, - $worklog->comment - )); - - return $result; -} - -/** - * New user work log entry - * - * @param int $userId - * @return Worklog - */ -function UserWorkLog_new($userId) -{ - /** @var Carbon $buildup */ - $buildup = config('buildup_start'); - /** @var Carbon $event */ - $event = config('event_start'); - - $work_date = Carbon::today(); - if (!empty($buildup) && (empty($event) || $event->lessThan(Carbon::now()))) { - $work_date = $buildup; - } - - $work_date - ->setHour(0) - ->setMinute(0) - ->setSecond(0); - - $worklog = new Worklog(); - $worklog->user_id = $userId; - $worklog->worked_at = $work_date; - - return $worklog; -} diff --git a/includes/view/UserWorkLog_view.php b/includes/view/UserWorkLog_view.php deleted file mode 100644 index 3ad51877..00000000 --- a/includes/view/UserWorkLog_view.php +++ /dev/null @@ -1,104 +0,0 @@ -id), icon('x-lg') . __('cancel')), - form_submit('submit', icon('check-lg') . __('delete'), 'btn-danger', false), - ]), - ]), - ]); -} - -/** - * Title for work log delete. - */ -function UserWorkLog_delete_title() -{ - return __('Delete work log entry'); -} - -/** - * Render edit table. - * - * @param User $user - * @param Worklog $worklog - * @return string - */ -function UserWorkLog_edit_form(User $user, Worklog $worklog) -{ - return form([ - form_info(__('User'), User_Nick_render($user)), - form_date('work_timestamp', __('Work date'), $worklog->worked_at->timestamp, null, time()), - form_text('work_hours', __('Work hours'), $worklog->hours), - form_text('comment', __('Comment'), $worklog->comment, false, 200), - form_submit('submit', __('Save')) - ]); -} - -/** - * Form for edit a user work log entry. - * - * @param User $user - * @param Worklog $worklog - * @return string - */ -function UserWorkLog_edit_view(User $user, Worklog $worklog) -{ - return page_with_title(UserWorkLog_edit_title(), [ - buttons([ - button(user_link($user->id), __('back')) - ]), - msg(), - UserWorkLog_edit_form($user, $worklog) - ]); -} - -/** - * Form for adding a user work log entry. - * - * @param User $user - * @param Worklog $worklog - * @return string - */ -function UserWorkLog_add_view(User $user, Worklog $worklog) -{ - return page_with_title(UserWorkLog_add_title(), [ - buttons([ - button(user_link($user->id), __('back')) - ]), - msg(), - UserWorkLog_edit_form($user, $worklog) - ]); -} - -/** - * Title text for editing work log entry. - */ -function UserWorkLog_edit_title() -{ - return __('Edit work log entry'); -} - -/** - * Title text for adding work log entry. - */ -function UserWorkLog_add_title() -{ - return __('Add work log entry'); -} diff --git a/includes/view/User_view.php b/includes/view/User_view.php index 84f89894..4ff712fe 100644 --- a/includes/view/User_view.php +++ b/includes/view/User_view.php @@ -398,12 +398,12 @@ function User_view_worklog(Worklog $worklog, $admin_user_worklog_privilege) if ($admin_user_worklog_privilege) { $actions = table_buttons([ button( - user_worklog_edit_link($worklog), + url('/admin/user/' . $worklog->user->id . '/worklog/' . $worklog->id), icon('pencil-square') . __('edit'), 'btn-sm' ), button( - user_worklog_delete_link($worklog), + url('/admin/user/' . $worklog->user->id . '/worklog/' . $worklog->id . '/delete'), icon('trash') . __('delete'), 'btn-sm' ) @@ -500,7 +500,7 @@ function User_view( . htmlspecialchars($user_source->name) . (config('enable_user_name') ? ' ' . $user_name . '' : ''), [ - msg(), + msg(true), div('row', [ div('col-md-12', [ buttons([ @@ -532,8 +532,8 @@ function User_view( ) : '', $admin_user_worklog_privilege ? button( - user_worklog_add_link($user_source), - icon('list') . __('Add work log') + url('/admin/user/' . $user_source->id . '/worklog'), + icon('list') . __('worklog.add') ) : '', $its_me ? button( page_link_to('settings/profile'), diff --git a/resources/lang/de_DE/additional.po b/resources/lang/de_DE/additional.po index 425c67e9..2ea82b44 100644 --- a/resources/lang/de_DE/additional.po +++ b/resources/lang/de_DE/additional.po @@ -200,3 +200,12 @@ msgstr "Benutzer erfolgreich bearbeitet." msgid "message.delete.success" msgstr "Nachricht erfolgreich gelöscht." + +msgid "worklog.add.success" +msgstr "Arbeitseinsatz erfolgreich angelegt." + +msgid "worklog.edit.success" +msgstr "Arbeitseinsatz erfolgreich bearbeitet." + +msgid "worklog.delete.success" +msgstr "Arbeitseinsatz erfolgreich gelöscht." diff --git a/resources/lang/de_DE/default.po b/resources/lang/de_DE/default.po index 53d10d21..59286bdd 100644 --- a/resources/lang/de_DE/default.po +++ b/resources/lang/de_DE/default.po @@ -233,7 +233,7 @@ msgstr "Passwort wiederholen" #: includes/view/ShiftEntry_view.php:118 includes/view/ShiftEntry_view.php:141 #: includes/view/ShiftEntry_view.php:207 includes/view/ShiftTypes_view.php:64 #: includes/view/UserDriverLicenses_view.php:54 -#: includes/view/UserWorkLog_view.php:49 includes/view/User_view.php:84 +#: includes/view/User_view.php:84 #: includes/view/User_view.php:93 includes/view/User_view.php:98 #: includes/view/User_view.php:103 includes/view/User_view.php:154 msgid "Save" @@ -330,7 +330,7 @@ msgstr "bearbeiten" #: includes/view/ShiftEntry_view.php:28 includes/view/ShiftEntry_view.php:57 #: includes/view/ShiftTypes_view.php:28 includes/view/ShiftTypes_view.php:96 #: includes/view/ShiftTypes_view.php:128 includes/view/Shifts_view.php:156 -#: includes/view/UserWorkLog_view.php:21 includes/view/User_view.php:501 +#: includes/view/User_view.php:501 msgid "delete" msgstr "löschen" @@ -762,30 +762,6 @@ msgstr "Deine Führerschein-Infos wurden gelöscht." msgid "Edit %s driving license information" msgstr "Bearbeite die Führerschein-Infos von %s" -#: includes/controller/user_worklog_controller.php:22 -msgid "Work log entry deleted." -msgstr "Arbeitseinsatz gelöscht." - -#: includes/controller/user_worklog_controller.php:52 -msgid "Work log entry updated." -msgstr "Arbeitseinsatz geändert." - -#: includes/controller/user_worklog_controller.php:81 -msgid "Please enter work date." -msgstr "Bitte Einsatzdatum angeben." - -#: includes/controller/user_worklog_controller.php:87 -msgid "Please enter work hours in format ##[.##]" -msgstr "Bitte Stunden im Format ##[.##] eingeben." - -#: includes/controller/user_worklog_controller.php:93 -msgid "Please enter a comment." -msgstr "Bitte Kommentar angeben." - -#: includes/controller/user_worklog_controller.php:123 -msgid "Work log entry created." -msgstr "Arbeitseinsatz angelegt." - #: includes/controller/users_controller.php:64 msgid "You cannot delete yourself." msgstr "Du kannst Dich nicht selber löschen." @@ -945,7 +921,6 @@ msgstr "Engel wurden markiert." #: includes/pages/admin_active.php:98 includes/pages/admin_rooms.php:155 #: includes/pages/admin_rooms.php:198 includes/pages/admin_shifts.php:333 #: includes/view/UserAngelTypes_view.php:147 -#: includes/view/UserWorkLog_view.php:64 includes/view/UserWorkLog_view.php:82 #: includes/view/User_view.php:121 includes/view/User_view.php:145 msgid "back" msgstr "zurück" @@ -1967,7 +1942,6 @@ msgstr "Möchtest Du den Engeltypen %s löschen?" #: includes/view/UserAngelTypes_view.php:98 #: includes/view/UserAngelTypes_view.php:122 #: includes/view/UserAngelTypes_view.php:175 -#: includes/view/UserWorkLog_view.php:20 msgid "cancel" msgstr "abbrechen" @@ -2280,7 +2254,6 @@ msgstr "Möchtest du den folgenden User für die Schicht eintragen?" #: includes/view/ShiftEntry_view.php:92 includes/view/ShiftEntry_view.php:117 #: includes/view/UserAngelTypes_view.php:153 -#: includes/view/UserWorkLog_view.php:45 msgid "User" msgstr "Benutzer" @@ -2471,35 +2444,14 @@ msgstr "7,5t LKW" msgid "Truck 12t" msgstr "12t LKW" -#: includes/view/UserWorkLog_view.php:15 #, php-format msgid "Do you want to delete the worklog entry for %s?" msgstr "Möchtest du den Arbeitseinsatz von %s wirklich löschen?" -#: includes/view/UserWorkLog_view.php:32 -msgid "Delete work log entry" -msgstr "Arbeitseinsatz gelöscht." - -#: includes/view/UserWorkLog_view.php:46 -msgid "Work date" -msgstr "Einsatzdatum" - -#: includes/view/UserWorkLog_view.php:47 -msgid "Work hours" -msgstr "Arbeitsstunden" - -#: includes/view/UserWorkLog_view.php:48 includes/view/User_view.php:572 +#: includes/view/User_view.php:572 msgid "Comment" msgstr "Kommentar" -#: includes/view/UserWorkLog_view.php:94 -msgid "Edit work log entry" -msgstr "Arbeitseinsatz bearbeiten" - -#: includes/view/UserWorkLog_view.php:102 -msgid "Add work log entry" -msgstr "Arbeitseinsatz hinzufügen" - msgid "Pronoun" msgstr "Pronomen" @@ -2649,10 +2601,6 @@ msgstr "Führerschein" msgid "Vouchers" msgstr "Gutscheine" -#: includes/view/User_view.php:612 -msgid "Add work log" -msgstr "Neuer Arbeitseinsatz" - #: includes/view/User_view.php:620 msgid "iCal Export" msgstr "iCal Export" @@ -2806,6 +2754,9 @@ msgstr "Löschen" msgid "form.updated" msgstr "Aktualisiert" +msgid "form.cancel" +msgstr "Abbrechen" + msgid "schedule.import" msgstr "Programm importieren" @@ -3125,3 +3076,24 @@ msgstr "Engel" msgid "date" msgstr "Datum" + +msgid "worklog.add" +msgstr "Arbeitseinsatz hinzufügen" + +msgid "worklog.edit" +msgstr "Arbeitseinsatz bearbeiten" + +msgid "worklog.date" +msgstr "Einsatzdatum" + +msgid "worklog.hours" +msgstr "Arbeitsstunden" + +msgid "worklog.comment" +msgstr "Kommentar" + +msgid "worklog.delete" +msgstr "Arbeitseinsatz löschen" + +msgid "worklog.delete.info" +msgstr "Möchtest du den Arbeitseinsatz von %s wirklich löschen?" diff --git a/resources/lang/en_US/additional.po b/resources/lang/en_US/additional.po index 861569a9..51a6f9c0 100644 --- a/resources/lang/en_US/additional.po +++ b/resources/lang/en_US/additional.po @@ -199,3 +199,12 @@ msgstr "User edited successfully." msgid "message.delete.success" msgstr "Message successfully deleted." + +msgid "worklog.add.success" +msgstr "Work log successfully added." + +msgid "worklog.edit.success" +msgstr "Work log successfully updated." + +msgid "worklog.delete.success" +msgstr "Work log successfully deleted." diff --git a/resources/lang/en_US/default.po b/resources/lang/en_US/default.po index 810c2673..7dd17f49 100644 --- a/resources/lang/en_US/default.po +++ b/resources/lang/en_US/default.po @@ -76,6 +76,9 @@ msgstr "Delete" msgid "form.updated" msgstr "Updated" +msgid "form.cancel" +msgstr "Cancel" + msgid "schedule.import" msgstr "Import schedule" @@ -397,3 +400,24 @@ msgstr "Angel" msgid "date" msgstr "Date" + +msgid "worklog.add" +msgstr "Add work log" + +msgid "worklog.edit" +msgstr "Edit work log" + +msgid "worklog.date" +msgstr "Work date" + +msgid "worklog.hours" +msgstr "Work hours" + +msgid "worklog.comment" +msgstr "Comment" + +msgid "worklog.delete" +msgstr "Delete work log" + +msgid "worklog.delete.info" +msgstr "Do you really want to delete the work log for %s?" diff --git a/resources/views/admin/user/delete-worklog.twig b/resources/views/admin/user/delete-worklog.twig new file mode 100644 index 00000000..97df9e1e --- /dev/null +++ b/resources/views/admin/user/delete-worklog.twig @@ -0,0 +1,21 @@ +{% extends "layouts/app.twig" %} +{% import 'macros/base.twig' as m %} +{% import 'macros/form.twig' as f %} + +{% block title %}{{ __('worklog.delete') }}{% endblock %} + +{% block content %} +
+

{{ block('title') }}

+
+ {{ csrf() }} +
+
+ {{ m.alert(__('worklog.delete.info', [m.user(user)]), 'danger', true) }} + {{ m.button(__('form.cancel'), url('/users?action=view&user_id=' ~ user.id)) }} + {{ f.submit(__('form.delete'), {'btn_type': 'danger'}) }} +
+
+
+
+{% endblock %} diff --git a/resources/views/admin/user/edit-worklog.twig b/resources/views/admin/user/edit-worklog.twig new file mode 100644 index 00000000..65239924 --- /dev/null +++ b/resources/views/admin/user/edit-worklog.twig @@ -0,0 +1,49 @@ +{% extends "layouts/app.twig" %} +{% import 'macros/base.twig' as m %} +{% import 'macros/form.twig' as f %} + +{% block title %}{{ __(is_edit ? 'worklog.edit' : 'worklog.add') }}{% endblock %} + +{% block content %} +
+

{{ block('title') }}

+ + {% include 'layouts/parts/messages.twig' %} + +
+ {{ csrf() }} +
+
+ {{ m.button(__('back'), url('/users?action=view&user_id=' ~ user.id)) }} +
+
+
+ +
+ {{ m.user(user, {'pronoun': true}) }} +
+
+ {{ f.input( + 'work_date', + __('worklog.date'), + 'date', + {'value': work_date.format('Y-m-d'), 'required': true} + ) }} + {{ f.input( + 'work_hours', + __('worklog.hours'), + 'number', + {'value': work_hours, 'required': true, 'step': '0.01', 'min': 0} + ) }} + {{ f.input( + 'comment', + __('worklog.comment'), + 'text', + {'value': comment, 'required': true, 'max_length': 200} + ) }} + {{ f.submit(__('form.save')) }} +
+
+
+
+{% endblock %} diff --git a/resources/views/macros/form.twig b/resources/views/macros/form.twig index f9364028..f20a9cf0 100644 --- a/resources/views/macros/form.twig +++ b/resources/views/macros/form.twig @@ -20,6 +20,7 @@ {%- if opt.max_length is defined %} maxlength="{{ opt.max_length }}"{% endif %} {%- if opt.min is defined %} min="{{ opt.min }}"{% endif %} {%- if opt.max is defined %} max="{{ opt.max }}"{% endif %} + {%- if opt.step is defined %} step="{{ opt.step }}"{% endif %} {%- if opt.required|default(false) %} required {%- endif -%} diff --git a/src/Controllers/Admin/UserWorkLogController.php b/src/Controllers/Admin/UserWorkLogController.php new file mode 100644 index 00000000..588534b3 --- /dev/null +++ b/src/Controllers/Admin/UserWorkLogController.php @@ -0,0 +1,210 @@ +auth = $auth; + $this->config = $config; + $this->log = $log; + $this->worklog = $worklog; + $this->redirect = $redirector; + $this->response = $response; + $this->user = $user; + } + + /** + * @param Request $request + * @return Response + */ + public function editWorklog(Request $request): Response + { + $user_id = $request->getAttribute('id'); + $user = $this->user->findOrFail($user_id); + + $worklog_id = $request->getAttribute('worklog_id'); + if (isset($worklog_id)) { + $worklog = $this->worklog->findOrFail($worklog_id); + + if ($worklog->user->id != $user_id) { + throw new HttpNotFound(); + } + return $this->showEditWorklog($user, $worklog->worked_at, $worklog->hours, $worklog->comment, true); + } else { + return $this->showEditWorklog($user, $this->getWorkDateSuggestion()); + } + } + + /** + * @param Request $request + * @return Response + */ + public function saveWorklog(Request $request): Response + { + $user_id = $request->getAttribute('id'); + $user = $this->user->findOrFail($user_id); + + $data = $this->validate($request, [ + 'work_date' => 'required|date:Y-m-d', + 'work_hours' => 'float|min:0', + 'comment' => 'required|max:200', + ]); + + $worklog_id = $request->getAttribute('worklog_id'); + if (isset($worklog_id)) { + $worklog = $this->worklog->findOrFail($worklog_id); + + if ($worklog->user->id != $user_id) { + throw new HttpNotFound(); + } + } else { + $worklog = new Worklog(); + $worklog->user()->associate($user); + $worklog->creator()->associate($this->auth->user()); + } + $worklog->worked_at = $data['work_date']; + $worklog->hours = $data['work_hours']; + $worklog->comment = $data['comment']; + $worklog->save(); + + $this->addNotification(isset($worklog_id) ? 'worklog.edit.success' : 'worklog.add.success'); + + return $this->redirect->to('/users?action=view&user_id=' . $user_id); + // TODO Once User_view.php gets removed, change this to withView + getNotifications + } + + /** + * @param Request $request + * @return Response + */ + public function showDeleteWorklog(Request $request): Response + { + $user_id = $request->getAttribute('id'); + $user = $this->user->findOrFail($user_id); + $worklog_id = $request->getAttribute('worklog_id'); + $worklog = $this->worklog->findOrFail($worklog_id); + + if ($worklog->user->id != $user_id) { + throw new HttpNotFound(); + } + + return $this->response->withView( + 'admin/user/delete-worklog.twig', + [ 'user' => $user ] + ); + } + + /** + * @param Request $request + * @return Response + */ + public function deleteWorklog(Request $request): Response + { + $user_id = $request->getAttribute('id'); + $worklog_id = $request->getAttribute('worklog_id'); + $worklog = $this->worklog->findOrFail($worklog_id); + + if ($worklog->user->id != $user_id) { + throw new HttpNotFound(); + } + $worklog->delete(); + + $this->addNotification('worklog.delete.success'); + + return $this->redirect->to('/users?action=view&user_id=' . $user_id); + // TODO Once User_view.php gets removed, change this to withView + getNotifications + } + + private function showEditWorklog( + User $user, + Carbon $work_date, + float $work_hours = 0, + string $comment = '', + bool $is_edit = false + ): Response { + return $this->response->withView( + 'admin/user/edit-worklog.twig', + [ + 'user' => $user, + 'work_date' => $work_date, + 'work_hours' => $work_hours, + 'comment' => $comment, + 'is_edit' => $is_edit, + ] + ); + } + + /** + * @return Carbon + */ + private function getWorkDateSuggestion(): Carbon + { + $buildup_start = config('buildup_start'); + $event_start = config('event_start'); + + $work_date_suggestion = Carbon::today(); + if (!empty($buildup_start) && (empty($event_start) || $event_start->lessThan(Carbon::now()))) { + $work_date_suggestion = $buildup_start->startOfDay(); + } + return $work_date_suggestion; + } +} diff --git a/src/Http/Validation/Validator.php b/src/Http/Validation/Validator.php index 976f5682..d4a60635 100644 --- a/src/Http/Validation/Validator.php +++ b/src/Http/Validation/Validator.php @@ -19,6 +19,7 @@ class Validator protected $mapping = [ 'accepted' => 'TrueVal', 'int' => 'IntVal', + 'float' => 'FloatVal', 'required' => 'NotEmpty', ]; diff --git a/src/Middleware/LegacyMiddleware.php b/src/Middleware/LegacyMiddleware.php index 4f860eba..af0c5a9c 100644 --- a/src/Middleware/LegacyMiddleware.php +++ b/src/Middleware/LegacyMiddleware.php @@ -26,7 +26,6 @@ class LegacyMiddleware implements MiddlewareInterface 'shifts_json_export', 'users', 'user_driver_licenses', - 'user_worklog', 'admin_shifts_history', ]; @@ -139,8 +138,6 @@ class LegacyMiddleware implements MiddlewareInterface $title = shifts_title(); $content = user_shifts(); return [$title, $content]; - case 'user_worklog': - return user_worklog_controller(); case 'register': $title = register_title(); $content = guest_register(); diff --git a/tests/Unit/Controllers/Admin/UserWorkLogControllerTest.php b/tests/Unit/Controllers/Admin/UserWorkLogControllerTest.php new file mode 100644 index 00000000..1678bd8b --- /dev/null +++ b/tests/Unit/Controllers/Admin/UserWorkLogControllerTest.php @@ -0,0 +1,394 @@ +request->withAttribute('id', 1234); + $this->expectException(ModelNotFoundException::class); + $this->controller->editWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::editWorklog + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::__construct + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::showEditWorklog + */ + public function testShowAddWorklog() + { + $request = $this->request->withAttribute('id', $this->user->id); + $this->response->expects($this->once()) + ->method('withView') + ->willReturnCallback(function (string $view, array $data) { + $this->assertEquals('admin/user/edit-worklog.twig', $view); + $this->assertEquals($this->user->id, $data['user']->id); + $this->assertEquals(Carbon::today(), $data['work_date']); + $this->assertEquals(0, $data['work_hours']); + $this->assertEquals('', $data['comment']); + $this->assertFalse($data['is_edit']); + return $this->response; + }); + $this->controller->editWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::editWorklog + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::getWorkDateSuggestion + * + * @dataProvider buildupConfigsAndWorkDates + */ + public function testShowAddWorklogWithSuggestedWorkDate($buildup_start, $event_start, $suggested_work_date) + { + $request = $this->request->withAttribute('id', $this->user->id); + config(['buildup_start' => $buildup_start]); + config(['event_start' => $event_start]); + $this->response->expects($this->once()) + ->method('withView') + ->willReturnCallback(function (string $view, array $data) use ($suggested_work_date) { + $this->assertEquals($suggested_work_date, $data['work_date']); + return $this->response; + }); + $this->controller->editWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::editWorklog + */ + public function testShowEditWorklogWithWorkLogNotAssociatedToUserThrows() + { + /** @var User $user2 */ + $user2 = User::factory()->create(); + /** @var Worklog $worklog */ + $worklog = Worklog::factory(['user_id' => $user2->id])->create(); + + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', $worklog->id); + $this->expectException(HttpNotFound::class); + $this->controller->editWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::editWorklog + */ + public function testShowEditWorklog() + { + /** @var Worklog $worklog */ + $worklog = Worklog::factory([ + 'user_id' => $this->user->id, + 'worked_at' => new Carbon('2022-01-01'), + 'hours' => 3.14, + 'comment' => 'a comment' + ])->create(); + + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', $worklog->id); + $this->response->expects($this->once()) + ->method('withView') + ->willReturnCallback(function (string $view, array $data) { + $this->assertEquals($this->user->id, $data['user']->id); + $this->assertEquals(new Carbon('2022-01-01'), $data['work_date']); + $this->assertEquals(3.14, $data['work_hours']); + $this->assertEquals('a comment', $data['comment']); + $this->assertTrue($data['is_edit']); + return $this->response; + }); + $this->controller->editWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::saveWorklog + */ + public function testSaveWorklogWithUnkownUserIdThrows() + { + $request = $this->request->withAttribute('id', 1234)->withParsedBody([]); + $this->expectException(ModelNotFoundException::class); + $this->controller->saveWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::saveWorklog + * + * @dataProvider invalidSaveWorkLogParams + */ + public function testSaveWorklogWithInvalidParamsThrows($body) + { + $request = $this->request->withAttribute('id', $this->user->id)->withParsedBody($body); + $this->expectException(ValidationException::class); + $this->controller->saveWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::saveWorklog + */ + public function testSaveNewWorklog() + { + $work_date = Carbon::today(); + $work_hours = 3.14; + $comment = str_repeat('X', 200); + $body = ['work_date' => $work_date, 'work_hours' => $work_hours, 'comment' => $comment]; + $request = $this->request->withAttribute('id', $this->user->id)->withParsedBody($body); + $this->setExpects($this->auth, 'user', null, $this->user, $this->any()); + $this->redirect->expects($this->once()) + ->method('to') + ->with('/users?action=view&user_id=' . $this->user->id) + ->willReturn($this->response); + + $this->controller->saveWorklog($request); + + $this->assertHasNotification('worklog.add.success'); + $this->assertEquals(1, $this->user->worklogs->count()); + $new_worklog = $this->user->worklogs[0]; + $this->assertEquals($this->user->id, $new_worklog->user->id); + $this->assertEquals($work_date, $new_worklog->worked_at); + $this->assertEquals($work_hours, $new_worklog->hours); + $this->assertEquals($comment, $new_worklog->comment); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::saveWorklog + */ + public function testOverwriteWorklogWithUnknownWorkLogIdThrows() + { + $body = ['work_date' => Carbon::today(), 'work_hours' => 3.14, 'comment' => 'a comment']; + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', 1234) + ->withParsedBody($body); + $this->expectException(ModelNotFoundException::class); + $this->controller->saveWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::saveWorklog + */ + public function testOverwriteWorklogWithWorkLogNotAssociatedToUserThrows() + { + /** @var User $user2 */ + $user2 = User::factory()->create(); + /** @var Worklog $worklog */ + $worklog = Worklog::factory(['user_id' => $user2->id])->create(); + + $body = ['work_date' => Carbon::today(), 'work_hours' => 3.14, 'comment' => 'a comment']; + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', $worklog->id) + ->withParsedBody($body); + $this->expectException(HttpNotFound::class); + $this->controller->saveWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::saveWorklog + */ + public function testOverwriteWorklog() + { + /** @var Worklog $worklog */ + $worklog = Worklog::factory(['user_id' => $this->user->id])->create(); + $work_date = Carbon::today(); + $work_hours = 3.14; + $comment = str_repeat('X', 200); + $body = ['work_date' => $work_date, 'work_hours' => $work_hours, 'comment' => $comment]; + + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', $worklog->id) + ->withParsedBody($body); + $this->setExpects($this->auth, 'user', null, $this->user, $this->any()); + $this->redirect->expects($this->once()) + ->method('to') + ->with('/users?action=view&user_id=' . $this->user->id) + ->willReturn($this->response); + + $this->controller->saveWorklog($request); + + $this->assertHasNotification('worklog.edit.success'); + $worklog = Worklog::find($worklog->id); + $this->assertEquals($work_date, $worklog->worked_at); + $this->assertEquals($work_hours, $worklog->hours); + $this->assertEquals($comment, $worklog->comment); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::showDeleteWorklog + */ + public function testShowDeleteWorklogWithWorkLogNotAssociatedToUserThrows() + { + /** @var User $user2 */ + $user2 = User::factory()->create(); + /** @var Worklog $worklog */ + $worklog = Worklog::factory(['user_id' => $user2->id])->create(); + + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', $worklog->id); + $this->expectException(HttpNotFound::class); + $this->controller->showDeleteWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::showDeleteWorklog + */ + public function testShowDeleteWorklog() + { + /** @var Worklog $worklog */ + $worklog = Worklog::factory(['user_id' => $this->user->id])->create(); + + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', $worklog->id); + $this->response->expects($this->once()) + ->method('withView') + ->willReturnCallback(function (string $view, array $data) { + $this->assertEquals($this->user->id, $data['user']->id); + return $this->response; + }); + $this->controller->showDeleteWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::deleteWorklog + */ + public function testDeleteWorklogWithUnknownWorkLogIdThrows() + { + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', 1234); + $this->expectException(ModelNotFoundException::class); + $this->controller->deleteWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::deleteWorklog + */ + public function testDeleteWorklogWithWorkLogNotAssociatedToUserThrows() + { + /** @var User $user2 */ + $user2 = User::factory()->create(); + /** @var Worklog $worklog */ + $worklog = Worklog::factory(['user_id' => $user2->id])->create(); + + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', $worklog->id); + $this->expectException(HttpNotFound::class); + $this->controller->deleteWorklog($request); + } + + /** + * @covers \Engelsystem\Controllers\Admin\UserWorkLogController::deleteWorklog + */ + public function testDeleteWorklog() + { + /** @var Worklog $worklog */ + $worklog = Worklog::factory(['user_id' => $this->user->id])->create(); + + $request = $this->request + ->withAttribute('id', $this->user->id) + ->withAttribute('worklog_id', $worklog->id); + $this->setExpects($this->auth, 'user', null, $this->user, $this->any()); + $this->redirect->expects($this->once()) + ->method('to') + ->with('/users?action=view&user_id=' . $this->user->id) + ->willReturn($this->response); + + $this->controller->deleteWorklog($request); + + $this->assertHasNotification('worklog.delete.success'); + $worklog = Worklog::find($worklog->id); + $this->assertNull($worklog); + } + + /** + * @return array[] + */ + public function invalidSaveWorkLogParams(): array + { + $today = Carbon::today(); + return [ + // missing work_date + [['work_hours' => 3.14, 'comment' => 'com']], + // missing work_hours + [['work_date' => $today, 'comment' => 'com']], + // missing comment + [['work_date' => $today, 'work_hours' => 3.14]], + // too low work_hours + [['work_date' => $today, 'work_hours' => -.1, 'comment' => 'com']], + // too low work_hours + [['work_date' => $today, 'work_hours' => 3.14, 'comment' => str_repeat('X', 201)]], + ]; + } + + /** + * @return array[] + */ + public function buildupConfigsAndWorkDates(): array + { + $day_before_yesterday = Carbon::today()->subDays(2); + $yesterday = Carbon::yesterday(); + $today = Carbon::today(); + $tomorrow = Carbon::tomorrow(); + + // buildup_start, event_start, suggested work date + return [ + [null, null, $today], + [$yesterday, null, $yesterday], + [$yesterday, $tomorrow, $today], + [$day_before_yesterday, $yesterday, $day_before_yesterday], + ]; + } + + /** + * Setup environment + */ + public function setUp(): void + { + parent::setUp(); + + $this->app->bind('http.urlGenerator', UrlGenerator::class); + + $this->auth = $this->createMock(Authenticator::class); + $this->app->instance(Authenticator::class, $this->auth); + + $this->redirect = $this->createMock(Redirector::class); + $this->app->instance(Redirector::class, $this->redirect); + + $this->user = User::factory()->create(); + $this->setExpects($this->auth, 'user', null, $this->user, $this->any()); + + $this->controller = $this->app->make(UserWorkLogController::class); + $this->controller->setValidator(new Validator()); + } +} diff --git a/tests/Unit/Http/Validation/ValidatorTest.php b/tests/Unit/Http/Validation/ValidatorTest.php index 35a9716d..44be56d9 100644 --- a/tests/Unit/Http/Validation/ValidatorTest.php +++ b/tests/Unit/Http/Validation/ValidatorTest.php @@ -112,6 +112,14 @@ class ValidatorTest extends TestCase ['foo' => '0'], ['foo' => 'int'] )); + $this->assertFalse($val->validate( + ['foo' => '0.0'], + ['foo' => 'int'] + )); + $this->assertTrue($val->validate( + ['foo' => '0.0'], + ['foo' => 'float'] + )); $this->assertTrue($val->validate( ['foo' => 'on'], ['foo' => 'accepted'] diff --git a/tests/Unit/Middleware/LegacyMiddlewareTest.php b/tests/Unit/Middleware/LegacyMiddlewareTest.php index a35768f3..713e8f4f 100644 --- a/tests/Unit/Middleware/LegacyMiddlewareTest.php +++ b/tests/Unit/Middleware/LegacyMiddlewareTest.php @@ -48,13 +48,13 @@ class LegacyMiddlewareTest extends TestCase $middleware->expects($this->once()) ->method('loadPage') - ->with('user_worklog') + ->with('users') ->willReturn(['title', 'content']); $middleware->expects($this->exactly(2)) ->method('renderPage') ->withConsecutive( - ['user_worklog', 'title', 'content'], + ['users', 'title', 'content'], ['404', 'Page not found', 'It\'s not available!'] ) ->willReturn($response); @@ -82,7 +82,7 @@ class LegacyMiddlewareTest extends TestCase $defaultRequest->query = $parameters; $defaultRequest->expects($this->once()) ->method('path') - ->willReturn('user-worklog'); + ->willReturn('users'); $parameters->expects($this->exactly(2)) ->method('get')