Refactored shift entry deletion: Use event for notification and worklog creation

This commit is contained in:
Igor Scheller 2022-07-31 19:17:44 +02:00
parent 870a92efd5
commit 7cd4befdfa
16 changed files with 297 additions and 109 deletions

View File

@ -64,11 +64,16 @@ return [
// a list of // a list of
// 'Class@method' or 'Class' (which uses @handle), // 'Class@method' or 'Class' (which uses @handle),
// ['Class', 'method'], // ['Class', 'method'],
// callable like [$instance, 'method] or 'function' // callable like [$instance, 'method'] or 'function'
// or $function // or $function
// ] // ]
'news.created' => \Engelsystem\Events\Listener\News::class . '@created', 'news.created' => \Engelsystem\Events\Listener\News::class . '@created',
'oauth2.login' => \Engelsystem\Events\Listener\OAuth2::class . '@login', 'oauth2.login' => \Engelsystem\Events\Listener\OAuth2::class . '@login',
'shift.entry.deleting' => [
\Engelsystem\Events\Listener\Shift::class . '@deletedEntryCreateWorklog',
\Engelsystem\Events\Listener\Shift::class . '@deletedEntrySendEmail',
],
], ],
]; ];

View File

@ -1,9 +1,10 @@
<?php <?php
use Carbon\Carbon; use Engelsystem\Helpers\Carbon;
use Engelsystem\Http\Exceptions\HttpForbidden; use Engelsystem\Http\Exceptions\HttpForbidden;
use Engelsystem\Models\Room; use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\ScheduleShift; use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Models\User\User;
use Engelsystem\ShiftSignupState; use Engelsystem\ShiftSignupState;
/** /**
@ -236,7 +237,21 @@ function shift_delete_controller()
// Schicht löschen bestätigt // Schicht löschen bestätigt
if ($request->hasPostData('delete')) { if ($request->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); Shift_delete($shift_id);
engelsystem_log( engelsystem_log(

View File

@ -0,0 +1,129 @@
<?php
namespace Engelsystem\Events\Listener;
use Engelsystem\Helpers\Carbon;
use Engelsystem\Helpers\Shifts;
use Engelsystem\Mail\EngelsystemMailer;
use Engelsystem\Models\Room;
use Engelsystem\Models\User\User;
use Engelsystem\Models\Worklog;
use Psr\Log\LoggerInterface;
use Symfony\Component\Mailer\Exception\TransportException;
class Shift
{
/** @var LoggerInterface */
protected LoggerInterface $log;
/** @var EngelsystemMailer */
protected EngelsystemMailer $mailer;
/**
* @param LoggerInterface $log
* @param EngelsystemMailer $mailer
*/
public function __construct(
LoggerInterface $log,
EngelsystemMailer $mailer
) {
$this->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]
);
}
}
}

View File

@ -59,6 +59,7 @@ $includeFiles = [
__DIR__ . '/../includes/helper/message_helper.php', __DIR__ . '/../includes/helper/message_helper.php',
__DIR__ . '/../includes/helper/email_helper.php', __DIR__ . '/../includes/helper/email_helper.php',
__DIR__ . '/../includes/helper/oauth_helper.php', __DIR__ . '/../includes/helper/oauth_helper.php',
__DIR__ . '/../includes/helper/shift_helper.php',
__DIR__ . '/../includes/mailer/shifts_mailer.php', __DIR__ . '/../includes/mailer/shifts_mailer.php',
__DIR__ . '/../includes/mailer/users_mailer.php', __DIR__ . '/../includes/mailer/users_mailer.php',

View File

@ -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 User $user
* @param array $shift * @param array $shift

View File

@ -512,7 +512,6 @@ function Shift_signup_allowed(
*/ */
function Shift_delete($shift_id) function Shift_delete($shift_id)
{ {
mail_shift_delete(Shift($shift_id));
Db::delete('DELETE FROM `Shifts` WHERE `SID`=?', [$shift_id]); Db::delete('DELETE FROM `Shifts` WHERE `SID`=?', [$shift_id]);
} }
@ -657,7 +656,9 @@ function Shift($shift_id)
} }
$shiftsEntry_source = Db::select(' $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` FROM `ShiftEntry`
LEFT JOIN `users` ON (`users`.`id` = `ShiftEntry`.`UID`) LEFT JOIN `users` ON (`users`.`id` = `ShiftEntry`.`UID`)
WHERE `SID`=?', [$shift_id]); WHERE `SID`=?', [$shift_id]);

View File

@ -66,45 +66,6 @@ function UserWorkLog_create(Worklog $worklog)
return $result; 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 * New user work log entry
* *

View File

@ -1,6 +1,8 @@
<?php <?php
use Engelsystem\Helpers\Carbon;
use Engelsystem\Models\Room; use Engelsystem\Models\Room;
use Engelsystem\Models\User\User;
/** /**
* @return string * @return string
@ -180,9 +182,19 @@ function admin_rooms()
$shifts = Shifts_by_room($room); $shifts = Shifts_by_room($room);
foreach ($shifts as $shift) { foreach ($shifts as $shift) {
$shift = Shift($shift['SID']); $shift = Shift($shift['SID']);
foreach ($shift['ShiftEntry'] as $entry) {
UserWorkLog_from_shift($shift); $type = AngelType($entry['TID']);
mail_shift_delete($shift); 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'],
]);
}
} }
Room_delete($room); Room_delete($room);

View File

@ -536,7 +536,21 @@ function admin_shifts_history(): string
foreach ($shifts as $shift) { foreach ($shifts as $shift) {
$shift = Shift($shift['SID']); $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']); shift_delete($shift['SID']);
engelsystem_log( engelsystem_log(

View File

@ -4,7 +4,7 @@ declare(strict_types=1);
namespace Engelsystem\Controllers\Admin\Schedule; namespace Engelsystem\Controllers\Admin\Schedule;
use Carbon\Carbon; use Engelsystem\Helpers\Carbon;
use DateTimeInterface; use DateTimeInterface;
use Engelsystem\Controllers\BaseController; use Engelsystem\Controllers\BaseController;
use Engelsystem\Controllers\HasUserNotifications; use Engelsystem\Controllers\HasUserNotifications;
@ -17,6 +17,7 @@ use Engelsystem\Http\Response;
use Engelsystem\Models\Room as RoomModel; use Engelsystem\Models\Room as RoomModel;
use Engelsystem\Models\Shifts\Schedule as ScheduleUrl; use Engelsystem\Models\Shifts\Schedule as ScheduleUrl;
use Engelsystem\Models\Shifts\ScheduleShift; use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Models\User\User;
use ErrorException; use ErrorException;
use GuzzleHttp\Client as GuzzleClient; use GuzzleHttp\Client as GuzzleClient;
use Illuminate\Database\Connection as DatabaseConnection; use Illuminate\Database\Connection as DatabaseConnection;
@ -267,6 +268,7 @@ class ImportSchedule extends BaseController
} }
foreach ($deleteEvents as $event) { foreach ($deleteEvents as $event) {
$this->fireDeleteShiftEntryEvents($event);
$this->deleteEvent($event); $this->deleteEvent($event);
} }
@ -289,6 +291,39 @@ class ImportSchedule extends BaseController
$this->log('Created schedule room "{room}"', ['room' => $room->getName()]); $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 Event $shift
* @param int $shiftTypeId * @param int $shiftTypeId

View File

@ -152,6 +152,17 @@ msgstr "Du wurdest von einem Supporter als %1$s hinzugefügt."
msgid "notification.angeltype.added.text" msgid "notification.angeltype.added.text"
msgstr "Eine Beschreibung findest du unter %2$s" 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" msgid "user.edit.success"
msgstr "Benutzer erfolgreich bearbeitet." msgstr "Benutzer erfolgreich bearbeitet."

View File

@ -150,6 +150,17 @@ msgstr "You have been added as an %1$s by a supporter."
msgid "notification.angeltype.added.text" msgid "notification.angeltype.added.text"
msgstr "You can find a description at %2$s" 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" msgid "user.edit.success"
msgstr "User edited successfully." msgstr "User edited successfully."

View File

@ -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 %}

View File

@ -2,7 +2,7 @@
namespace Engelsystem\Helpers; namespace Engelsystem\Helpers;
use \Carbon\Carbon; use Carbon\Carbon;
// Should be moved to the shift model if it's available // Should be moved to the shift model if it's available
class Shifts class Shifts

View File

@ -47,7 +47,7 @@ class EngelsystemMailer extends Mailer
): void { ): void {
if ($to instanceof User) { if ($to instanceof User) {
$locale = $locale ?: $to->settings->language; $locale = $locale ?: $to->settings->language;
$to = $to->contact->email ? $to->contact->email : $to->email; $to = $to->contact->email ?: $to->email;
} }
$activeLocale = null; $activeLocale = null;

View File

@ -14,7 +14,7 @@ class ShiftsTest extends TestCase
/** /**
* @covers \Engelsystem\Helpers\Shifts::isNightShift * @covers \Engelsystem\Helpers\Shifts::isNightShift
*/ */
public function testIsNightShift() public function testIsNightShiftDisabled()
{ {
$config = new Config(['night_shifts' => [ $config = new Config(['night_shifts' => [
'enabled' => false, 'enabled' => false,
@ -29,38 +29,43 @@ class ShiftsTest extends TestCase
new Carbon('2042-01-01 04:00'), new Carbon('2042-01-01 04:00'),
new Carbon('2042-01-01 05: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( * @covers \Engelsystem\Helpers\Shifts::isNightShift
new Carbon('2042-01-01 04:00'), * @dataProvider nightShiftData
new Carbon('2042-01-01 05:00') */
)); 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->assertEquals($isNightShift, Shifts::isNightShift($start, $end));
$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')
));
} }
/** /**