Add ShiftEntry model

This commit is contained in:
Igor Scheller 2023-01-18 13:02:11 +01:00 committed by GitHub
parent 89f9b423b1
commit 89dc85c3d5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 787 additions and 606 deletions

View File

@ -0,0 +1,29 @@
<?php
namespace Database\Factories\Engelsystem\Models\Shifts;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Factories\Factory;
class ShiftEntryFactory extends Factory
{
/** @var string */
protected $model = ShiftEntry::class; // phpcs:ignore
public function definition(): array
{
$freeloaded = $this->faker->optional(.01, false)->boolean();
return [
'shift_id' => Shift::factory(),
'angel_type_id' => AngelType::factory(),
'user_id' => User::factory(),
'user_comment' => $this->faker->optional(.05, '')->text(),
'freeloaded' => $freeloaded,
'freeloaded_comment' => $freeloaded ? $this->faker->text() : '',
];
}
}

View File

@ -0,0 +1,114 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
use stdClass;
class CreateShiftEntriesTable extends Migration
{
use ChangesReferences;
use Reference;
/**
* Creates the new table, copies the data and drops the old one
*/
public function up(): void
{
$connection = $this->schema->getConnection();
$previous = $this->schema->hasTable('ShiftEntry');
$this->schema->create('shift_entries', function (Blueprint $table): void {
$table->increments('id');
$this->references($table, 'shifts');
$this->references($table, 'angel_types');
$this->referencesUser($table);
$table->mediumText('user_comment')->default('');
$table->boolean('freeloaded')->default(false)->index();
$table->mediumText('freeloaded_comment')->default('');
$table->index(['angel_type_id', 'shift_id']);
});
if (!$previous) {
return;
}
/** @var stdClass[] $records */
$records = $connection
->table('ShiftEntry')
->get();
foreach ($records as $record) {
$connection->table('shift_entries')->insert([
'id' => $record->id,
'shift_id' => $record->SID,
'angel_type_id' => $record->TID,
'user_id' => $record->UID,
'user_comment' => $record->Comment,
'freeloaded' => (bool) $record->freeloaded,
'freeloaded_comment' => $record->freeload_comment,
]);
}
$this->changeReferences(
'ShiftEntry',
'id',
'shift_entries',
'id'
);
$this->schema->drop('ShiftEntry');
}
/**
* Recreates the previous table, copies the data and drops the new one
*/
public function down(): void
{
$connection = $this->schema->getConnection();
$this->schema->create('ShiftEntry', function (Blueprint $table): void {
$table->increments('id');
$this->references($table, 'shifts', 'SID')->default(0);
$this->references($table, 'angel_types', 'TID')->default(0);
$this->references($table, 'users', 'UID')->default(0);
$table->mediumText('Comment')->nullable();
$table->mediumText('freeload_comment')->nullable()->default(null);
$table->boolean('freeloaded')->index();
$table->index(['SID', 'TID']);
});
/** @var stdClass[] $records */
$records = $connection
->table('shift_entries')
->get();
foreach ($records as $record) {
$connection->table('ShiftEntry')->insert([
'id' => $record->id,
'SID' => $record->shift_id,
'TID' => $record->angel_type_id,
'UID' => $record->user_id,
'Comment' => $record->user_comment,
'freeloaded' => (bool) $record->freeloaded,
'freeload_comment' => $record->freeloaded_comment,
]);
}
$this->changeReferences(
'shift_entries',
'id',
'ShiftEntry',
'id'
);
$this->schema->drop('shift_entries');
}
}

View File

@ -107,8 +107,8 @@ function public_dashboard_needed_angels($needed_angels, ShiftsFilter $filter = n
$result = [];
foreach ($needed_angels as $needed_angel) {
$need = $needed_angel['count'] - $needed_angel['taken'];
if ($need > 0 && (!$filter || in_array($needed_angel['TID'], $filter->getTypes()))) {
$angeltype = AngelType::find($needed_angel['TID']);
if ($need > 0 && (!$filter || in_array($needed_angel['angel_type_id'], $filter->getTypes()))) {
$angeltype = AngelType::find($needed_angel['angel_type_id']);
if ($angeltype->show_on_dashboard) {
$result[] = [
'need' => $need,

View File

@ -2,6 +2,7 @@
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Engelsystem\Models\UserAngelType;
use Engelsystem\ShiftSignupState;
@ -27,7 +28,7 @@ function shift_entries_controller(): array
return match ($action) {
'create' => shift_entry_create_controller(),
'delete' => shift_entry_delete_controller(),
default => ['', ''],
default => ['', ''],
};
}
@ -41,7 +42,7 @@ function shift_entry_create_controller(): array
$user = auth()->user();
$request = request();
if (User_is_freeloader($user)) {
if ($user->isFreeloader()) {
throw_redirect(page_link_to('user_myshifts'));
}
@ -99,14 +100,12 @@ function shift_entry_create_controller_admin(Shift $shift, ?AngelType $angeltype
}
if ($request->hasPostData('submit')) {
ShiftEntry_create([
'SID' => $shift->id,
'TID' => $angeltype->id,
'UID' => $signup_user->id,
'Comment' => '',
'freeloaded' => false,
'freeload_comment' => ''
]);
$shiftEntry = new ShiftEntry();
$shiftEntry->shift()->associate($shift);
$shiftEntry->angelType()->associate($angeltype);
$shiftEntry->user()->associate($signup_user);
$shiftEntry->save();
ShiftEntry_onCreate($shiftEntry);
success(sprintf(__('%s has been subscribed to the shift.'), User_Nick_render($signup_user)));
throw_redirect(shift_link($shift));
@ -150,14 +149,12 @@ function shift_entry_create_controller_supporter(Shift $shift, AngelType $angelt
}
if ($request->hasPostData('submit')) {
ShiftEntry_create([
'SID' => $shift->id,
'TID' => $angeltype->id,
'UID' => $signup_user->id,
'Comment' => '',
'freeloaded' => false,
'freeload_comment' => ''
]);
$shiftEntry = new ShiftEntry();
$shiftEntry->shift()->associate($shift);
$shiftEntry->angelType()->associate($angeltype);
$shiftEntry->user()->associate($signup_user);
$shiftEntry->save();
ShiftEntry_onCreate($shiftEntry);
success(sprintf(__('%s has been subscribed to the shift.'), User_Nick_render($signup_user)));
throw_redirect(shift_link($shift));
@ -214,7 +211,9 @@ function shift_entry_create_controller_user(Shift $shift, AngelType $angeltype):
$signup_user = auth()->user();
$needed_angeltype = (new AngelType())->forceFill(NeededAngeltype_by_Shift_and_Angeltype($shift, $angeltype));
$shift_entries = ShiftEntries_by_shift_and_angeltype($shift->id, $angeltype->id);
$shift_entries = $shift->shiftEntries()
->where('angel_type_id', $angeltype->id)
->get();
$shift_signup_state = Shift_signup_allowed(
$signup_user,
$shift,
@ -232,14 +231,14 @@ function shift_entry_create_controller_user(Shift $shift, AngelType $angeltype):
$comment = '';
if ($request->hasPostData('submit')) {
$comment = strip_request_item_nl('comment');
ShiftEntry_create([
'SID' => $shift->id,
'TID' => $angeltype->id,
'UID' => $signup_user->id,
'Comment' => $comment,
'freeloaded' => false,
'freeload_comment' => ''
]);
$shiftEntry = new ShiftEntry();
$shiftEntry->shift()->associate($shift);
$shiftEntry->angelType()->associate($angeltype);
$shiftEntry->user()->associate($signup_user);
$shiftEntry->user_comment = $comment;
$shiftEntry->save();
ShiftEntry_onCreate($shiftEntry);
if (
!$angeltype->restricted
@ -299,7 +298,7 @@ function shift_entry_create_link_admin(Shift $shift, $params = [])
/**
* Load a shift entry from get parameter shift_entry_id.
*
* @return array
* @return ShiftEntry
*/
function shift_entry_load()
{
@ -308,11 +307,7 @@ function shift_entry_load()
if (!$request->has('shift_entry_id') || !test_request_int('shift_entry_id')) {
throw_redirect(page_link_to('user_shifts'));
}
$shiftEntry = ShiftEntry($request->input('shift_entry_id'));
if (empty($shiftEntry)) {
error(__('Shift entry not found.'));
throw_redirect(page_link_to('user_shifts'));
}
$shiftEntry = ShiftEntry::findOrFail($request->input('shift_entry_id'));
return $shiftEntry;
}
@ -328,9 +323,9 @@ function shift_entry_delete_controller()
$request = request();
$shiftEntry = shift_entry_load();
$shift = Shift($shiftEntry['SID']);
$angeltype = AngelType::find($shiftEntry['TID']);
$signout_user = User::find($shiftEntry['UID']);
$shift = Shift($shiftEntry->shift);
$angeltype = $shiftEntry->angelType;
$signout_user = $shiftEntry->user;
if (!Shift_signout_allowed($shift, $angeltype, $signout_user->id)) {
error(__(
'You are not allowed to remove this shift entry. If necessary, ask your supporter or heaven to do so.'
@ -339,7 +334,8 @@ function shift_entry_delete_controller()
}
if ($request->hasPostData('delete')) {
ShiftEntry_delete($shiftEntry);
$shiftEntry->delete();
ShiftEntry_onDelete($shiftEntry);
success(__('Shift entry removed.'));
throw_redirect(shift_link($shift));
}
@ -347,7 +343,7 @@ function shift_entry_delete_controller()
if ($user->id == $signout_user->id) {
return [
ShiftEntry_delete_title(),
ShiftEntry_delete_view($shift, $angeltype, $signout_user->id)
ShiftEntry_delete_view($shift, $angeltype, $signout_user)
];
}
@ -360,8 +356,8 @@ function shift_entry_delete_controller()
/**
* Link to delete a shift entry.
*
* @param array|Shift $shiftEntry
* @param array $params
* @param Shift|ShiftEntry $shiftEntry
* @param array $params
* @return string URL
*/
function shift_entry_delete_link($shiftEntry, $params = [])

View File

@ -6,7 +6,6 @@ use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftType;
use Engelsystem\Models\User\User;
use Engelsystem\ShiftSignupState;
use Illuminate\Support\Collection;
@ -159,7 +158,6 @@ function shift_edit_controller()
$shift->updatedBy()->associate(auth()->user());
// Remove merged data as it is not really part of the model and thus can't be saved
unset($shift->shiftEntry);
unset($shift->neededAngels);
$shift->save();
@ -244,24 +242,23 @@ function shift_delete_controller()
// Schicht löschen bestätigt
if ($request->hasPostData('delete')) {
foreach ($shift->shiftEntry as $entry) {
$type = AngelType::find($entry['TID']);
foreach ($shift->shiftEntries as $entry) {
event('shift.entry.deleting', [
'user' => User::find($entry['user_id']),
'user' => $entry->user,
'start' => $shift->start,
'end' => $shift->end,
'name' => $shift->shiftType->name,
'title' => $shift->title,
'type' => $type->name,
'type' => $entry->angelType->name,
'room' => $shift->room,
'freeloaded' => (bool) $entry['freeloaded'],
'freeloaded' => $entry->freeloaded,
]);
}
$shift->delete();
engelsystem_log(
'Deleted shift ' . $shift->title . ': ' . $shift->shiftType->name
'Deleted shift ' . $shift->title . ': ' . $shift->shiftType->name
. ' from ' . $shift->start->format('Y-m-d H:i')
. ' to ' . $shift->end->format('Y-m-d H:i')
);
@ -318,7 +315,9 @@ function shift_controller()
continue;
}
$shift_entries = ShiftEntries_by_shift_and_angeltype($shift->id, $angeltype->id);
$shift_entries = $shift->shiftEntries()
->where('angel_type_id', $angeltype->id)
->get();
$needed_angeltype = (new AngelType())->forceFill($needed_angeltype);
$angeltype_signup_state = Shift_signup_allowed(
@ -351,8 +350,8 @@ function shifts_controller()
}
return match ($request->input('action')) {
'view' => shift_controller(),
'next' => shift_next_controller(), // throws redirect
'view' => shift_controller(),
'next' => shift_next_controller(), // throws redirect
default => throw_redirect(page_link_to('/')),
};
}
@ -368,8 +367,8 @@ function shift_next_controller()
$upcoming_shifts = ShiftEntries_upcoming_for_user(auth()->user());
if (!empty($upcoming_shifts)) {
throw_redirect(shift_link($upcoming_shifts[0]));
if (!$upcoming_shifts->isEmpty()) {
throw_redirect(shift_link($upcoming_shifts[0]->shift));
}
throw_redirect(page_link_to('user_shifts'));
@ -406,41 +405,41 @@ function shifts_json_export_controller()
// See engelsystem-base/src/main/kotlin/info/metadude/kotlin/library/engelsystem/models/Shift.kt
$data = [
// Name of the shift (type)
'name' => $shift->shiftType->name,
'name' => $shift->shiftType->name,
// Shift / Talk title
'title' => $shift->title,
'title' => $shift->title,
// Shift description
'description' => $shift->description,
'description' => $shift->description,
// Users comment
'Comment' => $shift->Comment,
'Comment' => $shift->user_comment,
// Shift id
'SID' => $shift->id,
'SID' => $shift->id,
// Shift type id
'shifttype_id' => $shift->shift_type_id,
'shifttype_id' => $shift->shift_type_id,
// Talk URL
'URL' => $shift->url,
'URL' => $shift->url,
// Room name
'Name' => $shift->room->name,
'Name' => $shift->room->name,
// Location map url
'map_url' => $shift->room->map_url,
'map_url' => $shift->room->map_url,
// Start timestamp
/** @deprecated start_date should be used */
'start' => $shift->start->timestamp,
'start' => $shift->start->timestamp,
// Start date
'start_date' => $shift->start->toRfc3339String(),
'start_date' => $shift->start->toRfc3339String(),
// End timestamp
/** @deprecated end_date should be used */
'end' => $shift->end->timestamp,
'end' => $shift->end->timestamp,
// End date
'end_date' => $shift->end->toRfc3339String(),
'end_date' => $shift->end->toRfc3339String(),
// Timezone offset like "+01:00"
/** @deprecated should be retrieved from start_date or end_date */
'timezone' => $timeZone->toOffsetName(),
'timezone' => $timeZone->toOffsetName(),
// The events timezone like "Europe/Berlin"
'event_timezone' => $timeZone->getName(),
];

View File

@ -1,6 +1,7 @@
<?php
use Engelsystem\Database\Db;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\State;
use Engelsystem\Models\User\User;
use Engelsystem\ShiftCalendarRenderer;
@ -209,9 +210,9 @@ function user_controller()
$shift->needed_angeltypes = Db::select(
'
SELECT DISTINCT `angel_types`.*
FROM `ShiftEntry`
JOIN `angel_types` ON `ShiftEntry`.`TID`=`angel_types`.`id`
WHERE `ShiftEntry`.`SID` = ?
FROM `shift_entries`
JOIN `angel_types` ON `shift_entries`.`angel_type_id`=`angel_types`.`id`
WHERE `shift_entries`.`shift_id` = ?
ORDER BY `angel_types`.`name`
',
[$shift->id]
@ -220,11 +221,11 @@ function user_controller()
foreach ($neededAngeltypes as &$needed_angeltype) {
$needed_angeltype['users'] = Db::select(
'
SELECT `ShiftEntry`.`freeloaded`, `users`.*
FROM `ShiftEntry`
JOIN `users` ON `ShiftEntry`.`UID`=`users`.`id`
WHERE `ShiftEntry`.`SID` = ?
AND `ShiftEntry`.`TID` = ?
SELECT `shift_entries`.`freeloaded`, `users`.*
FROM `shift_entries`
JOIN `users` ON `shift_entries`.`user_id`=`users`.`id`
WHERE `shift_entries`.`shift_id` = ?
AND `shift_entries`.`angel_type_id` = ?
',
[$shift->id, $needed_angeltype['id']]
);
@ -247,7 +248,7 @@ function user_controller()
User_view(
$user_source,
auth()->can('admin_user'),
User_is_freeloader($user_source),
$user_source->isFreeloader(),
$user_source->userAngelTypes,
$user_source->groups,
$shifts,
@ -300,7 +301,12 @@ function users_list_controller()
->orderBy('name')
->get();
foreach ($users as $user) {
$user->setAttribute('freeloads', count(ShiftEntries_freeloaded_by_user($user->id)));
$user->setAttribute(
'freeloads',
$user->shiftEntries()
->where('freeloaded', true)
->count()
);
}
$users = $users->sortBy(function (User $user) use ($order_by) {
@ -321,7 +327,7 @@ function users_list_controller()
State::whereArrived(true)->count(),
State::whereActive(true)->count(),
State::whereForceActive(true)->count(),
ShiftEntries_freeloaded_count(),
ShiftEntry::whereFreeloaded(true)->count(),
State::whereGotShirt(true)->count(),
State::query()->sum('got_voucher')
)
@ -360,6 +366,7 @@ function shiftCalendarRendererByShiftFilter(ShiftsFilter $shiftsFilter)
$shift_entries_source = ShiftEntries_by_ShiftsFilter($shiftsFilter);
$needed_angeltypes = [];
/** @var ShiftEntry[][] $shift_entries */
$shift_entries = [];
foreach ($shifts as $shift) {
$needed_angeltypes[$shift->id] = [];
@ -367,8 +374,8 @@ function shiftCalendarRendererByShiftFilter(ShiftsFilter $shiftsFilter)
}
foreach ($shift_entries_source as $shift_entry) {
if (isset($shift_entries[$shift_entry['SID']])) {
$shift_entries[$shift_entry['SID']][] = $shift_entry;
if (isset($shift_entries[$shift_entry->shift_id])) {
$shift_entries[$shift_entry->shift_id][] = $shift_entry;
}
}
@ -403,8 +410,8 @@ function shiftCalendarRendererByShiftFilter(ShiftsFilter $shiftsFilter)
foreach ($shift_entries[$shift->id] as $shift_entry) {
if (
$needed_angeltype['angel_type_id'] == $shift_entry['TID']
&& $shift_entry['freeloaded'] == 0
$needed_angeltype['angel_type_id'] == $shift_entry->angel_type_id
&& !$shift_entry->freeloaded
) {
$taken++;
}

View File

@ -1,11 +1,16 @@
<?php
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Collection;
function mail_shift_change(Shift $old_shift, Shift $new_shift)
{
$users = ShiftEntries_by_shift($old_shift->id);
/** @var ShiftEntry[]|Collection $shiftEntries */
$shiftEntries = $old_shift->shiftEntries()
->with(['user', 'user.settings'])
->get();
$old_room = $old_shift->room;
$new_room = $new_shift->room;
@ -65,8 +70,8 @@ function mail_shift_change(Shift $old_shift, Shift $new_shift)
$message .= $new_room->name . "\n\n";
$message .= url('/shifts', ['action' => 'view', 'shift_id' => $new_shift->id]) . "\n";
foreach ($users as $user) {
$user = (new User())->forceFill($user);
foreach ($shiftEntries as $shiftEntry) {
$user = $shiftEntry->user;
if ($user->settings->email_shiftinfo) {
engelsystem_email_to_user(
$user,

View File

@ -1,6 +1,8 @@
<?php
use Engelsystem\Database\Db;
use Engelsystem\Models\Shifts\ShiftEntry;
use Illuminate\Database\Eloquent\Collection;
/**
* Entity needed angeltypes describes how many angels of given type are needed for a shift or in a room.
@ -108,13 +110,16 @@ function NeededAngelTypes_by_shift($shiftId)
', [$shiftId]);
}
$shift_entries = ShiftEntries_by_shift($shiftId);
/** @var ShiftEntry[]|Collection $shift_entries */
$shift_entries = ShiftEntry::with('user', 'angelType')
->where('shift_id', $shiftId)
->get();
$needed_angeltypes = [];
foreach ($needed_angeltypes_source as $angeltype) {
$angeltype['shift_entries'] = [];
$angeltype['taken'] = 0;
foreach ($shift_entries as $shift_entry) {
if ($shift_entry['TID'] == $angeltype['angel_type_id'] && $shift_entry['freeloaded'] == 0) {
if ($shift_entry->angel_type_id == $angeltype['angel_type_id'] && !$shift_entry->freeloaded) {
$angeltype['taken']++;
$angeltype['shift_entries'][] = $shift_entry;
}

View File

@ -1,153 +1,40 @@
<?php
use Carbon\Carbon;
use Engelsystem\Database\Db;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
/**
* Counts all freeloaded shifts.
*
* @return int
*/
function ShiftEntries_freeloaded_count()
{
$result = Db::selectOne('SELECT COUNT(*) FROM `ShiftEntry` WHERE `freeloaded` = 1');
if (empty($result)) {
return 0;
}
return (int) array_shift($result);
}
/**
* List users subscribed to a given shift.
*
* @param int $shift_id
* @return array
*/
function ShiftEntries_by_shift($shift_id)
{
return Db::select(
'
SELECT
`users`.*,
`ShiftEntry`.`UID`,
`ShiftEntry`.`TID`,
`ShiftEntry`.`SID`,
`angel_types`.`name` AS `angel_type_name`,
`ShiftEntry`.`Comment`,
`ShiftEntry`.`freeloaded`
FROM `ShiftEntry`
JOIN `users` ON `ShiftEntry`.`UID`=`users`.`id`
JOIN `angel_types` ON `ShiftEntry`.`TID`=`angel_types`.`id`
WHERE `ShiftEntry`.`SID` = ?
',
[$shift_id]
);
}
use Illuminate\Database\Eloquent\Collection;
/**
* Create a new shift entry.
*
* @param array $shift_entry
* @return bool
*/
function ShiftEntry_create($shift_entry)
function ShiftEntry_onCreate(ShiftEntry $shiftEntry): void
{
$user = User::find($shift_entry['UID']);
$shift = Shift($shift_entry['SID']);
$shifttype = $shift->shiftType;
$room = $shift->room;
$angeltype = AngelType::find($shift_entry['TID']);
$result = Db::insert(
'
INSERT INTO `ShiftEntry` (
`SID`,
`TID`,
`UID`,
`Comment`,
`freeload_comment`,
`freeloaded`
)
VALUES(?, ?, ?, ?, ?, ?)
',
[
$shift_entry['SID'],
$shift_entry['TID'],
$shift_entry['UID'],
$shift_entry['Comment'],
$shift_entry['freeload_comment'],
(int) $shift_entry['freeloaded'],
]
);
$shift = $shiftEntry->shift;
engelsystem_log(
'User ' . User_Nick_render($user, true)
. ' signed up for shift ' . $shift->title
. ' (' . $shifttype->name . ')'
. ' at ' . $room->name
'User ' . User_Nick_render($shiftEntry->user, true)
. ' signed up for shift ' . $shiftEntry->shift->title
. ' (' . $shift->shiftType->name . ')'
. ' at ' . $shift->room->name
. ' from ' . $shift->start->format('Y-m-d H:i')
. ' to ' . $shift->end->format('Y-m-d H:i')
. ' as ' . $angeltype->name
. ' as ' . $shiftEntry->angelType->name
);
mail_shift_assign($user, $shift);
return $result;
}
/**
* Update a shift entry.
*
* @param array $shift_entry
*/
function ShiftEntry_update($shift_entry)
{
Db::update(
'
UPDATE `ShiftEntry`
SET
`Comment` = ?,
`freeload_comment` = ?,
`freeloaded` = ?
WHERE `id` = ?
',
[
$shift_entry['Comment'],
$shift_entry['freeload_comment'],
(int) $shift_entry['freeloaded'],
$shift_entry['id']
]
);
}
/**
* Get a shift entry.
*
* @param int $shift_entry_id
* @return array|null
*/
function ShiftEntry($shift_entry_id)
{
$shiftEntry = Db::selectOne('SELECT * FROM `ShiftEntry` WHERE `id` = ?', [$shift_entry_id]);
return empty($shiftEntry) ? null : $shiftEntry;
mail_shift_assign($shiftEntry->user, $shift);
}
/**
* Delete a shift entry.
*
* @param array $shiftEntry
* @param ShiftEntry $shiftEntry
*/
function ShiftEntry_delete($shiftEntry)
function ShiftEntry_onDelete(ShiftEntry $shiftEntry)
{
Db::delete('DELETE FROM `ShiftEntry` WHERE `id` = ?', [$shiftEntry['id']]);
$signout_user = User::find($shiftEntry['UID']);
$shift = Shift($shiftEntry['SID']);
$signout_user = $shiftEntry->user;
$shift = Shift($shiftEntry->shift);
$shifttype = $shift->shiftType;
$room = $shift->room;
$angeltype = AngelType::find($shiftEntry['TID']);
$angeltype = $shiftEntry->angelType;
engelsystem_log(
'Shift signout: ' . User_Nick_render($signout_user, true)
@ -159,100 +46,44 @@ function ShiftEntry_delete($shiftEntry)
. ' as ' . $angeltype->name
);
mail_shift_removed(User::find($shiftEntry['UID']), $shift);
mail_shift_removed($signout_user, $shift);
}
/**
* Returns next (or current) shifts of given user.
*
* @param User $user
* @return array
* @return ShiftEntry[]|Collection
*/
function ShiftEntries_upcoming_for_user(User $user)
{
return Db::select(
'
SELECT *, shifts.id as shift_id
FROM `ShiftEntry`
JOIN `shifts` ON (`shifts`.`id` = `ShiftEntry`.`SID`)
JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id`
WHERE `ShiftEntry`.`UID` = ?
AND `shifts`.`end` > NOW()
ORDER BY `shifts`.`end`
',
[
$user->id
]
);
return $user->shiftEntries()
->with(['shift', 'shift.shiftType'])
->join('shifts', 'shift_entries.shift_id', 'shifts.id')
->where('shifts.end', '>', Carbon::now())
->orderBy('shifts.end')
->get();
}
/**
* Returns shifts completed by the given user.
*
* @param User $user
* @param User $user
* @param Carbon|null $sinceTime
* @return array
* @return ShiftEntry[]|Collection
*/
function ShiftEntries_finished_by_user(User $user, Carbon $sinceTime = null)
{
return Db::select(
'
SELECT *
FROM `ShiftEntry`
JOIN `shifts` ON (`shifts`.`id` = `ShiftEntry`.`SID`)
JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id`
WHERE `ShiftEntry`.`UID` = ?
AND `shifts`.`end` < NOW()
AND `ShiftEntry`.`freeloaded` = 0
' . ($sinceTime ? 'AND shifts.start >= "' . $sinceTime->toString() . '"' : '') . '
ORDER BY `shifts`.`end` desc
',
[
$user->id,
]
);
}
$query = $user->shiftEntries()
->with(['shift', 'shift.shiftType'])
->join('shifts', 'shift_entries.shift_id', 'shifts.id')
->where('shifts.end', '<', Carbon::now())
->where('freeloaded', false)
->orderByDesc('shifts.end');
/**
* Returns all shift entries in given shift for given angeltype.
*
* @param int $shift_id
* @param int $angeltype_id
* @return array
*/
function ShiftEntries_by_shift_and_angeltype($shift_id, $angeltype_id)
{
return Db::select(
'
SELECT *
FROM `ShiftEntry`
WHERE `SID` = ?
AND `TID` = ?
',
[
$shift_id,
$angeltype_id,
]
);
}
if ($sinceTime) {
$query = $query->where('shifts.start', '>=', $sinceTime);
}
/**
* Returns all freeloaded shifts for given user.
*
* @param int $userId
* @return array
*/
function ShiftEntries_freeloaded_by_user($userId)
{
return Db::select(
'
SELECT *
FROM `ShiftEntry`
WHERE `freeloaded` = 1
AND `UID` = ?
',
[
$userId
]
);
return $query->get();
}

View File

@ -4,11 +4,13 @@ use Engelsystem\Database\Db;
use Engelsystem\Helpers\Carbon;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Engelsystem\Models\UserAngelType;
use Engelsystem\ShiftsFilter;
use Engelsystem\ShiftSignupState;
use Illuminate\Support\Collection;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Collection as SupportCollection;
/**
* @param AngelType $angeltype
@ -57,7 +59,7 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`shift_id`=`shifts`.`id`' . ($filter ? ' AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
> (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`shifts`.`id` AND `freeloaded`=0' . ($filter ? ' AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
> (SELECT COUNT(*) FROM `shift_entries` WHERE `shift_entries`.`shift_id`=`shifts`.`id` AND shift_entries.`freeloaded`=0' . ($filter ? ' AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
AND s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
@ -68,7 +70,7 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `NeededAngelTypes` WHERE `NeededAngelTypes`.`room_id`=`shifts`.`room_id`' . ($filter ? ' AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
> (SELECT COUNT(*) FROM `ShiftEntry` WHERE `ShiftEntry`.`SID`=`shifts`.`id` AND `freeloaded`=0' . ($filter ? ' AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
> (SELECT COUNT(*) FROM `shift_entries` WHERE `shift_entries`.`shift_id`=`shifts`.`id` AND `freeloaded`=0' . ($filter ? ' AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
AND NOT s.shift_id IS NULL
' . ($filter ? 'AND shifts.room_id IN (' . implode(',', $filter->getRooms()) . ')' : '') . '
) AS `tmp`
@ -246,41 +248,21 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty
/**
* @param ShiftsFilter $shiftsFilter
* @return array[]
* @return ShiftEntry[]|Collection
*/
function ShiftEntries_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
{
$sql = sprintf(
'
SELECT
users.*,
`ShiftEntry`.`UID`,
`ShiftEntry`.`TID`,
`ShiftEntry`.`SID`,
`ShiftEntry`.`Comment`,
`ShiftEntry`.`freeloaded`
FROM `shifts`
JOIN `ShiftEntry` ON `ShiftEntry`.`SID`=`shifts`.`id`
JOIN `users` ON `ShiftEntry`.`UID`=`users`.`id`
WHERE `shifts`.`room_id` IN (%s)
AND `start` BETWEEN ? AND ?
ORDER BY `shifts`.`start`
',
implode(',', $shiftsFilter->getRooms())
);
return Db::select(
$sql,
[
$shiftsFilter->getStart(),
$shiftsFilter->getEnd(),
]
);
return ShiftEntry::with('user')
->join('shifts', 'shifts.id', 'shift_entries.shift_id')
->whereIn('shifts.room_id', $shiftsFilter->getRooms())
->whereBetween('start', [$shiftsFilter->getStart(), $shiftsFilter->getEnd()])
->get();
}
/**
* Check if a shift collides with other shifts (in time).
*
* @param Shift $shift
* @param Shift $shift
* @param Shift[]|Collection $shifts
* @return bool
*/
@ -290,8 +272,8 @@ function Shift_collides(Shift $shift, $shifts)
if ($shift->id != $other_shift->id) {
if (
!(
$shift->start->timestamp >= $other_shift->end->timestamp
|| $shift->end->timestamp <= $other_shift->start->timestamp
$shift->start->timestamp >= $other_shift->end->timestamp
|| $shift->end->timestamp <= $other_shift->start->timestamp
)
) {
return true;
@ -304,15 +286,15 @@ function Shift_collides(Shift $shift, $shifts)
/**
* Returns the number of needed angels/free shift entries for an angeltype.
*
* @param AngelType $needed_angeltype
* @param array[] $shift_entries
* @param AngelType $needed_angeltype
* @param ShiftEntry[]|Collection $shift_entries
* @return int
*/
function Shift_free_entries(AngelType $needed_angeltype, $shift_entries)
{
$taken = 0;
foreach ($shift_entries as $shift_entry) {
if ($shift_entry['freeloaded'] == 0) {
if (!$shift_entry->freeloaded) {
$taken++;
}
}
@ -330,7 +312,7 @@ function Shift_free_entries(AngelType $needed_angeltype, $shift_entries)
* @param array|null $user_angeltype
* @param SHift[]|Collection|null $user_shifts List of the users shifts
* @param AngelType $needed_angeltype
* @param array[] $shift_entries
* @param ShiftEntry[]|Collection $shift_entries
* @return ShiftSignupState
*/
function Shift_signup_allowed_angel(
@ -410,8 +392,8 @@ function Shift_signup_allowed_angel(
/**
* Check if an angeltype supporter can sign up a user to a shift.
*
* @param AngelType $needed_angeltype
* @param array[] $shift_entries
* @param AngelType $needed_angeltype
* @param ShiftEntry[]|Collection $shift_entries
* @return ShiftSignupState
*/
function Shift_signup_allowed_angeltype_supporter(AngelType $needed_angeltype, $shift_entries)
@ -427,8 +409,8 @@ function Shift_signup_allowed_angeltype_supporter(AngelType $needed_angeltype, $
/**
* Check if an admin can sign up a user to a shift.
*
* @param AngelType $needed_angeltype
* @param array[] $shift_entries
* @param AngelType $needed_angeltype
* @param ShiftEntry[]|Collection $shift_entries
* @return ShiftSignupState
*/
function Shift_signup_allowed_admin(AngelType $needed_angeltype, $shift_entries)
@ -484,7 +466,7 @@ function Shift_signout_allowed(Shift $shift, AngelType $angeltype, $signout_user
* @param array|null $user_angeltype
* @param Shift[]|Collection|null $user_shifts List of the users shifts
* @param AngelType $needed_angeltype
* @param array[] $shift_entries
* @param ShiftEntry[]|Collection $shift_entries
* @return ShiftSignupState
*/
function Shift_signup_allowed(
@ -522,10 +504,10 @@ function Shift_signup_allowed(
* Return users shifts.
*
* @param int $userId
* @param bool $include_freeload_comments
* @return Collection|Shift[]
* @param bool $include_freeloaded_comments
* @return SupportCollection|Shift[]
*/
function Shifts_by_user($userId, $include_freeload_comments = false)
function Shifts_by_user($userId, $include_freeloaded_comments = false)
{
$shiftsData = Db::select(
'
@ -534,19 +516,19 @@ function Shifts_by_user($userId, $include_freeload_comments = false)
`rooms`.name AS Name,
`shift_types`.`id` AS `shifttype_id`,
`shift_types`.`name`,
`ShiftEntry`.`id` as shift_entry_id,
`ShiftEntry`.`SID`,
`ShiftEntry`.`TID`,
`ShiftEntry`.`UID`,
`ShiftEntry`.`freeloaded`,
`ShiftEntry`.`Comment`,
' . ($include_freeload_comments ? '`ShiftEntry`.`freeload_comment`, ' : '') . '
`shift_entries`.`id` as shift_entry_id,
`shift_entries`.`shift_id`,
`shift_entries`.`angel_type_id`,
`shift_entries`.`user_id`,
`shift_entries`.`freeloaded`,
`shift_entries`.`user_comment`,
' . ($include_freeloaded_comments ? '`shift_entries`.`freeloaded_comment`, ' : '') . '
`shifts`.*
FROM `ShiftEntry`
JOIN `shifts` ON (`ShiftEntry`.`SID` = `shifts`.`id`)
FROM `shift_entries`
JOIN `shifts` ON (`shift_entries`.`shift_id` = `shifts`.`id`)
JOIN `shift_types` ON (`shift_types`.`id` = `shifts`.`shift_type_id`)
JOIN `rooms` ON (`shifts`.`room_id` = `rooms`.`id`)
WHERE ShiftEntry.`UID` = ?
WHERE shift_entries.`user_id` = ?
ORDER BY `start`
',
[
@ -578,22 +560,14 @@ function Shift($shift)
return null;
}
$shift->shiftEntry = Db::select('
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]);
$neededAngels = [];
$angelTypes = NeededAngelTypes_by_shift($shift->id);
foreach ($angelTypes as $type) {
$neededAngels[] = [
'TID' => $type['angel_type_id'],
'count' => $type['count'],
'restricted' => $type['restricted'],
'taken' => $type['taken']
'angel_type_id' => $type['angel_type_id'],
'count' => $type['count'],
'restricted' => $type['restricted'],
'taken' => $type['taken']
];
}
$shift->neededAngels = $neededAngels;

View File

@ -16,10 +16,10 @@ function stats_currently_working(ShiftsFilter $filter = null)
'
SELECT SUM((
SELECT COUNT(*)
FROM `ShiftEntry`
WHERE `ShiftEntry`.`SID`=`shifts`.`id`
FROM `shift_entries`
WHERE `shift_entries`.`shift_id`=`shifts`.`id`
AND `freeloaded`=0
' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)) AS `count`
FROM `shifts`
WHERE (`end` >= NOW() AND `start` <= NOW())
@ -89,12 +89,12 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
AND `NeededAngelTypes`.`shift_id`=`shifts`.`id`
' . ($filter ? 'AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
SELECT COUNT(*) FROM `ShiftEntry`
JOIN `angel_types` ON `angel_types`.`id`=`ShiftEntry`.`TID`
SELECT COUNT(*) FROM `shift_entries`
JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `ShiftEntry`.`SID`=`shifts`.`id`
AND `shift_entries`.`shift_id`=`shifts`.`id`
AND `freeloaded`=0
' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`
@ -116,12 +116,13 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
AND `NeededAngelTypes`.`room_id`=`shifts`.`room_id`
' . ($filter ? 'AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
SELECT COUNT(*) FROM `ShiftEntry`
JOIN `angel_types` ON `angel_types`.`id`=`ShiftEntry`.`TID`
SELECT COUNT(*)
FROM `shift_entries`
JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `ShiftEntry`.`SID`=`shifts`.`id`
AND `shift_entries`.`shift_id`=`shifts`.`id`
AND `freeloaded`=0
' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`
@ -168,12 +169,12 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null)
AND `NeededAngelTypes`.`shift_id`=`shifts`.`id`
' . ($filter ? 'AND NeededAngelTypes.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
SELECT COUNT(*) FROM `ShiftEntry`
JOIN `angel_types` ON `angel_types`.`id`=`ShiftEntry`.`TID`
SELECT COUNT(*) FROM `shift_entries`
JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `ShiftEntry`.`SID`=`shifts`.`id`
AND `freeloaded`=0
' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
AND `shift_entries`.`shift_id`=`shifts`.`id`
AND shift_entries.`freeloaded`=0
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`
@ -195,12 +196,12 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null)
AND `NeededAngelTypes`.`room_id`=`shifts`.`room_id`
' . ($filter ? 'AND angel_types.id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
SELECT COUNT(*) FROM `ShiftEntry`
JOIN `angel_types` ON `angel_types`.`id`=`ShiftEntry`.`TID`
SELECT COUNT(*) FROM `shift_entries`
JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `ShiftEntry`.`SID`=`shifts`.`id`
AND `shift_entries`.`shift_id`=`shifts`.`id`
AND `freeloaded`=0
' . ($filter ? 'AND ShiftEntry.TID IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`

View File

@ -25,8 +25,8 @@ function User_tshirt_score($userId)
$shift_sum_formula = User_get_shifts_sum_query();
$result_shifts = Db::selectOne(sprintf('
SELECT ROUND((%s) / 3600, 2) AS `tshirt_score`
FROM `users` LEFT JOIN `ShiftEntry` ON `users`.`id` = `ShiftEntry`.`UID`
LEFT JOIN `shifts` ON `ShiftEntry`.`SID` = `shifts`.`id`
FROM `users` LEFT JOIN `shift_entries` ON `users`.`id` = `shift_entries`.`user_id`
LEFT JOIN `shifts` ON `shift_entries`.`shift_id` = `shifts`.`id`
WHERE `users`.`id` = ?
AND `shifts`.`end` < NOW()
GROUP BY `users`.`id`
@ -45,17 +45,6 @@ function User_tshirt_score($userId)
return $result_shifts['tshirt_score'] + $worklogHours;
}
/**
* Returns true if user is freeloader
*
* @param User $user
* @return bool
*/
function User_is_freeloader($user)
{
return count(ShiftEntries_freeloaded_by_user($user->id)) >= config('max_freeloadable_shifts');
}
/**
* Returns all users that are not member of given angeltype.
*
@ -196,15 +185,15 @@ function User_get_eligable_voucher_count($user)
? Carbon::createFromFormat('Y-m-d', $voucher_settings['voucher_start'])->setTime(0, 0)
: null;
$shifts = ShiftEntries_finished_by_user($user, $start);
$shiftEntries = ShiftEntries_finished_by_user($user, $start);
$worklog = UserWorkLogsForUser($user->id, $start);
$shifts_done =
count($shifts)
count($shiftEntries)
+ $worklog->count();
$shiftsTime = 0;
foreach ($shifts as $shift) {
$shiftsTime += (Carbon::make($shift['end'])->timestamp - Carbon::make($shift['start'])->timestamp) / 60 / 60;
foreach ($shiftEntries as $shiftEntry) {
$shiftsTime += ($shiftEntry->shift->end->timestamp - $shiftEntry->shift->start->timestamp) / 60 / 60;
}
foreach ($worklog as $entry) {
$shiftsTime += $entry->hours;
@ -248,7 +237,7 @@ function User_get_shifts_sum_query()
OR (HOUR(shifts.start) <= %1$d AND HOUR(shifts.end) >= %2$d)
))
* (UNIX_TIMESTAMP(shifts.end) - UNIX_TIMESTAMP(shifts.start))
* (1 - (%3$d + 1) * `ShiftEntry`.`freeloaded`)
* (1 - (%3$d + 1) * `shift_entries`.`freeloaded`)
), 0)
',
$nightShifts['start'],

View File

@ -60,7 +60,7 @@ function admin_active()
sprintf(
'
users.*,
COUNT(ShiftEntry.id) AS shift_count,
COUNT(shift_entries.id) AS shift_count,
(%s + (
SELECT COALESCE(SUM(`hours`) * 3600, 0)
FROM `worklogs` WHERE `user_id`=`users`.`id`
@ -70,8 +70,8 @@ function admin_active()
$shift_sum_formula
)
)
->leftJoin('ShiftEntry', 'users.id', '=', 'ShiftEntry.UID')
->leftJoin('shifts', 'ShiftEntry.SID', '=', 'shifts.id')
->leftJoin('shift_entries', 'users.id', '=', 'shift_entries.user_id')
->leftJoin('shifts', 'shift_entries.shift_id', '=', 'shifts.id')
->leftJoin('users_state', 'users.id', '=', 'users_state.user_id')
->where('users_state.arrived', '=', true)
->groupBy('users.id')
@ -152,7 +152,7 @@ function admin_active()
sprintf(
'
users.*,
COUNT(ShiftEntry.id) AS shift_count,
COUNT(shift_entries.id) AS shift_count,
(%s + (
SELECT COALESCE(SUM(`hours`) * 3600, 0)
FROM `worklogs` WHERE `user_id`=`users`.`id`
@ -162,10 +162,10 @@ function admin_active()
$shift_sum_formula
)
)
->leftJoin('ShiftEntry', 'users.id', '=', 'ShiftEntry.UID')
->leftJoin('shift_entries', 'users.id', '=', 'shift_entries.user_id')
->leftJoin('shifts', function ($join) use ($show_all_shifts) {
/** @var JoinClause $join */
$join->on('ShiftEntry.SID', '=', 'shifts.id');
$join->on('shift_entries.shift_id', '=', 'shifts.id');
if (!$show_all_shifts) {
$join->where(function ($query) {
/** @var Builder $query */

View File

@ -40,11 +40,11 @@ function admin_free()
if ($request->has('submit')) {
$query = User::with('personalData')
->select('users.*')
->leftJoin('ShiftEntry', 'users.id', 'ShiftEntry.UID')
->leftJoin('shift_entries', 'users.id', 'shift_entries.user_id')
->leftJoin('users_state', 'users.id', 'users_state.user_id')
->leftJoin('shifts', function ($join) {
/** @var JoinClause $join */
$join->on('ShiftEntry.SID', '=', 'shifts.id')
$join->on('shift_entries.shift_id', '=', 'shifts.id')
->where('shifts.start', '<', Carbon::now())
->where('shifts.end', '>', Carbon::now());
})

View File

@ -189,15 +189,14 @@ function admin_rooms()
$shifts = $room->shifts;
foreach ($shifts as $shift) {
$shift = Shift($shift);
foreach ($shift->shiftEntry as $entry) {
$type = AngelType::find($entry['TID']);
foreach ($shift->shiftEntries as $entry) {
event('shift.entry.deleting', [
'user' => User::find($entry['user_id']),
'start' => $shift->start,
'end' => $shift->end,
'name' => $shift->shiftType->name,
'title' => $shift->title,
'type' => $type->name,
'type' => $entry->angelType->name,
'room' => $room,
'freeloaded' => (bool) $entry['freeloaded'],
]);

View File

@ -563,17 +563,16 @@ function admin_shifts_history(): string
foreach ($shifts as $shift) {
$shift = Shift($shift);
foreach ($shift->shiftEntry as $entry) {
$type = AngelType::find($entry['TID']);
foreach ($shift->shiftEntries as $entry) {
event('shift.entry.deleting', [
'user' => User::find($entry['user_id']),
'user' => $entry->user,
'start' => $shift->start,
'end' => $shift->end,
'name' => $shift->shiftType->name,
'title' => $shift->title,
'type' => $type->name,
'type' => $entry->angelType->name,
'room' => $shift->room,
'freeloaded' => (bool) $entry['freeloaded'],
'freeloaded' => $entry->freeloaded,
]);
}

View File

@ -266,15 +266,15 @@ class ImportSchedule extends BaseController
protected function fireDeleteShiftEntryEvents(Event $event): void
{
$shiftEntries = $this->db
->table('ShiftEntry')
->table('shift_entries')
->select([
'shift_types.name', 'shifts.title', 'angel_types.name AS type', 'rooms.id AS room_id',
'shifts.start', 'shifts.end', 'ShiftEntry.UID as user_id', 'ShiftEntry.freeloaded'
'shifts.start', 'shifts.end', 'shift_entries.user_id', 'shift_entries.freeloaded'
])
->join('shifts', 'shifts.id', 'ShiftEntry.SID')
->join('shifts', 'shifts.id', 'shift_entries.shift_id')
->join('schedule_shift', 'shifts.id', 'schedule_shift.shift_id')
->join('rooms', 'rooms.id', 'shifts.room_id')
->join('angel_types', 'angel_types.id', 'ShiftEntry.TID')
->join('angel_types', 'angel_types.id', 'shift_entries.angel_type_id')
->join('shift_types', 'shift_types.id', 'shifts.shift_type_id')
->where('schedule_shift.guid', $event->getGuid())
->get();

View File

@ -60,8 +60,8 @@ function make_ical_entry_from_shift(Shift $shift)
$output .= 'UID:' . md5($shift->start->timestamp . $shift->end->timestamp . $shift->shiftType->name) . "\r\n";
$output .= 'SUMMARY:' . str_replace("\n", "\\n", $shift->shiftType->name)
. ' (' . str_replace("\n", "\\n", $shift->title) . ")\r\n";
if (isset($shift->Comment)) {
$output .= 'DESCRIPTION:' . str_replace("\n", "\\n", $shift->Comment) . "\r\n";
if (isset($shift->user_comment)) {
$output .= 'DESCRIPTION:' . str_replace("\n", "\\n", $shift->user_comment) . "\r\n";
}
$output .= 'DTSTAMP:' . $shift->start->utc()->format('Ymd\THis\Z') . "\r\n";
$output .= 'DTSTART:' . $shift->start->utc()->format('Ymd\THis\Z') . "\r\n";

View File

@ -1,7 +1,6 @@
<?php
use Engelsystem\Database\Db;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
/**
@ -49,60 +48,38 @@ function user_myshifts()
]);
} elseif ($request->has('edit') && preg_match('/^\d+$/', $request->input('edit'))) {
$shift_entry_id = $request->input('edit');
$shift = Db::selectOne(
'
SELECT
`ShiftEntry`.`freeloaded`,
`ShiftEntry`.`freeload_comment`,
`ShiftEntry`.`Comment`,
`ShiftEntry`.`UID`,
`shift_types`.`name`,
`shifts`.*,
`angel_types`.`name` AS `angel_type`
FROM `ShiftEntry`
JOIN `angel_types` ON (`ShiftEntry`.`TID` = `angel_types`.`id`)
JOIN `shifts` ON (`ShiftEntry`.`SID` = `shifts`.`id`)
JOIN `shift_types` ON (`shift_types`.`id` = `shifts`.`shift_type_id`)
WHERE `ShiftEntry`.`id`=?
AND `UID`=?
LIMIT 1
',
[
$shift_entry_id,
$shifts_user->id,
]
);
if (!empty($shift)) {
/** @var Shift $shift */
$shift = (new Shift())->forceFill($shift);
$freeloaded = $shift->freeloaded;
$freeload_comment = $shift->freeloaded_comment;
/** @var ShiftEntry $shiftEntry */
$shiftEntry = ShiftEntry::where('id', $shift_entry_id)
->where('user_id', $shifts_user->id)
->with(['shift', 'shift.shiftType', 'shift.room', 'user'])
->first();
if (!empty($shiftEntry)) {
$shift = $shiftEntry->shift;
$freeloaded = $shiftEntry->freeloaded;
$freeloaded_comment = $shiftEntry->freeloaded_comment;
if ($request->hasPostData('submit')) {
$valid = true;
if (auth()->can('user_shifts_admin')) {
$freeloaded = $request->has('freeloaded');
$freeload_comment = strip_request_item_nl('freeload_comment');
if ($freeloaded && $freeload_comment == '') {
$freeloaded_comment = strip_request_item_nl('freeloaded_comment');
if ($freeloaded && $freeloaded_comment == '') {
$valid = false;
error(__('Please enter a freeload comment!'));
}
}
$comment = $shift->Comment;
$user_source = User::find($shift->UID);
$comment = $shiftEntry->user_comment;
$user_source = $shiftEntry->user;
if (auth()->user()->id == $user_source->id) {
$comment = strip_request_item_nl('comment');
}
if ($valid) {
ShiftEntry_update([
'id' => $shift_entry_id,
'Comment' => $comment,
'freeloaded' => $freeloaded,
'freeload_comment' => $freeload_comment
]);
$shiftEntry->user_comment = $comment;
$shiftEntry->freeloaded = $freeloaded;
$shiftEntry->freeloaded_comment = $freeloaded_comment;
$shiftEntry->save();
engelsystem_log(
'Updated ' . User_Nick_render($user_source, true) . '\'s shift '
@ -110,7 +87,7 @@ function user_myshifts()
. ' from ' . $shift->start->format('Y-m-d H:i')
. ' to ' . $shift->end->format('Y-m-d H:i')
. ' with comment ' . $comment
. '. Freeloaded: ' . ($freeloaded ? 'YES Comment: ' . $freeload_comment : 'NO')
. '. Freeloaded: ' . ($freeloaded ? 'YES Comment: ' . $freeloaded_comment : 'NO')
);
success(__('Shift saved.'));
throw_redirect(page_link_to('users', ['action' => 'view', 'user_id' => $shifts_user->id]));
@ -122,10 +99,10 @@ function user_myshifts()
$shift->start->format('Y-m-d H:i') . ', ' . shift_length($shift),
$shift->room->name,
$shift->shiftType->name,
$shift->angel_type,
$shift->Comment,
$shift->freeloaded,
$shift->freeload_comment,
$shiftEntry->angelType->name,
$shiftEntry->user_comment,
$shiftEntry->freeloaded,
$shiftEntry->freeloaded_comment,
auth()->can('user_shifts_admin')
);
} else {

View File

@ -31,7 +31,7 @@ function user_shifts()
{
$request = request();
if (User_is_freeloader(auth()->user())) {
if (auth()->user()->isFreeloader()) {
throw_redirect(page_link_to('user_myshifts'));
}

View File

@ -3,6 +3,8 @@
namespace Engelsystem;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Illuminate\Support\Collection;
class ShiftCalendarRenderer
{
@ -45,10 +47,10 @@ class ShiftCalendarRenderer
/**
* ShiftCalendarRenderer constructor.
*
* @param Shift[] $shifts
* @param array[] $needed_angeltypes
* @param array[] $shift_entries
* @param ShiftsFilter $shiftsFilter
* @param Shift[] $shifts
* @param array[] $needed_angeltypes
* @param ShiftEntry[][]|Collection $shift_entries
* @param ShiftsFilter $shiftsFilter
*/
public function __construct($shifts, private $needed_angeltypes, private $shift_entries, ShiftsFilter $shiftsFilter)
{
@ -204,7 +206,7 @@ class ShiftCalendarRenderer
/**
* Renders a tick/block for given time
*
* @param int $time unix timestamp
* @param int $time unix timestamp
* @param boolean $label Should time labels be generated?
* @return string rendered tick html
*/

View File

@ -4,7 +4,9 @@ namespace Engelsystem;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Illuminate\Support\Collection;
use function theme_type;
@ -16,10 +18,10 @@ class ShiftCalendarShiftRenderer
/**
* Renders a shift
*
* @param Shift $shift The shift to render
* @param array[] $needed_angeltypes
* @param array $shift_entries
* @param User $user The user who is viewing the shift calendar
* @param Shift $shift The shift to render
* @param array[] $needed_angeltypes
* @param ShiftEntry[]|Collection $shift_entries
* @param User $user The user who is viewing the shift calendar
* @return array
*/
public function render(Shift $shift, $needed_angeltypes, $shift_entries, $user)
@ -80,10 +82,10 @@ class ShiftCalendarShiftRenderer
}
/**
* @param Shift $shift
* @param array[] $needed_angeltypes
* @param array[] $shift_entries
* @param User $user
* @param Shift $shift
* @param array[] $needed_angeltypes
* @param ShiftEntry[]|Collection $shift_entries
* @param User $user
* @return array
*/
private function renderShiftNeededAngeltypes(Shift $shift, $needed_angeltypes, $shift_entries, $user)
@ -93,7 +95,7 @@ class ShiftCalendarShiftRenderer
$shift_entries_filtered[$needed_angeltype['id']] = [];
}
foreach ($shift_entries as $shift_entry) {
$shift_entries_filtered[$shift_entry['TID']][] = $shift_entry;
$shift_entries_filtered[$shift_entry->angel_type_id][] = $shift_entry;
}
$html = '';
@ -144,11 +146,11 @@ class ShiftCalendarShiftRenderer
/**
* Renders a list entry containing the needed angels for an angeltype
*
* @param Shift $shift The shift which is rendered
* @param array[] $shift_entries
* @param array $angeltype The angeltype, containing information about needed angeltypes
* @param Shift $shift The shift which is rendered
* @param ShiftEntry[]|Collection $shift_entries
* @param array $angeltype The angeltype, containing information about needed angeltypes
* and already signed up angels
* @param User $user The user who is viewing the shift calendar
* @param User $user The user who is viewing the shift calendar
* @return array
*/
private function renderShiftNeededAngeltype(Shift $shift, $shift_entries, $angeltype, $user)
@ -156,8 +158,8 @@ class ShiftCalendarShiftRenderer
$angeltype = (new AngelType())->forceFill($angeltype);
$entry_list = [];
foreach ($shift_entries as $entry) {
$class = $entry['freeloaded'] ? 'text-decoration-line-through' : '';
$entry_list[] = '<span class="text-nowrap ' . $class . '">' . User_Nick_render($entry) . '</span>';
$class = $entry->freeloaded ? 'text-decoration-line-through' : '';
$entry_list[] = '<span class="text-nowrap ' . $class . '">' . User_Nick_render($entry->user) . '</span>';
}
$shift_signup_state = Shift_signup_allowed(
$user,

View File

@ -39,11 +39,11 @@ function ShiftEntry_delete_view_admin(Shift $shift, AngelType $angeltype, User $
*
* @param Shift $shift
* @param AngelType $angeltype
* @param int $signoff_user_id
* @param User $signoff_user
*
* @return string HTML
*/
function ShiftEntry_delete_view(Shift $shift, AngelType $angeltype, $signoff_user_id)
function ShiftEntry_delete_view(Shift $shift, AngelType $angeltype, User $signoff_user)
{
return page_with_title(ShiftEntry_delete_title(), [
info(sprintf(
@ -56,7 +56,7 @@ function ShiftEntry_delete_view(Shift $shift, AngelType $angeltype, $signoff_use
form([
buttons([
button(user_link($signoff_user_id), icon('x-lg') . __('cancel')),
button(user_link($signoff_user->id), icon('x-lg') . __('cancel')),
form_submit('delete', icon('trash') . __('delete'), 'btn-danger', false),
]),
]),
@ -180,7 +180,7 @@ function ShiftEntry_create_title()
* @param string $type
* @param string $comment
* @param bool $freeloaded
* @param string $freeload_comment
* @param string $freeloaded_comment
* @param bool $user_admin_shifts
* @return string
*/
@ -192,7 +192,7 @@ function ShiftEntry_edit_view(
$type,
$comment,
$freeloaded,
$freeload_comment,
$freeloaded_comment,
$user_admin_shifts = false
) {
$freeload_form = [];
@ -200,9 +200,9 @@ function ShiftEntry_edit_view(
$freeload_form = [
form_checkbox('freeloaded', __('Freeloaded'), $freeloaded),
form_textarea(
'freeload_comment',
'freeloaded_comment',
__('Freeload comment (Only for shift coordination):'),
$freeload_comment
$freeloaded_comment
)
];
}

View File

@ -3,8 +3,8 @@
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\Shifts\ShiftType;
use Engelsystem\Models\User\User;
use Engelsystem\Models\UserAngelType;
use Engelsystem\ShiftSignupState;
use Illuminate\Support\Collection;
@ -75,8 +75,8 @@ function Shift_editor_info_render(Shift $shift)
}
/**
* @param Shift $shift
* @param AngelType $angeltype
* @param Shift $shift
* @param AngelType $angeltype
* @return string
*/
function Shift_signup_button_render(Shift $shift, AngelType $angeltype)
@ -136,16 +136,16 @@ function Shift_view(Shift $shift, ShiftType $shifttype, Room $room, $angeltypes_
$needed_angels .= Shift_view_render_needed_angeltype($needed_angeltype, $angeltypes, $shift, $user_shift_admin);
}
$shiftEntry = new Collection($shift->shiftEntry);
foreach ($shiftEntry->groupBy('TID') as $angelTypes) {
$shiftEntry = $shift->shiftEntries;
foreach ($shiftEntry->groupBy('angel_type_id') as $angelTypes) {
/** @var Collection $angelTypes */
$type = $angelTypes->first()['TID'];
if (!$neededAngels->where('TID', $type)->first()) {
$type = $angelTypes->first()['angel_type_id'];
if (!$neededAngels->where('angel_type_id', $type)->first()) {
$needed_angels .= Shift_view_render_needed_angeltype([
'TID' => $type,
'count' => 0,
'restricted' => true,
'taken' => $angelTypes->count(),
'angel_type_id' => $type,
'count' => 0,
'restricted' => true,
'taken' => $angelTypes->count(),
], $angeltypes, $shift, $user_shift_admin);
}
}
@ -213,7 +213,7 @@ function Shift_view(Shift $shift, ShiftType $shifttype, Room $room, $angeltypes_
*/
function Shift_view_render_needed_angeltype($needed_angeltype, $angeltypes, Shift $shift, $user_shift_admin)
{
$angeltype = $angeltypes[$needed_angeltype['TID']];
$angeltype = $angeltypes[$needed_angeltype['angel_type_id']];
$angeltype_supporter = auth()->user()->isAngelTypeSupporter($angeltype)
|| auth()->can('admin_user_angeltypes');
@ -242,8 +242,8 @@ function Shift_view_render_needed_angeltype($needed_angeltype, $angeltypes, Shif
);
$angels = [];
foreach ($shift->shiftEntry as $shift_entry) {
if ($shift_entry['TID'] == $needed_angeltype['TID']) {
foreach ($shift->shiftEntries as $shift_entry) {
if ($shift_entry->angel_type_id == $needed_angeltype['angel_type_id']) {
$angels[] = Shift_view_render_shift_entry($shift_entry, $user_shift_admin, $angeltype_supporter, $shift);
}
}
@ -255,30 +255,30 @@ function Shift_view_render_needed_angeltype($needed_angeltype, $angeltypes, Shif
}
/**
* @param array $shift_entry
* @param ShiftEntry $shift_entry
* @param bool $user_shift_admin
* @param bool $angeltype_supporter
* @param Shift $shift
* @return string
*/
function Shift_view_render_shift_entry($shift_entry, $user_shift_admin, $angeltype_supporter, Shift $shift)
function Shift_view_render_shift_entry(ShiftEntry $shift_entry, $user_shift_admin, $angeltype_supporter, Shift $shift)
{
$entry = User_Nick_render(User::find($shift_entry['UID']));
if ($shift_entry['freeloaded']) {
$entry = User_Nick_render($shift_entry->user);
if ($shift_entry->freeloaded) {
$entry = '<del>' . $entry . '</del>';
}
$isUser = $shift_entry['UID'] == auth()->user()->id;
$isUser = $shift_entry->user_id == auth()->user()->id;
if ($user_shift_admin || $angeltype_supporter || $isUser) {
$entry .= ' <div class="btn-group m-1">';
if ($user_shift_admin || $isUser) {
$entry .= button_icon(
page_link_to('user_myshifts', ['edit' => $shift_entry['id'], 'id' => $shift_entry['UID']]),
page_link_to('user_myshifts', ['edit' => $shift_entry->id, 'id' => $shift_entry->user_id]),
'pencil',
'btn-sm'
);
}
$angeltype = AngelType::find($shift_entry['TID']);
$disabled = Shift_signout_allowed($shift, $angeltype, $shift_entry['UID']) ? '' : ' btn-disabled';
$angeltype = $shift_entry->angelType;
$disabled = Shift_signout_allowed($shift, $angeltype, $shift_entry->user_id) ? '' : ' btn-disabled';
$entry .= button_icon(shift_entry_delete_link($shift_entry), 'trash', 'btn-sm' . $disabled);
$entry .= '</div>';
}

View File

@ -4,6 +4,7 @@ use Carbon\Carbon;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Group;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Engelsystem\Models\Worklog;
use Illuminate\Support\Collection;
@ -197,14 +198,15 @@ function User_shift_state_render($user)
}
$upcoming_shifts = ShiftEntries_upcoming_for_user($user);
if (empty($upcoming_shifts)) {
if ($upcoming_shifts->isEmpty()) {
return '<span class="text-success">' . __('Free') . '</span>';
}
$nextShift = array_shift($upcoming_shifts);
/** @var ShiftEntry $nextShiftEntry */
$nextShiftEntry = $upcoming_shifts->first();
$start = Carbon::make($nextShift['start']);
$end = Carbon::make($nextShift['end']);
$start = $nextShiftEntry->shift->start;
$end = $nextShiftEntry->shift->end;
$startFormat = $start->format(__('Y-m-d H:i'));
$endFormat = $end->format(__('Y-m-d H:i'));
$startTimestamp = $start->timestamp;
@ -244,8 +246,9 @@ function User_last_shift_render($user)
return '';
}
$lastShift = array_shift($last_shifts);
$end = Carbon::make($lastShift['end']);
/** @var ShiftEntry $lastShiftEntry */
$lastShiftEntry = $last_shifts->first();
$end = $lastShiftEntry->shift->end;
return '<span title="' . $end->format(__('Y-m-d H:i')) . '" data-countdown-ts="' . $end->timestamp . '">'
. __('Shift ended %c')
@ -307,7 +310,7 @@ function User_view_myshift(Shift $shift, $user_source, $its_me)
];
if ($its_me) {
$myshift['comment'] = $shift->Comment;
$myshift['comment'] = $shift->user_comment;
}
if ($shift->freeloaded) {
@ -316,7 +319,7 @@ function User_view_myshift(Shift $shift, $user_source, $its_me)
. '</p>';
if (auth()->can('user_shifts_admin')) {
$myshift['comment'] .= '<br />'
. '<p class="text-danger">' . __('Freeloaded') . ': ' . $shift->freeload_comment . '</p>';
. '<p class="text-danger">' . __('Freeloaded') . ': ' . $shift->freeloaded_comment . '</p>';
} else {
$myshift['comment'] .= '<br /><p class="text-danger">' . __('Freeloaded') . '</p>';
}
@ -332,7 +335,8 @@ function User_view_myshift(Shift $shift, $user_source, $its_me)
'btn-sm'
);
}
if (Shift_signout_allowed($shift, (new AngelType())->forceFill(['id' => $shift->TID]), $user_source->id)) {
if (Shift_signout_allowed($shift, (new AngelType())->forceFill(['id' => $shift->angel_type_id]), $user_source->id)) {
$myshift['actions'][] = button(
shift_entry_delete_link($shift),
icon('trash') . __('sign off'),
@ -517,7 +521,7 @@ function User_view(
return page_with_title(
'<span class="icon-icon_angel"></span> '
. (
(config('enable_pronoun') && $user_source->personalData->pronoun)
(config('enable_pronoun') && $user_source->personalData->pronoun)
? '<small>' . htmlspecialchars($user_source->personalData->pronoun) . '</small> '
: ''
)
@ -554,7 +558,7 @@ function User_view(
),
icon('valentine') . __('Vouchers')
)
: '',
: '',
$admin_user_worklog_privilege ? button(
url('/admin/user/' . $user_source->id . '/worklog'),
icon('clock-history') . __('worklog.add')
@ -572,13 +576,13 @@ function User_view(
icon('braces') . __('JSON Export')
) : '',
($its_me && (
$auth->can('shifts_json_export')
|| $auth->can('ical')
|| $auth->can('atom')
)) ? button(
page_link_to('user_myshifts', ['reset' => 1]),
icon('arrow-repeat') . __('Reset API key')
) : ''
$auth->can('shifts_json_export')
|| $auth->can('ical')
|| $auth->can('atom')
)) ? button(
page_link_to('user_myshifts', ['reset' => 1]),
icon('arrow-repeat') . __('Reset API key')
) : ''
])
])
]),
@ -591,7 +595,7 @@ function User_view(
. $user_source->contact->dect
. '</a>'
)
: '' ,
: '',
config('enable_mobile_show') && $user_source->contact->mobile ?
$user_source->settings->mobile_show ?
heading(
@ -600,15 +604,15 @@ function User_view(
. $user_source->contact->mobile
. '</a>'
)
: ''
: '' ,
: ''
: '',
$auth->can('user_messages') ?
heading(
'<a href="' . page_link_to('/messages/' . $user_source->id) . '">'
. icon('envelope')
. '</a>'
)
: '' ,
: '',
]),
User_view_state($admin_user_privilege, $freeloader, $user_source),
User_angeltypes_render($user_angeltypes),
@ -794,8 +798,8 @@ function User_oauth_render(User $user)
foreach ($user->oauth as $oauth) {
$output[] = __(
isset($config[$oauth->provider]['name'])
? $config[$oauth->provider]['name']
: Str::ucfirst($oauth->provider)
? $config[$oauth->provider]['name']
: Str::ucfirst($oauth->provider)
);
}
@ -887,7 +891,7 @@ function render_user_departure_date_hint()
*/
function render_user_freeloader_hint()
{
if (User_is_freeloader(auth()->user())) {
if (auth()->user()->isFreeloader()) {
return sprintf(
__('You freeloaded at least %s shifts. Shift signup is locked. Please go to heavens desk to be unlocked again.'),
config('max_freeloadable_shifts')

View File

@ -45,27 +45,25 @@ class Stats
$query = State::whereArrived(true);
if (!is_null($working)) {
// @codeCoverageIgnoreStart
$query
->leftJoin('worklogs', 'worklogs.user_id', '=', 'users_state.user_id')
->leftJoin('ShiftEntry', 'ShiftEntry.UID', '=', 'users_state.user_id')
->leftJoin('shift_entries', 'shift_entries.user_id', '=', 'users_state.user_id')
->distinct();
$query->where(function ($query) use ($working): void {
/** @var QueryBuilder $query */
if ($working) {
$query
->whereNotNull('ShiftEntry.SID')
->whereNotNull('shift_entries.shift_id')
->orWhereNotNull('worklogs.hours');
return;
}
$query
->whereNull('ShiftEntry.SID')
->whereNull('shift_entries.shift_id')
->whereNull('worklogs.hours');
});
// @codeCoverageIgnoreEnd
}
return $query->count('users_state.user_id');
@ -104,18 +102,17 @@ class Stats
* The number of currently working users
*
* @param bool|null $freeloaded
* @codeCoverageIgnore
*/
public function currentlyWorkingUsers(bool $freeloaded = null): int
{
$query = User::query()
->join('ShiftEntry', 'ShiftEntry.UID', '=', 'users.id')
->join('shifts', 'shifts.id', '=', 'ShiftEntry.SID')
->join('shift_entries', 'shift_entries.user_id', '=', 'users.id')
->join('shifts', 'shifts.id', '=', 'shift_entries.shift_id')
->where('shifts.start', '<=', Carbon::now())
->where('shifts.end', '>', Carbon::now());
if (!is_null($freeloaded)) {
$query->where('ShiftEntry.freeloaded', '=', $freeloaded);
$query->where('shift_entries.freeloaded', '=', $freeloaded);
}
return $query->count();
@ -202,13 +199,13 @@ class Stats
* @param bool|null $done
* @param bool|null $freeloaded
*
* @codeCoverageIgnore
* @codeCoverageIgnore because it is only used in functions that use TIMESTAMPDIFF
*/
protected function workSecondsQuery(bool $done = null, bool $freeloaded = null): QueryBuilder
{
$query = $this
->getQuery('ShiftEntry')
->join('shifts', 'shifts.id', '=', 'ShiftEntry.SID');
->getQuery('shift_entries')
->join('shifts', 'shifts.id', '=', 'shift_entries.shift_id');
if (!is_null($freeloaded)) {
$query->where('freeloaded', '=', $freeloaded);
@ -227,13 +224,13 @@ class Stats
* @param bool|null $done
* @param bool|null $freeloaded
*
* @codeCoverageIgnore
* @codeCoverageIgnore as TIMESTAMPDIFF is not implemented in SQLite
*/
public function workSeconds(bool $done = null, bool $freeloaded = null): int
{
$query = $this->workSecondsQuery($done, $freeloaded);
return (int) $query->sum($this->raw('end - start'));
return (int) $query->sum($this->raw('TIMESTAMPDIFF(MINUTE, start, end) * 60'));
}
/**
@ -242,22 +239,19 @@ class Stats
* @param bool|null $done
* @param bool|null $freeloaded
*
* @codeCoverageIgnore
* @codeCoverageIgnore as TIMESTAMPDIFF is not implemented in SQLite
*/
public function workBuckets(array $buckets, bool $done = null, bool $freeloaded = null): array
{
return $this->getBuckets(
$buckets,
$this->workSecondsQuery($done, $freeloaded),
'UID',
'SUM(end - start)',
'SUM(end - start)'
'user_id',
'SUM(TIMESTAMPDIFF(MINUTE, start, end) * 60)',
'SUM(TIMESTAMPDIFF(MINUTE, start, end) * 60)'
);
}
/**
* @codeCoverageIgnore As long as its only used for old tables
*/
protected function getBuckets(
array $buckets,
BuilderContract $basicQuery,
@ -281,18 +275,12 @@ class Stats
return $return;
}
/**
* @codeCoverageIgnore
*/
public function worklogSeconds(): int
{
return (int) Worklog::query()
->sum($this->raw('hours * 60 * 60'));
}
/**
* @codeCoverageIgnore
*/
public function worklogBuckets(array $buckets): array
{
return $this->getBuckets(

View File

@ -4,28 +4,31 @@ declare(strict_types=1);
namespace Engelsystem\Models;
use Engelsystem\Models\Shifts\ShiftEntry;
use Illuminate\Database\Eloquent\Builder;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Query\Builder as QueryBuilder;
/**
* @property int $id
* @property string $name
* @property string $description
* @property string $contact_name
* @property string $contact_dect
* @property string $contact_email
* @property boolean $restricted # If users need an introduction
* @property boolean $requires_driver_license # If users must have a driver license
* @property boolean $no_self_signup # Users can sign up for shifts
* @property boolean $show_on_dashboard # Show on public dashboard
* @property boolean $hide_register # Hide from registration page
* @property int $id
* @property string $name
* @property string $description
* @property string $contact_name
* @property string $contact_dect
* @property string $contact_email
* @property boolean $restricted # If users need an introduction
* @property boolean $requires_driver_license # If users must have a driver license
* @property boolean $no_self_signup # Users can sign up for shifts
* @property boolean $show_on_dashboard # Show on public dashboard
* @property boolean $hide_register # Hide from registration page
*
* @property-read Collection|User[] $userAngelTypes
* @property-read UserAngelType $pivot
* @property-read UserAngelType $pivot
* @property-read Collection|ShiftEntry[] $shiftEntries
* @property-read Collection|User[] $userAngelTypes
*
* @method static QueryBuilder|AngelType[] whereId($value)
* @method static QueryBuilder|AngelType[] whereName($value)
@ -68,6 +71,11 @@ class AngelType extends BaseModel
'hide_register' => 'boolean',
];
public function shiftEntries(): HasMany
{
return $this->hasMany(ShiftEntry::class);
}
public function userAngelTypes(): BelongsToMany
{
return $this

View File

@ -8,31 +8,34 @@ use Carbon\Carbon;
use Engelsystem\Models\BaseModel;
use Engelsystem\Models\Room;
use Engelsystem\Models\User\User;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Illuminate\Database\Query\Builder as QueryBuilder;
/**
* @property int $id
* @property string $title
* @property string $description
* @property string $url
* @property Carbon $start
* @property Carbon $end
* @property int $shift_type_id
* @property int $room_id
* @property string $transaction_id
* @property int $created_by
* @property int|null $updated_by
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
* @property int $id
* @property string $title
* @property string $description
* @property string $url
* @property Carbon $start
* @property Carbon $end
* @property int $shift_type_id
* @property int $room_id
* @property string $transaction_id
* @property int $created_by
* @property int|null $updated_by
* @property Carbon|null $created_at
* @property Carbon|null $updated_at
*
* @property-read QueryBuilder|Schedule $schedule
* @property-read QueryBuilder|ShiftType $shiftType
* @property-read QueryBuilder|Room $room
* @property-read QueryBuilder|User $createdBy
* @property-read QueryBuilder|User|null $updatedBy
* @property-read QueryBuilder|Schedule $schedule
* @property-read QueryBuilder|Collection|ShiftEntry[] $shiftEntries
* @property-read QueryBuilder|ShiftType $shiftType
* @property-read QueryBuilder|Room $room
* @property-read QueryBuilder|User $createdBy
* @property-read QueryBuilder|User|null $updatedBy
*
* @method static QueryBuilder|Shift[] whereId($value)
* @method static QueryBuilder|Shift[] whereTitle($value)
@ -88,6 +91,11 @@ class Shift extends BaseModel
return $this->hasOneThrough(Schedule::class, ScheduleShift::class, null, 'id', null, 'schedule_id');
}
public function shiftEntries(): HasMany
{
return $this->hasMany(ShiftEntry::class);
}
public function shiftType(): BelongsTo
{
return $this->belongsTo(ShiftType::class);

View File

@ -0,0 +1,63 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Models\Shifts;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\BaseModel;
use Engelsystem\Models\User\UsesUserModel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Query\Builder as QueryBuilder;
/**
* @property int $id
* @property int $shift_id
* @property int $angel_type_id
* @property string $user_comment
* @property bool $freeloaded
* @property string $freeloaded_comment
*
* @property-read Shift $shift
* @property-read AngelType $angelType
*
* @method static QueryBuilder|ShiftEntry[] whereId($value)
* @method static QueryBuilder|ShiftEntry[] whereShiftId($value)
* @method static QueryBuilder|ShiftEntry[] whereAngelTypeId($value)
* @method static QueryBuilder|ShiftEntry[] whereUserComment($value)
* @method static QueryBuilder|ShiftEntry[] whereFreeloaded($value)
* @method static QueryBuilder|ShiftEntry[] whereFreeloadedComment($value)
*/
class ShiftEntry extends BaseModel
{
use HasFactory;
use UsesUserModel;
/** @var array<string> */
protected $fillable = [ // phpcs:ignore
'shift_id',
'angel_type_id',
'user_id',
'user_comment',
'freeloaded',
'freeloaded_comment',
];
/** @var array<string, string|bool> default attributes */
protected $attributes = [ // phpcs:ignore
'user_comment' => '',
'freeloaded' => false,
'freeloaded_comment' => '',
];
public function shift(): BelongsTo
{
return $this->belongsTo(Shift::class);
}
public function angelType(): BelongsTo
{
return $this->belongsTo(AngelType::class);
}
}

View File

@ -13,6 +13,7 @@ use Engelsystem\Models\OAuth;
use Engelsystem\Models\Privilege;
use Engelsystem\Models\Question;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\UserAngelType;
use Engelsystem\Models\Worklog;
use Illuminate\Database\Eloquent\Builder;
@ -47,6 +48,7 @@ use Illuminate\Support\Collection as SupportCollection;
* @property-read SupportCollection|Privilege[] $privileges
* @property-read Collection|AngelType[] $userAngelTypes
* @property-read UserAngelType $pivot
* @property-read Collection|ShiftEntry[] $shiftEntries
* @property-read Collection|Worklog[] $worklogs
* @property-read Collection|Worklog[] $worklogsCreated
* @property-read Collection|Question[] $questionsAsked
@ -109,6 +111,14 @@ class User extends BaseModel
return $this->belongsToMany(Group::class, 'users_groups');
}
public function isFreeloader(): bool
{
return $this->shiftEntries()
->where('freeloaded', true)
->count()
>= config('max_freeloadable_shifts');
}
public function license(): HasOne
{
return $this
@ -189,6 +199,11 @@ class User extends BaseModel
return $this->hasMany(OAuth::class);
}
public function shiftEntries(): HasMany
{
return $this->hasMany(ShiftEntry::class);
}
public function worklogs(): HasMany
{
return $this->hasMany(Worklog::class);

View File

@ -76,8 +76,10 @@ class TwigServiceProvider extends ServiceProvider
$this->app->instance('twig.loader', $twigLoader);
$cache = $this->app->get('path.cache.views');
$twigDebug = false;
if ($config->get('environment') == 'development') {
$cache = false;
$twigDebug = true;
}
$twig = $this->app->make(
@ -86,7 +88,8 @@ class TwigServiceProvider extends ServiceProvider
'options' => [
'cache' => $cache,
'auto_reload' => true,
'strict_variables' => ($config->get('environment') == 'development'),
'debug' => $twigDebug,
'strict_variables' => $twigDebug,
],
]
);

View File

@ -13,6 +13,7 @@ use Engelsystem\Models\OAuth;
use Engelsystem\Models\Question;
use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\License;
use Engelsystem\Models\User\PasswordReset;
use Engelsystem\Models\User\PersonalData;
@ -163,6 +164,35 @@ class StatsTest extends TestCase
$this->assertEquals(2.4 * 60 * 60 + 1.2 * 60 * 60, $seconds);
}
/**
* @covers \Engelsystem\Controllers\Metrics\Stats::worklogBuckets
* @covers \Engelsystem\Controllers\Metrics\Stats::getBuckets
*/
public function testWorklogBuckets(): void
{
Worklog::factory()->create(['hours' => 1.2, 'worked_at' => Carbon::now()->subDay()]);
Worklog::factory()->create(['hours' => 1.9, 'worked_at' => Carbon::now()->subDay()]);
Worklog::factory()->create(['hours' => 3, 'worked_at' => Carbon::now()->subDay()]);
Worklog::factory()->create(['hours' => 10, 'worked_at' => Carbon::now()->subDay()]);
$stats = new Stats($this->database);
$buckets = $stats->worklogBuckets([
1 * 60 * 60,
2 * 60 * 60,
3 * 60 * 60,
4 * 60 * 60,
'+Inf'
]);
$this->assertEquals([
3600 => 0,
7200 => 2,
10800 => 3,
14400 => 3,
'+Inf' => 4,
], $buckets);
}
/**
* @covers \Engelsystem\Controllers\Metrics\Stats::rooms
*/
@ -251,9 +281,13 @@ class StatsTest extends TestCase
public function testArrivedUsers(): void
{
$this->addUsers();
ShiftEntry::factory()->create(['user_id' => 3]);
ShiftEntry::factory()->create(['user_id' => 4]);
$stats = new Stats($this->database);
$this->assertEquals(7, $stats->arrivedUsers());
$this->assertEquals(5, $stats->arrivedUsers(false));
$this->assertEquals(2, $stats->arrivedUsers(true));
}
/**
@ -293,6 +327,25 @@ class StatsTest extends TestCase
$this->assertEquals(1, $stats->email('news'));
}
/**
* @covers \Engelsystem\Controllers\Metrics\Stats::currentlyWorkingUsers
*/
public function testCurrentlyWorkingUsers(): void
{
$this->addUsers();
/** @var Shift $shift */
$shift = Shift::factory()->create(['start' => Carbon::now()->subHour(), 'end' => Carbon::now()->addHour()]);
ShiftEntry::factory()->create(['shift_id' => $shift->id]);
ShiftEntry::factory()->create(['shift_id' => $shift->id]);
ShiftEntry::factory()->create(['shift_id' => $shift->id, 'freeloaded' => true]);
$stats = new Stats($this->database);
$this->assertEquals(3, $stats->currentlyWorkingUsers());
$this->assertEquals(2, $stats->currentlyWorkingUsers(false));
$this->assertEquals(1, $stats->currentlyWorkingUsers(true));
}
/**
* @covers \Engelsystem\Controllers\Metrics\Stats::faq
*/

View File

@ -10,6 +10,7 @@ use Engelsystem\Models\Question;
use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\Schedule;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\License;
use Engelsystem\Models\User\PasswordReset;
@ -41,6 +42,7 @@ class FactoriesTest extends TestCase
[Question::class],
[Room::class],
[Schedule::class],
[ShiftEntry::class],
[Settings::class],
[Shift::class],
[State::class],

View File

@ -3,6 +3,7 @@
namespace Engelsystem\Test\Unit\Models;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Engelsystem\Models\UserAngelType;
@ -47,8 +48,7 @@ class AngelTypeTest extends ModelTest
User::factory(1)->create();
$user2 = User::factory()->create();
$angelType = new AngelType(['name' => 'Test']);
$angelType->save();
$angelType = AngelType::create(['name' => 'Test']);
$angelType->userAngelTypes()->attach($user1);
$angelType->userAngelTypes()->attach($user2);
@ -61,6 +61,18 @@ class AngelTypeTest extends ModelTest
$this->assertCount(2, $angeltypes);
}
/**
* @covers \Engelsystem\Models\AngelType::shiftEntries
*/
public function testShiftEntries(): void
{
$angelType = AngelType::create(['name' => 'test type']);
ShiftEntry::factory(3)->create(['angel_type_id' => $angelType->id]);
$this->assertCount(3, $angelType->shiftEntries);
}
/**
* @covers \Engelsystem\Models\AngelType::boot
*/

View File

@ -0,0 +1,37 @@
<?php
namespace Engelsystem\Test\Unit\Models\Shifts;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\Models\ModelTest;
class ShiftEntryTest extends ModelTest
{
/**
* @covers \Engelsystem\Models\Shifts\ShiftEntry::shift
* @covers \Engelsystem\Models\Shifts\ShiftEntry::angelType
*/
public function testShift(): void
{
/** @var Shift $shift */
$shift = Shift::factory()->create();
/** @var AngelType $angelType */
$angelType = AngelType::factory()->create();
/** @var User $user */
$user = User::factory()->create();
$model = new ShiftEntry();
$model->shift()->associate($shift);
$model->angelType()->associate($angelType);
$model->user()->associate($user);
$model->save();
$model = ShiftEntry::find(1);
$this->assertEquals($shift->id, $model->shift->id);
$this->assertEquals($angelType->id, $model->angelType->id);
$this->assertEquals($user->id, $model->user->id);
}
}

View File

@ -9,6 +9,7 @@ use Engelsystem\Models\Room;
use Engelsystem\Models\Shifts\Schedule;
use Engelsystem\Models\Shifts\ScheduleShift;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\Shifts\ShiftType;
use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\Models\ModelTest;
@ -73,4 +74,18 @@ class ShiftTest extends ModelTest
$this->assertEquals(1, Shift::find(2)->schedule->id);
$this->assertEquals(1, Shift::find(3)->schedule->id);
}
/**
* @covers \Engelsystem\Models\Shifts\Shift::shiftEntries
*/
public function testShiftEntries(): void
{
/** @var Shift $shift */
$shift = Shift::factory()->make();
$shift->save();
ShiftEntry::factory(5)->create(['shift_id' => $shift->id]);
$this->assertCount(5, $shift->shiftEntries);
}
}

View File

@ -4,6 +4,7 @@ namespace Engelsystem\Test\Unit\Models\User;
use Carbon\Carbon;
use DMS\PHPUnitExtensions\ArraySubset\ArraySubsetAsserts;
use Engelsystem\Config\Config;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\BaseModel;
use Engelsystem\Models\Group;
@ -13,6 +14,7 @@ use Engelsystem\Models\OAuth;
use Engelsystem\Models\Privilege;
use Engelsystem\Models\Question;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\User\Contact;
use Engelsystem\Models\User\HasUserModel;
use Engelsystem\Models\User\License;
@ -209,6 +211,30 @@ class UserTest extends ModelTest
$this->assertEquals($relatedModelIds, $user->{$name}->modelKeys());
}
/**
* @covers \Engelsystem\Models\User\User::isFreeloader
*/
public function testIsFreeloader(): void
{
$this->app->instance('config', new Config([
'max_freeloadable_shifts' => 2,
]));
$user = new User($this->data);
$user->save();
$this->assertFalse($user->isFreeloader());
ShiftEntry::factory()->create(['user_id' => $user->id]);
ShiftEntry::factory()->create(['user_id' => $user->id, 'freeloaded' => true]);
$this->assertFalse($user->isFreeloader());
ShiftEntry::factory()->create(['user_id' => $user->id, 'freeloaded' => true]);
$this->assertTrue($user->isFreeloader());
ShiftEntry::factory()->create(['user_id' => $user->id, 'freeloaded' => true]);
$this->assertTrue($user->isFreeloader());
}
/**
* @covers \Engelsystem\Models\User\User::userAngelTypes
*/
@ -334,6 +360,19 @@ class UserTest extends ModelTest
$this->assertCount(1, $oauth);
}
/**
* @covers \Engelsystem\Models\User\User::shiftEntries
*/
public function testShiftEntries(): void
{
$user = new User($this->data);
$user->save();
ShiftEntry::factory(2)->create(['user_id' => $user->id]);
$this->assertCount(2, $user->shiftEntries);
}
/**
* @covers \Engelsystem\Models\User\User::worklogs
*/

View File

@ -40,7 +40,7 @@ abstract class ExtensionTest extends TestCase
*/
protected function assertExtensionExists(
string $name,
callable $callback,
mixed $callback,
array $functions,
array $options = []
): void {

View File

@ -136,7 +136,12 @@ class TwigServiceProviderTest extends ServiceProviderTest
->method('make')
->withConsecutive(
[TwigLoader::class, ['paths' => $viewsPath]],
[Twig::class, ['options' => ['cache' => false, 'auto_reload' => true, 'strict_variables' => true]]],
[Twig::class, ['options' => [
'cache' => false,
'auto_reload' => true,
'strict_variables' => true,
'debug' => true,
]]],
[TwigEngine::class]
)->willReturnOnConsecutiveCalls(
$twigLoader,
@ -162,10 +167,10 @@ class TwigServiceProviderTest extends ServiceProviderTest
$this->setExpects($app, 'tag', ['renderer.twigEngine', ['renderer.engine']]);
$config->expects($this->exactly(3))
$config->expects($this->exactly(2))
->method('get')
->withConsecutive(['environment'], ['environment'], ['timezone'])
->willReturnOnConsecutiveCalls('development', 'development', 'The/World');
->withConsecutive(['environment'], ['timezone'])
->willReturnOnConsecutiveCalls('development', 'The/World');
$twig->expects($this->once())
->method('getExtension')