From 4bbeb93d64129dd7deede36ca8bab1537e8ebb0c Mon Sep 17 00:00:00 2001 From: Igor Scheller Date: Wed, 20 Dec 2023 18:08:50 +0100 Subject: [PATCH] Implemented loading angels by shift type on schedule import --- .../Shifts/NeededAngelTypeFactory.php | 7 +- db/factories/Shifts/ScheduleFactory.php | 1 + ...schedule_shift_type_needed_angel_types.php | 40 ++++++ includes/controller/shifts_controller.php | 2 +- includes/model/NeededAngelTypes_model.php | 55 ++++++--- includes/model/Shifts_model.php | 114 +++++++++++++++++- includes/model/Stats.php | 100 ++++++++++++++- includes/pages/admin_shifts.php | 18 ++- includes/pages/schedule/ImportSchedule.php | 40 +++--- includes/pages/user_shifts.php | 4 +- resources/lang/de_DE/default.po | 15 ++- resources/lang/en_US/default.po | 8 +- resources/views/admin/schedule/edit.twig | 4 + resources/views/admin/shifttypes/edit.twig | 21 ++++ .../Admin/ShiftTypesController.php | 42 ++++++- src/Controllers/ShiftsController.php | 7 +- src/Models/Shifts/NeededAngelType.php | 10 ++ src/Models/Shifts/Schedule.php | 4 + src/Models/Shifts/ShiftType.php | 6 + ...rTest.php => ShiftTypesControllerTest.php} | 10 +- .../Models/Shifts/NeededAngelTypeTest.php | 6 + tests/Unit/Models/Shifts/ScheduleTest.php | 1 + tests/Unit/Models/Shifts/ShiftTypeTest.php | 14 +++ 23 files changed, 467 insertions(+), 62 deletions(-) create mode 100644 db/migrations/2023_12_19_000000_schedule_shift_type_needed_angel_types.php rename tests/Unit/Controllers/Admin/{ShiftTypeControllerTest.php => ShiftTypesControllerTest.php} (94%) diff --git a/db/factories/Shifts/NeededAngelTypeFactory.php b/db/factories/Shifts/NeededAngelTypeFactory.php index 0b4ab3bc..7698068e 100644 --- a/db/factories/Shifts/NeededAngelTypeFactory.php +++ b/db/factories/Shifts/NeededAngelTypeFactory.php @@ -17,11 +17,12 @@ class NeededAngelTypeFactory extends Factory public function definition(): array { - $forLocation = $this->faker->boolean(); + $type = $this->faker->numberBetween(0, 2); return [ - 'location_id' => $forLocation ? Location::factory() : null, - 'shift_id' => $forLocation ? null : Shift::factory(), + 'location_id' => $type == 0 ? Location::factory() : null, + 'shift_id' => $type == 1 ? null : Shift::factory(), + 'shift_type_id' => $type == 2 ? null : Shift::factory(), 'angel_type_id' => AngelType::factory(), 'count' => $this->faker->numberBetween(1, 5), ]; diff --git a/db/factories/Shifts/ScheduleFactory.php b/db/factories/Shifts/ScheduleFactory.php index 42a323af..350d0be3 100644 --- a/db/factories/Shifts/ScheduleFactory.php +++ b/db/factories/Shifts/ScheduleFactory.php @@ -18,6 +18,7 @@ class ScheduleFactory extends Factory 'name' => $this->faker->unique()->words(4, true), 'url' => $this->faker->parse('https://{{safeEmailDomain}}/{{slug}}.xml'), 'shift_type' => $this->faker->numberBetween(1, 5), + 'needed_from_shift_type' => $this->faker->boolean(.2), 'minutes_before' => 15, 'minutes_after' => 15, ]; diff --git a/db/migrations/2023_12_19_000000_schedule_shift_type_needed_angel_types.php b/db/migrations/2023_12_19_000000_schedule_shift_type_needed_angel_types.php new file mode 100644 index 00000000..e9ce54d5 --- /dev/null +++ b/db/migrations/2023_12_19_000000_schedule_shift_type_needed_angel_types.php @@ -0,0 +1,40 @@ +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'); + }); + } +} diff --git a/includes/controller/shifts_controller.php b/includes/controller/shifts_controller.php index 5ec5eb5c..a32ad3b4 100644 --- a/includes/controller/shifts_controller.php +++ b/includes/controller/shifts_controller.php @@ -74,7 +74,7 @@ function shift_edit_controller() $angeltypes = AngelType::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) { if (!isset($needed_angel_types[$angeltype_id])) { $needed_angel_types[$angeltype_id] = 0; diff --git a/includes/model/NeededAngelTypes_model.php b/includes/model/NeededAngelTypes_model.php index 3ef2d06b..c1edbaae 100644 --- a/includes/model/NeededAngelTypes_model.php +++ b/includes/model/NeededAngelTypes_model.php @@ -1,47 +1,70 @@ schedule) { + $needed_angeltypes_source = Db::select( + ' SELECT `needed_angel_types`.*, - `angel_types`.`id`, `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 `shift_id` = ? - ORDER BY `location_id` DESC', - [$shiftId] - ); + WHERE `needed_angel_types`.`shift_id` = ? + ORDER BY `location_id` DESC + ', + [$shift->id] + ); + } - // Use settings from location - if (count($needed_angeltypes_source) == 0) { + // Get needed by shift type + if ($shift->schedule && $shift->schedule->needed_from_shift_type) { $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` 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 `shifts`.`id` = ? + WHERE `needed_angel_types`.`shift_type_id` = ? 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 */ $shift_entries = ShiftEntry::with('user', 'angelType') - ->where('shift_id', $shiftId) + ->where('shift_id', $shift->id) ->get(); $needed_angeltypes = []; foreach ($needed_angeltypes_source as $angeltype) { diff --git a/includes/model/Shifts_model.php b/includes/model/Shifts_model.php index 09a1add1..8443514f 100644 --- a/includes/model/Shifts_model.php +++ b/includes/model/Shifts_model.php @@ -28,12 +28,26 @@ function Shifts_by_angeltype(AngelType $angeltype) 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` 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 schedules AS se on s.schedule_id = se.id WHERE `needed_angel_types`.`angel_type_id` = ? 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(' SELECT * FROM ( - SELECT id, start + SELECT shifts.id, start FROM `shifts` LEFT JOIN schedule_shift AS s on shifts.id = s.shift_id WHERE (`end` > ? AND `start` < ?) @@ -64,13 +78,30 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null) UNION - SELECT id, start + /* By shift type */ + 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` < ?) + 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` < ?) 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 se.needed_from_shift_type = FALSE ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ) AS `tmp` ORDER BY `tmp`.`start` @@ -79,6 +110,8 @@ function Shifts_free($start, $end, ShiftsFilter $filter = null) $end, $start, $end, + $start, + $end, ]); $shifts = collect($shifts); @@ -110,16 +143,35 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter) 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` 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`.`location_id`=`shifts`.`location_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 = FALSE ) AS tmp_shifts ORDER BY `location_name`, `start` @@ -132,6 +184,8 @@ function Shifts_by_ShiftsFilter(ShiftsFilter $shiftsFilter) $shiftsFilter->getEnd(), $shiftsFilter->getStart(), $shiftsFilter->getEnd(), + $shiftsFilter->getStart(), + $shiftsFilter->getEnd(), ] ); @@ -167,6 +221,27 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter) 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 `needed_angel_types`.*, `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 `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 = FALSE '; return Db::select( @@ -190,6 +267,8 @@ function NeededAngeltypes_by_ShiftsFilter(ShiftsFilter $shiftsFilter) $shiftsFilter->getEnd(), $shiftsFilter->getStart(), $shiftsFilter->getEnd(), + $shiftsFilter->getStart(), + $shiftsFilter->getEnd(), ] ); } @@ -220,6 +299,27 @@ function NeededAngeltype_by_Shift_and_Angeltype(Shift $shift, AngelType $angelty 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 `needed_angel_types`.*, `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 `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 = FALSE ', [ $shift->id, $angeltype->id, $shift->id, $angeltype->id, + $shift->id, + $angeltype->id, ] ); } @@ -559,7 +663,7 @@ function Shift($shift) } $neededAngels = []; - $angelTypes = NeededAngelTypes_by_shift($shift->id); + $angelTypes = NeededAngelTypes_by_shift($shift); foreach ($angelTypes as $type) { $neededAngels[] = [ 'angel_type_id' => $type['angel_type_id'], diff --git a/includes/model/Stats.php b/includes/model/Stats.php index 06eb30cf..6dc04d51 100644 --- a/includes/model/Stats.php +++ b/includes/model/Stats.php @@ -47,19 +47,36 @@ function stats_hours_to_work(ShiftsFilter $filter = null) * TIMESTAMPDIFF(MINUTE, `shifts`.`start`, `shifts`.`end`) / 60 AS `count` FROM `shifts` 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 ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' 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 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` FROM `shifts` 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 se.needed_from_shift_type = FALSE ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ) AS `tmp` ' @@ -90,7 +107,8 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null) AND `needed_angel_types`.`shift_id`=`shifts`.`id` ' . ($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` WHERE `angel_types`.`show_on_dashboard`=TRUE AND `shift_entries`.`shift_id`=`shifts`.`id` @@ -107,6 +125,38 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null) 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 GREATEST(0, ( @@ -129,12 +179,15 @@ function stats_angels_needed_three_hours(ShiftsFilter $filter = null) AS `count` FROM `shifts` 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 se.needed_from_shift_type = FALSE ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ) AS `tmp`', [ $in3hours, $in3hours, + $in3hours, ]); return $result['count'] ?: '-'; @@ -189,6 +242,37 @@ function stats_angels_needed_for_nightshifts(ShiftsFilter $filter = null) 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 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` WHERE `angel_types`.`show_on_dashboard`=TRUE 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()) . ')' : '') . ' ) ) AS `count` FROM `shifts` 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 se.needed_from_shift_type = FALSE ' . ($filter ? 'AND shifts.location_id IN (' . implode(',', $filter->getLocations()) . ')' : '') . ' ) AS `tmp`', [ $night_start, $night_end, $night_start, $night_end, + $night_start, + $night_end, ]); return $result['count'] ?: '-'; diff --git a/includes/pages/admin_shifts.php b/includes/pages/admin_shifts.php index 96d073ea..fb23de91 100644 --- a/includes/pages/admin_shifts.php +++ b/includes/pages/admin_shifts.php @@ -171,7 +171,9 @@ function admin_shifts() } 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'; } elseif ($request->input('angelmode') == 'manually') { foreach ($types as $type) { @@ -203,7 +205,11 @@ function admin_shifts() // Alle Eingaben in Ordnung 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) ->pluck('count', 'angel_type_id') ->toArray() + $needed_angel_types; @@ -541,7 +547,13 @@ function admin_shifts() form_info(__('Needed angels')), form_radio( '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', 'location' ), diff --git a/includes/pages/schedule/ImportSchedule.php b/includes/pages/schedule/ImportSchedule.php index 8403c465..30beb33a 100644 --- a/includes/pages/schedule/ImportSchedule.php +++ b/includes/pages/schedule/ImportSchedule.php @@ -118,6 +118,7 @@ class ImportSchedule extends BaseController 'name' => 'required', 'url' => 'required', 'shift_type' => 'required|int', + 'needed_from_shift_type' => 'optional|checked', 'minutes_before' => 'int', 'minutes_after' => 'int', ]); @@ -129,17 +130,19 @@ class ImportSchedule extends BaseController $schedule->name = $data['name']; $schedule->url = $data['url']; $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_after = $data['minutes_after']; $schedule->save(); $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, 'url' => $schedule->name, 'shift_type' => $schedule->shift_type, + 'need' => $schedule->needed_from_shift_type ? 'from shift type' : 'from room', 'before' => $schedule->minutes_before, 'after' => $schedule->minutes_after, ] @@ -169,8 +172,8 @@ class ImportSchedule extends BaseController '' ); - $this->fireDeleteShiftEntryEvents($event); - $this->deleteEvent($event); + $this->fireDeleteShiftEntryEvents($event, $schedule); + $this->deleteEvent($event, $schedule); } $schedule->delete(); @@ -271,13 +274,14 @@ class ImportSchedule extends BaseController $shiftType, $locations ->where('name', $event->getRoom()->getName()) - ->first() + ->first(), + $scheduleUrl ); } foreach ($deleteEvents as $event) { - $this->fireDeleteShiftEntryEvents($event); - $this->deleteEvent($event); + $this->fireDeleteShiftEntryEvents($event, $scheduleUrl); + $this->deleteEvent($event, $scheduleUrl); } $scheduleUrl->touch(); @@ -296,7 +300,7 @@ class ImportSchedule extends BaseController $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 ->table('shift_entries') @@ -310,6 +314,7 @@ class ImportSchedule extends BaseController ->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()) + ->where('schedule_shift.schedule_id', $schedule->id) ->get(); 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(); $eventTimeZone = Carbon::now()->timezone; /** @var ScheduleShift $scheduleShift */ - $scheduleShift = ScheduleShift::whereGuid($event->getGuid())->first(); + $scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first(); $shift = $scheduleShift->shift; $shift->title = $event->getTitle(); $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 */ - $scheduleShift = ScheduleShift::whereGuid($event->getGuid())->first(); + $scheduleShift = ScheduleShift::whereGuid($event->getGuid())->where('schedule_id', $schedule->id)->first(); $shift = $scheduleShift->shift; $shift->delete(); @@ -509,10 +514,10 @@ class ImportSchedule extends BaseController $scheduleEventsGuidList = array_keys($scheduleEvents); $existingShifts = $this->getScheduleShiftsByGuid($scheduleUrl, $scheduleEventsGuidList); - foreach ($existingShifts as $shift) { - $guid = $shift->guid; + foreach ($existingShifts as $scheduleShift) { + $guid = $scheduleShift->guid; /** @var Shift $shift */ - $shift = Shift::with('location')->find($shift->shift_id); + $shift = Shift::with('location')->find($scheduleShift->shift_id); $event = $scheduleEvents[$guid]; $location = $locations->where('name', $event->getRoom()->getName())->first(); @@ -535,8 +540,8 @@ class ImportSchedule extends BaseController } $scheduleShifts = $this->getScheduleShiftsWhereNotGuid($scheduleUrl, $scheduleEventsGuidList); - foreach ($scheduleShifts as $shift) { - $event = $this->eventFromScheduleShift($shift); + foreach ($scheduleShifts as $scheduleShift) { + $event = $this->eventFromScheduleShift($scheduleShift); $deleteEvents[$event->getGuid()] = $event; } @@ -545,8 +550,7 @@ class ImportSchedule extends BaseController protected function eventFromScheduleShift(ScheduleShift $scheduleShift): Event { - /** @var Shift $shift */ - $shift = Shift::with('location')->find($scheduleShift->shift_id); + $shift = $scheduleShift->shift; $duration = $shift->start->diff($shift->end); return new Event( diff --git a/includes/pages/user_shifts.php b/includes/pages/user_shifts.php index bfaa692d..63b5fb9e 100644 --- a/includes/pages/user_shifts.php +++ b/includes/pages/user_shifts.php @@ -126,7 +126,9 @@ function load_locations(bool $onlyWithActiveShifts = false) $locationIdsFromShift = Shift::query() ->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'); $locations->whereIn('id', $locationIdsFromAngelType) diff --git a/resources/lang/de_DE/default.po b/resources/lang/de_DE/default.po index 3677eac9..85661719 100644 --- a/resources/lang/de_DE/default.po +++ b/resources/lang/de_DE/default.po @@ -722,8 +722,11 @@ msgstr "Schichtwechsel-Stunden" msgid "Create a shift over midnight." msgstr "Erstelle Schichten über Mitternacht" -msgid "Take needed angels from location settings" -msgstr "Übernehme benötigte Engel von den Ort-Einstellungen" +msgid "Copy needed angels from location settings" +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" msgstr "Die folgenden Engel werden benötigt" @@ -1447,6 +1450,9 @@ msgstr "Programm URL (schedule.xml)" msgid "schedule.shift-type" msgstr "Schichttyp" +msgid "schedule.needed-from-shift-type" +msgstr "Engeltypen vom Schichttyp laden (sonst vom Ort)" + msgid "schedule.minutes-before" msgstr "Minuten vor Talk beginn hinzufügen" @@ -1934,7 +1940,7 @@ msgid "location.map_url" msgstr "Karte" msgid "location.required_angels" -msgstr "Benötigte Engel" +msgstr "Benötigte Engel (bei Fahrplan import)" msgid "location.map_url.info" msgstr "Die Karte wird auf der Ort-Seite als iframe eingebettet." @@ -1960,6 +1966,9 @@ msgstr "Schichttyp erstellen" msgid "shifttype.delete.title" msgstr "Schichttyp \"%s\" löschen" +msgid "shifttype.required_angels" +msgstr "Benötigte Engel (bei Fahrplan import)" + msgid "event.day" msgstr "Tag %1$d" diff --git a/resources/lang/en_US/default.po b/resources/lang/en_US/default.po index 167da913..11372266 100644 --- a/resources/lang/en_US/default.po +++ b/resources/lang/en_US/default.po @@ -151,6 +151,9 @@ msgstr "Schedule URL (schedule.xml)" msgid "schedule.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" msgstr "Add minutes before talk begins" @@ -664,7 +667,7 @@ msgid "location.map_url" msgstr "Map" msgid "location.required_angels" -msgstr "Required angels" +msgstr "Required angels (on schedule import)" msgid "location.map_url.info" 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" msgstr "Delete shift type \"%s\"" +msgid "shifttype.required_angels" +msgstr "Required angels (on schedule import)" + msgid "event.day" msgstr "Day %1$d" diff --git a/resources/views/admin/schedule/edit.twig b/resources/views/admin/schedule/edit.twig index 178130a1..953423e3 100644 --- a/resources/views/admin/schedule/edit.twig +++ b/resources/views/admin/schedule/edit.twig @@ -34,6 +34,10 @@ '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'), { 'type': 'number', 'required': true, diff --git a/resources/views/admin/shifttypes/edit.twig b/resources/views/admin/shifttypes/edit.twig index a63ef907..e2993547 100644 --- a/resources/views/admin/shifttypes/edit.twig +++ b/resources/views/admin/shifttypes/edit.twig @@ -23,6 +23,27 @@ 'info': __('form.markdown') }) }} + +
+

{{ __('shifttype.required_angels') }}

+ {% for types in angel_types.chunk(3) %} +
+ {% 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 %} +
+ {{ f.number(name, angel_type.name, { + 'value': f.formData(name, needed ? needed.count : 0), + 'min': 0, + 'step': 1, + }) }} +
+ {% endfor %} +
+ {% endfor %} +
diff --git a/src/Controllers/Admin/ShiftTypesController.php b/src/Controllers/Admin/ShiftTypesController.php index 6b49dadf..c828ed64 100644 --- a/src/Controllers/Admin/ShiftTypesController.php +++ b/src/Controllers/Admin/ShiftTypesController.php @@ -11,7 +11,10 @@ use Engelsystem\Http\Redirector; use Engelsystem\Http\Request; use Engelsystem\Http\Response; use Engelsystem\Http\Validation\Validator; +use Engelsystem\Models\AngelType; +use Engelsystem\Models\Shifts\NeededAngelType; use Engelsystem\Models\Shifts\ShiftType; +use Illuminate\Database\Eloquent\Collection; use Psr\Log\LoggerInterface; class ShiftTypesController extends BaseController @@ -48,10 +51,15 @@ class ShiftTypesController extends BaseController $shiftTypeId = (int) $request->getAttribute('shift_type_id'); $shiftType = $this->shiftType->find($shiftTypeId); + $angeltypes = AngelType::all() + ->sortBy('name'); return $this->response->withView( 'admin/shifttypes/edit', - ['shifttype' => $shiftType] + [ + 'shifttype' => $shiftType, + 'angel_types' => $angeltypes, + ] ); } @@ -77,12 +85,19 @@ class ShiftTypesController extends BaseController 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( $request, [ 'name' => 'required', 'description' => 'required|optional', - ] + ] + $validation ); if (ShiftType::whereName($data['name'])->where('id', '!=', $shiftType->id)->exists()) { @@ -93,12 +108,33 @@ class ShiftTypesController extends BaseController $shiftType->description = $data['description']; $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( - 'Updated shift type "{name}": {description}', + 'Updated shift type "{name}": {description} {angels}', [ 'name' => $shiftType->name, 'description' => $shiftType->description, + 'angels' => $angelsInfo, ] ); diff --git a/src/Controllers/ShiftsController.php b/src/Controllers/ShiftsController.php index 2fbf5a92..af051cf6 100644 --- a/src/Controllers/ShiftsController.php +++ b/src/Controllers/ShiftsController.php @@ -78,6 +78,10 @@ class ShiftsController extends BaseController $query->on('needed_angel_types.shift_id', '=', 'shifts.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 { $query->on('nas.location_id', '=', 'shifts.location_id') ->whereNotNull('schedule_shift.shift_id'); @@ -88,6 +92,7 @@ class ShiftsController extends BaseController ->where(function (EloquentBuilder $query) use ($angelTypes): void { $query ->whereIn('needed_angel_types.angel_type_id', $angelTypes) + ->orWhereIn('nast.angel_type_id', $angelTypes) ->orWhereIn('nas.angel_type_id', $angelTypes); }) // Starts soon @@ -98,7 +103,7 @@ class ShiftsController extends BaseController ->from('shift_entries') ->selectRaw('COUNT(*)') ->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) ->orderBy('start'); diff --git a/src/Models/Shifts/NeededAngelType.php b/src/Models/Shifts/NeededAngelType.php index 5c876bb2..4241f767 100644 --- a/src/Models/Shifts/NeededAngelType.php +++ b/src/Models/Shifts/NeededAngelType.php @@ -15,16 +15,19 @@ use Illuminate\Database\Query\Builder as QueryBuilder; * @property int $id * @property int|null $location_id * @property int|null $shift_id + * @property int|null $shift_type_id * @property int $angel_type_id * @property int $count * * @property-read Location|null $location * @property-read Shift|null $shift + * @property-read ShiftType|null $shiftType * @property-read AngelType $angelType * * @method static QueryBuilder|NeededAngelType[] whereId($value) * @method static QueryBuilder|NeededAngelType[] whereLocationId($value) * @method static QueryBuilder|NeededAngelType[] whereShiftId($value) + * @method static QueryBuilder|NeededAngelType[] whereShiftTypeId($value) * @method static QueryBuilder|NeededAngelType[] whereAngelTypeId($value) * @method static QueryBuilder|NeededAngelType[] whereCount($value) */ @@ -36,12 +39,14 @@ class NeededAngelType extends BaseModel protected $attributes = [ // phpcs:ignore 'location_id' => null, 'shift_id' => null, + 'shift_type_id' => null, ]; /** @var array */ protected $fillable = [ // phpcs:ignore 'location_id', 'shift_id', + 'shift_type_id', 'angel_type_id', 'count', ]; @@ -56,6 +61,11 @@ class NeededAngelType extends BaseModel return $this->belongsTo(Shift::class); } + public function shiftType(): BelongsTo + { + return $this->belongsTo(ShiftType::class); + } + public function angelType(): BelongsTo { return $this->belongsTo(AngelType::class); diff --git a/src/Models/Shifts/Schedule.php b/src/Models/Shifts/Schedule.php index b8cbd89c..e0aafffc 100644 --- a/src/Models/Shifts/Schedule.php +++ b/src/Models/Shifts/Schedule.php @@ -18,6 +18,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder; * @property string $name * @property string $url * @property int $shift_type + * @property bool $needed_from_shift_type * @property int $minutes_before * @property int $minutes_after * @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[] whereUrl($value) * @method static QueryBuilder|Schedule[] whereShiftType($value) + * @method static QueryBuilder|Schedule[] whereNeededFromShiftType($value) * @method static QueryBuilder|Schedule[] whereMinutesBefore($value) * @method static QueryBuilder|Schedule[] whereMinutesAfter($value) * @method static QueryBuilder|Schedule[] whereCreatedAt($value) @@ -46,6 +48,7 @@ class Schedule extends BaseModel /** @var array */ protected $casts = [ // phpcs:ignore 'shift_type' => 'integer', + 'needed_from_shift_type' => 'boolean', 'minutes_before' => 'integer', 'minutes_after' => 'integer', ]; @@ -55,6 +58,7 @@ class Schedule extends BaseModel 'name', 'url', 'shift_type', + 'needed_from_shift_type', 'minutes_before', 'minutes_after', ]; diff --git a/src/Models/Shifts/ShiftType.php b/src/Models/Shifts/ShiftType.php index 5ee37dc0..bc3556ed 100644 --- a/src/Models/Shifts/ShiftType.php +++ b/src/Models/Shifts/ShiftType.php @@ -15,6 +15,7 @@ use Illuminate\Database\Query\Builder as QueryBuilder; * @property string $name * @property string $description * + * @property-read Collection|NeededAngelType[] $neededAngelTypes * @property-read Collection|Schedule[] $schedules * @property-read Collection|Shift[] $shifts * @@ -32,6 +33,11 @@ class ShiftType extends BaseModel 'description', ]; + public function neededAngelTypes(): HasMany + { + return $this->hasMany(NeededAngelType::class); + } + public function schedules(): HasMany { return $this->hasMany(Schedule::class, 'shift_type'); diff --git a/tests/Unit/Controllers/Admin/ShiftTypeControllerTest.php b/tests/Unit/Controllers/Admin/ShiftTypesControllerTest.php similarity index 94% rename from tests/Unit/Controllers/Admin/ShiftTypeControllerTest.php rename to tests/Unit/Controllers/Admin/ShiftTypesControllerTest.php index ebaedf8a..d160ae6d 100644 --- a/tests/Unit/Controllers/Admin/ShiftTypeControllerTest.php +++ b/tests/Unit/Controllers/Admin/ShiftTypesControllerTest.php @@ -11,6 +11,7 @@ use Engelsystem\Http\Exceptions\ValidationException; use Engelsystem\Http\Redirector; use Engelsystem\Http\Request; use Engelsystem\Http\Validation\Validator; +use Engelsystem\Models\AngelType; use Engelsystem\Models\Shifts\ShiftEntry; use Engelsystem\Models\Shifts\ShiftType; use Engelsystem\Models\Shifts\Shift; @@ -18,7 +19,7 @@ use Engelsystem\Models\User\User; use Engelsystem\Test\Unit\Controllers\ControllerTest; use PHPUnit\Framework\MockObject\MockObject; -class ShiftTypeControllerTest extends ControllerTest +class ShiftTypesControllerTest extends ControllerTest { protected Redirector|MockObject $redirect; @@ -82,6 +83,7 @@ class ShiftTypeControllerTest extends ControllerTest $this->assertEquals($shifttype->id, $data['shifttype']?->id); $this->assertNotEmpty($data['shifttype']?->name); $this->assertNotEmpty($data['shifttype']?->description); + $this->assertNotNull($data['angel_types']); return $this->response; }); @@ -104,6 +106,7 @@ class ShiftTypeControllerTest extends ControllerTest $this->assertEquals('admin/shifttypes/edit', $view); $this->assertArrayHasKey('shifttype', $data); $this->assertNull($data['shifttype']); + $this->assertNotNull($data['angel_types']); return $this->response; }); @@ -115,6 +118,8 @@ class ShiftTypeControllerTest extends ControllerTest */ public function testSave(): void { + $angelType = AngelType::factory(2)->create()->first(); + /** @var ShiftTypesController $controller */ $controller = $this->app->make(ShiftTypesController::class); $controller->setValidator(new Validator()); @@ -124,6 +129,8 @@ class ShiftTypeControllerTest extends ControllerTest $this->request = $this->request->withParsedBody([ 'name' => 'Test shift type', 'description' => 'Something', + 'angel_type_' . $angelType->id => 3, + 'angel_type_' . $angelType->id + 1 => 0, ]); $controller->save($this->request); @@ -131,6 +138,7 @@ class ShiftTypeControllerTest extends ControllerTest $this->assertTrue($this->log->hasInfoThatContains('Updated shift type')); $this->assertHasNotification('shifttype.edit.success'); $this->assertCount(1, ShiftType::whereName('Test shift type')->get()); + $this->assertCount(1, ShiftType::first()->neededAngelTypes); } /** diff --git a/tests/Unit/Models/Shifts/NeededAngelTypeTest.php b/tests/Unit/Models/Shifts/NeededAngelTypeTest.php index c7c03a10..f3413021 100644 --- a/tests/Unit/Models/Shifts/NeededAngelTypeTest.php +++ b/tests/Unit/Models/Shifts/NeededAngelTypeTest.php @@ -8,6 +8,7 @@ use Engelsystem\Models\AngelType; use Engelsystem\Models\Location; use Engelsystem\Models\Shifts\NeededAngelType; use Engelsystem\Models\Shifts\Shift; +use Engelsystem\Models\Shifts\ShiftType; use Engelsystem\Test\Unit\Models\ModelTest; class NeededAngelTypeTest extends ModelTest @@ -15,6 +16,7 @@ class NeededAngelTypeTest extends ModelTest /** * @covers \Engelsystem\Models\Shifts\NeededAngelType::location * @covers \Engelsystem\Models\Shifts\NeededAngelType::shift + * @covers \Engelsystem\Models\Shifts\NeededAngelType::shiftType * @covers \Engelsystem\Models\Shifts\NeededAngelType::angelType */ public function testShift(): void @@ -23,12 +25,15 @@ class NeededAngelTypeTest extends ModelTest $location = Location::factory()->create(); /** @var Shift $shift */ $shift = Shift::factory()->create(); + /** @var ShiftType $shiftType */ + $shiftType = ShiftType::factory()->create(); /** @var AngelType $angelType */ $angelType = AngelType::factory()->create(); $model = new NeededAngelType(); $model->location()->associate($location); $model->shift()->associate($shift); + $model->shiftType()->associate($shiftType); $model->angelType()->associate($angelType); $model->count = 3; $model->save(); @@ -36,6 +41,7 @@ class NeededAngelTypeTest extends ModelTest $model = NeededAngelType::find(1); $this->assertEquals($location->id, $model->location->id); $this->assertEquals($shift->id, $model->shift->id); + $this->assertEquals($shiftType->id, $model->shiftType->id); $this->assertEquals($angelType->id, $model->angelType->id); $this->assertEquals(3, $model->count); } diff --git a/tests/Unit/Models/Shifts/ScheduleTest.php b/tests/Unit/Models/Shifts/ScheduleTest.php index 29a514cc..b6ed96a7 100644 --- a/tests/Unit/Models/Shifts/ScheduleTest.php +++ b/tests/Unit/Models/Shifts/ScheduleTest.php @@ -17,6 +17,7 @@ class ScheduleTest extends ModelTest 'url' => 'https://foo.bar/schedule.xml', 'name' => 'Testing', 'shift_type' => 1, + 'needed_from_shift_type' => false, 'minutes_before' => 10, 'minutes_after' => 10, ]; diff --git a/tests/Unit/Models/Shifts/ShiftTypeTest.php b/tests/Unit/Models/Shifts/ShiftTypeTest.php index f1413de1..32147fae 100644 --- a/tests/Unit/Models/Shifts/ShiftTypeTest.php +++ b/tests/Unit/Models/Shifts/ShiftTypeTest.php @@ -4,6 +4,7 @@ declare(strict_types=1); namespace Engelsystem\Test\Unit\Models\Shifts; +use Engelsystem\Models\Shifts\NeededAngelType; use Engelsystem\Models\Shifts\Schedule; use Engelsystem\Models\Shifts\Shift; use Engelsystem\Models\Shifts\ShiftType; @@ -11,6 +12,19 @@ use Engelsystem\Test\Unit\Models\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 */