From 593372bae52369b39c8005d7561e198635d426d9 Mon Sep 17 00:00:00 2001 From: Constantin Graf Date: Mon, 31 Aug 2026 11:14:39 +0200 Subject: [PATCH] Add still-running email preference --- .../TimeEntrySendStillRunningMailsCommand.php | 3 ++- .../Controllers/Api/V1/UserController.php | 4 ++++ .../Requests/V1/User/UserUpdateRequest.php | 10 ++++++++ app/Http/Resources/V1/User/UserResource.php | 2 ++ app/Models/User.php | 3 +++ ...try_still_running_email_to_users_table.php | 24 +++++++++++++++++++ .../Partials/UpdateProfileInformationForm.vue | 23 ++++++++++++++++++ .../packages/api/src/openapi.json.client.ts | 2 ++ resources/js/types/models.d.ts | 1 + ...eEntrySendStillRunningMailsCommandTest.php | 22 +++++++++++++++++ .../Unit/Endpoint/Api/V1/UserEndpointTest.php | 22 ++++++++++++++++- 11 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 database/migrations/2026_08_31_000001_add_send_time_entry_still_running_email_to_users_table.php diff --git a/app/Console/Commands/TimeEntry/TimeEntrySendStillRunningMailsCommand.php b/app/Console/Commands/TimeEntry/TimeEntrySendStillRunningMailsCommand.php index 3d15283d..3239446b 100644 --- a/app/Console/Commands/TimeEntry/TimeEntrySendStillRunningMailsCommand.php +++ b/app/Console/Commands/TimeEntry/TimeEntrySendStillRunningMailsCommand.php @@ -51,7 +51,8 @@ class TimeEntrySendStillRunningMailsCommand extends Command ]) ->whereHas('user', function (Builder $query): void { /** @var Builder $query */ - $query->where('is_placeholder', '=', false); + $query->where('is_placeholder', '=', false) + ->where('send_time_entry_still_running_email', '=', true); }) ->orderBy('created_at', 'asc') ->chunk(500, function (Collection $timeEntries) use ($dryRun, &$sentMails): void { diff --git a/app/Http/Controllers/Api/V1/UserController.php b/app/Http/Controllers/Api/V1/UserController.php index c4e2edca..4d1dd3e9 100644 --- a/app/Http/Controllers/Api/V1/UserController.php +++ b/app/Http/Controllers/Api/V1/UserController.php @@ -124,6 +124,10 @@ class UserController extends Controller $user->week_start = $request->getWeekStart(); } + if ($request->getSendTimeEntryStillRunningEmail() !== null) { + $user->send_time_entry_still_running_email = $request->getSendTimeEntryStillRunningEmail(); + } + $user->save(); if ($emailToVerify !== null) { diff --git a/app/Http/Requests/V1/User/UserUpdateRequest.php b/app/Http/Requests/V1/User/UserUpdateRequest.php index e0d1ecd7..7c911396 100644 --- a/app/Http/Requests/V1/User/UserUpdateRequest.php +++ b/app/Http/Requests/V1/User/UserUpdateRequest.php @@ -58,6 +58,9 @@ class UserUpdateRequest extends BaseFormRequest 'week_start' => [ Rule::enum(Weekday::class), ], + 'send_time_entry_still_running_email' => [ + 'boolean', + ], ]; } @@ -81,6 +84,13 @@ class UserUpdateRequest extends BaseFormRequest return $this->has('week_start') ? Weekday::from($this->input('week_start')) : null; } + public function getSendTimeEntryStillRunningEmail(): ?bool + { + return $this->has('send_time_entry_still_running_email') + ? $this->boolean('send_time_entry_still_running_email') + : null; + } + public function hasPhotoKey(): bool { return $this->has('photo'); diff --git a/app/Http/Resources/V1/User/UserResource.php b/app/Http/Resources/V1/User/UserResource.php index 675380d3..980c9335 100644 --- a/app/Http/Resources/V1/User/UserResource.php +++ b/app/Http/Resources/V1/User/UserResource.php @@ -36,6 +36,8 @@ class UserResource extends BaseResource 'timezone' => $this->resource->timezone, /** @var Weekday $week_start Starting day of the week */ 'week_start' => $this->resource->week_start->value, + /** @var bool $send_time_entry_still_running_email Whether to email the user when a time entry has been running for more than 8 hours */ + 'send_time_entry_still_running_email' => $this->resource->send_time_entry_still_running_email, ]; } } diff --git a/app/Models/User.php b/app/Models/User.php index 9fcea6ff..5d7fc298 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -42,6 +42,7 @@ use OwenIt\Auditing\Contracts\Auditable as AuditableContract; * @property string $timezone * @property bool $is_placeholder * @property Weekday $week_start + * @property bool $send_time_entry_still_running_email * @property string|null $profile_photo_path * @property-read Organization|null $currentOrganization * @property-read string $profile_photo_url @@ -108,6 +109,7 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M 'is_admin' => 'boolean', 'is_placeholder' => 'boolean', 'week_start' => Weekday::class, + 'send_time_entry_still_running_email' => 'boolean', ]; /** @@ -117,6 +119,7 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M */ protected $attributes = [ 'week_start' => Weekday::Monday, + 'send_time_entry_still_running_email' => true, ]; /** diff --git a/database/migrations/2026_08_31_000001_add_send_time_entry_still_running_email_to_users_table.php b/database/migrations/2026_08_31_000001_add_send_time_entry_still_running_email_to_users_table.php new file mode 100644 index 00000000..f23064c2 --- /dev/null +++ b/database/migrations/2026_08_31_000001_add_send_time_entry_still_running_email_to_users_table.php @@ -0,0 +1,24 @@ +boolean('send_time_entry_still_running_email')->default(true)->after('week_start'); + }); + } + + public function down(): void + { + Schema::table('users', function (Blueprint $table): void { + $table->dropColumn('send_time_entry_still_running_email'); + }); + } +}; diff --git a/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue b/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue index e1957e07..4a2810a9 100644 --- a/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue +++ b/resources/js/Pages/Profile/Partials/UpdateProfileInformationForm.vue @@ -8,6 +8,7 @@ import { Button } from '@/packages/ui/src/Buttons'; import PrimaryButton from '@/packages/ui/src/Buttons/PrimaryButton.vue'; import SecondaryButton from '@/packages/ui/src/Buttons/SecondaryButton.vue'; import TextInput from '@/packages/ui/src/Input/TextInput.vue'; +import { Checkbox } from '@/packages/ui/src'; import { useResendUserEmailVerificationMutation, useResetUserPendingEmailMutation, @@ -26,6 +27,7 @@ const name = ref(''); const email = ref(''); const timezone = ref(''); const weekStart = ref(''); +const sendTimeEntryStillRunningEmail = ref(true); const photoBase64 = ref(null); const photoPreview = ref(null); @@ -40,6 +42,7 @@ function seedForm(u: User) { email.value = u.email; timezone.value = u.timezone; weekStart.value = u.week_start; + sendTimeEntryStillRunningEmail.value = u.send_time_entry_still_running_email; } watch( @@ -78,6 +81,11 @@ function buildPayload(): UpdateUserBody { if (weekStart.value !== user.value.week_start) { body.week_start = weekStart.value as UpdateUserBody['week_start']; } + if ( + sendTimeEntryStillRunningEmail.value !== user.value.send_time_entry_still_running_email + ) { + body.send_time_entry_still_running_email = sendTimeEntryStillRunningEmail.value; + } if (photoBase64.value !== null) body.photo = photoBase64.value; return body; } @@ -330,6 +338,21 @@ const page = usePage<{ {{ fieldErrors.week_start }} + +
+ + + + Email me when a time entry has been running for more than 8 hours + + + + {{ fieldErrors.send_time_entry_still_running_email }} + +