Skip to content

Commit

Permalink
Staff: added the option to exclude staff from internal coverage or se…
Browse files Browse the repository at this point in the history
…t a coverage priority, updated weighings
  • Loading branch information
SKuipers committed Nov 7, 2024
1 parent d9d4cd8 commit 6ea9916
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 31 deletions.
2 changes: 2 additions & 0 deletions CHANGEDB.php
Original file line number Diff line number Diff line change
Expand Up @@ -882,5 +882,7 @@
ALTER TABLE gibbonMessengerReceipt ADD `unsubscribeKey` varchar(50) DEFAULT NULL AFTER `nameListStudent`;end
UPDATE gibbonAction SET `URLList`='mailingListRecipients_manage.php, mailingListRecipients_manage_add.php, mailingListRecipients_manage_edit.php, mailingListRecipients_manage_delete.php' WHERE name='Manage Mailing List Recipients' AND gibbonModuleID=(SELECT gibbonModuleID FROM gibbonModule WHERE name='Messenger');end
INSERT INTO `gibbonAction` (`gibbonModuleID`, `name`, `precedence`, `category`, `description`, `helpURL`, `URLList`, `entryURL`, `entrySidebar`, `menuShow`, `defaultPermissionAdmin`, `defaultPermissionTeacher`, `defaultPermissionStudent`, `defaultPermissionParent`, `defaultPermissionSupport`, `categoryPermissionStaff`, `categoryPermissionStudent`, `categoryPermissionParent`, `categoryPermissionOther`) VALUES ((SELECT gibbonModuleID FROM gibbonModule WHERE name='Behaviour'), 'View Behaviour Records_myself', '0', 'Behaviour Records', 'View basic details of behaviour records about themselves.', 'teachers/people/behaviour/', 'behaviour_view.php,behaviour_view_details.php', 'behaviour_view.php', 'Y', 'Y', 'N', 'N', 'N', 'N', 'N', 'N', 'Y', 'N', 'N');end
ALTER TABLE `gibbonStaff` ADD `coverageExclude` ENUM('N','Y') NOT NULL DEFAULT 'N' AFTER `biographicalGroupingPriority`;end
ALTER TABLE `gibbonStaff` ADD `coveragePriority` INT(1) DEFAULT 0 AFTER `coverageExclude`;end
";
77 changes: 59 additions & 18 deletions modules/Staff/coverage_planner_assign.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@
}

$dateObject = new \DateTimeImmutable($coverage['date']);
$startOfWeek = $dateObject->modify('last Sunday')->format('Y-m-d');
$endOfWeek = $dateObject->modify('next Sunday')->format('Y-m-d');

$times = $staffCoverageDateGateway->getCoverageTimesByForeignTable($coverage['foreignTable'], $coverage['foreignTableID'], $coverage['date']);

Expand Down Expand Up @@ -137,7 +139,7 @@
'Not Required' => __('Not Required'),
];

$row = $form->addRow()->setClass('border bg-gray-100 rounded mt-6 p-2');
$row = $form->addRow()->setClass('border bg-gray-100 rounded mt-6 p-2 flex justify-between items-center');
$row->addLabel('coverageStatus', __('Status'))->setClass('text-sm font-bold');
$row->addSelect('coverageStatus')
->fromArray($statuses)
Expand All @@ -163,31 +165,63 @@
$people = $subs->getColumn('gibbonPersonID');
$coverageCounts = $staffCoverageGateway->selectCoverageCountsByPerson($people, $coverage['date'])->fetchGroupedUnique();
$subs->joinColumn('gibbonPersonID', 'coverageCounts', $coverageCounts);
$timetableCounts = $staffCoverageGateway->selectTimetableCountsByPerson($people, $startOfWeek, $endOfWeek)->fetchGroupedUnique();
$subs->joinColumn('gibbonPersonID', 'timetableCounts', $timetableCounts);

// Collect together the sub availability data
$subs->transform(function (&$sub) use (&$availability) {
$sub['dates'] = $availability[intval($sub['gibbonPersonID'])] ?? [];
$sub['availability'] = count($sub['dates']);
$sub['workload'] = array_sum(array_map(function ($item) {
return $item['status'] == 'Teaching' || $item['status'] == 'Covering' || $item['status'] == 'Staff Duty'
? $item['mins']
: 0;
}, $sub['dates']));
$sub['absences'] = count(array_filter($sub['dates'], function ($item) {
return $item['status'] == 'Absent' && $item['allDay'] == 'Y';
}));

});

// Sort by highest availability to lowest availability
$subList = $subs->toArray();
usort($subList, function ($a, $b) {
if ($a['available'] != $b['available']) {
return $b['available'] <=> $a['available'];
}
// Get all the maximum values for coverage counts
$counts = ['workload' => 0, 'timetable' => 0, 'absences' => 0, 'weeklyCoverage' => 0, 'yearlyCoverage' => 0];
$subs->transform(function (&$sub) use (&$counts) {
if ($sub['coveragePriority'] > 5) return;
$counts['workload'] = max($sub['workload'] ?? 0, $counts['workload']);
$counts['absences'] = max($sub['absences'] ?? 0, $counts['absences']);
$counts['timetable'] = max($sub['timetableCounts']['totalMinutes'] ?? 0, $counts['timetable']);
$counts['weeklyCoverage'] = max($sub['coverageCounts']['weeklyCoverageMins'] ?? 0, $counts['weeklyCoverage']);
$counts['yearlyCoverage'] = max($sub['coverageCounts']['yearlyCoverage'] ?? 0, $counts['yearlyCoverage']);
});

if ($a['absences'] != $b['absences']) {
return $a['absences'] <=> $b['absences'];
}
// Create a weighing for sorting coverage availability
$subs->transform(function (&$sub) use (&$counts) {
$coveragePriority = ($sub['coveragePriority'] ?? 0) / 9.0;

if ($a['availability'] != $b['availability']) {
return $a['availability'] <=> $b['availability'];
}
// Total workload in minutes on that day
$workloadByDay = 1.0 - min($sub['workload'] ?? 0, $counts['workload']) / max(1, $counts['workload']);

// Total teaching minutes in the week
$weeklyClasses = 1.0 - min($sub['timetableCounts']['totalMinutes'] ?? 0, $counts['timetable']) / max(1, $counts['timetable']);

// Normalized coverage for that week/year
$weeklyCoverage = min($sub['coverageCounts']['weeklyCoverageMins'] ?? 0, $counts['weeklyCoverage']) / max(1, $counts['weeklyCoverage']);
$yearlyCoverage = min($sub['coverageCounts']['yearlyCoverage'] ?? 0, $counts['yearlyCoverage']) / max(1, $counts['yearlyCoverage']);

return ($a['coverageCounts']['totalCoverage'] ?? 0) <=> ($b['coverageCounts']['totalCoverage'] ?? 0);
$sub['coverageWeight'] = ($coveragePriority * 4.5);
$sub['coverageWeight'] += ($workloadByDay * 1.0);
$sub['coverageWeight'] += ($weeklyClasses * 0.35);
$sub['coverageWeight'] += ($weeklyCoverage * -0.15);
$sub['coverageWeight'] += ($yearlyCoverage * -0.04);

$sub['timetableLoad'] = round(min($sub['timetableCounts']['totalMinutes'] ?? 0, $counts['timetable']) / max(1, $counts['timetable']) * 100);

});

// Sort by highest availability to lowest availability
$subList = $subs->toArray();
usort($subList, function ($a, $b) {
return $b['available'] <=> $a['available'] ?: $b['coverageWeight'] <=> $a['coverageWeight'];
});

// Sort the current selected sub to the top of the list
Expand Down Expand Up @@ -256,11 +290,19 @@
return Format::link($url, $name, ['target' => '_blank']).'<br/>'.Format::small($person['jobTitle'] ?? $person['type']);
});

$table->addColumn('load', __('Timetable'))
->format(function ($person) {
$class = 'dull';
if ($person['timetableLoad'] >= 75) $class = 'warning';
if ($person['timetableLoad'] >= 90) $class = 'error';
return Format::tag($person['timetableLoad'].'%', $class);
});

$table->addColumn('details', __('Details'))
->format(function ($person) {
return Format::listDetails([
__('Week') => $person['coverageCounts']['weekCoverage'] ?? 0,
__('Year') => $person['coverageCounts']['totalCoverage'] ?? 0,
__('Week') => $person['coverageCounts']['weeklyCoverage'] ?? 0,
__('Year') => $person['coverageCounts']['yearlyCoverage'] ?? 0,
], 'ul', 'list-none text-xs text-right p-0 m-0', 'w-2/3 whitespace-nowrap');
});

Expand All @@ -279,8 +321,7 @@
$form->toggleVisibilityByClass('unavailableSub')->onCheckbox('showUnavailable')->when('Y');

$row = $form->addRow();
$row->addFooter();
$row->addSubmit();
$row->addSubmit()->addClass('text-right');

echo $form->getOutput();
}
Expand Down
15 changes: 15 additions & 0 deletions modules/Staff/staff_manage_add.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

use Gibbon\Domain\System\SettingGateway;
use Gibbon\Http\Url;
use Gibbon\Forms\Form;
use Gibbon\Forms\CustomFieldHandler;
Expand Down Expand Up @@ -112,6 +113,20 @@
$row->addLabel('biography', __('Biography'));
$row->addTextArea('biography')->setRows(10);

$internalCoverage = $container->get(SettingGateway::class)->getSettingByScope('Staff', 'coverageInternal');
if ($internalCoverage == 'Y') {
$form->addRow()->addHeading('Staff Coverage', __('Staff Coverage'));

$row = $form->addRow();
$row->addLabel('coverageExclude', __('Exclude from coverage?'))->description(__('If enabled, this user will be excluded from internal staff coverage lists.'));
$row->addYesNo('coverageExclude')->placeHolder()->setValue('N');

$form->toggleVisibilityByClass('coveragePriority')->onInput('coverageExclude')->when('N');
$row = $form->addRow()->addClass('coveragePriority');
$row->addLabel('coveragePriority', __('Priority'))->description(__('Higher priority substitutes appear first when booking coverage.'));
$row->addSelect('coveragePriority')->fromArray(range(-9, 9))->required()->selected(0);
}

// Custom Fields
$container->get(CustomFieldHandler::class)->addCustomFieldsToForm($form, 'Staff', ['requiredOverride' => 'N']);

Expand Down
6 changes: 4 additions & 2 deletions modules/Staff/staff_manage_addProcess.php
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,8 @@
$biographicalGrouping = $_POST['biographicalGrouping'] ?? '';
$biographicalGroupingPriority = $_POST['biographicalGroupingPriority'] ?? '';
$biography = $_POST['biography'] ?? '';
$coverageExclude = $_POST['coverageExclude'] ?? 'N';
$coveragePriority = $_POST['coveragePriority'] ?? 0;

//Validate Inputs
if ($gibbonPersonID == '' or $type == '') {
Expand Down Expand Up @@ -97,8 +99,8 @@
} else {
//Write to database
try {
$data = array('gibbonPersonID' => $gibbonPersonID, 'initials' => $initials, 'type' => $type, 'jobTitle' => $jobTitle, 'firstAidQualified' => $firstAidQualified, 'firstAidQualification' => $firstAidQualification, 'firstAidExpiry' => $firstAidExpiry, 'countryOfOrigin' => $countryOfOrigin, 'qualifications' => $qualifications, 'biographicalGrouping' => $biographicalGrouping, 'biographicalGroupingPriority' => $biographicalGroupingPriority, 'biography' => $biography, 'fields' => $fields);
$sql = 'INSERT INTO gibbonStaff SET gibbonPersonID=:gibbonPersonID, initials=:initials, type=:type, jobTitle=:jobTitle, firstAidQualified=:firstAidQualified, firstAidQualification=:firstAidQualification, firstAidExpiry=:firstAidExpiry, countryOfOrigin=:countryOfOrigin, qualifications=:qualifications, biographicalGrouping=:biographicalGrouping, biographicalGroupingPriority=:biographicalGroupingPriority, biography=:biography, fields=:fields';
$data = array('gibbonPersonID' => $gibbonPersonID, 'initials' => $initials, 'type' => $type, 'jobTitle' => $jobTitle, 'firstAidQualified' => $firstAidQualified, 'firstAidQualification' => $firstAidQualification, 'firstAidExpiry' => $firstAidExpiry, 'countryOfOrigin' => $countryOfOrigin, 'qualifications' => $qualifications, 'biographicalGrouping' => $biographicalGrouping, 'biographicalGroupingPriority' => $biographicalGroupingPriority, 'biography' => $biography, 'coveragePriority' => $coveragePriority, 'coverageExclude' => $coverageExclude, 'fields' => $fields);
$sql = 'INSERT INTO gibbonStaff SET gibbonPersonID=:gibbonPersonID, initials=:initials, type=:type, jobTitle=:jobTitle, firstAidQualified=:firstAidQualified, firstAidQualification=:firstAidQualification, firstAidExpiry=:firstAidExpiry, countryOfOrigin=:countryOfOrigin, qualifications=:qualifications, biographicalGrouping=:biographicalGrouping, biographicalGroupingPriority=:biographicalGroupingPriority, biography=:biography, coveragePriority=:coveragePriority, coverageExclude=:coverageExclude, fields=:fields';
$result = $connection2->prepare($sql);
$result->execute($data);
} catch (PDOException $e) {
Expand Down
15 changes: 15 additions & 0 deletions modules/Staff/staff_manage_edit.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
use Gibbon\Forms\DatabaseFormFactory;
use Gibbon\Domain\Staff\StaffContractGateway;
use Gibbon\Domain\Staff\StaffFacilityGateway;
use Gibbon\Domain\System\SettingGateway;

if (isActionAccessible($guid, $connection2, '/modules/Staff/staff_manage_edit.php') == false) {
// Access denied
Expand Down Expand Up @@ -152,6 +153,20 @@
$row->addLabel('biography', __('Biography'));
$row->addTextArea('biography')->setRows(10);

$internalCoverage = $container->get(SettingGateway::class)->getSettingByScope('Staff', 'coverageInternal');
if ($internalCoverage == 'Y') {
$form->addRow()->addHeading('Staff Coverage', __('Staff Coverage'));

$row = $form->addRow();
$row->addLabel('coverageExclude', __('Exclude from coverage?'))->description(__('If enabled, this user will be excluded from internal staff coverage lists.'));
$row->addYesNo('coverageExclude')->placeHolder();

$form->toggleVisibilityByClass('coveragePriority')->onClick('coverageExclude')->when('N');
$row = $form->addRow()->addClass('coveragePriority');
$row->addLabel('coveragePriority', __('Priority'))->description(__('Higher priority substitutes appear first when booking coverage.'));
$row->addSelect('coveragePriority')->fromArray(range(-9, 9))->required();
}

// Custom Fields
$customFieldHandler->addCustomFieldsToForm($form, 'Staff', ['requiredOverride' => 'N'], $values['fields']);

Expand Down
6 changes: 4 additions & 2 deletions modules/Staff/staff_manage_editProcess.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
$biographicalGrouping = $_POST['biographicalGrouping'] ?? '';
$biographicalGroupingPriority = $_POST['biographicalGroupingPriority'] ?? '';
$biography = $_POST['biography'] ?? '';
$coverageExclude = $_POST['coverageExclude'] ?? 'N';
$coveragePriority = $_POST['coveragePriority'] ?? 0;

if ($type == '') {
$URL .= '&return=error3';
Expand Down Expand Up @@ -115,8 +117,8 @@
} else {
//Write to database
try {
$data = array('initials' => $initials, 'type' => $type, 'jobTitle' => $jobTitle, 'dateStart' => $dateStart, 'dateEnd' => $dateEnd, 'firstAidQualified' => $firstAidQualified, 'firstAidQualification' => $firstAidQualification, 'firstAidExpiry' => $firstAidExpiry, 'countryOfOrigin' => $countryOfOrigin, 'qualifications' => $qualifications, 'biographicalGrouping' => $biographicalGrouping, 'biographicalGroupingPriority' => $biographicalGroupingPriority, 'biography' => $biography, 'fields' => $fields, 'gibbonStaffID' => $gibbonStaffID);
$sql = 'UPDATE gibbonStaff JOIN gibbonPerson ON (gibbonStaff.gibbonPersonID=gibbonPerson.gibbonPersonID) SET initials=:initials, type=:type, gibbonStaff.jobTitle=:jobTitle, dateStart=:dateStart, dateEnd=:dateEnd, firstAidQualified=:firstAidQualified, firstAidQualification=:firstAidQualification, firstAidExpiry=:firstAidExpiry, countryOfOrigin=:countryOfOrigin, qualifications=:qualifications, biographicalGrouping=:biographicalGrouping, biographicalGroupingPriority=:biographicalGroupingPriority, biography=:biography, gibbonStaff.fields=:fields WHERE gibbonStaffID=:gibbonStaffID';
$data = array('initials' => $initials, 'type' => $type, 'jobTitle' => $jobTitle, 'dateStart' => $dateStart, 'dateEnd' => $dateEnd, 'firstAidQualified' => $firstAidQualified, 'firstAidQualification' => $firstAidQualification, 'firstAidExpiry' => $firstAidExpiry, 'countryOfOrigin' => $countryOfOrigin, 'qualifications' => $qualifications, 'biographicalGrouping' => $biographicalGrouping, 'biographicalGroupingPriority' => $biographicalGroupingPriority, 'biography' => $biography, 'coveragePriority' => $coveragePriority, 'coverageExclude' => $coverageExclude, 'fields' => $fields, 'gibbonStaffID' => $gibbonStaffID);
$sql = 'UPDATE gibbonStaff JOIN gibbonPerson ON (gibbonStaff.gibbonPersonID=gibbonPerson.gibbonPersonID) SET initials=:initials, type=:type, gibbonStaff.jobTitle=:jobTitle, dateStart=:dateStart, dateEnd=:dateEnd, firstAidQualified=:firstAidQualified, firstAidQualification=:firstAidQualification, firstAidExpiry=:firstAidExpiry, countryOfOrigin=:countryOfOrigin, qualifications=:qualifications, biographicalGrouping=:biographicalGrouping, biographicalGroupingPriority=:biographicalGroupingPriority, biography=:biography, coveragePriority=:coveragePriority, coverageExclude=:coverageExclude, gibbonStaff.fields=:fields WHERE gibbonStaffID=:gibbonStaffID';
$result = $connection2->prepare($sql);
$result->execute($data);
} catch (PDOException $e) {
Expand Down
Loading

0 comments on commit 6ea9916

Please sign in to comment.