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') }) }} + +