From 9bd90f637cb2e72d2ebd41550ee604a467e3b468 Mon Sep 17 00:00:00 2001 From: conwelld Date: Tue, 21 Jul 2026 10:54:46 -0400 Subject: [PATCH 001/128] added changes from previous branch to avoid all files being commited --- app/controllers/main_routes/__init__.py | 1 + app/controllers/main_routes/main_routes.py | 113 ++++- app/models/positionHistory.py | 1 + app/static/css/base.css | 1 + app/static/css/departmentPortal.css | 75 +++ app/static/js/departmentPortal.js | 4 + app/templates/main/departmentPortal.html | 47 +- database/demo_data.py | 515 ++++++++++++++++++++- 8 files changed, 747 insertions(+), 10 deletions(-) create mode 100644 app/static/css/departmentPortal.css diff --git a/app/controllers/main_routes/__init__.py b/app/controllers/main_routes/__init__.py index 54b32d3c5..e8c4c6bb0 100755 --- a/app/controllers/main_routes/__init__.py +++ b/app/controllers/main_routes/__init__.py @@ -25,3 +25,4 @@ def injectGlobalData(): from app.controllers.main_routes import studentLaborEvaluation from app.controllers.main_routes import search from app.controllers.main_routes import studentResponse +from app.controllers.main_routes import departmentPortal diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 0a2f21e4b..de9a189c3 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -1,5 +1,6 @@ from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify -from peewee import JOIN, DoesNotExist +from peewee import JOIN, DoesNotExist, fn +from flask_bootstrap import forms from functools import reduce import operator from app.models.department import Department @@ -16,6 +17,9 @@ from app.login_manager import require_login, logout from app.logic.getTableData import getDatatableData from app.logic.banner import Banner +from app.models.allocation import Allocation +from app.logic.tracy import Tracy +from app.models.positionHistory import PositionHistory @main_bp.route('/logout', methods=['GET']) def triggerLogout(): @@ -51,9 +55,12 @@ def supervisorPortal(): @main_bp.route('/department/', methods=['GET']) @main_bp.route('/department//', methods=['GET']) def departmentPortal(org=None,account=None): - try: - dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) - except (NameError, DoesNotExist): + if org and account: + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + dept = None + else: dept = None @@ -62,11 +69,105 @@ def departmentPortal(org=None,account=None): 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())) + try: + allocation = Allocation.select(Allocation, Term).join(Term).where(Allocation.department == dept, Allocation.termCode == 202500).get() + except DoesNotExist: + allocation = None + + supervisorDepartments = (SupervisorDepartment.select().join(Supervisor).where(SupervisorDepartment.department == dept) + .order_by(fn.COALESCE(Supervisor.preferred_name, Supervisor.legal_name, Supervisor.LAST_NAME).asc())) + + laborCoordinators = [] + supervisors = [] + + for supervisorDepartment in supervisorDepartments: + supervisor = supervisorDepartment.supervisor + + if supervisor is None: + continue + + firstName = supervisor.preferred_name or supervisor.legal_name or "" + lastName = supervisor.LAST_NAME or "" + + supervisorName = f"{firstName} {lastName}".strip() + + supervisorDisplay = { + "name": supervisorName, + "email": supervisor.EMAIL + } + + if supervisorDepartment.isCoordinator: + laborCoordinators.append(supervisorDisplay) + else: + supervisors.append(supervisorDisplay) + + totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == 202500).scalar() + usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(True))]) + studentHours = {} + for form in LaborStatusForm.select().where(LaborStatusForm.department == dept,LaborStatusForm.termCode_id == 202500,LaborStatusForm.contractHours.is_null(True) + ): + studentSuperviseeId = form.studentSupervisee_id + if studentSuperviseeId not in studentHours: + studentHours[studentSuperviseeId] = [] + studentHours[studentSuperviseeId].append({ + "jobType": form.jobType, + "weeklyHours": form.weeklyHours + }) + + + def count_workers(job_type, hours_bucket): + return LaborStatusForm.select().where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.jobType == job_type, LaborStatusForm.weeklyHours == hours_bucket, LaborStatusForm.contractHours.is_null(True)).count() + + usedPositions = { + "used_10": count_workers("Primary", "10"), + "used_12": count_workers("Primary", "12"), + "used_15": count_workers("Primary", "15"), + "used_20": count_workers("Primary", "20"), + "used_5_sec": count_workers("Secondary", "5"), + "used_10_sec": count_workers("Secondary", "10"), +} + break_allocation = LaborStatusForm.select(LaborStatusForm.contractHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(False)) + sumBreak = sum(form.contractHours or 0 for form in break_allocation) + positions = list(PositionHistory.select().where(PositionHistory.department == dept, PositionHistory.status == "Active").order_by(PositionHistory.positionTitle.asc())) if dept else [] + positionsList = [] + posUrl = [] + if not positions: + positionsList = ["No active positions in this department"] + else: + for i in positions: + positionsList.append(i.positionTitle + ": " + "(WLS " + str(i.wls) + ")") + posUrl.append(str(i.positionCode)) return render_template('main/departmentPortal.html', departments = departments, - department = dept) - + department = dept, + allocation = allocation, + total_allocation = totalPositions, + used_allocation = usedAllocation, + term = g.openTerm.termName, + studentHours = studentHours, + usedPositions = usedPositions, + break_hours = sumBreak, + positions = positionsList, + posUrl = posUrl, + supervisors = supervisors, + laborCoordinators=laborCoordinators, + currentUser=g.currentUser + ) +@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 diff --git a/app/models/positionHistory.py b/app/models/positionHistory.py index 514670759..9a248aacb 100644 --- a/app/models/positionHistory.py +++ b/app/models/positionHistory.py @@ -2,6 +2,7 @@ from app.models.department import Department class PositionHistory(baseModel): + positionTitle = CharField() positionCode = CharField() department = ForeignKeyField(Department) status = CharField() diff --git a/app/static/css/base.css b/app/static/css/base.css index 940f8d6e5..6b87b2286 100755 --- a/app/static/css/base.css +++ b/app/static/css/base.css @@ -207,6 +207,7 @@ a { background-clip: border-box; border: 1px solid rgba(0, 0, 0, 0.125); border-radius: 0.25rem; + overflow: hidden; } .card-body { diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css new file mode 100644 index 000000000..6cccf556a --- /dev/null +++ b/app/static/css/departmentPortal.css @@ -0,0 +1,75 @@ +.card { + border-radius: 1rem; + overflow: hidden; + width: 100%; + height: 100%; + min-width: 100%; + box-shadow: 2px 2px 4px rgba(100, 100, 100, 0.26); +} +.bi-suitcase-lg-fill { /* Bootstrap Icon */ + border: 1px solid #c0c0c0; + border-radius: 8px; + padding: 3px 3.5px 1.5px 3.5px; + font-size: 3rem; + color:#6e6e6e; +} +.bi-clock { + border: 1px solid #c0c0c0; + border-radius: 8px; + padding: 3px 3.5px 1.5px 3.5px; + font-size: 3rem; + color:#6e6e6e; +} +.bi-info-circle { + padding: 3px 3.5px 1.5px 3.5px; + font-size: 1.5rem; + vertical-align: middle; + color:#6e6e6e; +} +.header-container { + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 2%; + padding-right: 10px; +} +.allocation-list { + display: flex; + justify-content: space-between; + align-items: center; + padding-left: 10px; + padding-right: 10px; +} +.primary-chart { + font-size: 1.2em; + padding-left: 15%; +} +.secondary-chart { + font-size: 1.2em; + padding-right: 15%; +} + +.bi-people-fill { /* Bootstrap Icon for Members Card */ + border: 1px solid #c0c0c0; + border-radius: 8px; + padding: 3px 3.5px 1.5px 3.5px; + font-size: 3rem; + color:#6e6e6e; +} + +.card-group { + gap: 1rem; +} +.form-group { + width: 50%; + margin-left: auto ; + margin-right:auto; +} +.card-body { + padding: 1rem 1rem; + min-width: 100% +} +.row { + display: flex; + flex-wrap: wrap; +} \ No newline at end of file diff --git a/app/static/js/departmentPortal.js b/app/static/js/departmentPortal.js index cb2023a3a..06ae72e76 100644 --- a/app/static/js/departmentPortal.js +++ b/app/static/js/departmentPortal.js @@ -4,3 +4,7 @@ $(document).ready(function() { window.location = `/department/${deptData.org}/${deptData.account}`; }); }); + +$(function () { + $('[data-toggle="tooltip"]').tooltip() +}) diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 8ee01838b..b51354f0e 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -3,11 +3,12 @@ {% block scripts %} {{super()}} + {% endblock %} - {% block app_content %}

{% if department %} {{department.DEPT_NAME}} Portal {% else %} Choose a Department: {% endif %}

+
+
+{% if department %} +
+
+
+
+
+
+ +
+
+

Allocations

+
+
+

AY 2025-2026

{{used_allocation}}/{{total_allocation or 0}} Positions

+
+
+
    +
  • 10 Hour - {{usedPositions.used_10}}/{{allocation.primary_10}}
  • +
  • 12 Hour - {{usedPositions.used_12}}/{{allocation.primary_12}}
  • +
  • 15 Hour - {{usedPositions.used_15}}/{{allocation.primary_15}}
  • +
  • 20 Hour - {{usedPositions.used_20}}/{{allocation.primary_20}}
  • +
+
    +
  • 5 Hour - {{usedPositions.used_5_sec}}/{{allocation.secondary_5}}
  • +
  • 10 Hour - {{usedPositions.used_10_sec}}/{{allocation.secondary_10}}
  • +
+
+
+

Break Hours

{{break_hours}}/{{allocation.breakHours or 0}} Hours

+
+
+
+ +
+
-{% endblock %} +{% endif %} + +{% endblock %} \ No newline at end of file diff --git a/database/demo_data.py b/database/demo_data.py index de6047624..3bce122a8 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -19,6 +19,10 @@ from app.models.supervisorDepartment import SupervisorDepartment from app.models.allocation import Allocation from app.models.positionHistory import PositionHistory +from app.models.laborReleaseForm import LaborReleaseForm +from app.models.supervisorDepartment import SupervisorDepartment +from app.models.allocation import Allocation +from app.models.positionHistory import PositionHistory print("Inserting data for demo and testing purposes") @@ -41,7 +45,36 @@ "LAST_POSN":"Media Technician", "LAST_SUP_PIDM":"7" }, - + { + "ID":"B00741361", + "PIDM":"99", + "FIRST_NAME":"Antonia", + "LAST_NAME":"Schmith", + "CLASS_LEVEL":"Freshman", + "ACADEMIC_FOCUS":"Computer Science", + "MAJOR":"Computer Science", + "PROBATION":"0", + "ADVISOR":"Scott Heggen", + "STU_EMAIL":"schmitha@berea.edu", + "STU_CPO":"777", + "LAST_POSN":"TA", + "LAST_SUP_PIDM":"7" + }, + { + "ID":"B00732363", + "PIDM":"58", + "FIRST_NAME":"Barbara", + "LAST_NAME":"Williams", + "CLASS_LEVEL":"Junior", + "ACADEMIC_FOCUS":"Computer Science", + "MAJOR":"Computer Science", + "PROBATION":"0", + "ADVISOR":"Jasmine Jones", + "STU_EMAIL":"williamsb@berea.edu", + "STU_CPO":"118", + "LAST_POSN":"TA", + "LAST_SUP_PIDM":"7" + }, { "ID":"B00730361", "PIDM":"1", @@ -104,6 +137,12 @@ "LAST_POSN":"Student Manager", "LAST_SUP_PIDM":"7" }, + {"ID": "B00811617", "legal_name": "Chris Georgiev", "isActive": True, "PIDM": "8", "FIRST_NAME": "Chris", "LAST_NAME": "Georgiev"}, + {"ID": "B00815474", "legal_name": "Julius Fritz", "isActive": True, "PIDM": "9", "FIRST_NAME": "Julius", "LAST_NAME": "Fritz"}, + {"ID": "B12345223", "legal_name": "Subaru Natsuki", "isActive": True, "PIDM": "10", "FIRST_NAME": "Subaru", "LAST_NAME": "Natsuki"}, + {"ID": "B12345003", "legal_name": "Hatsune Miku", "isActive": True, "PIDM": "11", "FIRST_NAME": "Hatsune", "LAST_NAME": "Miku"}, + {"ID": "B12345772", "legal_name": "Michael Jackson", "isActive": True, "PIDM": "12", "FIRST_NAME": "Michael", "LAST_NAME": "Jackson"}, + {"ID": "B12345756", "legal_name": "Genji Overwatch", "isActive": True, "PIDM": "13", "FIRST_NAME": "Genji", "LAST_NAME": "Overwatch"} ] tracyStudents = [ { @@ -411,6 +450,22 @@ "isSaasAdmin": None }, { + "student": "B00741361", + "supervisor": None, + "username": "schmitha", + "isLaborAdmin": None, + "isFinancialAidAdmin": None, + "isSaasAdmin": None + }, + { + "student": "B00732363", + "supervisor": None, + "username": "williamsb", + "isLaborAdmin": None, + "isFinancialAidAdmin": None, + "isSaasAdmin": None + }, + { "student": "B00730361", "supervisor": None, "username": "jamalie", @@ -567,6 +622,124 @@ "createdDate": f"2025-04-14", "status_id": "Pending" }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + "laborStatusFormID": 11, + "termCode_id": f"202500", + "studentName": "Antonia Schmith", + "studentSupervisee_id": "B00741361", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Student Programmer", + "POSN_CODE": "S61407", + "weeklyHours": 10, + "startDate": f"2026-04-01", + "endDate": f"2026-09-01", + "studentConfirmation": True + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 11, + "formID_id": "11", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 12, + "termCode_id": f"202500", + "studentName": "Barbara Williams", + "studentSupervisee_id": "B00732363", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Student Programmer", + "POSN_CODE": "S61407", + "weeklyHours": 10, + "startDate": f"2027-04-01", + "endDate": f"2029-09-01", + "studentConfirmation": True + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 12, + "formID_id": "12", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status": "Approved" + }]).on_conflict_replace().execute() + +LaborReleaseForm.insert([{ + "laborReleaseFormID": 10, + "conditionAtRelease": "unsatisfactory", + "releaseDate": f"2025-04-14", + "reasonForRelease": "Smoking Cigarettes in the Programmers' space." + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 13, + "formID_id": "12", + "historyType_id": "Labor Release Form", + "releaseForm": 10, + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 4, + "termCode_id": f"202500", + "studentName": "Elaleh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Labor Workers", + "POSN_CODE": "S61419", + "weeklyHours": 10, + "startDate": f"2027-04-01", + "endDate": "2027-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 4, + "formID_id": "4", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 5, + "termCode_id": f"202500", + "studentName": "Oluwagbayi Makinde", + "studentSupervisee_id": "B00791326", + "supervisor_id": "B12365892", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Labor Workers", + "POSN_CODE": "S61429", + "weeklyHours": 10, + "startDate": f"2025-04-01", + "endDate": "2029-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 5, + "formID_id": "5", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status": "Approved" + }]).on_conflict_replace().execute() LaborStatusForm.insert([{ "laborStatusFormID": 3, @@ -592,6 +765,23 @@ "createdDate": f"2025-04-14", "status_id": "Approved" }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + + "laborStatusFormID": 9, + "termCode_id": f"202500", + "studentName": "Genji Overwatch", + "studentSupervisee_id": "B12345756", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "overwtahc guy", + "POSN_CODE": "S61410", + "contractHours": 15, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + + }]).on_conflict_replace().execute() @@ -796,4 +986,325 @@ ] PositionHistory.insert_many(positionHistory).on_conflict_replace().execute() -print(" * position history added") \ No newline at end of file +print(" * position history added") + +############################ +# Allocation Dummy Data: +########################### +allocations = [ + { + "termCode": 202500, + "department": 3, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Downscaling due to decrease in student enrollment caused by current economic conditions", + "primary_10": 2, + "primary_12": 2, + "primary_15": 1, + "primary_20": 0, + "secondary_5": 1, + "secondary_10": 0, + "breakHours": 260, + }, + { + "termCode": 202500, + "department": 2, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Increase in student enrollment due to exodous from CS department", + "primary_10": 4, + "primary_12": 2, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 2, + "secondary_10": 0, + "breakHours": 750, + }, + { + "termCode": 202500, + "department": 1, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "We are hiring more students to help with the increased workload in the department", + "primary_10": 5, + "primary_12": 6, + "primary_15": 4, + "primary_20": 1, + "secondary_5": 7, + "secondary_10": 0, + "breakHours": 550, + }, + { + "termCode": 202500, + "department": 4, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Downscaling the number of students in the department due to budget cuts", + "primary_10": 4, + "primary_12": 5, + "primary_15": 0, + "primary_20": 0, + "secondary_5": 1, + "secondary_10": 0, + "breakHours": 300, + }, + { + "termCode": 202500, + "department": 5, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + + ] +Allocation.insert_many(allocations).on_conflict_replace().execute() + +print("Data insertion complete :)") +allocation =[ + { + "termCode":f"{2025}00", + "department": 3, + "isFinal": True, + "approvedOn": f"{2025}-06-30", + "approvedBy": "B12365892", + "justification": "We just want it for fun", + "primary_10": 2, + "primary_12": 3, + "primary_15": 1, + "primary_20": 6, + "secondary_5": 2, + "secondary_10": 0, + "breakHours": 500 + }, + { + "termCode":f"{2025}00", + "department": 2, + "isFinal": False, + "approvedOn": f"{2025}-06-20", + "approvedBy": "B00763721", + "justification": "We need it to lower the amount of allocations we have", + "primary_10": 1, + "primary_12": 2, + "primary_15": 5, + "primary_20": 2, + "secondary_5": 10, + "secondary_10": 0, + "breakHours": 1500 + } + ] +Allocation.insert_many(allocation).on_conflict_replace().execute() +print(" * allocation added") + + +############################# +# Position History +############################# + +positionHistory = [ + { + "positionTitle": "Student Programmer", + "positionCode": "S61407", + "status": "Active", + "wls": 1, + "revisionDate": f"2026-07-01", + "description": "", + "department": 1 + }, + { + "positionTitle": "Research Associate", + "positionCode": "S61408", + "status": "Active", + "wls": 2, + "revisionDate": f"2026-09-01", + "description": "", + "department": 1 + }, + { + "positionTitle": "Labor Workers", + "positionCode": "S61409", + "status": "Active", + "wls": 3, + "revisionDate": f"2026-07-01", + "description": "", + "department": 1 + }, + { + "positionTitle": "Teaching Associate", + "positionCode": "S61411", + "status": "Active", + "wls":3, + "revisionDate" : f"2026-01-01", + "description": "", + "department" : 1 + + }, + { + "positionTitle": "Teaching Associate", + "positionCode": "S61410", + "status": "Inactive", + "wls":2, + "revisionDate" : f"2026-01-01", + "description": "", + "department" : 3 + }, + { + "positionTitle": "Teaching Associate", + "positionCode": "S61410", + "status": "Active", + "wls":2, + "revisionDate" : f"2026-03-29", + "description": "", + "department" : 3 + }, + { + "positionTitle": "DUMMY POSITION", + "positionCode": "S12345", + "status": "Active", + "wls":3, + "revisionDate" : f"2026-01-23", + "description": "", + "department" : 1 + }, + { + "positionTitle": "Junior Data Analyst", + "positionCode": "S39568", + "status": "Active", + "wls":4, + "revisionDate" : f"2026-01-31", + "description": "", + "department" : 1 + }, + { + "positionTitle": "Student Manager", + "positionCode": "S74933", + "status": "Active", + "wls":5, + "revisionDate" : f"2026-04-01", + "description": "", + "department" : 1 + }, + { + "positionTitle": "IT Technician", + "positionCode": "S94932", + "status": "Active", + "wls":6, + "revisionDate" : f"2026-05-03", + "description": "", + "department" : 1 + }, + { + "positionTitle": "Human code generator", + "positionCode": "S22222", + "status": "Active", + "wls":1, + "revisionDate" : f"2026-05-03", + "description": "", + "department" : 1 + }, + { + "positionTitle": "Senior Software Engineer", + "positionCode": "S00000", + "status": "Active", + "wls":6, + "revisionDate" : f"2026-05-03", + "description": "", + "department" : 1 + } + + + +] +PositionHistory.insert_many(positionHistory).on_conflict_replace().execute() + +dummy_lsf = [ + { + "laborStatusFormID": 4, + "termCode_id": f"202500", + "studentName": "Chris Georgiev", + "studentSupervisee_id": "B00811617", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 4, + "POSN_TITLE": "guy who does stuff", + "POSN_CODE": "S61415", + "weeklyHours": 12, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }, + { + + "laborStatusFormID": 5, + "termCode_id": f"202500", + "studentName": "Julius Fritz", + "studentSupervisee_id": "B00815474", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 2, + "POSN_TITLE": "guy who sits in chair", + "POSN_CODE": "S61416", + "weeklyHours": 15, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }, + { + "laborStatusFormID": 6, + "termCode_id": f"202500", + "studentName": "Subaru Natsuki", + "studentSupervisee_id": "B12345223", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Aura Monster", + "POSN_CODE": "S61417", + "weeklyHours": 20, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }, + { + "laborStatusFormID": 7, + "termCode_id": f"202500", + "studentName": "Hatsune Miku", + "studentSupervisee_id": "B12345003", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 6, + "POSN_TITLE": "Singer", + "POSN_CODE": "S61409", + "weeklyHours": 20, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + + }, + { + "laborStatusFormID": 8, + "termCode_id": f"202500", + "studentName": "Michael Jackson", + "studentSupervisee_id": "B12345772", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 6, + "POSN_TITLE": "Famous singer", + "POSN_CODE": "S61410", + "weeklyHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + } +] +LaborStatusForm.insert_many(dummy_lsf).on_conflict_replace().execute() \ No newline at end of file From 56d5a2971ea6a3d93be4341c5e1096ecfac94091 Mon Sep 17 00:00:00 2001 From: conwelld Date: Tue, 21 Jul 2026 11:28:11 -0400 Subject: [PATCH 002/128] fixed some pr comments --- app/controllers/main_routes/main_routes.py | 30 +++------------------- 1 file changed, 4 insertions(+), 26 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index de9a189c3..45a791318 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -101,9 +101,9 @@ def departmentPortal(org=None,account=None): else: supervisors.append(supervisorDisplay) - totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == 202500).scalar() - usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(True))]) - studentHours = {} + totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == 202500).scalar() #grabs the total positions that can be fufilled by contracts + usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(True))]) #grabs the total amount of contracts fufilled from totalPositions + studentHours = {} #Group each active LSF (job type + weekly hours) by student, so we can show all of a student's jobs together for form in LaborStatusForm.select().where(LaborStatusForm.department == dept,LaborStatusForm.termCode_id == 202500,LaborStatusForm.contractHours.is_null(True) ): studentSuperviseeId = form.studentSupervisee_id @@ -128,16 +128,7 @@ def count_workers(job_type, hours_bucket): } break_allocation = LaborStatusForm.select(LaborStatusForm.contractHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(False)) sumBreak = sum(form.contractHours or 0 for form in break_allocation) - positions = list(PositionHistory.select().where(PositionHistory.department == dept, PositionHistory.status == "Active").order_by(PositionHistory.positionTitle.asc())) if dept else [] - positionsList = [] - posUrl = [] - if not positions: - positionsList = ["No active positions in this department"] - else: - for i in positions: - positionsList.append(i.positionTitle + ": " + "(WLS " + str(i.wls) + ")") - posUrl.append(str(i.positionCode)) - + return render_template('main/departmentPortal.html', departments = departments, department = dept, @@ -154,20 +145,7 @@ def count_workers(job_type, hours_bucket): laborCoordinators=laborCoordinators, currentUser=g.currentUser ) -@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 From faf19578132b3b969d583813ac8929a1ced636cf Mon Sep 17 00:00:00 2001 From: conwelld Date: Tue, 21 Jul 2026 11:34:27 -0400 Subject: [PATCH 003/128] fixed some pr comments for css styling --- app/controllers/main_routes/main_routes.py | 24 ++++++++++++++-------- app/static/css/departmentPortal.css | 4 ++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 45a791318..15ef9faf7 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -101,9 +101,9 @@ def departmentPortal(org=None,account=None): else: supervisors.append(supervisorDisplay) - totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == 202500).scalar() #grabs the total positions that can be fufilled by contracts - usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(True))]) #grabs the total amount of contracts fufilled from totalPositions - studentHours = {} #Group each active LSF (job type + weekly hours) by student, so we can show all of a student's jobs together + totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == 202500).scalar() # Total allocated positions for this department/term, summed across all hour buckets + usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(True))]) # Count how many of those positions are currently filled (excludes contract/break-hour forms) + studentHours = {} # Group each active LSF (job type + weekly hours) by student, so we can show all of a student's jobs together for form in LaborStatusForm.select().where(LaborStatusForm.department == dept,LaborStatusForm.termCode_id == 202500,LaborStatusForm.contractHours.is_null(True) ): studentSuperviseeId = form.studentSupervisee_id @@ -139,13 +139,21 @@ def count_workers(job_type, hours_bucket): studentHours = studentHours, usedPositions = usedPositions, break_hours = sumBreak, - positions = positionsList, - posUrl = posUrl, - supervisors = supervisors, - laborCoordinators=laborCoordinators, - currentUser=g.currentUser ) +@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 diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index 6cccf556a..9937e0a99 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -42,11 +42,11 @@ } .primary-chart { font-size: 1.2em; - padding-left: 15%; + padding-left: 7%; } .secondary-chart { font-size: 1.2em; - padding-right: 15%; + padding-right: 7%; } .bi-people-fill { /* Bootstrap Icon for Members Card */ From 120426913fcf4e951b3b545c3fcd05c86da32522 Mon Sep 17 00:00:00 2001 From: conwelld Date: Tue, 21 Jul 2026 11:43:35 -0400 Subject: [PATCH 004/128] fixed useless code --- app/controllers/main_routes/main_routes.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 15ef9faf7..5d8d60b52 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -103,18 +103,7 @@ def departmentPortal(org=None,account=None): totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == 202500).scalar() # Total allocated positions for this department/term, summed across all hour buckets usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(True))]) # Count how many of those positions are currently filled (excludes contract/break-hour forms) - studentHours = {} # Group each active LSF (job type + weekly hours) by student, so we can show all of a student's jobs together - for form in LaborStatusForm.select().where(LaborStatusForm.department == dept,LaborStatusForm.termCode_id == 202500,LaborStatusForm.contractHours.is_null(True) - ): - studentSuperviseeId = form.studentSupervisee_id - if studentSuperviseeId not in studentHours: - studentHours[studentSuperviseeId] = [] - studentHours[studentSuperviseeId].append({ - "jobType": form.jobType, - "weeklyHours": form.weeklyHours - }) - - + def count_workers(job_type, hours_bucket): return LaborStatusForm.select().where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.jobType == job_type, LaborStatusForm.weeklyHours == hours_bucket, LaborStatusForm.contractHours.is_null(True)).count() @@ -136,7 +125,6 @@ def count_workers(job_type, hours_bucket): total_allocation = totalPositions, used_allocation = usedAllocation, term = g.openTerm.termName, - studentHours = studentHours, usedPositions = usedPositions, break_hours = sumBreak, ) From 0f166c268ab2d36f1bf369a5b11ecda37458d8e1 Mon Sep 17 00:00:00 2001 From: conwelld Date: Tue, 21 Jul 2026 12:01:04 -0400 Subject: [PATCH 005/128] fixed department code --- app/controllers/main_routes/main_routes.py | 27 ---------------------- 1 file changed, 27 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 5d8d60b52..340be409f 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -74,33 +74,6 @@ def departmentPortal(org=None,account=None): except DoesNotExist: allocation = None - supervisorDepartments = (SupervisorDepartment.select().join(Supervisor).where(SupervisorDepartment.department == dept) - .order_by(fn.COALESCE(Supervisor.preferred_name, Supervisor.legal_name, Supervisor.LAST_NAME).asc())) - - laborCoordinators = [] - supervisors = [] - - for supervisorDepartment in supervisorDepartments: - supervisor = supervisorDepartment.supervisor - - if supervisor is None: - continue - - firstName = supervisor.preferred_name or supervisor.legal_name or "" - lastName = supervisor.LAST_NAME or "" - - supervisorName = f"{firstName} {lastName}".strip() - - supervisorDisplay = { - "name": supervisorName, - "email": supervisor.EMAIL - } - - if supervisorDepartment.isCoordinator: - laborCoordinators.append(supervisorDisplay) - else: - supervisors.append(supervisorDisplay) - totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == 202500).scalar() # Total allocated positions for this department/term, summed across all hour buckets usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(True))]) # Count how many of those positions are currently filled (excludes contract/break-hour forms) From 6f5850a121c0c2bbea42505c12abd5e8630aadbb Mon Sep 17 00:00:00 2001 From: rukwashai <{rukwashai}@berea.edu> Date: Wed, 22 Jul 2026 10:14:03 -0400 Subject: [PATCH 006/128] We have change the hard coded part with variable able to be flexible everything touching the 202500 --- app/controllers/main_routes/main_routes.py | 15 +++++++++------ app/templates/main/departmentPortal.html | 4 ++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 340be409f..e457970e2 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -55,6 +55,9 @@ def supervisorPortal(): @main_bp.route('/department/', methods=['GET']) @main_bp.route('/department//', methods=['GET']) def departmentPortal(org=None,account=None): + open_term = g.openTerm + term_code = open_term.termCode + if org and account: try: dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) @@ -70,15 +73,15 @@ def departmentPortal(org=None,account=None): else: departments = list(getDepartmentsForSupervisor(g.currentUser).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) try: - allocation = Allocation.select(Allocation, Term).join(Term).where(Allocation.department == dept, Allocation.termCode == 202500).get() + allocation = Allocation.select(Allocation, Term).join(Term).where(Allocation.department == dept, Allocation.termCode == term_code).get() except DoesNotExist: allocation = None - totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == 202500).scalar() # Total allocated positions for this department/term, summed across all hour buckets - usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(True))]) # Count how many of those positions are currently filled (excludes contract/break-hour forms) + totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == term_code).scalar() # Total allocated positions for this department/term, summed across all hour buckets + usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == term_code, LaborStatusForm.contractHours.is_null(True))]) # Count how many of those positions are currently filled (excludes contract/break-hour forms) def count_workers(job_type, hours_bucket): - return LaborStatusForm.select().where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.jobType == job_type, LaborStatusForm.weeklyHours == hours_bucket, LaborStatusForm.contractHours.is_null(True)).count() + return LaborStatusForm.select().where(LaborStatusForm.department == dept, LaborStatusForm.termCode == term_code, LaborStatusForm.jobType == job_type, LaborStatusForm.weeklyHours == hours_bucket, LaborStatusForm.contractHours.is_null(True)).count() usedPositions = { "used_10": count_workers("Primary", "10"), @@ -88,7 +91,7 @@ def count_workers(job_type, hours_bucket): "used_5_sec": count_workers("Secondary", "5"), "used_10_sec": count_workers("Secondary", "10"), } - break_allocation = LaborStatusForm.select(LaborStatusForm.contractHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == 202500, LaborStatusForm.contractHours.is_null(False)) + break_allocation = LaborStatusForm.select(LaborStatusForm.contractHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == term_code, LaborStatusForm.contractHours.is_null(False)) sumBreak = sum(form.contractHours or 0 for form in break_allocation) return render_template('main/departmentPortal.html', @@ -97,7 +100,7 @@ def count_workers(job_type, hours_bucket): allocation = allocation, total_allocation = totalPositions, used_allocation = usedAllocation, - term = g.openTerm.termName, + term = open_term, usedPositions = usedPositions, break_hours = sumBreak, ) diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index b51354f0e..6d17feae5 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -40,7 +40,7 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e

Allocations

-

AY 2025-2026

{{used_allocation}}/{{total_allocation or 0}} Positions

+

{{ term.termName }}

{{used_allocation}}/{{total_allocation or 0}} Positions

    @@ -67,4 +67,4 @@

    Break Hours {% endif %} -{% endblock %} \ No newline at end of file +{% endblock %} From 813f398c440e34acf15c96f79cc3918cc2e671f2 Mon Sep 17 00:00:00 2001 From: rukwashai <{rukwashai}@berea.edu> Date: Wed, 22 Jul 2026 13:52:37 -0400 Subject: [PATCH 007/128] Temporary allocation logic will be replaced by the official shared service. See #657 but beyond that we created allo files --- app/controllers/main_routes/main_routes.py | 28 +++------ app/logic/allocation_utilization.py | 71 ++++++++++++++++++++++ database/reset_database.sh | 2 - 3 files changed, 78 insertions(+), 23 deletions(-) create mode 100644 app/logic/allocation_utilization.py diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index e457970e2..ca45fa7c7 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -1,5 +1,5 @@ from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify -from peewee import JOIN, DoesNotExist, fn +from peewee import JOIN, DoesNotExist from flask_bootstrap import forms from functools import reduce import operator @@ -17,6 +17,7 @@ from app.login_manager import require_login, logout from app.logic.getTableData import getDatatableData from app.logic.banner import Banner +from app.logic.allocation_utilization import get_department_allocation_summary from app.models.allocation import Allocation from app.logic.tracy import Tracy from app.models.positionHistory import PositionHistory @@ -77,32 +78,17 @@ def departmentPortal(org=None,account=None): except DoesNotExist: allocation = None - totalPositions = Allocation.select(fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) + fn.SUM(Allocation.primary_15) + fn.SUM(Allocation.primary_20) + fn.sum(Allocation.secondary_5) + fn.SUM(Allocation.secondary_10)).where(Allocation.department == dept, Allocation.termCode == term_code).scalar() # Total allocated positions for this department/term, summed across all hour buckets - usedAllocation = len([hours for hours in LaborStatusForm.select(LaborStatusForm.weeklyHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == term_code, LaborStatusForm.contractHours.is_null(True))]) # Count how many of those positions are currently filled (excludes contract/break-hour forms) - - def count_workers(job_type, hours_bucket): - return LaborStatusForm.select().where(LaborStatusForm.department == dept, LaborStatusForm.termCode == term_code, LaborStatusForm.jobType == job_type, LaborStatusForm.weeklyHours == hours_bucket, LaborStatusForm.contractHours.is_null(True)).count() - - usedPositions = { - "used_10": count_workers("Primary", "10"), - "used_12": count_workers("Primary", "12"), - "used_15": count_workers("Primary", "15"), - "used_20": count_workers("Primary", "20"), - "used_5_sec": count_workers("Secondary", "5"), - "used_10_sec": count_workers("Secondary", "10"), -} - break_allocation = LaborStatusForm.select(LaborStatusForm.contractHours).where(LaborStatusForm.department == dept, LaborStatusForm.termCode == term_code, LaborStatusForm.contractHours.is_null(False)) - sumBreak = sum(form.contractHours or 0 for form in break_allocation) + allocation_summary = get_department_allocation_summary(dept, term_code) return render_template('main/departmentPortal.html', departments = departments, department = dept, allocation = allocation, - total_allocation = totalPositions, - used_allocation = usedAllocation, + total_allocation = allocation_summary["total_positions"], + used_allocation = allocation_summary["used_allocation"], term = open_term, - usedPositions = usedPositions, - break_hours = sumBreak, + usedPositions = allocation_summary["used_positions"], + break_hours = allocation_summary["break_hours"], ) @main_bp.route('/department///managepositions', methods=['GET']) def managePositions(org, account): diff --git a/app/logic/allocation_utilization.py b/app/logic/allocation_utilization.py new file mode 100644 index 000000000..77df38e73 --- /dev/null +++ b/app/logic/allocation_utilization.py @@ -0,0 +1,71 @@ +from peewee import fn + +from app.models.allocation import Allocation +from app.models.laborStatusForm import LaborStatusForm + + +def get_department_allocation_summary(department, term_code): + """Return allocation-utilization values for one department and term.""" + total_positions = ( + Allocation.select( + fn.SUM(Allocation.primary_10) + + fn.SUM(Allocation.primary_12) + + fn.SUM(Allocation.primary_15) + + fn.SUM(Allocation.primary_20) + + fn.SUM(Allocation.secondary_5) + + fn.SUM(Allocation.secondary_10) + ) + .where( + Allocation.department == department, + Allocation.termCode == term_code, + ) + .scalar() + ) + + used_allocation = ( + LaborStatusForm.select() + .where( + LaborStatusForm.department == department, + LaborStatusForm.termCode == term_code, + LaborStatusForm.contractHours.is_null(True), + ) + .count() + ) + + def count_workers(job_type, hours_bucket): + return ( + LaborStatusForm.select() + .where( + LaborStatusForm.department == department, + LaborStatusForm.termCode == term_code, + LaborStatusForm.jobType == job_type, + LaborStatusForm.weeklyHours == hours_bucket, + LaborStatusForm.contractHours.is_null(True), + ) + .count() + ) + + used_positions = { + "used_10": count_workers("Primary", 10), + "used_12": count_workers("Primary", 12), + "used_15": count_workers("Primary", 15), + "used_20": count_workers("Primary", 20), + "used_5_sec": count_workers("Secondary", 5), + "used_10_sec": count_workers("Secondary", 10), + } + + break_hours = sum( + form.contractHours or 0 + for form in LaborStatusForm.select(LaborStatusForm.contractHours).where( + LaborStatusForm.department == department, + LaborStatusForm.termCode == term_code, + LaborStatusForm.contractHours.is_null(False), + ) + ) + + return { + "total_positions": total_positions or 0, + "used_allocation": used_allocation, + "used_positions": used_positions, + "break_hours": break_hours, + } diff --git a/database/reset_database.sh b/database/reset_database.sh index 82f6cff53..ba87204db 100755 --- a/database/reset_database.sh +++ b/database/reset_database.sh @@ -29,8 +29,6 @@ echo "Recreating databases and users" mysql -u root -proot --execute="CREATE DATABASE IF NOT EXISTS \`lsf\`; CREATE USER IF NOT EXISTS 'lsf_user'@'%' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON *.* TO 'lsf_user'@'%';" mysql -u root -proot --execute="CREATE DATABASE IF NOT EXISTS \`UTE\`; CREATE USER IF NOT EXISTS 'tracy_user'@'%' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON *.* TO 'tracy_user'@'%';" -cd database - rm -rf lsf_migrations rm -rf tracy_migrations rm -rf migrations.json From 7584ffa12f6bfccb3a4d2fb762c0d6c527ed453e Mon Sep 17 00:00:00 2001 From: rukwashai Date: Mon, 27 Jul 2026 11:59:42 -0400 Subject: [PATCH 008/128] fix the review comment from Minran, all of them --- app/controllers/main_routes/main_routes.py | 44 ++++++++----------- ...tilization.py => allocationUtilization.py} | 32 ++++++++++++-- app/templates/main/departmentPortal.html | 4 +- database/base_data.py | 5 +++ database/demo_data.py | 5 +++ database/migrate_db.sh | 2 + database/migrate_db_tracy.sh | 2 + 7 files changed, 63 insertions(+), 31 deletions(-) rename app/logic/{allocation_utilization.py => allocationUtilization.py} (68%) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index ca45fa7c7..60ce9b65e 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -1,6 +1,5 @@ from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify from peewee import JOIN, DoesNotExist -from flask_bootstrap import forms from functools import reduce import operator from app.models.department import Department @@ -17,9 +16,8 @@ from app.login_manager import require_login, logout from app.logic.getTableData import getDatatableData from app.logic.banner import Banner -from app.logic.allocation_utilization import get_department_allocation_summary +from app.logic.allocationUtilization import getDepartmentAllocationSummary from app.models.allocation import Allocation -from app.logic.tracy import Tracy from app.models.positionHistory import PositionHistory @main_bp.route('/logout', methods=['GET']) @@ -56,37 +54,33 @@ def supervisorPortal(): @main_bp.route('/department/', methods=['GET']) @main_bp.route('/department//', methods=['GET']) def departmentPortal(org=None,account=None): - open_term = g.openTerm - term_code = open_term.termCode - - if org and account: - try: - dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) - except (NameError, DoesNotExist): - dept = None - else: + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): 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())) - try: - allocation = Allocation.select(Allocation, Term).join(Term).where(Allocation.department == dept, Allocation.termCode == term_code).get() - except DoesNotExist: + + allocation_summary = getDepartmentAllocationSummary(dept) + recentTerm = allocation_summary["term"] + + if recentTerm: + try: + allocation = Allocation.select(Allocation, Term).join(Term).where(Allocation.department == dept, Allocation.termCode == recentTerm.termCode).get() + except DoesNotExist: + allocation = None + else: allocation = None - - allocation_summary = get_department_allocation_summary(dept, term_code) - - return render_template('main/departmentPortal.html', + + return render_template('main/departmentPortal.html', departments = departments, department = dept, allocation = allocation, - total_allocation = allocation_summary["total_positions"], - used_allocation = allocation_summary["used_allocation"], - term = open_term, + allocated = allocation_summary["allocated"], + used = allocation_summary["used"], + term = recentTerm, usedPositions = allocation_summary["used_positions"], break_hours = allocation_summary["break_hours"], ) diff --git a/app/logic/allocation_utilization.py b/app/logic/allocationUtilization.py similarity index 68% rename from app/logic/allocation_utilization.py rename to app/logic/allocationUtilization.py index 77df38e73..5fc14abfc 100644 --- a/app/logic/allocation_utilization.py +++ b/app/logic/allocationUtilization.py @@ -2,10 +2,33 @@ from app.models.allocation import Allocation from app.models.laborStatusForm import LaborStatusForm +from app.models.term import Term -def get_department_allocation_summary(department, term_code): - """Return allocation-utilization values for one department and term.""" +def getDepartmentAllocationSummary(department): + """Return allocation-utilization values for a department's most recent term.""" + departmentAllocations = list( + Allocation.select(Allocation, Term).join(Term).where(Allocation.department == department) + ) + if not departmentAllocations: + return { + "term": None, + "allocated": 0, + "used": 0, + "used_positions": { + "used_10": 0, + "used_12": 0, + "used_15": 0, + "used_20": 0, + "used_5_sec": 0, + "used_10_sec": 0, + }, + "break_hours": 0, + } + + recentTerm = Term.order_by_term([a.termCode for a in departmentAllocations], reverse=True)[0] + term_code = recentTerm.termCode + total_positions = ( Allocation.select( fn.SUM(Allocation.primary_10) @@ -64,8 +87,9 @@ def count_workers(job_type, hours_bucket): ) return { - "total_positions": total_positions or 0, - "used_allocation": used_allocation, + "term": recentTerm, + "allocated": total_positions or 0, + "used": used_allocation, "used_positions": used_positions, "break_hours": break_hours, } diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 6d17feae5..0fa624887 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -37,10 +37,10 @@

    {% if department %} {{department.DEPT_NAME}} Portal {% e

-

Allocations

+

Allocations

-

{{ term.termName }}

{{used_allocation}}/{{total_allocation or 0}} Positions

+

AY 2025-2026

{{used}}/{{allocated or 0}} Positions

    diff --git a/database/base_data.py b/database/base_data.py index c081fb752..f42d57ce9 100644 --- a/database/base_data.py +++ b/database/base_data.py @@ -1,3 +1,8 @@ +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) + from app.models.status import Status from app.models.historyType import HistoryType from app.models.emailTemplate import EmailTemplate diff --git a/database/demo_data.py b/database/demo_data.py index 3bce122a8..d231963ab 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -2,6 +2,11 @@ Chech phpmyadmin to see if your changes are reflected This file will need to be changed if the format of models changes (new fields, dropping fields, renaming...)''' +import os +import sys + +sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) + from app import app from app.models.Tracy import db diff --git a/database/migrate_db.sh b/database/migrate_db.sh index dea226d8e..c693c7086 100755 --- a/database/migrate_db.sh +++ b/database/migrate_db.sh @@ -1,4 +1,6 @@ +export PYTHONPATH="$(cd "$(dirname "$0")/.." && pwd):$PYTHONPATH" + pem init # See: https://stackoverflow.com/questions/394230/how-to-detect-the-os-from-a-bash-script/18434831 diff --git a/database/migrate_db_tracy.sh b/database/migrate_db_tracy.sh index 0b5466dc8..fabca9d30 100755 --- a/database/migrate_db_tracy.sh +++ b/database/migrate_db_tracy.sh @@ -1,4 +1,6 @@ +export FLASK_APP="$(cd "$(dirname "$0")/.." && pwd)/app.py" + DB_DIR=tracy_migrations flask db init -d $DB_DIR From 5cfa98c885d9e8750a23b3ec13615e5d0b99fea6 Mon Sep 17 00:00:00 2001 From: lolongaj Date: Tue, 28 Jul 2026 16:30:22 -0400 Subject: [PATCH 009/128] added revisedBy to positionHistory.py and created demo data for it --- app/models/positionHistory.py | 1 + database/demo_data.py | 14 +++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/models/positionHistory.py b/app/models/positionHistory.py index 9a248aacb..72dba0888 100644 --- a/app/models/positionHistory.py +++ b/app/models/positionHistory.py @@ -8,6 +8,7 @@ class PositionHistory(baseModel): status = CharField() wls = IntegerField() revisionDate = DateField() + revisedBy = CharField() description = TextField(default=None) class Meta: diff --git a/database/demo_data.py b/database/demo_data.py index 1311a7487..c010fe863 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -757,6 +757,7 @@ "status": "Active", "wls": 1, "revisionDate": f"2026-07-01", + "revisedBy": "John Doe", "description": "", "department": 1 }, @@ -766,6 +767,7 @@ "status": "Active", "wls": 2, "revisionDate": f"2026-09-01", + "revisedBy": "Jane Smith", "description": "", "department": 1 }, @@ -775,6 +777,7 @@ "status": "Active", "wls": 3, "revisionDate": f"2026-07-01", + "revisedBy": "Alice Johnson", "description": "", "department": 1 }, @@ -784,6 +787,7 @@ "status": "Active", "wls":3, "revisionDate" : f"2026-01-01", + "revisedBy": "Bob Wilson", "description": "", "department" : 1 @@ -794,6 +798,7 @@ "status": "Inactive", "wls":2, "revisionDate" : f"2026-01-01", + "revisedBy": "Charlie Brown", "description": "", "department" : 3 }, @@ -803,6 +808,7 @@ "status": "Active", "wls":2, "revisionDate" : f"2026-03-29", + "revisedBy": "David Davis", "description": "", "department" : 3 }, @@ -812,6 +818,7 @@ "status": "Active", "wls":3, "revisionDate" : f"2026-01-23", + "revisedBy": "Eve Thompson", "description": "", "department" : 1 }, @@ -821,6 +828,7 @@ "status": "Active", "wls":4, "revisionDate" : f"2026-01-31", + "revisedBy": "Frank Miller", "description": "", "department" : 1 }, @@ -830,6 +838,7 @@ "status": "Active", "wls":5, "revisionDate" : f"2026-04-01", + "revisedBy": "Grace Lee", "description": "", "department" : 1 }, @@ -839,6 +848,7 @@ "status": "Active", "wls":6, "revisionDate" : f"2026-05-03", + "revisedBy": "Henry Adams", "description": "", "department" : 1 }, @@ -848,6 +858,7 @@ "status": "Active", "wls":1, "revisionDate" : f"2026-05-03", + "revisedBy": "Ivy Johnson", "description": "", "department" : 1 }, @@ -857,7 +868,8 @@ "status": "Active", "wls":6, "revisionDate" : f"2026-05-03", - "description": "", + "revisedBy": "Jack Smith", + "description": "This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work- Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.", "department" : 1 } From fb4924fa57a80c5ab163387ded5a201166e8192d Mon Sep 17 00:00:00 2001 From: lolongaj Date: Tue, 28 Jul 2026 16:32:53 -0400 Subject: [PATCH 010/128] added the routes for the individual position page and created the html and css for the page --- app/controllers/main_routes/main_routes.py | 25 +++++++++ app/static/css/individualPositions.css | 3 ++ app/templates/main/individualPositions.html | 56 +++++++++++++++++++++ 3 files changed, 84 insertions(+) create mode 100644 app/static/css/individualPositions.css create mode 100644 app/templates/main/individualPositions.html diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 88651a11f..d93d92e20 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -73,6 +73,31 @@ def departmentPortal(org=None,account=None): positions = positionsList, posURL = posURL) +@main_bp.route('/department///positions/', methods=['GET']) +def individualPosition(org, account, positionCode): + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + return render_template('errors/404.html'), 404 + + position = PositionHistory.get_or_none( + PositionHistory.department == dept, + PositionHistory.positionCode == positionCode, + PositionHistory.status == "Active" + ) + + if not position: + return render_template('errors/404.html'), 404 + + revision_author = position.revisedBy if getattr(position, 'revisedBy', None) else None + + return render_template( + 'main/individualPositions.html', + department=dept, + position=position, + revision_author=revision_author + ) + @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) def addUserToDept(): userDeptData = request.form diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css new file mode 100644 index 000000000..d14ee4d14 --- /dev/null +++ b/app/static/css/individualPositions.css @@ -0,0 +1,3 @@ +h3 { + font-weight: 700; +} \ No newline at end of file diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html new file mode 100644 index 000000000..20db3cc36 --- /dev/null +++ b/app/templates/main/individualPositions.html @@ -0,0 +1,56 @@ +{% extends "base.html" %} + +{% block scripts %} +{{super()}} + + +{% endblock %} + +{% block app_content %} + +
    +
    +
    +
    +

    {{ department.DEPT_NAME }}

    +
    + +
    +
    + +
    Position Title
    +
    {{ position.positionTitle }}
    + +
    Position Code
    +
    {{ position.positionCode }}
    + +
    WLS Level
    +
    {{ position.wls }}
    + +
    Status
    +
    {{ position.status }}
    + +
    Last Revision Date
    +
    {{ position.revisionDate }}
    + +
    Revised By
    +
    {{ revision_author or "Unknown" }}
    +
    +
    + +
    +

    Description

    + {% if position.description %} +

    {{ position.description }}

    + {% else %} +

    No description available.

    + {% endif %} +
    + + +
    +
    +
    +{% endblock %} From bc35aa4f8052ac320b0b94ff249455a091f59761 Mon Sep 17 00:00:00 2001 From: lolongaj Date: Tue, 28 Jul 2026 17:14:45 -0400 Subject: [PATCH 011/128] added the download position description button --- app/controllers/main_routes/main_routes.py | 2 +- app/static/css/individualPositions.css | 53 ++++++++++++++++++++- app/templates/main/individualPositions.html | 24 ++++++---- 3 files changed, 67 insertions(+), 12 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index d93d92e20..600fb64bd 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -1,4 +1,4 @@ -from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify +from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify, make_response from peewee import JOIN, DoesNotExist from functools import reduce import operator diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index d14ee4d14..93e2e7772 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -1,3 +1,54 @@ h3 { font-weight: 700; -} \ No newline at end of file +} + +/* Individual Positions page styles */ +/* Header */ +.container .row .col-12 .mb-4 h1, +.individual-position-header { + text-align: center; + font-weight: 700; + margin-bottom: 1rem; +} + +/* Metadata list (dt / dd spacing) */ +.container .mb-5 dl.row dt { + font-weight: 600; + color: #333; +} +.container .mb-5 dl.row dd { + margin-bottom: 0.75rem; + color: #444; +} + +/* Description section card */ +.container section.mb-5 { + background: #fff; + border: 1px solid #e6e6e6; + padding: 1.25rem; + border-radius: 6px; + box-shadow: 0 1px 2px rgba(0,0,0,0.03); +} +.container section.mb-5 h2 { + margin-top: 0; + margin-bottom: 0.75rem; + font-size: 1.25rem; +} + +/* Small screens: stack meta labels and values */ +@media (max-width: 768px) { + .container .mb-5 dl.row dt, + .container .mb-5 dl.row dd { + display: block; + width: 100%; + } + .container section.mb-5 { + padding: 1rem; + } +} + +/* Utility float class used across the app */ +.floatright { + float: right; + margin-left: 0.5rem; +} diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index 20db3cc36..b00bd27de 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -2,7 +2,7 @@ {% block scripts %} {{super()}} - + {% endblock %} @@ -18,28 +18,27 @@

    {{ department.DEPT_NAME }}

    -
    Position Title
    +
    Position Title:
    {{ position.positionTitle }}
    -
    Position Code
    +
    Position Code:
    {{ position.positionCode }}
    -
    WLS Level
    +
    WLS Level:
    {{ position.wls }}
    -
    Status
    +
    Status:
    {{ position.status }}
    -
    Last Revision Date
    +
    Last Revision Date:
    {{ position.revisionDate }}
    Revised By
    {{ revision_author or "Unknown" }}
    - +

    Description

    -

    Description

    {% if position.description %}

    {{ position.description }}

    {% else %} @@ -47,8 +46,13 @@

    Description

    {% endif %}
    -
From 64c729b47fe28c3249f7f614f0fad8fe26b01e44 Mon Sep 17 00:00:00 2001 From: lolongaj Date: Wed, 29 Jul 2026 11:34:17 -0400 Subject: [PATCH 012/128] resizig html texts --- app/static/css/individualPositions.css | 34 ++++++++------------- app/templates/main/individualPositions.html | 15 ++++----- 2 files changed, 21 insertions(+), 28 deletions(-) diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index 93e2e7772..bdddb9954 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -4,51 +4,43 @@ h3 { /* Individual Positions page styles */ /* Header */ -.container .row .col-12 .mb-4 h1, -.individual-position-header { +.mb-4 h1, +.department-header { text-align: center; font-weight: 700; - margin-bottom: 1rem; + margin-bottom: 5rem; } /* Metadata list (dt / dd spacing) */ -.container .mb-5 dl.row dt { +.mb-5 dl.row dt { font-weight: 600; color: #333; + text-align: left; + font-size: 2rem; } -.container .mb-5 dl.row dd { +.mb-5 dl.row dd { margin-bottom: 0.75rem; color: #444; + text-align: left; + font-size: 2rem; + } /* Description section card */ -.container section.mb-5 { +.container-fluid section.mb-5{ background: #fff; border: 1px solid #e6e6e6; padding: 1.25rem; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.03); -} -.container section.mb-5 h2 { + text-align: left; margin-top: 0; margin-bottom: 0.75rem; font-size: 1.25rem; } -/* Small screens: stack meta labels and values */ -@media (max-width: 768px) { - .container .mb-5 dl.row dt, - .container .mb-5 dl.row dd { - display: block; - width: 100%; - } - .container section.mb-5 { - padding: 1rem; - } -} - /* Utility float class used across the app */ .floatright { float: right; margin-left: 0.5rem; -} +} \ No newline at end of file diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index b00bd27de..d068612c1 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -8,12 +8,13 @@ {% block app_content %} -
-
-
-
-

{{ department.DEPT_NAME }}

-
+
+

{{ department.DEPT_NAME }}

+
+ +
+
+
@@ -33,7 +34,7 @@

{{ department.DEPT_NAME }}

Last Revision Date:
{{ position.revisionDate }}
-
Revised By
+
Revised By:
{{ revision_author or "Unknown" }}
From 30030a2f993ca5a1e822fa73b6064435dd886a5e Mon Sep 17 00:00:00 2001 From: ACBerea Date: Wed, 29 Jul 2026 11:41:17 -0400 Subject: [PATCH 013/128] Fixed alignment issue with buttons caused by bootstrap version descrepencies. --- app/templates/main/individualPositions.html | 24 ++++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index d068612c1..72f3cb09f 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -47,14 +47,22 @@

Description

{% endif %} - +
From 20b424cc04434031b48b3a03ea4b9c72e83a6fc9 Mon Sep 17 00:00:00 2001 From: rukwashai Date: Wed, 29 Jul 2026 14:42:20 -0400 Subject: [PATCH 014/128] Fix import after allocationUtilization rename to getAllocation, add integration tests --- app/controllers/main_routes/main_routes.py | 7 +- ...ocationUtilization.py => getAllocation.py} | 0 tests/code/test_getAllocation.py | 208 ++++++++++++++++++ 3 files changed, 212 insertions(+), 3 deletions(-) rename app/logic/{allocationUtilization.py => getAllocation.py} (100%) create mode 100644 tests/code/test_getAllocation.py diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 40f53aa14..ae9a4d49d 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -9,6 +9,8 @@ from app.models.laborStatusForm import LaborStatusForm from app.models.formHistory import FormHistory from app.models.term import Term +from app.models.allocation import Allocation +from app.models.positionHistory import PositionHistory 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,9 +18,8 @@ from app.login_manager import require_login, logout from app.logic.getTableData import getDatatableData from app.logic.banner import Banner -from app.logic.allocationUtilization import getDepartmentAllocationSummary -from app.models.allocation import Allocation -from app.models.positionHistory import PositionHistory +from app.logic.getAllocation import getDepartmentAllocationSummary + from app.logic.getPositions import getActivePositions @main_bp.route('/logout', methods=['GET']) diff --git a/app/logic/allocationUtilization.py b/app/logic/getAllocation.py similarity index 100% rename from app/logic/allocationUtilization.py rename to app/logic/getAllocation.py diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py new file mode 100644 index 000000000..fb8c6d51a --- /dev/null +++ b/tests/code/test_getAllocation.py @@ -0,0 +1,208 @@ +import pytest +from app.models import mainDB +from app.models.department import Department +from app.models.term import Term +from app.models.allocation import Allocation +from app.models.laborStatusForm import LaborStatusForm +from app.models.student import Student +from app.models.supervisor import Supervisor +from app.logic.getAllocation import getDepartmentAllocationSummary + + +@pytest.mark.integration +def test_getDepartmentAllocationSummary_no_allocation(): + """ + Test that a department with no Allocation rows gets a zeroed-out summary + with term=None, instead of an error. + """ + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6750", ORG="2120", isActive=True) + + summary = getDepartmentAllocationSummary(dept) + + assert summary["term"] is None + assert summary["allocated"] == 0 + assert summary["used"] == 0 + assert summary["break_hours"] == 0 + assert summary["used_positions"] == { + "used_10": 0, + "used_12": 0, + "used_15": 0, + "used_20": 0, + "used_5_sec": 0, + "used_10_sec": 0, + } + + transaction.rollback() + + +@pytest.mark.integration +def test_getDepartmentAllocationSummary_uses_most_recent_term(): + """ + Test that when a department has allocations across multiple terms, the + summary reflects only the most recent term's data. + """ + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=201, DEPT_NAME="Chemistry", ACCOUNT="6751", ORG="2121", isActive=True) + + oldTerm = Term.create(termCode=900000, termName="AY Test Old") + newTerm = Term.create(termCode=900100, termName="AY Test New") + + Allocation.create( + termCode=oldTerm, department=dept, isFinal=True, justification="old", + primary_10=1, primary_12=0, primary_15=0, primary_20=0, + secondary_5=0, secondary_10=0, breakHours=50, + ) + Allocation.create( + termCode=newTerm, department=dept, isFinal=True, justification="new", + primary_10=2, primary_12=3, primary_15=0, primary_20=0, + secondary_5=1, secondary_10=0, breakHours=100, + ) + + supervisor = Supervisor.create(ID="SUP001", isActive=True) + student = Student.create(ID="STU001", isActive=True) + + # Under the OLD term - should be excluded from the summary + LaborStatusForm.create( + termCode=oldTerm, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Old Job", POSN_CODE="S001", + weeklyHours=10, contractHours=None, + ) + # Under the NEW (most recent) term - should be counted + LaborStatusForm.create( + termCode=newTerm, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="New Job", POSN_CODE="S002", + weeklyHours=10, contractHours=None, + ) + + summary = getDepartmentAllocationSummary(dept) + + assert summary["term"].termCode == 900100 + assert summary["allocated"] == 6 # 2 + 3 + 0 + 0 + 1 + 0, from the new term only + assert summary["used"] == 1 # only the new term's LaborStatusForm counts + assert summary["used_positions"]["used_10"] == 1 + assert summary["break_hours"] == 0 + + transaction.rollback() + + +@pytest.mark.integration +def test_getDepartmentAllocationSummary_break_hours(): + """ + Test that break_hours only sums forms with contractHours set (break-term + contracts), and that those forms are excluded from the weekly "used" count. + """ + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=202, DEPT_NAME="Biology", ACCOUNT="6752", ORG="2122", isActive=True) + term = Term.create(termCode=900200, termName="AY Test Break") + + Allocation.create( + termCode=term, department=dept, isFinal=True, justification="test", + primary_10=1, primary_12=0, primary_15=0, primary_20=0, + secondary_5=0, secondary_10=0, breakHours=200, + ) + + supervisor = Supervisor.create(ID="SUP002", isActive=True) + student = Student.create(ID="STU002", isActive=True) + + LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Break Worker", POSN_CODE="S003", + weeklyHours=None, contractHours=40, + ) + + summary = getDepartmentAllocationSummary(dept) + + assert summary["break_hours"] == 40 + assert summary["used"] == 0 + + transaction.rollback() + + +@pytest.mark.integration +def test_getDepartmentAllocationSummary_department_none(): + """ + Test that passing department=None (e.g. when Department.get() fails in + the departmentPortal route) returns the zeroed-out fallback instead of + raising an error. + """ + summary = getDepartmentAllocationSummary(None) + + assert summary["term"] is None + assert summary["allocated"] == 0 + assert summary["used"] == 0 + assert summary["break_hours"] == 0 + assert summary["used_positions"] == { + "used_10": 0, + "used_12": 0, + "used_15": 0, + "used_20": 0, + "used_5_sec": 0, + "used_10_sec": 0, + } + + +@pytest.mark.integration +def test_getDepartmentAllocationSummary_multiple_allocations_same_term(): + """ + Test that if a department has more than one Allocation row for the same + most-recent term (e.g. a draft and a final revision, which the model's + (termCode, department, isFinal) index allows), the totals sum across + both rows rather than picking just one. + """ + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=203, DEPT_NAME="Mathematics", ACCOUNT="6753", ORG="2123", isActive=True) + term = Term.create(termCode=900300, termName="AY Test Multi") + + Allocation.create( + termCode=term, department=dept, isFinal=False, justification="draft", + primary_10=1, primary_12=0, primary_15=0, primary_20=0, + secondary_5=0, secondary_10=0, breakHours=10, + ) + Allocation.create( + termCode=term, department=dept, isFinal=True, justification="final", + primary_10=2, primary_12=0, primary_15=0, primary_20=0, + secondary_5=0, secondary_10=0, breakHours=20, + ) + + summary = getDepartmentAllocationSummary(dept) + + assert summary["term"].termCode == 900300 + assert summary["allocated"] == 3 # 1 + 2, summed across both rows + + transaction.rollback() + + +@pytest.mark.integration +def test_getDepartmentAllocationSummary_allocation_no_labor_status_forms(): + """ + Test that a department with an allocation for the most recent term but no + LaborStatusForm records at all shows allocated > 0 with used/break_hours + at 0, rather than erroring on an empty result set. + """ + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=204, DEPT_NAME="History", ACCOUNT="6754", ORG="2124", isActive=True) + term = Term.create(termCode=900400, termName="AY Test Empty") + + Allocation.create( + termCode=term, department=dept, isFinal=True, justification="test", + primary_10=3, primary_12=2, primary_15=0, primary_20=0, + secondary_5=1, secondary_10=0, breakHours=150, + ) + + summary = getDepartmentAllocationSummary(dept) + + assert summary["term"].termCode == 900400 + assert summary["allocated"] == 6 + assert summary["used"] == 0 + assert summary["break_hours"] == 0 + assert summary["used_positions"] == { + "used_10": 0, + "used_12": 0, + "used_15": 0, + "used_20": 0, + "used_5_sec": 0, + "used_10_sec": 0, + } + + transaction.rollback() From cdc18b6cf1cca9dacdc7c2810d2b9d47c880dcfb Mon Sep 17 00:00:00 2001 From: ACBerea Date: Wed, 29 Jul 2026 15:29:45 -0400 Subject: [PATCH 015/128] Improved css namaing structure and began working on improved demo data. --- app/static/css/individualPositions.css | 20 ++++++++++++-------- app/templates/main/individualPositions.html | 16 ++++++++-------- database/demo_data.py | 2 +- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index bdddb9954..2f9c31166 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -1,10 +1,5 @@ -h3 { - font-weight: 700; -} - /* Individual Positions page styles */ /* Header */ -.mb-4 h1, .department-header { text-align: center; font-weight: 700; @@ -12,13 +7,13 @@ h3 { } /* Metadata list (dt / dd spacing) */ -.mb-5 dl.row dt { +.position-information dl.row dt { font-weight: 600; color: #333; text-align: left; font-size: 2rem; } -.mb-5 dl.row dd { +.position-information dl.row dd { margin-bottom: 0.75rem; color: #444; text-align: left; @@ -26,8 +21,12 @@ h3 { } +.description-header { + font-weight: 700; +} + /* Description section card */ -.container-fluid section.mb-5{ +.position-description { background: #fff; border: 1px solid #e6e6e6; padding: 1.25rem; @@ -43,4 +42,9 @@ h3 { .floatright { float: right; margin-left: 0.5rem; +} + +/* Review and improve this later. (Also move it to a more appropriate location) */ +.position-container { + margin-top: 2rem; } \ No newline at end of file diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index 72f3cb09f..bca9161cb 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -8,15 +8,15 @@ {% block app_content %} -
-

{{ department.DEPT_NAME }}

+
+

{{ department.DEPT_NAME }}

-
+
-
+
Position Title:
@@ -38,8 +38,8 @@

{{ department.DEPT_NAME }}

{{ revision_author or "Unknown" }}
-

Description

-
+

Description

+
{% if position.description %}

{{ position.description }}

{% else %} @@ -50,7 +50,7 @@

Description

-
+
{% endblock %} diff --git a/database/demo_data.py b/database/demo_data.py index c010fe863..46f0d3f0f 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -768,7 +768,7 @@ "wls": 2, "revisionDate": f"2026-09-01", "revisedBy": "Jane Smith", - "description": "", + "description": "-WLS Level Justification\n- Description of Duties\n- Learning Opportunities\n- Required Qualifications", "department": 1 }, { From d8b4eceab36070be61bc10701807b313432007dd Mon Sep 17 00:00:00 2001 From: lolongaj Date: Wed, 29 Jul 2026 16:20:47 -0400 Subject: [PATCH 016/128] made minor to changes to css, html, and demo_data files --- app/static/css/individualPositions.css | 11 ++++---- app/templates/main/individualPositions.html | 30 ++++++++++----------- database/demo_data.py | 4 +-- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index 2f9c31166..f3189f3b5 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -25,6 +25,11 @@ font-weight: 700; } +/* Review and improve this later. (Also move it to a more appropriate location) */ +.position-container { + margin-top: 2rem; +} + /* Description section card */ .position-description { background: #fff; @@ -36,15 +41,11 @@ margin-top: 0; margin-bottom: 0.75rem; font-size: 1.25rem; + white-space: pre-line; } /* Utility float class used across the app */ .floatright { float: right; margin-left: 0.5rem; -} - -/* Review and improve this later. (Also move it to a more appropriate location) */ -.position-container { - margin-top: 2rem; } \ No newline at end of file diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index bca9161cb..3a32443a7 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -47,23 +47,23 @@

Description

{% endif %} -
-
{% endblock %} diff --git a/database/demo_data.py b/database/demo_data.py index 46f0d3f0f..17c7be8a1 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -768,7 +768,7 @@ "wls": 2, "revisionDate": f"2026-09-01", "revisedBy": "Jane Smith", - "description": "-WLS Level Justification\n- Description of Duties\n- Learning Opportunities\n- Required Qualifications", + "description": "WLS Level Justification:\nThis position is assigned WLS 2 because it supports key research work with moderate technical complexity.\n\nDescription of Duties:\nProvide research assistance, coordinate data collection, and help prepare reports.\n\nLearning Opportunities:\nGain experience with research practices, data management, and academic collaboration.\n\nRequired Qualifications:\nStrong communication skills, attention to detail, and ability to work independently.", "department": 1 }, { @@ -869,7 +869,7 @@ "wls":6, "revisionDate" : f"2026-05-03", "revisedBy": "Jack Smith", - "description": "This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work- Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.", + "description": "", "department" : 1 } From e4fd883106a4fea81284be8e075196bbd2213839 Mon Sep 17 00:00:00 2001 From: lolongaj Date: Wed, 29 Jul 2026 17:15:18 -0400 Subject: [PATCH 017/128] changes around the position descrription for format --- app/static/css/individualPositions.css | 7 ++++++- app/templates/main/individualPositions.html | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index f3189f3b5..36f3eaa10 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -34,7 +34,7 @@ .position-description { background: #fff; border: 1px solid #e6e6e6; - padding: 1.25rem; + padding: 5px; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.03); text-align: left; @@ -44,6 +44,11 @@ white-space: pre-line; } +.description-content { + margin: 0; + padding: 0; +} + /* Utility float class used across the app */ .floatright { float: right; diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index 3a32443a7..71bb7cf7d 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -41,7 +41,7 @@

{{ department.DEPT_NAME }}

Description

{% if position.description %} -

{{ position.description }}

+
{{ position.description }}
{% else %}

No description available.

{% endif %} From 722606bdb77b9c2976e369186889c907938aae0b Mon Sep 17 00:00:00 2001 From: ACBerea Date: Thu, 30 Jul 2026 09:24:10 -0400 Subject: [PATCH 018/128] Adjusted HTML in individualPositions.html for consistent formatting. --- app/templates/main/individualPositions.html | 39 ++++++++++----------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index 71bb7cf7d..4dd768c8f 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -1,15 +1,14 @@ {% extends "base.html" %} {% block scripts %} -{{super()}} - - + {{ super() }} + + {% endblock %} {% block app_content %} -
-

{{ department.DEPT_NAME }}

+

{{ department.DEPT_NAME }}

@@ -18,7 +17,6 @@

{{ department.DEPT_NAME }}

-
Position Title:
{{ position.positionTitle }}
@@ -38,6 +36,7 @@

{{ department.DEPT_NAME }}

{{ revision_author or "Unknown" }}
+

Description

{% if position.description %} @@ -48,22 +47,22 @@

Description

+
-{% endblock %} +{% endblock %} \ No newline at end of file From ee0e066beba6f1508c9dbb5d5444f7df224324dd Mon Sep 17 00:00:00 2001 From: rukwashai Date: Thu, 30 Jul 2026 09:34:58 -0400 Subject: [PATCH 019/128] Address PR review comments: consolidate getAllocation return dict, extract countWorkers/getBreakHours with FormHistory approval filter, dynamic term in departmentPortal.html, and revert environment-specific path edits in base_data.py/demo_data.py. Add integration test coverage for the new logic functions. --- app/logic/getAllocation.py | 108 +++++++++-------- app/templates/main/departmentPortal.html | 2 +- database/base_data.py | 5 - database/demo_data.py | 10 -- tests/code/test_getAllocation.py | 141 ++++++++++++++++++++++- 5 files changed, 197 insertions(+), 69 deletions(-) diff --git a/app/logic/getAllocation.py b/app/logic/getAllocation.py index 5fc14abfc..84e6e7389 100644 --- a/app/logic/getAllocation.py +++ b/app/logic/getAllocation.py @@ -3,31 +3,65 @@ from app.models.allocation import Allocation from app.models.laborStatusForm import LaborStatusForm from app.models.term import Term +from app.models.formHistory import FormHistory + + +def countWorkers(department, term_code, job_type, hours_bucket): + workerCount = ( + LaborStatusForm.select() + .where( + LaborStatusForm.department == department, + LaborStatusForm.termCode == term_code, + LaborStatusForm.jobType == job_type, + LaborStatusForm.weeklyHours == hours_bucket, + LaborStatusForm.contractHours.is_null(True), + ) + .count() + ) + return workerCount + + +def getBreakHours(department, term_code): + breakHoursTotal = ( + LaborStatusForm.select(fn.SUM(LaborStatusForm.contractHours)) + .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) + .where( + LaborStatusForm.department == department, + LaborStatusForm.termCode == term_code, + FormHistory.historyType == "Labor Status Form", + FormHistory.status == "Approved", + ) + .scalar() + ) or 0 + return breakHoursTotal def getDepartmentAllocationSummary(department): """Return allocation-utilization values for a department's most recent term.""" + result = { + "term": None, + "allocated": 0, + "used": 0, + "used_positions": { + "used_10": 0, + "used_12": 0, + "used_15": 0, + "used_20": 0, + "used_5_sec": 0, + "used_10_sec": 0, + }, + "break_hours": 0, + } + departmentAllocations = list( Allocation.select(Allocation, Term).join(Term).where(Allocation.department == department) ) if not departmentAllocations: - return { - "term": None, - "allocated": 0, - "used": 0, - "used_positions": { - "used_10": 0, - "used_12": 0, - "used_15": 0, - "used_20": 0, - "used_5_sec": 0, - "used_10_sec": 0, - }, - "break_hours": 0, - } + return result recentTerm = Term.order_by_term([a.termCode for a in departmentAllocations], reverse=True)[0] term_code = recentTerm.termCode + result["term"] = recentTerm total_positions = ( Allocation.select( @@ -44,6 +78,7 @@ def getDepartmentAllocationSummary(department): ) .scalar() ) + result["allocated"] = total_positions or 0 used_allocation = ( LaborStatusForm.select() @@ -54,42 +89,17 @@ def getDepartmentAllocationSummary(department): ) .count() ) + result["used"] = used_allocation - def count_workers(job_type, hours_bucket): - return ( - LaborStatusForm.select() - .where( - LaborStatusForm.department == department, - LaborStatusForm.termCode == term_code, - LaborStatusForm.jobType == job_type, - LaborStatusForm.weeklyHours == hours_bucket, - LaborStatusForm.contractHours.is_null(True), - ) - .count() - ) - - used_positions = { - "used_10": count_workers("Primary", 10), - "used_12": count_workers("Primary", 12), - "used_15": count_workers("Primary", 15), - "used_20": count_workers("Primary", 20), - "used_5_sec": count_workers("Secondary", 5), - "used_10_sec": count_workers("Secondary", 10), + result["used_positions"] = { + "used_10": countWorkers(department, term_code, "Primary", 10), + "used_12": countWorkers(department, term_code, "Primary", 12), + "used_15": countWorkers(department, term_code, "Primary", 15), + "used_20": countWorkers(department, term_code, "Primary", 20), + "used_5_sec": countWorkers(department, term_code, "Secondary", 5), + "used_10_sec": countWorkers(department, term_code, "Secondary", 10), } - break_hours = sum( - form.contractHours or 0 - for form in LaborStatusForm.select(LaborStatusForm.contractHours).where( - LaborStatusForm.department == department, - LaborStatusForm.termCode == term_code, - LaborStatusForm.contractHours.is_null(False), - ) - ) + result["break_hours"] = getBreakHours(department, term_code) - return { - "term": recentTerm, - "allocated": total_positions or 0, - "used": used_allocation, - "used_positions": used_positions, - "break_hours": break_hours, - } + return result diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 4cb45ee2a..c3d6eae2c 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -40,7 +40,7 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e

Allocations

-

AY 2025-2026

{{used}}/{{allocated or 0}} Positions

+

{{ term.termName if term else "No term data" }}

{{used}}/{{allocated or 0}} Positions

    diff --git a/database/base_data.py b/database/base_data.py index f42d57ce9..c081fb752 100644 --- a/database/base_data.py +++ b/database/base_data.py @@ -1,8 +1,3 @@ -import os -import sys - -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) - from app.models.status import Status from app.models.historyType import HistoryType from app.models.emailTemplate import EmailTemplate diff --git a/database/demo_data.py b/database/demo_data.py index ab2d0db27..08784e51c 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -1,14 +1,4 @@ -'''Add new fields to this file and run it to add new enteries into your local database. -Chech phpmyadmin to see if your changes are reflected -This file will need to be changed if the format of models changes (new fields, dropping fields, renaming...)''' - -import os -import sys - -sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) - from app import app - from app.models.Tracy import db from app.models.Tracy.studata import STUDATA from app.models.Tracy.stuposn import STUPOSN diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py index fb8c6d51a..6ac0fc246 100644 --- a/tests/code/test_getAllocation.py +++ b/tests/code/test_getAllocation.py @@ -1,3 +1,5 @@ +from datetime import date + import pytest from app.models import mainDB from app.models.department import Department @@ -6,7 +8,26 @@ from app.models.laborStatusForm import LaborStatusForm from app.models.student import Student from app.models.supervisor import Supervisor -from app.logic.getAllocation import getDepartmentAllocationSummary +from app.models.formHistory import FormHistory +from app.models.historyType import HistoryType +from app.models.status import Status +from app.models.user import User +from app.logic.getAllocation import getDepartmentAllocationSummary, countWorkers, getBreakHours + + +def _createFormHistory(form, statusName): + """Attach a FormHistory row to a LaborStatusForm, since getBreakHours now + only counts forms with an approved "Labor Status Form" history entry.""" + user = User.create(username=f"testuser_{form.laborStatusFormID}") + historyType = HistoryType.get(HistoryType.historyTypeName == "Labor Status Form") + status = Status.get(Status.statusName == statusName) + return FormHistory.create( + formID=form, + historyType=historyType, + createdBy=user, + createdDate=date.today(), + status=status, + ) @pytest.mark.integration @@ -89,8 +110,9 @@ def test_getDepartmentAllocationSummary_uses_most_recent_term(): @pytest.mark.integration def test_getDepartmentAllocationSummary_break_hours(): """ - Test that break_hours only sums forms with contractHours set (break-term - contracts), and that those forms are excluded from the weekly "used" count. + Test that break_hours only sums approved forms with contractHours set + (break-term contracts), and that those forms are excluded from the + weekly "used" count. """ with mainDB.atomic() as transaction: dept = Department.create(departmentID=202, DEPT_NAME="Biology", ACCOUNT="6752", ORG="2122", isActive=True) @@ -105,11 +127,12 @@ def test_getDepartmentAllocationSummary_break_hours(): supervisor = Supervisor.create(ID="SUP002", isActive=True) student = Student.create(ID="STU002", isActive=True) - LaborStatusForm.create( + breakForm = LaborStatusForm.create( termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, jobType="Primary", WLS="10", POSN_TITLE="Break Worker", POSN_CODE="S003", weeklyHours=None, contractHours=40, ) + _createFormHistory(breakForm, "Approved") summary = getDepartmentAllocationSummary(dept) @@ -206,3 +229,113 @@ def test_getDepartmentAllocationSummary_allocation_no_labor_status_forms(): } transaction.rollback() + + +@pytest.mark.integration +def test_countWorkers(): + """ + Test that countWorkers only counts LaborStatusForm rows matching the + given department, term, job type, and weekly-hours bucket, and excludes + forms with a different job type/hours bucket or a break-term contract + (contractHours set instead of weeklyHours). + """ + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=205, DEPT_NAME="English", ACCOUNT="6755", ORG="2125", isActive=True) + term = Term.create(termCode=900500, termName="AY Test Workers") + + supervisor = Supervisor.create(ID="SUP003", isActive=True) + student = Student.create(ID="STU003", isActive=True) + + # Matches department, term, job type, and hours bucket - should count + LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Match", POSN_CODE="S010", + weeklyHours=10, contractHours=None, + ) + # Different job type - should not count toward ("Primary", 10) + LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Secondary", WLS="10", POSN_TITLE="Wrong Job Type", POSN_CODE="S011", + weeklyHours=10, contractHours=None, + ) + # Different hours bucket - should not count toward ("Primary", 10) + LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="12", POSN_TITLE="Wrong Hours", POSN_CODE="S012", + weeklyHours=12, contractHours=None, + ) + # Break-term contract (contractHours set) - should not count even though + # job type and weeklyHours otherwise match + LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Break Contract", POSN_CODE="S013", + weeklyHours=10, contractHours=40, + ) + + assert countWorkers(dept, term.termCode, "Primary", 10) == 1 + assert countWorkers(dept, term.termCode, "Secondary", 10) == 1 + assert countWorkers(dept, term.termCode, "Primary", 12) == 1 + assert countWorkers(dept, term.termCode, "Primary", 15) == 0 + + transaction.rollback() + + +@pytest.mark.integration +def test_getBreakHours(): + """ + Test that getBreakHours sums only APPROVED forms with contractHours set + (break-term contracts) for the given department and term, excludes + weekly-hours forms, excludes forms under a different term, and excludes + forms that are not approved (e.g. still pending). + """ + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=206, DEPT_NAME="Philosophy", ACCOUNT="6756", ORG="2126", isActive=True) + term = Term.create(termCode=900600, termName="AY Test Break Hours") + otherTerm = Term.create(termCode=900601, termName="AY Test Other Term") + + supervisor = Supervisor.create(ID="SUP004", isActive=True) + student = Student.create(ID="STU004", isActive=True) + + # Approved break-term contracts under the target term - should be summed + formA = LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Break A", POSN_CODE="S020", + weeklyHours=None, contractHours=40, + ) + _createFormHistory(formA, "Approved") + + formB = LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Secondary", WLS="5", POSN_TITLE="Break B", POSN_CODE="S021", + weeklyHours=None, contractHours=60, + ) + _createFormHistory(formB, "Approved") + + # Weekly-hours form (contractHours=None) - should be excluded regardless + formC = LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Weekly Job", POSN_CODE="S022", + weeklyHours=10, contractHours=None, + ) + _createFormHistory(formC, "Approved") + + # Break-term contract under a DIFFERENT term - should be excluded + formD = LaborStatusForm.create( + termCode=otherTerm, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Break Other Term", POSN_CODE="S023", + weeklyHours=None, contractHours=100, + ) + _createFormHistory(formD, "Approved") + + # Break-term contract that is still PENDING - should be excluded + formE = LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Break Pending", POSN_CODE="S024", + weeklyHours=None, contractHours=999, + ) + _createFormHistory(formE, "Pending") + + assert getBreakHours(dept, term.termCode) == 100 # 40 + 60, excludes the pending form + assert getBreakHours(dept, otherTerm.termCode) == 100 + + transaction.rollback() From 0eb3bb1f511d94ba425217ea70a10960fd173243 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 30 Jul 2026 10:23:43 -0400 Subject: [PATCH 020/128] Added necessary files for the allocationTable --- .../main_routes/departmentPortal.py | 19 +++++- app/controllers/main_routes/main_routes.py | 10 +++ app/static/css/allocationTable.css | 0 app/templates/main/allocationTable.html | 62 +++++++++++++++++++ app/templates/main/departmentPortal.html | 2 +- 5 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 app/static/css/allocationTable.css create mode 100644 app/templates/main/allocationTable.html diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 351757701..b6b246ab7 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -1 +1,18 @@ -from flask import render_template +from flask import render_template, g +from peewee import DoesNotExist + +from app.models.department import Department +from app.models.allocation import Allocation +from app.models.laborStatusForm import LaborStatusForm +from app.models.formHistory import formHistory + +from app.controllers.main_routes import main_bp + +@main_bp.route('/department///allocations', methods=['GET']) +def allocationTable(org=None, account=None): + currentUser = g.currentUser + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + dept = None + diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index b2c62d1f2..cc4375979 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -82,6 +82,16 @@ def departmentPortal(org=None,account=None): positions = positionsList, posURL = posURL) +@main_bp.route('/department///allocations', methods=['GET']) +def allocationTable(org=None, account=None): + currentUser = g.currentUser + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + dept = None + + return render_template('main/allocationTable.html') + @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) def addUserToDept(): userDeptData = request.form diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css new file mode 100644 index 000000000..e69de29bb diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html new file mode 100644 index 000000000..028bc2f79 --- /dev/null +++ b/app/templates/main/allocationTable.html @@ -0,0 +1,62 @@ +{% extends "base.html" %} +{% block styles %} + {{super()}} + +{% endblock %} + +{% block scripts %} + {{super()}} + +{% endblock %} + +{% block app_content %} + +

    Aaaaa

    + + + +
    + + + + + + + + + {% for i in [1,2,3,4]%} + + + + + {% endfor %} + +
    DepartmentStatus
    name(org, account) + +
    +
    + +
    + + + + + + + + {% for i in [1,2,3,4] %} + + + + {% endfor %} + +
    Department
    name(aaa, numbers)
    +
    + + +{% endblock %} diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 549ab272f..06cc7adb4 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -36,7 +36,7 @@

    {% if department %} {{department.DEPT_NAME}} Portal {% e

    Insert Allocations Card Here

From 36e6d95aaac7a85ae74eb629d5549a3300219a8c Mon Sep 17 00:00:00 2001 From: ACBerea Date: Thu, 30 Jul 2026 10:33:42 -0400 Subject: [PATCH 021/128] Fixed some demo data to accuratly reflect sudo-real data. --- database/demo_data.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/database/demo_data.py b/database/demo_data.py index b0cd3c0b5..b37ea3519 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -832,7 +832,7 @@ "status": "Active", "wls": 1, "revisionDate": f"2026-07-01", - "revisedBy": "John Doe", + "revisedBy": "Mario Nakazawa", "description": "", "department": 1 }, @@ -842,7 +842,7 @@ "status": "Active", "wls": 2, "revisionDate": f"2026-09-01", - "revisedBy": "Jane Smith", + "revisedBy": "Deanna Wilborne", "description": "WLS Level Justification:\nThis position is assigned WLS 2 because it supports key research work with moderate technical complexity.\n\nDescription of Duties:\nProvide research assistance, coordinate data collection, and help prepare reports.\n\nLearning Opportunities:\nGain experience with research practices, data management, and academic collaboration.\n\nRequired Qualifications:\nStrong communication skills, attention to detail, and ability to work independently.", "department": 1 }, @@ -852,8 +852,8 @@ "status": "Active", "wls": 3, "revisionDate": f"2026-07-01", - "revisedBy": "Alice Johnson", - "description": "", + "revisedBy": "Jasmine Jones", + "description": "WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.\n\nA. Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.\nB. Ability to take advice and respond appropriately.\nC. A desire to mentor and work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software and debugging.", "department": 1 }, { @@ -862,9 +862,9 @@ "status": "Active", "wls":3, "revisionDate" : f"2026-01-01", - "revisedBy": "Bob Wilson", - "description": "", - "department" : 1 + "revisedBy": "Scott Heggen", + "description":"WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity和 accessibility.\n\nA. Ability to function with a little more independence和 complete tasks with assistance from team leader(s)和 other student colleagues.\nB. Ability to take advice和 respond appropriately.\nC. A desire to mentor和 work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software和 debugging.", + "department": 1 }, { @@ -873,8 +873,8 @@ "status": "Inactive", "wls":2, "revisionDate" : f"2026-01-01", - "revisedBy": "Charlie Brown", - "description": "", + "revisedBy": "Brian Ramsay", + "description": "WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity和 accessibility.\n\nA. Ability to function with a little more independence和 complete tasks with assistance from team leader(s)和 other student colleagues.\nB. Ability to take advice和 respond appropriately.\nC. A desire to mentor和 work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software和 debugging.", "department" : 3 }, { @@ -883,7 +883,7 @@ "status": "Active", "wls":2, "revisionDate" : f"2026-03-29", - "revisedBy": "David Davis", + "revisedBy": "Jan Pearce", "description": "", "department" : 3 }, @@ -893,7 +893,7 @@ "status": "Active", "wls":3, "revisionDate" : f"2026-01-23", - "revisedBy": "Eve Thompson", + "revisedBy": "Scott Heggen", "description": "", "department" : 1 }, @@ -903,7 +903,7 @@ "status": "Active", "wls":4, "revisionDate" : f"2026-01-31", - "revisedBy": "Frank Miller", + "revisedBy": "Jasmine Jones", "description": "", "department" : 1 }, @@ -913,7 +913,7 @@ "status": "Active", "wls":5, "revisionDate" : f"2026-04-01", - "revisedBy": "Grace Lee", + "revisedBy": "Deanna Wilborne", "description": "", "department" : 1 }, @@ -923,7 +923,7 @@ "status": "Active", "wls":6, "revisionDate" : f"2026-05-03", - "revisedBy": "Henry Adams", + "revisedBy": "Jan Pearce", "description": "", "department" : 1 }, @@ -933,7 +933,7 @@ "status": "Active", "wls":1, "revisionDate" : f"2026-05-03", - "revisedBy": "Ivy Johnson", + "revisedBy": "Jan Pearce", "description": "", "department" : 1 }, @@ -943,7 +943,7 @@ "status": "Active", "wls":6, "revisionDate" : f"2026-05-03", - "revisedBy": "Jack Smith", + "revisedBy": "Brian Ramsay", "description": "", "department" : 1 } From c0698e5b979d76696c0e21660c430856c46d9162 Mon Sep 17 00:00:00 2001 From: ACBerea Date: Thu, 30 Jul 2026 11:04:27 -0400 Subject: [PATCH 022/128] Fixed margin issue in individualPosition.html, replaced outdated jinja, and adjusted a few line of demp_data to provide more than one example of accurate data. --- app/templates/main/individualPositions.html | 8 ++++---- database/demo_data.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index 4dd768c8f..d4c761be6 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -33,17 +33,17 @@

{{ department.DEPT_NAME }}

{{ position.revisionDate }}
Revised By:
-
{{ revision_author or "Unknown" }}
+
{{ position.revisedBy or "Unknown" }}

Description

- {% if position.description %} + {%- if position.description %}
{{ position.description }}
- {% else %} + {%- else %}

No description available.

- {% endif %} + {%- endif %}
diff --git a/database/demo_data.py b/database/demo_data.py index b37ea3519..29adf3c34 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -863,7 +863,7 @@ "wls":3, "revisionDate" : f"2026-01-01", "revisedBy": "Scott Heggen", - "description":"WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity和 accessibility.\n\nA. Ability to function with a little more independence和 complete tasks with assistance from team leader(s)和 other student colleagues.\nB. Ability to take advice和 respond appropriately.\nC. A desire to mentor和 work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software和 debugging.", + "description":"WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.\n\nA. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.\nB. Ability to take advice respond appropriately.\nC. A desire to mentor work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software debugging.", "department": 1 }, @@ -874,7 +874,7 @@ "wls":2, "revisionDate" : f"2026-01-01", "revisedBy": "Brian Ramsay", - "description": "WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity和 accessibility.\n\nA. Ability to function with a little more independence和 complete tasks with assistance from team leader(s)和 other student colleagues.\nB. Ability to take advice和 respond appropriately.\nC. A desire to mentor和 work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software和 debugging.", + "description": "WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.\n\nA. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.\nB. Ability to take advice respond appropriately.\nC. A desire to mentor work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software debugging.", "department" : 3 }, { From 8618b68d1a74bc53fe883111c8dd4d3c6306eb19 Mon Sep 17 00:00:00 2001 From: rukwashai Date: Thu, 30 Jul 2026 11:12:53 -0400 Subject: [PATCH 023/128] Adopt approval-status filtering from UsedAllocFunction branch in getAllocation.py, excluding denied forms from countWorkers and the used count. Update tests to cover the new behavior. --- app/logic/getAllocation.py | 6 ++++++ tests/code/test_getAllocation.py | 30 +++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) diff --git a/app/logic/getAllocation.py b/app/logic/getAllocation.py index 84e6e7389..893da61bc 100644 --- a/app/logic/getAllocation.py +++ b/app/logic/getAllocation.py @@ -9,12 +9,15 @@ def countWorkers(department, term_code, job_type, hours_bucket): workerCount = ( LaborStatusForm.select() + .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) .where( LaborStatusForm.department == department, LaborStatusForm.termCode == term_code, LaborStatusForm.jobType == job_type, LaborStatusForm.weeklyHours == hours_bucket, LaborStatusForm.contractHours.is_null(True), + FormHistory.historyType == "Labor Status Form", + ~(FormHistory.status % "Denied%"), ) .count() ) @@ -82,10 +85,13 @@ def getDepartmentAllocationSummary(department): used_allocation = ( LaborStatusForm.select() + .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) .where( LaborStatusForm.department == department, LaborStatusForm.termCode == term_code, LaborStatusForm.contractHours.is_null(True), + FormHistory.historyType == "Labor Status Form", + ~(FormHistory.status % "Denied%"), ) .count() ) diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py index 6ac0fc246..3d9259066 100644 --- a/tests/code/test_getAllocation.py +++ b/tests/code/test_getAllocation.py @@ -90,11 +90,12 @@ def test_getDepartmentAllocationSummary_uses_most_recent_term(): weeklyHours=10, contractHours=None, ) # Under the NEW (most recent) term - should be counted - LaborStatusForm.create( + newForm = LaborStatusForm.create( termCode=newTerm, studentSupervisee=student, supervisor=supervisor, department=dept, jobType="Primary", WLS="10", POSN_TITLE="New Job", POSN_CODE="S002", weeklyHours=10, contractHours=None, ) + _createFormHistory(newForm, "Approved") summary = getDepartmentAllocationSummary(dept) @@ -236,8 +237,8 @@ def test_countWorkers(): """ Test that countWorkers only counts LaborStatusForm rows matching the given department, term, job type, and weekly-hours bucket, and excludes - forms with a different job type/hours bucket or a break-term contract - (contractHours set instead of weeklyHours). + forms with a different job type/hours bucket, a break-term contract + (contractHours set instead of weeklyHours), or a denied history status. """ with mainDB.atomic() as transaction: dept = Department.create(departmentID=205, DEPT_NAME="English", ACCOUNT="6755", ORG="2125", isActive=True) @@ -247,30 +248,45 @@ def test_countWorkers(): student = Student.create(ID="STU003", isActive=True) # Matches department, term, job type, and hours bucket - should count - LaborStatusForm.create( + matchForm = LaborStatusForm.create( termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, jobType="Primary", WLS="10", POSN_TITLE="Match", POSN_CODE="S010", weeklyHours=10, contractHours=None, ) + _createFormHistory(matchForm, "Approved") + # Different job type - should not count toward ("Primary", 10) - LaborStatusForm.create( + wrongJobTypeForm = LaborStatusForm.create( termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, jobType="Secondary", WLS="10", POSN_TITLE="Wrong Job Type", POSN_CODE="S011", weeklyHours=10, contractHours=None, ) + _createFormHistory(wrongJobTypeForm, "Approved") + # Different hours bucket - should not count toward ("Primary", 10) - LaborStatusForm.create( + wrongHoursForm = LaborStatusForm.create( termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, jobType="Primary", WLS="12", POSN_TITLE="Wrong Hours", POSN_CODE="S012", weeklyHours=12, contractHours=None, ) + _createFormHistory(wrongHoursForm, "Approved") + # Break-term contract (contractHours set) - should not count even though # job type and weeklyHours otherwise match - LaborStatusForm.create( + breakContractForm = LaborStatusForm.create( termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, jobType="Primary", WLS="10", POSN_TITLE="Break Contract", POSN_CODE="S013", weeklyHours=10, contractHours=40, ) + _createFormHistory(breakContractForm, "Approved") + + # Matches everything but was DENIED - should not count + deniedForm = LaborStatusForm.create( + termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + jobType="Primary", WLS="10", POSN_TITLE="Denied Match", POSN_CODE="S014", + weeklyHours=10, contractHours=None, + ) + _createFormHistory(deniedForm, "Denied by Admin") assert countWorkers(dept, term.termCode, "Primary", 10) == 1 assert countWorkers(dept, term.termCode, "Secondary", 10) == 1 From 79c21f9ca042560f56256b4a9904d230130cbd01 Mon Sep 17 00:00:00 2001 From: lolongaj Date: Thu, 30 Jul 2026 11:19:41 -0400 Subject: [PATCH 024/128] added functionality to the download button, by updating routing and add the pdf format in download.py --- app/controllers/main_routes/main_routes.py | 28 +++++++++++++--- app/logic/download.py | 39 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 5 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 94c46fa5a..3d1fdab80 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -15,7 +15,7 @@ from app.controllers.admin_routes.allPendingForms import checkAdjustment from app.controllers.main_routes import main_bp -from app.logic.download import CSVMaker, saveFormSearchResult, retrieveFormSearchResult +from app.logic.download import CSVMaker, saveFormSearchResult, retrieveFormSearchResult, makePositionDescriptionPDF from app.logic.search import getDepartmentsForSupervisor, searchPerson, searchSupervisorPortal from app.login_manager import require_login, logout from app.logic.getTableData import getDatatableData @@ -98,15 +98,33 @@ def individualPosition(org, account, positionCode): if not position: return render_template('errors/404.html'), 404 - revision_author = position.revisedBy if getattr(position, 'revisedBy', None) else None - return render_template( 'main/individualPositions.html', department=dept, - position=position, - revision_author=revision_author + position=position ) +@main_bp.route('/department///positions//download', methods=['GET']) +def downloadPositionDescription(org, account, positionCode): + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + return render_template('errors/404.html'), 404 + + position = PositionHistory.get_or_none( + PositionHistory.department == dept, + PositionHistory.positionCode == positionCode, + PositionHistory.status == "Active" + ) + + if not position: + return render_template('errors/404.html'), 404 + + pdfBuffer = makePositionDescriptionPDF(dept, position, position.revisedBy) + + filename = f'{position.positionCode}_position_description.pdf' + return send_file(pdfBuffer, mimetype='application/pdf', as_attachment=True, download_name=filename) + @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) def addUserToDept(): userDeptData = request.form diff --git a/app/logic/download.py b/app/logic/download.py index b9532446d..b5cdba320 100644 --- a/app/logic/download.py +++ b/app/logic/download.py @@ -1,7 +1,9 @@ import csv +import io import json from flask import g +from fpdf import FPDF from peewee import ModelSelect from app.models.formHistory import * @@ -28,6 +30,43 @@ def retrieveFormSearchResult(formSearchResultId): return None +def makePositionDescriptionPDF(department, position, revisionAuthor): + ''' + Builds a PDF of a position's description for the download button on the individual position page + ''' + pdf = FPDF() + pdf.add_page() + + pdf.set_font('Arial', 'B', 16) + pdf.cell(0, 10, department.DEPT_NAME, ln=True) + pdf.ln(2) + + fields = [ + ('Position Title', position.positionTitle), + ('Position Code', position.positionCode), + ('WLS Level', position.wls), + ('Status', position.status), + ('Last Revision Date', position.revisionDate), + ('Revised By', position.revisedBy or 'Unknown'), + ] + labelWidth = 45 + for label, value in fields: + pdf.set_font('Arial', 'B', 11) + pdf.cell(labelWidth, 8, f'{label}:', ln=False) + pdf.set_font('Arial', '', 11) + pdf.cell(0, 8, f' {value}', ln=True) + + pdf.ln(4) + pdf.set_font('Arial', 'B', 12) + pdf.cell(0, 10, 'Description', ln=True) + pdf.set_font('Arial', '', 11) + description = position.description or 'No description available.' + pdf.multi_cell(0, 7, description.encode('latin-1', 'replace').decode('latin-1')) + + pdfBytes = pdf.output(dest='S').encode('latin-1', 'replace') + return io.BytesIO(pdfBytes) + + class CSVMaker: ''' Create the CSV for the download bottons From 5ab2dca77fe1b007e8ea7d6e2f53f27baf3a9057 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 30 Jul 2026 13:59:43 -0400 Subject: [PATCH 025/128] added the individual term for each row in the table --- .../main_routes/departmentPortal.py | 1 + app/controllers/main_routes/main_routes.py | 33 +++++++- app/templates/main/allocationTable.html | 78 ++++++++++--------- 3 files changed, 76 insertions(+), 36 deletions(-) diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index b6b246ab7..74c5d2f0f 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -16,3 +16,4 @@ def allocationTable(org=None, account=None): except (NameError, DoesNotExist): dept = None + return render_template('main/') \ No newline at end of file diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index cc4375979..ee50fa032 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -11,6 +11,7 @@ from app.models.formHistory import FormHistory from app.models.term import Term from app.models.positionHistory import PositionHistory +from app.models.allocation import Allocation from app.controllers.admin_routes.allPendingForms import checkAdjustment from app.controllers.main_routes import main_bp @@ -90,7 +91,37 @@ def allocationTable(org=None, account=None): except (NameError, DoesNotExist): dept = None - return render_template('main/allocationTable.html') + returnTerms = [] + terms = Term.select().order_by(Term.termCode.desc()) + + testAllocationDict = {"primary_10": 1, + "primary_12": 2, + "primary_15": 3, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 6, + "breakHours": 500, + "totalPrimaries": 10, + "totalSecondaries": 11, + "totalAllocations": 21 } + allocationDict = {} + for term in terms: + if str(term.termCode).endswith("00"): + returnTerms.append(term.termName) + + try: + allocationObject = allocationObject = Allocation.select().where( + Allocation.termCode == term.termCode, + Allocation.department == 3,).dicts().get() + allocationDict[term.termName] = testAllocationDict + + except Exception as e: + allocationDict[term.termName] = {} + + return render_template('main/allocationTable.html', + department = dept, + terms = returnTerms, + allocations = allocationDict) @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) def addUserToDept(): diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 028bc2f79..8d24fb866 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -11,52 +11,60 @@ {% block app_content %} -

Aaaaa

+

View Allocations for {{department.DEPT_NAME}}

+
*Position allocations are placed (contracted/allocated) in the table
-
- +
+
- - - + + + + + + + + + + + + + + - - {% for i in [1,2,3,4]%} - - - - + + {% for term in terms%} + {% if allocations[term] != {} %} + + + + + + + + + + + + + + + + {% else %} + + + + {% endif %} {% endfor %}
DepartmentStatus
TermTotal Position AllocationTotal Break Allocations10 hour12 Hour15 Hour20 Hour5 Hour Secondary10 Hour SecondaryFall BreakWinter BreakSpring BreakSummer Break
name(org, account) - -
{{term}}{{allocations[term]["totalAllocations"]}}{{allocations[term]["breakHours"]}}{{allocations[term]["primary_10"]}}{{ allocations[term]["primary_12"]}}{{allocations[term]["primary_15"]}}{{allocations[term]["primary_20"]}}{{allocations[term]["secondary_5"]}}{{allocations[term]["secondary_10"]}}Fall BreakWinter BreakSpring BreakSummer Break
No allocation data for {{term}}
-
- - - - - - - - {% for i in [1,2,3,4] %} - - - - {% endfor %} - -
Department
name(aaa, numbers)
-
+ {% endblock %} From ff379585999c58baf395358d93c81a68017760d1 Mon Sep 17 00:00:00 2001 From: ACBerea Date: Thu, 30 Jul 2026 14:46:24 -0400 Subject: [PATCH 026/128] Renamed individualPosition to positionDescription and moved positionDescription and downloadPositionDescription out of main_routes.py and into departmentPortal.py. --- app/controllers/main_routes/__init__.py | 1 + .../main_routes/departmentPortal.py | 54 ++++++++++++++++++- app/controllers/main_routes/main_routes.py | 43 --------------- 3 files changed, 54 insertions(+), 44 deletions(-) diff --git a/app/controllers/main_routes/__init__.py b/app/controllers/main_routes/__init__.py index 54b32d3c5..e8c4c6bb0 100755 --- a/app/controllers/main_routes/__init__.py +++ b/app/controllers/main_routes/__init__.py @@ -25,3 +25,4 @@ def injectGlobalData(): from app.controllers.main_routes import studentLaborEvaluation from app.controllers.main_routes import search from app.controllers.main_routes import studentResponse +from app.controllers.main_routes import departmentPortal diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 351757701..7199e3489 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -1 +1,53 @@ -from flask import render_template +from flask import render_template, send_file +from peewee import DoesNotExist + +from app.models.department import Department +from app.models.positionHistory import PositionHistory + +from app.controllers.main_routes import main_bp +from app.logic.download import makePositionDescriptionPDF + + +@main_bp.route('/department///positions/', methods=['GET']) +def postionDescription(org, account, positionCode): + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + return render_template('errors/404.html'), 404 + + position = PositionHistory.get_or_none( + PositionHistory.department == dept, + PositionHistory.positionCode == positionCode, + PositionHistory.status == "Active" + ) + + if not position: + return render_template('errors/404.html'), 404 + + return render_template( + 'main/individualPositions.html', + department=dept, + position=position + ) + + +@main_bp.route('/department///positions//download', methods=['GET']) +def downloadPositionDescription(org, account, positionCode): + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + return render_template('errors/404.html'), 404 + + position = PositionHistory.get_or_none( + PositionHistory.department == dept, + PositionHistory.positionCode == positionCode, + PositionHistory.status == "Active" + ) + + if not position: + return render_template('errors/404.html'), 404 + + pdfBuffer = makePositionDescriptionPDF(dept, position, position.revisedBy) + + filename = f'{position.positionCode}_position_description.pdf' + return send_file(pdfBuffer, mimetype='application/pdf', as_attachment=True, download_name=filename) \ No newline at end of file diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 3d1fdab80..b0913745e 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -82,49 +82,6 @@ def departmentPortal(org=None,account=None): positions = positionsList, posURL = posURL) -@main_bp.route('/department///positions/', methods=['GET']) -def individualPosition(org, account, positionCode): - try: - dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) - except (NameError, DoesNotExist): - return render_template('errors/404.html'), 404 - - position = PositionHistory.get_or_none( - PositionHistory.department == dept, - PositionHistory.positionCode == positionCode, - PositionHistory.status == "Active" - ) - - if not position: - return render_template('errors/404.html'), 404 - - return render_template( - 'main/individualPositions.html', - department=dept, - position=position - ) - -@main_bp.route('/department///positions//download', methods=['GET']) -def downloadPositionDescription(org, account, positionCode): - try: - dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) - except (NameError, DoesNotExist): - return render_template('errors/404.html'), 404 - - position = PositionHistory.get_or_none( - PositionHistory.department == dept, - PositionHistory.positionCode == positionCode, - PositionHistory.status == "Active" - ) - - if not position: - return render_template('errors/404.html'), 404 - - pdfBuffer = makePositionDescriptionPDF(dept, position, position.revisedBy) - - filename = f'{position.positionCode}_position_description.pdf' - return send_file(pdfBuffer, mimetype='application/pdf', as_attachment=True, download_name=filename) - @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) def addUserToDept(): userDeptData = request.form From ce23c09b13919cac94d308cb4ced8b0f1cd5fcde Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 30 Jul 2026 14:58:22 -0400 Subject: [PATCH 027/128] Bootstrap datatable was implemented --- app/static/js/allocationTable.js | 7 +++++++ app/templates/main/allocationTable.html | 12 +++++------- 2 files changed, 12 insertions(+), 7 deletions(-) create mode 100644 app/static/js/allocationTable.js diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js new file mode 100644 index 000000000..53f5dfbde --- /dev/null +++ b/app/static/js/allocationTable.js @@ -0,0 +1,7 @@ +$(document).ready( function(){ + allocationTable = $('#allocationTable'); + allocationTable.DataTable({ + searching: true, + pageLength: 25 + }); +}); \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 8d24fb866..c3fc221c1 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -1,11 +1,14 @@ {% extends "base.html" %} {% block styles %} {{super()}} + {% endblock %} {% block scripts %} {{super()}} + + {% endblock %} @@ -17,7 +20,7 @@
*Position allocations are placed (contracted/alloca
- @@ -38,7 +41,6 @@
*Position allocations are placed (contracted/alloca
{% for term in terms%} - {% if allocations[term] != {} %} @@ -54,11 +56,7 @@
*Position allocations are placed (contracted/alloca
- {% else %} - - - - {% endif %} + {% endfor %}
{{term}} {{allocations[term]["totalAllocations"]}}Spring Break Summer Break
No allocation data for {{term}}
From 21e667063e94e955b047a99e2508f5be2e9b7c97 Mon Sep 17 00:00:00 2001 From: ACBerea Date: Thu, 30 Jul 2026 16:17:42 -0400 Subject: [PATCH 028/128] Implemented a new function in getPositions to get a single position and accounts for revision dates. Added checks for revision dates to account for positions that have status' other than active in departmentPortal.py. Modified parameters in downloads.py. --- .../main_routes/departmentPortal.py | 35 ++++++++++++------- app/logic/download.py | 2 +- app/logic/getPositions.py | 18 +++++++++- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 7199e3489..b9f90a0cc 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -1,4 +1,5 @@ -from flask import render_template, send_file +from datetime import datetime +from flask import render_template, send_file, request from peewee import DoesNotExist from app.models.department import Department @@ -6,7 +7,7 @@ from app.controllers.main_routes import main_bp from app.logic.download import makePositionDescriptionPDF - +from app.logic.getPositions import getPositionRevision @main_bp.route('/department///positions/', methods=['GET']) def postionDescription(org, account, positionCode): @@ -15,11 +16,15 @@ def postionDescription(org, account, positionCode): except (NameError, DoesNotExist): return render_template('errors/404.html'), 404 - position = PositionHistory.get_or_none( - PositionHistory.department == dept, - PositionHistory.positionCode == positionCode, - PositionHistory.status == "Active" - ) + revisionDateParam = request.args.get('revisionDate') + revisionDate = None + if revisionDateParam: + try: + revisionDate = datetime.strptime(revisionDateParam, '%Y-%m-%d').date() + except ValueError: + return render_template('errors/404.html'), 404 + + position = getPositionRevision(dept, positionCode, revisionDate) if not position: return render_template('errors/404.html'), 404 @@ -38,16 +43,20 @@ def downloadPositionDescription(org, account, positionCode): except (NameError, DoesNotExist): return render_template('errors/404.html'), 404 - position = PositionHistory.get_or_none( - PositionHistory.department == dept, - PositionHistory.positionCode == positionCode, - PositionHistory.status == "Active" - ) + revisionDateParam = request.args.get('revisionDate') + revisionDate = None + if revisionDateParam: + try: + revisionDate = datetime.strptime(revisionDateParam, '%Y-%m-%d').date() + except ValueError: + return render_template('errors/404.html'), 404 + + position = getPositionRevision(dept, positionCode, revisionDate) if not position: return render_template('errors/404.html'), 404 - pdfBuffer = makePositionDescriptionPDF(dept, position, position.revisedBy) + pdfBuffer = makePositionDescriptionPDF(dept, position) filename = f'{position.positionCode}_position_description.pdf' return send_file(pdfBuffer, mimetype='application/pdf', as_attachment=True, download_name=filename) \ No newline at end of file diff --git a/app/logic/download.py b/app/logic/download.py index b5cdba320..331857b99 100644 --- a/app/logic/download.py +++ b/app/logic/download.py @@ -30,7 +30,7 @@ def retrieveFormSearchResult(formSearchResultId): return None -def makePositionDescriptionPDF(department, position, revisionAuthor): +def makePositionDescriptionPDF(department, position): ''' Builds a PDF of a position's description for the download button on the individual position page ''' diff --git a/app/logic/getPositions.py b/app/logic/getPositions.py index 5ef125ece..a5d1b2e8c 100644 --- a/app/logic/getPositions.py +++ b/app/logic/getPositions.py @@ -18,4 +18,20 @@ def getActivePositions(dept): positionsList.append(i.positionTitle + ": " + "(WLS " + str(i.wls) + ")") posURL.append(str(i.positionCode)) - return positionsList, posURL \ No newline at end of file + return positionsList, posURL + +def getPositionRevision(dept, positionCode, revisionDate=None): + """ + Returns a single position for a given department, position code, and optional revision date. + If no revision date is provided, the most recent revision is returned. + """ + + positionQuery = PositionHistory.select().where( + PositionHistory.department == dept, + PositionHistory.positionCode == positionCode + ) + + if revisionDate: + positionQuery = positionQuery.where(PositionHistory.revisionDate == revisionDate) + + return positionQuery.order_by(PositionHistory.revisionDate.desc()).first() \ No newline at end of file From 05216d031c9281ede0d70206ff88284f516c1f75 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 30 Jul 2026 16:29:57 -0400 Subject: [PATCH 029/128] added a download button to the page --- app/static/css/allocationTable.css | 11 +++++++++++ app/static/js/allocationTable.js | 10 +++++++++- app/templates/main/allocationTable.html | 5 +++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index e69de29bb..4d596f8c2 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -0,0 +1,11 @@ +.grid-container { + display: grid; + grid-template-columns: 1fr 1fr; +} +.btn-success{ + justify-self:end; + align-self: center; + margin-left:8rem; + width:fit-content; + height:fit-content +} \ No newline at end of file diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js index 53f5dfbde..6087cf49e 100644 --- a/app/static/js/allocationTable.js +++ b/app/static/js/allocationTable.js @@ -1,7 +1,15 @@ $(document).ready( function(){ + const entriesPerYear = 5; + allocationTable = $('#allocationTable'); allocationTable.DataTable({ + paging: true, searching: true, - pageLength: 25 + pageLength: 5, + lengthMenu:[[5,10,20,50,-1], + ['5', '10', '20', '50', 'All']], + "order": [ + 0, "desc" + ] }); }); \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index c3fc221c1..61fed782a 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -13,10 +13,11 @@ {% endblock %} {% block app_content %} - +

View Allocations for {{department.DEPT_NAME}}

+
*Position allocations are placed (contracted/allocated) in the table
- +
From 85e232133056aa0820500c5cc27c6f67e03f98df Mon Sep 17 00:00:00 2001 From: ACBerea Date: Thu, 30 Jul 2026 17:24:02 -0400 Subject: [PATCH 030/128] Added a clarifying comment to positionHistory.py and added a test for the new getPositionRevision function in the test_getPosition.py. --- app/models/positionHistory.py | 2 +- tests/code/test_getPositions.py | 57 +++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 3 deletions(-) diff --git a/app/models/positionHistory.py b/app/models/positionHistory.py index 72dba0888..30837b946 100644 --- a/app/models/positionHistory.py +++ b/app/models/positionHistory.py @@ -5,7 +5,7 @@ class PositionHistory(baseModel): positionTitle = CharField() positionCode = CharField() department = ForeignKeyField(Department) - status = CharField() + status = CharField() # Active, Inactive, Requested wls = IntegerField() revisionDate = DateField() revisedBy = CharField() diff --git a/tests/code/test_getPositions.py b/tests/code/test_getPositions.py index 11527f5e9..0e37522ce 100644 --- a/tests/code/test_getPositions.py +++ b/tests/code/test_getPositions.py @@ -2,8 +2,7 @@ from app.models import mainDB from app.models.department import Department from app.models.positionHistory import PositionHistory -from app.logic.getPositions import getActivePositions - +from app.logic.getPositions import * @pytest.mark.integration def test_getActivePositions(): """ @@ -80,3 +79,57 @@ def test_getActivePositions(): assert len(posURL3) == 0 transaction.rollback() + +def test_getPositionRevision(): + """ + Test to check if the getPositionRevision function in getPositions.py correctly retrieves a single position for a given department, position code, and optional revision date. + """ + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6742", ORG="2116", departmentCompliance=True, isActive=True) + + position1 = PositionHistory.create(positionTitle="Lab Technician", + positionCode="S34516", + department=dept, + status="Inactive", + wls=3, + revisionDate="2024-01-01", + description="") + + position2 = PositionHistory.create(positionTitle="Lab Technician", + positionCode="S34516", + department=dept, + status="Requested", + wls=3, + revisionDate="2025-01-01", + description="") + + position3 = PositionHistory.create(positionTitle="Lab Technician", + positionCode="S34516", + department=dept, + status="Active", + wls=3, + revisionDate="2023-01-01", + description="") + + # Test retrieving the most recent revision - should return regardless of status + retrieved_position = getPositionRevision(dept, "S34516") + assert retrieved_position.revisionDate == "2025-01-01" + + # Test retrieving a specific revision - should return regardless of status + retrieved_position_specific = getPositionRevision(dept, "S34516", "2023-01-01") + assert retrieved_position_specific.revisionDate == "2023-01-01" + assert retrieved_position_specific.status == "Active" + + retrieved_position = getPositionRevision(dept, "S34516", "2024-01-01") + assert retrieved_position.revisionDate == "2024-01-01" + assert retrieved_position.status == "Inactive" + + retrieved_position_specific = getPositionRevision(dept, "S34516", "2025-01-01") + assert retrieved_position_specific.revisionDate == "2025-01-01" + assert retrieved_position_specific.status == "Requested" + + # Test retrieving a non-existent revision date + retrieved_position_non_existent = getPositionRevision(dept, "S34516", "2099-01-01") + assert retrieved_position_non_existent is None + + transaction.rollback() From 9268ad0491dc826e4ac9688472ec06977581fbee Mon Sep 17 00:00:00 2001 From: ACBerea Date: Thu, 30 Jul 2026 19:19:32 -0400 Subject: [PATCH 031/128] Reverted getPositionRevision function to getPosition and updated all instances accordingly. Began work on a test function for download,py --- app/controllers/main_routes/departmentPortal.py | 6 +++--- app/logic/getPositions.py | 3 +-- tests/code/test_getPositions.py | 14 +++++++------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index b9f90a0cc..4768e9d97 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -7,7 +7,7 @@ from app.controllers.main_routes import main_bp from app.logic.download import makePositionDescriptionPDF -from app.logic.getPositions import getPositionRevision +from app.logic.getPositions import getPosition @main_bp.route('/department///positions/', methods=['GET']) def postionDescription(org, account, positionCode): @@ -24,7 +24,7 @@ def postionDescription(org, account, positionCode): except ValueError: return render_template('errors/404.html'), 404 - position = getPositionRevision(dept, positionCode, revisionDate) + position = getPosition(dept, positionCode, revisionDate) if not position: return render_template('errors/404.html'), 404 @@ -51,7 +51,7 @@ def downloadPositionDescription(org, account, positionCode): except ValueError: return render_template('errors/404.html'), 404 - position = getPositionRevision(dept, positionCode, revisionDate) + position = getPosition(dept, positionCode, revisionDate) if not position: return render_template('errors/404.html'), 404 diff --git a/app/logic/getPositions.py b/app/logic/getPositions.py index a5d1b2e8c..efae4507c 100644 --- a/app/logic/getPositions.py +++ b/app/logic/getPositions.py @@ -20,12 +20,11 @@ def getActivePositions(dept): return positionsList, posURL -def getPositionRevision(dept, positionCode, revisionDate=None): +def getPosition(dept, positionCode, revisionDate=None): """ Returns a single position for a given department, position code, and optional revision date. If no revision date is provided, the most recent revision is returned. """ - positionQuery = PositionHistory.select().where( PositionHistory.department == dept, PositionHistory.positionCode == positionCode diff --git a/tests/code/test_getPositions.py b/tests/code/test_getPositions.py index 0e37522ce..eff0d78ed 100644 --- a/tests/code/test_getPositions.py +++ b/tests/code/test_getPositions.py @@ -80,9 +80,9 @@ def test_getActivePositions(): transaction.rollback() -def test_getPositionRevision(): +def test_getPosition(): """ - Test to check if the getPositionRevision function in getPositions.py correctly retrieves a single position for a given department, position code, and optional revision date. + Test to check if the getPosition function in getPositions.py correctly retrieves a single position for a given department, position code, and optional revision date. """ with mainDB.atomic() as transaction: dept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6742", ORG="2116", departmentCompliance=True, isActive=True) @@ -112,24 +112,24 @@ def test_getPositionRevision(): description="") # Test retrieving the most recent revision - should return regardless of status - retrieved_position = getPositionRevision(dept, "S34516") + retrieved_position = getPosition(dept, "S34516") assert retrieved_position.revisionDate == "2025-01-01" # Test retrieving a specific revision - should return regardless of status - retrieved_position_specific = getPositionRevision(dept, "S34516", "2023-01-01") + retrieved_position_specific = getPosition(dept, "S34516", "2023-01-01") assert retrieved_position_specific.revisionDate == "2023-01-01" assert retrieved_position_specific.status == "Active" - retrieved_position = getPositionRevision(dept, "S34516", "2024-01-01") + retrieved_position = getPosition(dept, "S34516", "2024-01-01") assert retrieved_position.revisionDate == "2024-01-01" assert retrieved_position.status == "Inactive" - retrieved_position_specific = getPositionRevision(dept, "S34516", "2025-01-01") + retrieved_position_specific = getPosition(dept, "S34516", "2025-01-01") assert retrieved_position_specific.revisionDate == "2025-01-01" assert retrieved_position_specific.status == "Requested" # Test retrieving a non-existent revision date - retrieved_position_non_existent = getPositionRevision(dept, "S34516", "2099-01-01") + retrieved_position_non_existent = getPosition(dept, "S34516", "2099-01-01") assert retrieved_position_non_existent is None transaction.rollback() From 5c830c12f05a38ae68f0ee409be519903e6829dc Mon Sep 17 00:00:00 2001 From: ACBerea Date: Thu, 30 Jul 2026 19:20:57 -0400 Subject: [PATCH 032/128] Added download.py test file missed in previous commit. --- tests/code/test_download.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 tests/code/test_download.py diff --git a/tests/code/test_download.py b/tests/code/test_download.py new file mode 100644 index 000000000..063aa350b --- /dev/null +++ b/tests/code/test_download.py @@ -0,0 +1,33 @@ +import io +from flask import g +from fpdf import FPDF +import pytest +from app.models import mainDB + +from app.logic.download import makePositionDescriptionPDF +from app.models.department import Department +from app.models.positionHistory import PositionHistory + + +def test_makePositionDescriptionPDF(): + with mainDB.atomic() as transaction: + dept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6742", ORG="2116", departmentCompliance=True, isActive=True) + + position = PositionHistory.create(positionTitle="Lab Technician", + positionCode="S34516", + department=dept, + status="Active", + wls=3, + revisionDate="2023-01-01", + revisedBy="Jane Doe", + description="This is a test position description.") + + pdf_buffer = makePositionDescriptionPDF(dept, position) + + assert isinstance(pdf_buffer, io.BytesIO) + + pdf_bytes = pdf_buffer.getvalue() + assert len(pdf_bytes) > 0 + assert pdf_bytes[:5] == b'%PDF-' + + transaction.rollback() \ No newline at end of file From bb55ca8ecde3924227ffc2e20a9bfb78b5e2acfa Mon Sep 17 00:00:00 2001 From: lolongaj Date: Fri, 31 Jul 2026 13:09:00 -0400 Subject: [PATCH 033/128] move the downoad button to the top right, and fixed pdf formatting --- app/logic/download.py | 4 ++-- app/static/css/individualPositions.css | 12 +++++++++++- app/templates/main/individualPositions.html | 13 +++++-------- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/app/logic/download.py b/app/logic/download.py index 331857b99..168236199 100644 --- a/app/logic/download.py +++ b/app/logic/download.py @@ -38,11 +38,11 @@ def makePositionDescriptionPDF(department, position): pdf.add_page() pdf.set_font('Arial', 'B', 16) - pdf.cell(0, 10, department.DEPT_NAME, ln=True) + pdf.cell(0, 10, position.positionTitle, ln=True) pdf.ln(2) fields = [ - ('Position Title', position.positionTitle), + ('Department Name', department.DEPT_NAME), ('Position Code', position.positionCode), ('WLS Level', position.wls), ('Status', position.status), diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index 36f3eaa10..6676ed9ab 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -1,11 +1,21 @@ /* Individual Positions page styles */ /* Header */ +.department-header-container { + position: relative; +} + .department-header { text-align: center; font-weight: 700; margin-bottom: 5rem; } +.download-btn { + position: absolute; + top: 0; + right: 5rem; +} + /* Metadata list (dt / dd spacing) */ .position-information dl.row dt { font-weight: 600; @@ -45,7 +55,7 @@ } .description-content { - margin: 0; + margin: 2rem; padding: 0; } diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index d4c761be6..c811a205c 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -8,6 +8,11 @@ {% block app_content %}
+ + Download Description +

{{ department.DEPT_NAME }}

@@ -52,14 +57,6 @@

Description

Back
- -
From c8f12b1aa382c11fec7572ba08ea474874745347 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 31 Jul 2026 14:41:10 -0400 Subject: [PATCH 034/128] Added accordians instead of a giant table --- app/controllers/main_routes/main_routes.py | 25 ++--- app/static/css/allocationTable.css | 10 ++ app/static/js/allocationTable.js | 16 +-- app/templates/main/allocationTable.html | 108 ++++++++++++--------- 4 files changed, 79 insertions(+), 80 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index ee50fa032..9bacf0091 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -91,10 +91,9 @@ def allocationTable(org=None, account=None): except (NameError, DoesNotExist): dept = None - returnTerms = [] - terms = Term.select().order_by(Term.termCode.desc()) + currentTerm = Term.select().where(Term.termCode == 202500).get() #FIXME - testAllocationDict = {"primary_10": 1, + allocationDict = {"primary_10": 1, "primary_12": 2, "primary_15": 3, "primary_20": 4, @@ -103,24 +102,12 @@ def allocationTable(org=None, account=None): "breakHours": 500, "totalPrimaries": 10, "totalSecondaries": 11, - "totalAllocations": 21 } - allocationDict = {} - for term in terms: - if str(term.termCode).endswith("00"): - returnTerms.append(term.termName) - - try: - allocationObject = allocationObject = Allocation.select().where( - Allocation.termCode == term.termCode, - Allocation.department == 3,).dicts().get() - allocationDict[term.termName] = testAllocationDict - - except Exception as e: - allocationDict[term.termName] = {} - + "totalAllocations": 21} + print("\n\n\n\n\n") + print(allocationDict) return render_template('main/allocationTable.html', department = dept, - terms = returnTerms, + term = currentTerm, allocations = allocationDict) @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index 4d596f8c2..bc1bdbebf 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -8,4 +8,14 @@ margin-left:8rem; width:fit-content; height:fit-content +} +.card-header { + background-color: #efebeb; + margin-bottom: 7px; +} +.mb-0, .collapsed { + outline-color: none; + color: black; + font-size: 18px; + font-weight: 525; } \ No newline at end of file diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js index 6087cf49e..566dc174a 100644 --- a/app/static/js/allocationTable.js +++ b/app/static/js/allocationTable.js @@ -1,15 +1 @@ -$(document).ready( function(){ - const entriesPerYear = 5; - - allocationTable = $('#allocationTable'); - allocationTable.DataTable({ - paging: true, - searching: true, - pageLength: 5, - lengthMenu:[[5,10,20,50,-1], - ['5', '10', '20', '50', 'All']], - "order": [ - 0, "desc" - ] - }); -}); \ No newline at end of file +$("#admin").collapse("show"); diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 61fed782a..a5e20d591 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -13,57 +13,73 @@ {% endblock %} {% block app_content %} -
-

View Allocations for {{department.DEPT_NAME}}

- + + +
+

View {{department.DEPT_NAME}} Allocations

+
*Position allocations are placed (contracted/allocated) in the table
- -
- - - - - - - - - - - - - - - - - - - - {% for term in terms%} - - - - - - - - - - - - - - - +
+
+

Current Term: {{term.termName}}

+

Total AY Positions: {{allocations['totalAllocations']}}

+
+ - {% endfor %} - -
TermTotal Position AllocationTotal Break Allocations10 hour12 Hour15 Hour20 Hour5 Hour Secondary10 Hour SecondaryFall BreakWinter BreakSpring BreakSummer Break
{{term}}{{allocations[term]["totalAllocations"]}}{{allocations[term]["breakHours"]}}{{allocations[term]["primary_10"]}}{{ allocations[term]["primary_12"]}}{{allocations[term]["primary_15"]}}{{allocations[term]["primary_20"]}}{{allocations[term]["secondary_5"]}}{{allocations[term]["secondary_10"]}}Fall BreakWinter BreakSpring BreakSummer Break
-
+
+ + +
+
+ + +
+
+
+ Distinctio, architecto ab quasi aut eius inventore placeat natus voluptatem sit excepturi. +
+
+
- + +
+
+ + +
+
+
+ Distinctio, architecto ab quasi aut eius inventore placeat natus voluptatem sit excepturi. +
+
+
+ +
+
+ + +
+
+
+ Distinctio, architecto ab quasi aut eius inventore placeat natus voluptatem sit excepturi. +
+
+
+
{% endblock %} From 2f9bcda068a56cecbda2dbb15f1f54e47a2f87a4 Mon Sep 17 00:00:00 2001 From: lolongaj Date: Fri, 31 Jul 2026 14:47:25 -0400 Subject: [PATCH 035/128] added position description model --- app/models/positionDescriptionSection.py | 12 ++++++++++++ app/models/positionHistory.py | 1 - database/migrate_db.sh | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 app/models/positionDescriptionSection.py diff --git a/app/models/positionDescriptionSection.py b/app/models/positionDescriptionSection.py new file mode 100644 index 000000000..acabe913f --- /dev/null +++ b/app/models/positionDescriptionSection.py @@ -0,0 +1,12 @@ +from app.models import * +from app.models.positionHistory import PositionHistory + + +class PositionDescriptionSection (baseModel): + position = ForeignKeyField(PositionHistory) + sectionTitle = CharField() + sectionContent = TextField() + order = IntegerField() + + + diff --git a/app/models/positionHistory.py b/app/models/positionHistory.py index 30837b946..fd38f26c3 100644 --- a/app/models/positionHistory.py +++ b/app/models/positionHistory.py @@ -9,7 +9,6 @@ class PositionHistory(baseModel): wls = IntegerField() revisionDate = DateField() revisedBy = CharField() - description = TextField(default=None) class Meta: indexes = ( (('positionCode', 'revisionDate', 'status'), True), ) diff --git a/database/migrate_db.sh b/database/migrate_db.sh index dea226d8e..e2ba150e7 100755 --- a/database/migrate_db.sh +++ b/database/migrate_db.sh @@ -32,6 +32,7 @@ pem add app.models.studentLaborEvaluation.StudentLaborEvaluation pem add app.models.formSearchResult.FormSearchResult pem add app.models.positionHistory.PositionHistory pem add app.models.allocation.Allocation +pem add app.models.positionDescriptionSection.PositionDescriptionSection pem watch pem migrate From 78e115287d0bb28c31d1468f03411d26390894c5 Mon Sep 17 00:00:00 2001 From: lolongaj Date: Fri, 31 Jul 2026 15:20:22 -0400 Subject: [PATCH 036/128] added the sections logic and demo data --- .../main_routes/departmentPortal.py | 7 +- app/logic/download.py | 27 ++-- app/logic/getPositions.py | 14 +- app/models/positionDescriptionSection.py | 2 +- app/static/css/individualPositions.css | 4 +- app/templates/main/individualPositions.html | 7 +- database/demo_data.py | 120 ++++++++++++++++-- 7 files changed, 152 insertions(+), 29 deletions(-) diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 4768e9d97..66259f0ec 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -7,7 +7,7 @@ from app.controllers.main_routes import main_bp from app.logic.download import makePositionDescriptionPDF -from app.logic.getPositions import getPosition +from app.logic.getPositions import getPosition, getPositionDescriptionSections @main_bp.route('/department///positions/', methods=['GET']) def postionDescription(org, account, positionCode): @@ -29,10 +29,13 @@ def postionDescription(org, account, positionCode): if not position: return render_template('errors/404.html'), 404 + sections = getPositionDescriptionSections(position) + return render_template( 'main/individualPositions.html', department=dept, - position=position + position=position, + sections=sections ) diff --git a/app/logic/download.py b/app/logic/download.py index 168236199..ecac9436e 100644 --- a/app/logic/download.py +++ b/app/logic/download.py @@ -10,6 +10,7 @@ from app.controllers.main_routes.main_routes import * from app.models.studentLaborEvaluation import StudentLaborEvaluation from app.models.formSearchResult import FormSearchResult +from app.logic.getPositions import getPositionDescriptionSections def saveFormSearchResult(displayName, formList, formType): ids = [form.formHistoryID for form in formList] @@ -37,7 +38,7 @@ def makePositionDescriptionPDF(department, position): pdf = FPDF() pdf.add_page() - pdf.set_font('Arial', 'B', 16) + pdf.set_font('Times', 'BU', 16) pdf.cell(0, 10, position.positionTitle, ln=True) pdf.ln(2) @@ -51,17 +52,27 @@ def makePositionDescriptionPDF(department, position): ] labelWidth = 45 for label, value in fields: - pdf.set_font('Arial', 'B', 11) + pdf.set_font('Times', 'B', 11) pdf.cell(labelWidth, 8, f'{label}:', ln=False) - pdf.set_font('Arial', '', 11) + pdf.set_font('Times', '', 11) pdf.cell(0, 8, f' {value}', ln=True) + sections = getPositionDescriptionSections(position) + pdf.ln(4) - pdf.set_font('Arial', 'B', 12) - pdf.cell(0, 10, 'Description', ln=True) - pdf.set_font('Arial', '', 11) - description = position.description or 'No description available.' - pdf.multi_cell(0, 7, description.encode('latin-1', 'replace').decode('latin-1')) + if sections: + for section in sections: + pdf.set_font('Times', 'B', 12) + pdf.cell(0, 10, section.sectionTitle, ln=True) + pdf.set_font('Times', '', 11) + content = section.sectionContent.encode('latin-1', 'replace').decode('latin-1') + pdf.multi_cell(0, 7, content) + pdf.ln(2) + else: + pdf.set_font('Times', 'B', 12) + pdf.cell(0, 10, 'Description', ln=True) + pdf.set_font('Times', '', 11) + pdf.multi_cell(0, 7, 'No description available.') pdfBytes = pdf.output(dest='S').encode('latin-1', 'replace') return io.BytesIO(pdfBytes) diff --git a/app/logic/getPositions.py b/app/logic/getPositions.py index efae4507c..aa18e87a4 100644 --- a/app/logic/getPositions.py +++ b/app/logic/getPositions.py @@ -1,4 +1,5 @@ from app.models.positionHistory import PositionHistory +from app.models.positionDescriptionSection import PositionDescriptionSection def getActivePositions(dept): """ @@ -33,4 +34,15 @@ def getPosition(dept, positionCode, revisionDate=None): if revisionDate: positionQuery = positionQuery.where(PositionHistory.revisionDate == revisionDate) - return positionQuery.order_by(PositionHistory.revisionDate.desc()).first() \ No newline at end of file + return positionQuery.order_by(PositionHistory.revisionDate.desc()).first() + +def getPositionDescriptionSections(position): + """ + Returns the description sections for a given position, ordered for display. + """ + positionDescriptionSections = list(PositionDescriptionSection.select() + .where(PositionDescriptionSection.position == position) + .order_by(PositionDescriptionSection.order.asc())) + + return positionDescriptionSections + diff --git a/app/models/positionDescriptionSection.py b/app/models/positionDescriptionSection.py index acabe913f..5488c315e 100644 --- a/app/models/positionDescriptionSection.py +++ b/app/models/positionDescriptionSection.py @@ -6,7 +6,7 @@ class PositionDescriptionSection (baseModel): position = ForeignKeyField(PositionHistory) sectionTitle = CharField() sectionContent = TextField() - order = IntegerField() + order = IntegerField() # Order of the sections in the position description diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index 6676ed9ab..39db95de0 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -44,7 +44,7 @@ .position-description { background: #fff; border: 1px solid #e6e6e6; - padding: 5px; + padding: 1rem; border-radius: 6px; box-shadow: 0 1px 2px rgba(0,0,0,0.03); text-align: left; @@ -55,7 +55,7 @@ } .description-content { - margin: 2rem; + margin: 0; padding: 0; } diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index c811a205c..893582f52 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -44,8 +44,11 @@

{{ department.DEPT_NAME }}

Description

- {%- if position.description %} -
{{ position.description }}
+ {%- if sections %} + {%- for section in sections %} +

{{ section.sectionTitle }}

+
{{ section.sectionContent }}
+ {%- endfor %} {%- else %}

No description available.

{%- endif %} diff --git a/database/demo_data.py b/database/demo_data.py index 29adf3c34..a20087aff 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -19,6 +19,7 @@ from app.models.supervisorDepartment import SupervisorDepartment from app.models.allocation import Allocation from app.models.positionHistory import PositionHistory +from app.models.positionDescriptionSection import PositionDescriptionSection print("Inserting data for demo and testing purposes") @@ -833,7 +834,6 @@ "wls": 1, "revisionDate": f"2026-07-01", "revisedBy": "Mario Nakazawa", - "description": "", "department": 1 }, { @@ -843,7 +843,6 @@ "wls": 2, "revisionDate": f"2026-09-01", "revisedBy": "Deanna Wilborne", - "description": "WLS Level Justification:\nThis position is assigned WLS 2 because it supports key research work with moderate technical complexity.\n\nDescription of Duties:\nProvide research assistance, coordinate data collection, and help prepare reports.\n\nLearning Opportunities:\nGain experience with research practices, data management, and academic collaboration.\n\nRequired Qualifications:\nStrong communication skills, attention to detail, and ability to work independently.", "department": 1 }, { @@ -853,7 +852,6 @@ "wls": 3, "revisionDate": f"2026-07-01", "revisedBy": "Jasmine Jones", - "description": "WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.\n\nA. Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.\nB. Ability to take advice and respond appropriately.\nC. A desire to mentor and work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software and debugging.", "department": 1 }, { @@ -863,7 +861,6 @@ "wls":3, "revisionDate" : f"2026-01-01", "revisedBy": "Scott Heggen", - "description":"WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.\n\nA. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.\nB. Ability to take advice respond appropriately.\nC. A desire to mentor work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software debugging.", "department": 1 }, @@ -874,7 +871,6 @@ "wls":2, "revisionDate" : f"2026-01-01", "revisedBy": "Brian Ramsay", - "description": "WLS Level Justification:\nRefer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.\n\nDescription of Duties:\nA. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.\n\nLearning Opportunities:\nList how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)\n\nRequired Qualifications:\nList the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.\n\nA. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.\nB. Ability to take advice respond appropriately.\nC. A desire to mentor work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software debugging.", "department" : 3 }, { @@ -884,7 +880,6 @@ "wls":2, "revisionDate" : f"2026-03-29", "revisedBy": "Jan Pearce", - "description": "", "department" : 3 }, { @@ -894,7 +889,6 @@ "wls":3, "revisionDate" : f"2026-01-23", "revisedBy": "Scott Heggen", - "description": "", "department" : 1 }, { @@ -904,7 +898,6 @@ "wls":4, "revisionDate" : f"2026-01-31", "revisedBy": "Jasmine Jones", - "description": "", "department" : 1 }, { @@ -914,7 +907,6 @@ "wls":5, "revisionDate" : f"2026-04-01", "revisedBy": "Deanna Wilborne", - "description": "", "department" : 1 }, { @@ -924,7 +916,6 @@ "wls":6, "revisionDate" : f"2026-05-03", "revisedBy": "Jan Pearce", - "description": "", "department" : 1 }, { @@ -934,7 +925,6 @@ "wls":1, "revisionDate" : f"2026-05-03", "revisedBy": "Jan Pearce", - "description": "", "department" : 1 }, { @@ -944,7 +934,6 @@ "wls":6, "revisionDate" : f"2026-05-03", "revisedBy": "Brian Ramsay", - "description": "", "department" : 1 } @@ -952,4 +941,109 @@ ] PositionHistory.insert_many(positionHistory).on_conflict_replace().execute() -print(" * position history added") \ No newline at end of file +print(" * position history added") + +############################# +# Position Description Sections +############################# + +positionDescriptionSections = [ + { + "position": 2, + "sectionTitle": 'WLS Level Justification', + "sectionContent": 'This position is assigned WLS 2 because it supports key research work with moderate technical complexity.', + "order": 1, + }, + { + "position": 2, + "sectionTitle": 'Description of Duties', + "sectionContent": 'Provide research assistance, coordinate data collection, and help prepare reports.', + "order": 2, + }, + { + "position": 2, + "sectionTitle": 'Learning Opportunities', + "sectionContent": 'Gain experience with research practices, data management, and academic collaboration.', + "order": 3, + }, + { + "position": 2, + "sectionTitle": 'Required Qualifications', + "sectionContent": 'Strong communication skills, attention to detail, and ability to work independently.', + "order": 4, + }, + { + "position": 3, + "sectionTitle": 'WLS Level Justification', + "sectionContent": 'Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.', + "order": 1, + }, + { + "position": 3, + "sectionTitle": 'Description of Duties', + "sectionContent": 'A. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.', + "order": 2, + }, + { + "position": 3, + "sectionTitle": 'Learning Opportunities', + "sectionContent": 'List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)', + "order": 3, + }, + { + "position": 3, + "sectionTitle": 'Required Qualifications', + "sectionContent": 'List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.\n\nA. Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.\nB. Ability to take advice and respond appropriately.\nC. A desire to mentor and work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software and debugging.', + "order": 4, + }, + { + "position": 4, + "sectionTitle": 'WLS Level Justification', + "sectionContent": 'Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.', + "order": 1, + }, + { + "position": 4, + "sectionTitle": 'Description of Duties', + "sectionContent": 'A. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.', + "order": 2, + }, + { + "position": 4, + "sectionTitle": 'Learning Opportunities', + "sectionContent": 'List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)', + "order": 3, + }, + { + "position": 4, + "sectionTitle": 'Required Qualifications', + "sectionContent": 'List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.\n\nA. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.\nB. Ability to take advice respond appropriately.\nC. A desire to mentor work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software debugging.', + "order": 4, + }, + { + "position": 5, + "sectionTitle": 'WLS Level Justification', + "sectionContent": 'Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.', + "order": 1, + }, + { + "position": 5, + "sectionTitle": 'Description of Duties', + "sectionContent": 'A. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.', + "order": 2, + }, + { + "position": 5, + "sectionTitle": 'Learning Opportunities', + "sectionContent": 'List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)', + "order": 3, + }, + { + "position": 5, + "sectionTitle": 'Required Qualifications', + "sectionContent": 'List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.\n\nA. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.\nB. Ability to take advice respond appropriately.\nC. A desire to mentor work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software debugging.', + "order": 4, + }, +] +PositionDescriptionSection.insert_many(positionDescriptionSections).on_conflict_replace().execute() +print(" * position description sections added") From beaba49b382ae93db30e4d00554527a9e1f72cfc Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 31 Jul 2026 16:11:08 -0400 Subject: [PATCH 037/128] structured primaries table --- app/static/css/allocationTable.css | 8 ++++ app/static/js/allocationTable.js | 11 ++++- app/templates/main/allocationTable.html | 60 +++++++++++++++++++------ 3 files changed, 64 insertions(+), 15 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index bc1bdbebf..23296bca0 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -18,4 +18,12 @@ color: black; font-size: 18px; font-weight: 525; +} +.collapse{ + padding-left: 5% ; + padding-right: 5%; +} +.accordion{ + padding-left: 5%; + padding-right: 5%; } \ No newline at end of file diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js index 566dc174a..d8260015a 100644 --- a/app/static/js/allocationTable.js +++ b/app/static/js/allocationTable.js @@ -1 +1,10 @@ -$("#admin").collapse("show"); +$(document).ready( function(){ + fallTermTable = $('#fallTermTable'); + fallTermTable.DataTable({ + pageLength: 25, + info: false, + lengthChange: false, + searching: false, + paging: false + }); +}); \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index a5e20d591..46acb7c7a 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -28,26 +28,58 @@

Total AY Positions: {{allocations['totalAllocations']}}

-
+
- +
-
+
-
-
-
- Distinctio, architecto ab quasi aut eius inventore placeat natus voluptatem sit excepturi. +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrimariesContractedAllocated
Total Primariesnot implemented{{allocations["totalPrimaries"]}}
10 Hournot implemented{{allocations["primary_10"]}}
12 Hournot implemented{{allocations["primary_12"]}}
15 Hournot implemented{{allocations["primary_15"]}}
20 Hournot implemented{{allocations["primary_20"]}}
-
- +
@@ -64,17 +96,17 @@

- +
-
+
-
-
+
Distinctio, architecto ab quasi aut eius inventore placeat natus voluptatem sit excepturi.
From e9811183e61bb5cd96b9b156c53254ed94490773 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 31 Jul 2026 16:56:28 -0400 Subject: [PATCH 038/128] Formatted the break table --- app/static/css/allocationTable.css | 3 + app/static/js/allocationTable.js | 17 ++++++ app/templates/main/allocationTable.html | 73 ++++++++++++++++++++++++- 3 files changed, 91 insertions(+), 2 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index 23296bca0..a908d4f4a 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -26,4 +26,7 @@ .accordion{ padding-left: 5%; padding-right: 5%; +} +.table-striped tbody tr:nth-of-type(odd) { + background-color: #f2f2f2; /* Your custom color */ } \ No newline at end of file diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js index d8260015a..946a7a300 100644 --- a/app/static/js/allocationTable.js +++ b/app/static/js/allocationTable.js @@ -7,4 +7,21 @@ $(document).ready( function(){ searching: false, paging: false }); + + springTermTable = $('#springTermTable'); + springTermTable.DataTable({ + pageLength: 25, + info: false, + lengthChange: false, + searching: false, + paging: false + }); + breakTable = $('#breakTable'); + breakTable.DataTable({ + pageLength: 25, + info: false, + lengthChange: false, + searching: false, + paging: false + }); }); \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 46acb7c7a..f139dbc72 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -91,7 +91,41 @@

- Distinctio, architecto ab quasi aut eius inventore placeat natus voluptatem sit excepturi. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrimariesContractedAllocated
Total Primariesnot implemented{{allocations["totalPrimaries"]}}
10 Hournot implemented{{allocations["primary_10"]}}
12 Hournot implemented{{allocations["primary_12"]}}
15 Hournot implemented{{allocations["primary_15"]}}
20 Hournot implemented{{allocations["primary_20"]}}
@@ -108,7 +142,42 @@

- Distinctio, architecto ab quasi aut eius inventore placeat natus voluptatem sit excepturi. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrimariesContractedAllocated
Total Break Hoursnot implemented{{allocations["breakHours"]}}
Thanksgiving / Fall Breaknot implemented
Winter Breaknot implemented
Springnot implemented
Summer Breaknot implemented
From fd7c2c66920dac19bb1777ad84cbd61b12d98bb5 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 3 Aug 2026 09:16:23 -0400 Subject: [PATCH 039/128] removed an unecessary card from the HTML --- app/templates/main/allocationTable.html | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index f139dbc72..c287f994c 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -90,7 +90,6 @@

-
@@ -126,7 +125,6 @@

-
@@ -141,7 +139,6 @@

-
@@ -178,7 +175,6 @@

-
From 5c35d1d6cf70cfb5c3df03459d20c0e170dfac6f Mon Sep 17 00:00:00 2001 From: ACBerea Date: Mon, 3 Aug 2026 10:19:31 -0400 Subject: [PATCH 040/128] Finished a test suite for the makePositionDescriptionPDF() to ensure that the PDF works properly. Made minor syntax corrections to individualPositions.html and test_getPositions. --- app/templates/main/individualPositions.html | 2 +- tests/code/test_download.py | 103 ++++++++++++++++---- tests/code/test_getPositions.py | 1 + 3 files changed, 87 insertions(+), 19 deletions(-) diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index 893582f52..7ca511687 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -38,7 +38,7 @@

{{ department.DEPT_NAME }}

{{ position.revisionDate }}
Revised By:
-
{{ position.revisedBy or "Unknown" }}
+
{{ position.revisedBy }}
diff --git a/tests/code/test_download.py b/tests/code/test_download.py index 063aa350b..287a2fe1c 100644 --- a/tests/code/test_download.py +++ b/tests/code/test_download.py @@ -1,33 +1,100 @@ import io -from flask import g -from fpdf import FPDF + import pytest -from app.models import mainDB from app.logic.download import makePositionDescriptionPDF +from app.models import mainDB from app.models.department import Department from app.models.positionHistory import PositionHistory +from app.models.positionDescriptionSection import PositionDescriptionSection - +@pytest.mark.integration def test_makePositionDescriptionPDF(): + """ + Tests that makePositionDescriptionPDF generates valid PDF buffers + for positions with and without description sections. + """ with mainDB.atomic() as transaction: - dept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6742", ORG="2116", departmentCompliance=True, isActive=True) - position = PositionHistory.create(positionTitle="Lab Technician", - positionCode="S34516", - department=dept, - status="Active", - wls=3, - revisionDate="2023-01-01", - revisedBy="Jane Doe", - description="This is a test position description.") + # Create the department used by all positions in the test. + department = Department.create( + departmentID=200, + DEPT_NAME="Physics", + ACCOUNT="6742", + ORG="2116", + departmentCompliance=True, + isActive=True, + ) + + # Create a position that will have description sections. + positionWithSections = PositionHistory.create( + positionTitle="Lab Technician", + positionCode="S34516", + department=department, + status="Active", + wls=3, + revisionDate="2023-01-01", + revisedBy="Jane Doe", + ) + + # Create a position that will not have description sections. + positionWithoutSections = PositionHistory.create( + positionTitle="Research Assistant", + positionCode="S34517", + department=department, + status="Active", + wls=2, + revisionDate="2023-01-02", + revisedBy="Sarah Smith", + ) + + # Create sections for the first position. Insertion is out of order in order to test the section query's ordering logic is applied when the PDF is generated. + PositionDescriptionSection.create( + position=positionWithSections, + sectionTitle="Responsibilities", + sectionContent="Supports experiments and records results.", + order=2, + ) + + PositionDescriptionSection.create( + position=positionWithSections, + sectionTitle="Position Summary", + sectionContent="Maintains laboratory equipment.", + order=1, + ) + + # Generate a PDF for the position that has description sections. + pdfBufferWithSections = makePositionDescriptionPDF( + department, + positionWithSections, + ) + + # Verify that the result is a nonempty BytesIO containing a PDF. + assert isinstance(pdfBufferWithSections, io.BytesIO) + + pdfBytesWithSections = pdfBufferWithSections.getvalue() + + assert len(pdfBytesWithSections) > 0 + assert pdfBytesWithSections.startswith(b"%PDF-") + assert pdfBytesWithSections.rstrip().endswith(b"%%EOF") + + # Generate a PDF for the position without description sections. This exercises the "No description available." branch. + pdfBufferWithoutSections = makePositionDescriptionPDF( + department, + positionWithoutSections, + ) + + # Verify that the second result is also a valid PDF buffer. + assert isinstance(pdfBufferWithoutSections, io.BytesIO) - pdf_buffer = makePositionDescriptionPDF(dept, position) + pdfBytesWithoutSections = pdfBufferWithoutSections.getvalue() - assert isinstance(pdf_buffer, io.BytesIO) + assert len(pdfBytesWithoutSections) > 0 + assert pdfBytesWithoutSections.startswith(b"%PDF-") + assert pdfBytesWithoutSections.rstrip().endswith(b"%%EOF") - pdf_bytes = pdf_buffer.getvalue() - assert len(pdf_bytes) > 0 - assert pdf_bytes[:5] == b'%PDF-' + # Verify that the two different positions produce different PDFs. + assert pdfBytesWithSections != pdfBytesWithoutSections + # Roll back all database records created by this test. transaction.rollback() \ No newline at end of file diff --git a/tests/code/test_getPositions.py b/tests/code/test_getPositions.py index eff0d78ed..dc3e0b8a0 100644 --- a/tests/code/test_getPositions.py +++ b/tests/code/test_getPositions.py @@ -3,6 +3,7 @@ from app.models.department import Department from app.models.positionHistory import PositionHistory from app.logic.getPositions import * + @pytest.mark.integration def test_getActivePositions(): """ From a0ba238cfdde9199ab6f075e7f7d672c26d0f1aa Mon Sep 17 00:00:00 2001 From: munsakad Date: Mon, 3 Aug 2026 11:19:36 -0400 Subject: [PATCH 041/128] Polish the allocation card: dynamic Fall/Spring term label, "X of Y" wording, and aligned Primary/Secondary table Replace the raw "AY 2025-2026" term name with a computed current-semester label (e.g. "Fall 2025") derived from the term's own year and today's month, matching the Fall/Spring termCode convention used elsewhere. Reword ratios as "X of Y" instead of "X/Y", rename the card title to "Current Allocation", and rebuild the Primary/Secondary breakdown as a single table so every row (term info, headers, hour buckets, break hours) shares the same column alignment and stays legible down to mobile widths. --- app/controllers/main_routes/main_routes.py | 1 + app/logic/getAllocation.py | 19 ++++++++ app/static/css/departmentPortal.css | 53 ++++++++++++++-------- app/templates/main/departmentPortal.html | 52 +++++++++++++-------- tests/code/test_getAllocation.py | 25 +++++++++- 5 files changed, 113 insertions(+), 37 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 442d39722..5d9d160dd 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -91,6 +91,7 @@ def departmentPortal(org=None,account=None): allocated = allocation_summary["allocated"], used = allocation_summary["used"], term = recentTerm, + currentSemester = allocation_summary["current_semester"], usedPositions = allocation_summary["used_positions"], break_hours = allocation_summary["break_hours"], supervisors = supervisors, diff --git a/app/logic/getAllocation.py b/app/logic/getAllocation.py index 893da61bc..f5d5d281f 100644 --- a/app/logic/getAllocation.py +++ b/app/logic/getAllocation.py @@ -1,3 +1,5 @@ +from datetime import date + from peewee import fn from app.models.allocation import Allocation @@ -6,6 +8,21 @@ from app.models.formHistory import FormHistory +def getCurrentSemesterLabel(term): + """Return the current Fall/Spring semester label (e.g. "Fall 2025") for + the academic year that the given term belongs to. The season is picked + from today's month and the year comes from the term's own termCode, + following the AY/Fall/Spring termCode convention in termManagement.py + (AY code, code+11 = Fall of that year, code+12 = Spring of the next). + """ + if not term: + return None + academicYear = int(str(term.termCode)[:4]) + if date.today().month >= 8: + return f"Fall {academicYear}" + return f"Spring {academicYear + 1}" + + def countWorkers(department, term_code, job_type, hours_bucket): workerCount = ( LaborStatusForm.select() @@ -43,6 +60,7 @@ def getDepartmentAllocationSummary(department): """Return allocation-utilization values for a department's most recent term.""" result = { "term": None, + "current_semester": None, "allocated": 0, "used": 0, "used_positions": { @@ -65,6 +83,7 @@ def getDepartmentAllocationSummary(department): recentTerm = Term.order_by_term([a.termCode for a in departmentAllocations], reverse=True)[0] term_code = recentTerm.termCode result["term"] = recentTerm + result["current_semester"] = getCurrentSemesterLabel(recentTerm) total_positions = ( Allocation.select( diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index 9904ae8bc..e0b15ed9d 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -43,27 +43,31 @@ vertical-align: middle; color:#6e6e6e; } -.header-container { - display: flex; - justify-content: space-between; - align-items: center; - padding-left: 2%; +.allocation-table-wrapper { + overflow-x: auto; + margin: 10px 0; +} +.allocation-table { + width: 100%; + border-collapse: collapse; + font-size: 1.2em; +} +.allocation-table th, +.allocation-table td { + text-align: left; + white-space: nowrap; +} +.allocation-table th:first-child, +.allocation-table td:first-child { padding-right: 10px; } -.allocation-list { - display: flex; - justify-content: space-between; - align-items: center; - padding-left: 10px; - padding-right: 10px; -} -.primary-chart { - font-size: 1.2em; - padding-left: 7%; +.allocation-table th { + font-weight: 700; + padding-top: 10px; } -.secondary-chart { - font-size: 1.2em; - padding-right: 7%; +.allocation-table .term-row h4, +.allocation-table .break-row h4 { + margin: 10px 0; } .bi-people-fill { /* Bootstrap Icon for Members Card */ @@ -96,3 +100,16 @@ flex-direction: column; } } + +@media (min-width: 1200px) and (max-width: 1450px), (max-width: 480px) { + .allocation-table { + font-size: 1em; + } + .allocation-table td { + padding: 2px 5px; + } + .allocation-table .term-row h4, + .allocation-table .break-row h4 { + font-size: 0.9rem; + } +} diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 3e37d3b25..c800784c3 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -42,25 +42,41 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e
-

Allocations

+

Current Allocation

-
-

{{ term.termName if term else "No term data" }}

{{used}}/{{allocated or 0}} Positions

-
-
-
    -
  • 10 Hour - {{usedPositions.used_10}}/{{allocation.primary_10}}
  • -
  • 12 Hour - {{usedPositions.used_12}}/{{allocation.primary_12}}
  • -
  • 15 Hour - {{usedPositions.used_15}}/{{allocation.primary_15}}
  • -
  • 20 Hour - {{usedPositions.used_20}}/{{allocation.primary_20}}
  • -
-
    -
  • 5 Hour - {{usedPositions.used_5_sec}}/{{allocation.secondary_5}}
  • -
  • 10 Hour - {{usedPositions.used_10_sec}}/{{allocation.secondary_10}}
  • -
-
-
-

Break Hours

{{break_hours}}/{{allocation.breakHours or 0}} Hours

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

{{ currentSemester if currentSemester else "No term data" }}

{{used}} of {{allocated or 0}} Positions

PrimarySecondary
10 Hour - {{usedPositions.used_10}} of {{allocation.primary_10}}5 Hour - {{usedPositions.used_5_sec}} of {{allocation.secondary_5}}
12 Hour - {{usedPositions.used_12}} of {{allocation.primary_12}}10 Hour - {{usedPositions.used_10_sec}} of {{allocation.secondary_10}}
15 Hour - {{usedPositions.used_15}} of {{allocation.primary_15}}
20 Hour - {{usedPositions.used_20}} of {{allocation.primary_20}}

Break Hours

{{break_hours}} of {{allocation.breakHours or 0}} Hours

diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py index 3d9259066..82d137106 100644 --- a/tests/code/test_getAllocation.py +++ b/tests/code/test_getAllocation.py @@ -1,4 +1,5 @@ from datetime import date +from unittest.mock import patch import pytest from app.models import mainDB @@ -12,7 +13,7 @@ from app.models.historyType import HistoryType from app.models.status import Status from app.models.user import User -from app.logic.getAllocation import getDepartmentAllocationSummary, countWorkers, getBreakHours +from app.logic.getAllocation import getDepartmentAllocationSummary, countWorkers, getBreakHours, getCurrentSemesterLabel def _createFormHistory(form, statusName): @@ -30,6 +31,28 @@ def _createFormHistory(form, statusName): ) +def test_getCurrentSemesterLabel_none_term(): + assert getCurrentSemesterLabel(None) is None + + +def test_getCurrentSemesterLabel_fall(): + """A term whose termCode's academic year is 2025 should read as Fall 2025 + when today falls in the Aug-Dec half of the academic year.""" + term = Term(termCode=202500) + with patch("app.logic.getAllocation.date") as mockDate: + mockDate.today.return_value = date(2025, 9, 15) + assert getCurrentSemesterLabel(term) == "Fall 2025" + + +def test_getCurrentSemesterLabel_spring(): + """The same academic-year term should read as Spring 2026 when today + falls in the Jan-Jul half of the academic year.""" + term = Term(termCode=202500) + with patch("app.logic.getAllocation.date") as mockDate: + mockDate.today.return_value = date(2026, 2, 10) + assert getCurrentSemesterLabel(term) == "Spring 2026" + + @pytest.mark.integration def test_getDepartmentAllocationSummary_no_allocation(): """ From ddad2d27bf27f0d841d9e8902c677853347850ab Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 3 Aug 2026 11:22:14 -0400 Subject: [PATCH 042/128] added logic using the allocationManager --- app/controllers/main_routes/main_routes.py | 50 +++++++- app/logic/allocationManager.py | 129 +++++++++++++++++++++ app/static/js/allocationTable.js | 9 +- app/templates/main/allocationTable.html | 33 +++--- 4 files changed, 198 insertions(+), 23 deletions(-) create mode 100644 app/logic/allocationManager.py diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 9bacf0091..75f010caa 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -1,6 +1,7 @@ from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify from peewee import JOIN, DoesNotExist, fn from functools import reduce +from datetime import datetime, date import operator from app.models.department import Department @@ -23,6 +24,8 @@ from app.logic.banner import Banner from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions +from app.logic.allocationManager import * + @main_bp.route('/logout', methods=['GET']) @@ -91,8 +94,21 @@ def allocationTable(org=None, account=None): except (NameError, DoesNotExist): dept = None - currentTerm = Term.select().where(Term.termCode == 202500).get() #FIXME + if currentUser.isLaborAdmin: + pass + else: + departments = list(Department.select().join(SupervisorDepartment).where(SupervisorDepartment.supervisor == currentUser.supervisor).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) + currentDate = date.today() + print(f"current datae \n\n\n\n\n\n\n{str(currentDate)[:4]} \n\n\n") + currentAY = currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "00")).get() + + if currentDate.month >= 1 and currentDate.month <= 6: + pass + currentTerm = Term.select().where(Term.termCode == int(str(2025)[:4] + "12")).get() + else: + pass + currentTerm = Term.select().where(Term.termCode == int(str(2025)[:4] + "11")).get() allocationDict = {"primary_10": 1, "primary_12": 2, "primary_15": 3, @@ -103,12 +119,40 @@ def allocationTable(org=None, account=None): "totalPrimaries": 10, "totalSecondaries": 11, "totalAllocations": 21} + fallContracts = { + "used_10": 4, + "used_12": 4, + "used_15": 4, + "used_20": 4, + "used_5_sec": 4, + "used_10_sec": 4, + "used_total": 0, + "break_hours": "3" + } + springContracts = { + "used_10": 4, + "used_12": 4, + "used_15": 4, + "used_20": 4, + "used_5_sec": 4, + "used_10_sec": 4, + "used_total": 0, + "break_hours": "3" + } + breakContracts = { + "coolContract": getBreakContracts(202500, dept) + } + print(f"\n\n\n coolcontract {breakContracts}\n\n\n") + print("\n\n\n\n\n") print(allocationDict) return render_template('main/allocationTable.html', department = dept, - term = currentTerm, - allocations = allocationDict) + currentAY = currentAY, + allocations = allocationDict, + fallContracts = fallContracts, + springContracts = springContracts) + @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) def addUserToDept(): diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py new file mode 100644 index 000000000..3da0c6222 --- /dev/null +++ b/app/logic/allocationManager.py @@ -0,0 +1,129 @@ +from app.models.allocation import Allocation +from app.models.laborStatusForm import * +from app.models.department import * +from app.models.term import * +from app.models.formHistory import FormHistory +from peewee import JOIN + + +def getAllocation(termCode, dept): + academicYearCode = int(str(termCode)[:5] + "00") + allocationObject = Allocation.select().where( + Allocation.termCode.in_([termCode,academicYearCode]), + Allocation.department == dept, + Allocation.isFinal == True).dicts().get() + return allocationObject + + +def getAllocationNonFinal(termCode, dept): + academicYearCode = int(str(termCode)[:5] + "00") + allocationObject = Allocation.select().where( + Allocation.termCode.in_([termCode,academicYearCode]), + Allocation.department == dept, + Allocation.isFinal == False).dicts().get() + return allocationObject + + + +def getTotalAllocations(termCode, dept): + allocationObject = getAllocation(termCode, dept) + allocationDict = {"primary_10": allocationObject["primary_10"], + "primary_12": allocationObject["primary_12"], + "primary_15": allocationObject["primary_15"], + "primary_20": allocationObject["primary_20"], + "secondary_5": allocationObject["secondary_5"], + "secondary_10": allocationObject["secondary_10"], + "breakHours": allocationObject["breakHours"], + "totalPrimaries": (allocationObject["primary_10"] + allocationObject["primary_12"] + allocationObject["primary_15"] + allocationObject["primary_20"]), + "totalSecondaries": (allocationObject["secondary_5"] + allocationObject["secondary_10"]), + "totalAllocations": (allocationObject["primary_10"] + allocationObject["primary_12"] + allocationObject["primary_15"] + allocationObject["primary_20"] + allocationObject["secondary_5"] + allocationObject["secondary_10"] )} + return allocationDict + +def countContracts(jobType, weeklyContractHours, termCode, dept): + academicYearCode = int(str(termCode)[:5] + "00") + lsfCountPrimaries = FormHistory.select( + ).join(LaborStatusForm + ).join(Department + ).where( + FormHistory.historyType == "Labor Status Form", + FormHistory.status.in_(["Approved", "Pending", "Pre-Student Approval"]), + LaborStatusForm.termCode.in_([termCode,academicYearCode]), + LaborStatusForm.jobType == jobType, + LaborStatusForm.weeklyHours == weeklyContractHours, + Department.departmentID == dept, + ).count() + return lsfCountPrimaries + +def getContractedAllocations(termCode, dept): + academicYearCode = int(str(termCode)[:5] + "00") + allocationObject = getAllocation(termCode, dept) + break_allocation = FormHistory.select( + LaborStatusForm.department, + LaborStatusForm.termCode, + fn.SUM(LaborStatusForm.contractHours).alias('total_hours') + ).join( + LaborStatusForm, + on=(FormHistory.formID == LaborStatusForm.laborStatusFormID), + ).join( + Term, + on = (LaborStatusForm.termCode == Term.termCode ) + ).where( + (FormHistory.historyType == "Labor Status Form") & + (FormHistory.status == "Approved") & + (LaborStatusForm.termCode.in_([termCode,academicYearCode])) + ).group_by( + LaborStatusForm.department, + LaborStatusForm.termCode).dicts() + + breakSum = {"total_hours": 0} + if dept: + for row in break_allocation: + if row["department"] == dept: + breakSum = row + break + + usedPositions = { + "used_10": countContracts("Primary", "10", termCode, dept), + "used_12": countContracts("Primary", "12", termCode, dept), + "used_15": countContracts("Primary", "15", termCode, dept), + "used_20": countContracts("Primary", "20", termCode, dept), + "used_5_sec": countContracts("Secondary", "5", termCode, dept), + "used_10_sec": countContracts("Secondary", "10", termCode, dept), + "used_total": 0, + "break_hours": breakSum["total_hours"] + } + usedPositions["used_total"] = sum(list(usedPositions.values())[:7]) + return usedPositions + +def getBreakContracts(termCode, dept): + academicYearCode = (str(termCode)[:4]) + break_allocaiton = FormHistory.select( + ).join(LaborStatusForm + ).join(Department + ).where( + FormHistory.historyType == "Labor Status Form", + FormHistory.status.in_(["Approved", "Pending", "Pre-Student Approval"]), + LaborStatusForm.termCode == [termCode], + LaborStatusForm.weeklyHours == None, + Department.departmentID == dept.departmentID, + ).count() + + + # break_allocation = FormHistory.select( + # LaborStatusForm.department, + # LaborStatusForm.termCode, + # fn.SUM(LaborStatusForm.contractHours).alias('total_hours') + # ).join( + # LaborStatusForm, + # on=(FormHistory.formID == LaborStatusForm.laborStatusFormID), + # ).join( + # Term, + # on = (LaborStatusForm.termCode == Term.termCode ) + # ).where( + # (FormHistory.historyType == "Labor Status Form") & + # (FormHistory.status == "Approved") & + # (LaborStatusForm.termCode.in_([termCode])) + # ).group_by( + # LaborStatusForm.department, + # LaborStatusForm.termCode).dicts() + return break_allocaiton \ No newline at end of file diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js index 946a7a300..bd3a5b910 100644 --- a/app/static/js/allocationTable.js +++ b/app/static/js/allocationTable.js @@ -5,7 +5,8 @@ $(document).ready( function(){ info: false, lengthChange: false, searching: false, - paging: false + paging: false, + "order": [] }); springTermTable = $('#springTermTable'); @@ -14,7 +15,8 @@ $(document).ready( function(){ info: false, lengthChange: false, searching: false, - paging: false + paging: false, + "order": [] }); breakTable = $('#breakTable'); breakTable.DataTable({ @@ -22,6 +24,7 @@ $(document).ready( function(){ info: false, lengthChange: false, searching: false, - paging: false + paging: false, + "order": [] }); }); \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index c287f994c..97310db71 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -23,8 +23,7 @@
*Position allocations are placed (contracted/alloca
-

Current Term: {{term.termName}}

-

Total AY Positions: {{allocations['totalAllocations']}}

+

Current Term: {{currentAY.termName}}

@@ -36,7 +35,7 @@

Total AY Positions: {{allocations['totalAllocations']}}

@@ -52,26 +51,26 @@

Total Primaries - not implemented + {{fallContracts["used_total"]}} {{allocations["totalPrimaries"]}} 10 Hour - not implemented + {{fallContracts["used_10"]}} {{allocations["primary_10"]}} 12 Hour - not implemented + {{fallContracts["used_12"]}} {{allocations["primary_12"]}} 15 Hour - not implemented + {{fallContracts["used_15"]}} {{allocations["primary_15"]}} 20 Hour - not implemented + {{fallContracts["used_20"]}} {{allocations["primary_20"]}} @@ -85,7 +84,7 @@

@@ -101,26 +100,26 @@

Total Primaries - not implemented + {{springContracts["used_total"]}} {{allocations["totalPrimaries"]}} 10 Hour - not implemented + {{springContracts["used_10"]}} {{allocations["primary_10"]}} 12 Hour - not implemented + {{springContracts["used_12"]}} {{allocations["primary_12"]}} - 15 Hour - not implemented + 15 Hour + {{springContracts["used_15"]}} {{allocations["primary_15"]}} - 20 Hour - not implemented + 20 Hour + {{springContracts["used_20"]}} {{allocations["primary_20"]}} @@ -134,7 +133,7 @@

From 45b13e0b291f554f1d137a257c6118dbe6b7d7c4 Mon Sep 17 00:00:00 2001 From: munsakad Date: Mon, 3 Aug 2026 14:35:07 -0400 Subject: [PATCH 043/128] Fix duplicate main.managePositions endpoint left over from the department-portal-base merge The base branch (department-portal-base) moved managePositions out of main_routes.py into its own departmentPortal.py with proper permission checks (commit ee19d87e), but merging that branch in and accepting both sides left the old, now-dead copy in main_routes.py alongside the new file, so Flask registered two view functions under the same endpoint name and crashed on startup with "View function mapping is overwriting an existing endpoint function: main.managePositions". Remove the stale duplicate (it also referenced an unimported Tracy class) and the duplicate departmentPortal import in __init__.py from the same merge. --- app/controllers/main_routes/__init__.py | 1 - app/controllers/main_routes/main_routes.py | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/app/controllers/main_routes/__init__.py b/app/controllers/main_routes/__init__.py index 5ab0bf423..e8c4c6bb0 100755 --- a/app/controllers/main_routes/__init__.py +++ b/app/controllers/main_routes/__init__.py @@ -26,4 +26,3 @@ def injectGlobalData(): from app.controllers.main_routes import search from app.controllers.main_routes import studentResponse from app.controllers.main_routes import departmentPortal -from app.controllers.main_routes import departmentPortal diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 5d9d160dd..e9ec85e8f 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -100,20 +100,6 @@ def departmentPortal(org=None,account=None): positions = positionsList, posURL = posURL, ) -@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 From 74bdd3ae687c6e45ae9c35a12cd38587ef35618d Mon Sep 17 00:00:00 2001 From: munsakad Date: Mon, 3 Aug 2026 15:03:24 -0400 Subject: [PATCH 044/128] Rework allocation rows to the "N hr: X contracts (out of Y allocations)" format and drop Break Hours Match the supervisor's whiteboard mockup: each Primary/Secondary hour bucket now reads as two lines ("10 hr: 2 contracts" / "(out of 5 allocations)") with correct singular/plural wording, via a small Jinja macro to avoid repeating the format six times. Break Hours is removed from the card entirely. Column alignment (left edges shared across the term row, headers, and hour rows) is unchanged, and the pinch-zone media query is retuned for the new, longer per-row text so nothing clips or overflows at narrow widths. --- app/static/css/departmentPortal.css | 16 ++++++---------- app/templates/main/departmentPortal.html | 19 +++++++++---------- 2 files changed, 15 insertions(+), 20 deletions(-) diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index e0b15ed9d..b04f98ad1 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -56,17 +56,14 @@ .allocation-table td { text-align: left; white-space: nowrap; -} -.allocation-table th:first-child, -.allocation-table td:first-child { - padding-right: 10px; + padding: 4px 10px 4px 0; + line-height: 1.3; } .allocation-table th { font-weight: 700; padding-top: 10px; } -.allocation-table .term-row h4, -.allocation-table .break-row h4 { +.allocation-table .term-row h4 { margin: 10px 0; } @@ -103,13 +100,12 @@ @media (min-width: 1200px) and (max-width: 1450px), (max-width: 480px) { .allocation-table { - font-size: 1em; + font-size: 0.85em; } .allocation-table td { - padding: 2px 5px; + padding: 2px 5px 2px 0; } - .allocation-table .term-row h4, - .allocation-table .break-row h4 { + .allocation-table .term-row h4 { font-size: 0.9rem; } } diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 41eab4f91..b6178bf99 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -44,6 +44,9 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e

Current Allocation

+ {% macro allocationCell(hours, used, allocated) -%} + {{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }}
(out of {{ allocated }} allocation{{ 's' if allocated != 1 else '' }}) + {%- endmacro %}
@@ -56,25 +59,21 @@

Current Allocation

- - + {{ allocationCell(10, usedPositions.used_10, allocation.primary_10) }} + {{ allocationCell(5, usedPositions.used_5_sec, allocation.secondary_5) }} - - + {{ allocationCell(12, usedPositions.used_12, allocation.primary_12) }} + {{ allocationCell(10, usedPositions.used_10_sec, allocation.secondary_10) }} - + {{ allocationCell(15, usedPositions.used_15, allocation.primary_15) }} - + {{ allocationCell(20, usedPositions.used_20, allocation.primary_20) }} - - - -
Secondary
10 Hour - {{usedPositions.used_10}} of {{allocation.primary_10}}5 Hour - {{usedPositions.used_5_sec}} of {{allocation.secondary_5}}
12 Hour - {{usedPositions.used_12}} of {{allocation.primary_12}}10 Hour - {{usedPositions.used_10_sec}} of {{allocation.secondary_10}}
15 Hour - {{usedPositions.used_15}} of {{allocation.primary_15}}
20 Hour - {{usedPositions.used_20}} of {{allocation.primary_20}}

Break Hours

{{break_hours}} of {{allocation.breakHours or 0}} Hours

From eb4e0af105fdc633365cf1544de4022b719b3ae0 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 3 Aug 2026 15:28:09 -0400 Subject: [PATCH 045/128] Added break contracts to the table --- app/controllers/main_routes/main_routes.py | 22 ++-- app/logic/allocationManager.py | 13 +-- app/templates/main/allocationTable.html | 12 +-- database/demo_data.py | 119 ++++++++++++++++++++- 4 files changed, 140 insertions(+), 26 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 75f010caa..607479166 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -103,12 +103,14 @@ def allocationTable(org=None, account=None): print(f"current datae \n\n\n\n\n\n\n{str(currentDate)[:4]} \n\n\n") currentAY = currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "00")).get() + # currentAY = currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "00")).get() + if currentDate.month >= 1 and currentDate.month <= 6: pass - currentTerm = Term.select().where(Term.termCode == int(str(2025)[:4] + "12")).get() + currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "12")).get() else: pass - currentTerm = Term.select().where(Term.termCode == int(str(2025)[:4] + "11")).get() + currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "11")).get() allocationDict = {"primary_10": 1, "primary_12": 2, "primary_15": 3, @@ -140,18 +142,20 @@ def allocationTable(org=None, account=None): "break_hours": "3" } breakContracts = { - "coolContract": getBreakContracts(202500, dept) - } - print(f"\n\n\n coolcontract {breakContracts}\n\n\n") - - print("\n\n\n\n\n") - print(allocationDict) + "thanksgiving":getBreakContracts(202201, dept),#FIXME + "winter": getBreakContracts(202202, dept), + "spring": getBreakContracts(202203, dept), + "fall":getBreakContracts(202204, dept), + "summer": getBreakContracts(202213, dept) + } + return render_template('main/allocationTable.html', department = dept, currentAY = currentAY, allocations = allocationDict, fallContracts = fallContracts, - springContracts = springContracts) + springContracts = springContracts, + breakContracts = breakContracts) @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index 3da0c6222..a9935920e 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -3,7 +3,7 @@ from app.models.department import * from app.models.term import * from app.models.formHistory import FormHistory -from peewee import JOIN +from peewee import JOIN, fn def getAllocation(termCode, dept): @@ -97,17 +97,14 @@ def getContractedAllocations(termCode, dept): def getBreakContracts(termCode, dept): academicYearCode = (str(termCode)[:4]) - break_allocaiton = FormHistory.select( + break_allocaiton = FormHistory.select(fn.SUM(LaborStatusForm.contractHours) ).join(LaborStatusForm - ).join(Department ).where( FormHistory.historyType == "Labor Status Form", FormHistory.status.in_(["Approved", "Pending", "Pre-Student Approval"]), - LaborStatusForm.termCode == [termCode], - LaborStatusForm.weeklyHours == None, - Department.departmentID == dept.departmentID, - ).count() - + LaborStatusForm.termCode == termCode, + LaborStatusForm.department == dept, + LaborStatusForm.weeklyHours == None).scalar() # break_allocation = FormHistory.select( # LaborStatusForm.department, diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 97310db71..03aa702aa 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -149,27 +149,27 @@

Total Break Hours - not implemented + {{breakContracts['total_hours']}} {{allocations["breakHours"]}} Thanksgiving / Fall Break - not implemented + {{breakContracts['thanksgiving']}} Winter Break - not implemented + {{breakContracts['winter']}} Spring - not implemented + {{breakContracts['spring']}} - Summer Break - not implemented + Summer Term + {{breakContracts['summer']}} diff --git a/database/demo_data.py b/database/demo_data.py index 54910f412..e9e45d6e0 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -40,8 +40,37 @@ "STU_CPO":"700", "LAST_POSN":"Media Technician", "LAST_SUP_PIDM":"7" + }, + { + "ID":"B00741361", + "PIDM":"99", + "FIRST_NAME":"Antonia", + "LAST_NAME":"Schmith", + "CLASS_LEVEL":"Freshman", + "ACADEMIC_FOCUS":"Computer Science", + "MAJOR":"Computer Science", + "PROBATION":"0", + "ADVISOR":"Scott Heggen", + "STU_EMAIL":"schmitha@berea.edu", + "STU_CPO":"777", + "LAST_POSN":"TA", + "LAST_SUP_PIDM":"7" + }, + { + "ID":"B00732363", + "PIDM":"58", + "FIRST_NAME":"Barbara", + "LAST_NAME":"Williams", + "CLASS_LEVEL":"Junior", + "ACADEMIC_FOCUS":"Computer Science", + "MAJOR":"Computer Science", + "PROBATION":"0", + "ADVISOR":"Jasmine Jones", + "STU_EMAIL":"williamsb@berea.edu", + "STU_CPO":"118", + "LAST_POSN":"TA", + "LAST_SUP_PIDM":"7" }, - { "ID":"B00730361", "PIDM":"1", @@ -104,7 +133,16 @@ "LAST_POSN":"Student Manager", "LAST_SUP_PIDM":"7" }, - ] + {"ID": "B00811617", "legal_name": "Chris Georgiev", "isActive": True, "PIDM": "8", "FIRST_NAME": "Chris", "LAST_NAME": "Georgiev"}, + {"ID": "B00815474", "legal_name": "Julius Fritz", "isActive": True, "PIDM": "9", "FIRST_NAME": "Julius", "LAST_NAME": "Fritz"}, + {"ID": "B12345223", "legal_name": "Subaru Natsuki", "isActive": True, "PIDM": "10", "FIRST_NAME": "Subaru", "LAST_NAME": "Natsuki"}, + {"ID": "B12345003", "legal_name": "Hatsune Miku", "isActive": True, "PIDM": "11", "FIRST_NAME": "Hatsune", "LAST_NAME": "Miku"}, + {"ID": "B12345772", "legal_name": "Michael Jackson", "isActive": True, "PIDM": "12", "FIRST_NAME": "Michael", "LAST_NAME": "Jackson"}, + {"ID": "B12345756", "legal_name": "Genji Overwatch", "isActive": True, "PIDM": "13", "FIRST_NAME": "Genji", "LAST_NAME": "Overwatch"}, + {"ID": "B12345759", "legal_name": "Mister Marlowe", "isActive": True, "PIDM": "14", "FIRST_NAME": "Mister", "LAST_NAME": "Marlowe"}, + {"ID": "B11231123", "legal_name": "Mister Thanksgiving", "isActive": True, "PIDM": "15", "FIRST_NAME": "Mister", "LAST_NAME": "Thanksgiving"} + + ] tracyStudents = [ { "ID":"B00785329", @@ -641,7 +679,82 @@ "createdBy_id": 1, "createdDate": f"2025-04-14", "status_id": "Approved" - }]).on_conflict_replace().execute() + }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + + "laborStatusFormID": 9, + "termCode_id": f"202500", + "studentName": "Genji Overwatch", + "studentSupervisee_id": "B12345756", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "overwtahc guy", + "POSN_CODE": "S61410", + "contractHours": 15, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + + }]).on_conflict_replace().execute() +FormHistory.insert([{ + "formHistoryID": 9, + "formID_id": "9", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + + "laborStatusFormID": 60, + "termCode_id": f"202500", + "studentName": "Mister Marlowe", + "studentSupervisee_id": "B12345759", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Break Worker", + "POSN_CODE": "S61412", + "contractHours": 400, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + + }]).on_conflict_replace().execute() +FormHistory.insert([{ + "formHistoryID": 60, + "formID_id": "60", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + + "laborStatusFormID": 61, + "termCode_id": f"202501", + "studentName": "Mister Thanksgiving", + "studentSupervisee_id": "B11231123", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Thanksgiving Worker", + "POSN_CODE": "S61412", + "contractHours": 50, + "startDate": f"2025-11-23", + "endDate": "2025-12-01" + + }]).on_conflict_replace().execute() +FormHistory.insert([{ + "formHistoryID": 61, + "formID_id": "61", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-11-01", + "status_id": "Approved" + }]).on_conflict_replace().execute() From e9a48ed7f7d1ab44234261902e5f2ab25bd305bd Mon Sep 17 00:00:00 2001 From: ACBerea Date: Mon, 3 Aug 2026 16:24:50 -0400 Subject: [PATCH 046/128] Fixed issues regarding a sidebar appearing on the x-axis of the page by fixing the width of the sidebar in base.css and sidebar.css. Reformatted demo_data.py for all Position Description Sections. Linked the view button on the managePositions.html to the individual position page of the selected position. Improved download.py logic to account for HTML elements implemented in demo_data.py. --- app/logic/download.py | 131 ++++++++++++++++++-- app/static/css/base.css | 1 - app/static/css/individualPositions.css | 59 +++++++-- app/static/css/sidebar.css | 2 +- app/templates/main/individualPositions.html | 6 +- app/templates/main/managePositions.html | 5 +- database/demo_data.py | 64 +++++----- 7 files changed, 213 insertions(+), 55 deletions(-) diff --git a/app/logic/download.py b/app/logic/download.py index ecac9436e..a7b164bf4 100644 --- a/app/logic/download.py +++ b/app/logic/download.py @@ -31,15 +31,98 @@ def retrieveFormSearchResult(formSearchResultId): return None +import html +import io + +from html.parser import HTMLParser +from fpdf import FPDF + + +class PDFHTMLTextExtractor(HTMLParser): + """ + Converts simple stored HTML into plain text suitable for FPDF. + """ + + blockTags = { + 'p', + 'div', + 'section', + 'article', + 'header', + 'footer', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'li', + 'ul', + 'ol', + 'br', + } + + def __init__(self): + super().__init__() + self.parts = [] + + def handle_starttag(self, tag, attrs): + tag = tag.lower() + + if tag == 'br': + self.parts.append('\n') + elif tag == 'li': + self.parts.append('\n• ') + + def handle_endtag(self, tag): + if tag.lower() in self.blockTags: + self.parts.append('\n') + + def handle_data(self, data): + self.parts.append(data) + + def getText(self): + text = ''.join(self.parts) + text = html.unescape(text) + + lines = [] + for line in text.splitlines(): + cleanedLine = ' '.join(line.split()) + + if cleanedLine: + lines.append(cleanedLine) + elif lines and lines[-1] != '': + lines.append('') + + return '\n'.join(lines).strip() + + +def removeHTML(value): + if value is None: + return '' + + parser = PDFHTMLTextExtractor() + parser.feed(str(value)) + parser.close() + + return parser.getText() + + def makePositionDescriptionPDF(department, position): - ''' - Builds a PDF of a position's description for the download button on the individual position page - ''' + """ + Builds a PDF of a position's description for the download button + on the individual position page. + """ pdf = FPDF() pdf.add_page() pdf.set_font('Times', 'BU', 16) - pdf.cell(0, 10, position.positionTitle, ln=True) + pdf.cell( + 0, + 10, + removeHTML(position.positionTitle), + ln=True, + ) pdf.ln(2) fields = [ @@ -48,33 +131,63 @@ def makePositionDescriptionPDF(department, position): ('WLS Level', position.wls), ('Status', position.status), ('Last Revision Date', position.revisionDate), - ('Revised By', position.revisedBy or 'Unknown'), + ('Revised By', position.revisedBy), ] + labelWidth = 45 + for label, value in fields: pdf.set_font('Times', 'B', 11) pdf.cell(labelWidth, 8, f'{label}:', ln=False) + pdf.set_font('Times', '', 11) - pdf.cell(0, 8, f' {value}', ln=True) + + plainValue = removeHTML(value) + plainValue = plainValue.encode( + 'latin-1', + 'replace', + ).decode('latin-1') + + pdf.cell(0, 8, f' {plainValue}', ln=True) sections = getPositionDescriptionSections(position) pdf.ln(4) + if sections: for section in sections: + title = removeHTML(section.sectionTitle) + content = removeHTML(section.sectionContent) + + title = title.encode( + 'latin-1', + 'replace', + ).decode('latin-1') + + content = content.encode( + 'latin-1', + 'replace', + ).decode('latin-1') + pdf.set_font('Times', 'B', 12) - pdf.cell(0, 10, section.sectionTitle, ln=True) + pdf.multi_cell(0, 10, title) + pdf.set_font('Times', '', 11) - content = section.sectionContent.encode('latin-1', 'replace').decode('latin-1') pdf.multi_cell(0, 7, content) + pdf.ln(2) else: pdf.set_font('Times', 'B', 12) pdf.cell(0, 10, 'Description', ln=True) + pdf.set_font('Times', '', 11) pdf.multi_cell(0, 7, 'No description available.') - pdfBytes = pdf.output(dest='S').encode('latin-1', 'replace') + pdfBytes = pdf.output(dest='S').encode( + 'latin-1', + 'replace', + ) + return io.BytesIO(pdfBytes) diff --git a/app/static/css/base.css b/app/static/css/base.css index a962b5494..7ff45dffc 100755 --- a/app/static/css/base.css +++ b/app/static/css/base.css @@ -126,7 +126,6 @@ a { box-sizing: border-box; left: 280px; right: 0px; - width: calc(100vw - 280px); margin-right: 280px; } diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index 39db95de0..1cb8cc354 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -13,7 +13,7 @@ .download-btn { position: absolute; top: 0; - right: 5rem; + right: 15px; } /* Metadata list (dt / dd spacing) */ @@ -28,39 +28,84 @@ color: #444; text-align: left; font-size: 2rem; - } .description-header { font-weight: 700; } -/* Review and improve this later. (Also move it to a more appropriate location) */ .position-container { margin-top: 2rem; } /* Description section card */ .position-description { - background: #fff; - border: 1px solid #e6e6e6; padding: 1rem; - border-radius: 6px; - box-shadow: 0 1px 2px rgba(0,0,0,0.03); text-align: left; margin-top: 0; margin-bottom: 0.75rem; font-size: 1.25rem; white-space: pre-line; + overflow-wrap: break-word; + word-wrap: break-word; } .description-content { margin: 0; padding: 0; + overflow-wrap: break-word; + word-wrap: break-word; +} + +/* Individual description sections */ +.section-title { + margin-top: 20px; + margin-bottom: 10px; + font-weight: 700; + overflow-wrap: break-word; + word-wrap: break-word; +} + +.section-title:first-child { + margin-top: 0; } /* Utility float class used across the app */ .floatright { float: right; margin-left: 0.5rem; +} + +/* Mobile layout */ +@media (max-width: 750px) { + .download-btn { + position: static; + display: table; + max-width: 100%; + margin: 0 auto 20px; + white-space: normal; + } + + .department-header { + margin-bottom: 30px; + font-size: 28px; + } + + .position-information dl.row dt, + .position-information dl.row dd { + font-size: 1.6rem; + } + + .position-information dl.row dd { + margin-bottom: 15px; + } + + .position-description { + font-size: 1.2rem; + } + + .floatright { + float: none; + margin-left: 0; + } } \ No newline at end of file diff --git a/app/static/css/sidebar.css b/app/static/css/sidebar.css index cb062a9fe..500cf74d9 100644 --- a/app/static/css/sidebar.css +++ b/app/static/css/sidebar.css @@ -65,7 +65,7 @@ .sidebar-push { left: 0 !important; - width: 100vw !important; + width: 100% !important; margin-right: 0 !important; } diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index 7ca511687..a1162eb90 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -18,7 +18,7 @@

{{ department.DEPT_NAME }}

-
+
@@ -46,8 +46,8 @@

Description

{%- if sections %} {%- for section in sections %} -

{{ section.sectionTitle }}

-
{{ section.sectionContent }}
+

{{ section.sectionTitle |safe }}

+
{{ section.sectionContent |safe }}
{%- endfor %} {%- else %}

No description available.

diff --git a/app/templates/main/managePositions.html b/app/templates/main/managePositions.html index 1d6dd8c60..81b139d45 100644 --- a/app/templates/main/managePositions.html +++ b/app/templates/main/managePositions.html @@ -29,11 +29,12 @@

{{ department_name }} Positions

{% for position in positions %} - {{ position.positionTitle }} {{ position.positionCode }} + {{ position.positionTitle }} {{ position.positionCode }} {{ position.wls }} {{ position.revisionDate }} - + View diff --git a/database/demo_data.py b/database/demo_data.py index 96d68cc6a..f7b64deab 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -950,98 +950,98 @@ positionDescriptionSections = [ { "position": 2, - "sectionTitle": 'WLS Level Justification', - "sectionContent": 'This position is assigned WLS 2 because it supports key research work with moderate technical complexity.', + "sectionTitle": '

WLS Level Justification

', + "sectionContent": '

This position is assigned WLS 2 because it supports key research work with moderate technical complexity.

', "order": 1, }, { "position": 2, - "sectionTitle": 'Description of Duties', - "sectionContent": 'Provide research assistance, coordinate data collection, and help prepare reports.', + "sectionTitle": '

Description of Duties

', + "sectionContent": '

Provide research assistance, coordinate data collection, and help prepare reports.

', "order": 2, }, { "position": 2, - "sectionTitle": 'Learning Opportunities', - "sectionContent": 'Gain experience with research practices, data management, and academic collaboration.', + "sectionTitle": '

Learning Opportunities

', + "sectionContent": '

Gain experience with research practices, data management, and academic collaboration.

', "order": 3, }, { "position": 2, - "sectionTitle": 'Required Qualifications', - "sectionContent": 'Strong communication skills, attention to detail, and ability to work independently.', + "sectionTitle": '

Required Qualifications

', + "sectionContent": '

Strong communication skills, attention to detail, and ability to work independently.

', "order": 4, }, { "position": 3, - "sectionTitle": 'WLS Level Justification', - "sectionContent": 'Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.', + "sectionTitle": '

WLS Level Justification

', + "sectionContent": '

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

', "order": 1, }, { "position": 3, - "sectionTitle": 'Description of Duties', - "sectionContent": 'A. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.', + "sectionTitle": '

Description of Duties

', + "sectionContent": '

A. Workplace Responsibility

\n\n

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

\n\n

B. Communication

\n\n

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

\n\n

C. Teamwork & Collaboration

\n\n

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

\n\n

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

\n\n

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

\n\n

E. Utilize Technology Effectively in the Workplace

\n\n

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

\n\n

F. Connect Work Experience to Career and Academic Goals

\n\n

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

\n\n

G. Foster Creativity and Innovation in the Workplace

\n\n

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

', "order": 2, }, { "position": 3, - "sectionTitle": 'Learning Opportunities', - "sectionContent": 'List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)', + "sectionTitle": '

Learning Opportunities

', + "sectionContent": '

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

\n\n

A. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

\n\n

B. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4) C. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3) D. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5) E. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

', "order": 3, }, { "position": 3, - "sectionTitle": 'Required Qualifications', - "sectionContent": 'List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.\n\nA. Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.\nB. Ability to take advice and respond appropriately.\nC. A desire to mentor and work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software and debugging.', + "sectionTitle": '

Required Qualifications

', + "sectionContent": '

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

\n\n

A. Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

\n\n

B. Ability to take advice and respond appropriately. C. A desire to mentor and work with high school students. D. Patience working with unskilled yet energetic high school students. E. Some basic understanding of software and debugging.

', "order": 4, }, { "position": 4, - "sectionTitle": 'WLS Level Justification', - "sectionContent": 'Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.', + "sectionTitle": '

WLS Level Justification

', + "sectionContent": '

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

', "order": 1, }, { "position": 4, - "sectionTitle": 'Description of Duties', - "sectionContent": 'A. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.', + "sectionTitle": '

Description of Duties

', + "sectionContent": '

A. Workplace Responsibility

\n\n

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

\n\n

B. Communication

\n\n

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

\n\n

C. Teamwork & Collaboration

\n\n

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

\n\n

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

\n\n

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

\n\n

E. Utilize Technology Effectively in the Workplace

\n\n

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

\n\n

F. Connect Work Experience to Career and Academic Goals

\n\n

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

\n\n

G. Foster Creativity and Innovation in the Workplace

\n\n

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

', "order": 2, }, { "position": 4, - "sectionTitle": 'Learning Opportunities', - "sectionContent": 'List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)', + "sectionTitle": '

Learning Opportunities

', + "sectionContent": '

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

\n\n

A. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

\n\n

B. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4) C. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3) D. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5) E. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

', "order": 3, }, { "position": 4, - "sectionTitle": 'Required Qualifications', - "sectionContent": 'List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.\n\nA. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.\nB. Ability to take advice respond appropriately.\nC. A desire to mentor work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software debugging.', + "sectionTitle": '

Required Qualifications

', + "sectionContent": '

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.

\n\n

A. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.

\n\n

B. Ability to take advice respond appropriately. C. A desire to mentor work with high school students. D. Patience working with unskilled yet energetic high school students. E. Some basic understanding of software debugging.

', "order": 4, }, { "position": 5, - "sectionTitle": 'WLS Level Justification', - "sectionContent": 'Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.', + "sectionTitle": '

WLS Level Justification

', + "sectionContent": '

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

', "order": 1, }, { "position": 5, - "sectionTitle": 'Description of Duties', - "sectionContent": 'A. Workplace Responsibility\nFollow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.\n\nB. Communication\nAssist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.\n\nC. Teamwork & Collaboration\nIn collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.\n\nD. Apply Critical Thinking and Problem Solving in Workplace Tasks\nAttend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.\n\nE. Utilize Technology Effectively in the Workplace\nIn collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.\n\nF. Connect Work Experience to Career and Academic Goals\nTrain themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).\n\nG. Foster Creativity and Innovation in the Workplace\nHelp high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.', + "sectionTitle": '

Description of Duties

', + "sectionContent": '

A. Workplace Responsibility

\n\n

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

\n\n

B. Communication

\n\n

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

\n\n

C. Teamwork & Collaboration

\n\n

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

\n\n

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

\n\n

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

\n\n

E. Utilize Technology Effectively in the Workplace

\n\n

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

\n\n

F. Connect Work Experience to Career and Academic Goals

\n\n

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

\n\n

G. Foster Creativity and Innovation in the Workplace

\n\n

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

', "order": 2, }, { "position": 5, - "sectionTitle": 'Learning Opportunities', - "sectionContent": 'List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.\n\nA. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)\nB. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)\nC. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)\nD. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)\nE. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)', + "sectionTitle": '

Learning Opportunities

', + "sectionContent": '

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

\n\n

A. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

\n\n

B. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4) C. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3) D. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5) E. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

', "order": 3, }, { "position": 5, - "sectionTitle": 'Required Qualifications', - "sectionContent": 'List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.\n\nA. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.\nB. Ability to take advice respond appropriately.\nC. A desire to mentor work with high school students.\nD. Patience working with unskilled yet energetic high school students.\nE. Some basic understanding of software debugging.', + "sectionTitle": '

Required Qualifications

', + "sectionContent": '

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.

\n\n

A. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.

\n\n

B. Ability to take advice respond appropriately. C. A desire to mentor work with high school students. D. Patience working with unskilled yet energetic high school students. E. Some basic understanding of software debugging.

', "order": 4, }, ] From 9eaf06176eba5a0d1cf9611e7862fb0647d433e9 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 3 Aug 2026 16:56:17 -0400 Subject: [PATCH 047/128] fixed function calls, added more data for breaks --- app/controllers/main_routes/main_routes.py | 28 +- database/demo_data.py | 314 ++++++++++++++++++++- 2 files changed, 326 insertions(+), 16 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 607479166..4a3001089 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -99,18 +99,15 @@ def allocationTable(org=None, account=None): else: departments = list(Department.select().join(SupervisorDepartment).where(SupervisorDepartment.supervisor == currentUser.supervisor).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) - currentDate = date.today() - print(f"current datae \n\n\n\n\n\n\n{str(currentDate)[:4]} \n\n\n") - currentAY = currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "00")).get() + currentDate = str(date.today()) - # currentAY = currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "00")).get() - - if currentDate.month >= 1 and currentDate.month <= 6: - pass - currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "12")).get() + if currentDate.month <= 6: + currentTerm = Term.select().where(Term.termCode == currentDate[:4] + "12").get() + currentAY = currentTerm = Term.select().where(Term.termCode == currentDate[:4] + "00").get() else: - pass - currentTerm = Term.select().where(Term.termCode == int(str(currentDate)[:4] + "11")).get() + currentTerm = Term.select().where(Term.termCode == currentDate[:4] + "11").get() + currentAY = Term.select().where(Term.termCode == currentDate[:4] + "00").get() + allocationDict = {"primary_10": 1, "primary_12": 2, "primary_15": 3, @@ -121,6 +118,7 @@ def allocationTable(org=None, account=None): "totalPrimaries": 10, "totalSecondaries": 11, "totalAllocations": 21} + # fallContracts = getContractedAllocations(currentTerm, dept) fallContracts = { "used_10": 4, "used_12": 4, @@ -142,11 +140,11 @@ def allocationTable(org=None, account=None): "break_hours": "3" } breakContracts = { - "thanksgiving":getBreakContracts(202201, dept),#FIXME - "winter": getBreakContracts(202202, dept), - "spring": getBreakContracts(202203, dept), - "fall":getBreakContracts(202204, dept), - "summer": getBreakContracts(202213, dept) + "thanksgiving":getBreakContracts(202401, dept),#FIXME + "winter": getBreakContracts(202402, dept), + "spring": getBreakContracts(202403, dept), + "fall":getBreakContracts(202404, dept), + "summer": getBreakContracts(202413, dept) } return render_template('main/allocationTable.html', diff --git a/database/demo_data.py b/database/demo_data.py index e9e45d6e0..7ad8493fe 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -140,7 +140,17 @@ {"ID": "B12345772", "legal_name": "Michael Jackson", "isActive": True, "PIDM": "12", "FIRST_NAME": "Michael", "LAST_NAME": "Jackson"}, {"ID": "B12345756", "legal_name": "Genji Overwatch", "isActive": True, "PIDM": "13", "FIRST_NAME": "Genji", "LAST_NAME": "Overwatch"}, {"ID": "B12345759", "legal_name": "Mister Marlowe", "isActive": True, "PIDM": "14", "FIRST_NAME": "Mister", "LAST_NAME": "Marlowe"}, - {"ID": "B11231123", "legal_name": "Mister Thanksgiving", "isActive": True, "PIDM": "15", "FIRST_NAME": "Mister", "LAST_NAME": "Thanksgiving"} + {"ID": "B11231123", "legal_name": "Mister Thanksgiving", "isActive": True, "PIDM": "15", "FIRST_NAME": "Mister", "LAST_NAME": "Thanksgiving"}, + {"ID": "B12345762", "legal_name": "Alex Carter", "isActive": True, "PIDM": "16", "FIRST_NAME": "Alex", "LAST_NAME": "Carter"}, + {"ID": "B12345763", "legal_name": "Morgan Hayes", "isActive": True, "PIDM": "17", "FIRST_NAME": "Morgan", "LAST_NAME": "Hayes"}, + {"ID": "B12345764", "legal_name": "Jordan Brooks", "isActive": True, "PIDM": "18", "FIRST_NAME": "Jordan", "LAST_NAME": "Brooks"}, + {"ID": "B12345765", "legal_name": "Taylor Morgan", "isActive": True, "PIDM": "19", "FIRST_NAME": "Taylor", "LAST_NAME": "Morgan"}, + {"ID": "B12345766", "legal_name": "Casey Turner", "isActive": True, "PIDM": "20", "FIRST_NAME": "Casey", "LAST_NAME": "Turner"}, + {"ID": "B12345767", "legal_name": "Jamie Foster", "isActive": True, "PIDM": "21", "FIRST_NAME": "Jamie", "LAST_NAME": "Foster"}, + {"ID": "B12345768", "legal_name": "Riley Cooper", "isActive": True, "PIDM": "22", "FIRST_NAME": "Riley", "LAST_NAME": "Cooper"}, + {"ID": "B12345769", "legal_name": "Drew Bennett", "isActive": True, "PIDM": "23", "FIRST_NAME": "Drew", "LAST_NAME": "Bennett"}, + {"ID": "B12345770", "legal_name": "Logan Price", "isActive": True, "PIDM": "24", "FIRST_NAME": "Logan", "LAST_NAME": "Price"}, + {"ID": "B12345771", "legal_name": "Avery Sullivan", "isActive": True, "PIDM": "25", "FIRST_NAME": "Avery", "LAST_NAME": "Sullivan"}, ] tracyStudents = [ @@ -623,6 +633,83 @@ "adjustmentCutOff": f"2025-09-01", "isBreak": 1, }, + { + "termCode": "202600", + "termName": "AY 2026-2027", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + }, + { + "termCode": "202601", + "termName": "Thanksgiving Break 2026", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + "isBreak": 1, + }, + { + "termCode": "202602", + "termName": "Christmas Break 2026", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + "isBreak": 1, + }, + { + "termCode": "202603", + "termName": "Spring Break 2027", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + "isBreak": 1, + }, + { + "termCode": "202604", + "termName": "Fall Break 2026", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + "isBreak": 1, + }, + { + "termCode": "202611", + "termName": "Fall 2026", + "termStart": "2026-08-01", + "termEnd": "2026-12-31", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + }, + { + "termCode": "202612", + "termName": "Spring 2027", + "termStart": "2027-01-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2027-02-01", + "adjustmentCutOff": "2027-03-01", + }, + { + "termCode": "202613", + "termName": "Summer 2027", + "termStart": "2027-05-02", + "termEnd": "2027-08-01", + "termState": 0, + "primaryCutOff": "2027-06-01", + "adjustmentCutOff": "2027-07-01", + "isSummer": 1, + }, ] Term.insert_many(terms).on_conflict_replace().execute() @@ -756,8 +843,218 @@ "status_id": "Approved" }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + "laborStatusFormID": 62, + "termCode_id": "202611", + "studentName": "Alex Carter", + "studentSupervisee_id": "B12345762", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Office Assistant", + "POSN_CODE": "S61413", + "weeklyHours": 10, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 62, + "formID_id": "62", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 63, + "termCode_id": "202611", + "studentName": "Morgan Hayes", + "studentSupervisee_id": "B12345763", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Computer Lab Assistant", + "POSN_CODE": "S61414", + "weeklyHours": 15, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 63, + "formID_id": "63", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + "laborStatusFormID": 64, + "termCode_id": "202611", + "studentName": "Jordan Brooks", + "studentSupervisee_id": "B12345764", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Help Desk Assistant", + "POSN_CODE": "S61415", + "weeklyHours": 20, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 64, + "formID_id": "64", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 65, + "termCode_id": "202611", + "studentName": "Taylor Morgan", + "studentSupervisee_id": "B12345765", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 0, + "POSN_TITLE": "Reception Assistant", + "POSN_CODE": "S61416", + "weeklyHours": 5, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 65, + "formID_id": "65", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 66, + "termCode_id": "202611", + "studentName": "Casey Turner", + "studentSupervisee_id": "B12345766", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 0, + "POSN_TITLE": "Library Assistant", + "POSN_CODE": "S61417", + "weeklyHours": 10, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 66, + "formID_id": "66", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +# Break Positions + +LaborStatusForm.insert([{ + "laborStatusFormID": 67, + "termCode_id": "202601", + "studentName": "Jamie Foster", + "studentSupervisee_id": "B12345767", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Thanksgiving Worker", + "POSN_CODE": "S61418", + "contractHours": 40, + "startDate": "2026-11-22", + "endDate": "2026-11-29" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 68, + "termCode_id": "202602", + "studentName": "Riley Cooper", + "studentSupervisee_id": "B12345768", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Christmas Worker", + "POSN_CODE": "S61419", + "contractHours": 120, + "startDate": "2026-12-20", + "endDate": "2027-01-03" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 69, + "termCode_id": "202603", + "studentName": "Drew Bennett", + "studentSupervisee_id": "B12345769", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Spring Break Worker", + "POSN_CODE": "S61420", + "contractHours": 80, + "startDate": "2027-03-07", + "endDate": "2027-03-14" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 70, + "termCode_id": "202604", + "studentName": "Logan Price", + "studentSupervisee_id": "B12345770", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Fall Break Worker", + "POSN_CODE": "S61421", + "contractHours": 24, + "startDate": "2026-10-11", + "endDate": "2026-10-18" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 71, + "termCode_id": "202613", + "studentName": "Avery Sullivan", + "studentSupervisee_id": "B12345771", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Summer Worker", + "POSN_CODE": "S61422", + "contractHours": 320, + "startDate": "2027-05-15", + "endDate": "2027-08-01" +}]).on_conflict_replace().execute() + ############################# # admin Notes ############################# @@ -927,6 +1224,21 @@ "secondary_10": 1, "breakHours": 900, }, + { + "termCode": 202600, + "department": 1, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Maintaining current staffing levels while allowing for moderate growth in student employment opportunities.", + "primary_10": 6, + "primary_12": 5, + "primary_15": 4, + "primary_20": 2, + "secondary_5": 6, + "secondary_10": 1, + "breakHours": 600, + }, ] Allocation.insert_many(allocations).on_conflict_replace().execute() From a7ca47461753507b9b94f8b84bfd834e03d92a3c Mon Sep 17 00:00:00 2001 From: munsakad Date: Tue, 4 Aug 2026 10:19:49 -0400 Subject: [PATCH 048/128] Address allocation card review: camelCase naming, stacked layout, consolidated tests - Rename allocation_summary and its snake_case keys/locals to camelCase (allocationSummary, currentSemester, usedPositions, breakHours, used10, usedSecondary5, ...) across the logic, route, template, and tests - Shorten the getCurrentSemesterLabel docstring - Reword the card to Contracted/Allocated in both the tooltip and the position count - Stack Secondary below Primary (and the count below the term) on narrow cards by splitting the paired table into two, instead of shrinking the font - Point the not-yet-built View Allocations page at "#" like the Members card, so the button no longer 404s - Collapse the per-scenario tests into one test per function, drop the leading underscore from createFormHistory, and mark the unit test so run_tests.sh stops deselecting it - Cover the used-count denial filter and make the term filter and the getBreakHours other-term assertion actually meaningful - Revert unrelated whitespace/formatting churn in main_routes.py --- app/controllers/main_routes/main_routes.py | 24 +- app/logic/getAllocation.py | 72 +++--- app/static/css/departmentPortal.css | 35 ++- app/templates/main/departmentPortal.html | 66 +++--- tests/code/test_getAllocation.py | 255 +++++++++------------ 5 files changed, 212 insertions(+), 240 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index e9ec85e8f..de37e4b4e 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -12,6 +12,7 @@ from app.models.term import Term from app.models.allocation import Allocation from app.models.positionHistory import PositionHistory + from app.controllers.admin_routes.allPendingForms import checkAdjustment from app.controllers.main_routes import main_bp @@ -24,6 +25,7 @@ from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions + @main_bp.route('/logout', methods=['GET']) def triggerLogout(): return redirect(logout()) @@ -71,8 +73,8 @@ def departmentPortal(org=None,account=None): supervisors, laborCoordinators = getSupervisors(dept) - allocation_summary = getDepartmentAllocationSummary(dept) - recentTerm = allocation_summary["term"] + allocationSummary = getDepartmentAllocationSummary(dept) + recentTerm = allocationSummary["term"] if recentTerm: try: @@ -82,24 +84,24 @@ def departmentPortal(org=None,account=None): else: allocation = None - positionsList, posURL = getActivePositions(dept) + positionsList, posURL = getActivePositions(dept) - return render_template('main/departmentPortal.html', + return render_template('main/departmentPortal.html', departments = departments, department = dept, allocation = allocation, - allocated = allocation_summary["allocated"], - used = allocation_summary["used"], + allocated = allocationSummary["allocated"], + used = allocationSummary["used"], term = recentTerm, - currentSemester = allocation_summary["current_semester"], - usedPositions = allocation_summary["used_positions"], - break_hours = allocation_summary["break_hours"], + currentSemester = allocationSummary["currentSemester"], + usedPositions = allocationSummary["usedPositions"], + breakHours = allocationSummary["breakHours"], supervisors = supervisors, laborCoordinators=laborCoordinators, currentUser=currentUser, positions = positionsList, - posURL = posURL, - ) + posURL = posURL) + @main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) def addUserToDept(): userDeptData = request.form diff --git a/app/logic/getAllocation.py b/app/logic/getAllocation.py index f5d5d281f..1f7c879d3 100644 --- a/app/logic/getAllocation.py +++ b/app/logic/getAllocation.py @@ -9,12 +9,8 @@ def getCurrentSemesterLabel(term): - """Return the current Fall/Spring semester label (e.g. "Fall 2025") for - the academic year that the given term belongs to. The season is picked - from today's month and the year comes from the term's own termCode, - following the AY/Fall/Spring termCode convention in termManagement.py - (AY code, code+11 = Fall of that year, code+12 = Spring of the next). - """ + """Return the Fall/Spring label (e.g. "Fall 2025") for the AY term's + current semester, picking the season from today's month.""" if not term: return None academicYear = int(str(term.termCode)[:4]) @@ -23,15 +19,15 @@ def getCurrentSemesterLabel(term): return f"Spring {academicYear + 1}" -def countWorkers(department, term_code, job_type, hours_bucket): +def countWorkers(department, termCode, jobType, hoursBucket): workerCount = ( LaborStatusForm.select() .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) .where( LaborStatusForm.department == department, - LaborStatusForm.termCode == term_code, - LaborStatusForm.jobType == job_type, - LaborStatusForm.weeklyHours == hours_bucket, + LaborStatusForm.termCode == termCode, + LaborStatusForm.jobType == jobType, + LaborStatusForm.weeklyHours == hoursBucket, LaborStatusForm.contractHours.is_null(True), FormHistory.historyType == "Labor Status Form", ~(FormHistory.status % "Denied%"), @@ -41,13 +37,13 @@ def countWorkers(department, term_code, job_type, hours_bucket): return workerCount -def getBreakHours(department, term_code): +def getBreakHours(department, termCode): breakHoursTotal = ( LaborStatusForm.select(fn.SUM(LaborStatusForm.contractHours)) .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) .where( LaborStatusForm.department == department, - LaborStatusForm.termCode == term_code, + LaborStatusForm.termCode == termCode, FormHistory.historyType == "Labor Status Form", FormHistory.status == "Approved", ) @@ -60,18 +56,18 @@ def getDepartmentAllocationSummary(department): """Return allocation-utilization values for a department's most recent term.""" result = { "term": None, - "current_semester": None, + "currentSemester": None, "allocated": 0, "used": 0, - "used_positions": { - "used_10": 0, - "used_12": 0, - "used_15": 0, - "used_20": 0, - "used_5_sec": 0, - "used_10_sec": 0, + "usedPositions": { + "used10": 0, + "used12": 0, + "used15": 0, + "used20": 0, + "usedSecondary5": 0, + "usedSecondary10": 0, }, - "break_hours": 0, + "breakHours": 0, } departmentAllocations = list( @@ -81,11 +77,11 @@ def getDepartmentAllocationSummary(department): return result recentTerm = Term.order_by_term([a.termCode for a in departmentAllocations], reverse=True)[0] - term_code = recentTerm.termCode + termCode = recentTerm.termCode result["term"] = recentTerm - result["current_semester"] = getCurrentSemesterLabel(recentTerm) + result["currentSemester"] = getCurrentSemesterLabel(recentTerm) - total_positions = ( + totalPositions = ( Allocation.select( fn.SUM(Allocation.primary_10) + fn.SUM(Allocation.primary_12) @@ -96,35 +92,35 @@ def getDepartmentAllocationSummary(department): ) .where( Allocation.department == department, - Allocation.termCode == term_code, + Allocation.termCode == termCode, ) .scalar() ) - result["allocated"] = total_positions or 0 + result["allocated"] = totalPositions or 0 - used_allocation = ( + usedAllocation = ( LaborStatusForm.select() .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) .where( LaborStatusForm.department == department, - LaborStatusForm.termCode == term_code, + LaborStatusForm.termCode == termCode, LaborStatusForm.contractHours.is_null(True), FormHistory.historyType == "Labor Status Form", ~(FormHistory.status % "Denied%"), ) .count() ) - result["used"] = used_allocation - - result["used_positions"] = { - "used_10": countWorkers(department, term_code, "Primary", 10), - "used_12": countWorkers(department, term_code, "Primary", 12), - "used_15": countWorkers(department, term_code, "Primary", 15), - "used_20": countWorkers(department, term_code, "Primary", 20), - "used_5_sec": countWorkers(department, term_code, "Secondary", 5), - "used_10_sec": countWorkers(department, term_code, "Secondary", 10), + result["used"] = usedAllocation + + result["usedPositions"] = { + "used10": countWorkers(department, termCode, "Primary", 10), + "used12": countWorkers(department, termCode, "Primary", 12), + "used15": countWorkers(department, termCode, "Primary", 15), + "used20": countWorkers(department, termCode, "Primary", 20), + "usedSecondary5": countWorkers(department, termCode, "Secondary", 5), + "usedSecondary10": countWorkers(department, termCode, "Secondary", 10), } - result["break_hours"] = getBreakHours(department, term_code) + result["breakHours"] = getBreakHours(department, termCode) return result diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index b04f98ad1..88ae06e29 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -47,8 +47,24 @@ overflow-x: auto; margin: 10px 0; } +.allocation-summary { + display: flex; + flex-wrap: wrap; + justify-content: space-between; + align-items: baseline; + gap: 0 1rem; +} +.allocation-summary h4 { + margin: 10px 0; +} +.allocation-columns { + display: flex; + flex-wrap: wrap; + gap: 0 2rem; +} .allocation-table { - width: 100%; + /* wraps onto its own line when the card is too narrow, instead of shrinking */ + flex: 1 1 180px; border-collapse: collapse; font-size: 1.2em; } @@ -63,9 +79,6 @@ font-weight: 700; padding-top: 10px; } -.allocation-table .term-row h4 { - margin: 10px 0; -} .bi-people-fill { /* Bootstrap Icon for Members Card */ border: 1px solid #c0c0c0; @@ -98,14 +111,14 @@ } } +/* Narrow card: stack Secondary below Primary, and the position count below the + term, rather than shrinking the text to keep them side by side. */ @media (min-width: 1200px) and (max-width: 1450px), (max-width: 480px) { - .allocation-table { - font-size: 0.85em; - } - .allocation-table td { - padding: 2px 5px 2px 0; + .allocation-summary { + flex-direction: column; + gap: 0; } - .allocation-table .term-row h4 { - font-size: 0.9rem; + .allocation-columns .allocation-table { + flex-basis: 100%; } } diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index b6178bf99..ce0b051fe 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -9,11 +9,11 @@ {% endblock %} + {% block app_content %}

{% if department %} {{department.DEPT_NAME}} Portal {% else %} Choose a Department: {% endif %}

-
-{% if department %} + {% if department %}
@@ -42,45 +42,43 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e

-

Current Allocation

+

Current Allocations

- {% macro allocationCell(hours, used, allocated) -%} - {{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }}
(out of {{ allocated }} allocation{{ 's' if allocated != 1 else '' }}) + {% macro allocationRow(hours, used, allocated) -%} + {{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }}
(out of {{ allocated }} allocation{{ 's' if allocated != 1 else '' }}) {%- endmacro %}
- - - - - - - - - - - - {{ allocationCell(10, usedPositions.used_10, allocation.primary_10) }} - {{ allocationCell(5, usedPositions.used_5_sec, allocation.secondary_5) }} - - - {{ allocationCell(12, usedPositions.used_12, allocation.primary_12) }} - {{ allocationCell(10, usedPositions.used_10_sec, allocation.secondary_10) }} - - - {{ allocationCell(15, usedPositions.used_15, allocation.primary_15) }} - - - - {{ allocationCell(20, usedPositions.used_20, allocation.primary_20) }} - - - -

{{ currentSemester if currentSemester else "No term data" }}

{{used}} of {{allocated or 0}} Positions

PrimarySecondary
+
+

{{ currentSemester if currentSemester else "No term data" }}

+

{{used}} contracted of {{allocated or 0}} allocated Positions

+
+
+ + + + + + {{ allocationRow(10, usedPositions.used10, allocation.primary_10) }} + {{ allocationRow(12, usedPositions.used12, allocation.primary_12) }} + {{ allocationRow(15, usedPositions.used15, allocation.primary_15) }} + {{ allocationRow(20, usedPositions.used20, allocation.primary_20) }} + +
Primary
+ + + + + + {{ allocationRow(5, usedPositions.usedSecondary5, allocation.secondary_5) }} + {{ allocationRow(10, usedPositions.usedSecondary10, allocation.secondary_10) }} + +
Secondary
+
diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py index 82d137106..72e115db3 100644 --- a/tests/code/test_getAllocation.py +++ b/tests/code/test_getAllocation.py @@ -16,9 +16,9 @@ from app.logic.getAllocation import getDepartmentAllocationSummary, countWorkers, getBreakHours, getCurrentSemesterLabel -def _createFormHistory(form, statusName): - """Attach a FormHistory row to a LaborStatusForm, since getBreakHours now - only counts forms with an approved "Labor Status Form" history entry.""" +def createFormHistory(form, statusName): + """Attach a "Labor Status Form" history entry with the given status, since + the allocation queries only count forms that have one.""" user = User.create(username=f"testuser_{form.laborStatusFormID}") historyType = HistoryType.get(HistoryType.historyTypeName == "Labor Status Form") status = Status.get(Status.statusName == statusName) @@ -31,74 +31,82 @@ def _createFormHistory(form, statusName): ) -def test_getCurrentSemesterLabel_none_term(): +@pytest.mark.unit +def test_getCurrentSemesterLabel(): + """ + Test that a term maps to the Fall/Spring label for whichever half of the + academic year today falls in, and that a missing term has no label. + """ + # No term (e.g. a department with no allocations) - nothing to label assert getCurrentSemesterLabel(None) is None - -def test_getCurrentSemesterLabel_fall(): - """A term whose termCode's academic year is 2025 should read as Fall 2025 - when today falls in the Aug-Dec half of the academic year.""" term = Term(termCode=202500) + + # Aug-Dec half of the academic year - reads as Fall of the term's own year with patch("app.logic.getAllocation.date") as mockDate: mockDate.today.return_value = date(2025, 9, 15) assert getCurrentSemesterLabel(term) == "Fall 2025" - -def test_getCurrentSemesterLabel_spring(): - """The same academic-year term should read as Spring 2026 when today - falls in the Jan-Jul half of the academic year.""" - term = Term(termCode=202500) + # Jan-Jul half of the same academic-year term - reads as Spring of the next year with patch("app.logic.getAllocation.date") as mockDate: mockDate.today.return_value = date(2026, 2, 10) assert getCurrentSemesterLabel(term) == "Spring 2026" @pytest.mark.integration -def test_getDepartmentAllocationSummary_no_allocation(): +def test_getDepartmentAllocationSummary(): """ - Test that a department with no Allocation rows gets a zeroed-out summary - with term=None, instead of an error. + Test that the summary reports allocated/used/breakHours for a department's + most recent term, covering a missing department, a department with no + Allocation rows, allocations spread across terms, several Allocation rows + in one term, break-term contracts, and an allocation with no forms. """ + zeroedUsedPositions = { + "used10": 0, + "used12": 0, + "used15": 0, + "used20": 0, + "usedSecondary5": 0, + "usedSecondary10": 0, + } + + # department=None (e.g. when Department.get() fails in the departmentPortal + # route) returns the zeroed-out fallback instead of raising an error + summary = getDepartmentAllocationSummary(None) + + assert summary["term"] is None + assert summary["allocated"] == 0 + assert summary["used"] == 0 + assert summary["breakHours"] == 0 + assert summary["usedPositions"] == zeroedUsedPositions + with mainDB.atomic() as transaction: - dept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6750", ORG="2120", isActive=True) + # A department with no Allocation rows gets the same zeroed-out summary + # with term=None + emptyDept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6750", ORG="2120", isActive=True) - summary = getDepartmentAllocationSummary(dept) + summary = getDepartmentAllocationSummary(emptyDept) assert summary["term"] is None assert summary["allocated"] == 0 assert summary["used"] == 0 - assert summary["break_hours"] == 0 - assert summary["used_positions"] == { - "used_10": 0, - "used_12": 0, - "used_15": 0, - "used_20": 0, - "used_5_sec": 0, - "used_10_sec": 0, - } - - transaction.rollback() - + assert summary["breakHours"] == 0 + assert summary["usedPositions"] == zeroedUsedPositions -@pytest.mark.integration -def test_getDepartmentAllocationSummary_uses_most_recent_term(): - """ - Test that when a department has allocations across multiple terms, the - summary reflects only the most recent term's data. - """ - with mainDB.atomic() as transaction: - dept = Department.create(departmentID=201, DEPT_NAME="Chemistry", ACCOUNT="6751", ORG="2121", isActive=True) + # With allocations across multiple terms, the summary reflects only the + # most recent term's data + multiTermDept = Department.create(departmentID=201, DEPT_NAME="Chemistry", ACCOUNT="6751", ORG="2121", isActive=True) oldTerm = Term.create(termCode=900000, termName="AY Test Old") newTerm = Term.create(termCode=900100, termName="AY Test New") Allocation.create( - termCode=oldTerm, department=dept, isFinal=True, justification="old", + termCode=oldTerm, department=multiTermDept, isFinal=True, justification="old", primary_10=1, primary_12=0, primary_15=0, primary_20=0, secondary_5=0, secondary_10=0, breakHours=50, ) Allocation.create( - termCode=newTerm, department=dept, isFinal=True, justification="new", + termCode=newTerm, department=multiTermDept, isFinal=True, justification="new", primary_10=2, primary_12=3, primary_15=0, primary_20=0, secondary_5=1, secondary_10=0, breakHours=100, ) @@ -106,151 +114,106 @@ def test_getDepartmentAllocationSummary_uses_most_recent_term(): supervisor = Supervisor.create(ID="SUP001", isActive=True) student = Student.create(ID="STU001", isActive=True) - # Under the OLD term - should be excluded from the summary - LaborStatusForm.create( - termCode=oldTerm, studentSupervisee=student, supervisor=supervisor, department=dept, + # Approved under the OLD term - excluded by the term filter alone + oldForm = LaborStatusForm.create( + termCode=oldTerm, studentSupervisee=student, supervisor=supervisor, department=multiTermDept, jobType="Primary", WLS="10", POSN_TITLE="Old Job", POSN_CODE="S001", weeklyHours=10, contractHours=None, ) + createFormHistory(oldForm, "Approved") + # Under the NEW (most recent) term - should be counted newForm = LaborStatusForm.create( - termCode=newTerm, studentSupervisee=student, supervisor=supervisor, department=dept, + termCode=newTerm, studentSupervisee=student, supervisor=supervisor, department=multiTermDept, jobType="Primary", WLS="10", POSN_TITLE="New Job", POSN_CODE="S002", weeklyHours=10, contractHours=None, ) - _createFormHistory(newForm, "Approved") + createFormHistory(newForm, "Approved") - summary = getDepartmentAllocationSummary(dept) + # Denied under the NEW term - should not count toward used + deniedForm = LaborStatusForm.create( + termCode=newTerm, studentSupervisee=student, supervisor=supervisor, department=multiTermDept, + jobType="Primary", WLS="12", POSN_TITLE="Denied Job", POSN_CODE="S004", + weeklyHours=12, contractHours=None, + ) + createFormHistory(deniedForm, "Denied by Admin") + + summary = getDepartmentAllocationSummary(multiTermDept) assert summary["term"].termCode == 900100 assert summary["allocated"] == 6 # 2 + 3 + 0 + 0 + 1 + 0, from the new term only - assert summary["used"] == 1 # only the new term's LaborStatusForm counts - assert summary["used_positions"]["used_10"] == 1 - assert summary["break_hours"] == 0 + assert summary["used"] == 1 # only the new term's approved LaborStatusForm counts + assert summary["usedPositions"]["used10"] == 1 + assert summary["usedPositions"]["used12"] == 0 # the denied form is not counted + assert summary["breakHours"] == 0 - transaction.rollback() - - -@pytest.mark.integration -def test_getDepartmentAllocationSummary_break_hours(): - """ - Test that break_hours only sums approved forms with contractHours set - (break-term contracts), and that those forms are excluded from the - weekly "used" count. - """ - with mainDB.atomic() as transaction: - dept = Department.create(departmentID=202, DEPT_NAME="Biology", ACCOUNT="6752", ORG="2122", isActive=True) - term = Term.create(termCode=900200, termName="AY Test Break") + # breakHours only sums approved forms with contractHours set (break-term + # contracts), and those forms are excluded from the weekly "used" count + breakDept = Department.create(departmentID=202, DEPT_NAME="Biology", ACCOUNT="6752", ORG="2122", isActive=True) + breakTerm = Term.create(termCode=900200, termName="AY Test Break") Allocation.create( - termCode=term, department=dept, isFinal=True, justification="test", + termCode=breakTerm, department=breakDept, isFinal=True, justification="test", primary_10=1, primary_12=0, primary_15=0, primary_20=0, secondary_5=0, secondary_10=0, breakHours=200, ) - supervisor = Supervisor.create(ID="SUP002", isActive=True) - student = Student.create(ID="STU002", isActive=True) + breakSupervisor = Supervisor.create(ID="SUP002", isActive=True) + breakStudent = Student.create(ID="STU002", isActive=True) breakForm = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, + termCode=breakTerm, studentSupervisee=breakStudent, supervisor=breakSupervisor, department=breakDept, jobType="Primary", WLS="10", POSN_TITLE="Break Worker", POSN_CODE="S003", weeklyHours=None, contractHours=40, ) - _createFormHistory(breakForm, "Approved") + createFormHistory(breakForm, "Approved") - summary = getDepartmentAllocationSummary(dept) + summary = getDepartmentAllocationSummary(breakDept) - assert summary["break_hours"] == 40 + assert summary["breakHours"] == 40 assert summary["used"] == 0 - transaction.rollback() - - -@pytest.mark.integration -def test_getDepartmentAllocationSummary_department_none(): - """ - Test that passing department=None (e.g. when Department.get() fails in - the departmentPortal route) returns the zeroed-out fallback instead of - raising an error. - """ - summary = getDepartmentAllocationSummary(None) - - assert summary["term"] is None - assert summary["allocated"] == 0 - assert summary["used"] == 0 - assert summary["break_hours"] == 0 - assert summary["used_positions"] == { - "used_10": 0, - "used_12": 0, - "used_15": 0, - "used_20": 0, - "used_5_sec": 0, - "used_10_sec": 0, - } - - -@pytest.mark.integration -def test_getDepartmentAllocationSummary_multiple_allocations_same_term(): - """ - Test that if a department has more than one Allocation row for the same - most-recent term (e.g. a draft and a final revision, which the model's - (termCode, department, isFinal) index allows), the totals sum across - both rows rather than picking just one. - """ - with mainDB.atomic() as transaction: - dept = Department.create(departmentID=203, DEPT_NAME="Mathematics", ACCOUNT="6753", ORG="2123", isActive=True) - term = Term.create(termCode=900300, termName="AY Test Multi") + # More than one Allocation row for the same most-recent term (e.g. a + # draft and a final revision, which the model's (termCode, department, + # isFinal) index allows) sums across both rows rather than picking one + multiRowDept = Department.create(departmentID=203, DEPT_NAME="Mathematics", ACCOUNT="6753", ORG="2123", isActive=True) + multiRowTerm = Term.create(termCode=900300, termName="AY Test Multi") Allocation.create( - termCode=term, department=dept, isFinal=False, justification="draft", + termCode=multiRowTerm, department=multiRowDept, isFinal=False, justification="draft", primary_10=1, primary_12=0, primary_15=0, primary_20=0, secondary_5=0, secondary_10=0, breakHours=10, ) Allocation.create( - termCode=term, department=dept, isFinal=True, justification="final", + termCode=multiRowTerm, department=multiRowDept, isFinal=True, justification="final", primary_10=2, primary_12=0, primary_15=0, primary_20=0, secondary_5=0, secondary_10=0, breakHours=20, ) - summary = getDepartmentAllocationSummary(dept) + summary = getDepartmentAllocationSummary(multiRowDept) assert summary["term"].termCode == 900300 assert summary["allocated"] == 3 # 1 + 2, summed across both rows - transaction.rollback() - - -@pytest.mark.integration -def test_getDepartmentAllocationSummary_allocation_no_labor_status_forms(): - """ - Test that a department with an allocation for the most recent term but no - LaborStatusForm records at all shows allocated > 0 with used/break_hours - at 0, rather than erroring on an empty result set. - """ - with mainDB.atomic() as transaction: - dept = Department.create(departmentID=204, DEPT_NAME="History", ACCOUNT="6754", ORG="2124", isActive=True) - term = Term.create(termCode=900400, termName="AY Test Empty") + # An allocation for the most recent term with no LaborStatusForm records + # at all shows allocated > 0 with used/breakHours at 0, rather than + # erroring on an empty result set + noFormsDept = Department.create(departmentID=204, DEPT_NAME="History", ACCOUNT="6754", ORG="2124", isActive=True) + noFormsTerm = Term.create(termCode=900400, termName="AY Test Empty") Allocation.create( - termCode=term, department=dept, isFinal=True, justification="test", + termCode=noFormsTerm, department=noFormsDept, isFinal=True, justification="test", primary_10=3, primary_12=2, primary_15=0, primary_20=0, secondary_5=1, secondary_10=0, breakHours=150, ) - summary = getDepartmentAllocationSummary(dept) + summary = getDepartmentAllocationSummary(noFormsDept) assert summary["term"].termCode == 900400 assert summary["allocated"] == 6 assert summary["used"] == 0 - assert summary["break_hours"] == 0 - assert summary["used_positions"] == { - "used_10": 0, - "used_12": 0, - "used_15": 0, - "used_20": 0, - "used_5_sec": 0, - "used_10_sec": 0, - } + assert summary["breakHours"] == 0 + assert summary["usedPositions"] == zeroedUsedPositions transaction.rollback() @@ -276,7 +239,7 @@ def test_countWorkers(): jobType="Primary", WLS="10", POSN_TITLE="Match", POSN_CODE="S010", weeklyHours=10, contractHours=None, ) - _createFormHistory(matchForm, "Approved") + createFormHistory(matchForm, "Approved") # Different job type - should not count toward ("Primary", 10) wrongJobTypeForm = LaborStatusForm.create( @@ -284,7 +247,7 @@ def test_countWorkers(): jobType="Secondary", WLS="10", POSN_TITLE="Wrong Job Type", POSN_CODE="S011", weeklyHours=10, contractHours=None, ) - _createFormHistory(wrongJobTypeForm, "Approved") + createFormHistory(wrongJobTypeForm, "Approved") # Different hours bucket - should not count toward ("Primary", 10) wrongHoursForm = LaborStatusForm.create( @@ -292,7 +255,7 @@ def test_countWorkers(): jobType="Primary", WLS="12", POSN_TITLE="Wrong Hours", POSN_CODE="S012", weeklyHours=12, contractHours=None, ) - _createFormHistory(wrongHoursForm, "Approved") + createFormHistory(wrongHoursForm, "Approved") # Break-term contract (contractHours set) - should not count even though # job type and weeklyHours otherwise match @@ -301,7 +264,7 @@ def test_countWorkers(): jobType="Primary", WLS="10", POSN_TITLE="Break Contract", POSN_CODE="S013", weeklyHours=10, contractHours=40, ) - _createFormHistory(breakContractForm, "Approved") + createFormHistory(breakContractForm, "Approved") # Matches everything but was DENIED - should not count deniedForm = LaborStatusForm.create( @@ -309,7 +272,7 @@ def test_countWorkers(): jobType="Primary", WLS="10", POSN_TITLE="Denied Match", POSN_CODE="S014", weeklyHours=10, contractHours=None, ) - _createFormHistory(deniedForm, "Denied by Admin") + createFormHistory(deniedForm, "Denied by Admin") assert countWorkers(dept, term.termCode, "Primary", 10) == 1 assert countWorkers(dept, term.termCode, "Secondary", 10) == 1 @@ -341,14 +304,14 @@ def test_getBreakHours(): jobType="Primary", WLS="10", POSN_TITLE="Break A", POSN_CODE="S020", weeklyHours=None, contractHours=40, ) - _createFormHistory(formA, "Approved") + createFormHistory(formA, "Approved") formB = LaborStatusForm.create( termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, jobType="Secondary", WLS="5", POSN_TITLE="Break B", POSN_CODE="S021", weeklyHours=None, contractHours=60, ) - _createFormHistory(formB, "Approved") + createFormHistory(formB, "Approved") # Weekly-hours form (contractHours=None) - should be excluded regardless formC = LaborStatusForm.create( @@ -356,15 +319,15 @@ def test_getBreakHours(): jobType="Primary", WLS="10", POSN_TITLE="Weekly Job", POSN_CODE="S022", weeklyHours=10, contractHours=None, ) - _createFormHistory(formC, "Approved") + createFormHistory(formC, "Approved") # Break-term contract under a DIFFERENT term - should be excluded formD = LaborStatusForm.create( termCode=otherTerm, studentSupervisee=student, supervisor=supervisor, department=dept, jobType="Primary", WLS="10", POSN_TITLE="Break Other Term", POSN_CODE="S023", - weeklyHours=None, contractHours=100, + weeklyHours=None, contractHours=25, ) - _createFormHistory(formD, "Approved") + createFormHistory(formD, "Approved") # Break-term contract that is still PENDING - should be excluded formE = LaborStatusForm.create( @@ -372,9 +335,9 @@ def test_getBreakHours(): jobType="Primary", WLS="10", POSN_TITLE="Break Pending", POSN_CODE="S024", weeklyHours=None, contractHours=999, ) - _createFormHistory(formE, "Pending") + createFormHistory(formE, "Pending") - assert getBreakHours(dept, term.termCode) == 100 # 40 + 60, excludes the pending form - assert getBreakHours(dept, otherTerm.termCode) == 100 + assert getBreakHours(dept, term.termCode) == 100 # 40 + 60, excludes the pending form + assert getBreakHours(dept, otherTerm.termCode) == 25 # only the other term's contract transaction.rollback() From fa1114dc82ea7f734e54d96eb5aafa77a9491734 Mon Sep 17 00:00:00 2001 From: ACBerea Date: Tue, 4 Aug 2026 10:26:16 -0400 Subject: [PATCH 049/128] Fixed issue with sidebar creating additional x-axis scroll bar and made adjustments to individualPositions.css and individualPositions.html. --- app/static/css/base.css | 1 + app/static/css/individualPositions.css | 16 ++++++++++++---- app/static/css/sidebar.css | 2 +- app/templates/main/individualPositions.html | 8 ++++---- database/demo_data.py | 2 +- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/static/css/base.css b/app/static/css/base.css index 7ff45dffc..19b36fd2b 100755 --- a/app/static/css/base.css +++ b/app/static/css/base.css @@ -127,6 +127,7 @@ a { left: 280px; right: 0px; margin-right: 280px; + width: calc(100vw - 300px); } diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index 1cb8cc354..505a80765 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -16,6 +16,11 @@ right: 15px; } +.position-description div{ + width: 100%; + max-width: 100%; + box-sizing: border-box; +} /* Metadata list (dt / dd spacing) */ .position-information dl.row dt { font-weight: 600; @@ -35,25 +40,28 @@ } .position-container { - margin-top: 2rem; + margin: 2rem; + } /* Description section card */ .position-description { - padding: 1rem; text-align: left; margin-top: 0; margin-bottom: 0.75rem; font-size: 1.25rem; white-space: pre-line; - overflow-wrap: break-word; + /* overflow-wrap: break-word; */ word-wrap: break-word; } .description-content { margin: 0; padding: 0; - overflow-wrap: break-word; + width: 100%; + max-width: 100%; + box-sizing: border-box; + overflow-wrap: anywhere; word-wrap: break-word; } diff --git a/app/static/css/sidebar.css b/app/static/css/sidebar.css index 500cf74d9..cb062a9fe 100644 --- a/app/static/css/sidebar.css +++ b/app/static/css/sidebar.css @@ -65,7 +65,7 @@ .sidebar-push { left: 0 !important; - width: 100% !important; + width: 100vw !important; margin-right: 0 !important; } diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index a1162eb90..e0e14f348 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -31,7 +31,7 @@

{{ department.DEPT_NAME }}

WLS Level:
{{ position.wls }}
-
Status:
+
Status:
{{ position.status }}
Last Revision Date:
@@ -42,16 +42,16 @@

{{ department.DEPT_NAME }}

-

Description

+
Description
{%- if sections %} {%- for section in sections %} -

{{ section.sectionTitle |safe }}

+
{{ section.sectionTitle |safe }}
{{ section.sectionContent |safe }}
{%- endfor %} {%- else %}

No description available.

- {%- endif %} + {%- endif %}side
diff --git a/database/demo_data.py b/database/demo_data.py index f7b64deab..2db83d6b5 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -987,7 +987,7 @@ { "position": 3, "sectionTitle": '

Learning Opportunities

', - "sectionContent": '

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

\n\n

A. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

\n\n

B. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4) C. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3) D. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5) E. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

', + "sectionContent": '

List how this position will support student learning through daily responsibilities and intentional reflection.

Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

\n\n

A. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

\n\n

B. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

\n\n

C. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

\n\n

D. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

\n\n

E. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

', "order": 3, }, { From 64f4dc48373fb337d10ad4cae9d72c5627506f2b Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 10:27:47 -0400 Subject: [PATCH 050/128] got the contracts to count in page --- app/controllers/main_routes/main_routes.py | 66 ++++++++++++---------- app/logic/allocationManager.py | 11 ++-- database/demo_data.py | 47 ++++++++++++++- 3 files changed, 87 insertions(+), 37 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 4a3001089..7116f999c 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -100,13 +100,17 @@ def allocationTable(org=None, account=None): departments = list(Department.select().join(SupervisorDepartment).where(SupervisorDepartment.supervisor == currentUser.supervisor).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) currentDate = str(date.today()) + if int(currentDate[5:7]) <= 6: + # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. + springTerm = Term.select().where(Term.termCode == int(currentDate[:4] + "12") - 100).get() + currentAY = currentTerm = Term.select().where(Term.termCode == int(currentDate[:4] + "00") - 100).get() + fallTerm = Term.select().where(Term.termCode == int(currentDate[:4] + "11") - 100).get() - if currentDate.month <= 6: - currentTerm = Term.select().where(Term.termCode == currentDate[:4] + "12").get() - currentAY = currentTerm = Term.select().where(Term.termCode == currentDate[:4] + "00").get() else: - currentTerm = Term.select().where(Term.termCode == currentDate[:4] + "11").get() + fallTerm = Term.select().where(Term.termCode == currentDate[:4] + "11").get() currentAY = Term.select().where(Term.termCode == currentDate[:4] + "00").get() + springTerm = Term.select().where(Term.termCode == currentDate[:4] + "12").get() + allocationDict = {"primary_10": 1, "primary_12": 2, @@ -118,33 +122,35 @@ def allocationTable(org=None, account=None): "totalPrimaries": 10, "totalSecondaries": 11, "totalAllocations": 21} - # fallContracts = getContractedAllocations(currentTerm, dept) - fallContracts = { - "used_10": 4, - "used_12": 4, - "used_15": 4, - "used_20": 4, - "used_5_sec": 4, - "used_10_sec": 4, - "used_total": 0, - "break_hours": "3" - } - springContracts = { - "used_10": 4, - "used_12": 4, - "used_15": 4, - "used_20": 4, - "used_5_sec": 4, - "used_10_sec": 4, - "used_total": 0, - "break_hours": "3" - } + fallContracts = getContractedAllocations(fallTerm, dept) + # fallContracts = { + # "used_10": 4, + # "used_12": 4, + # "used_15": 4, + # "used_20": 4, + # "used_5_sec": 4, + # "used_10_sec": 4, + # "used_total": 0, + # "break_hours": "3" + # } + springContracts = getContractedAllocations(springTerm, dept) + + # springContracts = { + # "used_10": 4, + # "used_12": 4, + # "used_15": 4, + # "used_20": 4, + # "used_5_sec": 4, + # "used_10_sec": 4, + # "used_total": 0, + # "break_hours": "3" + # } breakContracts = { - "thanksgiving":getBreakContracts(202401, dept),#FIXME - "winter": getBreakContracts(202402, dept), - "spring": getBreakContracts(202403, dept), - "fall":getBreakContracts(202404, dept), - "summer": getBreakContracts(202413, dept) + "thanksgiving":getBreakContracts(currentAY.termCode + 1, dept), + "winter": getBreakContracts(currentAY.termCode + 2, dept), + "spring": getBreakContracts(currentAY.termCode + 3, dept), + "fall":getBreakContracts(currentAY.termCode + 4, dept), + "summer": getBreakContracts(currentAY.termCode + 13, dept) } return render_template('main/allocationTable.html', diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index a9935920e..32df313dc 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -7,9 +7,9 @@ def getAllocation(termCode, dept): - academicYearCode = int(str(termCode)[:5] + "00") + academicYearCode = int(str(termCode)[:4] + "00") allocationObject = Allocation.select().where( - Allocation.termCode.in_([termCode,academicYearCode]), + ((Allocation.termCode == termCode) | (Allocation.termCode == academicYearCode)), Allocation.department == dept, Allocation.isFinal == True).dicts().get() return allocationObject @@ -40,14 +40,14 @@ def getTotalAllocations(termCode, dept): return allocationDict def countContracts(jobType, weeklyContractHours, termCode, dept): - academicYearCode = int(str(termCode)[:5] + "00") + academicYearCode = int(str(termCode)[:4] + "999") lsfCountPrimaries = FormHistory.select( ).join(LaborStatusForm ).join(Department ).where( FormHistory.historyType == "Labor Status Form", FormHistory.status.in_(["Approved", "Pending", "Pre-Student Approval"]), - LaborStatusForm.termCode.in_([termCode,academicYearCode]), + (LaborStatusForm.termCode == termCode) | (LaborStatusForm.termCode == academicYearCode), LaborStatusForm.jobType == jobType, LaborStatusForm.weeklyHours == weeklyContractHours, Department.departmentID == dept, @@ -96,7 +96,6 @@ def getContractedAllocations(termCode, dept): return usedPositions def getBreakContracts(termCode, dept): - academicYearCode = (str(termCode)[:4]) break_allocaiton = FormHistory.select(fn.SUM(LaborStatusForm.contractHours) ).join(LaborStatusForm ).where( @@ -104,7 +103,7 @@ def getBreakContracts(termCode, dept): FormHistory.status.in_(["Approved", "Pending", "Pre-Student Approval"]), LaborStatusForm.termCode == termCode, LaborStatusForm.department == dept, - LaborStatusForm.weeklyHours == None).scalar() + LaborStatusForm.contractHours != None).scalar() # break_allocation = FormHistory.select( # LaborStatusForm.department, diff --git a/database/demo_data.py b/database/demo_data.py index 7ad8493fe..e64ac8c6e 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -1055,6 +1055,51 @@ "endDate": "2027-08-01" }]).on_conflict_replace().execute() +FormHistory.insert([{ + "formHistoryID": 67, + "formID_id": "67", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-11-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 68, + "formID_id": "68", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-12-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 69, + "formID_id": "69", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-02-20", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 70, + "formID_id": "70", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-10-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 71, + "formID_id": "71", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-04-15", + "status_id": "Approved" +}]).on_conflict_replace().execute() + ############################# # admin Notes ############################# @@ -1227,7 +1272,7 @@ { "termCode": 202600, "department": 1, - "isFinal": False, + "isFinal": True, "approvedOn": None, "approvedBy": None, "justification": "Maintaining current staffing levels while allowing for moderate growth in student employment opportunities.", From c3e9cc1e7f0d5ac359f40987670f3085cd50f7a6 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 11:06:07 -0400 Subject: [PATCH 051/128] Request allocation modification button --- app/static/css/allocationTable.css | 6 ++++-- app/templates/main/allocationTable.html | 13 ++++++++----- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index a908d4f4a..2d95f1469 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -3,9 +3,7 @@ grid-template-columns: 1fr 1fr; } .btn-success{ - justify-self:end; align-self: center; - margin-left:8rem; width:fit-content; height:fit-content } @@ -29,4 +27,8 @@ } .table-striped tbody tr:nth-of-type(odd) { background-color: #f2f2f2; /* Your custom color */ +} +.btn-info{ + justify-self:end; + align-self: center; } \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 03aa702aa..14cb2ab22 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -15,10 +15,13 @@ {% block app_content %} -
-

View {{department.DEPT_NAME}} Allocations

- -
*Position allocations are placed (contracted/allocated) in the table
+
+

View {{department.DEPT_NAME}} Allocations

+
+ + Request Allocation Modifications +
+

@@ -131,7 +134,7 @@

-
- +
@@ -52,32 +52,69 @@

+ + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - -
Primaries
Total Contracts{{fallContracts["used_total"]}}{{allocations["totalAllocations"]}}
Total Primaries{{fallContracts["used_primaries"]}}{{allocations["totalPrimaries"]}}
10 Hour{{fallContracts["used_10"]}}{{allocations["primary_10"]}}
12 Hour{{fallContracts["used_12"]}}{{allocations["primary_12"]}}
15 Hour{{fallContracts["used_15"]}}{{allocations["primary_15"]}}
Total Primaries{{fallContracts["used_total"]}}{{allocations["totalPrimaries"]}}
10 Hour{{fallContracts["used_10"]}}{{allocations["primary_10"]}}
12 Hour{{fallContracts["used_12"]}}{{allocations["primary_12"]}}
15 Hour{{fallContracts["used_15"]}}{{allocations["primary_15"]}}
20 Hour {{fallContracts["used_20"]}} {{allocations["primary_20"]}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SecondariesContractedAllocated
Total Contracts{{fallContracts["used_total"]}}{{allocations["totalAllocations"]}}
Total Secondaries{{fallContracts["used_secondaries"]}}{{allocations["totalSecondaries"]}}
5 Hour{{fallContracts["used_5_sec"]}}{{allocations["secondary_5"]}}
10 Hour{{fallContracts["used_10_sec"]}}{{allocations["secondary_10"]}}
@@ -92,7 +129,7 @@

- +
@@ -101,32 +138,69 @@

+ + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - +
Primaries
Total Contracts{{springContracts["used_total"]}}{{allocations["totalAllocations"]}}
Total Primaries{{springContracts["used_primaries"]}}{{allocations["totalPrimaries"]}}
10 Hour{{springContracts["used_10"]}}{{allocations["primary_10"]}}
12 Hour{{springContracts["used_12"]}}{{allocations["primary_12"]}}
15 Hour{{springContracts["used_15"]}}{{allocations["primary_15"]}}
Total Primaries{{springContracts["used_total"]}}{{allocations["totalPrimaries"]}}
10 Hour{{springContracts["used_10"]}}{{allocations["primary_10"]}}
12 Hour{{springContracts["used_12"]}}{{allocations["primary_12"]}}
15 Hour{{springContracts["used_15"]}}{{allocations["primary_15"]}}
20 Hour20 Hour {{springContracts["used_20"]}} {{allocations["primary_20"]}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SecondariesContractedAllocated
Total Contracts{{springContracts["used_total"]}}{{allocations["totalAllocations"]}}
Total Secondaries{{springContracts["used_secondaries"]}}{{allocations["totalSecondaries"]}}
5 Hour{{springContracts["used_5_sec"]}}{{allocations["secondary_5"]}}
10 Hour{{springContracts["used_10_sec"]}}{{allocations["secondary_10"]}}
From f9670d35592560e68ff0be78cfc8e7a668346613 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 11:41:53 -0400 Subject: [PATCH 053/128] table now calls allocations --- app/controllers/main_routes/main_routes.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 8db35c343..d73b34cbb 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -111,17 +111,17 @@ def allocationTable(org=None, account=None): currentAY = Term.select().where(Term.termCode == currentDate[:4] + "00").get() springTerm = Term.select().where(Term.termCode == currentDate[:4] + "12").get() - - allocationDict = {"primary_10": 1, - "primary_12": 2, - "primary_15": 3, - "primary_20": 4, - "secondary_5": 5, - "secondary_10": 6, - "breakHours": 500, - "totalPrimaries": 10, - "totalSecondaries": 11, - "totalAllocations": 21} + allocationDict = getTotalAllocations(currentAY, dept) + # allocationDict = {"primary_10": 1, + # "primary_12": 2, + # "primary_15": 3, + # "primary_20": 4, + # "secondary_5": 5, + # "secondary_10": 6, + # "breakHours": 500, + # "totalPrimaries": 10, + # "totalSecondaries": 11, + # "totalAllocations": 21} fallContracts = getContractedAllocations(fallTerm, dept) springContracts = getContractedAllocations(springTerm, dept) From 23e84d1ad6834482e13f8414b77c958633cf1e09 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 11:48:59 -0400 Subject: [PATCH 054/128] fixed table sizing --- app/static/css/allocationTable.css | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index 2d95f1469..41b8c6189 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -25,8 +25,12 @@ padding-left: 5%; padding-right: 5%; } +.table-striped { + table-layout:fixed; + width:100%; +} .table-striped tbody tr:nth-of-type(odd) { - background-color: #f2f2f2; /* Your custom color */ + background-color: #f2f2f2; } .btn-info{ justify-self:end; From 4b746f020be4e65906336a9cf9d3cffb5b9ce0da Mon Sep 17 00:00:00 2001 From: ACBerea Date: Tue, 4 Aug 2026 12:03:03 -0400 Subject: [PATCH 055/128] Fully fixed the .sidebar-push issue for all pages, reformatted demo_data.py position history sections for readability, fixed the back button link to ensusure that it works with both the manage positions page and individual positions page, and updated the test suite test_download.py to account for new HTML removal and tag functions. --- app/static/css/individualPositions.css | 11 +- app/static/css/sidebar.css | 2 +- app/templates/main/individualPositions.html | 4 +- database/demo_data.py | 227 ++++++++++++++++++-- tests/code/test_download.py | 106 +++++++-- 5 files changed, 299 insertions(+), 51 deletions(-) diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index 505a80765..4274dc5e3 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -37,21 +37,19 @@ .description-header { font-weight: 700; + font-size: 2.5rem; } .position-container { margin: 2rem; - } -/* Description section card */ .position-description { text-align: left; - margin-top: 0; - margin-bottom: 0.75rem; + margin-top: 1rem; + margin-bottom: 2rem; font-size: 1.25rem; - white-space: pre-line; - /* overflow-wrap: break-word; */ + overflow-wrap: break-word; word-wrap: break-word; } @@ -61,7 +59,6 @@ width: 100%; max-width: 100%; box-sizing: border-box; - overflow-wrap: anywhere; word-wrap: break-word; } diff --git a/app/static/css/sidebar.css b/app/static/css/sidebar.css index cb062a9fe..8063dbf69 100644 --- a/app/static/css/sidebar.css +++ b/app/static/css/sidebar.css @@ -65,7 +65,7 @@ .sidebar-push { left: 0 !important; - width: 100vw !important; + width: 95vw !important; margin-right: 0 !important; } diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index e0e14f348..19dde5946 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -51,12 +51,12 @@

{{ department.DEPT_NAME }}

{%- endfor %} {%- else %}

No description available.

- {%- endif %}side + {%- endif %}
diff --git a/database/demo_data.py b/database/demo_data.py index 2db83d6b5..01105acc1 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -951,99 +951,282 @@ { "position": 2, "sectionTitle": '

WLS Level Justification

', - "sectionContent": '

This position is assigned WLS 2 because it supports key research work with moderate technical complexity.

', + "sectionContent": """ +

This position is assigned WLS 2 because it supports key research work with moderate technical complexity.

+ """, "order": 1, }, { "position": 2, "sectionTitle": '

Description of Duties

', - "sectionContent": '

Provide research assistance, coordinate data collection, and help prepare reports.

', + "sectionContent": """ +

Provide research assistance, coordinate data collection, and help prepare reports.

+ """, "order": 2, }, { "position": 2, "sectionTitle": '

Learning Opportunities

', - "sectionContent": '

Gain experience with research practices, data management, and academic collaboration.

', + "sectionContent": """ +

Gain experience with research practices, data management, and academic collaboration.

+ """, "order": 3, }, { "position": 2, "sectionTitle": '

Required Qualifications

', - "sectionContent": '

Strong communication skills, attention to detail, and ability to work independently.

', + "sectionContent": """ +

Strong communication skills, attention to detail, and ability to work independently.

+ """, "order": 4, }, { "position": 3, "sectionTitle": '

WLS Level Justification

', - "sectionContent": '

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

', + "sectionContent": """ +

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

+ """, "order": 1, }, { "position": 3, "sectionTitle": '

Description of Duties

', - "sectionContent": '

A. Workplace Responsibility

\n\n

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

\n\n

B. Communication

\n\n

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

\n\n

C. Teamwork & Collaboration

\n\n

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

\n\n

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

\n\n

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

\n\n

E. Utilize Technology Effectively in the Workplace

\n\n

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

\n\n

F. Connect Work Experience to Career and Academic Goals

\n\n

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

\n\n

G. Foster Creativity and Innovation in the Workplace

\n\n

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

', + "sectionContent": """ +

A. Workplace Responsibility

+

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

+ +

B. Communication

+

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

+ +

C. Teamwork & Collaboration

+

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

+ +

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

+

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

+ +

E. Utilize Technology Effectively in the Workplace

+

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

+ +

F. Connect Work Experience to Career and Academic Goals

+

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

+ +

G. Foster Creativity and Innovation in the Workplace

+

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

+ """, "order": 2, }, { "position": 3, - "sectionTitle": '

Learning Opportunities

', - "sectionContent": '

List how this position will support student learning through daily responsibilities and intentional reflection.

Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

\n\n

A. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

\n\n

B. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

\n\n

C. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

\n\n

D. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

\n\n

E. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

', + "sectionTitle": "

Learning Opportunities

", + "sectionContent": """ +

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

+ +

A. Peer Instruction and Facilitation

+

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

+ +

B. Inventory and Resource Management

+

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

+ +

C. Problem Solving

+

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

+ +

D. Technical Competency

+

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

+ +

E. Communication

+

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

+ """, "order": 3, }, { "position": 3, - "sectionTitle": '

Required Qualifications

', - "sectionContent": '

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

\n\n

A. Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

\n\n

B. Ability to take advice and respond appropriately. C. A desire to mentor and work with high school students. D. Patience working with unskilled yet energetic high school students. E. Some basic understanding of software and debugging.

', - "order": 4, + "sectionTitle": "

Required Qualifications

", + "sectionContent": """ +

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

+ +

A. Independence

+

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

+ +

B. Responsiveness to Feedback

+

Ability to take advice and respond appropriately.

+ +

C. Mentorship

+

A desire to mentor and work with high school students.

+ +

D. Patience

+

Patience working with unskilled yet energetic high school students.

+ +

E. Software Knowledge

+

Some basic understanding of software and debugging.

+ """, + "order": 4, }, { "position": 4, "sectionTitle": '

WLS Level Justification

', - "sectionContent": '

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

', + "sectionContent": """ +

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

+ """, "order": 1, }, { "position": 4, "sectionTitle": '

Description of Duties

', - "sectionContent": '

A. Workplace Responsibility

\n\n

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

\n\n

B. Communication

\n\n

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

\n\n

C. Teamwork & Collaboration

\n\n

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

\n\n

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

\n\n

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

\n\n

E. Utilize Technology Effectively in the Workplace

\n\n

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

\n\n

F. Connect Work Experience to Career and Academic Goals

\n\n

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

\n\n

G. Foster Creativity and Innovation in the Workplace

\n\n

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

', + "sectionContent": """ +

A. Workplace Responsibility

+

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

+ +

B. Communication

+

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

+ +

C. Teamwork & Collaboration

+

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

+ +

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

+

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

+ +

E. Utilize Technology Effectively in the Workplace

+

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

+ +

F. Connect Work Experience to Career and Academic Goals

+

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

+ +

G. Foster Creativity and Innovation in the Workplace

+

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

+ """, "order": 2, }, { "position": 4, - "sectionTitle": '

Learning Opportunities

', - "sectionContent": '

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

\n\n

A. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

\n\n

B. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4) C. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3) D. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5) E. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

', + "sectionTitle": "

Learning Opportunities

", + "sectionContent": """ +

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

+ +

A. Peer Instruction and Facilitation

+

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

+ +

B. Inventory and Resource Management

+

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

+ +

C. Problem Solving

+

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

+ +

D. Technical Competency

+

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

+ +

E. Communication

+

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

+ """, "order": 3, }, { "position": 4, - "sectionTitle": '

Required Qualifications

', - "sectionContent": '

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.

\n\n

A. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.

\n\n

B. Ability to take advice respond appropriately. C. A desire to mentor work with high school students. D. Patience working with unskilled yet energetic high school students. E. Some basic understanding of software debugging.

', + "sectionTitle": "

Required Qualifications

", + "sectionContent": """ +

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

+ +

A. Independence

+

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

+ +

B. Responsiveness to Feedback

+

Ability to take advice and respond appropriately.

+ +

C. Mentorship

+

A desire to mentor and work with high school students.

+ +

D. Patience

+

Patience working with unskilled yet energetic high school students.

+ +

E. Software Knowledge

+

Some basic understanding of software and debugging.

+ """, "order": 4, }, { "position": 5, "sectionTitle": '

WLS Level Justification

', - "sectionContent": '

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

', + "sectionContent": """ +

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

+ """, "order": 1, }, { "position": 5, "sectionTitle": '

Description of Duties

', - "sectionContent": '

A. Workplace Responsibility

\n\n

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

\n\n

B. Communication

\n\n

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

\n\n

C. Teamwork & Collaboration

\n\n

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

\n\n

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

\n\n

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

\n\n

E. Utilize Technology Effectively in the Workplace

\n\n

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

\n\n

F. Connect Work Experience to Career and Academic Goals

\n\n

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

\n\n

G. Foster Creativity and Innovation in the Workplace

\n\n

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

', + "sectionContent": """ +

A. Workplace Responsibility

+

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

+ +

B. Communication

+

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

+ +

C. Teamwork & Collaboration

+

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

+ +

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

+

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

+ +

E. Utilize Technology Effectively in the Workplace

+

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

+ +

F. Connect Work Experience to Career and Academic Goals

+

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

+ +

G. Foster Creativity and Innovation in the Workplace

+

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

+ """, "order": 2, }, { "position": 5, "sectionTitle": '

Learning Opportunities

', - "sectionContent": '

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

\n\n

A. Peer Instruction and Facilitation - Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

\n\n

B. Inventory and Resource Management - Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4) C. Problem Solving - Debugging code and testing said code on relevant robots. (Aligned with: Goal 3) D. Technical Competency - Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5) E. Communication - Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

', + "sectionContent": """ +

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

+ +

A. Peer Instruction and Facilitation

+

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

+ +

B. Inventory and Resource Management

+

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

+ +

C. Problem Solving

+

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

+ +

D. Technical Competency

+

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

+ +

E. Communication

+

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

+ """, "order": 3, }, { "position": 5, "sectionTitle": '

Required Qualifications

', - "sectionContent": '

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity accessibility.

\n\n

A. Ability to function with a little more independence complete tasks with assistance from team leader(s) other student colleagues.

\n\n

B. Ability to take advice respond appropriately. C. A desire to mentor work with high school students. D. Patience working with unskilled yet energetic high school students. E. Some basic understanding of software debugging.

', + "sectionContent": """ +

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

+ +

A. Independence

+

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

+ +

B. Responsiveness to Feedback

+

Ability to take advice and respond appropriately.

+ +

C. Mentorship

+

A desire to mentor and work with high school students.

+ +

D. Patience

+

Patience working with unskilled yet energetic high school students.

+ +

E. Software Knowledge

+

Some basic understanding of software and debugging.

+ """, "order": 4, }, ] -PositionDescriptionSection.insert_many(positionDescriptionSections).on_conflict_replace().execute() + +PositionDescriptionSection.insert_many( + positionDescriptionSections +).on_conflict_replace().execute() + print(" * position description sections added") diff --git a/tests/code/test_download.py b/tests/code/test_download.py index 287a2fe1c..7309dc7ee 100644 --- a/tests/code/test_download.py +++ b/tests/code/test_download.py @@ -2,21 +2,66 @@ import pytest -from app.logic.download import makePositionDescriptionPDF +from app.logic.download import makePositionDescriptionPDF, removeHTML from app.models import mainDB from app.models.department import Department -from app.models.positionHistory import PositionHistory from app.models.positionDescriptionSection import PositionDescriptionSection +from app.models.positionHistory import PositionHistory + @pytest.mark.integration def test_makePositionDescriptionPDF(): """ - Tests that makePositionDescriptionPDF generates valid PDF buffers - for positions with and without description sections. + Tests both HTML stripping and PDF generation using the makePositionDescriptionPDF() function. """ with mainDB.atomic() as transaction: - # Create the department used by all positions in the test. + # --------------------------------------------------------- + # Test the HTML-stripping functionality directly. + # --------------------------------------------------------- + + headingResult = removeHTML( + "

Learning Opportunities

" + ) + + # Confirm that the heading text remains. + assert "Learning Opportunities" in headingResult + + # Confirm that the HTML tags were removed. + assert "

" not in headingResult + assert "

" not in headingResult + + sectionResult = removeHTML( + """ +

A. Equipment Management

+

Maintains laboratory equipment & supplies.

+ +

B. Experiment Support

+

Supports experiments and records results.

+ """ + ) + + # Normalize whitespace so the test does not depend on the exact number of newlines produced by removeHTML(). + normalizedSectionResult = " ".join(sectionResult.split()) + + # Confirm that all readable content remains after stripping HTML. + assert "A. Equipment Management" in normalizedSectionResult + assert "Maintains laboratory equipment & supplies." in normalizedSectionResult + assert "B. Experiment Support" in normalizedSectionResult + assert "Supports experiments and records results." in normalizedSectionResult + + # Confirm that HTML tags and encoded entities were removed. + assert "

" not in normalizedSectionResult + assert "

" not in normalizedSectionResult + assert "

" not in normalizedSectionResult + assert "

" not in normalizedSectionResult + assert "&" not in normalizedSectionResult + + # --------------------------------------------------------- + # Create fake database records needed for PDF generation. + # --------------------------------------------------------- + + # Create the department shared by both test positions. department = Department.create( departmentID=200, DEPT_NAME="Physics", @@ -26,9 +71,9 @@ def test_makePositionDescriptionPDF(): isActive=True, ) - # Create a position that will have description sections. + # Create a position with HTML-formatted description sections. positionWithSections = PositionHistory.create( - positionTitle="Lab Technician", + positionTitle="Lab Technician", positionCode="S34516", department=department, status="Active", @@ -37,7 +82,7 @@ def test_makePositionDescriptionPDF(): revisedBy="Jane Doe", ) - # Create a position that will not have description sections. + # Create a position without description sections to test for "No description available." output. positionWithoutSections = PositionHistory.create( positionTitle="Research Assistant", positionCode="S34517", @@ -48,53 +93,76 @@ def test_makePositionDescriptionPDF(): revisedBy="Sarah Smith", ) - # Create sections for the first position. Insertion is out of order in order to test the section query's ordering logic is applied when the PDF is generated. + # Insert this section first even though its order is 2. Tests section-ordering logic used by getPositionDescriptionSections(). PositionDescriptionSection.create( position=positionWithSections, - sectionTitle="Responsibilities", - sectionContent="Supports experiments and records results.", + sectionTitle="

Responsibilities

", + sectionContent=""" +

A. Equipment Management

+

Maintains laboratory equipment & supplies.

+ +

B. Experiment Support

+

Supports experiments and records results.

+ """, order=2, ) + # Insert the order-1 section second. PositionDescriptionSection.create( position=positionWithSections, - sectionTitle="Position Summary", - sectionContent="Maintains laboratory equipment.", + sectionTitle="

Position Summary

", + sectionContent=""" +

Provides support for laboratory research.

+

Works with faculty and student researchers.

+ """, order=1, ) - # Generate a PDF for the position that has description sections. + # --------------------------------------------------------- + # Test PDF generation for a position with sections. + # --------------------------------------------------------- + pdfBufferWithSections = makePositionDescriptionPDF( department, positionWithSections, ) - # Verify that the result is a nonempty BytesIO containing a PDF. + # Confirm that the function returns an in-memory byte buffer. assert isinstance(pdfBufferWithSections, io.BytesIO) pdfBytesWithSections = pdfBufferWithSections.getvalue() + # Confirm that the generated PDF is not empty. assert len(pdfBytesWithSections) > 0 + + # Confirm that the output begins with the standard PDF header. assert pdfBytesWithSections.startswith(b"%PDF-") + + # Confirm that the output ends with the standard PDF marker. assert pdfBytesWithSections.rstrip().endswith(b"%%EOF") - # Generate a PDF for the position without description sections. This exercises the "No description available." branch. + # --------------------------------------------------------- + # Test PDF generation for a position without sections. + # --------------------------------------------------------- + pdfBufferWithoutSections = makePositionDescriptionPDF( department, positionWithoutSections, ) - # Verify that the second result is also a valid PDF buffer. + # Confirm that the fallback branch also returns a BytesIO object. assert isinstance(pdfBufferWithoutSections, io.BytesIO) pdfBytesWithoutSections = pdfBufferWithoutSections.getvalue() + # Confirm that the fallback PDF is not empty. assert len(pdfBytesWithoutSections) > 0 + + # Confirm that the fallback output is also a valid PDF. assert pdfBytesWithoutSections.startswith(b"%PDF-") assert pdfBytesWithoutSections.rstrip().endswith(b"%%EOF") - # Verify that the two different positions produce different PDFs. + # Confirm that the two positions did not produce identical PDFs. assert pdfBytesWithSections != pdfBytesWithoutSections - # Roll back all database records created by this test. transaction.rollback() \ No newline at end of file From 54a3f132e69521bbaf612d56340d88a7135532da Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 14:25:12 -0400 Subject: [PATCH 056/128] added arrows to the accordions in th tables --- app/static/css/allocationTable.css | 10 ++++++++++ app/templates/main/allocationTable.html | 22 ++++++++++++---------- 2 files changed, 22 insertions(+), 10 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index 41b8c6189..4455396c6 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -35,4 +35,14 @@ .btn-info{ justify-self:end; align-self: center; +} +.accordion-arrow { + display: inline-block; + transition: transform 0.2s ease-in-out; +} +.btn.collapsed .accordion-arrow { + transform: rotate(-90deg); +} +.btn:not(.collapsed) .accordion-arrow { + transform: rotate(0deg); } \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index a0e8c13e0..23e832dc6 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -37,9 +37,10 @@

Current Term: {{currentAY.termName}}

@@ -121,11 +122,11 @@

-
@@ -209,9 +210,10 @@

From cc374dc2d0cbd17defb57c3042bffc3968c8fbb2 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 14:43:26 -0400 Subject: [PATCH 057/128] fixed formatting of html --- app/templates/main/allocationTable.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 23e832dc6..ad0424cc6 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -43,7 +43,7 @@

Current Term: {{currentAY.termName}}

-
+
@@ -129,7 +129,7 @@

Current Term: {{currentAY.termName}}

-
+
@@ -216,7 +216,7 @@

Current Term: {{currentAY.termName}}

-
+
From 827f3902b0bf82dfc9f80a42773c515f3f649d42 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 15:04:28 -0400 Subject: [PATCH 058/128] changed allocation req wording --- app/templates/main/allocationTable.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index ad0424cc6..693402791 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -19,7 +19,7 @@

View {{department.DEPT_NAME}} Allocations

- Request Allocation Modifications + Request Allocation
From 597877497cfc645109c56b80b157ae0aa6d2b7c7 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 15:20:04 -0400 Subject: [PATCH 059/128] fixed the contract index, the total returns correctly now --- app/controllers/main_routes/main_routes.py | 10 -- app/logic/allocationManager.py | 8 +- app/templates/main/allocationTable.html | 1 - database/demo_data.py | 105 +++++++++++++++++++++ 4 files changed, 109 insertions(+), 15 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index d73b34cbb..c3717d39d 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -112,16 +112,6 @@ def allocationTable(org=None, account=None): springTerm = Term.select().where(Term.termCode == currentDate[:4] + "12").get() allocationDict = getTotalAllocations(currentAY, dept) - # allocationDict = {"primary_10": 1, - # "primary_12": 2, - # "primary_15": 3, - # "primary_20": 4, - # "secondary_5": 5, - # "secondary_10": 6, - # "breakHours": 500, - # "totalPrimaries": 10, - # "totalSecondaries": 11, - # "totalAllocations": 21} fallContracts = getContractedAllocations(fallTerm, dept) springContracts = getContractedAllocations(springTerm, dept) diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index 75dd303d6..d252ade50 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -40,7 +40,7 @@ def getTotalAllocations(termCode, dept): return allocationDict def countContracts(jobType, weeklyContractHours, termCode, dept): - academicYearCode = int(str(termCode)[:4] + "999") + academicYearCode = int(str(termCode)[:5] + "00") lsfCountPrimaries = FormHistory.select( ).join(LaborStatusForm ).join(Department @@ -94,9 +94,9 @@ def getContractedAllocations(termCode, dept): "used_total": 0, "break_hours": breakSum["total_hours"] } - usedPositions["used_primaries"] = sum(list(usedPositions.values())[:5]) - usedPositions["used_secondaries"] = sum(list(usedPositions.values())[5:7]) - usedPositions["used_total"] = sum(list(usedPositions.values())[:7]) + usedPositions["used_primaries"] = sum(list(usedPositions.values())[:4]) + usedPositions["used_secondaries"] = sum(list(usedPositions.values())[4:6]) + usedPositions["used_total"] = sum(list(usedPositions.values())[:6]) return usedPositions def getBreakContracts(termCode, dept): diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 693402791..c42db5653 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -24,7 +24,6 @@

View {{department.DEPT_NAME}} Allocations

-

Current Term: {{currentAY.termName}}

diff --git a/database/demo_data.py b/database/demo_data.py index e64ac8c6e..a05ada155 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -972,6 +972,111 @@ "status_id": "Approved" }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + "laborStatusFormID": 72, + "termCode_id": "202612", + "studentName": "Alex Carter", + "studentSupervisee_id": "B12345762", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Office Assistant", + "POSN_CODE": "S61413", + "weeklyHours": 10, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 72, + "formID_id": "72", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 73, + "termCode_id": "202612", + "studentName": "Morgan Hayes", + "studentSupervisee_id": "B12345763", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Computer Lab Assistant", + "POSN_CODE": "S61414", + "weeklyHours": 15, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 73, + "formID_id": "73", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 74, + "termCode_id": "202612", + "studentName": "Taylor Morgan", + "studentSupervisee_id": "B12345765", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 0, + "POSN_TITLE": "Reception Assistant", + "POSN_CODE": "S61416", + "weeklyHours": 5, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 74, + "formID_id": "74", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +# Student had a Fall-only position and receives a new Spring assignment. + +LaborStatusForm.insert([{ + "laborStatusFormID": 75, + "termCode_id": "202612", + "studentName": "Jordan Brooks", + "studentSupervisee_id": "B12345764", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Technology Assistant", + "POSN_CODE": "S61423", + "weeklyHours": 12, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 75, + "formID_id": "75", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + # Break Positions From 32f8f6b1180768c68c9e3b195a9efe02ce024185 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 15:36:38 -0400 Subject: [PATCH 060/128] fixed the sum of break hours --- app/controllers/main_routes/main_routes.py | 2 ++ app/templates/main/allocationTable.html | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index c3717d39d..b228aa5ed 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -116,12 +116,14 @@ def allocationTable(org=None, account=None): springContracts = getContractedAllocations(springTerm, dept) breakContracts = { + "total": 0, "thanksgiving":getBreakContracts(currentAY.termCode + 1, dept), "winter": getBreakContracts(currentAY.termCode + 2, dept), "spring": getBreakContracts(currentAY.termCode + 3, dept), "fall":getBreakContracts(currentAY.termCode + 4, dept), "summer": getBreakContracts(currentAY.termCode + 13, dept) } + breakContracts["total"] = sum(breakContracts.values()) return render_template('main/allocationTable.html', department = dept, diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index c42db5653..6ac3669b7 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -227,7 +227,7 @@

Current Term: {{currentAY.termName}}

- + From 51103592be65dbebcb439a7a5175d0a11ead0e7b Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 15:45:08 -0400 Subject: [PATCH 061/128] added the fall break row --- app/templates/main/allocationTable.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 6ac3669b7..2d06173cf 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -18,7 +18,7 @@

View {{department.DEPT_NAME}} Allocations

- + Download Allocation History Request Allocation
@@ -231,7 +231,12 @@

Current Term: {{currentAY.termName}}

- + + + + + + From 87d460486a1218e9b792b7db4aca0adfd00b9c97 Mon Sep 17 00:00:00 2001 From: ACBerea Date: Tue, 4 Aug 2026 15:59:46 -0400 Subject: [PATCH 062/128] Fixed spacing issues in download function. Fixed UI issues in both demo_data.py, individualPositions .html and .css. Removed large comments from test_download.py. --- app/logic/download.py | 86 +++++-------- app/static/css/individualPositions.css | 28 +++- app/templates/main/individualPositions.html | 2 +- database/demo_data.py | 134 ++++++++++---------- tests/code/test_download.py | 17 --- 5 files changed, 123 insertions(+), 144 deletions(-) diff --git a/app/logic/download.py b/app/logic/download.py index a7b164bf4..50d040f5a 100644 --- a/app/logic/download.py +++ b/app/logic/download.py @@ -44,22 +44,7 @@ class PDFHTMLTextExtractor(HTMLParser): """ blockTags = { - 'p', - 'div', - 'section', - 'article', - 'header', - 'footer', - 'h1', - 'h2', - 'h3', - 'h4', - 'h5', - 'h6', - 'li', - 'ul', - 'ol', - 'br', + 'p','div','section','article','header','footer','h1','h2','h3','h4','h5','h6','li','ul','ol','br', } def __init__(self): @@ -116,15 +101,12 @@ def makePositionDescriptionPDF(department, position): pdf = FPDF() pdf.add_page() - pdf.set_font('Times', 'BU', 16) - pdf.cell( - 0, - 10, - removeHTML(position.positionTitle), - ln=True, - ) + # Position title + pdf.set_font('Times', 'B', 16) + pdf.cell(0,10,removeHTML(position.positionTitle).encode('latin-1', 'replace').decode('latin-1'),ln=True) pdf.ln(2) + # Position metadata fields = [ ('Department Name', department.DEPT_NAME), ('Position Code', position.positionCode), @@ -134,61 +116,53 @@ def makePositionDescriptionPDF(department, position): ('Revised By', position.revisedBy), ] - labelWidth = 45 + label_width = 45 for label, value in fields: pdf.set_font('Times', 'B', 11) - pdf.cell(labelWidth, 8, f'{label}:', ln=False) + pdf.cell(label_width, 8, f'{label}:', ln=False) - pdf.set_font('Times', '', 11) - - plainValue = removeHTML(value) - plainValue = plainValue.encode( - 'latin-1', - 'replace', - ).decode('latin-1') + plain_value = removeHTML(value).encode('latin-1', 'replace').decode('latin-1') - pdf.cell(0, 8, f' {plainValue}', ln=True) + pdf.set_font('Times', '', 11) + pdf.cell(0, 8, f' {plain_value}', ln=True) sections = getPositionDescriptionSections(position) pdf.ln(4) if sections: - for section in sections: - title = removeHTML(section.sectionTitle) - content = removeHTML(section.sectionContent) - - title = title.encode( - 'latin-1', - 'replace', - ).decode('latin-1') - - content = content.encode( - 'latin-1', - 'replace', - ).decode('latin-1') - + for index, section in enumerate(sections): + title = removeHTML(section.sectionTitle).encode('latin-1', 'replace').decode('latin-1') + content = removeHTML(section.sectionContent).encode('latin-1', 'replace').decode('latin-1') + + # Horizontal rule before the description sections + if index == 0: + pdf.set_draw_color(180, 180, 180) + pdf.set_line_width(0.3) + y = pdf.get_y() + pdf.line(pdf.l_margin, y, pdf.w - pdf.r_margin, y) + pdf.ln(3) + + # Section heading pdf.set_font('Times', 'B', 12) - pdf.multi_cell(0, 10, title) + pdf.multi_cell(0, 8, title) + # Section content pdf.set_font('Times', '', 11) - pdf.multi_cell(0, 7, content) + pdf.multi_cell(0, 5, content) pdf.ln(2) + else: pdf.set_font('Times', 'B', 12) pdf.cell(0, 10, 'Description', ln=True) pdf.set_font('Times', '', 11) - pdf.multi_cell(0, 7, 'No description available.') - - pdfBytes = pdf.output(dest='S').encode( - 'latin-1', - 'replace', - ) + pdf.multi_cell(0, 5, 'No description available.') - return io.BytesIO(pdfBytes) + pdf_bytes = pdf.output(dest='S').encode('latin-1', 'replace') + return io.BytesIO(pdf_bytes) class CSVMaker: diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css index 4274dc5e3..9e22d5843 100644 --- a/app/static/css/individualPositions.css +++ b/app/static/css/individualPositions.css @@ -35,9 +35,31 @@ font-size: 2rem; } -.description-header { - font-weight: 700; - font-size: 2.5rem; +h2.description-header { + font-weight: 600; + font-size: 2rem; +} + + +.position-description h3 { + font-size: 1.7rem; + font-weight: 600; + margin-top: 1.5rem; + margin-bottom: 0.5rem; +} + +.position-description h4 { + font-size: 1.25rem; + font-weight: 300; + margin-top: 1.25rem; + margin-bottom: 0.5rem; +} + +.position-description h5 { + font-size: 1.15rem; + font-weight: 600; + margin-top: 1rem; + margin-bottom: 0.35rem; } .position-container { diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html index 19dde5946..87bd44ba4 100644 --- a/app/templates/main/individualPositions.html +++ b/app/templates/main/individualPositions.html @@ -42,7 +42,7 @@

{{ department.DEPT_NAME }}

-
Description
+

Description:

{%- if sections %} {%- for section in sections %} diff --git a/database/demo_data.py b/database/demo_data.py index 01105acc1..1b1e79783 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -950,7 +950,7 @@ positionDescriptionSections = [ { "position": 2, - "sectionTitle": '

WLS Level Justification

', + "sectionTitle": '

WLS Level Justification

', "sectionContent": """

This position is assigned WLS 2 because it supports key research work with moderate technical complexity.

""", @@ -958,7 +958,7 @@ }, { "position": 2, - "sectionTitle": '

Description of Duties

', + "sectionTitle": '

Description of Duties

', "sectionContent": """

Provide research assistance, coordinate data collection, and help prepare reports.

""", @@ -966,7 +966,7 @@ }, { "position": 2, - "sectionTitle": '

Learning Opportunities

', + "sectionTitle": '

Learning Opportunities

', "sectionContent": """

Gain experience with research practices, data management, and academic collaboration.

""", @@ -974,7 +974,7 @@ }, { "position": 2, - "sectionTitle": '

Required Qualifications

', + "sectionTitle": '

Required Qualifications

', "sectionContent": """

Strong communication skills, attention to detail, and ability to work independently.

""", @@ -982,7 +982,7 @@ }, { "position": 3, - "sectionTitle": '

WLS Level Justification

', + "sectionTitle": '

WLS Level Justification

', "sectionContent": """

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

""", @@ -990,80 +990,80 @@ }, { "position": 3, - "sectionTitle": '

Description of Duties

', + "sectionTitle": '

Description of Duties

', "sectionContent": """ -

A. Workplace Responsibility

+
A. Workplace Responsibility

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

-

B. Communication

+
B. Communication

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

-

C. Teamwork & Collaboration

+
C. Teamwork & Collaboration

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

-

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

+
D. Apply Critical Thinking and Problem Solving in Workplace Tasks

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

-

E. Utilize Technology Effectively in the Workplace

+
E. Utilize Technology Effectively in the Workplace

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

-

F. Connect Work Experience to Career and Academic Goals

+
F. Connect Work Experience to Career and Academic Goals

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

-

G. Foster Creativity and Innovation in the Workplace

+
G. Foster Creativity and Innovation in the Workplace

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

""", "order": 2, }, { "position": 3, - "sectionTitle": "

Learning Opportunities

", + "sectionTitle": "

Learning Opportunities

", "sectionContent": """

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

-

A. Peer Instruction and Facilitation

+
A. Peer Instruction and Facilitation

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

-

B. Inventory and Resource Management

+
B. Inventory and Resource Management

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

-

C. Problem Solving

+
C. Problem Solving

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

-

D. Technical Competency

+
D. Technical Competency

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

-

E. Communication

+
E. Communication

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

""", "order": 3, }, { "position": 3, - "sectionTitle": "

Required Qualifications

", + "sectionTitle": "

Required Qualifications

", "sectionContent": """

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

-

A. Independence

+
A. Independence

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

-

B. Responsiveness to Feedback

+
B. Responsiveness to Feedback

Ability to take advice and respond appropriately.

-

C. Mentorship

+
C. Mentorship

A desire to mentor and work with high school students.

-

D. Patience

+
D. Patience

Patience working with unskilled yet energetic high school students.

-

E. Software Knowledge

+
E. Software Knowledge

Some basic understanding of software and debugging.

""", "order": 4, }, { "position": 4, - "sectionTitle": '

WLS Level Justification

', + "sectionTitle": '

WLS Level Justification

', "sectionContent": """

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

""", @@ -1071,80 +1071,80 @@ }, { "position": 4, - "sectionTitle": '

Description of Duties

', + "sectionTitle": '

Description of Duties

', "sectionContent": """ -

A. Workplace Responsibility

+
A. Workplace Responsibility

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

-

B. Communication

+
B. Communication

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

-

C. Teamwork & Collaboration

+
C. Teamwork & Collaboration

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

-

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

+
D. Apply Critical Thinking and Problem Solving in Workplace Tasks

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

-

E. Utilize Technology Effectively in the Workplace

+
E. Utilize Technology Effectively in the Workplace

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

-

F. Connect Work Experience to Career and Academic Goals

+
F. Connect Work Experience to Career and Academic Goals

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

-

G. Foster Creativity and Innovation in the Workplace

+
G. Foster Creativity and Innovation in the Workplace

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

""", "order": 2, }, { "position": 4, - "sectionTitle": "

Learning Opportunities

", + "sectionTitle": "

Learning Opportunities

", "sectionContent": """

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

-

A. Peer Instruction and Facilitation

+
A. Peer Instruction and Facilitation

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

-

B. Inventory and Resource Management

+
B. Inventory and Resource Management

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

-

C. Problem Solving

+
C. Problem Solving

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

-

D. Technical Competency

+
D. Technical Competency

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

-

E. Communication

+
E. Communication

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

""", "order": 3, }, { "position": 4, - "sectionTitle": "

Required Qualifications

", + "sectionTitle": "

Required Qualifications

", "sectionContent": """

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

-

A. Independence

+
A. Independence

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

-

B. Responsiveness to Feedback

+
B. Responsiveness to Feedback

Ability to take advice and respond appropriately.

-

C. Mentorship

+
C. Mentorship

A desire to mentor and work with high school students.

-

D. Patience

+
D. Patience

Patience working with unskilled yet energetic high school students.

-

E. Software Knowledge

+
E. Software Knowledge

Some basic understanding of software and debugging.

""", "order": 4, }, { "position": 5, - "sectionTitle": '

WLS Level Justification

', + "sectionTitle": '

WLS Level Justification

', "sectionContent": """

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

""", @@ -1152,73 +1152,73 @@ }, { "position": 5, - "sectionTitle": '

Description of Duties

', + "sectionTitle": '

Description of Duties

', "sectionContent": """ -

A. Workplace Responsibility

+
A. Workplace Responsibility

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

-

B. Communication

+
B. Communication

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

-

C. Teamwork & Collaboration

+
C. Teamwork & Collaboration

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

-

D. Apply Critical Thinking and Problem Solving in Workplace Tasks

+
D. Apply Critical Thinking and Problem Solving in Workplace Tasks

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

-

E. Utilize Technology Effectively in the Workplace

+
E. Utilize Technology Effectively in the Workplace

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

-

F. Connect Work Experience to Career and Academic Goals

+
F. Connect Work Experience to Career and Academic Goals

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

-

G. Foster Creativity and Innovation in the Workplace

+
G. Foster Creativity and Innovation in the Workplace

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

""", "order": 2, }, { "position": 5, - "sectionTitle": '

Learning Opportunities

', + "sectionTitle": '

Learning Opportunities

', "sectionContent": """

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

-

A. Peer Instruction and Facilitation

+
A. Peer Instruction and Facilitation

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

-

B. Inventory and Resource Management

+
B. Inventory and Resource Management

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

-

C. Problem Solving

+
C. Problem Solving

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

-

D. Technical Competency

+
D. Technical Competency

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

-

E. Communication

+
E. Communication

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

""", "order": 3, }, { "position": 5, - "sectionTitle": '

Required Qualifications

', + "sectionTitle": '

Required Qualifications

', "sectionContent": """

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

-

A. Independence

+
A. Independence

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

-

B. Responsiveness to Feedback

+
B. Responsiveness to Feedback

Ability to take advice and respond appropriately.

-

C. Mentorship

+
C. Mentorship

A desire to mentor and work with high school students.

-

D. Patience

+
D. Patience

Patience working with unskilled yet energetic high school students.

-

E. Software Knowledge

+
E. Software Knowledge

Some basic understanding of software and debugging.

""", "order": 4, diff --git a/tests/code/test_download.py b/tests/code/test_download.py index 7309dc7ee..66c53b0c9 100644 --- a/tests/code/test_download.py +++ b/tests/code/test_download.py @@ -15,11 +15,6 @@ def test_makePositionDescriptionPDF(): Tests both HTML stripping and PDF generation using the makePositionDescriptionPDF() function. """ with mainDB.atomic() as transaction: - - # --------------------------------------------------------- - # Test the HTML-stripping functionality directly. - # --------------------------------------------------------- - headingResult = removeHTML( "

Learning Opportunities

" ) @@ -57,10 +52,6 @@ def test_makePositionDescriptionPDF(): assert "

" not in normalizedSectionResult assert "&" not in normalizedSectionResult - # --------------------------------------------------------- - # Create fake database records needed for PDF generation. - # --------------------------------------------------------- - # Create the department shared by both test positions. department = Department.create( departmentID=200, @@ -118,10 +109,6 @@ def test_makePositionDescriptionPDF(): order=1, ) - # --------------------------------------------------------- - # Test PDF generation for a position with sections. - # --------------------------------------------------------- - pdfBufferWithSections = makePositionDescriptionPDF( department, positionWithSections, @@ -141,10 +128,6 @@ def test_makePositionDescriptionPDF(): # Confirm that the output ends with the standard PDF marker. assert pdfBytesWithSections.rstrip().endswith(b"%%EOF") - # --------------------------------------------------------- - # Test PDF generation for a position without sections. - # --------------------------------------------------------- - pdfBufferWithoutSections = makePositionDescriptionPDF( department, positionWithoutSections, From 071232b77853b3cfe9d5e1768e30b8b9d78a23b8 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 16:09:10 -0400 Subject: [PATCH 063/128] moved the header to be centered --- app/static/css/allocationTable.css | 8 +++++--- app/templates/main/allocationTable.html | 5 ++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index 4455396c6..2c935c341 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -4,8 +4,6 @@ } .btn-success{ align-self: center; - width:fit-content; - height:fit-content } .card-header { background-color: #efebeb; @@ -33,7 +31,7 @@ background-color: #f2f2f2; } .btn-info{ - justify-self:end; + justify-self:flex-end; align-self: center; } .accordion-arrow { @@ -45,4 +43,8 @@ } .btn:not(.collapsed) .accordion-arrow { transform: rotate(0deg); +} +.button-container { + display:flex; + justify-content:flex-end; } \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 2d06173cf..fbcd5182d 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -14,14 +14,13 @@ {% block app_content %} +

{% if department %} {{department.DEPT_NAME}} Allocations {% else %} Choose a Department: {% endif %}

-
-

View {{department.DEPT_NAME}} Allocations

+
From fa4a35cfc44f6b8c046b6372c10814fbecfe8738 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 4 Aug 2026 16:39:39 -0400 Subject: [PATCH 064/128] fixed format of accordions --- app/static/css/allocationTable.css | 13 +++++++++++-- app/templates/main/allocationTable.html | 2 -- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index 2c935c341..2fbe640fa 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -4,10 +4,12 @@ } .btn-success{ align-self: center; + justify-self: end; } .card-header { background-color: #efebeb; margin-bottom: 7px; + height: 6rem; } .mb-0, .collapsed { outline-color: none; @@ -45,6 +47,13 @@ transform: rotate(0deg); } .button-container { - display:flex; - justify-content:flex-end; + display:grid; + justify-content:end; +} +.btn-block{ + align-items: center; + align-self: center; + text-align: center; + justify-content: center; + height: 100% } \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index fbcd5182d..e82c4aa70 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -17,10 +17,8 @@

{% if department %} {{department.DEPT_NAME}} Allocations {% else %} Choose a Department: {% endif %}

From 764db13f03ddc988fd054b928c43e4356002329b Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Wed, 5 Aug 2026 10:22:16 -0400 Subject: [PATCH 065/128] fixed the button layout --- app/static/css/allocationTable.css | 6 +++++- app/templates/main/allocationTable.html | 12 ++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index 2fbe640fa..dd1812c1d 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -35,6 +35,7 @@ .btn-info{ justify-self:flex-end; align-self: center; + margin-left: 4px } .accordion-arrow { display: inline-block; @@ -47,7 +48,7 @@ transform: rotate(0deg); } .button-container { - display:grid; + display:flex; justify-content:end; } .btn-block{ @@ -56,4 +57,7 @@ text-align: center; justify-content: center; height: 100% +} +.termDisplay{ + margin-right:auto; } \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index e82c4aa70..0e8c8650e 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -16,16 +16,12 @@

{% if department %} {{department.DEPT_NAME}} Allocations {% else %} Choose a Department: {% endif %}

-
- Download Allocation History - Request Allocation +
+

Current Term: {{currentAY.termName}}

+ Download Allocation History + Request Allocation
- -
-

Current Term: {{currentAY.termName}}

-
-
From 25d9a9be2eafbe8845b70052b066a5d6ee1ca7e0 Mon Sep 17 00:00:00 2001 From: munsakad Date: Wed, 5 Aug 2026 11:24:16 -0400 Subject: [PATCH 066/128] Reuse allocationManager.py in getAllocation.py getDepartmentAllocationSummary now calls allocationManager's getTotalAllocations/getContractedAllocations instead of reimplementing the same counts, removing countWorkers/getBreakHours and the manual Allocation SUM query. Falls back to zeroed defaults for a most-recent term that only has a draft allocation (no final row yet), and coalesces getContractedAllocations' break_hours since its SQL SUM() can return None. Updated test_getAllocation.py to match: removed the now-redundant countWorkers/ getBreakHours unit tests, updated the multi-row-allocation expectation to reflect final-only summing, and added coverage for the draft-only fallback. --- app/logic/getAllocation.py | 103 +++++--------------- tests/code/test_getAllocation.py | 158 +++++++------------------------ 2 files changed, 61 insertions(+), 200 deletions(-) diff --git a/app/logic/getAllocation.py b/app/logic/getAllocation.py index 1f7c879d3..2d45cc9f2 100644 --- a/app/logic/getAllocation.py +++ b/app/logic/getAllocation.py @@ -1,11 +1,8 @@ from datetime import date -from peewee import fn - +from app.logic.allocationManager import getContractedAllocations, getTotalAllocations from app.models.allocation import Allocation -from app.models.laborStatusForm import LaborStatusForm from app.models.term import Term -from app.models.formHistory import FormHistory def getCurrentSemesterLabel(term): @@ -19,39 +16,6 @@ def getCurrentSemesterLabel(term): return f"Spring {academicYear + 1}" -def countWorkers(department, termCode, jobType, hoursBucket): - workerCount = ( - LaborStatusForm.select() - .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) - .where( - LaborStatusForm.department == department, - LaborStatusForm.termCode == termCode, - LaborStatusForm.jobType == jobType, - LaborStatusForm.weeklyHours == hoursBucket, - LaborStatusForm.contractHours.is_null(True), - FormHistory.historyType == "Labor Status Form", - ~(FormHistory.status % "Denied%"), - ) - .count() - ) - return workerCount - - -def getBreakHours(department, termCode): - breakHoursTotal = ( - LaborStatusForm.select(fn.SUM(LaborStatusForm.contractHours)) - .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) - .where( - LaborStatusForm.department == department, - LaborStatusForm.termCode == termCode, - FormHistory.historyType == "Labor Status Form", - FormHistory.status == "Approved", - ) - .scalar() - ) or 0 - return breakHoursTotal - - def getDepartmentAllocationSummary(department): """Return allocation-utilization values for a department's most recent term.""" result = { @@ -81,46 +45,29 @@ def getDepartmentAllocationSummary(department): result["term"] = recentTerm result["currentSemester"] = getCurrentSemesterLabel(recentTerm) - totalPositions = ( - Allocation.select( - fn.SUM(Allocation.primary_10) - + fn.SUM(Allocation.primary_12) - + fn.SUM(Allocation.primary_15) - + fn.SUM(Allocation.primary_20) - + fn.SUM(Allocation.secondary_5) - + fn.SUM(Allocation.secondary_10) - ) - .where( - Allocation.department == department, - Allocation.termCode == termCode, - ) - .scalar() - ) - result["allocated"] = totalPositions or 0 - - usedAllocation = ( - LaborStatusForm.select() - .join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) - .where( - LaborStatusForm.department == department, - LaborStatusForm.termCode == termCode, - LaborStatusForm.contractHours.is_null(True), - FormHistory.historyType == "Labor Status Form", - ~(FormHistory.status % "Denied%"), - ) - .count() - ) - result["used"] = usedAllocation - - result["usedPositions"] = { - "used10": countWorkers(department, termCode, "Primary", 10), - "used12": countWorkers(department, termCode, "Primary", 12), - "used15": countWorkers(department, termCode, "Primary", 15), - "used20": countWorkers(department, termCode, "Primary", 20), - "usedSecondary5": countWorkers(department, termCode, "Secondary", 5), - "usedSecondary10": countWorkers(department, termCode, "Secondary", 10), - } - - result["breakHours"] = getBreakHours(department, termCode) + # allocationManager's helpers only look at the *final* Allocation row for the + # term (they call getAllocation(..., isFinal=True) under the hood) and raise + # if one doesn't exist yet. A department whose most recent term is still a + # draft has no final row - fall back to the zeroed defaults above rather + # than letting that propagate into a 500 on the department portal. + try: + result["allocated"] = getTotalAllocations(termCode, department.departmentID)["totalAllocations"] + + contractedAllocations = getContractedAllocations(termCode, department.departmentID) + result["used"] = contractedAllocations["used_total"] + result["usedPositions"] = { + "used10": contractedAllocations["used_10"], + "used12": contractedAllocations["used_12"], + "used15": contractedAllocations["used_15"], + "used20": contractedAllocations["used_20"], + "usedSecondary5": contractedAllocations["used_5_sec"], + "usedSecondary10": contractedAllocations["used_10_sec"], + } + # getContractedAllocations' underlying SQL SUM() returns None (not 0) + # for a department/term whose only approved forms are weekly-hours + # ones (no break contract) - coalesce so the card doesn't render "None" + result["breakHours"] = contractedAllocations["break_hours"] or 0 + except Allocation.DoesNotExist: + pass return result diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py index 72e115db3..6592fcffe 100644 --- a/tests/code/test_getAllocation.py +++ b/tests/code/test_getAllocation.py @@ -13,7 +13,7 @@ from app.models.historyType import HistoryType from app.models.status import Status from app.models.user import User -from app.logic.getAllocation import getDepartmentAllocationSummary, countWorkers, getBreakHours, getCurrentSemesterLabel +from app.logic.getAllocation import getDepartmentAllocationSummary, getCurrentSemesterLabel def createFormHistory(form, statusName): @@ -59,7 +59,13 @@ def test_getDepartmentAllocationSummary(): Test that the summary reports allocated/used/breakHours for a department's most recent term, covering a missing department, a department with no Allocation rows, allocations spread across terms, several Allocation rows - in one term, break-term contracts, and an allocation with no forms. + in one term, break-term contracts, an allocation with no forms, and a + most-recent term that only has a draft (not yet final) allocation. + + The allocated/used/breakHours values are sourced from allocationManager's + getTotalAllocations/getContractedAllocations (see test_allocationManger.py + for those functions' own unit coverage) - only the term-selection and + fallback behavior is re-verified here. """ zeroedUsedPositions = { "used10": 0, @@ -175,7 +181,9 @@ def test_getDepartmentAllocationSummary(): # More than one Allocation row for the same most-recent term (e.g. a # draft and a final revision, which the model's (termCode, department, - # isFinal) index allows) sums across both rows rather than picking one + # isFinal) index allows) reports only the final row - the draft is not + # counted, since allocationManager's getTotalAllocations only looks at + # the isFinal=True row for a term multiRowDept = Department.create(departmentID=203, DEPT_NAME="Mathematics", ACCOUNT="6753", ORG="2123", isActive=True) multiRowTerm = Term.create(termCode=900300, termName="AY Test Multi") @@ -193,7 +201,7 @@ def test_getDepartmentAllocationSummary(): summary = getDepartmentAllocationSummary(multiRowDept) assert summary["term"].termCode == 900300 - assert summary["allocated"] == 3 # 1 + 2, summed across both rows + assert summary["allocated"] == 2 # only the final row counts, the draft's 1 is not added in # An allocation for the most recent term with no LaborStatusForm records # at all shows allocated > 0 with used/breakHours at 0, rather than @@ -215,129 +223,35 @@ def test_getDepartmentAllocationSummary(): assert summary["breakHours"] == 0 assert summary["usedPositions"] == zeroedUsedPositions - transaction.rollback() - - -@pytest.mark.integration -def test_countWorkers(): - """ - Test that countWorkers only counts LaborStatusForm rows matching the - given department, term, job type, and weekly-hours bucket, and excludes - forms with a different job type/hours bucket, a break-term contract - (contractHours set instead of weeklyHours), or a denied history status. - """ - with mainDB.atomic() as transaction: - dept = Department.create(departmentID=205, DEPT_NAME="English", ACCOUNT="6755", ORG="2125", isActive=True) - term = Term.create(termCode=900500, termName="AY Test Workers") - - supervisor = Supervisor.create(ID="SUP003", isActive=True) - student = Student.create(ID="STU003", isActive=True) - - # Matches department, term, job type, and hours bucket - should count - matchForm = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Primary", WLS="10", POSN_TITLE="Match", POSN_CODE="S010", - weeklyHours=10, contractHours=None, - ) - createFormHistory(matchForm, "Approved") + # A most-recent term with only a draft (isFinal=False) allocation - no + # final row exists yet, so allocationManager's getTotalAllocations has + # nothing to select and would raise; the summary should fall back to + # the zeroed defaults (still reporting the term) instead of erroring + draftOnlyDept = Department.create(departmentID=205, DEPT_NAME="Art", ACCOUNT="6755", ORG="2125", isActive=True) + draftOnlyTerm = Term.create(termCode=900500, termName="AY Test Draft Only") - # Different job type - should not count toward ("Primary", 10) - wrongJobTypeForm = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Secondary", WLS="10", POSN_TITLE="Wrong Job Type", POSN_CODE="S011", - weeklyHours=10, contractHours=None, + Allocation.create( + termCode=draftOnlyTerm, department=draftOnlyDept, isFinal=False, justification="draft", + primary_10=5, primary_12=0, primary_15=0, primary_20=0, + secondary_5=0, secondary_10=0, breakHours=30, ) - createFormHistory(wrongJobTypeForm, "Approved") - # Different hours bucket - should not count toward ("Primary", 10) - wrongHoursForm = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Primary", WLS="12", POSN_TITLE="Wrong Hours", POSN_CODE="S012", - weeklyHours=12, contractHours=None, - ) - createFormHistory(wrongHoursForm, "Approved") - - # Break-term contract (contractHours set) - should not count even though - # job type and weeklyHours otherwise match - breakContractForm = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Primary", WLS="10", POSN_TITLE="Break Contract", POSN_CODE="S013", - weeklyHours=10, contractHours=40, - ) - createFormHistory(breakContractForm, "Approved") + summary = getDepartmentAllocationSummary(draftOnlyDept) - # Matches everything but was DENIED - should not count - deniedForm = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Primary", WLS="10", POSN_TITLE="Denied Match", POSN_CODE="S014", - weeklyHours=10, contractHours=None, - ) - createFormHistory(deniedForm, "Denied by Admin") - - assert countWorkers(dept, term.termCode, "Primary", 10) == 1 - assert countWorkers(dept, term.termCode, "Secondary", 10) == 1 - assert countWorkers(dept, term.termCode, "Primary", 12) == 1 - assert countWorkers(dept, term.termCode, "Primary", 15) == 0 + assert summary["term"].termCode == 900500 + assert summary["allocated"] == 0 + assert summary["used"] == 0 + assert summary["breakHours"] == 0 + assert summary["usedPositions"] == zeroedUsedPositions transaction.rollback() -@pytest.mark.integration -def test_getBreakHours(): - """ - Test that getBreakHours sums only APPROVED forms with contractHours set - (break-term contracts) for the given department and term, excludes - weekly-hours forms, excludes forms under a different term, and excludes - forms that are not approved (e.g. still pending). - """ - with mainDB.atomic() as transaction: - dept = Department.create(departmentID=206, DEPT_NAME="Philosophy", ACCOUNT="6756", ORG="2126", isActive=True) - term = Term.create(termCode=900600, termName="AY Test Break Hours") - otherTerm = Term.create(termCode=900601, termName="AY Test Other Term") - - supervisor = Supervisor.create(ID="SUP004", isActive=True) - student = Student.create(ID="STU004", isActive=True) - - # Approved break-term contracts under the target term - should be summed - formA = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Primary", WLS="10", POSN_TITLE="Break A", POSN_CODE="S020", - weeklyHours=None, contractHours=40, - ) - createFormHistory(formA, "Approved") - - formB = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Secondary", WLS="5", POSN_TITLE="Break B", POSN_CODE="S021", - weeklyHours=None, contractHours=60, - ) - createFormHistory(formB, "Approved") - - # Weekly-hours form (contractHours=None) - should be excluded regardless - formC = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Primary", WLS="10", POSN_TITLE="Weekly Job", POSN_CODE="S022", - weeklyHours=10, contractHours=None, - ) - createFormHistory(formC, "Approved") - - # Break-term contract under a DIFFERENT term - should be excluded - formD = LaborStatusForm.create( - termCode=otherTerm, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Primary", WLS="10", POSN_TITLE="Break Other Term", POSN_CODE="S023", - weeklyHours=None, contractHours=25, - ) - createFormHistory(formD, "Approved") - - # Break-term contract that is still PENDING - should be excluded - formE = LaborStatusForm.create( - termCode=term, studentSupervisee=student, supervisor=supervisor, department=dept, - jobType="Primary", WLS="10", POSN_TITLE="Break Pending", POSN_CODE="S024", - weeklyHours=None, contractHours=999, - ) - createFormHistory(formE, "Pending") - - assert getBreakHours(dept, term.termCode) == 100 # 40 + 60, excludes the pending form - assert getBreakHours(dept, otherTerm.termCode) == 25 # only the other term's contract - - transaction.rollback() +# countWorkers and getBreakHours were removed in favor of calling +# allocationManager's getContractedAllocations directly from +# getDepartmentAllocationSummary (see test_allocationManger.py's +# test_countContracts/test_getContractedAllocations for that function's own +# coverage). Note the counting rules aren't identical to the old +# countWorkers/getBreakHours: getContractedAllocations doesn't exclude +# break-term contracts (contractHours set) from the weekly-hours buckets, and +# uses a narrower status whitelist instead of "anything not Denied". From f5640b3aa829b0cec41590576083a9fa636b1b8b Mon Sep 17 00:00:00 2001 From: munsakad Date: Wed, 5 Aug 2026 11:31:55 -0400 Subject: [PATCH 067/128] Fix missing LaborReleaseForm import in demo_data.py database/demo_data.py called LaborReleaseForm.insert(...) but never imported the model, so any fresh database/reset_database.sh run failed with a NameError partway through seeding demo data. Pre-existing bug from the department-portal-base merge, unrelated to the allocation card changes. --- database/demo_data.py | 1 + 1 file changed, 1 insertion(+) diff --git a/database/demo_data.py b/database/demo_data.py index 2e4543d34..eb02f8e17 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -14,6 +14,7 @@ from app.models.user import User from app.models.term import Term from app.models.laborStatusForm import LaborStatusForm +from app.models.laborReleaseForm import LaborReleaseForm from app.models.formHistory import FormHistory from app.models.notes import Notes from app.models.supervisorDepartment import SupervisorDepartment From 4c17019ff02c46dccbd07315161a50c426dd87fa Mon Sep 17 00:00:00 2001 From: munsakad Date: Wed, 5 Aug 2026 11:39:35 -0400 Subject: [PATCH 068/128] Fix allocation card showing zeros for departments with only a draft allocation Root cause: allocationManager's getContractedAllocations called getAllocation (isFinal=True only) for a value it never used, so any department whose most recent term only has a draft (not yet final) Allocation row raised DoesNotExist. getAllocation.py's earlier try/except caught that and fell back to zeroed defaults, which is why real demo data (departments with only draft allocations) started rendering 0s instead of their actual numbers. - allocationManager.py: removed the dead getAllocation() call from getContractedAllocations. Also folded its break-hours query to filter by department directly in SQL instead of fetching every department's totals and picking the first match after grouping by (department, termCode) - that grouping meant a department with approved break hours under both the specific term and the academic-year "00" bucket term would silently keep only whichever row came back first instead of summing them. Coalesces the SQL SUM() to 0 instead of leaving it None. - getAllocation.py: "allocated" is now summed directly from the Allocation rows already fetched for the most recent term (draft + final, matching the original pre-refactor behavior) instead of going through getTotalAllocations, which only looks at the final row. Dropped the DoesNotExist fallback - no longer needed now that the crash source is fixed at the root. - Updated test_getAllocation.py's multi-row and draft-only cases to expect the correct summed/draft values instead of the zeroed fallback. - Added two regression tests to test_allocationManger.py: getContractedAllocations without any Allocation row present, and break hours summed across a specific term and its academic-year bucket term. --- app/logic/allocationManager.py | 42 ++++++++--------------- app/logic/getAllocation.py | 46 ++++++++++++------------- tests/code/test_allocationManger.py | 53 ++++++++++++++++++++++++++++- tests/code/test_getAllocation.py | 32 +++++++++-------- 4 files changed, 105 insertions(+), 68 deletions(-) diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index f1c58cbc6..537744ffc 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -59,36 +59,22 @@ def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: def getContractedAllocations(termCode: int, dept: int): ''' - This function returns a dictionary with a breakdown of all types of contracts + This function returns a dictionary with a breakdown of all types of contracts for the given department and term in the form of a dictionary. ''' academicYearCode = int(str(termCode)[:4] + "00") - allocationObject = getAllocation(termCode, dept) - breakAllocation = FormHistory.select( - LaborStatusForm.department, - LaborStatusForm.termCode, - fn.SUM(LaborStatusForm.contractHours).alias('total_hours') - ).join( - LaborStatusForm, - on=(FormHistory.formID == LaborStatusForm.laborStatusFormID), - ).join( - Term, - on = (LaborStatusForm.termCode == Term.termCode ) - ).where( - (FormHistory.historyType == "Labor Status Form") & - (FormHistory.status == "Approved") & - (LaborStatusForm.termCode.in_([termCode,academicYearCode])) - ).group_by( - LaborStatusForm.department, - LaborStatusForm.termCode).dicts() - - breakSum = {"total_hours": 0} - if dept: - for row in breakAllocation: - if row["department"] == dept: - breakSum = row - break - + breakHoursTotal = ( + FormHistory.select(fn.SUM(LaborStatusForm.contractHours)) + .join(LaborStatusForm, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) + .where( + FormHistory.historyType == "Labor Status Form", + FormHistory.status == "Approved", + LaborStatusForm.termCode.in_([termCode, academicYearCode]), + LaborStatusForm.department == dept, + ) + .scalar() + ) or 0 + # dictionary definition: usedPositions = { "used_10": countContracts("Primary", "10", termCode, dept), @@ -100,7 +86,7 @@ def getContractedAllocations(termCode: int, dept: int): "used_primaries": 0, "used_secondaries": 0, "used_total": 0, # all contracts with weekly hours, i.e. primaries + secondaries (not break contracts) - "break_hours": breakSum["total_hours"] # all break hours contracted (but not necessarily worked) + "break_hours": breakHoursTotal # all break hours contracted (but not necessarily worked) } usedPositions["used_primaries"] = sum(list(usedPositions.values())[:4]) usedPositions["used_secondaries"] = sum(list(usedPositions.values())[4:6]) diff --git a/app/logic/getAllocation.py b/app/logic/getAllocation.py index 2d45cc9f2..5c09cd497 100644 --- a/app/logic/getAllocation.py +++ b/app/logic/getAllocation.py @@ -1,6 +1,6 @@ from datetime import date -from app.logic.allocationManager import getContractedAllocations, getTotalAllocations +from app.logic.allocationManager import getContractedAllocations from app.models.allocation import Allocation from app.models.term import Term @@ -45,29 +45,27 @@ def getDepartmentAllocationSummary(department): result["term"] = recentTerm result["currentSemester"] = getCurrentSemesterLabel(recentTerm) - # allocationManager's helpers only look at the *final* Allocation row for the - # term (they call getAllocation(..., isFinal=True) under the hood) and raise - # if one doesn't exist yet. A department whose most recent term is still a - # draft has no final row - fall back to the zeroed defaults above rather - # than letting that propagate into a 500 on the department portal. - try: - result["allocated"] = getTotalAllocations(termCode, department.departmentID)["totalAllocations"] + # "allocated" is summed directly from the rows already fetched above rather + # than through allocationManager's getTotalAllocations, since that only + # looks at the *final* Allocation row for a term - a department whose most + # recent term is still a draft (isFinal=False, no final row yet) would + # otherwise show 0 allocated instead of its draft numbers. + recentTermAllocations = [a for a in departmentAllocations if a.termCode_id == termCode] + result["allocated"] = sum( + a.primary_10 + a.primary_12 + a.primary_15 + a.primary_20 + a.secondary_5 + a.secondary_10 + for a in recentTermAllocations + ) - contractedAllocations = getContractedAllocations(termCode, department.departmentID) - result["used"] = contractedAllocations["used_total"] - result["usedPositions"] = { - "used10": contractedAllocations["used_10"], - "used12": contractedAllocations["used_12"], - "used15": contractedAllocations["used_15"], - "used20": contractedAllocations["used_20"], - "usedSecondary5": contractedAllocations["used_5_sec"], - "usedSecondary10": contractedAllocations["used_10_sec"], - } - # getContractedAllocations' underlying SQL SUM() returns None (not 0) - # for a department/term whose only approved forms are weekly-hours - # ones (no break contract) - coalesce so the card doesn't render "None" - result["breakHours"] = contractedAllocations["break_hours"] or 0 - except Allocation.DoesNotExist: - pass + contractedAllocations = getContractedAllocations(termCode, department.departmentID) + result["used"] = contractedAllocations["used_total"] + result["usedPositions"] = { + "used10": contractedAllocations["used_10"], + "used12": contractedAllocations["used_12"], + "used15": contractedAllocations["used_15"], + "used20": contractedAllocations["used_20"], + "usedSecondary5": contractedAllocations["used_5_sec"], + "usedSecondary10": contractedAllocations["used_10_sec"], + } + result["breakHours"] = contractedAllocations["break_hours"] return result diff --git a/tests/code/test_allocationManger.py b/tests/code/test_allocationManger.py index ea7da6782..3b90c9260 100644 --- a/tests/code/test_allocationManger.py +++ b/tests/code/test_allocationManger.py @@ -204,4 +204,55 @@ def test_getContractedAllocations(testLaborStatusForm, testTerm, testDepartment, assert contractedAllocation['used_secondaries'] == 0 assert contractedAllocation['used_total'] == 1 - assert contractedAllocation['break_hours'] == 500 \ No newline at end of file + assert contractedAllocation['break_hours'] == 500 + +@pytest.mark.integration +def test_getContractedAllocations_withoutAnAllocationRow(testLaborStatusForm, testTerm, testDepartment, testFormHistory): + ''' + getContractedAllocations must not require an Allocation row to exist for + the department/term (e.g. before one has been created or finalized) - + it should still report the LaborStatusForm-derived counts. + ''' + contractedAllocation = getContractedAllocations(testTerm.termCode, testDepartment.departmentID) + assert contractedAllocation['used_15'] == 1 + assert contractedAllocation['break_hours'] == 500 + +@pytest.mark.integration +def test_getContractedAllocations_sumsBreakHoursAcrossAcademicYearCode(testDepartment, testStudent, testSupervisor, testUser): + ''' + A department can have approved break-term contracts under both a specific + term and that year's academic-year "00" bucket term - break_hours should + sum both, not silently keep only whichever one the query happens to see + first. + ''' + specificTerm = Term.create(termCode=200610) + academicYearTerm = Term.create(termCode=200600) # matches testTerm's code + + specificTermForm = LaborStatusForm.create( + laborStatusFormID=9001, termCode=specificTerm, studentSupervisee=testStudent, + supervisor_id=testSupervisor.ID, department=testDepartment, jobType="Primary", WLS=1, + POSN_TITLE="Specific Term Break", POSN_CODE="S9001", contractHours=100, weeklyHours=None, + ) + FormHistory.create( + formHistoryID=9001, formID=specificTermForm, historyType="Labor Status Form", + createdBy=testUser.userID, createdDate="2025-03-02", status="Approved", + ) + + academicYearForm = LaborStatusForm.create( + laborStatusFormID=9002, termCode=academicYearTerm, studentSupervisee=testStudent, + supervisor_id=testSupervisor.ID, department=testDepartment, jobType="Primary", WLS=1, + POSN_TITLE="Academic Year Break", POSN_CODE="S9002", contractHours=250, weeklyHours=None, + ) + FormHistory.create( + formHistoryID=9002, formID=academicYearForm, historyType="Labor Status Form", + createdBy=testUser.userID, createdDate="2025-03-02", status="Approved", + ) + + try: + contractedAllocation = getContractedAllocations(specificTerm.termCode, testDepartment.departmentID) + assert contractedAllocation['break_hours'] == 350 # 100 + 250, both terms summed + finally: + specificTermForm.delete_instance() + academicYearForm.delete_instance() + specificTerm.delete_instance() + academicYearTerm.delete_instance() \ No newline at end of file diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py index 6592fcffe..8fe21fcca 100644 --- a/tests/code/test_getAllocation.py +++ b/tests/code/test_getAllocation.py @@ -59,13 +59,17 @@ def test_getDepartmentAllocationSummary(): Test that the summary reports allocated/used/breakHours for a department's most recent term, covering a missing department, a department with no Allocation rows, allocations spread across terms, several Allocation rows - in one term, break-term contracts, an allocation with no forms, and a - most-recent term that only has a draft (not yet final) allocation. - - The allocated/used/breakHours values are sourced from allocationManager's - getTotalAllocations/getContractedAllocations (see test_allocationManger.py - for those functions' own unit coverage) - only the term-selection and - fallback behavior is re-verified here. + in one term (draft and final both counted), break-term contracts, an + allocation with no forms, and a most-recent term that only has a draft + (not yet final) allocation. + + "used"/"usedPositions"/"breakHours" are sourced from allocationManager's + getContractedAllocations (see test_allocationManger.py for that + function's own unit coverage) - only the term-selection and allocated-sum + behavior is re-verified here. "allocated" is summed directly from the + Allocation rows for the most recent term (both draft and final), not + routed through allocationManager, since a department's allocation is + often still a draft when this is viewed. """ zeroedUsedPositions = { "used10": 0, @@ -181,9 +185,7 @@ def test_getDepartmentAllocationSummary(): # More than one Allocation row for the same most-recent term (e.g. a # draft and a final revision, which the model's (termCode, department, - # isFinal) index allows) reports only the final row - the draft is not - # counted, since allocationManager's getTotalAllocations only looks at - # the isFinal=True row for a term + # isFinal) index allows) sums across both rows rather than picking one multiRowDept = Department.create(departmentID=203, DEPT_NAME="Mathematics", ACCOUNT="6753", ORG="2123", isActive=True) multiRowTerm = Term.create(termCode=900300, termName="AY Test Multi") @@ -201,7 +203,7 @@ def test_getDepartmentAllocationSummary(): summary = getDepartmentAllocationSummary(multiRowDept) assert summary["term"].termCode == 900300 - assert summary["allocated"] == 2 # only the final row counts, the draft's 1 is not added in + assert summary["allocated"] == 3 # 1 + 2, summed across both rows # An allocation for the most recent term with no LaborStatusForm records # at all shows allocated > 0 with used/breakHours at 0, rather than @@ -224,9 +226,9 @@ def test_getDepartmentAllocationSummary(): assert summary["usedPositions"] == zeroedUsedPositions # A most-recent term with only a draft (isFinal=False) allocation - no - # final row exists yet, so allocationManager's getTotalAllocations has - # nothing to select and would raise; the summary should fall back to - # the zeroed defaults (still reporting the term) instead of erroring + # final row exists yet - still reports the draft's own numbers rather + # than zeroing out, since a department's allocation is often still a + # draft before it's finalized draftOnlyDept = Department.create(departmentID=205, DEPT_NAME="Art", ACCOUNT="6755", ORG="2125", isActive=True) draftOnlyTerm = Term.create(termCode=900500, termName="AY Test Draft Only") @@ -239,7 +241,7 @@ def test_getDepartmentAllocationSummary(): summary = getDepartmentAllocationSummary(draftOnlyDept) assert summary["term"].termCode == 900500 - assert summary["allocated"] == 0 + assert summary["allocated"] == 5 assert summary["used"] == 0 assert summary["breakHours"] == 0 assert summary["usedPositions"] == zeroedUsedPositions From b67164b23593f56b1598908ae6f85e66fa357ed8 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Wed, 5 Aug 2026 11:51:24 -0400 Subject: [PATCH 069/128] Added tests for getBreakContracts --- tests/code/test_allocationManger.py | 78 ++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 1 deletion(-) diff --git a/tests/code/test_allocationManger.py b/tests/code/test_allocationManger.py index ea7da6782..3a235451b 100644 --- a/tests/code/test_allocationManger.py +++ b/tests/code/test_allocationManger.py @@ -48,6 +48,15 @@ def testTerm(): #destroy term.delete_instance() +@pytest.fixture +def testBreakTerm(): + #create + term = Term.create(termCode = 200601) + yield term + + #destroy + term.delete_instance() + @pytest.fixture def testAllocation(testDepartment,testTerm): #create @@ -142,6 +151,50 @@ def testLaborStatusForm(testStudent,testSupervisor,testDepartment,testTerm): #destroy laborStatusForm.delete_instance() +@pytest.fixture +def testBreakLaborStatusForm(testStudent,testSupervisor,testDepartment,testBreakTerm): + breakLaborStatusForm = LaborStatusForm.create( + studentName = "John Doe", + laborStatusFormID = 9898, + termCode = testBreakTerm.termCode, + studentSupervisee = testStudent.ID, + supervisor_id = testSupervisor.ID, + department = testDepartment.departmentID, + jobType = "Secondary", + WLS = 1, + POSN_TITLE = "Vacation Worker", + POSN_CODE = "S61412", + contractHours = 168, + weeklyHours = None, + startDate = "2006-04-01", + endDate = "2006-09-01", + supervisorNotes = None, + laborDepartmentNotes = None, + studentConfirmation = True, + confirmationToken = None, + studentExpirationDate = True, + studentResponseDate = True, + ) + + breakFormHistory = FormHistory.create( + formHistoryID = 9898, + formID_id = "9898", + historyType_id = "Labor Status Form", + releaseForm_id = None, + adjustedForm_id = None, + overloadForm_id = None, + createdBy_id = 1, + createdDate = "2006-02-01", + reviewedDate = "2006-03-01", + reviewedBy_id = 1, + status_id = "Approved", + rejectReason = None + ) + + yield breakLaborStatusForm, breakFormHistory + #destroy + breakLaborStatusForm.delete_instance() + @pytest.fixture def testFormHistory(testLaborStatusForm,testUser): formHistory = FormHistory.create( @@ -204,4 +257,27 @@ def test_getContractedAllocations(testLaborStatusForm, testTerm, testDepartment, assert contractedAllocation['used_secondaries'] == 0 assert contractedAllocation['used_total'] == 1 - assert contractedAllocation['break_hours'] == 500 \ No newline at end of file + assert contractedAllocation['break_hours'] == 500 + +@pytest.mark.integration +def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartment): + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == 168 + + testBreakLaborStatusForm[0].contractHours = 800 + testBreakLaborStatusForm[0].save() + + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == 800 + + testBreakLaborStatusForm[0].weeklyHours = 9999 + testBreakLaborStatusForm[0].save() + + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == 800 + + testBreakLaborStatusForm[1].status = "denied by student" + testBreakLaborStatusForm[1].save() + + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == None \ No newline at end of file From cdfbae51ba713ea6466bd1f34253543697800809 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Wed, 5 Aug 2026 11:51:52 -0400 Subject: [PATCH 070/128] made getStudents() less brittle --- tests/code/test_tracy.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/code/test_tracy.py b/tests/code/test_tracy.py index 730547dd5..e26168638 100644 --- a/tests/code/test_tracy.py +++ b/tests/code/test_tracy.py @@ -18,8 +18,11 @@ def test_init(self, tracy): def test_getStudents(self, tracy): with app.app_context(): students = tracy.getStudents() - assert ['Elaheh','Guillermo','Jeremiah','Kat', 'Oluwagbayi', 'Test', 'Tyler'] == [s.FIRST_NAME for s in students] - assert ['718','300','420','420', '883', '700', '420'] == [s.STU_CPO for s in students] + for student in ['Elaheh','Guillermo','Jeremiah','Kat', 'Oluwagbayi', 'Test', 'Tyler']: + assert student in [s.FIRST_NAME for s in students] + for cpo in ['718','300','420','420', '883', '700', '420']: + assert cpo in [s.STU_CPO for s in students] + @pytest.mark.integration def test_getStudentFromBNumber(self, tracy): From 2736b90fa5d788ef7702b2bdb85261c4859e7588 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Wed, 5 Aug 2026 11:59:22 -0400 Subject: [PATCH 071/128] added notes to getBreakContracts test --- tests/code/test_allocationManger.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/code/test_allocationManger.py b/tests/code/test_allocationManger.py index 3a235451b..ceef79950 100644 --- a/tests/code/test_allocationManger.py +++ b/tests/code/test_allocationManger.py @@ -261,21 +261,26 @@ def test_getContractedAllocations(testLaborStatusForm, testTerm, testDepartment, @pytest.mark.integration def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartment): + + # Test that the formHistory object exists breakContractHours = getBreakContracts(testBreakTerm, testDepartment) assert breakContractHours == 168 + # Test it with a higher amount of hours testBreakLaborStatusForm[0].contractHours = 800 testBreakLaborStatusForm[0].save() breakContractHours = getBreakContracts(testBreakTerm, testDepartment) assert breakContractHours == 800 + # Test that it works even if weeklyHours and contractHours are set testBreakLaborStatusForm[0].weeklyHours = 9999 testBreakLaborStatusForm[0].save() breakContractHours = getBreakContracts(testBreakTerm, testDepartment) assert breakContractHours == 800 + # Test that if the form is denied to not show up. testBreakLaborStatusForm[1].status = "denied by student" testBreakLaborStatusForm[1].save() From 22a1856ce2f2e16a706fca09d380cec5fca1373f Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Wed, 5 Aug 2026 13:54:22 -0400 Subject: [PATCH 072/128] added a test for changing terms --- app/controllers/main_routes/main_routes.py | 1 - tests/code/test_allocationManger.py | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 3cbaf09ef..f7dfbc972 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -27,7 +27,6 @@ from app.logic.allocationManager import * - @main_bp.route('/logout', methods=['GET']) def triggerLogout(): return redirect(logout()) diff --git a/tests/code/test_allocationManger.py b/tests/code/test_allocationManger.py index ceef79950..735f15822 100644 --- a/tests/code/test_allocationManger.py +++ b/tests/code/test_allocationManger.py @@ -260,12 +260,12 @@ def test_getContractedAllocations(testLaborStatusForm, testTerm, testDepartment, assert contractedAllocation['break_hours'] == 500 @pytest.mark.integration -def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartment): +def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartment,testTerm): # Test that the formHistory object exists breakContractHours = getBreakContracts(testBreakTerm, testDepartment) assert breakContractHours == 168 - + # Test it with a higher amount of hours testBreakLaborStatusForm[0].contractHours = 800 testBreakLaborStatusForm[0].save() @@ -285,4 +285,14 @@ def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartme testBreakLaborStatusForm[1].save() breakContractHours = getBreakContracts(testBreakTerm, testDepartment) - assert breakContractHours == None \ No newline at end of file + assert breakContractHours == None + + # Test if the term changes to a non-break term + testBreakLaborStatusForm[0].termCode = testTerm + testBreakLaborStatusForm[0].save() + testBreakLaborStatusForm[1].status = "Approved" + testBreakLaborStatusForm[1].save() + + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == None + \ No newline at end of file From f3b43c6ee70a4f12ae83640d0145e781d3dbf85c Mon Sep 17 00:00:00 2001 From: munsakad Date: Wed, 5 Aug 2026 15:05:48 -0400 Subject: [PATCH 073/128] removed unneccesary comments --- tests/code/test_getAllocation.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py index 8fe21fcca..7db6e8980 100644 --- a/tests/code/test_getAllocation.py +++ b/tests/code/test_getAllocation.py @@ -249,11 +249,3 @@ def test_getDepartmentAllocationSummary(): transaction.rollback() -# countWorkers and getBreakHours were removed in favor of calling -# allocationManager's getContractedAllocations directly from -# getDepartmentAllocationSummary (see test_allocationManger.py's -# test_countContracts/test_getContractedAllocations for that function's own -# coverage). Note the counting rules aren't identical to the old -# countWorkers/getBreakHours: getContractedAllocations doesn't exclude -# break-term contracts (contractHours set) from the weekly-hours buckets, and -# uses a narrower status whitelist instead of "anything not Denied". From 6ae39318b9575284a521d013d036dd4c645cdaf9 Mon Sep 17 00:00:00 2001 From: munsakad Date: Wed, 5 Aug 2026 15:28:29 -0400 Subject: [PATCH 074/128] removed department information in main routes --- app/controllers/main_routes/main_routes.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index bf2d382e4..ce3ac4c3c 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -102,21 +102,6 @@ def departmentPortal(org=None,account=None): positions = positionsList, posURL = posURL) -@main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) -def addUserToDept(): - userDeptData = request.form - supervisorDeptRecord = SupervisorDepartment.get_or_none(supervisor = userDeptData['supervisorID'], department = userDeptData['departmentID']) - try: - if supervisorDeptRecord: - return "False" - - else: - SupervisorDepartment.create(supervisor=userDeptData['supervisorID'], department=userDeptData['departmentID']) - return "True" - - except Exception as e: - print(f'Could not add user to department: {e}') - return "", 500 @main_bp.route('/supervisorPortal/download', methods=['POST']) def downloadSupervisorPortalResults(): From 2005c87b74d0680c8c47e4e4d351dce8a8ca7753 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Wed, 5 Aug 2026 16:31:53 -0400 Subject: [PATCH 075/128] created date check in allocationManager --- app/logic/allocationManager.py | 26 ++++++++++++++++++++++++++ database/demo_data.py | 25 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+) diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index 0c1bcd5fb..fcf15a5ca 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -44,6 +44,31 @@ def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: 5-hour positions in the CS department for the 2025 Fall term. ''' academicYearCode = int(str(termCode)[:4] + "00") + ### + # This sets the date condition to determine whether the form is within the boundaries of the term + # Fall only contracts end before spring, spring contracts start after fall. + ### + fallMonths = ["07","08","09","10","11","12"] + springMonths = ["01","02","03","04","05","06"] + if str(termCode).endswith("11"): + dateCondition = ( + (LaborStatusForm.endDate.month.in_(fallMonths)) | + (LaborStatusForm.startDate.month.in_(fallMonths) & + LaborStatusForm.endDate.month.in_(springMonths)) + ) + elif str(termCode).endswith("12"): + dateCondition = ( + (LaborStatusForm.startDate.month.in_(springMonths)) | + (LaborStatusForm.startDate.month.in_(fallMonths) & + LaborStatusForm.endDate.month.in_(springMonths)) + ) + else: + dateCondition = ( + (LaborStatusForm.startDate.month.in_(fallMonths) & + LaborStatusForm.endDate.month.in_(springMonths)) + + ) + lsfCountPositions = FormHistory.select( ).join(LaborStatusForm ).join(Department @@ -54,6 +79,7 @@ def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: LaborStatusForm.jobType == jobType, # 'primary' or 'secondary' LaborStatusForm.weeklyHours == weeklyContractHours, # 5, 10, 12, 15, or 20 Department.departmentID == dept, + dateCondition, ).count() return lsfCountPositions diff --git a/database/demo_data.py b/database/demo_data.py index 011d506ff..d1e500bb4 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -1078,6 +1078,31 @@ "status_id": "Approved" }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + "laborStatusFormID": 76, + "termCode_id": "202600", + "studentName": "Jordan Brooks", + "studentSupervisee_id": "B12345764", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Technology Assistant", + "POSN_CODE": "S61423", + "weeklyHours": 10, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 76, + "formID_id": "76", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + # Break Positions From b9f9cc48bfa226421c1a2f7be95fdc41639f460e Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 6 Aug 2026 10:02:27 -0400 Subject: [PATCH 076/128] fixed comments for date logic --- app/logic/allocationManager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index fcf15a5ca..dd8bddbc8 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -44,10 +44,9 @@ def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: 5-hour positions in the CS department for the 2025 Fall term. ''' academicYearCode = int(str(termCode)[:4] + "00") - ### + # This sets the date condition to determine whether the form is within the boundaries of the term # Fall only contracts end before spring, spring contracts start after fall. - ### fallMonths = ["07","08","09","10","11","12"] springMonths = ["01","02","03","04","05","06"] if str(termCode).endswith("11"): From 96ffe5fb316a6cd8f14404a56b52323116597d80 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 6 Aug 2026 11:00:49 -0400 Subject: [PATCH 077/128] fixed current term logic --- app/controllers/main_routes/main_routes.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index f7dfbc972..b63c6bc4f 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -98,17 +98,19 @@ def allocationTable(org=None, account=None): else: departments = list(Department.select().join(SupervisorDepartment).where(SupervisorDepartment.supervisor == currentUser.supervisor).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) - currentDate = str(date.today()) - if int(currentDate[5:7]) <= 6: - # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. - springTerm = Term.select().where(Term.termCode == int(currentDate[:4] + "12") - 100).get() - currentAY = currentTerm = Term.select().where(Term.termCode == int(currentDate[:4] + "00") - 100).get() - fallTerm = Term.select().where(Term.termCode == int(currentDate[:4] + "11") - 100).get() + + currentDate = date.today() + if currentDate.month <= 6: + # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. + # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() + currentAY = Term.select().where(Term.termCode == currentDate.year * 100 - 100).get() + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11 - 100).get() else: - fallTerm = Term.select().where(Term.termCode == currentDate[:4] + "11").get() - currentAY = Term.select().where(Term.termCode == currentDate[:4] + "00").get() - springTerm = Term.select().where(Term.termCode == currentDate[:4] + "12").get() + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() + currentAY = Term.select().where(Term.termCode == currentDate.year * 100).get() + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() allocationDict = getTotalAllocations(currentAY, dept) fallContracts = getContractedAllocations(fallTerm, dept) From ea2910906cb9c1f3edaf03635aa31106dc6d8207 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 6 Aug 2026 11:20:33 -0400 Subject: [PATCH 078/128] shortened js significantly. --- app/static/js/allocationTable.js | 62 +++++++++----------------------- 1 file changed, 16 insertions(+), 46 deletions(-) diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js index 2607e1eba..51a344a2c 100644 --- a/app/static/js/allocationTable.js +++ b/app/static/js/allocationTable.js @@ -1,48 +1,18 @@ $(document).ready( function(){ - fallTermPrimaries = $('#fallTermPrimaries'); - fallTermPrimaries.DataTable({ - pageLength: 25, - info: false, - lengthChange: false, - searching: false, - paging: false, - "order": [] - }); - fallTermSecondaries = $('#fallTermSecondaries'); - fallTermSecondaries.DataTable({ - pageLength: 25, - info: false, - lengthChange: false, - searching: false, - paging: false, - "order": [] - }); - - springTermPrimaries = $('#springTermPrimaries'); - springTermPrimaries.DataTable({ - pageLength: 25, - info: false, - lengthChange: false, - searching: false, - paging: false, - "order": [] - }); - springTermSecondaries = $('#springTermSecondaries'); - springTermSecondaries.DataTable({ - pageLength: 25, - info: false, - lengthChange: false, - searching: false, - paging: false, - "order": [] - }); - breakTable = $('#breakTable'); - breakTable.DataTable({ - pageLength: 25, - info: false, - lengthChange: false, - searching: false, - paging: false, - "order": [] - }); + function initTable(selector) { + return $(selector).DataTable({ + pageLength: 25, + info: false, + lengthChange: false, + searching: false, + paging: false, + order: [] + }); + } + + const fallTermPrimaries = initTable('#fallTermPrimaries'); + const fallTermSecondaries = initTable('#fallTermSecondaries'); + const springTermPrimaries = initTable('#springTermPrimaries'); + const springTermSecondaries = initTable('#springTermSecondaries'); + const breakTable = initTable('#breakTable'); }); \ No newline at end of file From 73af911abf42c8b0be2035a241550e423a5fdc3c Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 6 Aug 2026 12:00:10 -0400 Subject: [PATCH 079/128] added a check if no break contracts are found --- app/controllers/main_routes/main_routes.py | 3 +-- app/logic/allocationManager.py | 5 ++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index b63c6bc4f..84d3e4ea2 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -96,7 +96,7 @@ def allocationTable(org=None, account=None): if currentUser.isLaborAdmin: pass else: - departments = list(Department.select().join(SupervisorDepartment).where(SupervisorDepartment.supervisor == currentUser.supervisor).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) + departments = list(Department.select().join(SupervisorDepartment).where(SupervisorDepartment.supervisor == currentUser.supervisor).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) #FIXME currentDate = date.today() @@ -125,7 +125,6 @@ def allocationTable(org=None, account=None): "summer": getBreakContracts(currentAY.termCode + 13, dept) } breakContracts["total"] = sum(breakContracts.values()) - return render_template('main/allocationTable.html', department = dept, currentAY = currentAY, diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index dd8bddbc8..69ee9cb78 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -141,4 +141,7 @@ def getBreakContracts(termCode, dept): LaborStatusForm.termCode == termCode, LaborStatusForm.department == dept, LaborStatusForm.contractHours != None).scalar() - return break_allocation \ No newline at end of file + if break_allocation != None: + return break_allocation + else: + return 0 \ No newline at end of file From 76a167b8e3f928032b572cc21fba16890e7f5e81 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 6 Aug 2026 13:35:58 -0400 Subject: [PATCH 080/128] checks if the user is in a department --- app/controllers/main_routes/main_routes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 84d3e4ea2..1b9fa9f44 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -93,10 +93,10 @@ def allocationTable(org=None, account=None): except (NameError, DoesNotExist): dept = None - if currentUser.isLaborAdmin: - pass - else: - departments = list(Department.select().join(SupervisorDepartment).where(SupervisorDepartment.supervisor == currentUser.supervisor).order_by(Department.isActive.desc(), Department.DEPT_NAME.asc())) #FIXME + if not currentUser.isLaborAdmin: + allowedDepartmentIds = [d.departmentID for d in getDepartmentsForSupervisor(currentUser)] + if dept.departmentID not in allowedDepartmentIds: + return render_template('errors/403.html'), 403 currentDate = date.today() From f74fb7dd867a62ac8612d9e509f826e8b896b5db Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 6 Aug 2026 13:49:32 -0400 Subject: [PATCH 081/128] test_allocationManager accurately reflects changes to logic --- tests/code/test_allocationManger.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/code/test_allocationManger.py b/tests/code/test_allocationManger.py index 735f15822..eb3a149f6 100644 --- a/tests/code/test_allocationManger.py +++ b/tests/code/test_allocationManger.py @@ -137,8 +137,8 @@ def testLaborStatusForm(testStudent,testSupervisor,testDepartment,testTerm): POSN_CODE = "S61412", contractHours = 500, weeklyHours = 15, - startDate = "2025-04-01", - endDate = "2025-09-01", + startDate = "2006-08-01", + endDate = "2007-5-01", supervisorNotes = None, laborDepartmentNotes = None, studentConfirmation = True, @@ -285,7 +285,7 @@ def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartme testBreakLaborStatusForm[1].save() breakContractHours = getBreakContracts(testBreakTerm, testDepartment) - assert breakContractHours == None + assert breakContractHours == 0 # Test if the term changes to a non-break term testBreakLaborStatusForm[0].termCode = testTerm @@ -294,5 +294,5 @@ def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartme testBreakLaborStatusForm[1].save() breakContractHours = getBreakContracts(testBreakTerm, testDepartment) - assert breakContractHours == None + assert breakContractHours == 0 \ No newline at end of file From c8848114646e470313ea7d477edb79f7ecb8efd0 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Thu, 6 Aug 2026 14:02:33 -0400 Subject: [PATCH 082/128] Added 2026 allocation data and new 202600 term --- database/demo_data.py | 55 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/database/demo_data.py b/database/demo_data.py index b7a59297e..a9b117e71 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -637,6 +637,16 @@ "adjustmentCutOff": f"2025-09-01", "isBreak": 1, }, + { + "termCode": f"202600", + "termName": f"AY 2026-2027", + "termStart": f"2026-08-01", + "termEnd": f"2027-05-01", + "termState": 0, + "primaryCutOff": f"2026-09-01", + "adjustmentCutOff": f"2026-09-01", + "isBreak": 0, + }, ] Term.insert_many(terms).on_conflict_replace().execute() @@ -1181,6 +1191,51 @@ "secondary_10": 1, "breakHours": 900, }, + { + "termCode": 202600, + "department": 5, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 4, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 5, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, ] Allocation.insert_many(allocations).on_conflict_replace().execute() From 0b0861c0f38203a647590f672e4c49beb8f13703 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Thu, 6 Aug 2026 14:07:45 -0400 Subject: [PATCH 083/128] added data for the Requested Allocations for the AY26-27 --- database/demo_data.py | 188 +++++++++++++++++++++++++++++++++--------- 1 file changed, 147 insertions(+), 41 deletions(-) diff --git a/database/demo_data.py b/database/demo_data.py index a9b117e71..38da8a514 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -1192,51 +1192,157 @@ "breakHours": 900, }, { - "termCode": 202600, - "department": 5, - "isFinal": True, - "approvedOn": None, - "approvedBy": None, - "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", - "primary_10": 8, - "primary_12": 10, - "primary_15": 7, - "primary_20": 4, - "secondary_5": 5, - "secondary_10": 1, - "breakHours": 900, - }, + "termCode": 202600, + "department": 5, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 4, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 3, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 2, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 1, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + + # Requested Allocations for 2026-2027 (isFinal = False) { - "termCode": 202600, - "department": 4, - "isFinal": True, - "approvedOn": None, - "approvedBy": None, - "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", - "primary_10": 8, - "primary_12": 10, - "primary_15": 7, - "primary_20": 4, - "secondary_5": 5, - "secondary_10": 1, - "breakHours": 900, + "termCode": 202600, + "department": 5, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 1, + "primary_12": 2, + "primary_15": 3, + "primary_20": 4, + "secondary_5": 1, + "secondary_10": 1, + "breakHours": 100, }, { - "termCode": 202600, - "department": 5, - "isFinal": True, - "approvedOn": None, - "approvedBy": None, - "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", - "primary_10": 8, - "primary_12": 10, - "primary_15": 7, - "primary_20": 4, - "secondary_5": 5, - "secondary_10": 1, - "breakHours": 900, + "termCode": 202600, + "department": 4, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 1, + "primary_12": 2, + "primary_15": 3, + "primary_20": 4, + "secondary_5": 1, + "secondary_10": 1, + "breakHours": 100, + }, + { + "termCode": 202600, + "department": 3, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 1, + "primary_12": 2, + "primary_15": 3, + "primary_20": 4, + "secondary_5": 1, + "secondary_10": 1, + "breakHours": 100, + }, + { + "termCode": 202600, + "department": 2, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 1, + "primary_12": 2, + "primary_15": 3, + "primary_20": 4, + "secondary_5": 1, + "secondary_10": 1, + "breakHours": 100, + }, + { + "termCode": 202600, + "department": 1, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 1, + "primary_12": 2, + "primary_15": 3, + "primary_20": 4, + "secondary_5": 1, + "secondary_10": 1, + "breakHours": 100, }, - ] Allocation.insert_many(allocations).on_conflict_replace().execute() From 6c9835d39c6eacaa5dce589bc79de790426956fe Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Thu, 6 Aug 2026 21:43:57 -0400 Subject: [PATCH 084/128] Polished demodata for the allocations --- database/demo_data.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/database/demo_data.py b/database/demo_data.py index 38da8a514..13128f7cd 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -1115,10 +1115,12 @@ ############################ # Allocation Dummy Data: ########################### + +# Active Allocations for 2025 allocations = [ { "termCode": 202500, - "department": 3, + "department": 1, "isFinal": False, "approvedOn": None, "approvedBy": None, @@ -1148,7 +1150,7 @@ }, { "termCode": 202500, - "department": 1, + "department": 3, "isFinal": True, "approvedOn": None, "approvedBy": None, @@ -1164,7 +1166,7 @@ { "termCode": 202500, "department": 4, - "isFinal": False, + "isFinal": True, "approvedOn": None, "approvedBy": None, "justification": "Downscaling the number of students in the department due to budget cuts", @@ -1191,6 +1193,8 @@ "secondary_10": 1, "breakHours": 900, }, + + # Active Allocations for 2026 { "termCode": 202600, "department": 5, @@ -1276,12 +1280,12 @@ "approvedBy": None, "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", "primary_10": 1, - "primary_12": 2, + "primary_12": 22, "primary_15": 3, "primary_20": 4, "secondary_5": 1, "secondary_10": 1, - "breakHours": 100, + "breakHours": 89, }, { "termCode": 202600, @@ -1290,13 +1294,13 @@ "approvedOn": None, "approvedBy": None, "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", - "primary_10": 1, + "primary_10": 11, "primary_12": 2, "primary_15": 3, "primary_20": 4, "secondary_5": 1, "secondary_10": 1, - "breakHours": 100, + "breakHours": 293, }, { "termCode": 202600, @@ -1306,12 +1310,12 @@ "approvedBy": None, "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", "primary_10": 1, - "primary_12": 2, + "primary_12": 23, "primary_15": 3, "primary_20": 4, "secondary_5": 1, "secondary_10": 1, - "breakHours": 100, + "breakHours": 999, }, { "termCode": 202600, @@ -1320,13 +1324,13 @@ "approvedOn": None, "approvedBy": None, "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", - "primary_10": 1, + "primary_10": 10, "primary_12": 2, "primary_15": 3, - "primary_20": 4, + "primary_20": 13, "secondary_5": 1, - "secondary_10": 1, - "breakHours": 100, + "secondary_10": 19, + "breakHours": 1000, }, { "termCode": 202600, From 53a4f353a391b451fb274325ed0457f8acb3dbc7 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Thu, 6 Aug 2026 21:46:04 -0400 Subject: [PATCH 085/128] Changed backend logic by implementing a dictionary with the objects --- .../admin_routes/manageDepartments.py | 36 +++------- app/logic/manageDepartments.py | 68 ++++++++++++++----- app/templates/admin/manageDepartments.html | 64 ++++++++++------- 3 files changed, 99 insertions(+), 69 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index c202513a9..ec9ce2f52 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -17,7 +17,7 @@ from app.logic.manageDepartments import * - +from playhouse.shortcuts import model_to_dict @admin.route('/admin/manageDepartments/', methods=['GET']) def manageDepartments(academicYear = None): @@ -25,7 +25,6 @@ def manageDepartments(academicYear = None): Returns the Manage Departments page, which allows the admin to view all the departments and their allocations. """ - # Checking Admin Rights currentUser = require_login() if not currentUser: # If the current user is not logged in @@ -36,42 +35,25 @@ def manageDepartments(academicYear = None): elif currentUser.supervisor: return render_template('errors/403.html'), 403 - - # The condition below may be deleted if the routing to the Manage Departments page is changed. - if academicYear == None: - academicYear = g.openTerm.termCode - else: - academicYear = int(academicYear) - - - currentAY, nextAY = generateAdjacentYears(academicYear) - chosenAY = Term.get(Term.termCode == academicYear) - - breakHoursByDepartment = {row["department"]: str(row["totalHours"] or 0) for row in getUsedBreakHours(chosenAY)} - - activeDepartments = getActiveDepartmentsWithAllocation(chosenAY) - inactiveDepartments = Department.select().where(Department.isActive == False) + currentAY, nextAY = generateAdjacentYears(g.openTerm.termCode) - allocationStatus = { - department.departmentID: getAllocationStatus(chosenAY, department) - for department in activeDepartments - } + inactiveDepartments = Department.select().where(Department.isActive == False) + allSupervisors = Supervisor.select().order_by(Supervisor.LAST_NAME) - allSupervisors= Supervisor.select().order_by(Supervisor.LAST_NAME) + breakHoursByDepartment = {row["department"]: str(row["totalHours"] or 0) for row in getUsedBreakHours(currentAY)} + + activeDepartmentsAllocations = getActiveDepartmentsAllocations(currentAY,nextAY) return render_template( 'admin/manageDepartments.html', - activeDepartments = activeDepartments, + activeDepartmentsAllocations = activeDepartmentsAllocations, inactiveDepartments = inactiveDepartments, - allSupervisors = allSupervisors, + allSupervisors = allSupervisors, currentAY = currentAY, nextAY = nextAY, - academicYear = chosenAY.termName, breakHoursByDepartment = breakHoursByDepartment, - allocationStatus = allocationStatus ) - @admin.route('/admin/complianceStatus', methods=['POST']) def complianceStatusCheck(): """ diff --git a/app/logic/manageDepartments.py b/app/logic/manageDepartments.py index 4f3fbb9d8..6feeec2a2 100644 --- a/app/logic/manageDepartments.py +++ b/app/logic/manageDepartments.py @@ -12,17 +12,17 @@ from app.login_manager import require_login +from playhouse.shortcuts import model_to_dict + def generateAdjacentYears(academicYearTermCode=None): """ - Generates the current, the previous, and the following academic years. + Generates the current, and the following academic years. """ - currentYear = g.openTerm.termCode // 100 nextYear = currentYear + 1 - currentAYCode = currentYear * 100 nextAYCode = nextYear * 100 @@ -50,9 +50,6 @@ def generateAdjacentYears(academicYearTermCode=None): # Everything below this line will eventually be deleted - - - def getUsedBreakHours(term): """ Returns the total number of break hours used by each department for a given term. @@ -109,7 +106,7 @@ def getLSFCountSecondaries(currentTerm, department): -def getActiveDepartmentsWithAllocation(term): +def getActiveDepartmentsWithAllocation(term,isFinal = True): """ Returns a list of active departments with allocations for the given term. """ @@ -118,24 +115,61 @@ def getActiveDepartmentsWithAllocation(term): # activeDepartments = Department.select().where(Department.isActive == True) # allAllocations = Allocation.select().where(Allocation.termCode == currentAY) - activeDepartments = (Department + activeDepartments = (Allocation .select(Department, Allocation) - .join(Allocation) + .join(Department) .where( Department.isActive == True, - Allocation.termCode == term.termCode - ) + Allocation.termCode == term.termCode, + Allocation.isFinal == isFinal, + ) ) - for dept in activeDepartments: - dept.totalPrimaries = (dept.allocation.primary_10 + dept.allocation.primary_12 + dept.allocation.primary_15 + dept.allocation.primary_20) - dept.totalSecondaries = (dept.allocation.secondary_5 + dept.allocation.secondary_10) + + for allocation in activeDepartments: + allocation.totalPrimaries = (allocation.primary_10 + allocation.primary_12 + allocation.primary_15 + allocation.primary_20) + allocation.totalSecondaries = (allocation.secondary_5 + allocation.secondary_10) + + if isFinal: # do not need to count them for requested allocations + allocation.lsfCountPrimaries = getLSFCountPrimaries(term, allocation.department) + allocation.lsfCountSecondaries = getLSFCountSecondaries(term, allocation.department) + + return activeDepartments - dept.lsfCountPrimaries = getLSFCountPrimaries(term, dept) - dept.lsfCountSecondaries = getLSFCountSecondaries(term, dept) - return activeDepartments +def getActiveDepartmentsAllocations(term,nextTerm): + """ + This function gets active departments, active allocations for the current AY and future AY. + It returns a dictionary which contains three objects grouped by the departmentID + """ + + activeDepartments = Department.select().where(Department.isActive == True) + + allocations = getActiveDepartmentsWithAllocation(term) + + allocByDeptId = {} + + # index allocations by department ID + for alloc in allocations: + allocByDeptId[alloc.department.departmentID] = alloc + requestedAllocations = getActiveDepartmentsWithAllocation(nextTerm, False) + + # index requested allocations by department ID + reqAllocByDeptId = {} + for alloc in requestedAllocations: + reqAllocByDeptId[alloc.department.departmentID] = alloc + + # build combined dictionary for active departments + activeDepartmentsAllocations = {} + + for dept in activeDepartments: + activeDepartmentsAllocations[dept.departmentID] = { + "department": dept, + "allocation": allocByDeptId.get(dept.departmentID), # None if no allocation + "requestedAllocation": reqAllocByDeptId.get(dept.departmentID), # None if no requested allocation + } + return activeDepartmentsAllocations def getAllocationStatus(term, department): diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 88fcf297e..04c87db3e 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -91,63 +91,70 @@

Manage Departments

- - + - {% for department in activeDepartments %} + {% for entry in activeDepartmentsAllocations.values() %} - + @@ -162,19 +169,26 @@

Manage Departments

+ + + - {% endfor %} + {%endfor%}
Total Break Hours{{breakContracts['total_hours']}}{{breakContracts['total']}} {{allocations["breakHours"]}}
{{allocations["breakHours"]}}
Thanksgiving / Fall BreakFall Break{{breakContracts['fall']}}
Thanksgiving {{breakContracts['thanksgiving']}}
DepartmentStatusCurrent Allocations
({{ academicYear }})
Current Allocations
({{ currentAY.termName }})
Requested Allocations
({{ nextAY.termName }})
Actions
- {{department.DEPT_NAME}}
({{department.ORG}}, - {{department.ACCOUNT}}) + {{entry.department.DEPT_NAME}}
({{entry.department.ORG}}, + {{entry.department.ACCOUNT}})
- + + {% if entry.allocation %} Primary: - {{department.lsfCountPrimaries}} of {{department.totalPrimaries}} + {{entry.allocation.lsfCountPrimaries}} of {{entry.allocation.totalPrimaries}}
+ Secondary: - {{department.lsfCountSecondaries}} of {{department.totalSecondaries}} + {{entry.allocation.lsfCountSecondaries}} of {{entry.allocation.totalSecondaries}}
- Break: {{ breakHoursByDepartment.get(department.departmentID, 0) }} of {{ - department.allocation.breakHours }} hours + Break: {{ breakHoursByDepartment.get(entry.department.departmentID, 0) }} of + {{entry.allocation.breakHours }} hours + {% else %} + No current allocation + {% endif %}
Primary: - {{department.lsfCountPrimaries}} of {{department.totalPrimaries}} + {{entry.requestedAllocation.totalPrimaries}}
Secondary: - {{department.lsfCountSecondaries}} of {{department.totalSecondaries}} + {{entry.requestedAllocation.totalSecondaries}}
- Break: {{ breakHoursByDepartment.get(department.departmentID, 0) }} of {{ department.allocation.breakHours }} hours + Break: {{ entry.requestedAllocation.breakHours }} hours
From e4b2bc0b72d4ee6bc11a19dd448a4c165464448b Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 7 Aug 2026 09:51:37 -0400 Subject: [PATCH 086/128] fixed test_allocationManager.py name --- .../code/{test_allocationManger.py => test_allocationManager.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/code/{test_allocationManger.py => test_allocationManager.py} (100%) diff --git a/tests/code/test_allocationManger.py b/tests/code/test_allocationManager.py similarity index 100% rename from tests/code/test_allocationManger.py rename to tests/code/test_allocationManager.py From 8c417f7b23dc22c42da83e3a45e686f207fe09aa Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 7 Aug 2026 09:56:21 -0400 Subject: [PATCH 087/128] contractHours != None -> .is_null(False) --- app/logic/allocationManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index 69ee9cb78..390d2b92e 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -140,7 +140,7 @@ def getBreakContracts(termCode, dept): FormHistory.status.in_(["Approved", "Pending", "Pre-Student Approval"]), LaborStatusForm.termCode == termCode, LaborStatusForm.department == dept, - LaborStatusForm.contractHours != None).scalar() + LaborStatusForm.contractHours.is_null(False)).scalar() if break_allocation != None: return break_allocation else: From 62998206ca8387bafa0c9578aed911b6583e6595 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 7 Aug 2026 10:00:15 -0400 Subject: [PATCH 088/128] removed AddUserToDept route again --- app/controllers/main_routes/main_routes.py | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 1b9fa9f44..601bc58b2 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -134,22 +134,6 @@ def allocationTable(org=None, account=None): breakContracts = breakContracts) -@main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST']) -def addUserToDept(): - userDeptData = request.form - supervisorDeptRecord = SupervisorDepartment.get_or_none(supervisor = userDeptData['supervisorID'], department = userDeptData['departmentID']) - try: - if supervisorDeptRecord: - return "False" - - else: - SupervisorDepartment.create(supervisor=userDeptData['supervisorID'], department=userDeptData['departmentID']) - return "True" - - except Exception as e: - print(f'Could not add user to department: {e}') - return "", 500 - @main_bp.route('/supervisorPortal/download', methods=['POST']) def downloadSupervisorPortalResults(): ''' From 4b2ece2f364231911f2da219cd7ebb5394b8615f Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 7 Aug 2026 10:12:46 -0400 Subject: [PATCH 089/128] fixed allocationManager import functions --- app/controllers/main_routes/main_routes.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 601bc58b2..0eb84425e 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -24,7 +24,7 @@ from app.logic.banner import Banner from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions -from app.logic.allocationManager import * +from app.logic.allocationManager import getBreakContracts, getContractedAllocations, getTotalAllocations @main_bp.route('/logout', methods=['GET']) From 1e4d1d4d10cfdb49134d23aa05c26f29c87a9092 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 7 Aug 2026 10:29:44 -0400 Subject: [PATCH 090/128] fixed termCode function calls --- app/controllers/main_routes/main_routes.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 0eb84425e..97a427f89 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -112,9 +112,9 @@ def allocationTable(org=None, account=None): currentAY = Term.select().where(Term.termCode == currentDate.year * 100).get() springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() - allocationDict = getTotalAllocations(currentAY, dept) - fallContracts = getContractedAllocations(fallTerm, dept) - springContracts = getContractedAllocations(springTerm, dept) + allocationDict = getTotalAllocations(currentAY.termCode, dept) + fallContracts = getContractedAllocations(fallTerm.termCode, dept) + springContracts = getContractedAllocations(springTerm.termCode, dept) breakContracts = { "total": 0, From 18f1abc7c20451924e0d2f64fc2124d7d15fc28a Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 7 Aug 2026 10:34:16 -0400 Subject: [PATCH 091/128] added 404 error to no dept allocation view --- app/controllers/main_routes/main_routes.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 97a427f89..560d02fa7 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -91,8 +91,8 @@ def allocationTable(org=None, account=None): try: dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) except (NameError, DoesNotExist): - dept = None - + return render_template('errors/404.html'), 404 + if not currentUser.isLaborAdmin: allowedDepartmentIds = [d.departmentID for d in getDepartmentsForSupervisor(currentUser)] if dept.departmentID not in allowedDepartmentIds: From 7b0cd5a2a143c2d823acd6603a92d6c8bd47c42d Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 7 Aug 2026 11:37:16 -0400 Subject: [PATCH 092/128] added more comments to date contContracts --- app/logic/allocationManager.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index 390d2b92e..794d0cf6d 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -37,7 +37,7 @@ def getTotalAllocations(termCode: int, dept: int): "totalAllocations": (allocationObject["primary_10"] + allocationObject["primary_12"] + allocationObject["primary_15"] + allocationObject["primary_20"] + allocationObject["secondary_5"] + allocationObject["secondary_10"] )} return allocationDict -def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: int): +def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: int, AYtermCode: int = None): ''' This function counts the number of positions of a given type in a given department. For example, countContracts('secondary', 5, 202511, 1) returns the number of secondary @@ -52,21 +52,20 @@ def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: if str(termCode).endswith("11"): dateCondition = ( (LaborStatusForm.endDate.month.in_(fallMonths)) | - (LaborStatusForm.startDate.month.in_(fallMonths) & + (LaborStatusForm.startDate.month.in_(fallMonths) & # Reused check for year-long positions LaborStatusForm.endDate.month.in_(springMonths)) - ) + ) elif str(termCode).endswith("12"): dateCondition = ( (LaborStatusForm.startDate.month.in_(springMonths)) | (LaborStatusForm.startDate.month.in_(fallMonths) & LaborStatusForm.endDate.month.in_(springMonths)) - ) + ) else: dateCondition = ( (LaborStatusForm.startDate.month.in_(fallMonths) & LaborStatusForm.endDate.month.in_(springMonths)) - - ) + ) lsfCountPositions = FormHistory.select( ).join(LaborStatusForm From 2e7520744b76ec9f83561eb56ec3a77431c62ed3 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Sat, 8 Aug 2026 19:41:47 -0400 Subject: [PATCH 093/128] Finished surface testing of the functions --- app/templates/admin/manageDepartments.html | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 04c87db3e..e1bb7b264 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -142,6 +142,7 @@

Manage Departments

+ {% if entry.allocation %} Primary: @@ -156,6 +157,9 @@

Manage Departments

Break: {{ entry.requestedAllocation.breakHours }} hours + {% else %} + No current allocation + {% endif %} From 3fa1daf761ca6ebf69f2518b684595f7c8502535 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Sat, 8 Aug 2026 19:50:25 -0400 Subject: [PATCH 094/128] Fixed minor datatable bug realted to jinja rendering --- app/templates/admin/manageDepartments.html | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index e1bb7b264..abb4db223 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -142,8 +142,9 @@

Manage Departments

- {% if entry.allocation %} + + {% if entry.allocation %} Primary: {{entry.requestedAllocation.totalPrimaries}} @@ -163,8 +164,6 @@

Manage Departments

- - @@ -131,16 +131,6 @@

Current Term: {{currentAY.termName}}

- - Total Contracts - {{springContracts["used_total"]}} - {{allocations["totalAllocations"]}} - - - Total Primaries - {{springContracts["used_primaries"]}} - {{allocations["totalPrimaries"]}} - 10 Hour {{springContracts["used_10"]}} @@ -161,6 +151,16 @@

Current Term: {{currentAY.termName}}

{{springContracts["used_20"]}} {{allocations["primary_20"]}} + + Total Primaries + {{springContracts["used_primaries"]}} + {{allocations["totalPrimaries"]}} + + + Total Contracts + {{springContracts["used_total"]}} + {{allocations["totalAllocations"]}} + @@ -218,36 +218,36 @@

Current Term: {{currentAY.termName}}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Total Break Hours{{breakContracts['total']}}{{allocations["breakHours"]}}
Fall Break{{breakContracts['fall']}}
Thanksgiving{{breakContracts['thanksgiving']}}
Winter Break{{breakContracts['winter']}}
Spring Break{{breakContracts['spring']}}
Summer Term{{breakContracts['summer']}}
Fall Break{{breakContracts['fall']}}
Thanksgiving{{breakContracts['thanksgiving']}}
Winter Break{{breakContracts['winter']}}
Spring Break{{breakContracts['spring']}}
Summer Term{{breakContracts['summer']}}
Total Break Hours{{breakContracts['total']}}{{allocations["breakHours"]}}
From 47f4f57f4d0ac7dbef2c7716863ac4ee2e48f925 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 20 Aug 2026 14:43:14 -0400 Subject: [PATCH 098/128] added case for no allocation being found --- app/logic/allocationManager.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index 794d0cf6d..dbd35c18d 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -12,20 +12,24 @@ def getAllocation(termCode: int, dept: int, isFinal = True): This function returns a peewee object containing the selected allocation for given department and term. If you want the pending allocation, pass in False for isFinal. ''' - academicYearCode = int(str(termCode)[:4] + "00") - allocationObject = Allocation.select().where( - Allocation.termCode.in_([termCode,academicYearCode]), - Allocation.department == dept, - Allocation.isFinal == isFinal).dicts().get() - return allocationObject + try: + academicYearCode = int(str(termCode)[:4] + "00") + allocationObject = Allocation.select().where( + Allocation.termCode.in_([termCode,academicYearCode]), + Allocation.department == dept, + Allocation.isFinal == isFinal).dicts().get() + return allocationObject + except: + return 0 def getTotalAllocations(termCode: int, dept: int): ''' This function returns a dictionary representation of the given department's allocation for the given term. ''' - allocationObject = getAllocation(termCode, dept) - allocationDict = {"primary_10": allocationObject["primary_10"], + try: + allocationObject = getAllocation(termCode, dept) + allocationDict = {"primary_10": allocationObject["primary_10"], "primary_12": allocationObject["primary_12"], "primary_15": allocationObject["primary_15"], "primary_20": allocationObject["primary_20"], @@ -35,6 +39,17 @@ def getTotalAllocations(termCode: int, dept: int): "totalPrimaries": (allocationObject["primary_10"] + allocationObject["primary_12"] + allocationObject["primary_15"] + allocationObject["primary_20"]), "totalSecondaries": (allocationObject["secondary_5"] + allocationObject["secondary_10"]), "totalAllocations": (allocationObject["primary_10"] + allocationObject["primary_12"] + allocationObject["primary_15"] + allocationObject["primary_20"] + allocationObject["secondary_5"] + allocationObject["secondary_10"] )} + except: + allocationDict = {"primary_10": "", + "primary_12": "", + "primary_15": "", + "primary_20": "", + "secondary_5": "", + "secondary_10": "", + "breakHours": "No Allocations Found", + "totalPrimaries": "", + "totalSecondaries": "", + "totalAllocations": "No Allocations Found"} return allocationDict def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: int, AYtermCode: int = None): From 26be0a487f125d4c579f6c54cbce3524a4f9441a Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 20 Aug 2026 14:51:24 -0400 Subject: [PATCH 099/128] fixed order of table elements --- app/templates/main/allocationTable.html | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 39ca61016..72e82bc2e 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -172,16 +172,6 @@

Current Term: {{currentAY.termName}}

- - Total Contracts - {{springContracts["used_total"]}} - {{allocations["totalAllocations"]}} - - - Total Secondaries - {{springContracts["used_secondaries"]}} - {{allocations["totalSecondaries"]}} - 5 Hour {{springContracts["used_5_sec"]}} @@ -192,6 +182,16 @@

Current Term: {{currentAY.termName}}

{{springContracts["used_10_sec"]}} {{allocations["secondary_10"]}} + + Total Secondaries + {{springContracts["used_secondaries"]}} + {{allocations["totalSecondaries"]}} + + + Total Contracts + {{springContracts["used_total"]}} + {{allocations["totalAllocations"]}} +

From b3ec72e55828b40b53b415d26ebf4bb99342e88b Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 20 Aug 2026 14:51:54 -0400 Subject: [PATCH 100/128] removed print statements from tests --- tests/code/test_tracy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/code/test_tracy.py b/tests/code/test_tracy.py index 631c6eacd..f09eb5aee 100644 --- a/tests/code/test_tracy.py +++ b/tests/code/test_tracy.py @@ -19,7 +19,6 @@ def test_getStudents(self, tracy): with app.app_context(): students = tracy.getStudents() for s in students: - print(s.STU_CPO) assert ['Antonia','Barbara','Elaheh','Guillermo','Jeremiah','Kat', 'Oluwagbayi', 'Test', 'Tyler'] == [s.FIRST_NAME for s in students] assert ['777','118','718','300','420','420', '883', '700', '420'] == [s.STU_CPO for s in students] From d374990552a8daf35a11800d6bc109dfe3090ec9 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 21 Aug 2026 09:27:19 -0400 Subject: [PATCH 101/128] fixed table resizing issue --- app/static/css/allocationTable.css | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index dd1812c1d..80e0d820a 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -17,10 +17,6 @@ font-size: 18px; font-weight: 525; } -.collapse{ - padding-left: 5% ; - padding-right: 5%; -} .accordion{ padding-left: 5%; padding-right: 5%; From 4cc64c912cd931dc16ae8656c1355a6996649300 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 21 Aug 2026 09:51:21 -0400 Subject: [PATCH 102/128] added tooltips to the Total Contracts Table --- app/static/css/allocationTable.css | 6 ++++++ app/static/js/allocationTable.js | 6 +++++- app/templates/main/allocationTable.html | 8 ++++---- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index 80e0d820a..fa43d0205 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -56,4 +56,10 @@ } .termDisplay{ margin-right:auto; +} +.bi-info-circle { + padding: 3px 3.5px 1.5px 3.5px; + font-size: 1.5rem; + vertical-align: middle; + color:#6e6e6e; } \ No newline at end of file diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js index 51a344a2c..8bda5cd19 100644 --- a/app/static/js/allocationTable.js +++ b/app/static/js/allocationTable.js @@ -15,4 +15,8 @@ $(document).ready( function(){ const springTermPrimaries = initTable('#springTermPrimaries'); const springTermSecondaries = initTable('#springTermSecondaries'); const breakTable = initTable('#breakTable'); -}); \ No newline at end of file +}); + +$(function () { + $('[data-toggle="tooltip"]').tooltip() +}) \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index 72e82bc2e..ddc182b42 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -71,7 +71,7 @@

Current Term: {{currentAY.termName}}

{{allocations["totalPrimaries"]}} - Total Contracts + Total Contracts {{fallContracts["used_total"]}} {{allocations["totalAllocations"]}} @@ -102,7 +102,7 @@

Current Term: {{currentAY.termName}}

{{allocations["totalSecondaries"]}} - Total Contracts + Total Contracts {{fallContracts["used_total"]}} {{allocations["totalAllocations"]}} @@ -157,7 +157,7 @@

Current Term: {{currentAY.termName}}

{{allocations["totalPrimaries"]}} - Total Contracts + Total Contracts {{springContracts["used_total"]}} {{allocations["totalAllocations"]}} @@ -188,7 +188,7 @@

Current Term: {{currentAY.termName}}

{{allocations["totalSecondaries"]}} - Total Contracts + Total Contracts {{springContracts["used_total"]}} {{allocations["totalAllocations"]}} From da3268030ff56dfcfdad70bc67e27bcbeda84ff1 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 21 Aug 2026 10:15:21 -0400 Subject: [PATCH 103/128] centered the text for the accordions --- app/static/css/allocationTable.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css index fa43d0205..ec2485838 100644 --- a/app/static/css/allocationTable.css +++ b/app/static/css/allocationTable.css @@ -50,7 +50,7 @@ .btn-block{ align-items: center; align-self: center; - text-align: center; + text-align: left; justify-content: center; height: 100% } From 8d32fe207e919fdd3f680c2627682baed0c833b4 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 21 Aug 2026 10:15:50 -0400 Subject: [PATCH 104/128] added a tooltip for the break allocations --- app/templates/main/allocationTable.html | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html index ddc182b42..e7ccafcb0 100644 --- a/app/templates/main/allocationTable.html +++ b/app/templates/main/allocationTable.html @@ -71,7 +71,7 @@

Current Term: {{currentAY.termName}}

{{allocations["totalPrimaries"]}} - Total Contracts + Total Contracts {{fallContracts["used_total"]}} {{allocations["totalAllocations"]}} @@ -102,7 +102,7 @@

Current Term: {{currentAY.termName}}

{{allocations["totalSecondaries"]}} - Total Contracts + Total Contracts {{fallContracts["used_total"]}} {{allocations["totalAllocations"]}} @@ -157,7 +157,7 @@

Current Term: {{currentAY.termName}}

{{allocations["totalPrimaries"]}} - Total Contracts + Total Contracts {{springContracts["used_total"]}} {{allocations["totalAllocations"]}} @@ -188,7 +188,7 @@

Current Term: {{currentAY.termName}}

{{allocations["totalSecondaries"]}} - Total Contracts + Total Contracts {{springContracts["used_total"]}} {{allocations["totalAllocations"]}} @@ -244,7 +244,7 @@

Current Term: {{currentAY.termName}}

- Total Break Hours + Total Break Hours {{breakContracts['total']}} {{allocations["breakHours"]}} From 187a5b660ac742eea5d26dc6ea40d13cb8bc4c77 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 21 Aug 2026 10:37:37 -0400 Subject: [PATCH 105/128] added a logic file to get all Current Terms --- app/controllers/main_routes/main_routes.py | 17 +++------------ app/logic/getCurrentTerms.py | 24 ++++++++++++++++++++++ 2 files changed, 27 insertions(+), 14 deletions(-) create mode 100644 app/logic/getCurrentTerms.py diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 560d02fa7..4205c6b3b 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -25,6 +25,7 @@ from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions from app.logic.allocationManager import getBreakContracts, getContractedAllocations, getTotalAllocations +from app.logic.getCurrentTerms import getCurrentTerms @main_bp.route('/logout', methods=['GET']) @@ -98,20 +99,8 @@ def allocationTable(org=None, account=None): if dept.departmentID not in allowedDepartmentIds: return render_template('errors/403.html'), 403 - - currentDate = date.today() - if currentDate.month <= 6: - # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. - # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. - springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() - currentAY = Term.select().where(Term.termCode == currentDate.year * 100 - 100).get() - fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11 - 100).get() - - else: - fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() - currentAY = Term.select().where(Term.termCode == currentDate.year * 100).get() - springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() - + currentAY, fallTerm, springTerm = getCurrentTerms() + allocationDict = getTotalAllocations(currentAY.termCode, dept) fallContracts = getContractedAllocations(fallTerm.termCode, dept) springContracts = getContractedAllocations(springTerm.termCode, dept) diff --git a/app/logic/getCurrentTerms.py b/app/logic/getCurrentTerms.py new file mode 100644 index 000000000..6d26da809 --- /dev/null +++ b/app/logic/getCurrentTerms.py @@ -0,0 +1,24 @@ +from app.models.term import Term +from datetime import datetime, date + +def getCurrentTerms(): + ''' + Gets 3 primary terms for the current Academic Year (AY) + This means it attempt to get an AY, fall term, and spring term + This returns all three objects individually for each of the terms. + ''' + currentDate = date.today() + if currentDate.month <= 6: + # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. + # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() + currentAY = Term.select().where(Term.termCode == currentDate.year * 100 - 100).get() + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11 - 100).get() + return currentAY, fallTerm, springTerm + + else: + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() + currentAY = Term.select().where(Term.termCode == currentDate.year * 100).get() + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() + return currentAY, fallTerm, springTerm + From 8747fdad811da8f0606f6ec273ddee38f2653375 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 21 Aug 2026 11:16:26 -0400 Subject: [PATCH 106/128] removed unecessary import --- app/controllers/main_routes/main_routes.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 4205c6b3b..4a0199ec9 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -1,7 +1,6 @@ from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify from peewee import JOIN, DoesNotExist, fn from functools import reduce -from datetime import datetime, date import operator from app.models.department import Department @@ -100,7 +99,7 @@ def allocationTable(org=None, account=None): return render_template('errors/403.html'), 403 currentAY, fallTerm, springTerm = getCurrentTerms() - + allocationDict = getTotalAllocations(currentAY.termCode, dept) fallContracts = getContractedAllocations(fallTerm.termCode, dept) springContracts = getContractedAllocations(springTerm.termCode, dept) From dd2516f51967734d18e2aa63fecdc404fd61969e Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 21 Aug 2026 11:16:57 -0400 Subject: [PATCH 107/128] began writing test getCurrentTerms.py --- tests/code/test_getCurrentTerms.py | 55 ++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 tests/code/test_getCurrentTerms.py diff --git a/tests/code/test_getCurrentTerms.py b/tests/code/test_getCurrentTerms.py new file mode 100644 index 000000000..1171981c8 --- /dev/null +++ b/tests/code/test_getCurrentTerms.py @@ -0,0 +1,55 @@ +import pytest +from app import app + +from app.models.term import Term +from app.logic.getCurrentTerms import getCurrentTerms +from datetime import datetime, date + +@pytest.fixture +def test_terms(): + + currentYear = date.today().year - 12 + print(currentYear) + + test_currentAY = Term.create( + termCode = f"{currentYear}00", + termName = f"AY {currentYear}-2001", + termStart = f"{currentYear}-08-01", + termEnd = f"{currentYear + 1}-05-01", + termState = 1, + primaryCutOff = f"{currentYear + 1}-09-01", + adjustmentCutOff = f"2002-10-01" + ) + test_fallTerm = Term.create( + termCode = f"{currentYear}11", + termName = f"Fall {currentYear}", + termStart = f"{currentYear}-08-01", + termEnd = f"{currentYear}-12-12", + termState = 1, + primaryCutOff = f"{currentYear}-09-01", + adjustmentCutOff = f"{currentYear}-10-01" + ) + test_springTerm = Term.create( + termCode = f"{currentYear}12", + termName = f"Spring {currentYear + 1}", + termStart = f"{currentYear + 1}-01-01", + termEnd = f"{currentYear + 1}-05-01", + termState = 1, + primaryCutOff = f"{currentYear + 1}-02-01", + adjustmentCutOff = f"{currentYear + 1}-3-01" + ) + yield test_currentAY, test_fallTerm, test_springTerm + + #destroy all created data + test_currentAY.delete_instance() + test_fallTerm.delete_instance() + test_springTerm.delete_instance() + +@pytest.mark.integration +def test_getCurrentTerms(test_terms): + currentAY, fallTerm, springTerm = getCurrentTerms() + assert currentAY != test_terms[0] + assert fallTerm != test_terms[1] + assert springTerm != test_terms[2] + + return 1 \ No newline at end of file From 784f429901dc2dc9293aea533ae50e0afc1dc4c1 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 24 Aug 2026 09:51:42 -0400 Subject: [PATCH 108/128] reworked getCurrentTerms to allow for all terms --- app/controllers/main_routes/main_routes.py | 2 +- app/logic/getCurrentTerms.py | 24 ------------ app/logic/getTerms.py | 39 +++++++++++++++++++ ...st_getCurrentTerms.py => test_getTerms.py} | 25 ++++++------ 4 files changed, 52 insertions(+), 38 deletions(-) delete mode 100644 app/logic/getCurrentTerms.py create mode 100644 app/logic/getTerms.py rename tests/code/{test_getCurrentTerms.py => test_getTerms.py} (70%) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 4a0199ec9..267b98561 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -24,7 +24,7 @@ from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions from app.logic.allocationManager import getBreakContracts, getContractedAllocations, getTotalAllocations -from app.logic.getCurrentTerms import getCurrentTerms +from app.logic.getTerms import getCurrentTerms @main_bp.route('/logout', methods=['GET']) diff --git a/app/logic/getCurrentTerms.py b/app/logic/getCurrentTerms.py deleted file mode 100644 index 6d26da809..000000000 --- a/app/logic/getCurrentTerms.py +++ /dev/null @@ -1,24 +0,0 @@ -from app.models.term import Term -from datetime import datetime, date - -def getCurrentTerms(): - ''' - Gets 3 primary terms for the current Academic Year (AY) - This means it attempt to get an AY, fall term, and spring term - This returns all three objects individually for each of the terms. - ''' - currentDate = date.today() - if currentDate.month <= 6: - # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. - # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. - springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() - currentAY = Term.select().where(Term.termCode == currentDate.year * 100 - 100).get() - fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11 - 100).get() - return currentAY, fallTerm, springTerm - - else: - fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() - currentAY = Term.select().where(Term.termCode == currentDate.year * 100).get() - springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() - return currentAY, fallTerm, springTerm - diff --git a/app/logic/getTerms.py b/app/logic/getTerms.py new file mode 100644 index 000000000..9e4503a08 --- /dev/null +++ b/app/logic/getTerms.py @@ -0,0 +1,39 @@ +from app.models.term import Term +from datetime import datetime, date + +def getCurrentTerms(year: int = None, month: int = None): + ''' + Gets 3 primary terms for the current Academic Year (AY) + This means it attempt to get an AY, fall term, and spring term + This returns all three objects individually for each of the terms. + Leaving no input variables means that it will check for the current year instead of a given one. + ''' + if year and month: + if month <= 6: + # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. + # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. + springTerm = Term.select().where(Term.termCode == year * 100 + 12 - 100).get() + currentAY = Term.select().where(Term.termCode == year * 100 - 100).get() + fallTerm = Term.select().where(Term.termCode == year * 100 + 11 - 100).get() + return currentAY, fallTerm, springTerm + else: + fallTerm = Term.select().where(Term.termCode == year * 100 + 11).get() + currentAY = Term.select().where(Term.termCode == year * 100).get() + springTerm = Term.select().where(Term.termCode == year * 100 + 12).get() + + return currentAY, fallTerm, springTerm + else: + # The check for the current year, not a given one + currentDate = date.today() + if month <= 6: + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() + currentAY = Term.select().where(Term.termCode == currentDate.year * 100 - 100).get() + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11 - 100).get() + return currentAY, fallTerm, springTerm + + else: + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() + currentAY = Term.select().where(Term.termCode == currentDate.year * 100).get() + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() + return currentAY, fallTerm, springTerm + diff --git a/tests/code/test_getCurrentTerms.py b/tests/code/test_getTerms.py similarity index 70% rename from tests/code/test_getCurrentTerms.py rename to tests/code/test_getTerms.py index 1171981c8..a268568fb 100644 --- a/tests/code/test_getCurrentTerms.py +++ b/tests/code/test_getTerms.py @@ -2,18 +2,18 @@ from app import app from app.models.term import Term -from app.logic.getCurrentTerms import getCurrentTerms +from app.logic.getTerms import getCurrentTerms from datetime import datetime, date @pytest.fixture def test_terms(): - currentYear = date.today().year - 12 + currentYear = date.today().year - 2000 print(currentYear) test_currentAY = Term.create( - termCode = f"{currentYear}00", - termName = f"AY {currentYear}-2001", + termCode = int(f"{currentYear}00"), + termName = f"AY {currentYear}-{currentYear - 1}", termStart = f"{currentYear}-08-01", termEnd = f"{currentYear + 1}-05-01", termState = 1, @@ -21,7 +21,7 @@ def test_terms(): adjustmentCutOff = f"2002-10-01" ) test_fallTerm = Term.create( - termCode = f"{currentYear}11", + termCode = int(f"{currentYear}11"), termName = f"Fall {currentYear}", termStart = f"{currentYear}-08-01", termEnd = f"{currentYear}-12-12", @@ -30,7 +30,7 @@ def test_terms(): adjustmentCutOff = f"{currentYear}-10-01" ) test_springTerm = Term.create( - termCode = f"{currentYear}12", + termCode = int(f"{currentYear}12"), termName = f"Spring {currentYear + 1}", termStart = f"{currentYear + 1}-01-01", termEnd = f"{currentYear + 1}-05-01", @@ -38,7 +38,7 @@ def test_terms(): primaryCutOff = f"{currentYear + 1}-02-01", adjustmentCutOff = f"{currentYear + 1}-3-01" ) - yield test_currentAY, test_fallTerm, test_springTerm + yield test_currentAY, test_fallTerm, test_springTerm, currentYear #destroy all created data test_currentAY.delete_instance() @@ -47,9 +47,8 @@ def test_terms(): @pytest.mark.integration def test_getCurrentTerms(test_terms): - currentAY, fallTerm, springTerm = getCurrentTerms() - assert currentAY != test_terms[0] - assert fallTerm != test_terms[1] - assert springTerm != test_terms[2] - - return 1 \ No newline at end of file + currentAY, fallTerm, springTerm = getCurrentTerms(test_terms[3], 8) + + assert currentAY == test_terms[0] + assert fallTerm == test_terms[1] + assert springTerm == test_terms[2] \ No newline at end of file From 11a152b6aac515b9e8c4f6785e7ce67d58c2654b Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 24 Aug 2026 09:57:49 -0400 Subject: [PATCH 109/128] fixed logic month check for current year --- app/logic/getTerms.py | 2 +- tests/code/test_getTerms.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/logic/getTerms.py b/app/logic/getTerms.py index 9e4503a08..2d9acbe2e 100644 --- a/app/logic/getTerms.py +++ b/app/logic/getTerms.py @@ -25,7 +25,7 @@ def getCurrentTerms(year: int = None, month: int = None): else: # The check for the current year, not a given one currentDate = date.today() - if month <= 6: + if currentDate.month <= 6: springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() currentAY = Term.select().where(Term.termCode == currentDate.year * 100 - 100).get() fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11 - 100).get() diff --git a/tests/code/test_getTerms.py b/tests/code/test_getTerms.py index a268568fb..6a6c979ec 100644 --- a/tests/code/test_getTerms.py +++ b/tests/code/test_getTerms.py @@ -7,6 +7,7 @@ @pytest.fixture def test_terms(): + # Make terms and get a set year, its far enough back that the test won't break other data. currentYear = date.today().year - 2000 print(currentYear) @@ -47,7 +48,7 @@ def test_terms(): @pytest.mark.integration def test_getCurrentTerms(test_terms): - currentAY, fallTerm, springTerm = getCurrentTerms(test_terms[3], 8) + currentAY, fallTerm, springTerm = getCurrentTerms(test_terms[3], 8) # Get terms for the year that is selected. assert currentAY == test_terms[0] assert fallTerm == test_terms[1] From fe8edc5eb43c06e3f9fbce318d9cf2b37745e96b Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 24 Aug 2026 10:19:59 -0400 Subject: [PATCH 110/128] fixed spring/fall check when given an AY --- app/logic/getTerms.py | 26 +++++++++++--------------- tests/code/test_getTerms.py | 9 +++++---- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/app/logic/getTerms.py b/app/logic/getTerms.py index 2d9acbe2e..ea3b60da6 100644 --- a/app/logic/getTerms.py +++ b/app/logic/getTerms.py @@ -1,29 +1,25 @@ from app.models.term import Term from datetime import datetime, date -def getCurrentTerms(year: int = None, month: int = None): +def getTerms(academicYear: str = None): ''' - Gets 3 primary terms for the current Academic Year (AY) + Gets 3 primary terms for the current/given Academic Year (AY) This means it attempt to get an AY, fall term, and spring term This returns all three objects individually for each of the terms. Leaving no input variables means that it will check for the current year instead of a given one. ''' - if year and month: - if month <= 6: - # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. - # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. - springTerm = Term.select().where(Term.termCode == year * 100 + 12 - 100).get() - currentAY = Term.select().where(Term.termCode == year * 100 - 100).get() - fallTerm = Term.select().where(Term.termCode == year * 100 + 11 - 100).get() + if academicYear: + # Uses only the first half of the AY since term codes are split by year. + # i.e. Fall 2026 -> 202611, while Spring 2027 -> 202612. + springTerm = Term.select().where(Term.termCode == int(academicYear[:4]) * 100 + 12).get() + currentAY = Term.select().where(Term.termCode == int(academicYear[:4]) * 100).get() + fallTerm = Term.select().where(Term.termCode == int(academicYear[:4]) * 100 + 11).get() return currentAY, fallTerm, springTerm - else: - fallTerm = Term.select().where(Term.termCode == year * 100 + 11).get() - currentAY = Term.select().where(Term.termCode == year * 100).get() - springTerm = Term.select().where(Term.termCode == year * 100 + 12).get() - return currentAY, fallTerm, springTerm + else: - # The check for the current year, not a given one + # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. + # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. currentDate = date.today() if currentDate.month <= 6: springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() diff --git a/tests/code/test_getTerms.py b/tests/code/test_getTerms.py index 6a6c979ec..5016a5c44 100644 --- a/tests/code/test_getTerms.py +++ b/tests/code/test_getTerms.py @@ -9,12 +9,13 @@ def test_terms(): # Make terms and get a set year, its far enough back that the test won't break other data. - currentYear = date.today().year - 2000 + currentYear = 2000 + AcademicYear = f"{currentYear}-{currentYear + 1}" print(currentYear) test_currentAY = Term.create( termCode = int(f"{currentYear}00"), - termName = f"AY {currentYear}-{currentYear - 1}", + termName = f"AY {currentYear}-{currentYear + 1}", termStart = f"{currentYear}-08-01", termEnd = f"{currentYear + 1}-05-01", termState = 1, @@ -39,7 +40,7 @@ def test_terms(): primaryCutOff = f"{currentYear + 1}-02-01", adjustmentCutOff = f"{currentYear + 1}-3-01" ) - yield test_currentAY, test_fallTerm, test_springTerm, currentYear + yield test_currentAY, test_fallTerm, test_springTerm, AcademicYear #destroy all created data test_currentAY.delete_instance() @@ -48,7 +49,7 @@ def test_terms(): @pytest.mark.integration def test_getCurrentTerms(test_terms): - currentAY, fallTerm, springTerm = getCurrentTerms(test_terms[3], 8) # Get terms for the year that is selected. + currentAY, fallTerm, springTerm = getCurrentTerms(test_terms[3]) # Get terms for the year that is selected. assert currentAY == test_terms[0] assert fallTerm == test_terms[1] From 4357e6256db0f9a1db95dab7ef0d4dcba9d8b202 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 24 Aug 2026 10:25:47 -0400 Subject: [PATCH 111/128] refactored function names: getCurrentTerms() -> getTerms() --- app/controllers/main_routes/main_routes.py | 4 ++-- tests/code/test_getTerms.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 267b98561..b651fe495 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -24,7 +24,7 @@ from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions from app.logic.allocationManager import getBreakContracts, getContractedAllocations, getTotalAllocations -from app.logic.getTerms import getCurrentTerms +from app.logic.getTerms import getTerms @main_bp.route('/logout', methods=['GET']) @@ -98,7 +98,7 @@ def allocationTable(org=None, account=None): if dept.departmentID not in allowedDepartmentIds: return render_template('errors/403.html'), 403 - currentAY, fallTerm, springTerm = getCurrentTerms() + currentAY, fallTerm, springTerm = getTerms() allocationDict = getTotalAllocations(currentAY.termCode, dept) fallContracts = getContractedAllocations(fallTerm.termCode, dept) diff --git a/tests/code/test_getTerms.py b/tests/code/test_getTerms.py index 5016a5c44..eaca2be64 100644 --- a/tests/code/test_getTerms.py +++ b/tests/code/test_getTerms.py @@ -2,7 +2,7 @@ from app import app from app.models.term import Term -from app.logic.getTerms import getCurrentTerms +from app.logic.getTerms import getTerms from datetime import datetime, date @pytest.fixture @@ -49,7 +49,7 @@ def test_terms(): @pytest.mark.integration def test_getCurrentTerms(test_terms): - currentAY, fallTerm, springTerm = getCurrentTerms(test_terms[3]) # Get terms for the year that is selected. + currentAY, fallTerm, springTerm = getTerms(test_terms[3]) # Get terms for the year that is selected. assert currentAY == test_terms[0] assert fallTerm == test_terms[1] From 6d6997cec1fdaba1270770df7f42c65ec763658a Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 27 Aug 2026 08:29:33 -0400 Subject: [PATCH 112/128] removed merge conflict fragment --- app/templates/main/departmentPortal.html | 4 ---- tests/code/test_allocationManager.py | 3 --- 2 files changed, 7 deletions(-) diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 0350aee6b..7588ce342 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -78,11 +78,7 @@

{{used}} contracted of {{allocated or 0}} allocated Positions

diff --git a/tests/code/test_allocationManager.py b/tests/code/test_allocationManager.py index 45f901e34..7b4fe6e78 100644 --- a/tests/code/test_allocationManager.py +++ b/tests/code/test_allocationManager.py @@ -260,7 +260,6 @@ def test_getContractedAllocations(testLaborStatusForm, testTerm, testDepartment, assert contractedAllocation['break_hours'] == 500 @pytest.mark.integration -<<<<<<< HEAD:tests/code/test_allocationManger.py def test_getContractedAllocations_withoutAnAllocationRow(testLaborStatusForm, testTerm, testDepartment, testFormHistory): ''' getContractedAllocations must not require an Allocation row to exist for @@ -310,7 +309,6 @@ def test_getContractedAllocations_sumsBreakHoursAcrossAcademicYearCode(testDepar academicYearForm.delete_instance() specificTerm.delete_instance() academicYearTerm.delete_instance() -======= def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartment,testTerm): # Test that the formHistory object exists @@ -347,4 +345,3 @@ def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartme breakContractHours = getBreakContracts(testBreakTerm, testDepartment) assert breakContractHours == 0 ->>>>>>> f0ab8139ac7ae3967301c5e539fd15f4e69896ec:tests/code/test_allocationManager.py From ded0a5e029abce282f9cd80f6cfb51ba0c552fa1 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Thu, 27 Aug 2026 09:44:41 -0400 Subject: [PATCH 113/128] fixed broken icons in css --- app/static/css/departmentPortal.css | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index 88ae06e29..0f64aa2e9 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -11,18 +11,6 @@ padding: 1rem; min-width: 100%; } - -.members-icon { - display: inline-block; - border: 1px solid #c0c0c0; - border-radius: 8px; - padding: 3px 3.5px 1.5px; - font-size: 36px; - color: #6e6e6e; -} - margin-bottom: 20px; - min-height: 200px; -} .bi-suitcase-lg-fill { /* Bootstrap Icon */ border: 1px solid #c0c0c0; border-radius: 8px; From ba90482631697ffc91decadb0b0653e785c79170 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 28 Aug 2026 09:50:50 -0400 Subject: [PATCH 114/128] added smaller logic for the allocation card --- app/controllers/main_routes/main_routes.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 1e9dd42c4..89f6ca978 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -2,6 +2,7 @@ from peewee import JOIN, DoesNotExist, fn from functools import reduce import operator +from datetime import date from app.models.department import Department from app.models.supervisor import Supervisor @@ -79,6 +80,16 @@ def departmentPortal(org=None,account=None): allocationSummary = getDepartmentAllocationSummary(dept) recentTerm = allocationSummary["term"] + currentAY, fallTerm, springTerm = getTerms() + allocationDict = getTotalAllocations(currentAY, dept) + + contracts = {} + currentDate = date.today() + if currentDate.month >= 7: + contracts = getContractedAllocations(fallTerm, dept) + else: + contracts = getContractedAllocations(springTerm, dept) + if recentTerm: try: allocation = Allocation.select(Allocation, Term).join(Term).where(Allocation.department == dept, Allocation.termCode == recentTerm.termCode).get() From 300b7b0c1002f0eb38f1dddc258554982fa4553c Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 28 Aug 2026 10:28:34 -0400 Subject: [PATCH 115/128] HTML now uses new variables --- app/controllers/main_routes/main_routes.py | 8 +-- app/templates/main/departmentPortal.html | 74 +++++++++++----------- 2 files changed, 39 insertions(+), 43 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 89f6ca978..e321c87ca 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -103,13 +103,9 @@ def departmentPortal(org=None,account=None): return render_template('main/departmentPortal.html', departments = departments, department = dept, - allocation = allocation, - allocated = allocationSummary["allocated"], - used = allocationSummary["used"], - term = recentTerm, + contracts = contracts, + allocation = allocationDict, currentSemester = allocationSummary["currentSemester"], - usedPositions = allocationSummary["usedPositions"], - breakHours = allocationSummary["breakHours"], supervisors = supervisors, laborCoordinators=laborCoordinators, currentUser=currentUser, diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 7588ce342..3f2c0b33b 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -36,47 +36,47 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e
-
- -
-
-

Current Allocations

-
- {% macro allocationRow(hours, used, allocated) -%} - {{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }}
(out of {{ allocated }} allocation{{ 's' if allocated != 1 else '' }}) - {%- endmacro %} -
-
-

{{ currentSemester if currentSemester else "No term data" }}

-

{{used}} contracted of {{allocated or 0}} allocated Positions

+
+
-
- - - - - - {{ allocationRow(10, usedPositions.used10, allocation.primary_10) }} - {{ allocationRow(12, usedPositions.used12, allocation.primary_12) }} - {{ allocationRow(15, usedPositions.used15, allocation.primary_15) }} - {{ allocationRow(20, usedPositions.used20, allocation.primary_20) }} - -
Primary
- - - - - - {{ allocationRow(5, usedPositions.usedSecondary5, allocation.secondary_5) }} - {{ allocationRow(10, usedPositions.usedSecondary10, allocation.secondary_10) }} - -
Secondary
+
+

Current Allocations

+
+ {% macro allocationRow(hours, used, allocated) -%} + {{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }} out of {{ allocated }} allocation{{ 's' if allocated != 1 else '' }} + {%- endmacro %} +
+
+

{{ currentSemester if currentSemester else "No term data" }}

+
{{used}} contracted of {{allocated or 0}} allocated Positions
+
+
+ + + + + + {{ allocationRow(10, contracts["used_10"], allocation["primary_10"]) }} + {{ allocationRow(12, contracts["used_12"], allocation["primary_12"]) }} + {{ allocationRow(15, contracts["used_15"], allocation["primary_15"]) }} + {{ allocationRow(20, contracts["used_20"], allocation["primary_20"]) }} + +
Primary
+ + + + + + {{ allocationRow(5,contracts["used_5_sec"], allocation["secondary_5"]) }} + {{ allocationRow(10, contracts["used_10_sec"], allocation["secondary_10"]) }} + +
Secondary
+
-
From 10fdda4fa7f19ad219a7c09cdbebf53c7b4145e5 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 28 Aug 2026 10:38:59 -0400 Subject: [PATCH 116/128] added the current semester --- app/controllers/main_routes/main_routes.py | 22 ++++------------------ app/logic/getTerms.py | 12 ++++++++++++ 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index e321c87ca..7e8b0e3e1 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -27,7 +27,7 @@ from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions from app.logic.allocationManager import getBreakContracts, getContractedAllocations, getTotalAllocations -from app.logic.getTerms import getTerms +from app.logic.getTerms import getTerms, getCurrentSemester @main_bp.route('/logout', methods=['GET']) @@ -77,26 +77,12 @@ def departmentPortal(org=None,account=None): supervisors, laborCoordinators = getSupervisors(dept) - allocationSummary = getDepartmentAllocationSummary(dept) - recentTerm = allocationSummary["term"] currentAY, fallTerm, springTerm = getTerms() allocationDict = getTotalAllocations(currentAY, dept) + currentSemester = getCurrentSemester() + contracts = getContractedAllocations(currentSemester, dept) - contracts = {} - currentDate = date.today() - if currentDate.month >= 7: - contracts = getContractedAllocations(fallTerm, dept) - else: - contracts = getContractedAllocations(springTerm, dept) - - if recentTerm: - try: - allocation = Allocation.select(Allocation, Term).join(Term).where(Allocation.department == dept, Allocation.termCode == recentTerm.termCode).get() - except DoesNotExist: - allocation = None - else: - allocation = None positionsList, posURL = getActivePositions(dept) @@ -105,7 +91,7 @@ def departmentPortal(org=None,account=None): department = dept, contracts = contracts, allocation = allocationDict, - currentSemester = allocationSummary["currentSemester"], + currentSemester = currentSemester.termName, supervisors = supervisors, laborCoordinators=laborCoordinators, currentUser=currentUser, diff --git a/app/logic/getTerms.py b/app/logic/getTerms.py index ea3b60da6..19df06ec9 100644 --- a/app/logic/getTerms.py +++ b/app/logic/getTerms.py @@ -33,3 +33,15 @@ def getTerms(academicYear: str = None): springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() return currentAY, fallTerm, springTerm +def getCurrentSemester(): + ''' + The difference between this function and the one above is that it gets just the current term + It does not get both Fall and Spring, it just gets one depending on the month. + ''' + currentDate = date.today() + if currentDate.month <= 6: + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() + return springTerm + else: + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() + return fallTerm From 02a100d7d0561961fb9849e275041913f3f52546 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 28 Aug 2026 11:14:23 -0400 Subject: [PATCH 117/128] fixed syntax when no value it given --- app/templates/main/departmentPortal.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index 3f2c0b33b..d82a4f3fe 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -45,12 +45,12 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e

Current Allocations

{% macro allocationRow(hours, used, allocated) -%} - {{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }} out of {{ allocated }} allocation{{ 's' if allocated != 1 else '' }} + {{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }} out of {{ allocated if allocated is integer else 0 }} allocation{{ 's' if allocated != 1 else '' }} {%- endmacro %}
-

{{ currentSemester if currentSemester else "No term data" }}

-
{{used}} contracted of {{allocated or 0}} allocated Positions
+

{{ currentSemester if currentSemester else "No term data" }}

+

{{contracts["used_total"]}} contracted of {{allocation["totalAllocations"] if allocation["totalAllocations"] is integer else 0}} allocations

From 9bb31976a3d256000b979650da9aa27687ea0445 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 28 Aug 2026 11:16:48 -0400 Subject: [PATCH 118/128] removed out of scope change --- database/migrate_db_tracy.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/database/migrate_db_tracy.sh b/database/migrate_db_tracy.sh index fabca9d30..0b5466dc8 100755 --- a/database/migrate_db_tracy.sh +++ b/database/migrate_db_tracy.sh @@ -1,6 +1,4 @@ -export FLASK_APP="$(cd "$(dirname "$0")/.." && pwd)/app.py" - DB_DIR=tracy_migrations flask db init -d $DB_DIR From fd86d2a7210755c5ab7b0d0042c9148c1e0a8b05 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 28 Aug 2026 11:17:47 -0400 Subject: [PATCH 119/128] removed out of scope database issue --- database/migrate_db.sh | 2 -- 1 file changed, 2 deletions(-) diff --git a/database/migrate_db.sh b/database/migrate_db.sh index 4153d172a..e2ba150e7 100755 --- a/database/migrate_db.sh +++ b/database/migrate_db.sh @@ -1,6 +1,4 @@ -export PYTHONPATH="$(cd "$(dirname "$0")/.." && pwd):$PYTHONPATH" - pem init # See: https://stackoverflow.com/questions/394230/how-to-detect-the-os-from-a-bash-script/18434831 From 0b0324a5e471df492f32e87a3dceb0bd5b0a92de Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 28 Aug 2026 11:20:19 -0400 Subject: [PATCH 120/128] removed unused dateTime import --- app/controllers/main_routes/main_routes.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 7e8b0e3e1..d5d632b36 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -2,7 +2,6 @@ from peewee import JOIN, DoesNotExist, fn from functools import reduce import operator -from datetime import date from app.models.department import Department from app.models.supervisor import Supervisor From db6f90a51d90d0168e5fe29cbf81b3758a0f4d6b Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Fri, 28 Aug 2026 11:49:01 -0400 Subject: [PATCH 121/128] removed unused logic file --- app/controllers/main_routes/main_routes.py | 2 - app/logic/getAllocation.py | 71 ------ tests/code/test_getAllocation.py | 251 --------------------- 3 files changed, 324 deletions(-) delete mode 100644 app/logic/getAllocation.py delete mode 100644 tests/code/test_getAllocation.py diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index d5d632b36..cc6f3b86c 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -12,7 +12,6 @@ from app.models.term import Term from app.models.allocation import Allocation from app.models.positionHistory import PositionHistory -from app.models.allocation import Allocation from app.controllers.admin_routes.allPendingForms import checkAdjustment from app.controllers.main_routes import main_bp @@ -22,7 +21,6 @@ from app.login_manager import require_login, logout from app.logic.getTableData import getDatatableData from app.logic.banner import Banner -from app.logic.getAllocation import getDepartmentAllocationSummary from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions from app.logic.allocationManager import getBreakContracts, getContractedAllocations, getTotalAllocations diff --git a/app/logic/getAllocation.py b/app/logic/getAllocation.py deleted file mode 100644 index 5c09cd497..000000000 --- a/app/logic/getAllocation.py +++ /dev/null @@ -1,71 +0,0 @@ -from datetime import date - -from app.logic.allocationManager import getContractedAllocations -from app.models.allocation import Allocation -from app.models.term import Term - - -def getCurrentSemesterLabel(term): - """Return the Fall/Spring label (e.g. "Fall 2025") for the AY term's - current semester, picking the season from today's month.""" - if not term: - return None - academicYear = int(str(term.termCode)[:4]) - if date.today().month >= 8: - return f"Fall {academicYear}" - return f"Spring {academicYear + 1}" - - -def getDepartmentAllocationSummary(department): - """Return allocation-utilization values for a department's most recent term.""" - result = { - "term": None, - "currentSemester": None, - "allocated": 0, - "used": 0, - "usedPositions": { - "used10": 0, - "used12": 0, - "used15": 0, - "used20": 0, - "usedSecondary5": 0, - "usedSecondary10": 0, - }, - "breakHours": 0, - } - - departmentAllocations = list( - Allocation.select(Allocation, Term).join(Term).where(Allocation.department == department) - ) - if not departmentAllocations: - return result - - recentTerm = Term.order_by_term([a.termCode for a in departmentAllocations], reverse=True)[0] - termCode = recentTerm.termCode - result["term"] = recentTerm - result["currentSemester"] = getCurrentSemesterLabel(recentTerm) - - # "allocated" is summed directly from the rows already fetched above rather - # than through allocationManager's getTotalAllocations, since that only - # looks at the *final* Allocation row for a term - a department whose most - # recent term is still a draft (isFinal=False, no final row yet) would - # otherwise show 0 allocated instead of its draft numbers. - recentTermAllocations = [a for a in departmentAllocations if a.termCode_id == termCode] - result["allocated"] = sum( - a.primary_10 + a.primary_12 + a.primary_15 + a.primary_20 + a.secondary_5 + a.secondary_10 - for a in recentTermAllocations - ) - - contractedAllocations = getContractedAllocations(termCode, department.departmentID) - result["used"] = contractedAllocations["used_total"] - result["usedPositions"] = { - "used10": contractedAllocations["used_10"], - "used12": contractedAllocations["used_12"], - "used15": contractedAllocations["used_15"], - "used20": contractedAllocations["used_20"], - "usedSecondary5": contractedAllocations["used_5_sec"], - "usedSecondary10": contractedAllocations["used_10_sec"], - } - result["breakHours"] = contractedAllocations["break_hours"] - - return result diff --git a/tests/code/test_getAllocation.py b/tests/code/test_getAllocation.py deleted file mode 100644 index 7db6e8980..000000000 --- a/tests/code/test_getAllocation.py +++ /dev/null @@ -1,251 +0,0 @@ -from datetime import date -from unittest.mock import patch - -import pytest -from app.models import mainDB -from app.models.department import Department -from app.models.term import Term -from app.models.allocation import Allocation -from app.models.laborStatusForm import LaborStatusForm -from app.models.student import Student -from app.models.supervisor import Supervisor -from app.models.formHistory import FormHistory -from app.models.historyType import HistoryType -from app.models.status import Status -from app.models.user import User -from app.logic.getAllocation import getDepartmentAllocationSummary, getCurrentSemesterLabel - - -def createFormHistory(form, statusName): - """Attach a "Labor Status Form" history entry with the given status, since - the allocation queries only count forms that have one.""" - user = User.create(username=f"testuser_{form.laborStatusFormID}") - historyType = HistoryType.get(HistoryType.historyTypeName == "Labor Status Form") - status = Status.get(Status.statusName == statusName) - return FormHistory.create( - formID=form, - historyType=historyType, - createdBy=user, - createdDate=date.today(), - status=status, - ) - - -@pytest.mark.unit -def test_getCurrentSemesterLabel(): - """ - Test that a term maps to the Fall/Spring label for whichever half of the - academic year today falls in, and that a missing term has no label. - """ - # No term (e.g. a department with no allocations) - nothing to label - assert getCurrentSemesterLabel(None) is None - - term = Term(termCode=202500) - - # Aug-Dec half of the academic year - reads as Fall of the term's own year - with patch("app.logic.getAllocation.date") as mockDate: - mockDate.today.return_value = date(2025, 9, 15) - assert getCurrentSemesterLabel(term) == "Fall 2025" - - # Jan-Jul half of the same academic-year term - reads as Spring of the next year - with patch("app.logic.getAllocation.date") as mockDate: - mockDate.today.return_value = date(2026, 2, 10) - assert getCurrentSemesterLabel(term) == "Spring 2026" - - -@pytest.mark.integration -def test_getDepartmentAllocationSummary(): - """ - Test that the summary reports allocated/used/breakHours for a department's - most recent term, covering a missing department, a department with no - Allocation rows, allocations spread across terms, several Allocation rows - in one term (draft and final both counted), break-term contracts, an - allocation with no forms, and a most-recent term that only has a draft - (not yet final) allocation. - - "used"/"usedPositions"/"breakHours" are sourced from allocationManager's - getContractedAllocations (see test_allocationManger.py for that - function's own unit coverage) - only the term-selection and allocated-sum - behavior is re-verified here. "allocated" is summed directly from the - Allocation rows for the most recent term (both draft and final), not - routed through allocationManager, since a department's allocation is - often still a draft when this is viewed. - """ - zeroedUsedPositions = { - "used10": 0, - "used12": 0, - "used15": 0, - "used20": 0, - "usedSecondary5": 0, - "usedSecondary10": 0, - } - - # department=None (e.g. when Department.get() fails in the departmentPortal - # route) returns the zeroed-out fallback instead of raising an error - summary = getDepartmentAllocationSummary(None) - - assert summary["term"] is None - assert summary["allocated"] == 0 - assert summary["used"] == 0 - assert summary["breakHours"] == 0 - assert summary["usedPositions"] == zeroedUsedPositions - - with mainDB.atomic() as transaction: - # A department with no Allocation rows gets the same zeroed-out summary - # with term=None - emptyDept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6750", ORG="2120", isActive=True) - - summary = getDepartmentAllocationSummary(emptyDept) - - assert summary["term"] is None - assert summary["allocated"] == 0 - assert summary["used"] == 0 - assert summary["breakHours"] == 0 - assert summary["usedPositions"] == zeroedUsedPositions - - # With allocations across multiple terms, the summary reflects only the - # most recent term's data - multiTermDept = Department.create(departmentID=201, DEPT_NAME="Chemistry", ACCOUNT="6751", ORG="2121", isActive=True) - - oldTerm = Term.create(termCode=900000, termName="AY Test Old") - newTerm = Term.create(termCode=900100, termName="AY Test New") - - Allocation.create( - termCode=oldTerm, department=multiTermDept, isFinal=True, justification="old", - primary_10=1, primary_12=0, primary_15=0, primary_20=0, - secondary_5=0, secondary_10=0, breakHours=50, - ) - Allocation.create( - termCode=newTerm, department=multiTermDept, isFinal=True, justification="new", - primary_10=2, primary_12=3, primary_15=0, primary_20=0, - secondary_5=1, secondary_10=0, breakHours=100, - ) - - supervisor = Supervisor.create(ID="SUP001", isActive=True) - student = Student.create(ID="STU001", isActive=True) - - # Approved under the OLD term - excluded by the term filter alone - oldForm = LaborStatusForm.create( - termCode=oldTerm, studentSupervisee=student, supervisor=supervisor, department=multiTermDept, - jobType="Primary", WLS="10", POSN_TITLE="Old Job", POSN_CODE="S001", - weeklyHours=10, contractHours=None, - ) - createFormHistory(oldForm, "Approved") - - # Under the NEW (most recent) term - should be counted - newForm = LaborStatusForm.create( - termCode=newTerm, studentSupervisee=student, supervisor=supervisor, department=multiTermDept, - jobType="Primary", WLS="10", POSN_TITLE="New Job", POSN_CODE="S002", - weeklyHours=10, contractHours=None, - ) - createFormHistory(newForm, "Approved") - - # Denied under the NEW term - should not count toward used - deniedForm = LaborStatusForm.create( - termCode=newTerm, studentSupervisee=student, supervisor=supervisor, department=multiTermDept, - jobType="Primary", WLS="12", POSN_TITLE="Denied Job", POSN_CODE="S004", - weeklyHours=12, contractHours=None, - ) - createFormHistory(deniedForm, "Denied by Admin") - - summary = getDepartmentAllocationSummary(multiTermDept) - - assert summary["term"].termCode == 900100 - assert summary["allocated"] == 6 # 2 + 3 + 0 + 0 + 1 + 0, from the new term only - assert summary["used"] == 1 # only the new term's approved LaborStatusForm counts - assert summary["usedPositions"]["used10"] == 1 - assert summary["usedPositions"]["used12"] == 0 # the denied form is not counted - assert summary["breakHours"] == 0 - - # breakHours only sums approved forms with contractHours set (break-term - # contracts), and those forms are excluded from the weekly "used" count - breakDept = Department.create(departmentID=202, DEPT_NAME="Biology", ACCOUNT="6752", ORG="2122", isActive=True) - breakTerm = Term.create(termCode=900200, termName="AY Test Break") - - Allocation.create( - termCode=breakTerm, department=breakDept, isFinal=True, justification="test", - primary_10=1, primary_12=0, primary_15=0, primary_20=0, - secondary_5=0, secondary_10=0, breakHours=200, - ) - - breakSupervisor = Supervisor.create(ID="SUP002", isActive=True) - breakStudent = Student.create(ID="STU002", isActive=True) - - breakForm = LaborStatusForm.create( - termCode=breakTerm, studentSupervisee=breakStudent, supervisor=breakSupervisor, department=breakDept, - jobType="Primary", WLS="10", POSN_TITLE="Break Worker", POSN_CODE="S003", - weeklyHours=None, contractHours=40, - ) - createFormHistory(breakForm, "Approved") - - summary = getDepartmentAllocationSummary(breakDept) - - assert summary["breakHours"] == 40 - assert summary["used"] == 0 - - # More than one Allocation row for the same most-recent term (e.g. a - # draft and a final revision, which the model's (termCode, department, - # isFinal) index allows) sums across both rows rather than picking one - multiRowDept = Department.create(departmentID=203, DEPT_NAME="Mathematics", ACCOUNT="6753", ORG="2123", isActive=True) - multiRowTerm = Term.create(termCode=900300, termName="AY Test Multi") - - Allocation.create( - termCode=multiRowTerm, department=multiRowDept, isFinal=False, justification="draft", - primary_10=1, primary_12=0, primary_15=0, primary_20=0, - secondary_5=0, secondary_10=0, breakHours=10, - ) - Allocation.create( - termCode=multiRowTerm, department=multiRowDept, isFinal=True, justification="final", - primary_10=2, primary_12=0, primary_15=0, primary_20=0, - secondary_5=0, secondary_10=0, breakHours=20, - ) - - summary = getDepartmentAllocationSummary(multiRowDept) - - assert summary["term"].termCode == 900300 - assert summary["allocated"] == 3 # 1 + 2, summed across both rows - - # An allocation for the most recent term with no LaborStatusForm records - # at all shows allocated > 0 with used/breakHours at 0, rather than - # erroring on an empty result set - noFormsDept = Department.create(departmentID=204, DEPT_NAME="History", ACCOUNT="6754", ORG="2124", isActive=True) - noFormsTerm = Term.create(termCode=900400, termName="AY Test Empty") - - Allocation.create( - termCode=noFormsTerm, department=noFormsDept, isFinal=True, justification="test", - primary_10=3, primary_12=2, primary_15=0, primary_20=0, - secondary_5=1, secondary_10=0, breakHours=150, - ) - - summary = getDepartmentAllocationSummary(noFormsDept) - - assert summary["term"].termCode == 900400 - assert summary["allocated"] == 6 - assert summary["used"] == 0 - assert summary["breakHours"] == 0 - assert summary["usedPositions"] == zeroedUsedPositions - - # A most-recent term with only a draft (isFinal=False) allocation - no - # final row exists yet - still reports the draft's own numbers rather - # than zeroing out, since a department's allocation is often still a - # draft before it's finalized - draftOnlyDept = Department.create(departmentID=205, DEPT_NAME="Art", ACCOUNT="6755", ORG="2125", isActive=True) - draftOnlyTerm = Term.create(termCode=900500, termName="AY Test Draft Only") - - Allocation.create( - termCode=draftOnlyTerm, department=draftOnlyDept, isFinal=False, justification="draft", - primary_10=5, primary_12=0, primary_15=0, primary_20=0, - secondary_5=0, secondary_10=0, breakHours=30, - ) - - summary = getDepartmentAllocationSummary(draftOnlyDept) - - assert summary["term"].termCode == 900500 - assert summary["allocated"] == 5 - assert summary["used"] == 0 - assert summary["breakHours"] == 0 - assert summary["usedPositions"] == zeroedUsedPositions - - transaction.rollback() - - From c64353ef7cc460d0d5e62fdc88af09e1550c8ad3 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 31 Aug 2026 09:49:05 -0400 Subject: [PATCH 122/128] fixed screen resizing with the allocation card --- app/static/css/departmentPortal.css | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index 0f64aa2e9..2757dd57f 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -46,21 +46,19 @@ margin: 10px 0; } .allocation-columns { - display: flex; - flex-wrap: wrap; gap: 0 2rem; } .allocation-table { /* wraps onto its own line when the card is too narrow, instead of shrinking */ flex: 1 1 180px; border-collapse: collapse; - font-size: 1.2em; + font-size: 1.05em; } .allocation-table th, .allocation-table td { text-align: left; white-space: nowrap; - padding: 4px 10px 4px 0; + padding: 4px 0px 4px 0; line-height: 1.3; } .allocation-table th { From e42fd249388b825cc3c36db5f54bc1a0f569a20d Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 31 Aug 2026 10:00:09 -0400 Subject: [PATCH 123/128] removed BI info circle from css and js as it is unused --- app/static/css/departmentPortal.css | 6 ------ app/static/js/departmentPortal.js | 6 +----- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index 2757dd57f..4c021378c 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -25,12 +25,6 @@ font-size: 3rem; color:#6e6e6e; } -.bi-info-circle { - padding: 3px 3.5px 1.5px 3.5px; - font-size: 1.5rem; - vertical-align: middle; - color:#6e6e6e; -} .allocation-table-wrapper { overflow-x: auto; margin: 10px 0; diff --git a/app/static/js/departmentPortal.js b/app/static/js/departmentPortal.js index 06ae72e76..856e44790 100644 --- a/app/static/js/departmentPortal.js +++ b/app/static/js/departmentPortal.js @@ -3,8 +3,4 @@ $(document).ready(function() { deptData = $(this).find('option:selected').data(); window.location = `/department/${deptData.org}/${deptData.account}`; }); -}); - -$(function () { - $('[data-toggle="tooltip"]').tooltip() -}) +}); \ No newline at end of file From a675ef8fc88c20897e6091bcf0029680dd8833bb Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 31 Aug 2026 10:28:44 -0400 Subject: [PATCH 124/128] cleaned up css styling --- app/static/css/departmentPortal.css | 39 ++++++----------------------- 1 file changed, 8 insertions(+), 31 deletions(-) diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index 4c021378c..cc67a8967 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -11,20 +11,16 @@ padding: 1rem; min-width: 100%; } -.bi-suitcase-lg-fill { /* Bootstrap Icon */ - border: 1px solid #c0c0c0; - border-radius: 8px; - padding: 3px 3.5px 1.5px 3.5px; - font-size: 3rem; - color:#6e6e6e; -} -.bi-clock { +.bi-suitcase-lg-fill, +.bi-clock, +.bi-people-fill { border: 1px solid #c0c0c0; border-radius: 8px; - padding: 3px 3.5px 1.5px 3.5px; + padding: 3px 3.5px 1.5px; font-size: 3rem; - color:#6e6e6e; + color: #6e6e6e; } + .allocation-table-wrapper { overflow-x: auto; margin: 10px 0; @@ -43,8 +39,6 @@ gap: 0 2rem; } .allocation-table { - /* wraps onto its own line when the card is too narrow, instead of shrinking */ - flex: 1 1 180px; border-collapse: collapse; font-size: 1.05em; } @@ -52,33 +46,16 @@ .allocation-table td { text-align: left; white-space: nowrap; - padding: 4px 0px 4px 0; + padding: 4px 0px; line-height: 1.3; } .allocation-table th { - font-weight: 700; padding-top: 10px; } -.bi-people-fill { /* Bootstrap Icon for Members Card */ - border: 1px solid #c0c0c0; - border-radius: 8px; - padding: 3px 3.5px 1.5px 3.5px; - font-size: 3rem; - color:#6e6e6e; -} - -.card-group { - gap: 1rem; -} .form-group { width: 50%; - margin-left: auto ; - margin-right:auto; -} -.card-body { - padding: 1rem 1rem; - min-width: 100% + margin: 0 auto; } .card-row { display: flex; From e775b403a3a63b1990b279200d28112487220633 Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Mon, 31 Aug 2026 10:56:22 -0400 Subject: [PATCH 125/128] reoved unused if statement for term check --- app/templates/main/departmentPortal.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index d82a4f3fe..dfdc54232 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -49,7 +49,7 @@

Current Allocations

{%- endmacro %}
-

{{ currentSemester if currentSemester else "No term data" }}

+

{{ currentSemester }}

{{contracts["used_total"]}} contracted of {{allocation["totalAllocations"] if allocation["totalAllocations"] is integer else 0}} allocations

From ac9beda7eaa6154006a57a105d8f24df22c1498d Mon Sep 17 00:00:00 2001 From: fritzj2 Date: Tue, 1 Sep 2026 09:10:15 -0400 Subject: [PATCH 126/128] readded cd database into reset_database --- database/reset_database.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/database/reset_database.sh b/database/reset_database.sh index ba87204db..82f6cff53 100755 --- a/database/reset_database.sh +++ b/database/reset_database.sh @@ -29,6 +29,8 @@ echo "Recreating databases and users" mysql -u root -proot --execute="CREATE DATABASE IF NOT EXISTS \`lsf\`; CREATE USER IF NOT EXISTS 'lsf_user'@'%' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON *.* TO 'lsf_user'@'%';" mysql -u root -proot --execute="CREATE DATABASE IF NOT EXISTS \`UTE\`; CREATE USER IF NOT EXISTS 'tracy_user'@'%' IDENTIFIED BY 'password'; GRANT ALL PRIVILEGES ON *.* TO 'tracy_user'@'%';" +cd database + rm -rf lsf_migrations rm -rf tracy_migrations rm -rf migrations.json From bf7fa1e16395ba442ca94d470b5e4672d4e1a52c Mon Sep 17 00:00:00 2001 From: zawn Date: Mon, 14 Sep 2026 15:35:16 -0400 Subject: [PATCH 127/128] remove extra line of code that is not used. --- app/controllers/admin_routes/manageDepartments.py | 2 -- app/logic/manageDepartments.py | 3 --- 2 files changed, 5 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index ec9ce2f52..1b93a5abf 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -17,8 +17,6 @@ from app.logic.manageDepartments import * -from playhouse.shortcuts import model_to_dict - @admin.route('/admin/manageDepartments/', methods=['GET']) def manageDepartments(academicYear = None): """ diff --git a/app/logic/manageDepartments.py b/app/logic/manageDepartments.py index 6feeec2a2..41acf64b0 100644 --- a/app/logic/manageDepartments.py +++ b/app/logic/manageDepartments.py @@ -12,9 +12,6 @@ from app.login_manager import require_login -from playhouse.shortcuts import model_to_dict - - def generateAdjacentYears(academicYearTermCode=None): """ From 347256dbd3381a90653e2f2ece918a9cec9a17e6 Mon Sep 17 00:00:00 2001 From: zawn Date: Mon, 14 Sep 2026 15:55:45 -0400 Subject: [PATCH 128/128] Revert "Merge branch 'department-portal-base' of https://github.com/BCStudentSoftwareDevTeam/lsf into ManDep_new_alloc_table_logic" This reverts commit 9c36440abf5ea0cda128645f7ccdc9aef4104686, reversing changes made to bf7fa1e16395ba442ca94d470b5e4672d4e1a52c. --- app/controllers/main_routes/__init__.py | 2 +- .../main_routes/departmentPortal.py | 68 +- app/controllers/main_routes/main_routes.py | 52 +- app/logic/allocationManager.py | 115 +- app/logic/download.py | 137 -- app/logic/getPositions.py | 33 +- app/logic/getTerms.py | 47 - app/models/positionDescriptionSection.py | 12 - app/models/positionHistory.py | 4 +- app/static/css/allocationTable.css | 65 - app/static/css/base.css | 2 +- app/static/css/departmentPortal.css | 79 +- app/static/css/individualPositions.css | 138 -- app/static/css/sidebar.css | 2 +- app/static/js/allocationTable.js | 22 - app/static/js/departmentPortal.js | 2 +- app/templates/main/allocationTable.html | 257 --- app/templates/main/departmentPortal.html | 48 +- app/templates/main/individualPositions.html | 68 - app/templates/main/managePositions.html | 5 +- database/demo_data.py | 1753 ++++------------- database/migrate_db.sh | 1 - ...ionManager.py => test_allocationManger.py} | 146 +- tests/code/test_download.py | 151 -- tests/code/test_getPositions.py | 77 +- tests/code/test_getTerms.py | 56 - tests/code/test_tracy.py | 6 +- 27 files changed, 492 insertions(+), 2856 deletions(-) delete mode 100644 app/logic/getTerms.py delete mode 100644 app/models/positionDescriptionSection.py delete mode 100644 app/static/css/allocationTable.css delete mode 100644 app/static/css/individualPositions.css delete mode 100644 app/static/js/allocationTable.js delete mode 100644 app/templates/main/allocationTable.html delete mode 100644 app/templates/main/individualPositions.html rename tests/code/{test_allocationManager.py => test_allocationManger.py} (52%) delete mode 100644 tests/code/test_download.py delete mode 100644 tests/code/test_getTerms.py diff --git a/app/controllers/main_routes/__init__.py b/app/controllers/main_routes/__init__.py index e8c4c6bb0..52853db73 100755 --- a/app/controllers/main_routes/__init__.py +++ b/app/controllers/main_routes/__init__.py @@ -25,4 +25,4 @@ def injectGlobalData(): from app.controllers.main_routes import studentLaborEvaluation from app.controllers.main_routes import search from app.controllers.main_routes import studentResponse -from app.controllers.main_routes import departmentPortal +from app.controllers.main_routes import departmentPortal \ No newline at end of file diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 5d69fdd7f..b0c33f5e7 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -1,72 +1,10 @@ -from datetime import datetime - -from flask import g, render_template, request, send_file -from peewee import DoesNotExist - +from flask import render_template, g from app.controllers.main_routes import main_bp -from app.logic.download import makePositionDescriptionPDF -from app.logic.getPositions import getPosition, getPositions, getPositionDescriptionSections +from app.logic.getPositions import getPositions +from peewee import DoesNotExist from app.models.department import Department -from app.models.positionHistory import PositionHistory from app.models.supervisorDepartment import SupervisorDepartment -@main_bp.route('/department///positions/', methods=['GET']) -def postionDescription(org, account, positionCode): - try: - dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) - except (NameError, DoesNotExist): - return render_template('errors/404.html'), 404 - - revisionDateParam = request.args.get('revisionDate') - revisionDate = None - if revisionDateParam: - try: - revisionDate = datetime.strptime(revisionDateParam, '%Y-%m-%d').date() - except ValueError: - return render_template('errors/404.html'), 404 - - position = getPosition(dept, positionCode, revisionDate) - - if not position: - return render_template('errors/404.html'), 404 - - sections = getPositionDescriptionSections(position) - - return render_template( - 'main/individualPositions.html', - department=dept, - position=position, - sections=sections - ) - - -@main_bp.route('/department///positions//download', methods=['GET']) -def downloadPositionDescription(org, account, positionCode): - try: - dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) - except (NameError, DoesNotExist): - return render_template('errors/404.html'), 404 - - revisionDateParam = request.args.get('revisionDate') - revisionDate = None - if revisionDateParam: - try: - revisionDate = datetime.strptime(revisionDateParam, '%Y-%m-%d').date() - except ValueError: - return render_template('errors/404.html'), 404 - - position = getPosition(dept, positionCode, revisionDate) - - if not position: - return render_template('errors/404.html'), 404 - - pdfBuffer = makePositionDescriptionPDF(dept, position) - - filename = f'{position.positionCode}_position_description.pdf' - return send_file(pdfBuffer, mimetype='application/pdf', as_attachment=True, download_name=filename) - - - @main_bp.route('/department///positions', methods=['GET']) def managePositions(org, account): try: diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index cc6f3b86c..0875988cc 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -10,21 +10,18 @@ from app.models.laborStatusForm import LaborStatusForm from app.models.formHistory import FormHistory from app.models.term import Term -from app.models.allocation import Allocation from app.models.positionHistory import PositionHistory from app.controllers.admin_routes.allPendingForms import checkAdjustment from app.controllers.main_routes import main_bp -from app.logic.download import CSVMaker, saveFormSearchResult, retrieveFormSearchResult, makePositionDescriptionPDF +from app.logic.download import CSVMaker, saveFormSearchResult, retrieveFormSearchResult from app.logic.search import getDepartmentsForSupervisor, searchPerson, searchSupervisorPortal from app.login_manager import require_login, logout from app.logic.getTableData import getDatatableData from app.logic.banner import Banner from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions -from app.logic.allocationManager import getBreakContracts, getContractedAllocations, getTotalAllocations -from app.logic.getTerms import getTerms, getCurrentSemester @main_bp.route('/logout', methods=['GET']) @@ -74,64 +71,17 @@ def departmentPortal(org=None,account=None): supervisors, laborCoordinators = getSupervisors(dept) - - currentAY, fallTerm, springTerm = getTerms() - allocationDict = getTotalAllocations(currentAY, dept) - currentSemester = getCurrentSemester() - contracts = getContractedAllocations(currentSemester, dept) - - positionsList, posURL = getActivePositions(dept) return render_template('main/departmentPortal.html', departments = departments, department = dept, - contracts = contracts, - allocation = allocationDict, - currentSemester = currentSemester.termName, supervisors = supervisors, laborCoordinators=laborCoordinators, currentUser=currentUser, positions = positionsList, posURL = posURL) -@main_bp.route('/department///allocations', methods=['GET']) -def allocationTable(org=None, account=None): - currentUser = g.currentUser - try: - dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) - except (NameError, DoesNotExist): - return render_template('errors/404.html'), 404 - - if not currentUser.isLaborAdmin: - allowedDepartmentIds = [d.departmentID for d in getDepartmentsForSupervisor(currentUser)] - if dept.departmentID not in allowedDepartmentIds: - return render_template('errors/403.html'), 403 - - currentAY, fallTerm, springTerm = getTerms() - - allocationDict = getTotalAllocations(currentAY.termCode, dept) - fallContracts = getContractedAllocations(fallTerm.termCode, dept) - springContracts = getContractedAllocations(springTerm.termCode, dept) - - breakContracts = { - "total": 0, - "thanksgiving":getBreakContracts(currentAY.termCode + 1, dept), - "winter": getBreakContracts(currentAY.termCode + 2, dept), - "spring": getBreakContracts(currentAY.termCode + 3, dept), - "fall":getBreakContracts(currentAY.termCode + 4, dept), - "summer": getBreakContracts(currentAY.termCode + 13, dept) - } - breakContracts["total"] = sum(breakContracts.values()) - return render_template('main/allocationTable.html', - department = dept, - currentAY = currentAY, - allocations = allocationDict, - fallContracts = fallContracts, - springContracts = springContracts, - breakContracts = breakContracts) - - @main_bp.route('/supervisorPortal/download', methods=['POST']) def downloadSupervisorPortalResults(): ''' diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index 53e23862f..f1c58cbc6 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -12,24 +12,20 @@ def getAllocation(termCode: int, dept: int, isFinal = True): This function returns a peewee object containing the selected allocation for given department and term. If you want the pending allocation, pass in False for isFinal. ''' - try: - academicYearCode = int(str(termCode)[:4] + "00") - allocationObject = Allocation.select().where( - Allocation.termCode.in_([termCode,academicYearCode]), - Allocation.department == dept, - Allocation.isFinal == isFinal).dicts().get() - return allocationObject - except: - return 0 + academicYearCode = int(str(termCode)[:4] + "00") + allocationObject = Allocation.select().where( + Allocation.termCode.in_([termCode,academicYearCode]), + Allocation.department == dept, + Allocation.isFinal == isFinal).dicts().get() + return allocationObject def getTotalAllocations(termCode: int, dept: int): ''' This function returns a dictionary representation of the given department's allocation for the given term. ''' - try: - allocationObject = getAllocation(termCode, dept) - allocationDict = {"primary_10": allocationObject["primary_10"], + allocationObject = getAllocation(termCode, dept) + allocationDict = {"primary_10": allocationObject["primary_10"], "primary_12": allocationObject["primary_12"], "primary_15": allocationObject["primary_15"], "primary_20": allocationObject["primary_20"], @@ -39,49 +35,15 @@ def getTotalAllocations(termCode: int, dept: int): "totalPrimaries": (allocationObject["primary_10"] + allocationObject["primary_12"] + allocationObject["primary_15"] + allocationObject["primary_20"]), "totalSecondaries": (allocationObject["secondary_5"] + allocationObject["secondary_10"]), "totalAllocations": (allocationObject["primary_10"] + allocationObject["primary_12"] + allocationObject["primary_15"] + allocationObject["primary_20"] + allocationObject["secondary_5"] + allocationObject["secondary_10"] )} - except: - allocationDict = {"primary_10": "", - "primary_12": "", - "primary_15": "", - "primary_20": "", - "secondary_5": "", - "secondary_10": "", - "breakHours": "No Allocations Found", - "totalPrimaries": "", - "totalSecondaries": "", - "totalAllocations": "No Allocations Found"} return allocationDict -def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: int, AYtermCode: int = None): +def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: int): ''' This function counts the number of positions of a given type in a given department. For example, countContracts('secondary', 5, 202511, 1) returns the number of secondary 5-hour positions in the CS department for the 2025 Fall term. ''' academicYearCode = int(str(termCode)[:4] + "00") - - # This sets the date condition to determine whether the form is within the boundaries of the term - # Fall only contracts end before spring, spring contracts start after fall. - fallMonths = ["07","08","09","10","11","12"] - springMonths = ["01","02","03","04","05","06"] - if str(termCode).endswith("11"): - dateCondition = ( - (LaborStatusForm.endDate.month.in_(fallMonths)) | - (LaborStatusForm.startDate.month.in_(fallMonths) & # Reused check for year-long positions - LaborStatusForm.endDate.month.in_(springMonths)) - ) - elif str(termCode).endswith("12"): - dateCondition = ( - (LaborStatusForm.startDate.month.in_(springMonths)) | - (LaborStatusForm.startDate.month.in_(fallMonths) & - LaborStatusForm.endDate.month.in_(springMonths)) - ) - else: - dateCondition = ( - (LaborStatusForm.startDate.month.in_(fallMonths) & - LaborStatusForm.endDate.month.in_(springMonths)) - ) - lsfCountPositions = FormHistory.select( ).join(LaborStatusForm ).join(Department @@ -92,28 +54,41 @@ def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: LaborStatusForm.jobType == jobType, # 'primary' or 'secondary' LaborStatusForm.weeklyHours == weeklyContractHours, # 5, 10, 12, 15, or 20 Department.departmentID == dept, - dateCondition, ).count() return lsfCountPositions def getContractedAllocations(termCode: int, dept: int): ''' - This function returns a dictionary with a breakdown of all types of contracts + This function returns a dictionary with a breakdown of all types of contracts for the given department and term in the form of a dictionary. ''' academicYearCode = int(str(termCode)[:4] + "00") - breakHoursTotal = ( - FormHistory.select(fn.SUM(LaborStatusForm.contractHours)) - .join(LaborStatusForm, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID)) - .where( - FormHistory.historyType == "Labor Status Form", - FormHistory.status == "Approved", - LaborStatusForm.termCode.in_([termCode, academicYearCode]), - LaborStatusForm.department == dept, - ) - .scalar() - ) or 0 - + allocationObject = getAllocation(termCode, dept) + breakAllocation = FormHistory.select( + LaborStatusForm.department, + LaborStatusForm.termCode, + fn.SUM(LaborStatusForm.contractHours).alias('total_hours') + ).join( + LaborStatusForm, + on=(FormHistory.formID == LaborStatusForm.laborStatusFormID), + ).join( + Term, + on = (LaborStatusForm.termCode == Term.termCode ) + ).where( + (FormHistory.historyType == "Labor Status Form") & + (FormHistory.status == "Approved") & + (LaborStatusForm.termCode.in_([termCode,academicYearCode])) + ).group_by( + LaborStatusForm.department, + LaborStatusForm.termCode).dicts() + + breakSum = {"total_hours": 0} + if dept: + for row in breakAllocation: + if row["department"] == dept: + breakSum = row + break + # dictionary definition: usedPositions = { "used_10": countContracts("Primary", "10", termCode, dept), @@ -125,23 +100,9 @@ def getContractedAllocations(termCode: int, dept: int): "used_primaries": 0, "used_secondaries": 0, "used_total": 0, # all contracts with weekly hours, i.e. primaries + secondaries (not break contracts) - "break_hours": breakHoursTotal # all break hours contracted (but not necessarily worked) + "break_hours": breakSum["total_hours"] # all break hours contracted (but not necessarily worked) } usedPositions["used_primaries"] = sum(list(usedPositions.values())[:4]) usedPositions["used_secondaries"] = sum(list(usedPositions.values())[4:6]) usedPositions["used_total"] = sum(list(usedPositions.values())[:6]) - return usedPositions - -def getBreakContracts(termCode, dept): - break_allocation = FormHistory.select(fn.SUM(LaborStatusForm.contractHours) - ).join(LaborStatusForm - ).where( - FormHistory.historyType == "Labor Status Form", - FormHistory.status.in_(["Approved", "Pending", "Pre-Student Approval"]), - LaborStatusForm.termCode == termCode, - LaborStatusForm.department == dept, - LaborStatusForm.contractHours.is_null(False)).scalar() - if break_allocation != None: - return break_allocation - else: - return 0 \ No newline at end of file + return usedPositions \ No newline at end of file diff --git a/app/logic/download.py b/app/logic/download.py index 50d040f5a..b9532446d 100644 --- a/app/logic/download.py +++ b/app/logic/download.py @@ -1,16 +1,13 @@ import csv -import io import json from flask import g -from fpdf import FPDF from peewee import ModelSelect from app.models.formHistory import * from app.controllers.main_routes.main_routes import * from app.models.studentLaborEvaluation import StudentLaborEvaluation from app.models.formSearchResult import FormSearchResult -from app.logic.getPositions import getPositionDescriptionSections def saveFormSearchResult(displayName, formList, formType): ids = [form.formHistoryID for form in formList] @@ -31,140 +28,6 @@ def retrieveFormSearchResult(formSearchResultId): return None -import html -import io - -from html.parser import HTMLParser -from fpdf import FPDF - - -class PDFHTMLTextExtractor(HTMLParser): - """ - Converts simple stored HTML into plain text suitable for FPDF. - """ - - blockTags = { - 'p','div','section','article','header','footer','h1','h2','h3','h4','h5','h6','li','ul','ol','br', - } - - def __init__(self): - super().__init__() - self.parts = [] - - def handle_starttag(self, tag, attrs): - tag = tag.lower() - - if tag == 'br': - self.parts.append('\n') - elif tag == 'li': - self.parts.append('\n• ') - - def handle_endtag(self, tag): - if tag.lower() in self.blockTags: - self.parts.append('\n') - - def handle_data(self, data): - self.parts.append(data) - - def getText(self): - text = ''.join(self.parts) - text = html.unescape(text) - - lines = [] - for line in text.splitlines(): - cleanedLine = ' '.join(line.split()) - - if cleanedLine: - lines.append(cleanedLine) - elif lines and lines[-1] != '': - lines.append('') - - return '\n'.join(lines).strip() - - -def removeHTML(value): - if value is None: - return '' - - parser = PDFHTMLTextExtractor() - parser.feed(str(value)) - parser.close() - - return parser.getText() - - -def makePositionDescriptionPDF(department, position): - """ - Builds a PDF of a position's description for the download button - on the individual position page. - """ - pdf = FPDF() - pdf.add_page() - - # Position title - pdf.set_font('Times', 'B', 16) - pdf.cell(0,10,removeHTML(position.positionTitle).encode('latin-1', 'replace').decode('latin-1'),ln=True) - pdf.ln(2) - - # Position metadata - fields = [ - ('Department Name', department.DEPT_NAME), - ('Position Code', position.positionCode), - ('WLS Level', position.wls), - ('Status', position.status), - ('Last Revision Date', position.revisionDate), - ('Revised By', position.revisedBy), - ] - - label_width = 45 - - for label, value in fields: - pdf.set_font('Times', 'B', 11) - pdf.cell(label_width, 8, f'{label}:', ln=False) - - plain_value = removeHTML(value).encode('latin-1', 'replace').decode('latin-1') - - pdf.set_font('Times', '', 11) - pdf.cell(0, 8, f' {plain_value}', ln=True) - - sections = getPositionDescriptionSections(position) - - pdf.ln(4) - - if sections: - for index, section in enumerate(sections): - title = removeHTML(section.sectionTitle).encode('latin-1', 'replace').decode('latin-1') - content = removeHTML(section.sectionContent).encode('latin-1', 'replace').decode('latin-1') - - # Horizontal rule before the description sections - if index == 0: - pdf.set_draw_color(180, 180, 180) - pdf.set_line_width(0.3) - y = pdf.get_y() - pdf.line(pdf.l_margin, y, pdf.w - pdf.r_margin, y) - pdf.ln(3) - - # Section heading - pdf.set_font('Times', 'B', 12) - pdf.multi_cell(0, 8, title) - - # Section content - pdf.set_font('Times', '', 11) - pdf.multi_cell(0, 5, content) - - pdf.ln(2) - - else: - pdf.set_font('Times', 'B', 12) - pdf.cell(0, 10, 'Description', ln=True) - - pdf.set_font('Times', '', 11) - pdf.multi_cell(0, 5, 'No description available.') - - pdf_bytes = pdf.output(dest='S').encode('latin-1', 'replace') - return io.BytesIO(pdf_bytes) - - class CSVMaker: ''' Create the CSV for the download bottons diff --git a/app/logic/getPositions.py b/app/logic/getPositions.py index 7410d7f75..67253f890 100644 --- a/app/logic/getPositions.py +++ b/app/logic/getPositions.py @@ -1,5 +1,4 @@ from app.models.positionHistory import PositionHistory -from app.models.positionDescriptionSection import PositionDescriptionSection def getActivePositions(dept): """ @@ -21,38 +20,8 @@ def getActivePositions(dept): return positionsList, posURL -def getPosition(dept, positionCode, revisionDate=None): - """ - Returns a single position for a given department, position code, and optional revision date. - If no revision date is provided, the most recent revision is returned. - """ - positionQuery = PositionHistory.select().where( - PositionHistory.department == dept, - PositionHistory.positionCode == positionCode - ) - - if revisionDate: - positionQuery = positionQuery.where(PositionHistory.revisionDate == revisionDate) - - return positionQuery.order_by(PositionHistory.revisionDate.desc()).first() - def getPositions(dept): - """ - Returns a list of all positions for a given department, ordered by position title. - """ return((PositionHistory.select() .where((PositionHistory.department == dept) & (PositionHistory.status == "Active")) - .order_by(PositionHistory.positionTitle.asc()))) - -def getPositionDescriptionSections(position): - """ - Returns the description sections for a given position, ordered for display. - """ - positionDescriptionSections = list(PositionDescriptionSection.select() - .where(PositionDescriptionSection.position == position) - .order_by(PositionDescriptionSection.order.asc())) - - return positionDescriptionSections - - + .order_by(PositionHistory.positionTitle.asc()))) \ No newline at end of file diff --git a/app/logic/getTerms.py b/app/logic/getTerms.py deleted file mode 100644 index 19df06ec9..000000000 --- a/app/logic/getTerms.py +++ /dev/null @@ -1,47 +0,0 @@ -from app.models.term import Term -from datetime import datetime, date - -def getTerms(academicYear: str = None): - ''' - Gets 3 primary terms for the current/given Academic Year (AY) - This means it attempt to get an AY, fall term, and spring term - This returns all three objects individually for each of the terms. - Leaving no input variables means that it will check for the current year instead of a given one. - ''' - if academicYear: - # Uses only the first half of the AY since term codes are split by year. - # i.e. Fall 2026 -> 202611, while Spring 2027 -> 202612. - springTerm = Term.select().where(Term.termCode == int(academicYear[:4]) * 100 + 12).get() - currentAY = Term.select().where(Term.termCode == int(academicYear[:4]) * 100).get() - fallTerm = Term.select().where(Term.termCode == int(academicYear[:4]) * 100 + 11).get() - return currentAY, fallTerm, springTerm - - - else: - # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. - # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. - currentDate = date.today() - if currentDate.month <= 6: - springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() - currentAY = Term.select().where(Term.termCode == currentDate.year * 100 - 100).get() - fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11 - 100).get() - return currentAY, fallTerm, springTerm - - else: - fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() - currentAY = Term.select().where(Term.termCode == currentDate.year * 100).get() - springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() - return currentAY, fallTerm, springTerm - -def getCurrentSemester(): - ''' - The difference between this function and the one above is that it gets just the current term - It does not get both Fall and Spring, it just gets one depending on the month. - ''' - currentDate = date.today() - if currentDate.month <= 6: - springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() - return springTerm - else: - fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() - return fallTerm diff --git a/app/models/positionDescriptionSection.py b/app/models/positionDescriptionSection.py deleted file mode 100644 index 5488c315e..000000000 --- a/app/models/positionDescriptionSection.py +++ /dev/null @@ -1,12 +0,0 @@ -from app.models import * -from app.models.positionHistory import PositionHistory - - -class PositionDescriptionSection (baseModel): - position = ForeignKeyField(PositionHistory) - sectionTitle = CharField() - sectionContent = TextField() - order = IntegerField() # Order of the sections in the position description - - - diff --git a/app/models/positionHistory.py b/app/models/positionHistory.py index fd38f26c3..9a248aacb 100644 --- a/app/models/positionHistory.py +++ b/app/models/positionHistory.py @@ -5,10 +5,10 @@ class PositionHistory(baseModel): positionTitle = CharField() positionCode = CharField() department = ForeignKeyField(Department) - status = CharField() # Active, Inactive, Requested + status = CharField() wls = IntegerField() revisionDate = DateField() - revisedBy = CharField() + description = TextField(default=None) class Meta: indexes = ( (('positionCode', 'revisionDate', 'status'), True), ) diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css deleted file mode 100644 index ec2485838..000000000 --- a/app/static/css/allocationTable.css +++ /dev/null @@ -1,65 +0,0 @@ -.grid-container { - display: grid; - grid-template-columns: 1fr 1fr; -} -.btn-success{ - align-self: center; - justify-self: end; -} -.card-header { - background-color: #efebeb; - margin-bottom: 7px; - height: 6rem; -} -.mb-0, .collapsed { - outline-color: none; - color: black; - font-size: 18px; - font-weight: 525; -} -.accordion{ - padding-left: 5%; - padding-right: 5%; -} -.table-striped { - table-layout:fixed; - width:100%; -} -.table-striped tbody tr:nth-of-type(odd) { - background-color: #f2f2f2; -} -.btn-info{ - justify-self:flex-end; - align-self: center; - margin-left: 4px -} -.accordion-arrow { - display: inline-block; - transition: transform 0.2s ease-in-out; -} -.btn.collapsed .accordion-arrow { - transform: rotate(-90deg); -} -.btn:not(.collapsed) .accordion-arrow { - transform: rotate(0deg); -} -.button-container { - display:flex; - justify-content:end; -} -.btn-block{ - align-items: center; - align-self: center; - text-align: left; - justify-content: center; - height: 100% -} -.termDisplay{ - margin-right:auto; -} -.bi-info-circle { - padding: 3px 3.5px 1.5px 3.5px; - font-size: 1.5rem; - vertical-align: middle; - color:#6e6e6e; -} \ No newline at end of file diff --git a/app/static/css/base.css b/app/static/css/base.css index 19b36fd2b..a962b5494 100755 --- a/app/static/css/base.css +++ b/app/static/css/base.css @@ -126,8 +126,8 @@ a { box-sizing: border-box; left: 280px; right: 0px; + width: calc(100vw - 280px); margin-right: 280px; - width: calc(100vw - 300px); } diff --git a/app/static/css/departmentPortal.css b/app/static/css/departmentPortal.css index cc67a8967..d9acbba71 100644 --- a/app/static/css/departmentPortal.css +++ b/app/static/css/departmentPortal.css @@ -11,51 +11,36 @@ padding: 1rem; min-width: 100%; } -.bi-suitcase-lg-fill, -.bi-clock, -.bi-people-fill { - border: 1px solid #c0c0c0; - border-radius: 8px; - padding: 3px 3.5px 1.5px; - font-size: 3rem; - color: #6e6e6e; -} -.allocation-table-wrapper { - overflow-x: auto; - margin: 10px 0; -} -.allocation-summary { - display: flex; - flex-wrap: wrap; - justify-content: space-between; - align-items: baseline; - gap: 0 1rem; -} -.allocation-summary h4 { - margin: 10px 0; -} -.allocation-columns { - gap: 0 2rem; -} -.allocation-table { - border-collapse: collapse; - font-size: 1.05em; -} -.allocation-table th, -.allocation-table td { - text-align: left; - white-space: nowrap; - padding: 4px 0px; - line-height: 1.3; -} -.allocation-table th { - padding-top: 10px; +.members-icon { + display: inline-block; + border: 1px solid #c0c0c0; + border-radius: 8px; + padding: 3px 3.5px 1.5px; + font-size: 36px; + color: #6e6e6e; +} + margin-bottom: 20px; + min-height: 200px; +} +.bi-suitcase-lg-fill { /* Bootstrap Icon */ + border: 1px solid #c0c0c0; + border-radius: 8px; + padding: 3px 3.5px 1.5px 3.5px; + font-size: 3rem; + color:#6e6e6e; +} +.card-group { + gap: 1rem; } - .form-group { width: 50%; - margin: 0 auto; + margin-left: auto ; + margin-right:auto; +} +.card-body { + padding: 1rem 1rem; + min-width: 100% } .card-row { display: flex; @@ -67,15 +52,3 @@ flex-direction: column; } } - -/* Narrow card: stack Secondary below Primary, and the position count below the - term, rather than shrinking the text to keep them side by side. */ -@media (min-width: 1200px) and (max-width: 1450px), (max-width: 480px) { - .allocation-summary { - flex-direction: column; - gap: 0; - } - .allocation-columns .allocation-table { - flex-basis: 100%; - } -} diff --git a/app/static/css/individualPositions.css b/app/static/css/individualPositions.css deleted file mode 100644 index 9e22d5843..000000000 --- a/app/static/css/individualPositions.css +++ /dev/null @@ -1,138 +0,0 @@ -/* Individual Positions page styles */ -/* Header */ -.department-header-container { - position: relative; -} - -.department-header { - text-align: center; - font-weight: 700; - margin-bottom: 5rem; -} - -.download-btn { - position: absolute; - top: 0; - right: 15px; -} - -.position-description div{ - width: 100%; - max-width: 100%; - box-sizing: border-box; -} -/* Metadata list (dt / dd spacing) */ -.position-information dl.row dt { - font-weight: 600; - color: #333; - text-align: left; - font-size: 2rem; -} -.position-information dl.row dd { - margin-bottom: 0.75rem; - color: #444; - text-align: left; - font-size: 2rem; -} - -h2.description-header { - font-weight: 600; - font-size: 2rem; -} - - -.position-description h3 { - font-size: 1.7rem; - font-weight: 600; - margin-top: 1.5rem; - margin-bottom: 0.5rem; -} - -.position-description h4 { - font-size: 1.25rem; - font-weight: 300; - margin-top: 1.25rem; - margin-bottom: 0.5rem; -} - -.position-description h5 { - font-size: 1.15rem; - font-weight: 600; - margin-top: 1rem; - margin-bottom: 0.35rem; -} - -.position-container { - margin: 2rem; -} - -.position-description { - text-align: left; - margin-top: 1rem; - margin-bottom: 2rem; - font-size: 1.25rem; - overflow-wrap: break-word; - word-wrap: break-word; -} - -.description-content { - margin: 0; - padding: 0; - width: 100%; - max-width: 100%; - box-sizing: border-box; - word-wrap: break-word; -} - -/* Individual description sections */ -.section-title { - margin-top: 20px; - margin-bottom: 10px; - font-weight: 700; - overflow-wrap: break-word; - word-wrap: break-word; -} - -.section-title:first-child { - margin-top: 0; -} - -/* Utility float class used across the app */ -.floatright { - float: right; - margin-left: 0.5rem; -} - -/* Mobile layout */ -@media (max-width: 750px) { - .download-btn { - position: static; - display: table; - max-width: 100%; - margin: 0 auto 20px; - white-space: normal; - } - - .department-header { - margin-bottom: 30px; - font-size: 28px; - } - - .position-information dl.row dt, - .position-information dl.row dd { - font-size: 1.6rem; - } - - .position-information dl.row dd { - margin-bottom: 15px; - } - - .position-description { - font-size: 1.2rem; - } - - .floatright { - float: none; - margin-left: 0; - } -} \ No newline at end of file diff --git a/app/static/css/sidebar.css b/app/static/css/sidebar.css index 8063dbf69..cb062a9fe 100644 --- a/app/static/css/sidebar.css +++ b/app/static/css/sidebar.css @@ -65,7 +65,7 @@ .sidebar-push { left: 0 !important; - width: 95vw !important; + width: 100vw !important; margin-right: 0 !important; } diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js deleted file mode 100644 index 8bda5cd19..000000000 --- a/app/static/js/allocationTable.js +++ /dev/null @@ -1,22 +0,0 @@ -$(document).ready( function(){ - function initTable(selector) { - return $(selector).DataTable({ - pageLength: 25, - info: false, - lengthChange: false, - searching: false, - paging: false, - order: [] - }); - } - - const fallTermPrimaries = initTable('#fallTermPrimaries'); - const fallTermSecondaries = initTable('#fallTermSecondaries'); - const springTermPrimaries = initTable('#springTermPrimaries'); - const springTermSecondaries = initTable('#springTermSecondaries'); - const breakTable = initTable('#breakTable'); -}); - -$(function () { - $('[data-toggle="tooltip"]').tooltip() -}) \ No newline at end of file diff --git a/app/static/js/departmentPortal.js b/app/static/js/departmentPortal.js index 856e44790..cb2023a3a 100644 --- a/app/static/js/departmentPortal.js +++ b/app/static/js/departmentPortal.js @@ -3,4 +3,4 @@ $(document).ready(function() { deptData = $(this).find('option:selected').data(); window.location = `/department/${deptData.org}/${deptData.account}`; }); -}); \ No newline at end of file +}); diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html deleted file mode 100644 index e7ccafcb0..000000000 --- a/app/templates/main/allocationTable.html +++ /dev/null @@ -1,257 +0,0 @@ -{% extends "base.html" %} -{% block styles %} - {{super()}} - - -{% endblock %} - -{% block scripts %} - {{super()}} - - - -{% endblock %} - -{% block app_content %} - -

{% if department %} {{department.DEPT_NAME}} Allocations {% else %} Choose a Department: {% endif %}

- -
-

Current Term: {{currentAY.termName}}

- Download Allocation History - Request Allocation -
- -
- - -
-
- - -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PrimariesContractedAllocated
10 Hour{{fallContracts["used_10"]}}{{allocations["primary_10"]}}
12 Hour{{fallContracts["used_12"]}}{{allocations["primary_12"]}}
15 Hour{{fallContracts["used_15"]}}{{allocations["primary_15"]}}
20 Hour{{fallContracts["used_20"]}}{{allocations["primary_20"]}}
Total Primaries{{fallContracts["used_primaries"]}}{{allocations["totalPrimaries"]}}
Total Contracts{{fallContracts["used_total"]}}{{allocations["totalAllocations"]}}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SecondariesContractedAllocated
5 Hour{{fallContracts["used_5_sec"]}}{{allocations["secondary_5"]}}
10 Hour{{fallContracts["used_10_sec"]}}{{allocations["secondary_10"]}}
Total Secondaries{{fallContracts["used_secondaries"]}}{{allocations["totalSecondaries"]}}
Total Contracts{{fallContracts["used_total"]}}{{allocations["totalAllocations"]}}
-
-
- - -
-
- -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PrimariesContractedAllocated
10 Hour{{springContracts["used_10"]}}{{allocations["primary_10"]}}
12 Hour{{springContracts["used_12"]}}{{allocations["primary_12"]}}
15 Hour{{springContracts["used_15"]}}{{allocations["primary_15"]}}
20 Hour{{springContracts["used_20"]}}{{allocations["primary_20"]}}
Total Primaries{{springContracts["used_primaries"]}}{{allocations["totalPrimaries"]}}
Total Contracts{{springContracts["used_total"]}}{{allocations["totalAllocations"]}}
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SecondariesContractedAllocated
5 Hour{{springContracts["used_5_sec"]}}{{allocations["secondary_5"]}}
10 Hour{{springContracts["used_10_sec"]}}{{allocations["secondary_10"]}}
Total Secondaries{{springContracts["used_secondaries"]}}{{allocations["totalSecondaries"]}}
Total Contracts{{springContracts["used_total"]}}{{allocations["totalAllocations"]}}
-
-
- - -
-
- - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PrimariesContractedAllocated
Fall Break{{breakContracts['fall']}}
Thanksgiving{{breakContracts['thanksgiving']}}
Winter Break{{breakContracts['winter']}}
Spring Break{{breakContracts['spring']}}
Summer Term{{breakContracts['summer']}}
Total Break Hours {{breakContracts['total']}}{{allocations["breakHours"]}}
-
-
- -
-{% endblock %} diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index dfdc54232..e7786c855 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -33,52 +33,10 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e {% if department %}
-
-
-
-
- -
-
-

Current Allocations

-
- {% macro allocationRow(hours, used, allocated) -%} - {{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }} out of {{ allocated if allocated is integer else 0 }} allocation{{ 's' if allocated != 1 else '' }} - {%- endmacro %} -
-
-

{{ currentSemester }}

-

{{contracts["used_total"]}} contracted of {{allocation["totalAllocations"] if allocation["totalAllocations"] is integer else 0}} allocations

-
-
- - - - - - {{ allocationRow(10, contracts["used_10"], allocation["primary_10"]) }} - {{ allocationRow(12, contracts["used_12"], allocation["primary_12"]) }} - {{ allocationRow(15, contracts["used_15"], allocation["primary_15"]) }} - {{ allocationRow(20, contracts["used_20"], allocation["primary_20"]) }} - -
Primary
- - - - - - {{ allocationRow(5,contracts["used_5_sec"], allocation["secondary_5"]) }} - {{ allocationRow(10, contracts["used_10_sec"], allocation["secondary_10"]) }} - -
Secondary
-
-
-
-
+
+

Insert Allocations Card Here

diff --git a/app/templates/main/individualPositions.html b/app/templates/main/individualPositions.html deleted file mode 100644 index 87bd44ba4..000000000 --- a/app/templates/main/individualPositions.html +++ /dev/null @@ -1,68 +0,0 @@ -{% extends "base.html" %} - -{% block scripts %} - {{ super() }} - - -{% endblock %} - -{% block app_content %} -
- - Download Description - -

{{ department.DEPT_NAME }}

-
- -
-
-
- -
-
-
Position Title:
-
{{ position.positionTitle }}
- -
Position Code:
-
{{ position.positionCode }}
- -
WLS Level:
-
{{ position.wls }}
- -
Status:
-
{{ position.status }}
- -
Last Revision Date:
-
{{ position.revisionDate }}
- -
Revised By:
-
{{ position.revisedBy }}
-
-
- -

Description:

-
- {%- if sections %} - {%- for section in sections %} -
{{ section.sectionTitle |safe }}
-
{{ section.sectionContent |safe }}
- {%- endfor %} - {%- else %} -

No description available.

- {%- endif %} -
- -
- -
- -
-
-
-{% endblock %} \ No newline at end of file diff --git a/app/templates/main/managePositions.html b/app/templates/main/managePositions.html index 81b139d45..1d6dd8c60 100644 --- a/app/templates/main/managePositions.html +++ b/app/templates/main/managePositions.html @@ -29,12 +29,11 @@

{{ department_name }} Positions

{% for position in positions %} - {{ position.positionTitle }} {{ position.positionCode }} + {{ position.positionTitle }} {{ position.positionCode }} {{ position.wls }} {{ position.revisionDate }} - View + diff --git a/database/demo_data.py b/database/demo_data.py index dcf8eb43c..13128f7cd 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -14,13 +14,11 @@ from app.models.user import User from app.models.term import Term from app.models.laborStatusForm import LaborStatusForm -from app.models.laborReleaseForm import LaborReleaseForm from app.models.formHistory import FormHistory from app.models.notes import Notes from app.models.supervisorDepartment import SupervisorDepartment from app.models.allocation import Allocation from app.models.positionHistory import PositionHistory -from app.models.positionDescriptionSection import PositionDescriptionSection print("Inserting data for demo and testing purposes") @@ -42,37 +40,8 @@ "STU_CPO":"700", "LAST_POSN":"Media Technician", "LAST_SUP_PIDM":"7" - }, - { - "ID":"B00741361", - "PIDM":"99", - "FIRST_NAME":"Antonia", - "LAST_NAME":"Schmith", - "CLASS_LEVEL":"Freshman", - "ACADEMIC_FOCUS":"Computer Science", - "MAJOR":"Computer Science", - "PROBATION":"0", - "ADVISOR":"Scott Heggen", - "STU_EMAIL":"schmitha@berea.edu", - "STU_CPO":"777", - "LAST_POSN":"TA", - "LAST_SUP_PIDM":"7" - }, - { - "ID":"B00732363", - "PIDM":"58", - "FIRST_NAME":"Barbara", - "LAST_NAME":"Williams", - "CLASS_LEVEL":"Junior", - "ACADEMIC_FOCUS":"Computer Science", - "MAJOR":"Computer Science", - "PROBATION":"0", - "ADVISOR":"Jasmine Jones", - "STU_EMAIL":"williamsb@berea.edu", - "STU_CPO":"118", - "LAST_POSN":"TA", - "LAST_SUP_PIDM":"7" }, + { "ID":"B00730361", "PIDM":"1", @@ -135,26 +104,7 @@ "LAST_POSN":"Student Manager", "LAST_SUP_PIDM":"7" }, - {"ID": "B00811617", "legal_name": "Chris Georgiev", "isActive": True, "PIDM": "8", "FIRST_NAME": "Chris", "LAST_NAME": "Georgiev"}, - {"ID": "B00815474", "legal_name": "Julius Fritz", "isActive": True, "PIDM": "9", "FIRST_NAME": "Julius", "LAST_NAME": "Fritz"}, - {"ID": "B12345223", "legal_name": "Subaru Natsuki", "isActive": True, "PIDM": "10", "FIRST_NAME": "Subaru", "LAST_NAME": "Natsuki"}, - {"ID": "B12345003", "legal_name": "Hatsune Miku", "isActive": True, "PIDM": "11", "FIRST_NAME": "Hatsune", "LAST_NAME": "Miku"}, - {"ID": "B12345772", "legal_name": "Michael Jackson", "isActive": True, "PIDM": "12", "FIRST_NAME": "Michael", "LAST_NAME": "Jackson"}, - {"ID": "B12345756", "legal_name": "Genji Overwatch", "isActive": True, "PIDM": "13", "FIRST_NAME": "Genji", "LAST_NAME": "Overwatch"}, - {"ID": "B12345759", "legal_name": "Mister Marlowe", "isActive": True, "PIDM": "14", "FIRST_NAME": "Mister", "LAST_NAME": "Marlowe"}, - {"ID": "B11231123", "legal_name": "Mister Thanksgiving", "isActive": True, "PIDM": "15", "FIRST_NAME": "Mister", "LAST_NAME": "Thanksgiving"}, - {"ID": "B12345762", "legal_name": "Alex Carter", "isActive": True, "PIDM": "16", "FIRST_NAME": "Alex", "LAST_NAME": "Carter"}, - {"ID": "B12345763", "legal_name": "Morgan Hayes", "isActive": True, "PIDM": "17", "FIRST_NAME": "Morgan", "LAST_NAME": "Hayes"}, - {"ID": "B12345764", "legal_name": "Jordan Brooks", "isActive": True, "PIDM": "18", "FIRST_NAME": "Jordan", "LAST_NAME": "Brooks"}, - {"ID": "B12345765", "legal_name": "Taylor Morgan", "isActive": True, "PIDM": "19", "FIRST_NAME": "Taylor", "LAST_NAME": "Morgan"}, - {"ID": "B12345766", "legal_name": "Casey Turner", "isActive": True, "PIDM": "20", "FIRST_NAME": "Casey", "LAST_NAME": "Turner"}, - {"ID": "B12345767", "legal_name": "Jamie Foster", "isActive": True, "PIDM": "21", "FIRST_NAME": "Jamie", "LAST_NAME": "Foster"}, - {"ID": "B12345768", "legal_name": "Riley Cooper", "isActive": True, "PIDM": "22", "FIRST_NAME": "Riley", "LAST_NAME": "Cooper"}, - {"ID": "B12345769", "legal_name": "Drew Bennett", "isActive": True, "PIDM": "23", "FIRST_NAME": "Drew", "LAST_NAME": "Bennett"}, - {"ID": "B12345770", "legal_name": "Logan Price", "isActive": True, "PIDM": "24", "FIRST_NAME": "Logan", "LAST_NAME": "Price"}, - {"ID": "B12345771", "legal_name": "Avery Sullivan", "isActive": True, "PIDM": "25", "FIRST_NAME": "Avery", "LAST_NAME": "Sullivan"}, - - ] + ] tracyStudents = [ { "ID":"B00785329", @@ -511,22 +461,6 @@ "isSaasAdmin": None }, { - "student": "B00741361", - "supervisor": None, - "username": "schmitha", - "isLaborAdmin": None, - "isFinancialAidAdmin": None, - "isSaasAdmin": None - }, - { - "student": "B00732363", - "supervisor": None, - "username": "williamsb", - "isLaborAdmin": None, - "isFinancialAidAdmin": None, - "isSaasAdmin": None - }, - { "student": "B00730361", "supervisor": None, "username": "jamalie", @@ -704,81 +638,14 @@ "isBreak": 1, }, { - "termCode": "202600", - "termName": "AY 2026-2027", - "termStart": "2026-08-01", - "termEnd": "2027-05-01", - "termState": 0, - "primaryCutOff": "2026-09-01", - "adjustmentCutOff": "2026-10-01", - }, - { - "termCode": "202601", - "termName": "Thanksgiving Break 2026", - "termStart": "2026-08-01", - "termEnd": "2027-05-01", - "termState": 0, - "primaryCutOff": "2026-09-01", - "adjustmentCutOff": "2026-10-01", - "isBreak": 1, - }, - { - "termCode": "202602", - "termName": "Christmas Break 2026", - "termStart": "2026-08-01", - "termEnd": "2027-05-01", - "termState": 0, - "primaryCutOff": "2026-09-01", - "adjustmentCutOff": "2026-10-01", - "isBreak": 1, - }, - { - "termCode": "202603", - "termName": "Spring Break 2027", - "termStart": "2026-08-01", - "termEnd": "2027-05-01", - "termState": 0, - "primaryCutOff": "2026-09-01", - "adjustmentCutOff": "2026-10-01", - "isBreak": 1, - }, - { - "termCode": "202604", - "termName": "Fall Break 2026", - "termStart": "2026-08-01", - "termEnd": "2027-05-01", - "termState": 0, - "primaryCutOff": "2026-09-01", - "adjustmentCutOff": "2026-10-01", - "isBreak": 1, - }, - { - "termCode": "202611", - "termName": "Fall 2026", - "termStart": "2026-08-01", - "termEnd": "2026-12-31", - "termState": 0, - "primaryCutOff": "2026-09-01", - "adjustmentCutOff": "2026-10-01", - }, - { - "termCode": "202612", - "termName": "Spring 2027", - "termStart": "2027-01-01", - "termEnd": "2027-05-01", - "termState": 0, - "primaryCutOff": "2027-02-01", - "adjustmentCutOff": "2027-03-01", - }, - { - "termCode": "202613", - "termName": "Summer 2027", - "termStart": "2027-05-02", - "termEnd": "2027-08-01", - "termState": 0, - "primaryCutOff": "2027-06-01", - "adjustmentCutOff": "2027-07-01", - "isSummer": 1, + "termCode": f"202600", + "termName": f"AY 2026-2027", + "termStart": f"2026-08-01", + "termEnd": f"2027-05-01", + "termState": 0, + "primaryCutOff": f"2026-09-01", + "adjustmentCutOff": f"2026-09-01", + "isBreak": 0, }, ] @@ -812,89 +679,107 @@ "createdDate": f"2025-04-14", "status_id": "Pending" }]).on_conflict_replace().execute() + LaborStatusForm.insert([{ - "laborStatusFormID": 11, + "laborStatusFormID": 3, "termCode_id": f"202500", - "studentName": "Antonia Schmith", - "studentSupervisee_id": "B00741361", + "studentName": "Test Taker", + "studentSupervisee_id": "B12345773", "supervisor_id": "B12361006", - "department_id": 1, + "department_id": 5, "jobType": "Primary", "WLS": 1, - "POSN_TITLE": "Student Programmer", - "POSN_CODE": "S61407", + "POSN_TITLE": "Labor Workers", + "POSN_CODE": "S61409", "weeklyHours": 10, - "startDate": f"2026-04-01", - "endDate": f"2026-09-01", - "studentConfirmation": True - }]).on_conflict_replace().execute() + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() FormHistory.insert([{ - "formHistoryID": 11, - "formID_id": "11", + "formHistoryID": 3, + "formID_id": "3", "historyType_id": "Labor Status Form", "createdBy_id": 1, "createdDate": f"2025-04-14", - "status": "Approved" - }]).on_conflict_replace().execute() + "status_id": "Approved" + }]).on_conflict_replace().execute() + + +############################# +# Create Active Labor Status Form for the Break Term +############################# + +# cs department LaborStatusForm.insert([{ - "laborStatusFormID": 12, + "laborStatusFormID": 6, "termCode_id": f"202500", - "studentName": "Barbara Williams", - "studentSupervisee_id": "B00732363", + "studentName": "Pizza Taker", + "studentSupervisee_id": "B12345773", "supervisor_id": "B12361006", "department_id": 1, "jobType": "Primary", "WLS": 1, - "POSN_TITLE": "Student Programmer", - "POSN_CODE": "S61407", - "weeklyHours": 10, - "startDate": f"2027-04-01", - "endDate": f"2029-09-01", - "studentConfirmation": True - }]).on_conflict_replace().execute() + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 15, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() FormHistory.insert([{ - "formHistoryID": 12, - "formID_id": "12", + "formHistoryID": 6, + "formID_id": "6", "historyType_id": "Labor Status Form", "createdBy_id": 1, "createdDate": f"2025-04-14", - "status": "Approved" - }]).on_conflict_replace().execute() + "status_id": "Approved" + }]).on_conflict_replace().execute() -LaborReleaseForm.insert([{ - "laborReleaseFormID": 10, - "conditionAtRelease": "unsatisfactory", - "releaseDate": f"2025-04-14", - "reasonForRelease": "Smoking Cigarettes in the Programmers' space." - }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + "laborStatusFormID": 7, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 3, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() FormHistory.insert([{ - "formHistoryID": 13, - "formID_id": "12", - "historyType_id": "Labor Release Form", - "releaseForm": 10, + "formHistoryID": 7, + "formID_id": "7", + "historyType_id": "Labor Status Form", "createdBy_id": 1, "createdDate": f"2025-04-14", - "status": "Approved" - }]).on_conflict_replace().execute() + "status_id": "Approved" + }]).on_conflict_replace().execute() + + + +# labor department LaborStatusForm.insert([{ "laborStatusFormID": 4, "termCode_id": f"202500", - "studentName": "Elaleh Jamali", + "studentName": "Elaheh Jamali", "studentSupervisee_id": "B00730361", "supervisor_id": "B12361006", - "department_id": 1, + "department_id": 5, "jobType": "Secondary", "WLS": 1, - "POSN_TITLE": "Labor Workers", - "POSN_CODE": "S61419", - "weeklyHours": 10, - "startDate": f"2027-04-01", - "endDate": "2027-09-01" + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" }]).on_conflict_replace().execute() FormHistory.insert([{ @@ -903,23 +788,23 @@ "historyType_id": "Labor Status Form", "createdBy_id": 1, "createdDate": f"2025-04-14", - "status": "Approved" - }]).on_conflict_replace().execute() + "status_id": "Approved" + }]).on_conflict_replace().execute() LaborStatusForm.insert([{ "laborStatusFormID": 5, "termCode_id": f"202500", - "studentName": "Oluwagbayi Makinde", - "studentSupervisee_id": "B00791326", - "supervisor_id": "B12365892", - "department_id": 1, - "jobType": "Primary", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 5, + "jobType": "Secondary", "WLS": 1, - "POSN_TITLE": "Labor Workers", - "POSN_CODE": "S61429", - "weeklyHours": 10, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, "startDate": f"2025-04-01", - "endDate": "2029-09-01" + "endDate": "2025-09-01" }]).on_conflict_replace().execute() FormHistory.insert([{ @@ -928,50 +813,52 @@ "historyType_id": "Labor Status Form", "createdBy_id": 1, "createdDate": f"2025-04-14", - "status": "Approved" + "status_id": "Approved" }]).on_conflict_replace().execute() +# Biology Department + LaborStatusForm.insert([{ - "laborStatusFormID": 3, + "laborStatusFormID": 8, "termCode_id": f"202500", - "studentName": "Test Taker", - "studentSupervisee_id": "B12345773", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", "supervisor_id": "B12361006", - "department_id": 5, + "department_id": 4, "jobType": "Primary", "WLS": 1, - "POSN_TITLE": "Labor Workers", + "POSN_TITLE": "Media Technician", "POSN_CODE": "S61409", - "weeklyHours": 10, + "contractHours": 5, "startDate": f"2025-04-01", "endDate": "2025-09-01" }]).on_conflict_replace().execute() FormHistory.insert([{ - "formHistoryID": 3, - "formID_id": "3", + "formHistoryID": 8, + "formID_id": "8", "historyType_id": "Labor Status Form", "createdBy_id": 1, "createdDate": f"2025-04-14", "status_id": "Approved" }]).on_conflict_replace().execute() + LaborStatusForm.insert([{ - "laborStatusFormID": 9, "termCode_id": f"202500", - "studentName": "Genji Overwatch", - "studentSupervisee_id": "B12345756", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", + "department_id": 4, + "jobType": "Secondary", "WLS": 1, - "POSN_TITLE": "overwtahc guy", - "POSN_CODE": "S61410", - "contractHours": 15, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, "startDate": f"2025-04-01", "endDate": "2025-09-01" + }]).on_conflict_replace().execute() - }]).on_conflict_replace().execute() FormHistory.insert([{ "formHistoryID": 9, "formID_id": "9", @@ -979,684 +866,86 @@ "createdBy_id": 1, "createdDate": f"2025-04-14", "status_id": "Approved" - }]).on_conflict_replace().execute() -LaborStatusForm.insert([{ + }]).on_conflict_replace().execute() - "laborStatusFormID": 60, +# Mathematics Department + +LaborStatusForm.insert([{ + "laborStatusFormID": 10, "termCode_id": f"202500", - "studentName": "Mister Marlowe", - "studentSupervisee_id": "B12345759", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", "supervisor_id": "B12361006", - "department_id": 1, + "department_id": 3, "jobType": "Primary", "WLS": 1, - "POSN_TITLE": "Break Worker", - "POSN_CODE": "S61412", - "contractHours": 400, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, "startDate": f"2025-04-01", "endDate": "2025-09-01" + }]).on_conflict_replace().execute() - }]).on_conflict_replace().execute() FormHistory.insert([{ - "formHistoryID": 60, - "formID_id": "60", + "formHistoryID": 10, + "formID_id": "10", "historyType_id": "Labor Status Form", "createdBy_id": 1, "createdDate": f"2025-04-14", "status_id": "Approved" }]).on_conflict_replace().execute() -LaborStatusForm.insert([{ - "laborStatusFormID": 61, - "termCode_id": f"202501", - "studentName": "Mister Thanksgiving", - "studentSupervisee_id": "B11231123", +LaborStatusForm.insert([{ + "laborStatusFormID": 11, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", + "department_id": 3, + "jobType": "Secondary", "WLS": 1, - "POSN_TITLE": "Thanksgiving Worker", - "POSN_CODE": "S61412", - "contractHours": 50, - "startDate": f"2025-11-23", - "endDate": "2025-12-01" + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() - }]).on_conflict_replace().execute() FormHistory.insert([{ - "formHistoryID": 61, - "formID_id": "61", + "formHistoryID": 11, + "formID_id": "11", "historyType_id": "Labor Status Form", "createdBy_id": 1, - "createdDate": f"2025-11-01", + "createdDate": f"2025-04-14", "status_id": "Approved" - }]).on_conflict_replace().execute() + }]).on_conflict_replace().execute() + +#Technology and Applied Design Department LaborStatusForm.insert([{ - "laborStatusFormID": 62, - "termCode_id": "202611", - "studentName": "Alex Carter", - "studentSupervisee_id": "B12345762", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Office Assistant", - "POSN_CODE": "S61413", - "weeklyHours": 10, - "startDate": "2026-08-15", - "endDate": "2026-12-15" -}]).on_conflict_replace().execute() + "laborStatusFormID": 12, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 2, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() FormHistory.insert([{ - "formHistoryID": 62, - "formID_id": "62", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2026-08-01", - "status_id": "Approved" -}]).on_conflict_replace().execute() - - -LaborStatusForm.insert([{ - "laborStatusFormID": 63, - "termCode_id": "202611", - "studentName": "Morgan Hayes", - "studentSupervisee_id": "B12345763", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Computer Lab Assistant", - "POSN_CODE": "S61414", - "weeklyHours": 15, - "startDate": "2026-08-15", - "endDate": "2026-12-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 63, - "formID_id": "63", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2026-08-01", - "status_id": "Approved" -}]).on_conflict_replace().execute() - - -LaborStatusForm.insert([{ - "laborStatusFormID": 64, - "termCode_id": "202611", - "studentName": "Jordan Brooks", - "studentSupervisee_id": "B12345764", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Help Desk Assistant", - "POSN_CODE": "S61415", - "weeklyHours": 20, - "startDate": "2026-08-15", - "endDate": "2026-12-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 64, - "formID_id": "64", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2026-08-01", - "status_id": "Approved" -}]).on_conflict_replace().execute() - - -LaborStatusForm.insert([{ - "laborStatusFormID": 65, - "termCode_id": "202611", - "studentName": "Taylor Morgan", - "studentSupervisee_id": "B12345765", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Secondary", - "WLS": 0, - "POSN_TITLE": "Reception Assistant", - "POSN_CODE": "S61416", - "weeklyHours": 5, - "startDate": "2026-08-15", - "endDate": "2026-12-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 65, - "formID_id": "65", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2026-08-01", - "status_id": "Approved" -}]).on_conflict_replace().execute() - - -LaborStatusForm.insert([{ - "laborStatusFormID": 66, - "termCode_id": "202611", - "studentName": "Casey Turner", - "studentSupervisee_id": "B12345766", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Secondary", - "WLS": 0, - "POSN_TITLE": "Library Assistant", - "POSN_CODE": "S61417", - "weeklyHours": 10, - "startDate": "2026-08-15", - "endDate": "2026-12-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 66, - "formID_id": "66", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2026-08-01", - "status_id": "Approved" -}]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 72, - "termCode_id": "202612", - "studentName": "Alex Carter", - "studentSupervisee_id": "B12345762", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Office Assistant", - "POSN_CODE": "S61413", - "weeklyHours": 10, - "startDate": "2027-01-15", - "endDate": "2027-05-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 72, - "formID_id": "72", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2027-01-05", - "status_id": "Approved" -}]).on_conflict_replace().execute() - - -LaborStatusForm.insert([{ - "laborStatusFormID": 73, - "termCode_id": "202612", - "studentName": "Morgan Hayes", - "studentSupervisee_id": "B12345763", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Computer Lab Assistant", - "POSN_CODE": "S61414", - "weeklyHours": 15, - "startDate": "2027-01-15", - "endDate": "2027-05-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 73, - "formID_id": "73", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2027-01-05", - "status_id": "Approved" -}]).on_conflict_replace().execute() - - -LaborStatusForm.insert([{ - "laborStatusFormID": 74, - "termCode_id": "202612", - "studentName": "Taylor Morgan", - "studentSupervisee_id": "B12345765", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Secondary", - "WLS": 0, - "POSN_TITLE": "Reception Assistant", - "POSN_CODE": "S61416", - "weeklyHours": 5, - "startDate": "2027-01-15", - "endDate": "2027-05-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 74, - "formID_id": "74", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2027-01-05", - "status_id": "Approved" -}]).on_conflict_replace().execute() - - -# Student had a Fall-only position and receives a new Spring assignment. - -LaborStatusForm.insert([{ - "laborStatusFormID": 75, - "termCode_id": "202612", - "studentName": "Jordan Brooks", - "studentSupervisee_id": "B12345764", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Technology Assistant", - "POSN_CODE": "S61423", - "weeklyHours": 12, - "startDate": "2027-01-15", - "endDate": "2027-05-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 75, - "formID_id": "75", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2027-01-05", - "status_id": "Approved" -}]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 76, - "termCode_id": "202600", - "studentName": "Jordan Brooks", - "studentSupervisee_id": "B12345764", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Secondary", - "WLS": 1, - "POSN_TITLE": "Technology Assistant", - "POSN_CODE": "S61423", - "weeklyHours": 10, - "startDate": "2027-01-15", - "endDate": "2027-05-15" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 76, - "formID_id": "76", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2027-01-05", - "status_id": "Approved" -}]).on_conflict_replace().execute() - - -# Break Positions - -LaborStatusForm.insert([{ - "laborStatusFormID": 67, - "termCode_id": "202601", - "studentName": "Jamie Foster", - "studentSupervisee_id": "B12345767", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Thanksgiving Worker", - "POSN_CODE": "S61418", - "contractHours": 40, - "startDate": "2026-11-22", - "endDate": "2026-11-29" -}]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 68, - "termCode_id": "202602", - "studentName": "Riley Cooper", - "studentSupervisee_id": "B12345768", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Christmas Worker", - "POSN_CODE": "S61419", - "contractHours": 120, - "startDate": "2026-12-20", - "endDate": "2027-01-03" -}]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 69, - "termCode_id": "202603", - "studentName": "Drew Bennett", - "studentSupervisee_id": "B12345769", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Spring Break Worker", - "POSN_CODE": "S61420", - "contractHours": 80, - "startDate": "2027-03-07", - "endDate": "2027-03-14" -}]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 70, - "termCode_id": "202604", - "studentName": "Logan Price", - "studentSupervisee_id": "B12345770", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Fall Break Worker", - "POSN_CODE": "S61421", - "contractHours": 24, - "startDate": "2026-10-11", - "endDate": "2026-10-18" -}]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 71, - "termCode_id": "202613", - "studentName": "Avery Sullivan", - "studentSupervisee_id": "B12345771", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Summer Worker", - "POSN_CODE": "S61422", - "contractHours": 320, - "startDate": "2027-05-15", - "endDate": "2027-08-01" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 67, - "formID_id": "67", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2026-11-01", - "status_id": "Approved" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 68, - "formID_id": "68", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2026-12-01", - "status_id": "Approved" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 69, - "formID_id": "69", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2027-02-20", - "status_id": "Approved" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 70, - "formID_id": "70", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2026-10-01", - "status_id": "Approved" -}]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 71, - "formID_id": "71", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": "2027-04-15", - "status_id": "Approved" -}]).on_conflict_replace().execute() -############################# -# Create Active Labor Status Form for the Break Term -############################# - -# cs department - -LaborStatusForm.insert([{ - "laborStatusFormID": 6, - "termCode_id": f"202500", - "studentName": "Pizza Taker", - "studentSupervisee_id": "B12345773", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 15, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 6, - "formID_id": "6", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 7, - "termCode_id": f"202500", - "studentName": "Elaheh Jamali", - "studentSupervisee_id": "B00730361", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 3, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 7, - "formID_id": "7", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() - - - -# labor department - -LaborStatusForm.insert([{ - "laborStatusFormID": 4, - "termCode_id": f"202500", - "studentName": "Elaheh Jamali", - "studentSupervisee_id": "B00730361", - "supervisor_id": "B12361006", - "department_id": 5, - "jobType": "Secondary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 5, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 4, - "formID_id": "4", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 5, - "termCode_id": f"202500", - "studentName": "Elaheh Jamali", - "studentSupervisee_id": "B00730361", - "supervisor_id": "B12361006", - "department_id": 5, - "jobType": "Secondary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 5, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 5, - "formID_id": "5", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() - -# Biology Department - -LaborStatusForm.insert([{ - "laborStatusFormID": 8, - "termCode_id": f"202500", - "studentName": "Elaheh Jamali", - "studentSupervisee_id": "B00730361", - "supervisor_id": "B12361006", - "department_id": 4, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 5, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 8, - "formID_id": "8", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 9, - "termCode_id": f"202500", - "studentName": "Elaheh Jamali", - "studentSupervisee_id": "B00730361", - "supervisor_id": "B12361006", - "department_id": 4, - "jobType": "Secondary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 5, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 9, - "formID_id": "9", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() - -# Mathematics Department - -LaborStatusForm.insert([{ - "laborStatusFormID": 10, - "termCode_id": f"202500", - "studentName": "Elaheh Jamali", - "studentSupervisee_id": "B00730361", - "supervisor_id": "B12361006", - "department_id": 3, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 5, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 10, - "formID_id": "10", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() - -LaborStatusForm.insert([{ - "laborStatusFormID": 11, - "termCode_id": f"202500", - "studentName": "Elaheh Jamali", - "studentSupervisee_id": "B00730361", - "supervisor_id": "B12361006", - "department_id": 3, - "jobType": "Secondary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 5, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 11, - "formID_id": "11", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() - -#Technology and Applied Design Department - -LaborStatusForm.insert([{ - "laborStatusFormID": 12, - "termCode_id": f"202500", - "studentName": "Elaheh Jamali", - "studentSupervisee_id": "B00730361", - "supervisor_id": "B12361006", - "department_id": 2, - "jobType": "Secondary", - "WLS": 1, - "POSN_TITLE": "Media Technician", - "POSN_CODE": "S61409", - "contractHours": 5, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }]).on_conflict_replace().execute() - -FormHistory.insert([{ - "formHistoryID": 12, - "formID_id": "12", - "historyType_id": "Labor Status Form", - "createdBy_id": 1, - "createdDate": f"2025-04-14", - "status_id": "Approved" - }]).on_conflict_replace().execute() + "formHistoryID": 12, + "formID_id": "12", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() LaborStatusForm.insert([{ "laborStatusFormID": 13, @@ -1808,103 +1097,256 @@ "isCoordinator": False }, { - "supervisor": "B00123112", - "department": 1, - "isCoordinator": True + "supervisor": "B00123112", + "department": 1, + "isCoordinator": True + }, + { + "supervisor": "B00012213", + "department": 1, + "isCoordinator": True + } +] + +SupervisorDepartment.insert_many(supervisorDepartmentMembers).on_conflict_replace().execute() +print(" * Department members added") +print(f"termCode_id being used: {202500!r}") + +############################ +# Allocation Dummy Data: +########################### + +# Active Allocations for 2025 +allocations = [ + { + "termCode": 202500, + "department": 1, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Downscaling due to decrease in student enrollment caused by current economic conditions", + "primary_10": 2, + "primary_12": 2, + "primary_15": 1, + "primary_20": 0, + "secondary_5": 1, + "secondary_10": 0, + "breakHours": 260, + }, + { + "termCode": 202500, + "department": 2, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Increase in student enrollment due to exodous from CS department", + "primary_10": 4, + "primary_12": 2, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 2, + "secondary_10": 0, + "breakHours": 750, + }, + { + "termCode": 202500, + "department": 3, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "We are hiring more students to help with the increased workload in the department", + "primary_10": 5, + "primary_12": 6, + "primary_15": 4, + "primary_20": 1, + "secondary_5": 7, + "secondary_10": 0, + "breakHours": 550, + }, + { + "termCode": 202500, + "department": 4, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Downscaling the number of students in the department due to budget cuts", + "primary_10": 4, + "primary_12": 5, + "primary_15": 0, + "primary_20": 0, + "secondary_5": 1, + "secondary_10": 0, + "breakHours": 300, + }, + { + "termCode": 202500, + "department": 5, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + + # Active Allocations for 2026 + { + "termCode": 202600, + "department": 5, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 4, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 3, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, + { + "termCode": 202600, + "department": 2, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, }, { - "supervisor": "B00012213", - "department": 1, - "isCoordinator": True - } -] - -SupervisorDepartment.insert_many(supervisorDepartmentMembers).on_conflict_replace().execute() -print(" * Department members added") -print(f"termCode_id being used: {202500!r}") - -############################ -# Allocation Dummy Data: -########################### + "termCode": 202600, + "department": 1, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 8, + "primary_12": 10, + "primary_15": 7, + "primary_20": 4, + "secondary_5": 5, + "secondary_10": 1, + "breakHours": 900, + }, -# Active Allocations for 2025 -allocations = [ + # Requested Allocations for 2026-2027 (isFinal = False) { - "termCode": 202500, - "department": 1, + "termCode": 202600, + "department": 5, "isFinal": False, "approvedOn": None, "approvedBy": None, - "justification": "Downscaling due to decrease in student enrollment caused by current economic conditions", - "primary_10": 2, - "primary_12": 2, - "primary_15": 1, - "primary_20": 0, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 1, + "primary_12": 22, + "primary_15": 3, + "primary_20": 4, "secondary_5": 1, - "secondary_10": 0, - "breakHours": 260, + "secondary_10": 1, + "breakHours": 89, }, { - "termCode": 202500, - "department": 2, - "isFinal": True, + "termCode": 202600, + "department": 4, + "isFinal": False, "approvedOn": None, "approvedBy": None, - "justification": "Increase in student enrollment due to exodous from CS department", - "primary_10": 4, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 11, "primary_12": 2, - "primary_15": 7, + "primary_15": 3, "primary_20": 4, - "secondary_5": 2, - "secondary_10": 0, - "breakHours": 750, + "secondary_5": 1, + "secondary_10": 1, + "breakHours": 293, }, { - "termCode": 202500, + "termCode": 202600, "department": 3, - "isFinal": True, + "isFinal": False, "approvedOn": None, "approvedBy": None, - "justification": "We are hiring more students to help with the increased workload in the department", - "primary_10": 5, - "primary_12": 6, - "primary_15": 4, - "primary_20": 1, - "secondary_5": 7, - "secondary_10": 0, - "breakHours": 550, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 1, + "primary_12": 23, + "primary_15": 3, + "primary_20": 4, + "secondary_5": 1, + "secondary_10": 1, + "breakHours": 999, }, { - "termCode": 202500, - "department": 4, - "isFinal": True, + "termCode": 202600, + "department": 2, + "isFinal": False, "approvedOn": None, "approvedBy": None, - "justification": "Downscaling the number of students in the department due to budget cuts", - "primary_10": 4, - "primary_12": 5, - "primary_15": 0, - "primary_20": 0, + "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", + "primary_10": 10, + "primary_12": 2, + "primary_15": 3, + "primary_20": 13, "secondary_5": 1, - "secondary_10": 0, - "breakHours": 300, + "secondary_10": 19, + "breakHours": 1000, }, { - "termCode": 202500, - "department": 5, - "isFinal": True, + "termCode": 202600, + "department": 1, + "isFinal": False, "approvedOn": None, "approvedBy": None, "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", - "primary_10": 8, - "primary_12": 10, - "primary_15": 7, + "primary_10": 1, + "primary_12": 2, + "primary_15": 3, "primary_20": 4, - "secondary_5": 5, + "secondary_5": 1, "secondary_10": 1, - "breakHours": 900, + "breakHours": 100, }, - ] Allocation.insert_many(allocations).on_conflict_replace().execute() @@ -1921,8 +1363,8 @@ "positionCode": "S61407", "status": "Active", "wls": 1, - "revisionDate": f"2026-07-01", - "revisedBy": "Mario Nakazawa", + "revisionDate": f"2025-07-01", + "description": "", "department": 1 }, { @@ -1933,7 +1375,7 @@ "wls": 2, "revisionDate": f"2025-09-01", "revisionDate": f"2026-09-01", - "revisedBy": "Deanna Wilborne", + "description": "", "department": 1 }, { @@ -1941,8 +1383,8 @@ "positionCode": "S61409", "status": "Active", "wls": 3, - "revisionDate": f"2026-07-01", - "revisedBy": "Jasmine Jones", + "revisionDate": f"2025-07-01", + "description": "", "department": 1 }, { @@ -1951,9 +1393,8 @@ "status": "Active", "wls":3, "revisionDate" : f"2026-01-01", - "revisedBy": "Scott Heggen", - "department": 1 - + "description": "", + "department" : 1 }, { "positionTitle": "Teaching Associate", @@ -1961,7 +1402,7 @@ "status": "Inactive", "wls":2, "revisionDate" : f"2026-01-01", - "revisedBy": "Brian Ramsay", + "description": "", "department" : 3 }, { @@ -1970,7 +1411,7 @@ "status": "Active", "wls":2, "revisionDate" : f"2026-03-29", - "revisedBy": "Jan Pearce", + "description": "", "department" : 3 }, { @@ -1979,7 +1420,7 @@ "status": "Active", "wls":3, "revisionDate" : f"2026-01-23", - "revisedBy": "Scott Heggen", + "description": "", "department" : 1 }, { @@ -1988,7 +1429,7 @@ "status": "Active", "wls":4, "revisionDate" : f"2026-01-31", - "revisedBy": "Jasmine Jones", + "description": "", "department" : 1 }, { @@ -1997,7 +1438,7 @@ "status": "Active", "wls":5, "revisionDate" : f"2026-04-01", - "revisedBy": "Deanna Wilborne", + "description": "", "department" : 1 }, { @@ -2006,7 +1447,7 @@ "status": "Active", "wls":6, "revisionDate" : f"2026-05-03", - "revisedBy": "Jan Pearce", + "description": "", "department" : 1 }, { @@ -2015,7 +1456,7 @@ "status": "Active", "wls":1, "revisionDate" : f"2026-05-03", - "revisedBy": "Jan Pearce", + "description": "", "department" : 1 }, { @@ -2024,416 +1465,10 @@ "status": "Active", "wls":6, "revisionDate" : f"2026-05-03", - "revisedBy": "Brian Ramsay", + "description": "", "department" : 1 } ] PositionHistory.insert_many(positionHistory).on_conflict_replace().execute() -print(" * position history added") - -############################# -# Position Description Sections -############################# - -positionDescriptionSections = [ - { - "position": 2, - "sectionTitle": '

WLS Level Justification

', - "sectionContent": """ -

This position is assigned WLS 2 because it supports key research work with moderate technical complexity.

- """, - "order": 1, - }, - { - "position": 2, - "sectionTitle": '

Description of Duties

', - "sectionContent": """ -

Provide research assistance, coordinate data collection, and help prepare reports.

- """, - "order": 2, - }, - { - "position": 2, - "sectionTitle": '

Learning Opportunities

', - "sectionContent": """ -

Gain experience with research practices, data management, and academic collaboration.

- """, - "order": 3, - }, - { - "position": 2, - "sectionTitle": '

Required Qualifications

', - "sectionContent": """ -

Strong communication skills, attention to detail, and ability to work independently.

- """, - "order": 4, - }, - { - "position": 3, - "sectionTitle": '

WLS Level Justification

', - "sectionContent": """ -

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

- """, - "order": 1, - }, - { - "position": 3, - "sectionTitle": '

Description of Duties

', - "sectionContent": """ -
A. Workplace Responsibility
-

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

- -
B. Communication
-

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

- -
C. Teamwork & Collaboration
-

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

- -
D. Apply Critical Thinking and Problem Solving in Workplace Tasks
-

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

- -
E. Utilize Technology Effectively in the Workplace
-

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

- -
F. Connect Work Experience to Career and Academic Goals
-

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

- -
G. Foster Creativity and Innovation in the Workplace
-

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

- """, - "order": 2, - }, - { - "position": 3, - "sectionTitle": "

Learning Opportunities

", - "sectionContent": """ -

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

- -
A. Peer Instruction and Facilitation
-

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

- -
B. Inventory and Resource Management
-

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

- -
C. Problem Solving
-

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

- -
D. Technical Competency
-

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

- -
E. Communication
-

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

- """, - "order": 3, - }, - { - "position": 3, - "sectionTitle": "

Required Qualifications

", - "sectionContent": """ -

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

- -
A. Independence
-

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

- -
B. Responsiveness to Feedback
-

Ability to take advice and respond appropriately.

- -
C. Mentorship
-

A desire to mentor and work with high school students.

- -
D. Patience
-

Patience working with unskilled yet energetic high school students.

- -
E. Software Knowledge
-

Some basic understanding of software and debugging.

- """, - "order": 4, - }, - { - "position": 4, - "sectionTitle": '

WLS Level Justification

', - "sectionContent": """ -

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

- """, - "order": 1, - }, - { - "position": 4, - "sectionTitle": '

Description of Duties

', - "sectionContent": """ -
A. Workplace Responsibility
-

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

- -
B. Communication
-

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

- -
C. Teamwork & Collaboration
-

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

- -
D. Apply Critical Thinking and Problem Solving in Workplace Tasks
-

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

- -
E. Utilize Technology Effectively in the Workplace
-

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

- -
F. Connect Work Experience to Career and Academic Goals
-

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

- -
G. Foster Creativity and Innovation in the Workplace
-

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

- """, - "order": 2, - }, - { - "position": 4, - "sectionTitle": "

Learning Opportunities

", - "sectionContent": """ -

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

- -
A. Peer Instruction and Facilitation
-

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

- -
B. Inventory and Resource Management
-

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

- -
C. Problem Solving
-

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

- -
D. Technical Competency
-

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

- -
E. Communication
-

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

- """, - "order": 3, - }, - { - "position": 4, - "sectionTitle": "

Required Qualifications

", - "sectionContent": """ -

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

- -
A. Independence
-

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

- -
B. Responsiveness to Feedback
-

Ability to take advice and respond appropriately.

- -
C. Mentorship
-

A desire to mentor and work with high school students.

- -
D. Patience
-

Patience working with unskilled yet energetic high school students.

- -
E. Software Knowledge
-

Some basic understanding of software and debugging.

- """, - "order": 4, - }, - { - "position": 5, - "sectionTitle": '

WLS Level Justification

', - "sectionContent": """ -

Refer to the WLS Level definitions to describe why this level is appropriate for the role. Highlight supervision level, skill requirements, and scope of responsibility. This position assumes some previous experience on an FRC team or with software/programming. WLS Level 2 is appropriate for first-year students with some relevant experience or those new to Work-Learning-Service. It introduces students to professional habits, collaboration, and foundational technical tasks while providing structured guidance.

- """, - "order": 1, - }, - { - "position": 5, - "sectionTitle": '

Description of Duties

', - "sectionContent": """ -
A. Workplace Responsibility
-

Follow team procedures for robot software development, daily check-ins, and documentation practices. Assist with organizing digital repositories and labeling source code for reuse and version control. Participate in sessions and preparations for outreach or competition in a timely and consistent manner.

- -
B. Communication
-

Assist team leader(s) and student colleagues in planning lessons for FRC high school students, including researching materials and other investigations as assigned by team leader(s) with the goal of learning. Ask questions and provide updates on assigned coding or testing tasks.

- -
C. Teamwork & Collaboration
-

In collaboration with team leader(s), assist the team in supporting other student colleagues, generally overseeing high school students while working on and testing robot code.

- -
D. Apply Critical Thinking and Problem Solving in Workplace Tasks
-

Attend the annual FRC competition and assist the team in supporting high school students in explaining and refining their software work and problem-solving skills under pressure. Identify and troubleshoot errors in logic, syntax, or structure in robot software projects.

- -
E. Utilize Technology Effectively in the Workplace
-

In collaboration with team leader(s) and other student colleagues, assist high school students with projects and assignments related to the software of the robot.

- -
F. Connect Work Experience to Career and Academic Goals
-

Train themselves with FIRST/Team resources in software to be competition-ready and prepare for the workforce (material provided by the supervisor).

- -
G. Foster Creativity and Innovation in the Workplace
-

Help high school students stay engaged and safe while working with software tools (e.g., WPILib, VS Code, Git, GitHub, and Java) and during collaborative design reviews.

- """, - "order": 2, - }, - { - "position": 5, - "sectionTitle": '

Learning Opportunities

', - "sectionContent": """ -

List how this position will support student learning through daily responsibilities and intentional reflection. Supervisors are encouraged to reference specific Learning Goals (1–7) and describe how these goals show up in the work.

- -
A. Peer Instruction and Facilitation
-

Gain experience in tutoring, lab assistance, and student mentorship. (Aligned with: Goals 2, 3, and 6)

- -
B. Inventory and Resource Management
-

Track and maintain computer equipment and supplies effectively (e.g. update software regularly and install new relevant software). (Aligned with: Goals 1 and 4)

- -
C. Problem Solving
-

Debugging code and testing said code on relevant robots. (Aligned with: Goal 3)

- -
D. Technical Competency
-

Advance their knowledge of skills in specific areas of interest, namely software. (Aligned with: Goals 4 and 5)

- -
E. Communication
-

Interaction with faculty, student colleagues, high school students, and their parents in a professional manner. (Aligned with: Goal 2)

- """, - "order": 3, - }, - { - "position": 5, - "sectionTitle": '

Required Qualifications

', - "sectionContent": """ -

List the baseline skills or attributes a student should have to be successful in this role, while ensuring equity and accessibility.

- -
A. Independence
-

Ability to function with a little more independence and complete tasks with assistance from team leader(s) and other student colleagues.

- -
B. Responsiveness to Feedback
-

Ability to take advice and respond appropriately.

- -
C. Mentorship
-

A desire to mentor and work with high school students.

- -
D. Patience
-

Patience working with unskilled yet energetic high school students.

- -
E. Software Knowledge
-

Some basic understanding of software and debugging.

- """, - "order": 4, - }, -] - -PositionDescriptionSection.insert_many( - positionDescriptionSections -).on_conflict_replace().execute() - -print(" * position description sections added") - - -allocation =[ - { - "termCode":f"{2025}00", - "department": 3, - "isFinal": True, - "approvedOn": f"{2025}-06-30", - "approvedBy": "B12365892", - "justification": "We just want it for fun", - "primary_10": 2, - "primary_12": 3, - "primary_15": 1, - "primary_20": 6, - "secondary_5": 2, - "secondary_10": 0, - "breakHours": 500 - }, - { - "termCode":f"{2025}00", - "department": 2, - "isFinal": False, - "approvedOn": f"{2025}-06-20", - "approvedBy": "B00763721", - "justification": "We need it to lower the amount of allocations we have", - "primary_10": 1, - "primary_12": 2, - "primary_15": 5, - "primary_20": 2, - "secondary_5": 10, - "secondary_10": 0, - "breakHours": 1500 - } - ] -Allocation.insert_many(allocation).on_conflict_replace().execute() -print(" * allocation added") - - -dummy_lsf = [ - { - "laborStatusFormID": 13, - "termCode_id": f"202500", - "studentName": "Chris Georgiev", - "studentSupervisee_id": "B00811617", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 4, - "POSN_TITLE": "guy who does stuff", - "POSN_CODE": "S61415", - "weeklyHours": 12, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }, - { - - "laborStatusFormID": 14, - "termCode_id": f"202500", - "studentName": "Julius Fritz", - "studentSupervisee_id": "B00815474", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 2, - "POSN_TITLE": "guy who sits in chair", - "POSN_CODE": "S61416", - "weeklyHours": 15, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }, - { - "laborStatusFormID": 15, - "termCode_id": f"202500", - "studentName": "Subaru Natsuki", - "studentSupervisee_id": "B12345223", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 1, - "POSN_TITLE": "Aura Monster", - "POSN_CODE": "S61417", - "weeklyHours": 20, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - }, - { - "laborStatusFormID": 16, - "termCode_id": f"202500", - "studentName": "Hatsune Miku", - "studentSupervisee_id": "B12345003", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Primary", - "WLS": 6, - "POSN_TITLE": "Singer", - "POSN_CODE": "S61409", - "weeklyHours": 20, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - - }, - { - "laborStatusFormID": 17, - "termCode_id": f"202500", - "studentName": "Michael Jackson", - "studentSupervisee_id": "B12345772", - "supervisor_id": "B12361006", - "department_id": 1, - "jobType": "Secondary", - "WLS": 6, - "POSN_TITLE": "Famous singer", - "POSN_CODE": "S61410", - "weeklyHours": 5, - "startDate": f"2025-04-01", - "endDate": "2025-09-01" - } -] -LaborStatusForm.insert_many(dummy_lsf).on_conflict_replace().execute() +print(" * position history added") \ No newline at end of file diff --git a/database/migrate_db.sh b/database/migrate_db.sh index e2ba150e7..dea226d8e 100755 --- a/database/migrate_db.sh +++ b/database/migrate_db.sh @@ -32,7 +32,6 @@ pem add app.models.studentLaborEvaluation.StudentLaborEvaluation pem add app.models.formSearchResult.FormSearchResult pem add app.models.positionHistory.PositionHistory pem add app.models.allocation.Allocation -pem add app.models.positionDescriptionSection.PositionDescriptionSection pem watch pem migrate diff --git a/tests/code/test_allocationManager.py b/tests/code/test_allocationManger.py similarity index 52% rename from tests/code/test_allocationManager.py rename to tests/code/test_allocationManger.py index 7b4fe6e78..ea7da6782 100644 --- a/tests/code/test_allocationManager.py +++ b/tests/code/test_allocationManger.py @@ -48,15 +48,6 @@ def testTerm(): #destroy term.delete_instance() -@pytest.fixture -def testBreakTerm(): - #create - term = Term.create(termCode = 200601) - yield term - - #destroy - term.delete_instance() - @pytest.fixture def testAllocation(testDepartment,testTerm): #create @@ -137,8 +128,8 @@ def testLaborStatusForm(testStudent,testSupervisor,testDepartment,testTerm): POSN_CODE = "S61412", contractHours = 500, weeklyHours = 15, - startDate = "2006-08-01", - endDate = "2007-5-01", + startDate = "2025-04-01", + endDate = "2025-09-01", supervisorNotes = None, laborDepartmentNotes = None, studentConfirmation = True, @@ -151,50 +142,6 @@ def testLaborStatusForm(testStudent,testSupervisor,testDepartment,testTerm): #destroy laborStatusForm.delete_instance() -@pytest.fixture -def testBreakLaborStatusForm(testStudent,testSupervisor,testDepartment,testBreakTerm): - breakLaborStatusForm = LaborStatusForm.create( - studentName = "John Doe", - laborStatusFormID = 9898, - termCode = testBreakTerm.termCode, - studentSupervisee = testStudent.ID, - supervisor_id = testSupervisor.ID, - department = testDepartment.departmentID, - jobType = "Secondary", - WLS = 1, - POSN_TITLE = "Vacation Worker", - POSN_CODE = "S61412", - contractHours = 168, - weeklyHours = None, - startDate = "2006-04-01", - endDate = "2006-09-01", - supervisorNotes = None, - laborDepartmentNotes = None, - studentConfirmation = True, - confirmationToken = None, - studentExpirationDate = True, - studentResponseDate = True, - ) - - breakFormHistory = FormHistory.create( - formHistoryID = 9898, - formID_id = "9898", - historyType_id = "Labor Status Form", - releaseForm_id = None, - adjustedForm_id = None, - overloadForm_id = None, - createdBy_id = 1, - createdDate = "2006-02-01", - reviewedDate = "2006-03-01", - reviewedBy_id = 1, - status_id = "Approved", - rejectReason = None - ) - - yield breakLaborStatusForm, breakFormHistory - #destroy - breakLaborStatusForm.delete_instance() - @pytest.fixture def testFormHistory(testLaborStatusForm,testUser): formHistory = FormHistory.create( @@ -257,91 +204,4 @@ def test_getContractedAllocations(testLaborStatusForm, testTerm, testDepartment, assert contractedAllocation['used_secondaries'] == 0 assert contractedAllocation['used_total'] == 1 - assert contractedAllocation['break_hours'] == 500 - -@pytest.mark.integration -def test_getContractedAllocations_withoutAnAllocationRow(testLaborStatusForm, testTerm, testDepartment, testFormHistory): - ''' - getContractedAllocations must not require an Allocation row to exist for - the department/term (e.g. before one has been created or finalized) - - it should still report the LaborStatusForm-derived counts. - ''' - contractedAllocation = getContractedAllocations(testTerm.termCode, testDepartment.departmentID) - assert contractedAllocation['used_15'] == 1 - assert contractedAllocation['break_hours'] == 500 - -@pytest.mark.integration -def test_getContractedAllocations_sumsBreakHoursAcrossAcademicYearCode(testDepartment, testStudent, testSupervisor, testUser): - ''' - A department can have approved break-term contracts under both a specific - term and that year's academic-year "00" bucket term - break_hours should - sum both, not silently keep only whichever one the query happens to see - first. - ''' - specificTerm = Term.create(termCode=200610) - academicYearTerm = Term.create(termCode=200600) # matches testTerm's code - - specificTermForm = LaborStatusForm.create( - laborStatusFormID=9001, termCode=specificTerm, studentSupervisee=testStudent, - supervisor_id=testSupervisor.ID, department=testDepartment, jobType="Primary", WLS=1, - POSN_TITLE="Specific Term Break", POSN_CODE="S9001", contractHours=100, weeklyHours=None, - ) - FormHistory.create( - formHistoryID=9001, formID=specificTermForm, historyType="Labor Status Form", - createdBy=testUser.userID, createdDate="2025-03-02", status="Approved", - ) - - academicYearForm = LaborStatusForm.create( - laborStatusFormID=9002, termCode=academicYearTerm, studentSupervisee=testStudent, - supervisor_id=testSupervisor.ID, department=testDepartment, jobType="Primary", WLS=1, - POSN_TITLE="Academic Year Break", POSN_CODE="S9002", contractHours=250, weeklyHours=None, - ) - FormHistory.create( - formHistoryID=9002, formID=academicYearForm, historyType="Labor Status Form", - createdBy=testUser.userID, createdDate="2025-03-02", status="Approved", - ) - - try: - contractedAllocation = getContractedAllocations(specificTerm.termCode, testDepartment.departmentID) - assert contractedAllocation['break_hours'] == 350 # 100 + 250, both terms summed - finally: - specificTermForm.delete_instance() - academicYearForm.delete_instance() - specificTerm.delete_instance() - academicYearTerm.delete_instance() -def test_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartment,testTerm): - - # Test that the formHistory object exists - breakContractHours = getBreakContracts(testBreakTerm, testDepartment) - assert breakContractHours == 168 - - # Test it with a higher amount of hours - testBreakLaborStatusForm[0].contractHours = 800 - testBreakLaborStatusForm[0].save() - - breakContractHours = getBreakContracts(testBreakTerm, testDepartment) - assert breakContractHours == 800 - - # Test that it works even if weeklyHours and contractHours are set - testBreakLaborStatusForm[0].weeklyHours = 9999 - testBreakLaborStatusForm[0].save() - - breakContractHours = getBreakContracts(testBreakTerm, testDepartment) - assert breakContractHours == 800 - - # Test that if the form is denied to not show up. - testBreakLaborStatusForm[1].status = "denied by student" - testBreakLaborStatusForm[1].save() - - breakContractHours = getBreakContracts(testBreakTerm, testDepartment) - assert breakContractHours == 0 - - # Test if the term changes to a non-break term - testBreakLaborStatusForm[0].termCode = testTerm - testBreakLaborStatusForm[0].save() - testBreakLaborStatusForm[1].status = "Approved" - testBreakLaborStatusForm[1].save() - - breakContractHours = getBreakContracts(testBreakTerm, testDepartment) - assert breakContractHours == 0 - + assert contractedAllocation['break_hours'] == 500 \ No newline at end of file diff --git a/tests/code/test_download.py b/tests/code/test_download.py deleted file mode 100644 index 66c53b0c9..000000000 --- a/tests/code/test_download.py +++ /dev/null @@ -1,151 +0,0 @@ -import io - -import pytest - -from app.logic.download import makePositionDescriptionPDF, removeHTML -from app.models import mainDB -from app.models.department import Department -from app.models.positionDescriptionSection import PositionDescriptionSection -from app.models.positionHistory import PositionHistory - - -@pytest.mark.integration -def test_makePositionDescriptionPDF(): - """ - Tests both HTML stripping and PDF generation using the makePositionDescriptionPDF() function. - """ - with mainDB.atomic() as transaction: - headingResult = removeHTML( - "

Learning Opportunities

" - ) - - # Confirm that the heading text remains. - assert "Learning Opportunities" in headingResult - - # Confirm that the HTML tags were removed. - assert "

" not in headingResult - assert "

" not in headingResult - - sectionResult = removeHTML( - """ -

A. Equipment Management

-

Maintains laboratory equipment & supplies.

- -

B. Experiment Support

-

Supports experiments and records results.

- """ - ) - - # Normalize whitespace so the test does not depend on the exact number of newlines produced by removeHTML(). - normalizedSectionResult = " ".join(sectionResult.split()) - - # Confirm that all readable content remains after stripping HTML. - assert "A. Equipment Management" in normalizedSectionResult - assert "Maintains laboratory equipment & supplies." in normalizedSectionResult - assert "B. Experiment Support" in normalizedSectionResult - assert "Supports experiments and records results." in normalizedSectionResult - - # Confirm that HTML tags and encoded entities were removed. - assert "

" not in normalizedSectionResult - assert "

" not in normalizedSectionResult - assert "

" not in normalizedSectionResult - assert "

" not in normalizedSectionResult - assert "&" not in normalizedSectionResult - - # Create the department shared by both test positions. - department = Department.create( - departmentID=200, - DEPT_NAME="Physics", - ACCOUNT="6742", - ORG="2116", - departmentCompliance=True, - isActive=True, - ) - - # Create a position with HTML-formatted description sections. - positionWithSections = PositionHistory.create( - positionTitle="Lab Technician", - positionCode="S34516", - department=department, - status="Active", - wls=3, - revisionDate="2023-01-01", - revisedBy="Jane Doe", - ) - - # Create a position without description sections to test for "No description available." output. - positionWithoutSections = PositionHistory.create( - positionTitle="Research Assistant", - positionCode="S34517", - department=department, - status="Active", - wls=2, - revisionDate="2023-01-02", - revisedBy="Sarah Smith", - ) - - # Insert this section first even though its order is 2. Tests section-ordering logic used by getPositionDescriptionSections(). - PositionDescriptionSection.create( - position=positionWithSections, - sectionTitle="

Responsibilities

", - sectionContent=""" -

A. Equipment Management

-

Maintains laboratory equipment & supplies.

- -

B. Experiment Support

-

Supports experiments and records results.

- """, - order=2, - ) - - # Insert the order-1 section second. - PositionDescriptionSection.create( - position=positionWithSections, - sectionTitle="

Position Summary

", - sectionContent=""" -

Provides support for laboratory research.

-

Works with faculty and student researchers.

- """, - order=1, - ) - - pdfBufferWithSections = makePositionDescriptionPDF( - department, - positionWithSections, - ) - - # Confirm that the function returns an in-memory byte buffer. - assert isinstance(pdfBufferWithSections, io.BytesIO) - - pdfBytesWithSections = pdfBufferWithSections.getvalue() - - # Confirm that the generated PDF is not empty. - assert len(pdfBytesWithSections) > 0 - - # Confirm that the output begins with the standard PDF header. - assert pdfBytesWithSections.startswith(b"%PDF-") - - # Confirm that the output ends with the standard PDF marker. - assert pdfBytesWithSections.rstrip().endswith(b"%%EOF") - - pdfBufferWithoutSections = makePositionDescriptionPDF( - department, - positionWithoutSections, - ) - - # Confirm that the fallback branch also returns a BytesIO object. - assert isinstance(pdfBufferWithoutSections, io.BytesIO) - - pdfBytesWithoutSections = pdfBufferWithoutSections.getvalue() - - # Confirm that the fallback PDF is not empty. - assert len(pdfBytesWithoutSections) > 0 - - # Confirm that the fallback output is also a valid PDF. - assert pdfBytesWithoutSections.startswith(b"%PDF-") - assert pdfBytesWithoutSections.rstrip().endswith(b"%%EOF") - - # Confirm that the two positions did not produce identical PDFs. - assert pdfBytesWithSections != pdfBytesWithoutSections - - transaction.rollback() \ No newline at end of file diff --git a/tests/code/test_getPositions.py b/tests/code/test_getPositions.py index 4e4ee5e98..a59a43747 100644 --- a/tests/code/test_getPositions.py +++ b/tests/code/test_getPositions.py @@ -10,8 +10,8 @@ def test_getActivePositions(): Test to check if the getActivePositions function in getPositions.py correctly retrieves active positions for a single department. """ with mainDB.atomic() as transaction: - dept1 = Department.create(departmentID=102, DEPT_NAME="Computer Science", ACCOUNT="6740", ORG="2114", departmentCompliance=True, isActive=True) - dept2 = Department.create(departmentID=103, DEPT_NAME="Mathematics", ACCOUNT="6741", ORG="2115", departmentCompliance=True, isActive=True) + dept1 = Department.create(departmentID=100, DEPT_NAME="Computer Science", ACCOUNT="6740", ORG="2114", departmentCompliance=True, isActive=True) + dept2 = Department.create(departmentID=101, DEPT_NAME="Mathematics", ACCOUNT="6741", ORG="2115", departmentCompliance=True, isActive=True) position1 = PositionHistory.create(positionTitle="Teaching Assistant", positionCode="S34512", @@ -80,62 +80,7 @@ def test_getActivePositions(): assert len(posURL3) == 0 transaction.rollback() - -@pytest.mark.integration -def test_getPosition(): - """ - Test to check if the getPosition function in getPositions.py correctly retrieves a single position for a given department, position code, and optional revision date. - """ - with mainDB.atomic() as transaction: - dept = Department.create(departmentID=200, DEPT_NAME="Physics", ACCOUNT="6742", ORG="2116", departmentCompliance=True, isActive=True) - - position1 = PositionHistory.create(positionTitle="Lab Technician", - positionCode="S34516", - department=dept, - status="Inactive", - wls=3, - revisionDate="2024-01-01", - description="") - - position2 = PositionHistory.create(positionTitle="Lab Technician", - positionCode="S34516", - department=dept, - status="Requested", - wls=3, - revisionDate="2025-01-01", - description="") - - position3 = PositionHistory.create(positionTitle="Lab Technician", - positionCode="S34516", - department=dept, - status="Active", - wls=3, - revisionDate="2023-01-01", - description="") - - # Test retrieving the most recent revision - should return regardless of status - retrieved_position = getPosition(dept, "S34516") - assert retrieved_position.revisionDate.isoformat() == "2025-01-01" - - # Test retrieving a specific revision - should return regardless of status - retrieved_position_specific = getPosition(dept, "S34516", "2023-01-01") - assert retrieved_position_specific.revisionDate.isoformat() == "2023-01-01" - assert retrieved_position_specific.status == "Active" - - retrieved_position = getPosition(dept, "S34516", "2024-01-01") - assert retrieved_position.revisionDate.isoformat() == "2024-01-01" - assert retrieved_position.status == "Inactive" - - retrieved_position_specific = getPosition(dept, "S34516", "2025-01-01") - assert retrieved_position_specific.revisionDate.isoformat() == "2025-01-01" - assert retrieved_position_specific.status == "Requested" - - # Test retrieving a non-existent revision date - retrieved_position_non_existent = getPosition(dept, "S34516", "2099-01-01") - assert retrieved_position_non_existent is None - - transaction.rollback() - + @pytest.mark.integration def test_getPositions(): with mainDB.atomic() as transaction: @@ -147,28 +92,32 @@ def test_getPositions(): department=dept1, status="Active", wls=4, - revisionDate="2023-01-01") - + revisionDate="2023-01-01", + description="") + position2 = PositionHistory.create(positionTitle="Research Assistant", positionCode="S34513", department=dept1, status="Inactive", wls=3, - revisionDate="2023-01-01",) + revisionDate="2023-01-01", + description="") position3 = PositionHistory.create(positionTitle="Lab Assistant", positionCode="S34514", department=dept1, status="Active", wls=2, - revisionDate="2026-01-01") + revisionDate="2026-01-01", + description="") position4 = PositionHistory.create(positionTitle="Intern", positionCode="S34515", department=dept1, status="Active", wls=1, - revisionDate="2023-01-01") + revisionDate="2023-01-01", + description="") test1 = list(getPositions(dept1)) test2 = list(getPositions(dept2)) @@ -200,4 +149,4 @@ def test_getPositions(): # Check that no positions are returned when department is None assert len(test3) == 0 - transaction.rollback() \ No newline at end of file + transaction.rollback() diff --git a/tests/code/test_getTerms.py b/tests/code/test_getTerms.py deleted file mode 100644 index eaca2be64..000000000 --- a/tests/code/test_getTerms.py +++ /dev/null @@ -1,56 +0,0 @@ -import pytest -from app import app - -from app.models.term import Term -from app.logic.getTerms import getTerms -from datetime import datetime, date - -@pytest.fixture -def test_terms(): - # Make terms and get a set year, its far enough back that the test won't break other data. - - currentYear = 2000 - AcademicYear = f"{currentYear}-{currentYear + 1}" - print(currentYear) - - test_currentAY = Term.create( - termCode = int(f"{currentYear}00"), - termName = f"AY {currentYear}-{currentYear + 1}", - termStart = f"{currentYear}-08-01", - termEnd = f"{currentYear + 1}-05-01", - termState = 1, - primaryCutOff = f"{currentYear + 1}-09-01", - adjustmentCutOff = f"2002-10-01" - ) - test_fallTerm = Term.create( - termCode = int(f"{currentYear}11"), - termName = f"Fall {currentYear}", - termStart = f"{currentYear}-08-01", - termEnd = f"{currentYear}-12-12", - termState = 1, - primaryCutOff = f"{currentYear}-09-01", - adjustmentCutOff = f"{currentYear}-10-01" - ) - test_springTerm = Term.create( - termCode = int(f"{currentYear}12"), - termName = f"Spring {currentYear + 1}", - termStart = f"{currentYear + 1}-01-01", - termEnd = f"{currentYear + 1}-05-01", - termState = 1, - primaryCutOff = f"{currentYear + 1}-02-01", - adjustmentCutOff = f"{currentYear + 1}-3-01" - ) - yield test_currentAY, test_fallTerm, test_springTerm, AcademicYear - - #destroy all created data - test_currentAY.delete_instance() - test_fallTerm.delete_instance() - test_springTerm.delete_instance() - -@pytest.mark.integration -def test_getCurrentTerms(test_terms): - currentAY, fallTerm, springTerm = getTerms(test_terms[3]) # Get terms for the year that is selected. - - assert currentAY == test_terms[0] - assert fallTerm == test_terms[1] - assert springTerm == test_terms[2] \ No newline at end of file diff --git a/tests/code/test_tracy.py b/tests/code/test_tracy.py index f09eb5aee..730547dd5 100644 --- a/tests/code/test_tracy.py +++ b/tests/code/test_tracy.py @@ -18,10 +18,8 @@ def test_init(self, tracy): def test_getStudents(self, tracy): with app.app_context(): students = tracy.getStudents() - for s in students: - assert ['Antonia','Barbara','Elaheh','Guillermo','Jeremiah','Kat', 'Oluwagbayi', 'Test', 'Tyler'] == [s.FIRST_NAME for s in students] - assert ['777','118','718','300','420','420', '883', '700', '420'] == [s.STU_CPO for s in students] - + assert ['Elaheh','Guillermo','Jeremiah','Kat', 'Oluwagbayi', 'Test', 'Tyler'] == [s.FIRST_NAME for s in students] + assert ['718','300','420','420', '883', '700', '420'] == [s.STU_CPO for s in students] @pytest.mark.integration def test_getStudentFromBNumber(self, tracy):