diff --git a/app/controllers/main_routes/laborStatusForm.py b/app/controllers/main_routes/laborStatusForm.py index dc794952d..3f050938a 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, getAllocationWarning @main_bp.route('/laborstatusform', methods=['GET']) @@ -170,6 +171,41 @@ def checkTotalHours(termCode, student, hours): totalHours = totalHours + int(hours) return json.dumps(totalHours) +@main_bp.route("/laborstatusform/checkallocation", methods=["GET"]) +def checkAllocation(): + """ Checks the department's allocation status for the hour-band being submitted. """ + departmentOrg = request.args.get("departmentOrg") + departmentAcct = request.args.get("departmentAcct") + jobType = request.args.get("jobType") + hours = request.args.get("hours") + + dept = Department.get_or_none(Department.ORG == departmentOrg, Department.ACCOUNT == departmentAcct) + if not dept: + return jsonify({"error": "Department not found"}), 404 + + status = getBandAllocationStatus(dept, g.openTerm, jobType, int(hours)) + if status is None: + return jsonify({"error": "No allocation data for this job type/hours band"}), 404 + + return jsonify(status) + +@main_bp.route("/laborstatusform/allocationsummary", methods=["GET"]) +def allocationSummary(): + """ Returns the department's current total-positions and break-hours allocation status, so the + labor status form can show a live summary that updates as students are added before submission. """ + departmentOrg = request.args.get("departmentOrg") + departmentAcct = request.args.get("departmentAcct") + + dept = Department.get_or_none(Department.ORG == departmentOrg, Department.ACCOUNT == departmentAcct) + if not dept: + return jsonify({"error": "Department not found"}), 404 + + warning = getAllocationWarning(dept, g.openTerm) + if warning is None: + return jsonify({"error": "No allocation data for this department"}), 404 + + return jsonify(warning) + @main_bp.route("/laborStatusForm/modal/releaseAndRehire", methods=['POST']) def releaseAndRehire(): try: 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..35219eabe --- /dev/null +++ b/app/logic/allocation.py @@ -0,0 +1,139 @@ +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 + +# (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 getTotalAllocations(term, dept): + """Return the department's allocated totals per band for a term.""" + if not term or not dept: + return None + + allocation = Allocation.get_or_none(Allocation.department == dept, Allocation.termCode == term) + if not allocation: + return None + + bandTotals = {fieldName: getattr(allocation, fieldName) for fieldName, _, _ in ALLOCATION_BAND_FIELDS} + return { + "allocation": allocation, + "bandTotals": bandTotals, + "totalAllocations": sum(bandTotals.values()), + } + + +def getContractedAllocations(term, dept): + """Return the department's used positions per band and approved break hours for a term.""" + if not term or not dept: + return None + + usedPositions = {} + for fieldName, jobType, hours in ALLOCATION_BAND_FIELDS: + usedPositions[fieldName] = ( + 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() + ) + + # 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) + ] + breakHours = ( + 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 { + "usedPositions": usedPositions, + "usedTotal": sum(usedPositions.values()), + "breakHours": breakHours, + } + + +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 + + totals = getTotalAllocations(term, dept) + if not totals: + return None + contracted = getContractedAllocations(term, dept) + + allocated = totals["bandTotals"][fieldName] + used = contracted["usedPositions"][fieldName] + return { + 'label': BAND_LABELS[fieldName], + 'used': used, + 'allocated': allocated, + 'remaining': allocated - used, + 'isOverAllocated': used > allocated, + } + + +def getAllocationWarning(dept, term): + totals = getTotalAllocations(term, dept) + if not totals: + return None + contracted = getContractedAllocations(term, dept) + + positionsRemaining = totals["totalAllocations"] - contracted["usedTotal"] + breakHoursRemaining = totals["allocation"].breakHours - contracted["breakHours"] + + # 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': contracted["usedPositions"][fieldName], 'allocated': totals["bandTotals"][fieldName]} + for fieldName, _, _ in ALLOCATION_BAND_FIELDS + if contracted["usedPositions"][fieldName] > totals["bandTotals"][fieldName] + ] + isPositionsOverAllocated = positionsRemaining < 0 or bool(overAllocatedBands) + isBreakHoursOverAllocated = breakHoursRemaining < 0 + + return { + 'departmentName': dept.DEPT_NAME, + 'totalPositionsAllocated': totals["totalAllocations"], + 'totalPositionsUsed': contracted["usedTotal"], + 'positionsRemaining': positionsRemaining, + 'isPositionsOverAllocated': isPositionsOverAllocated, + 'overAllocatedBands': overAllocatedBands, + 'breakHoursAllocated': totals["allocation"].breakHours, + 'breakHoursUsed': contracted["breakHours"], + 'breakHoursRemaining': breakHoursRemaining, + 'isBreakHoursOverAllocated': isBreakHoursOverAllocated, + 'isOverAllocated': isPositionsOverAllocated or isBreakHoursOverAllocated, + } diff --git a/app/models/positionHistory.py b/app/models/positionHistory.py index 9a248aacb..e9b4e0c2f 100644 --- a/app/models/positionHistory.py +++ b/app/models/positionHistory.py @@ -12,4 +12,3 @@ class PositionHistory(baseModel): class Meta: indexes = ( (('positionCode', 'revisionDate', 'status'), True), ) - diff --git a/app/static/css/laborStatusForm.css b/app/static/css/laborStatusForm.css index c87073a9a..a1a8271ca 100755 --- a/app/static/css/laborStatusForm.css +++ b/app/static/css/laborStatusForm.css @@ -20,13 +20,13 @@ .floatleft { float: left; width: 47%; -height: 310px; +min-height: 310px; } .floatright { float: right; width: 47%; -height: 310px; +min-height: 310px; } #mytable { @@ -68,7 +68,7 @@ selectpicker, label { } #plus { - text-align: center; + text-align: left; padding-bottom: 30px; } diff --git a/app/static/js/allPendingForms.js b/app/static/js/allPendingForms.js index ae79740e4..8ec4b59cb 100644 --- a/app/static/js/allPendingForms.js +++ b/app/static/js/allPendingForms.js @@ -41,6 +41,7 @@ $(document).ready(function() { // CHECK ALL CHECKBOX ON APPROVE BUTTON $('#checkAll').change(function(){ $(".approveCheckbox").prop('checked', $(this).prop("checked")); + updatePageAllocationWarnings(); }); @@ -53,11 +54,42 @@ $(document).ready(function() { if ($('.approveCheckbox:checked').length == $('.approveCheckbox').length ){ $("#checkAll")[0].checked = true; } + updatePageAllocationWarnings(); }); }); +// Shows the same per-department allocation warnings directly on the list page as boxes get +// checked/unchecked, so the admin sees the impact before ever opening the approval modal. +// Reuses the same endpoint the modal uses, since it already groups by department and only +// returns warnings for departments represented among the given form IDs. +function updatePageAllocationWarnings() { + if ($('#pageAllocationWarnings').length === 0) { + return; + } + + var checkedIds = $('.approveCheckbox:checked').map(function() { return this.value; }).get(); + $('#pageAllocationWarnings').empty(); + + if (checkedIds.length === 0) { + return; + } + + $.ajax({ + type: "POST", + url: "/admin/checkedForms", + datatype: "json", + data: JSON.stringify(checkedIds), + contentType: 'application/json', + success: function(response) { + if (response) { + renderAllocationWarningBoxes(response.allocationWarnings, '#pageAllocationWarnings'); + } + } + }); +} + var labor_details_ids = []; // for insertApprovals() and final_approval() only function insertApprovals(laborHistoryId = null) { @@ -87,8 +119,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 +144,45 @@ 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. Used both for the approval +// modal and for the live warnings on the list page itself (see updatePageAllocationWarnings). +function renderAllocationWarningBoxes(allocationWarnings, targetSelector) { + var $target = $(targetSelector); + $target.empty(); + 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 normalStyle = 'color:#000000;'; + var positionsStyle = w.isPositionsOverAllocated ? overStyle : normalStyle; + var breakHoursStyle = w.isBreakHoursOverAllocated ? overStyle : normalStyle; + var title = w.isOverAllocated ? (w.departmentName + ' Over Allocation Warning') : (w.departmentName + ' 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 = ''; + $target.append(html); + } +} + +function updateAllocationWarnings(allocationWarnings) { + renderAllocationWarningBoxes(allocationWarnings, '#allocationWarnings'); +} + $('#approvalModal').on('hidden.bs.modal', function () {// Makes the close functionality work when clicking outside of the modal approvalModalClose(); @@ -120,6 +191,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/laborStatusForm.js b/app/static/js/laborStatusForm.js index ff9b252a9..b8e77590b 100755 --- a/app/static/js/laborStatusForm.js +++ b/app/static/js/laborStatusForm.js @@ -6,11 +6,13 @@ $(document).ready(function(){ if($("#selectedDepartment").val()){ // prepopulates position on redirect from rehire button and checks whether department is in compliance. checkCompliance($("#selectedDepartment")); getDepartment($("#selectedDepartment")); + loadAllocationSummary(); } if($("#jobType").val()){ // fills hours per week selectpicker with correct information from laborstatusform. This is triggered on redirect from form history. var value = $("#selectedHoursPerWeek").val(); $("#selectedHoursPerWeek").val(value); fillHoursPerWeek("fillhours"); + checkAllocation(); } var cookies = document.cookie; if (cookies){ @@ -23,6 +25,7 @@ $(document).ready(function(){ $("#selectedSupervisor option[value=" + parsedArrayOfStudentCookies[0].stuSupervisorID + "]").attr('selected', 'selected'); $("#selectedDepartment option[value=\"" + parsedArrayOfStudentCookies[0].stuDepartmentORG + "\"]").attr('selected', 'selected'); getDepartment($("#selectedDepartment")); + loadAllocationSummary(); preFilledDate($("#selectedTerm")); showAccessLevel($("#selectedTerm")); disableTermSupervisorDept(); @@ -336,6 +339,194 @@ 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; + } + + $.ajax({ + url: "/laborstatusform/checkallocation", + data: { + departmentOrg: departmentOrg, + departmentAcct: departmentAcct, + jobType: jobType, + hours: hours + }, + 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(); + } + }, + error: function () { + // Informational only - if the check fails, just leave the allocation panels hidden. + } + }); +} + +// Live department allocation summary (Total Positions / Break Hours). Loaded once per +// department selection, then bumped locally by +1/-1 as students are added or removed +// from the table below, so the supervisor sees the effect immediately without waiting +// on a server round trip for every add/remove. +var allocationSummaryState = null; + +function clearAllocationSummary() { + allocationSummaryState = null; + $("#allocationSummaryPositionsAllocated").text(""); + $("#allocationSummaryPositionsContracted").text(""); + $("#allocationSummaryBreakHoursAllocated").text(""); + $("#allocationSummaryBreakHoursContracted").text(""); + $("#allocation-warning").hide(); +} + +function loadAllocationSummary() { + var departmentSelect = $("#selectedDepartment"); + var departmentOrg = departmentSelect.val(); + var departmentAcct = departmentSelect.find('option:selected').attr('value-account'); + + clearAllocationSummary(); + + if (!departmentOrg) { + return; + } + + $.ajax({ + url: "/laborstatusform/allocationsummary", + data: { + departmentOrg: departmentOrg, + departmentAcct: departmentAcct + }, + dataType: "json", + success: function (response) { + if (!response || response.error) { + return; + } + allocationSummaryState = { + positionsAllocated: response.totalPositionsAllocated, + positionsUsed: response.totalPositionsUsed, + breakHoursAllocated: response.breakHoursAllocated, + breakHoursUsed: response.breakHoursUsed + }; + // Students already staged in the table (e.g. restored from a cookie before this + // request returned) aren't reflected in the server totals yet, since they haven't + // been submitted. Fold them in so the summary matches what's already on screen. + for (var i = 0; i < globalArrayOfStudents.length; i++) { + applyAllocationDelta(globalArrayOfStudents[i], 1); + } + renderAllocationSummary(); + checkLiveAllocationWarning(); + }, + error: function () { + // Informational only - if the check fails, just leave the summary cells blank. + } + }); +} + +function renderAllocationSummary() { + if (!allocationSummaryState) { + return; + } + var s = allocationSummaryState; + $("#allocationSummaryPositionsAllocated").text(s.positionsAllocated); + $("#allocationSummaryPositionsContracted").text(s.positionsUsed); + $("#allocationSummaryBreakHoursAllocated").text(s.breakHoursAllocated); + $("#allocationSummaryBreakHoursContracted").text(s.breakHoursUsed); +} + +// Mutates the running totals only, without touching the DOM. Used both by the live +// add/remove flow below and to silently fold in students already staged in the table +// (e.g. restored from a cookie) when the baseline first loads. Returns the break-hours +// delta actually applied, so callers can decide whether to flash that number too. +function applyAllocationDelta(studentDict, delta) { + if (!allocationSummaryState || !studentDict) { + return 0; + } + allocationSummaryState.positionsUsed += delta; + var breakHoursDelta = 0; + if (studentDict.isTermBreak) { + breakHoursDelta = delta * (parseInt(studentDict.stuContractHours, 10) || 0); + allocationSummaryState.breakHoursUsed += breakHoursDelta; + } + return breakHoursDelta; +} + +// Warns as soon as the students staged in the table (not just what's already saved to the +// database) would push the department over its allocation. checkAllocation() above only +// ever sees committed/approved forms, so it never fires for a batch being built up right +// now in this session - this covers that gap using the live running totals instead. +function checkLiveAllocationWarning() { + if (!allocationSummaryState) { + return; + } + var s = allocationSummaryState; + var overPositions = s.positionsUsed > s.positionsAllocated; + var overBreakHours = s.breakHoursUsed > s.breakHoursAllocated; + + if (!overPositions && !overBreakHours) { + $("#allocation-warning").hide(); + return; + } + + var messages = []; + if (overPositions) { + messages.push("Total Positions (" + s.positionsUsed + "/" + s.positionsAllocated + ")"); + } + if (overBreakHours) { + messages.push("Break Hours (" + s.breakHoursUsed + "/" + s.breakHoursAllocated + ")"); + } + $("#allocation-warning-text").html("The students added so far put this department over its allocation for " + + messages.join(" and ") + ". You may still continue, but please contact the Labor Office."); + $("#allocation-warning").show(); +} + +var allocationFlashTimeoutId = null; + +// delta is +1 when a student is added to the table, -1 when a row is removed. On an add, +// flashes " +1" immediately so the supervisor sees the click +// register right away, then settles to the plain running total a moment later - ready to +// show " +1" on the next add. +function bumpAllocationSummary(studentDict, delta) { + if (!allocationSummaryState || !studentDict) { + return; + } + var previousPositionsUsed = allocationSummaryState.positionsUsed; + var previousBreakHoursUsed = allocationSummaryState.breakHoursUsed; + var breakHoursDelta = applyAllocationDelta(studentDict, delta); + + if (allocationFlashTimeoutId) { + clearTimeout(allocationFlashTimeoutId); + allocationFlashTimeoutId = null; + } + + if (delta > 0) { + $("#allocationSummaryPositionsContracted").text(previousPositionsUsed + " +" + delta); + if (breakHoursDelta > 0) { + $("#allocationSummaryBreakHoursContracted").text(previousBreakHoursUsed + " +" + breakHoursDelta); + } + allocationFlashTimeoutId = setTimeout(renderAllocationSummary, 1500); + } else { + renderAllocationSummary(); + } + checkLiveAllocationWarning(); +} + // TABLE LABELS $("#contractHours").hide(); $("#hoursPerWeek").hide(); @@ -394,6 +585,7 @@ function deleteRow(glyphicon) { for (var i = 0, row; row = table.rows[i]; i++) { if (rowParent === table.rows[i]) { $(glyphicon).parents("tr").remove(); + bumpAllocationSummary(globalArrayOfStudents[i], -1); globalArrayOfStudents.splice(i, 1); if(globalArrayOfStudents.length > 1){ document.cookie = JSON.stringify(globalArrayOfStudents) + ";max-age=28800;"; @@ -571,6 +763,7 @@ function initialLSFInsert(studentDict){ //Add student info to the table if they function createAndFillTable(studentDict) { globalArrayOfStudents.push(studentDict); document.cookie = JSON.stringify(globalArrayOfStudents) + ";max-age=28800;"; + bumpAllocationSummary(studentDict, 1); $("#mytable").show(); $("#jobTable").show(); $("#hoursTable").show(); diff --git a/app/templates/admin/allPendingForms.html b/app/templates/admin/allPendingForms.html index 651cb2d1c..0de4c6f98 100644 --- a/app/templates/admin/allPendingForms.html +++ b/app/templates/admin/allPendingForms.html @@ -120,7 +120,7 @@

{{title}}

{% else %} {# APPROVE #} - {% if allForms.formID.studentSupervisee.isActive and allForms.formID.supervisor.isActive %} {%endif%} + {% endif %} {% endif %} @@ -309,6 +309,7 @@

{{title}}

{% include "snips/pendingApprovalModal.html" %} +
{% endif %} diff --git a/app/templates/main/laborStatusForm.html b/app/templates/main/laborStatusForm.html index 83b2b2864..ce6c76c80 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(); loadAllocationSummary()"> {% for department in departments %}