diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py new file mode 100644 index 000000000..351757701 --- /dev/null +++ b/app/controllers/main_routes/departmentPortal.py @@ -0,0 +1 @@ +from flask import render_template diff --git a/app/controllers/main_routes/laborStatusForm.py b/app/controllers/main_routes/laborStatusForm.py index dc794952d..c958e96cb 100755 --- a/app/controllers/main_routes/laborStatusForm.py +++ b/app/controllers/main_routes/laborStatusForm.py @@ -14,7 +14,7 @@ from flask import json, jsonify from flask import request from datetime import datetime, date, timedelta -from flask import Flask, redirect, url_for, flash +from flask import Flask, redirect, url_for, flash, g from app.logic.emailHandler import* from app.logic.userInsertFunctions import* from app.models.supervisor import Supervisor @@ -22,6 +22,7 @@ from app.controllers.main_routes.laborReleaseForm import createLaborReleaseForm from app.logic.allPendingForms import saveStatus from app.logic.statusFormFunctions import * +from app.logic.allocation import getBandAllocationStatus @main_bp.route('/laborstatusform', methods=['GET']) @@ -170,6 +171,15 @@ def checkTotalHours(termCode, student, hours): totalHours = totalHours + int(hours) return json.dumps(totalHours) +@main_bp.route("/laborstatusform/checkallocation////", methods=["GET"]) +def checkAllocation(departmentOrg, departmentAcct, jobType, hours): + """ Checks the department's allocation status for the hour-band being submitted. """ + dept = Department.get_or_none(Department.ORG == departmentOrg, Department.ACCOUNT == departmentAcct) + if not dept: + return jsonify(None) + status = getBandAllocationStatus(dept, g.openTerm, jobType, int(hours)) + return jsonify(status) + @main_bp.route("/laborStatusForm/modal/releaseAndRehire", methods=['POST']) def releaseAndRehire(): try: diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index b3af06c9e..ac48a1c4a 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -1,14 +1,12 @@ from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify -from peewee import JOIN +from peewee import JOIN, DoesNotExist from functools import reduce import operator from app.models.department import Department from app.models.supervisor import Supervisor from app.models.supervisorDepartment import SupervisorDepartment from app.models.student import Student -from app.models.laborStatusForm import LaborStatusForm from app.models.formHistory import FormHistory -from app.models.term import Term from app.controllers.admin_routes.allPendingForms import checkAdjustment from app.controllers.main_routes import main_bp from app.logic.download import CSVMaker, saveFormSearchResult, retrieveFormSearchResult @@ -16,6 +14,8 @@ from app.login_manager import require_login, logout from app.logic.getTableData import getDatatableData from app.logic.banner import Banner +from app.logic.tracy import Tracy +from app.logic.allocation import getAllocationSummary @main_bp.route('/logout', methods=['GET']) def triggerLogout(): @@ -47,6 +47,67 @@ def supervisorPortal(): currentUser = currentUser ) +@main_bp.route('/department', methods=['GET']) +@main_bp.route('/department/', methods=['GET']) +@main_bp.route('/department//', methods=['GET']) +def departmentPortal(org=None,account=None): + if org and account: + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + dept = None + else: + dept = None + + if g.currentUser.isLaborAdmin: + departments = list(Department.select().order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) + else: + departments = list(getDepartmentsForSupervisor(g.currentUser).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) + + pos = Tracy().getPositionsFromDepartment(org, account) + positions = [] + if pos == []: + positions = ["No Positions for this Department"] + else: + for i in pos: + positions.append(i.POSN_TITLE + "" + "(" + i.WLS + ")") + + staff = Tracy().getSupervisors() + supervisors = [] + + for i in staff: + if i.ORG == org: + supervisors.append(i.FIRST_NAME + " " + i.LAST_NAME + " (" + i.EMAIL + ")") + + allocationSummary = getAllocationSummary(dept, g.openTerm) + + return render_template('main/departmentPortal.html', + departments = departments, + department = dept, + positions = positions, + supervisors = supervisors, + allocation = allocationSummary['allocation'], + allocationBands = allocationSummary['allocationBands'], + totalPositionsAllocated = allocationSummary['totalPositionsAllocated'], + totalPositionsUsed = allocationSummary['totalPositionsUsed'], + breakHoursUsed = allocationSummary['breakHoursUsed'], + currentTerm = g.openTerm) + +@main_bp.route('/department///managepositions', methods=['GET']) +def managePositions(org, account): + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except DoesNotExist: + return render_template('errors/404.html'), 404 + + positions = Tracy().getPositionsFromDepartment(org, account) + print(positions) + return render_template('main/managepositions.html', + department = dept, + department_name = dept.DEPT_NAME, + positions = positions + ) + @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) def addUserToDept(): userDeptData = request.form @@ -113,4 +174,5 @@ def submitToBanner(formHistoryId): if save_form_status: return "Form successfully submitted to Banner.", 200 else: - return "Submitting to Banner failed.", 500 \ No newline at end of file + return "Submitting to Banner failed.", 500 + diff --git a/app/logic/allPendingForms.py b/app/logic/allPendingForms.py index a9d4a3bd2..f93af930d 100644 --- a/app/logic/allPendingForms.py +++ b/app/logic/allPendingForms.py @@ -1,6 +1,6 @@ import json from datetime import date -from flask import jsonify +from flask import jsonify, g, flash from app.models.formHistory import FormHistory from app.models.status import Status from app.logic.banner import Banner @@ -14,9 +14,11 @@ from app.models.overloadForm import OverloadForm from app.models.notes import Notes from app.login_manager import DoesNotExist, render_template +from app.logic.allocation import getAllocationWarning def saveStatus(new_status, formHistoryIds, currentUser): + approvedDepartments = {} try: if new_status == 'Denied by Admin': # Index 1 will always hold the reject reason in the list, so we can @@ -66,6 +68,8 @@ def saveStatus(new_status, formHistoryIds, currentUser): email.laborStatusFormRejected() if new_status == "Approved" and formType == "Labor Status Form": email.laborStatusFormApproved() + dept = formHistory.formID.department + approvedDepartments[dept.departmentID] = dept if new_status == "Approved" and formType == "Labor Adjustment Form": # This function is triggered whenever an adjustment form is approved. # The following function overrides the original data in lsf with the new data from adjustment form. @@ -80,6 +84,16 @@ def saveStatus(new_status, formHistoryIds, currentUser): print("Error preparing form for status update:", e) return jsonify({"success": False}), 500 + # After approving, let the admin know right away if any affected department + # is now over its allocated positions or break hours (informational only). + for dept in approvedDepartments.values(): + warning = getAllocationWarning(dept, g.openTerm) + if warning and warning['isOverAllocated']: + messageParts = [f"{b['label']} ({b['used']}/{b['allocated']})" for b in warning['overAllocatedBands']] + if warning['isBreakHoursOverAllocated']: + messageParts.append(f"break hours ({warning['breakHoursUsed']}/{warning['breakHoursAllocated']})") + flash(f"{dept.DEPT_NAME} is now over its allocation for: {', '.join(messageParts)}.", "warning") + return jsonify({"success": True}) def overrideOriginalStatusFormOnAdjustmentFormApproval(form, LSF): @@ -196,9 +210,8 @@ def laborAdminOverloadApproval(rsp, historyForm, status, currentUser, currentDat # extract data from the database to populate pending form approval modal def modal_approval_and_denial_data(formHistoryIdList): - ''' This method grabs the data that populated the on approve modal for lsf''' - details_list = [] + allocationWarningsByDept = {} for fhID in formHistoryIdList: formHistory = FormHistory.get(FormHistory.formHistoryID == fhID) lsf = formHistory.formID @@ -208,7 +221,8 @@ def modal_approval_and_denial_data(formHistoryIdList): supervisorName = f"{lsf.supervisor.FIRST_NAME} {lsf.supervisor.LAST_NAME}" weeklyHours = lsf.weeklyHours contractHours = lsf.contractHours - deptName = lsf.department.DEPT_NAME + dept = lsf.department + deptName = dept.DEPT_NAME if formHistory.adjustedForm: match formHistory.adjustedForm.fieldAdjusted: @@ -223,11 +237,17 @@ def modal_approval_and_denial_data(formHistoryIdList): case "contractHours": contractHours = formHistory.adjustedForm.newValue case "department": - deptName = Department.get(Department.ORG==formHistory.adjustedForm.newValue).DEPT_NAME + dept = Department.get(Department.ORG==formHistory.adjustedForm.newValue) + deptName = dept.DEPT_NAME details_list.append([studentName, deptName, position, str(weeklyHours),str(contractHours), supervisorName]) - return details_list + if dept.departmentID not in allocationWarningsByDept: + warning = getAllocationWarning(dept, g.openTerm) + if warning: + allocationWarningsByDept[dept.departmentID] = warning + + return {"details": details_list, "allocationWarnings": list(allocationWarningsByDept.values())} def financialAidSAASOverloadApproval(historyForm, rsp, status, currentUser, currentDate): diff --git a/app/logic/allocation.py b/app/logic/allocation.py new file mode 100644 index 000000000..1557c01b5 --- /dev/null +++ b/app/logic/allocation.py @@ -0,0 +1,123 @@ +from peewee import fn +from app.models.allocation import Allocation +from app.models.laborStatusForm import LaborStatusForm +from app.models.formHistory import FormHistory +from app.models.term import Term + +# Each entry is (Allocation field name, LaborStatusForm.jobType, LaborStatusForm.weeklyHours) +ALLOCATION_BAND_FIELDS = [ + ('primary_10', 'Primary', 10), + ('primary_12', 'Primary', 12), + ('primary_15', 'Primary', 15), + ('primary_20', 'Primary', 20), + ('secondary_5', 'Secondary', 5), + ('secondary_10', 'Secondary', 10), +] + +BAND_LABELS = {fieldName: f"{hours} Hour {jobType}" for fieldName, jobType, hours in ALLOCATION_BAND_FIELDS} + + +def getAllocationSummary(dept, term): + summary = { + 'allocation': None, + 'allocationBands': None, + 'totalPositionsAllocated': None, + 'totalPositionsUsed': None, + 'breakHoursUsed': None, + } + + if not (dept and term): + return summary + + allocation = Allocation.get_or_none(Allocation.department == dept, Allocation.termCode == term) + summary['allocation'] = allocation + if not allocation: + return summary + + allocationBands = {} + for fieldName, jobType, hours in ALLOCATION_BAND_FIELDS: + used = (LaborStatusForm + .select() + .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) + .where(LaborStatusForm.department == dept, + LaborStatusForm.termCode == term, + LaborStatusForm.jobType == jobType, + LaborStatusForm.weeklyHours == hours, + FormHistory.historyType == "Labor Status Form", + ~(FormHistory.status % "Denied%")) + .distinct() + .count()) + allocationBands[fieldName] = {'used': used, 'allocated': getattr(allocation, fieldName)} + + summary['allocationBands'] = allocationBands + summary['totalPositionsAllocated'] = sum(band['allocated'] for band in allocationBands.values()) + summary['totalPositionsUsed'] = sum(band['used'] for band in allocationBands.values()) + + # Break hours are tracked on separate break-term rows (e.g. Thanksgiving Break) + # that share the same academic year prefix as the given AY term. + yearPrefix = str(term.termCode)[:-2] + breakTermCodes = [t.termCode for t in Term.select().where(Term.isBreak == True) + if str(t.termCode).startswith(yearPrefix)] + summary['breakHoursUsed'] = (LaborStatusForm + .select(fn.SUM(LaborStatusForm.contractHours)) + .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) + .where(LaborStatusForm.department == dept, + LaborStatusForm.termCode.in_(breakTermCodes), + FormHistory.historyType == "Labor Status Form", + ~(FormHistory.status % "Denied%")) + .scalar()) or 0 + + return summary + + +def getBandAllocationStatus(dept, term, jobType, hours): + fieldName = next((f for f, j, h in ALLOCATION_BAND_FIELDS if j == jobType and h == hours), None) + if not fieldName: + return None + + summary = getAllocationSummary(dept, term) + if not summary['allocationBands']: + return None + + band = summary['allocationBands'][fieldName] + return { + 'label': BAND_LABELS[fieldName], + 'used': band['used'], + 'allocated': band['allocated'], + 'remaining': band['allocated'] - band['used'], + 'isOverAllocated': band['used'] > band['allocated'], + } + + +def getAllocationWarning(dept, term): + summary = getAllocationSummary(dept, term) + if not summary['allocation']: + return None + + positionsRemaining = summary['totalPositionsAllocated'] - summary['totalPositionsUsed'] + breakHoursRemaining = summary['allocation'].breakHours - summary['breakHoursUsed'] + + # A department can be within its total position count while still exceeding + # one specific hour-band (e.g. over on 10-hour Primary but under on others), + # so each band needs to be checked individually, not just the aggregate total. + overAllocatedBands = [ + {'label': BAND_LABELS[fieldName], 'used': band['used'], 'allocated': band['allocated']} + for fieldName, band in summary['allocationBands'].items() + if band['used'] > band['allocated'] + ] + isPositionsOverAllocated = positionsRemaining < 0 or bool(overAllocatedBands) + isBreakHoursOverAllocated = breakHoursRemaining < 0 + + return { + 'departmentName': dept.DEPT_NAME, + 'totalPositionsAllocated': summary['totalPositionsAllocated'], + 'totalPositionsUsed': summary['totalPositionsUsed'], + 'positionsRemaining': positionsRemaining, + 'isPositionsOverAllocated': isPositionsOverAllocated, + 'overAllocatedBands': overAllocatedBands, + 'breakHoursAllocated': summary['allocation'].breakHours, + 'breakHoursUsed': summary['breakHoursUsed'], + 'breakHoursRemaining': breakHoursRemaining, + 'isBreakHoursOverAllocated': isBreakHoursOverAllocated, + 'isOverAllocated': isPositionsOverAllocated or isBreakHoursOverAllocated, + } diff --git a/app/models/allocation.py b/app/models/allocation.py new file mode 100644 index 000000000..eb83877a0 --- /dev/null +++ b/app/models/allocation.py @@ -0,0 +1,23 @@ +from app.models import * +from app.models.department import Department +from app.models.supervisor import Supervisor +from app.models.term import Term + +class Allocation(baseModel): + termCode = ForeignKeyField(Term) + department = ForeignKeyField(Department) + isFinal = BooleanField(default=False) + approvedOn = DateField(null=True) + approvedBy = ForeignKeyField(Supervisor, null=True) + justification = TextField() + primary_10 = IntegerField() + primary_12 = IntegerField() + primary_15 = IntegerField() + primary_20 = IntegerField() + secondary_5 = IntegerField() + secondary_10 = IntegerField() + breakHours = IntegerField() + + class Meta: + indexes = ( (('termCode', 'department', 'isFinal'), True), ) + diff --git a/app/models/positionHistory.py b/app/models/positionHistory.py new file mode 100644 index 000000000..3679cf147 --- /dev/null +++ b/app/models/positionHistory.py @@ -0,0 +1,13 @@ +from app.models import * +from app.models.department import Department + +class PositionHistory(baseModel): + positionCode = CharField() + department = ForeignKeyField(Department) + status = CharField() + wls = IntegerField() + revisionDate = DateField() + description = TextField(default=None) + + class Meta: + indexes = ( (('positionCode', 'revisionDate', 'status'), True), ) diff --git a/app/models/supervisor.py b/app/models/supervisor.py index 7c08354d3..e16d43282 100644 --- a/app/models/supervisor.py +++ b/app/models/supervisor.py @@ -16,6 +16,7 @@ class Supervisor(baseModel): legal_name = CharField(null=True) preferred_name = CharField(null=True) isActive = BooleanField(default=False) + isBanned = BooleanField(default=False) @property diff --git a/app/models/supervisorDepartment.py b/app/models/supervisorDepartment.py index 3585e1eb2..dd5b15385 100644 --- a/app/models/supervisorDepartment.py +++ b/app/models/supervisorDepartment.py @@ -3,5 +3,6 @@ from app.models.department import Department class SupervisorDepartment(baseModel): - supervisor = ForeignKeyField(Supervisor, null=True) + supervisor = ForeignKeyField(Supervisor) department = ForeignKeyField(Department) + isCoordinator = BooleanField(default=False) diff --git a/app/static/css/base.css b/app/static/css/base.css index 81f010ff4..8c06943d6 100755 --- a/app/static/css/base.css +++ b/app/static/css/base.css @@ -192,6 +192,83 @@ a { z-index:999; } +.card { + position: relative; + display: -webkit-box; + display: -ms-flexbox; + display: flex; + -webkit-box-orient: vertical; + -webkit-box-direction: normal; + -ms-flex-direction: column; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid rgba(0, 0, 0, 0.125); + border-radius: 0.25rem; + overflow: hidden; +} + +.card-body { + -webkit-box-flex: 1; + -ms-flex: 1 1 auto; + flex: 1 1 auto; + padding: 1rem 1rem; +} + +.card-title { + margin-bottom: 0.5rem; + font-size: 1.25rem; + font-weight: 500; +} + +.card-subtitle { + margin-top: -0.25rem; + margin-bottom: 0; +} + +.card-text:last-child { + margin-bottom: 0; +} + +.card-link + .card-link { + margin-left: 1rem; +} + +.card-header { + padding: 0.5rem 1rem; + margin-bottom: 0; + background-color: rgba(0, 0, 0, 0.03); + border-bottom: 1px solid rgba(0, 0, 0, 0.125); +} + +.card-header:first-child { + border-radius: calc(0.25rem - 1px) calc(0.25rem - 1px) 0 0; +} + +.card-footer { + padding: 0.5rem 1rem; + background-color: rgba(0, 0, 0, 0.03); + border-top: 1px solid rgba(0, 0, 0, 0.125); + border-bottom-left-radius: 1rem; + border-bottom-right-radius: 1rem; + +} + +.card-footer:last-child { + border-radius: 0 0 calc(0.25rem - 1px) calc(0.25rem - 1px); +} + +.card-img, .card-img-top { + width: 100%; +} + +.card-img-top { + border-top-left-radius: calc(0.25rem - 1px); + border-top-right-radius: calc(0.25rem - 1px); +} + .site-footer { position: fixed; left: 280px; diff --git a/app/static/css/managepositions.css b/app/static/css/managepositions.css new file mode 100644 index 000000000..9c40bbe38 --- /dev/null +++ b/app/static/css/managepositions.css @@ -0,0 +1,15 @@ +.width-12{ + width:12%; +} + +*{ + /* outline:solid 1px lime; */ + margin:0; + padding:0; + box-sizing:border-box; +} + +.department-header{ + padding:1%; + margin-top:-20px; +} \ No newline at end of file diff --git a/app/static/js/allPendingForms.js b/app/static/js/allPendingForms.js index ae79740e4..19034745a 100644 --- a/app/static/js/allPendingForms.js +++ b/app/static/js/allPendingForms.js @@ -87,8 +87,8 @@ function insertApprovals(laborHistoryId = null) { contentType: 'application/json', success: function(response) { if (response) { - var returned_details = response; - updateApproveTableData(returned_details); + updateApproveTableData(response.details); + updateAllocationWarnings(response.allocationWarnings); } } }); @@ -112,6 +112,40 @@ function updateApproveTableData(returned_details) { } } +// Shows a non-blocking allocation warning per department represented among the +// selected forms, so admins can see the impact of approval before confirming. +// Each category (positions / break hours) is highlighted independently, since +// a department can be over on one and fine on the other. +function updateAllocationWarnings(allocationWarnings) { + if (!allocationWarnings) { return; } + for (var i = 0; i < allocationWarnings.length; i++) { + var w = allocationWarnings[i]; + var boxClass = w.isOverAllocated ? 'alert-warning' : 'alert-info'; + var overStyle = 'color:#a94442; font-weight:bold;'; + var positionsStyle = w.isPositionsOverAllocated ? overStyle : ''; + var breakHoursStyle = w.isBreakHoursOverAllocated ? overStyle : ''; + var positionsFlag = w.isPositionsOverAllocated ? ' ⚠ Over allocation' : ''; + var breakHoursFlag = w.isBreakHoursOverAllocated ? ' ⚠ Over allocation' : ''; + // A department can look fine in total while one specific hour-band is over, + // so call those bands out by name instead of only showing the aggregate. + var bandDetail = ''; + if (w.overAllocatedBands && w.overAllocatedBands.length > 0) { + var bandStrings = w.overAllocatedBands.map(function(b) { + return b.label + ' (' + b.used + ' used / ' + b.allocated + ' allocated)'; + }); + bandDetail = '
Over on: ' + bandStrings.join(', ') + ''; + } + var html = ''; + $('#allocationWarnings').append(html); + } +} + $('#approvalModal').on('hidden.bs.modal', function () {// Makes the close functionality work when clicking outside of the modal approvalModalClose(); @@ -120,6 +154,7 @@ $('#approvalModal').on('hidden.bs.modal', function () {// Makes the close functi function approvalModalClose(){// on close of approval modal we are clearing the table to prevent duplicate data. $('#classTableBody').empty(); + $('#allocationWarnings').empty(); labor_details_ids = [] // emptying the list, becuase otherwise will cause duplicate data. } diff --git a/app/static/js/departmentPortal.js b/app/static/js/departmentPortal.js new file mode 100644 index 000000000..d8dbbc84e --- /dev/null +++ b/app/static/js/departmentPortal.js @@ -0,0 +1,7 @@ +$(document).ready(function() { + $("#selectedDepartment").on("change",function() { + deptData = $(this).find('option:selected').data(); + window.location = `/department/${deptData.org}/${deptData.account}`; + + }); +}); diff --git a/app/static/js/laborStatusForm.js b/app/static/js/laborStatusForm.js index ff9b252a9..91d7ec783 100755 --- a/app/static/js/laborStatusForm.js +++ b/app/static/js/laborStatusForm.js @@ -11,6 +11,7 @@ $(document).ready(function(){ var value = $("#selectedHoursPerWeek").val(); $("#selectedHoursPerWeek").val(value); fillHoursPerWeek("fillhours"); + checkAllocation(); } var cookies = document.cookie; if (cookies){ @@ -336,6 +337,40 @@ function checkCompliance(obj) { }); } +// Checks the department's allocation status for the selected job type/hours band. +// This is informational only and never blocks or disables form submission. +function checkAllocation() { + $("#allocation-remaining-text").hide(); + $("#allocation-warning").hide(); + + var departmentSelect = $("#selectedDepartment"); + var departmentOrg = departmentSelect.val(); + var departmentAcct = departmentSelect.find('option:selected').attr('value-account'); + var jobType = $("#jobType").val(); + var hours = $("#selectedHoursPerWeek").val(); + + if (!departmentOrg || !jobType || !hours) { + return; + } + + var url = "/laborstatusform/checkallocation/" + departmentOrg + "/" + departmentAcct + "/" + jobType + "/" + hours; + $.ajax({ + url: url, + dataType: "json", + success: function (response){ + if (!response) { + return; + } + var remaining = response.remaining >= 0 ? response.remaining : 0; + $("#allocation-remaining-text").text(response.label + " Positions: " + response.used + "/" + response.allocated + " used (" + remaining + " remaining)").show(); + if (response.isOverAllocated) { + $("#allocation-warning-text").html("This department is already over its allocation for " + response.label + " positions (" + response.used + "/" + response.allocated + "). You may still submit this form, but please contact the Labor Office."); + $("#allocation-warning").show(); + } + } + }); +} + // TABLE LABELS $("#contractHours").hide(); $("#hoursPerWeek").hide(); diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html new file mode 100644 index 000000000..494a92f6d --- /dev/null +++ b/app/templates/main/departmentPortal.html @@ -0,0 +1,151 @@ +{% extends "base.html" %} + +{% block scripts %} +{{super()}} + + {% if department %} {{department.DEPT_NAME}} Portal {% else %} Choose a Department: {% endif %} + + + +
+ +
+ +{% if department %} +
+ +
+
+
+
+ +
+
+
+
+

{{ currentTerm.termName.replace("AY", "Term") if currentTerm else "No open term" }}

+

{{ totalPositionsUsed if allocation else "No allocation" }}{% if allocation %}/{{ totalPositionsAllocated }}{% endif %} Positions

+
+ {% if allocation %} +
+
+

Primary

+
    +
  • 10 Hour - {{ allocationBands.primary_10.used }}/{{ allocationBands.primary_10.allocated }}
  • +
  • 12 Hour - {{ allocationBands.primary_12.used }}/{{ allocationBands.primary_12.allocated }}
  • +
  • 15 Hour - {{ allocationBands.primary_15.used }}/{{ allocationBands.primary_15.allocated }}
  • +
  • 20 Hour - {{ allocationBands.primary_20.used }}/{{ allocationBands.primary_20.allocated }}
  • +
+
+
+

Secondary

+
    +
  • 5 Hour - {{ allocationBands.secondary_5.used }}/{{ allocationBands.secondary_5.allocated }}
  • +
  • 10 Hour - {{ allocationBands.secondary_10.used }}/{{ allocationBands.secondary_10.allocated }}
  • +
+
+
+ {% endif %} +
+
+

Break Hours

+

{{ breakHoursUsed if allocation else "No allocation" }}{% if allocation %}/{{ allocation.breakHours }}{% endif %} Hours

+
+
+ +
+ +
+
+
+
+ +
+ +
+

+ Members +

+
+
+

Labor Coordinator(s)

+

Dr. Jones

+ +

Supervisors

+ {% for s in supervisors %} + {% if 3 > loop.index0 %} +

{{ s }}

+ {% elif 4 > loop.index0 %} +

and {{ supervisors | length}} more...

+ {% endif %} + {% endfor %} +
+ + +
+ +
+
+
+
+ +
+
+

Positions

+
+ +
+
    + {% for p in positions %} + {% if 7 > loop.index0 %} +
  • {{ p }}
  • + {% elif 8 == loop.index0 %} +

and {{ positions | length}} more...

+ {% endif %} + {% endfor %} + + +
+ +
+
+ +{% endif %} + + +{% endblock %} + + + + diff --git a/app/templates/main/laborStatusForm.html b/app/templates/main/laborStatusForm.html index 83b2b2864..ad7e03a84 100755 --- a/app/templates/main/laborStatusForm.html +++ b/app/templates/main/laborStatusForm.html @@ -85,7 +85,7 @@

Labor Status Form data-live-search='true' title="Department" data-width="100%" - onchange = "checkCompliance(this); getDepartment(this); showAccessLevel()"> + onchange = "checkCompliance(this); getDepartment(this); showAccessLevel(); checkAllocation()"> {% for department in departments %} @@ -203,10 +203,12 @@

Labor Status Form id='selectedHoursPerWeek' data-live-search='true' data-width="100%" - title="Hours per week" > + title="Hours per week" + onchange="checkAllocation()"> {% if forms %} {% endif %} ​ +
@@ -222,6 +224,9 @@

Labor Status Form

+
+
+ + +
+ + + + + + + + + + + + + + + {% for position in positions %} + + + + + + + + + {% endfor %} + +
Position (WSL)Position CodeStatusLast Revision DateView Position DescriptionEdit Position Description
{{position.POSN_TITLE}} ({{position.WLS}}){{position.POSN_CODE}} +

+ Active +

+
01/01/2024 + + + +
+
+

+ Total Positions: + {{ positions|length }} +

+ + +{% endblock %} diff --git a/app/templates/main/supervisorPortal.html b/app/templates/main/supervisorPortal.html index 78383566c..3ab521a54 100644 --- a/app/templates/main/supervisorPortal.html +++ b/app/templates/main/supervisorPortal.html @@ -227,7 +227,7 @@

Form Search


-
+