Implemented loading angels by shift type on schedule import

This commit is contained in:
Igor Scheller 2023-12-20 18:08:50 +01:00 committed by msquare
parent 909f7bba5a
commit 4bbeb93d64
23 changed files with 467 additions and 62 deletions

View File

@ -17,11 +17,12 @@ class NeededAngelTypeFactory extends Factory
public function definition(): array public function definition(): array
{ {
$forLocation = $this->faker->boolean(); $type = $this->faker->numberBetween(0, 2);
return [ return [
'location_id' => $forLocation ? Location::factory() : null, 'location_id' => $type == 0 ? Location::factory() : null,
'shift_id' => $forLocation ? null : Shift::factory(), 'shift_id' => $type == 1 ? null : Shift::factory(),
'shift_type_id' => $type == 2 ? null : Shift::factory(),
'angel_type_id' => AngelType::factory(), 'angel_type_id' => AngelType::factory(),
'count' => $this->faker->numberBetween(1, 5), 'count' => $this->faker->numberBetween(1, 5),
]; ];

View File

@ -18,6 +18,7 @@ class ScheduleFactory extends Factory
'name' => $this->faker->unique()->words(4, true), 'name' => $this->faker->unique()->words(4, true),
'url' => $this->faker->parse('https://{{safeEmailDomain}}/{{slug}}.xml'), 'url' => $this->faker->parse('https://{{safeEmailDomain}}/{{slug}}.xml'),
'shift_type' => $this->faker->numberBetween(1, 5), 'shift_type' => $this->faker->numberBetween(1, 5),
'needed_from_shift_type' => $this->faker->boolean(.2),
'minutes_before' => 15, 'minutes_before' => 15,
'minutes_after' => 15, 'minutes_after' => 15,
]; ];

View File

@ -0,0 +1,40 @@
<?php
declare(strict_types=1);
namespace Engelsystem\Migrations;
use Engelsystem\Database\Migration\Migration;
use Illuminate\Database\Schema\Blueprint;
class ScheduleShiftTypeNeededAngelTypes extends Migration
{
use Reference;
/**
* Run the migration
*/
public function up(): void
{
$this->schema->table('schedules', function (Blueprint $table): void {
$table->boolean('needed_from_shift_type')->after('shift_type')->default(false);
});
$this->schema->table('needed_angel_types', function (Blueprint $table): void {
$this->references($table, 'shift_types')->after('shift_id')->nullable();
});
}
/**
* Reverse the migration
*/
public function down(): void
{
$this->schema->table('schedules', function (Blueprint $table): void {
$table->dropColumn('needed_from_shift_type');
});
$this->schema->table('needed_angel_types', function (Blueprint $table): void {
$table->dropForeign('needed_angel_types_shift_type_id_foreign');
$table->dropColumn('shift_type_id');
});
}
}

View File

@ -74,7 +74,7 @@ function shift_edit_controller()
$angeltypes = AngelType::all()->pluck('name', 'id')->toArray(); $angeltypes = AngelType::all()->pluck('name', 'id')->toArray();
$shifttypes = ShiftType::all()->pluck('name', 'id')->toArray(); $shifttypes = ShiftType::all()->pluck('name', 'id')->toArray();
$needed_angel_types = collect(NeededAngelTypes_by_shift($shift_id))->pluck('count', 'angel_type_id')->toArray(); $needed_angel_types = collect(NeededAngelTypes_by_shift($shift))->pluck('count', 'angel_type_id')->toArray();
foreach (array_keys($angeltypes) as $angeltype_id) { foreach (array_keys($angeltypes) as $angeltype_id) {
if (!isset($needed_angel_types[$angeltype_id])) { if (!isset($needed_angel_types[$angeltype_id])) {
$needed_angel_types[$angeltype_id] = 0; $needed_angel_types[$angeltype_id] = 0;

View File

@ -1,47 +1,70 @@
<?php <?php
use Engelsystem\Database\Db; use Engelsystem\Database\Db;
use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\Shifts\ShiftEntry;
use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Collection;
/** /**
* Returns all needed angeltypes and already taken needs. * Returns all needed angeltypes and already taken needs.
* *
* @param int $shiftId id of shift * @param Shift $shift
* @return array * @return array
*/ */
function NeededAngelTypes_by_shift($shiftId) function NeededAngelTypes_by_shift($shift)
{ {
$needed_angeltypes_source = Db::select( $needed_angeltypes_source = [];
' // Select from shift
if (!$shift->schedule) {
$needed_angeltypes_source = Db::select(
'
SELECT SELECT
`needed_angel_types`.*, `needed_angel_types`.*,
`angel_types`.`id`,
`angel_types`.`name`, `angel_types`.`name`,
`angel_types`.`restricted`, `angel_types`.`restricted`,
`angel_types`.`shift_self_signup` `angel_types`.`shift_self_signup`
FROM `needed_angel_types` FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id` = `needed_angel_types`.`angel_type_id` JOIN `angel_types` ON `angel_types`.`id` = `needed_angel_types`.`angel_type_id`
WHERE `shift_id` = ? WHERE `needed_angel_types`.`shift_id` = ?
ORDER BY `location_id` DESC', ORDER BY `location_id` DESC
[$shiftId] ',
); [$shift->id]
);
}
// Use settings from location // Get needed by shift type
if (count($needed_angeltypes_source) == 0) { if ($shift->schedule && $shift->schedule->needed_from_shift_type) {
$needed_angeltypes_source = Db::select(' $needed_angeltypes_source = Db::select('
SELECT `needed_angel_types`.*, `angel_types`.`name`, `angel_types`.`restricted` SELECT
`needed_angel_types`.*,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`shift_self_signup`
FROM `needed_angel_types` FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id` = `needed_angel_types`.`angel_type_id` JOIN `angel_types` ON `angel_types`.`id` = `needed_angel_types`.`angel_type_id`
JOIN `shifts` ON `shifts`.`location_id` = `needed_angel_types`.`location_id` WHERE `needed_angel_types`.`shift_type_id` = ?
WHERE `shifts`.`id` = ?
ORDER BY `location_id` DESC ORDER BY `location_id` DESC
', [$shiftId]); ', [$shift->shift_type_id]);
}
// Load from room
if ($shift->schedule && !$shift->schedule->needed_from_shift_type) {
$needed_angeltypes_source = Db::select('
SELECT
`needed_angel_types`.*,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`shift_self_signup`
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id` = `needed_angel_types`.`angel_type_id`
WHERE `needed_angel_types`.`location_id` = ?
ORDER BY `location_id` DESC
', [$shift->location_id]);
} }
/** @var ShiftEntry[]|Collection $shift_entries */ /** @var ShiftEntry[]|Collection $shift_entries */
$shift_entries = ShiftEntry::with('user', 'angelType') $shift_entries = ShiftEntry::with('user', 'angelType')
->where('shift_id', $shiftId) ->where('shift_id', $shift->id)
->get(); ->get();
$needed_angeltypes = []; $needed_angeltypes = [];
foreach ($needed_angeltypes_source as $angeltype) { foreach ($needed_angeltypes_source as $angeltype) {

View File

@ -28,12 +28,26 @@ function Shifts_by_angeltype(AngelType $angeltype)
UNION UNION
/* By shift type */
SELECT DISTINCT `shifts`.* FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_type_id` = `shifts`.`shift_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `needed_angel_types`.`angel_type_id` = ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
UNION
/* By location */
SELECT DISTINCT `shifts`.* FROM `shifts` SELECT DISTINCT `shifts`.* FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`location_id` = `shifts`.`location_id` JOIN `needed_angel_types` ON `needed_angel_types`.`location_id` = `shifts`.`location_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `needed_angel_types`.`angel_type_id` = ? WHERE `needed_angel_types`.`angel_type_id` = ?
AND NOT s.shift_id IS NULL AND NOT s.shift_id IS NULL
', [$angeltype->id, $angeltype->id]); AND se.needed_from_shift_type = FALSE
', [$angeltype->id, $angeltype->id, $angeltype->id]);
} }
/** /**
@ -53,7 +67,7 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
$shifts = Db::select(' $shifts = Db::select('
SELECT * SELECT *
FROM ( FROM (
SELECT id, start SELECT shifts.id, start
FROM `shifts` FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE (`end` > ? AND `start` < ?) WHERE (`end` > ? AND `start` < ?)
@ -64,13 +78,30 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
UNION UNION
SELECT id, start /* By shift type */
SELECT shifts.id, start
FROM `shifts` FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`' . ($filter ? ' AND needed_angel_types.angel_type_id 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 NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION
/* By location */
SELECT shifts.id, start
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE (`end` > ? AND `start` < ?) WHERE (`end` > ? AND `start` < ?)
AND (SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`location_id`=`shifts`.`location_id`' . ($filter ? ' AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ') AND (SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`location_id`=`shifts`.`location_id`' . ($filter ? ' AND needed_angel_types.angel_type_id 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()) . ')' : '') . ') > (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 NOT s.shift_id IS NULL AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
) AS `tmp` ) AS `tmp`
ORDER BY `tmp`.`start` ORDER BY `tmp`.`start`
@ -79,6 +110,8 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null)
$end, $end,
$start, $start,
$end, $end,
$start,
$end,
]); ]);
$shifts = collect($shifts); $shifts = collect($shifts);
@ -110,16 +143,35 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
UNION UNION
/* By shift type */
SELECT DISTINCT `shifts`.*, `shift_types`.`name`, `locations`.`name` AS `location_name`
FROM `shifts`
JOIN `locations` ON `shifts`.`location_id` = `locations`.`id`
JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND `start` BETWEEN ? AND ?
AND `needed_angel_types`.`angel_type_id` IN (' . implode(',', $shiftsFilter->getTypes()) . ')
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
UNION
/* By location */
SELECT DISTINCT `shifts`.*, `shift_types`.`name`, `locations`.`name` AS `location_name` SELECT DISTINCT `shifts`.*, `shift_types`.`name`, `locations`.`name` AS `location_name`
FROM `shifts` FROM `shifts`
JOIN `locations` ON `shifts`.`location_id` = `locations`.`id` JOIN `locations` ON `shifts`.`location_id` = `locations`.`id`
JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id` JOIN `shift_types` ON `shift_types`.`id` = `shifts`.`shift_type_id`
JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id` JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ') WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND `start` BETWEEN ? AND ? AND `start` BETWEEN ? AND ?
AND `needed_angel_types`.`angel_type_id` IN (' . implode(',', $shiftsFilter->getTypes()) . ') AND `needed_angel_types`.`angel_type_id` IN (' . implode(',', $shiftsFilter->getTypes()) . ')
AND NOT s.shift_id IS NULL AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
) AS tmp_shifts ) AS tmp_shifts
ORDER BY `location_name`, `start` ORDER BY `location_name`, `start`
@ -132,6 +184,8 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
$shiftsFilter->getEnd(), $shiftsFilter->getEnd(),
$shiftsFilter->getStart(), $shiftsFilter->getStart(),
$shiftsFilter->getEnd(), $shiftsFilter->getEnd(),
$shiftsFilter->getStart(),
$shiftsFilter->getEnd(),
] ]
); );
@ -167,6 +221,27 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
UNION UNION
/* By shift type */
SELECT
`needed_angel_types`.*,
`shifts`.`id` AS shift_id,
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`shift_self_signup`
FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND shifts.`start` BETWEEN ? AND ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
UNION
/* By location */
SELECT SELECT
`needed_angel_types`.*, `needed_angel_types`.*,
`shifts`.`id` AS shift_id, `shifts`.`id` AS shift_id,
@ -178,9 +253,11 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id` JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id` JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ') WHERE `shifts`.`location_id` IN (' . implode(',', $shiftsFilter->getLocations()) . ')
AND shifts.`start` BETWEEN ? AND ? AND shifts.`start` BETWEEN ? AND ?
AND NOT s.shift_id IS NULL AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
'; ';
return Db::select( return Db::select(
@ -190,6 +267,8 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter)
$shiftsFilter->getEnd(), $shiftsFilter->getEnd(),
$shiftsFilter->getStart(), $shiftsFilter->getStart(),
$shiftsFilter->getEnd(), $shiftsFilter->getEnd(),
$shiftsFilter->getStart(),
$shiftsFilter->getEnd(),
] ]
); );
} }
@ -220,6 +299,27 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty
UNION UNION
/* By shift type */
SELECT
`needed_angel_types`.*,
`shifts`.`id` AS shift_id,
`angel_types`.`id`,
`angel_types`.`name`,
`angel_types`.`restricted`,
`angel_types`.`shift_self_signup`
FROM `shifts`
JOIN `needed_angel_types` ON `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`id`=?
AND `angel_types`.`id`=?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
UNION
/* By location */
SELECT SELECT
`needed_angel_types`.*, `needed_angel_types`.*,
`shifts`.`id` AS shift_id, `shifts`.`id` AS shift_id,
@ -231,15 +331,19 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty
JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id` JOIN `needed_angel_types` ON `needed_angel_types`.`location_id`=`shifts`.`location_id`
JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id` JOIN `angel_types` ON `angel_types`.`id`= `needed_angel_types`.`angel_type_id`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE `shifts`.`id`=? WHERE `shifts`.`id`=?
AND `angel_types`.`id`=? AND `angel_types`.`id`=?
AND NOT s.shift_id IS NULL AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
', ',
[ [
$shift->id, $shift->id,
$angeltype->id, $angeltype->id,
$shift->id, $shift->id,
$angeltype->id, $angeltype->id,
$shift->id,
$angeltype->id,
] ]
); );
} }
@ -559,7 +663,7 @@ function Shift($shift)
} }
$neededAngels = []; $neededAngels = [];
$angelTypes = NeededAngelTypes_by_shift($shift->id); $angelTypes = NeededAngelTypes_by_shift($shift);
foreach ($angelTypes as $type) { foreach ($angelTypes as $type) {
$neededAngels[] = [ $neededAngels[] = [
'angel_type_id' => $type['angel_type_id'], 'angel_type_id' => $type['angel_type_id'],

View File

@ -47,19 +47,36 @@ function stats_hours_to_work(ShiftsFilter $filter = null)
* TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count` * TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count`
FROM `shifts` FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `shifts`.`end` >= NOW() WHERE shifts.`end` >= NOW()
AND s.shift_id IS NULL AND s.shift_id IS NULL
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL UNION ALL
/* By shift type */
SELECT
(SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`' . ($filter ? ' AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
* TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` >= NOW()
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By location */
SELECT SELECT
(SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`location_id`=`shifts`.`location_id`' . ($filter ? ' AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ') (SELECT SUM(`count`) FROM `needed_angel_types` WHERE `needed_angel_types`.`location_id`=`shifts`.`location_id`' . ($filter ? ' AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ')
* TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count` * TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count`
FROM `shifts` FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE shifts.`end` >= NOW() LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` >= NOW()
AND NOT s.shift_id IS NULL AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
) AS `tmp` ) AS `tmp`
' '
@ -90,7 +107,8 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
AND `needed_angel_types`.`shift_id`=`shifts`.`id` AND `needed_angel_types`.`shift_id`=`shifts`.`id`
' . ($filter ? 'AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ' . ($filter ? 'AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - ( ) - (
SELECT COUNT(*) FROM `shift_entries` SELECT COUNT(*)
FROM `shift_entries`
JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id` JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `shift_entries`.`shift_id`=`shifts`.`id` AND `shift_entries`.`shift_id`=`shifts`.`id`
@ -107,6 +125,38 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
UNION ALL UNION ALL
/* By shift type */
SELECT
GREATEST(0,
(
SELECT SUM(needed_angel_types.`count`)
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id`=`needed_angel_types`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
' . ($filter ? 'AND needed_angel_types.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
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 `shift_entries`.`shift_id`=`shifts`.`id`
AND `freeloaded`=0
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` > NOW() AND shifts.`start` < ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By location */
SELECT SELECT
GREATEST(0, GREATEST(0,
( (
@ -129,12 +179,15 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null)
AS `count` AS `count`
FROM `shifts` FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `end` > NOW() AND `start` < ? LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` > NOW() AND shifts.`start` < ?
AND NOT s.shift_id IS NULL AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
) AS `tmp`', [ ) AS `tmp`', [
$in3hours, $in3hours,
$in3hours, $in3hours,
$in3hours,
]); ]);
return $result['count'] ?: '-'; return $result['count'] ?: '-';
@ -189,6 +242,37 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null)
UNION ALL UNION ALL
/* By shift type */
SELECT
GREATEST(0,
(
SELECT SUM(needed_angel_types.`count`)
FROM `needed_angel_types`
JOIN `angel_types` ON `angel_types`.`id`=`needed_angel_types`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `needed_angel_types`.`shift_type_id`=`shifts`.`shift_type_id`
' . ($filter ? 'AND angel_types.id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) - (
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 `shift_entries`.`shift_id`=`shifts`.`id`
AND shift_entries.`freeloaded`=0
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
)
)
AS `count`
FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` > ? AND shifts.`start` < ?
AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = TRUE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
UNION ALL
/* By location */
SELECT SELECT
GREATEST(0, GREATEST(0,
( (
@ -203,21 +287,25 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null)
JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id` JOIN `angel_types` ON `angel_types`.`id`=`shift_entries`.`angel_type_id`
WHERE `angel_types`.`show_on_dashboard`=TRUE WHERE `angel_types`.`show_on_dashboard`=TRUE
AND `shift_entries`.`shift_id`=`shifts`.`id` AND `shift_entries`.`shift_id`=`shifts`.`id`
AND `freeloaded`=0 AND shift_entries.`freeloaded`=0
' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . ' ' . ($filter ? 'AND shift_entries.angel_type_id IN (' . implode(',', $filter->getTypes()) . ')' : '') . '
) )
) )
AS `count` AS `count`
FROM `shifts` FROM `shifts`
LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id
WHERE `end` > ? AND `start` < ? LEFT JOIN schedules AS se on s.schedule_id = se.id
WHERE shifts.`end` > ? AND shifts.`start` < ?
AND NOT s.shift_id IS NULL AND NOT s.shift_id IS NULL
AND se.needed_from_shift_type = FALSE
' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . '
) AS `tmp`', [ ) AS `tmp`', [
$night_start, $night_start,
$night_end, $night_end,
$night_start, $night_start,
$night_end, $night_end,
$night_start,
$night_end,
]); ]);
return $result['count'] ?: '-'; return $result['count'] ?: '-';

View File

@ -171,7 +171,9 @@ function admin_shifts()
} }
if ($request->has('angelmode')) { if ($request->has('angelmode')) {
if ($request->input('angelmode') == 'location') { if ($request->input('angelmode') == 'shift_type') {
$angelmode = 'shift_type';
} elseif ($request->input('angelmode') == 'location') {
$angelmode = 'location'; $angelmode = 'location';
} elseif ($request->input('angelmode') == 'manually') { } elseif ($request->input('angelmode') == 'manually') {
foreach ($types as $type) { foreach ($types as $type) {
@ -203,7 +205,11 @@ function admin_shifts()
// Alle Eingaben in Ordnung // Alle Eingaben in Ordnung
if ($valid) { if ($valid) {
if ($angelmode == 'location') { if ($angelmode == 'shift_type') {
$needed_angel_types = NeededAngelType::whereShiftTypeId($shifttype_id)
->pluck('count', 'angel_type_id')
->toArray() + $needed_angel_types;
} elseif ($angelmode == 'location') {
$needed_angel_types = NeededAngelType::whereLocationId($lid) $needed_angel_types = NeededAngelType::whereLocationId($lid)
->pluck('count', 'angel_type_id') ->pluck('count', 'angel_type_id')
->toArray() + $needed_angel_types; ->toArray() + $needed_angel_types;
@ -541,7 +547,13 @@ function admin_shifts()
form_info(__('Needed angels')), form_info(__('Needed angels')),
form_radio( form_radio(
'angelmode', 'angelmode',
__('Take needed angels from location settings'), __('Copy needed angels from shift type settings'),
$angelmode == 'shift_type',
'shift_type'
),
form_radio(
'angelmode',
__('Copy needed angels from location settings'),
$angelmode == 'location', $angelmode == 'location',
'location' 'location'
), ),

View File

@ -118,6 +118,7 @@ class ImportSchedule extends BaseController
'name' => 'required', 'name' => 'required',
'url' => 'required', 'url' => 'required',
'shift_type' => 'required|int', 'shift_type' => 'required|int',
'needed_from_shift_type' => 'optional|checked',
'minutes_before' => 'int', 'minutes_before' => 'int',
'minutes_after' => 'int', 'minutes_after' => 'int',
]); ]);
@ -129,17 +130,19 @@ class ImportSchedule extends BaseController
$schedule->name = $data['name']; $schedule->name = $data['name'];
$schedule->url = $data['url']; $schedule->url = $data['url'];
$schedule->shift_type = $data['shift_type']; $schedule->shift_type = $data['shift_type'];
$schedule->needed_from_shift_type = (bool) $data['needed_from_shift_type'];
$schedule->minutes_before = $data['minutes_before']; $schedule->minutes_before = $data['minutes_before'];
$schedule->minutes_after = $data['minutes_after']; $schedule->minutes_after = $data['minutes_after'];
$schedule->save(); $schedule->save();
$this->log->info( $this->log->info(
'Schedule {name}: Url {url}, Shift Type {shift_type}, minutes before/after {before}/{after}', 'Schedule {name}: Url {url}, Shift Type {shift_type}, ({need}), minutes before/after {before}/{after}',
[ [
'name' => $schedule->name, 'name' => $schedule->name,
'url' => $schedule->name, 'url' => $schedule->name,
'shift_type' => $schedule->shift_type, 'shift_type' => $schedule->shift_type,
'need' => $schedule->needed_from_shift_type ? 'from shift type' : 'from room',
'before' => $schedule->minutes_before, 'before' => $schedule->minutes_before,
'after' => $schedule->minutes_after, 'after' => $schedule->minutes_after,
] ]
@ -169,8 +172,8 @@ class ImportSchedule extends BaseController
'' ''
); );
$this->fireDeleteShiftEntryEvents($event); $this->fireDeleteShiftEntryEvents($event, $schedule);
$this->deleteEvent($event); $this->deleteEvent($event, $schedule);
} }
$schedule->delete(); $schedule->delete();
@ -271,13 +274,14 @@ class ImportSchedule extends BaseController
$shiftType, $shiftType,
$locations $locations
->where('name', $event->getRoom()->getName()) ->where('name', $event->getRoom()->getName())
->first() ->first(),
$scheduleUrl
); );
} }
foreach ($deleteEvents as $event) { foreach ($deleteEvents as $event) {
$this->fireDeleteShiftEntryEvents($event); $this->fireDeleteShiftEntryEvents($event, $scheduleUrl);
$this->deleteEvent($event); $this->deleteEvent($event, $scheduleUrl);
} }
$scheduleUrl->touch(); $scheduleUrl->touch();
@ -296,7 +300,7 @@ class ImportSchedule extends BaseController
$this->log('Created schedule location "{location}"', ['location' => $room->getName()]); $this->log('Created schedule location "{location}"', ['location' => $room->getName()]);
} }
protected function fireDeleteShiftEntryEvents(Event $event): void protected function fireDeleteShiftEntryEvents(Event $event, ScheduleUrl $schedule): void
{ {
$shiftEntries = $this->db $shiftEntries = $this->db
->table('shift_entries') ->table('shift_entries')
@ -310,6 +314,7 @@ class ImportSchedule extends BaseController
->join('angel_types', 'angel_types.id', 'shift_entries.angel_type_id') ->join('angel_types', 'angel_types.id', 'shift_entries.angel_type_id')
->join('shift_types', 'shift_types.id', 'shifts.shift_type_id') ->join('shift_types', 'shift_types.id', 'shifts.shift_type_id')
->where('schedule_shift.guid', $event->getGuid()) ->where('schedule_shift.guid', $event->getGuid())
->where('schedule_shift.schedule_id', $schedule->id)
->get(); ->get();
foreach ($shiftEntries as $shiftEntry) { foreach ($shiftEntries as $shiftEntry) {
@ -359,13 +364,13 @@ class ImportSchedule extends BaseController
); );
} }
protected function updateEvent(Event $event, int $shiftTypeId, Location $location): void protected function updateEvent(Event $event, int $shiftTypeId, Location $location, ScheduleUrl $schedule): void
{ {
$user = auth()->user(); $user = auth()->user();
$eventTimeZone = Carbon::now()->timezone; $eventTimeZone = Carbon::now()->timezone;
/** @var ScheduleShift $scheduleShift */ /** @var ScheduleShift $scheduleShift */
$scheduleShift = ScheduleShift::whereGuid($event->getGuid())->first(); $scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first();
$shift = $scheduleShift->shift; $shift = $scheduleShift->shift;
$shift->title = $event->getTitle(); $shift->title = $event->getTitle();
$shift->shift_type_id = $shiftTypeId; $shift->shift_type_id = $shiftTypeId;
@ -388,10 +393,10 @@ class ImportSchedule extends BaseController
); );
} }
protected function deleteEvent(Event $event): void protected function deleteEvent(Event $event, ScheduleUrl $schedule): void
{ {
/** @var ScheduleShift $scheduleShift */ /** @var ScheduleShift $scheduleShift */
$scheduleShift = ScheduleShift::whereGuid($event->getGuid())->first(); $scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first();
$shift = $scheduleShift->shift; $shift = $scheduleShift->shift;
$shift->delete(); $shift->delete();
@ -509,10 +514,10 @@ class ImportSchedule extends BaseController
$scheduleEventsGuidList = array_keys($scheduleEvents); $scheduleEventsGuidList = array_keys($scheduleEvents);
$existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList); $existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList);
foreach ($existingShifts as $shift) { foreach ($existingShifts as $scheduleShift) {
$guid = $shift->guid; $guid = $scheduleShift->guid;
/** @var Shift $shift */ /** @var Shift $shift */
$shift = Shift::with('location')->find($shift->shift_id); $shift = Shift::with('location')->find($scheduleShift->shift_id);
$event = $scheduleEvents[$guid]; $event = $scheduleEvents[$guid];
$location = $locations->where('name', $event->getRoom()->getName())->first(); $location = $locations->where('name', $event->getRoom()->getName())->first();
@ -535,8 +540,8 @@ class ImportSchedule extends BaseController
} }
$scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleUrl, $scheduleEventsGuidList); $scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleUrl, $scheduleEventsGuidList);
foreach ($scheduleShifts as $shift) { foreach ($scheduleShifts as $scheduleShift) {
$event = $this->eventFromScheduleShift($shift); $event = $this->eventFromScheduleShift($scheduleShift);
$deleteEvents[$event->getGuid()] = $event; $deleteEvents[$event->getGuid()] = $event;
} }
@ -545,8 +550,7 @@ class ImportSchedule extends BaseController
protected function eventFromScheduleShift(ScheduleShift $scheduleShift): Event protected function eventFromScheduleShift(ScheduleShift $scheduleShift): Event
{ {
/** @var Shift $shift */ $shift = $scheduleShift->shift;
$shift = Shift::with('location')->find($scheduleShift->shift_id);
$duration = $shift->start->diff($shift->end); $duration = $shift->start->diff($shift->end);
return new Event( return new Event(

View File

@ -126,7 +126,9 @@ function load_locations(bool $onlyWithActiveShifts = false)
$locationIdsFromShift = Shift::query() $locationIdsFromShift = Shift::query()
->leftJoin('needed_angel_types', 'shifts.id', 'needed_angel_types.shift_id') ->leftJoin('needed_angel_types', 'shifts.id', 'needed_angel_types.shift_id')
->whereNotNull('needed_angel_types.shift_id') ->leftJoin('needed_angel_types AS nast', 'shifts.shift_type_id', 'nast.shift_type_id')
->whereNotNull('needed_angel_types.id')
->orWhereNotNull('nast.id')
->select('shifts.location_id'); ->select('shifts.location_id');
$locations->whereIn('id', $locationIdsFromAngelType) $locations->whereIn('id', $locationIdsFromAngelType)

View File

@ -722,8 +722,11 @@ msgstr "Schichtwechsel-Stunden"
msgid "Create a shift over midnight." msgid "Create a shift over midnight."
msgstr "Erstelle Schichten über Mitternacht" msgstr "Erstelle Schichten über Mitternacht"
msgid "Take needed angels from location settings" msgid "Copy needed angels from location settings"
msgstr "Übernehme benötigte Engel von den Ort-Einstellungen" msgstr "Kopiere benötigte Engel von den Ort-Einstellungen"
msgid "Copy needed angels from shift type settings"
msgstr "Kopiere benötigte Engel von den Schichttyp-Einstellungen"
msgid "The following angels are needed" msgid "The following angels are needed"
msgstr "Die folgenden Engel werden benötigt" msgstr "Die folgenden Engel werden benötigt"
@ -1447,6 +1450,9 @@ msgstr "Programm URL (schedule.xml)"
msgid "schedule.shift-type" msgid "schedule.shift-type"
msgstr "Schichttyp" msgstr "Schichttyp"
msgid "schedule.needed-from-shift-type"
msgstr "Engeltypen vom Schichttyp laden (sonst vom Ort)"
msgid "schedule.minutes-before" msgid "schedule.minutes-before"
msgstr "Minuten vor Talk beginn hinzufügen" msgstr "Minuten vor Talk beginn hinzufügen"
@ -1934,7 +1940,7 @@ msgid "location.map_url"
msgstr "Karte" msgstr "Karte"
msgid "location.required_angels" msgid "location.required_angels"
msgstr "Benötigte Engel" msgstr "Benötigte Engel (bei Fahrplan import)"
msgid "location.map_url.info" msgid "location.map_url.info"
msgstr "Die Karte wird auf der Ort-Seite als iframe eingebettet." msgstr "Die Karte wird auf der Ort-Seite als iframe eingebettet."
@ -1960,6 +1966,9 @@ msgstr "Schichttyp erstellen"
msgid "shifttype.delete.title" msgid "shifttype.delete.title"
msgstr "Schichttyp \"%s\" löschen" msgstr "Schichttyp \"%s\" löschen"
msgid "shifttype.required_angels"
msgstr "Benötigte Engel (bei Fahrplan import)"
msgid "event.day" msgid "event.day"
msgstr "Tag %1$d" msgstr "Tag %1$d"

View File

@ -151,6 +151,9 @@ msgstr "Schedule URL (schedule.xml)"
msgid "schedule.shift-type" msgid "schedule.shift-type"
msgstr "Shift type" msgstr "Shift type"
msgid "schedule.needed-from-shift-type"
msgstr "Load angel types from shift type (else from location)"
msgid "schedule.minutes-before" msgid "schedule.minutes-before"
msgstr "Add minutes before talk begins" msgstr "Add minutes before talk begins"
@ -664,7 +667,7 @@ msgid "location.map_url"
msgstr "Map" msgstr "Map"
msgid "location.required_angels" msgid "location.required_angels"
msgstr "Required angels" msgstr "Required angels (on schedule import)"
msgid "location.map_url.info" msgid "location.map_url.info"
msgstr "The map will be embedded on the location page as an iframe." msgstr "The map will be embedded on the location page as an iframe."
@ -690,6 +693,9 @@ msgstr "Create shift type"
msgid "shifttype.delete.title" msgid "shifttype.delete.title"
msgstr "Delete shift type \"%s\"" msgstr "Delete shift type \"%s\""
msgid "shifttype.required_angels"
msgstr "Required angels (on schedule import)"
msgid "event.day" msgid "event.day"
msgstr "Day %1$d" msgstr "Day %1$d"

View File

@ -34,6 +34,10 @@
'selected': schedule ? schedule.shift_type : '', 'selected': schedule ? schedule.shift_type : '',
}) }} }) }}
{{ f.checkbox('needed_from_shift_type', __('schedule.needed-from-shift-type'), {
'checked': schedule ? schedule.needed_from_shift_type : '',
}) }}
{{ f.input('minutes_before', __('schedule.minutes-before'), { {{ f.input('minutes_before', __('schedule.minutes-before'), {
'type': 'number', 'type': 'number',
'required': true, 'required': true,

View File

@ -23,6 +23,27 @@
'info': __('form.markdown') 'info': __('form.markdown')
}) }} }) }}
</div> </div>
<div class="col-lg-6">
<h4>{{ __('shifttype.required_angels') }}</h4>
{% for types in angel_types.chunk(3) %}
<div class="row">
{% for angel_type in types %}
{% set needed = shifttype
? shifttype.neededAngelTypes.where('angel_type_id', angel_type.id).first()
: null %}
{% set name = 'angel_type_' ~ angel_type.id %}
<div class="col-md-4">
{{ f.number(name, angel_type.name, {
'value': f.formData(name, needed ? needed.count : 0),
'min': 0,
'step': 1,
}) }}
</div>
{% endfor %}
</div>
{% endfor %}
</div>
</div> </div>
<div class="col-md-12"> <div class="col-md-12">

View File

@ -11,7 +11,10 @@ use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Response; use Engelsystem\Http\Response;
use Engelsystem\Http\Validation\Validator; use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\ShiftType; use Engelsystem\Models\Shifts\ShiftType;
use Illuminate\Database\Eloquent\Collection;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
class ShiftTypesController extends BaseController class ShiftTypesController extends BaseController
@ -48,10 +51,15 @@ class ShiftTypesController extends BaseController
$shiftTypeId = (int) $request->getAttribute('shift_type_id'); $shiftTypeId = (int) $request->getAttribute('shift_type_id');
$shiftType = $this->shiftType->find($shiftTypeId); $shiftType = $this->shiftType->find($shiftTypeId);
$angeltypes = AngelType::all()
->sortBy('name');
return $this->response->withView( return $this->response->withView(
'admin/shifttypes/edit', 'admin/shifttypes/edit',
['shifttype' => $shiftType] [
'shifttype' => $shiftType,
'angel_types' => $angeltypes,
]
); );
} }
@ -77,12 +85,19 @@ class ShiftTypesController extends BaseController
return $this->delete($request); return $this->delete($request);
} }
/** @var Collection|AngelType[] $angelTypes */
$angelTypes = AngelType::all();
$validation = [];
foreach ($angelTypes as $angelType) {
$validation['angel_type_' . $angelType->id] = 'optional|int';
}
$data = $this->validate( $data = $this->validate(
$request, $request,
[ [
'name' => 'required', 'name' => 'required',
'description' => 'required|optional', 'description' => 'required|optional',
] ] + $validation
); );
if (ShiftType::whereName($data['name'])->where('id', '!=', $shiftType->id)->exists()) { if (ShiftType::whereName($data['name'])->where('id', '!=', $shiftType->id)->exists()) {
@ -93,12 +108,33 @@ class ShiftTypesController extends BaseController
$shiftType->description = $data['description']; $shiftType->description = $data['description'];
$shiftType->save(); $shiftType->save();
$shiftType->neededAngelTypes()->delete();
$angelsInfo = '';
foreach ($angelTypes as $angelType) {
$count = $data['angel_type_' . $angelType->id];
if (!$count) {
continue;
}
$neededAngelType = new NeededAngelType();
$neededAngelType->shiftType()->associate($shiftType);
$neededAngelType->angelType()->associate($angelType);
$neededAngelType->count = $data['angel_type_' . $angelType->id];
$neededAngelType->save();
$angelsInfo .= sprintf(', %s: %s', $angelType->name, $count);
}
$this->log->info( $this->log->info(
'Updated shift type "{name}": {description}', 'Updated shift type "{name}": {description} {angels}',
[ [
'name' => $shiftType->name, 'name' => $shiftType->name,
'description' => $shiftType->description, 'description' => $shiftType->description,
'angels' => $angelsInfo,
] ]
); );

View File

@ -78,6 +78,10 @@ class ShiftsController extends BaseController
$query->on('needed_angel_types.shift_id', '=', 'shifts.id') $query->on('needed_angel_types.shift_id', '=', 'shifts.id')
->whereNull('schedule_shift.shift_id'); ->whereNull('schedule_shift.shift_id');
}) })
->leftJoin('needed_angel_types AS nast', function (JoinClause $query): void {
$query->on('nast.shift_type_id', '=', 'shifts.shift_type_id')
->whereNotNull('schedule_shift.shift_id');
})
->leftJoin('needed_angel_types AS nas', function (JoinClause $query): void { ->leftJoin('needed_angel_types AS nas', function (JoinClause $query): void {
$query->on('nas.location_id', '=', 'shifts.location_id') $query->on('nas.location_id', '=', 'shifts.location_id')
->whereNotNull('schedule_shift.shift_id'); ->whereNotNull('schedule_shift.shift_id');
@ -88,6 +92,7 @@ class ShiftsController extends BaseController
->where(function (EloquentBuilder $query) use ($angelTypes): void { ->where(function (EloquentBuilder $query) use ($angelTypes): void {
$query $query
->whereIn('needed_angel_types.angel_type_id', $angelTypes) ->whereIn('needed_angel_types.angel_type_id', $angelTypes)
->orWhereIn('nast.angel_type_id', $angelTypes)
->orWhereIn('nas.angel_type_id', $angelTypes); ->orWhereIn('nas.angel_type_id', $angelTypes);
}) })
// Starts soon // Starts soon
@ -98,7 +103,7 @@ class ShiftsController extends BaseController
->from('shift_entries') ->from('shift_entries')
->selectRaw('COUNT(*)') ->selectRaw('COUNT(*)')
->where(fn(Builder $query) => $this->queryShiftEntries($query)); ->where(fn(Builder $query) => $this->queryShiftEntries($query));
}, '<', Shift::query()->raw('COALESCE(needed_angel_types.count, nas.count)')) }, '<', Shift::query()->raw('COALESCE(needed_angel_types.count, nast.count, nas.count)'))
->limit(10) ->limit(10)
->orderBy('start'); ->orderBy('start');

View File

@ -15,16 +15,19 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @property int $id * @property int $id
* @property int|null $location_id * @property int|null $location_id
* @property int|null $shift_id * @property int|null $shift_id
* @property int|null $shift_type_id
* @property int $angel_type_id * @property int $angel_type_id
* @property int $count * @property int $count
* *
* @property-read Location|null $location * @property-read Location|null $location
* @property-read Shift|null $shift * @property-read Shift|null $shift
* @property-read ShiftType|null $shiftType
* @property-read AngelType $angelType * @property-read AngelType $angelType
* *
* @method static QueryBuilder|NeededAngelType[] whereId($value) * @method static QueryBuilder|NeededAngelType[] whereId($value)
* @method static QueryBuilder|NeededAngelType[] whereLocationId($value) * @method static QueryBuilder|NeededAngelType[] whereLocationId($value)
* @method static QueryBuilder|NeededAngelType[] whereShiftId($value) * @method static QueryBuilder|NeededAngelType[] whereShiftId($value)
* @method static QueryBuilder|NeededAngelType[] whereShiftTypeId($value)
* @method static QueryBuilder|NeededAngelType[] whereAngelTypeId($value) * @method static QueryBuilder|NeededAngelType[] whereAngelTypeId($value)
* @method static QueryBuilder|NeededAngelType[] whereCount($value) * @method static QueryBuilder|NeededAngelType[] whereCount($value)
*/ */
@ -36,12 +39,14 @@ class NeededAngelType extends BaseModel
protected $attributes = [ // phpcs:ignore protected $attributes = [ // phpcs:ignore
'location_id' => null, 'location_id' => null,
'shift_id' => null, 'shift_id' => null,
'shift_type_id' => null,
]; ];
/** @var array<string> */ /** @var array<string> */
protected $fillable = [ // phpcs:ignore protected $fillable = [ // phpcs:ignore
'location_id', 'location_id',
'shift_id', 'shift_id',
'shift_type_id',
'angel_type_id', 'angel_type_id',
'count', 'count',
]; ];
@ -56,6 +61,11 @@ class NeededAngelType extends BaseModel
return $this->belongsTo(Shift::class); return $this->belongsTo(Shift::class);
} }
public function shiftType(): BelongsTo
{
return $this->belongsTo(ShiftType::class);
}
public function angelType(): BelongsTo public function angelType(): BelongsTo
{ {
return $this->belongsTo(AngelType::class); return $this->belongsTo(AngelType::class);

View File

@ -18,6 +18,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @property string $name * @property string $name
* @property string $url * @property string $url
* @property int $shift_type * @property int $shift_type
* @property bool $needed_from_shift_type
* @property int $minutes_before * @property int $minutes_before
* @property int $minutes_after * @property int $minutes_after
* @property Carbon $created_at * @property Carbon $created_at
@ -31,6 +32,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @method static QueryBuilder|Schedule[] whereName($value) * @method static QueryBuilder|Schedule[] whereName($value)
* @method static QueryBuilder|Schedule[] whereUrl($value) * @method static QueryBuilder|Schedule[] whereUrl($value)
* @method static QueryBuilder|Schedule[] whereShiftType($value) * @method static QueryBuilder|Schedule[] whereShiftType($value)
* @method static QueryBuilder|Schedule[] whereNeededFromShiftType($value)
* @method static QueryBuilder|Schedule[] whereMinutesBefore($value) * @method static QueryBuilder|Schedule[] whereMinutesBefore($value)
* @method static QueryBuilder|Schedule[] whereMinutesAfter($value) * @method static QueryBuilder|Schedule[] whereMinutesAfter($value)
* @method static QueryBuilder|Schedule[] whereCreatedAt($value) * @method static QueryBuilder|Schedule[] whereCreatedAt($value)
@ -46,6 +48,7 @@ class Schedule extends BaseModel
/** @var array<string> */ /** @var array<string> */
protected $casts = [ // phpcs:ignore protected $casts = [ // phpcs:ignore
'shift_type' => 'integer', 'shift_type' => 'integer',
'needed_from_shift_type' => 'boolean',
'minutes_before' => 'integer', 'minutes_before' => 'integer',
'minutes_after' => 'integer', 'minutes_after' => 'integer',
]; ];
@ -55,6 +58,7 @@ class Schedule extends BaseModel
'name', 'name',
'url', 'url',
'shift_type', 'shift_type',
'needed_from_shift_type',
'minutes_before', 'minutes_before',
'minutes_after', 'minutes_after',
]; ];

View File

@ -15,6 +15,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder;
* @property string $name * @property string $name
* @property string $description * @property string $description
* *
* @property-read Collection|NeededAngelType[] $neededAngelTypes
* @property-read Collection|Schedule[] $schedules * @property-read Collection|Schedule[] $schedules
* @property-read Collection|Shift[] $shifts * @property-read Collection|Shift[] $shifts
* *
@ -32,6 +33,11 @@ class ShiftType extends BaseModel
'description', 'description',
]; ];
public function neededAngelTypes(): HasMany
{
return $this->hasMany(NeededAngelType::class);
}
public function schedules(): HasMany public function schedules(): HasMany
{ {
return $this->hasMany(Schedule::class, 'shift_type'); return $this->hasMany(Schedule::class, 'shift_type');

View File

@ -11,6 +11,7 @@ use Engelsystem\Http\Exceptions\ValidationException;
use Engelsystem\Http\Redirector; use Engelsystem\Http\Redirector;
use Engelsystem\Http\Request; use Engelsystem\Http\Request;
use Engelsystem\Http\Validation\Validator; use Engelsystem\Http\Validation\Validator;
use Engelsystem\Models\AngelType;
use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\Shifts\ShiftEntry;
use Engelsystem\Models\Shifts\ShiftType; use Engelsystem\Models\Shifts\ShiftType;
use Engelsystem\Models\Shifts\Shift; use Engelsystem\Models\Shifts\Shift;
@ -18,7 +19,7 @@ use Engelsystem\Models\User\User;
use Engelsystem\Test\Unit\Controllers\ControllerTest; use Engelsystem\Test\Unit\Controllers\ControllerTest;
use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\MockObject\MockObject;
class ShiftTypeControllerTest extends ControllerTest class ShiftTypesControllerTest extends ControllerTest
{ {
protected Redirector|MockObject $redirect; protected Redirector|MockObject $redirect;
@ -82,6 +83,7 @@ class ShiftTypeControllerTest extends ControllerTest
$this->assertEquals($shifttype->id, $data['shifttype']?->id); $this->assertEquals($shifttype->id, $data['shifttype']?->id);
$this->assertNotEmpty($data['shifttype']?->name); $this->assertNotEmpty($data['shifttype']?->name);
$this->assertNotEmpty($data['shifttype']?->description); $this->assertNotEmpty($data['shifttype']?->description);
$this->assertNotNull($data['angel_types']);
return $this->response; return $this->response;
}); });
@ -104,6 +106,7 @@ class ShiftTypeControllerTest extends ControllerTest
$this->assertEquals('admin/shifttypes/edit', $view); $this->assertEquals('admin/shifttypes/edit', $view);
$this->assertArrayHasKey('shifttype', $data); $this->assertArrayHasKey('shifttype', $data);
$this->assertNull($data['shifttype']); $this->assertNull($data['shifttype']);
$this->assertNotNull($data['angel_types']);
return $this->response; return $this->response;
}); });
@ -115,6 +118,8 @@ class ShiftTypeControllerTest extends ControllerTest
*/ */
public function testSave(): void public function testSave(): void
{ {
$angelType = AngelType::factory(2)->create()->first();
/** @var ShiftTypesController $controller */ /** @var ShiftTypesController $controller */
$controller = $this->app->make(ShiftTypesController::class); $controller = $this->app->make(ShiftTypesController::class);
$controller->setValidator(new Validator()); $controller->setValidator(new Validator());
@ -124,6 +129,8 @@ class ShiftTypeControllerTest extends ControllerTest
$this->request = $this->request->withParsedBody([ $this->request = $this->request->withParsedBody([
'name' => 'Test shift type', 'name' => 'Test shift type',
'description' => 'Something', 'description' => 'Something',
'angel_type_' . $angelType->id => 3,
'angel_type_' . $angelType->id + 1 => 0,
]); ]);
$controller->save($this->request); $controller->save($this->request);
@ -131,6 +138,7 @@ class ShiftTypeControllerTest extends ControllerTest
$this->assertTrue($this->log->hasInfoThatContains('Updated shift type')); $this->assertTrue($this->log->hasInfoThatContains('Updated shift type'));
$this->assertHasNotification('shifttype.edit.success'); $this->assertHasNotification('shifttype.edit.success');
$this->assertCount(1, ShiftType::whereName('Test shift type')->get()); $this->assertCount(1, ShiftType::whereName('Test shift type')->get());
$this->assertCount(1, ShiftType::first()->neededAngelTypes);
} }
/** /**

View File

@ -8,6 +8,7 @@ use Engelsystem\Models\AngelType;
use Engelsystem\Models\Location; use Engelsystem\Models\Location;
use Engelsystem\Models\Shifts\NeededAngelType; use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\Shift; use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftType;
use Engelsystem\Test\Unit\Models\ModelTest; use Engelsystem\Test\Unit\Models\ModelTest;
class NeededAngelTypeTest extends ModelTest class NeededAngelTypeTest extends ModelTest
@ -15,6 +16,7 @@ class NeededAngelTypeTest extends ModelTest
/** /**
* @covers \Engelsystem\Models\Shifts\NeededAngelType::location * @covers \Engelsystem\Models\Shifts\NeededAngelType::location
* @covers \Engelsystem\Models\Shifts\NeededAngelType::shift * @covers \Engelsystem\Models\Shifts\NeededAngelType::shift
* @covers \Engelsystem\Models\Shifts\NeededAngelType::shiftType
* @covers \Engelsystem\Models\Shifts\NeededAngelType::angelType * @covers \Engelsystem\Models\Shifts\NeededAngelType::angelType
*/ */
public function testShift(): void public function testShift(): void
@ -23,12 +25,15 @@ class NeededAngelTypeTest extends ModelTest
$location = Location::factory()->create(); $location = Location::factory()->create();
/** @var Shift $shift */ /** @var Shift $shift */
$shift = Shift::factory()->create(); $shift = Shift::factory()->create();
/** @var ShiftType $shiftType */
$shiftType = ShiftType::factory()->create();
/** @var AngelType $angelType */ /** @var AngelType $angelType */
$angelType = AngelType::factory()->create(); $angelType = AngelType::factory()->create();
$model = new NeededAngelType(); $model = new NeededAngelType();
$model->location()->associate($location); $model->location()->associate($location);
$model->shift()->associate($shift); $model->shift()->associate($shift);
$model->shiftType()->associate($shiftType);
$model->angelType()->associate($angelType); $model->angelType()->associate($angelType);
$model->count = 3; $model->count = 3;
$model->save(); $model->save();
@ -36,6 +41,7 @@ class NeededAngelTypeTest extends ModelTest
$model = NeededAngelType::find(1); $model = NeededAngelType::find(1);
$this->assertEquals($location->id, $model->location->id); $this->assertEquals($location->id, $model->location->id);
$this->assertEquals($shift->id, $model->shift->id); $this->assertEquals($shift->id, $model->shift->id);
$this->assertEquals($shiftType->id, $model->shiftType->id);
$this->assertEquals($angelType->id, $model->angelType->id); $this->assertEquals($angelType->id, $model->angelType->id);
$this->assertEquals(3, $model->count); $this->assertEquals(3, $model->count);
} }

View File

@ -17,6 +17,7 @@ class ScheduleTest extends ModelTest
'url' => 'https://foo.bar/schedule.xml', 'url' => 'https://foo.bar/schedule.xml',
'name' => 'Testing', 'name' => 'Testing',
'shift_type' => 1, 'shift_type' => 1,
'needed_from_shift_type' => false,
'minutes_before' => 10, 'minutes_before' => 10,
'minutes_after' => 10, 'minutes_after' => 10,
]; ];

View File

@ -4,6 +4,7 @@ declare(strict_types=1);
namespace Engelsystem\Test\Unit\Models\Shifts; namespace Engelsystem\Test\Unit\Models\Shifts;
use Engelsystem\Models\Shifts\NeededAngelType;
use Engelsystem\Models\Shifts\Schedule; use Engelsystem\Models\Shifts\Schedule;
use Engelsystem\Models\Shifts\Shift; use Engelsystem\Models\Shifts\Shift;
use Engelsystem\Models\Shifts\ShiftType; use Engelsystem\Models\Shifts\ShiftType;
@ -11,6 +12,19 @@ use Engelsystem\Test\Unit\Models\ModelTest;
class ShiftTypeTest extends ModelTest class ShiftTypeTest extends ModelTest
{ {
/**
* @covers \Engelsystem\Models\Shifts\ShiftType::neededAngelTypes
*/
public function testNeededAngelTypes(): void
{
$shiftType = new ShiftType(['name' => 'Another type', 'description' => '']);
$shiftType->save();
NeededAngelType::factory()->create(['shift_type_id' => 1]);
$this->assertCount(1, ShiftType::find(1)->neededAngelTypes);
}
/** /**
* @covers \Engelsystem\Models\Shifts\ShiftType::schedules * @covers \Engelsystem\Models\Shifts\ShiftType::schedules
*/ */