From 9d4acbfc556df1ff9eb45d879467e0fc630aef5b Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Thu, 2 Jul 2026 20:56:48 +0000 Subject: [PATCH 01/92] Started working on HTML design --- app/templates/admin/manageDepartments.html | 215 ++++++++++++--------- 1 file changed, 121 insertions(+), 94 deletions(-) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index ddb1b0431..95ed236e7 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -2,15 +2,17 @@ {% block styles %} {{super()}} - - + + {% endblock %} {% block scripts %} {{super()}} - - - + + + {% endblock %} {% block app_content %} @@ -19,118 +21,143 @@
-
- Click to Skip -
-
-

Manage Departments

+
+ Click to Skip +
+
+

Manage Departments

-

Position descriptions are up to date.

-

Position descriptions are not up to date

+

Monitor allocation usage, compliance, and department position needs across + campus.

-
-
-
+

+ Position descriptions are up to date.

+

Position descriptions are not up to date

+ +
-
- +
+
+
+ +
-
-
- - -
-
- - - - - - - - - {% for department in activeDepartments %} - - - - - {% endfor %} - -
DepartmentStatus
{{department.DEPT_NAME}}({{department.ORG}}, {{department.ACCOUNT}}) - -
-
- -
- - - - - - - - {% for department in inactiveDepartments %} - - - - {% endfor %} - -
Department
{{department.DEPT_NAME}}({{department.ORG}}, {{department.ACCOUNT}})
-
+
+ + +
+ +
+ + +
+
+
+ + + +
+
+
+ + +
+
+ + + + + + + + + {% for department in activeDepartments %} + + + + + {% endfor %} + +
DepartmentStatus
{{department.DEPT_NAME}}({{department.ORG}}, + {{department.ACCOUNT}}) + +
+
+ + + +
+ + + + + + + + {% for department in inactiveDepartments %} + + + + {% endfor %} + +
Department
{{department.DEPT_NAME}}({{department.ORG}}, + {{department.ACCOUNT}})
+
-
-
+
+
- -{% endblock %} +{% endblock %} \ No newline at end of file From 8deecb550f554f47fa1d5a2a8cda8fc9d2c76cb4 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Sat, 4 Jul 2026 21:26:22 +0000 Subject: [PATCH 02/92] Finished initial design of the department page --- app/templates/admin/manageDepartments.html | 59 +++++++++++++++++----- 1 file changed, 45 insertions(+), 14 deletions(-) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 95ed236e7..4b726e798 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -41,22 +41,19 @@

Manage Departments


-
-
-
- - -
+
-
- - -
-
+
+ +
+
+ + +
+
- - +
@@ -67,8 +64,9 @@

Manage Departments

  • Inactive Departments
  • + - +
    @@ -76,6 +74,10 @@

    Manage Departments

    + + + + @@ -92,8 +94,37 @@

    Manage Departments

    value="{{department.departmentID}}">{% if department.departmentCompliance == True %}In Compliance{% else %}Not in Compliance{% endif %} + + + + + {% endfor %} +
    Department StatusPositionsAllocation approval statusBreak Hours Used/GivenActions
    +

    Prim: 6 of 16

    +

    Sec: 12 of 16

    +
    + + +

    271 hrs / 500 hrs

    +
    +
    + + + +
    From 9a14e6df3fbf33416a8f20a7b13db18129296537 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Mon, 6 Jul 2026 13:15:03 +0000 Subject: [PATCH 03/92] Fixed current year button position --- app/templates/admin/manageDepartments.html | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 4b726e798..9359ab032 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -64,7 +64,10 @@

    Manage Departments

  • Inactive Departments
  • - +
  • + +
  • +
    From 6a5fec70fce2775c3c866d2394d55d2010e1905d Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Mon, 6 Jul 2026 14:51:40 +0000 Subject: [PATCH 04/92] centered data in the table and div container --- app/static/css/manageDepartments.css | 6 + app/templates/admin/manageDepartments.html | 288 +++++++++++++-------- 2 files changed, 192 insertions(+), 102 deletions(-) diff --git a/app/static/css/manageDepartments.css b/app/static/css/manageDepartments.css index 81fcfb05a..df77648f9 100755 --- a/app/static/css/manageDepartments.css +++ b/app/static/css/manageDepartments.css @@ -34,4 +34,10 @@ h1 { #flasher{ z-index: 999999; +} + +#activeDepartmentsTable th, +#activeDepartmentsTable td { + vertical-align: middle; + text-align: center; } \ No newline at end of file diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 9359ab032..8626bf687 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -1,78 +1,108 @@ -{% extends "base.html" %} - -{% block styles %} -{{super()}} - - -{% endblock %} - -{% block scripts %} -{{super()}} +{% extends "base.html" %} {% block styles %} {{super()}} + + +{% endblock %} {% block scripts %} {{super()}} - - -{% endblock %} - -{% block app_content %} + + +{% endblock %} {% block app_content %}
    - Click to Skip -
    -
    + Click to Skip +
    +

    Manage Departments

    -

    Monitor allocation usage, compliance, and department position needs across - campus.

    +

    + Monitor allocation usage, compliance, and department position needs + across campus. +

    -

    - Position descriptions are up to date.

    -

    Position descriptions are not up to date

    +

    + + Position descriptions are up to date. +

    +

    + + Position descriptions are not up to date +


    -
    +

    -
    - -
    - - -
    -
    - - +
    +
    + + +
    +
    + + +
    -
    -
    +
    -
    - -{% endblock %} \ No newline at end of file +{% endblock %} From 7341b74d6582741d6235f8fca09c824c2acc9893 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Tue, 7 Jul 2026 15:19:40 +0000 Subject: [PATCH 05/92] Fixed actions button: made it similar to the other dropdown menus --- app/templates/admin/manageDepartments.html | 33 ++++++++-------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 8626bf687..bf4d0ffd6 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -155,27 +155,18 @@

    Manage Departments

    271 hrs / 500 hrs

    -
    - - - +
    From 25b6251363e1f5940b7087b718778d28bcfb269c Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Tue, 7 Jul 2026 15:29:07 +0000 Subject: [PATCH 06/92] Finished requested UI changes: renaming some elements --- app/templates/admin/manageDepartments.html | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index bf4d0ffd6..b7614f0ba 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -70,7 +70,7 @@

    Manage Departments

    -
    @@ -108,7 +103,7 @@

    Manage Departments

    Department Status Positions - Allocation approval status + Allocation Approval Status AY 26-27 Break Hours Used/Given Actions From c6c7293d728d3c2a037720c3f392efb292800d11 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Tue, 7 Jul 2026 20:56:49 +0000 Subject: [PATCH 07/92] Added brake hours from the database to the UI --- .../admin_routes/manage_departments.py | 10 +++++++++- app/templates/admin/manageDepartments.html | 16 +++++++++++++--- database/demo_data.py | 15 ++++++++------- database/reset_database.sh | 10 +++++----- 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/app/controllers/admin_routes/manage_departments.py b/app/controllers/admin_routes/manage_departments.py index 6b16877cb..44ef9d938 100644 --- a/app/controllers/admin_routes/manage_departments.py +++ b/app/controllers/admin_routes/manage_departments.py @@ -9,11 +9,13 @@ from app.models.term import * from flask_bootstrap import bootstrap_find_resource from app.models.department import * +from app.models.allocation import * from flask import request, redirect from flask import jsonify from playhouse.shortcuts import model_to_dict from app.logic.tracy import Tracy + @admin.route('/admin/manageDepartments', methods=['GET']) # @login_required def manage_departments(): @@ -34,17 +36,23 @@ def manage_departments(): activeDepartments = Department.select().where(Department.isActive == True) inactiveDepartments = Department.select().where(Department.isActive == False) + allAllocations = Allocation.select() + print("Something",allAllocations, "\n\n\n\n\n","Something") + allSupervisors= Supervisor.select().order_by(Supervisor.LAST_NAME) return render_template( 'admin/manageDepartments.html', title = ("Manage Departments"), activeDepartments = activeDepartments, inactiveDepartments = inactiveDepartments, - allSupervisors = allSupervisors + allSupervisors = allSupervisors, + allAllocations = allAllocations ) except Exception as e: print("Error Loading all Departments", e) return render_template('errors/500.html'), 500 + + @admin.route("/admin/manageDepartments/", methods=['GET']) def getSupervisorsInDepartment(departmentID): currentUser = require_login() diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index b7614f0ba..86d42a6ac 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -110,6 +110,7 @@

    Manage Departments

    {% for department in activeDepartments %} + Manage Departments Approved - -

    271 hrs / 500 hrs

    - + + + + {% for allocation in allAllocations %} + {% if allocation.department == department %} + {{ allocation.breakHours }} + {% endif %} + {% endfor %} + + + diff --git a/database/demo_data.py b/database/demo_data.py index ade1c6379..8ef27c266 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -17,15 +17,12 @@ from app.models.laborStatusForm import LaborStatusForm from app.models.formHistory import FormHistory from app.models.notes import Notes -<<<<<<< HEAD from app.models.supervisorDepartment import SupervisorDepartment -======= from app.models.allocation import Allocation from app.models.positionHistory import PositionHistory from app.models.supervisorDepartment import SupervisorDepartment from app.models.allocation import Allocation ->>>>>>> PeeWeeAttribute print("Inserting data for demo and testing purposes") @@ -472,28 +469,32 @@ "DEPT_NAME": "Computer Science", "ACCOUNT": "6740", "ORG": "2114", - "departmentCompliance": 1 + "departmentCompliance": 1, + "isActive": 1 }, { "departmentID":2, "DEPT_NAME": "Technology and Applied Design", "ACCOUNT": "6740", "ORG": "2147", - "departmentCompliance": 1 + "departmentCompliance": 1, + "isActive": 1 }, { "departmentID":3, "DEPT_NAME": "Mathematics", "ACCOUNT": "6740", "ORG": "2150", - "departmentCompliance": 1 + "departmentCompliance": 1, + "isActive": 1 }, { "departmentID":4, "DEPT_NAME": "Biology", "ACCOUNT": "6740", "ORG": "2107", - "departmentCompliance": 1 + "departmentCompliance": 1, + "isActive": 1 }, { "departmentID":5, diff --git a/database/reset_database.sh b/database/reset_database.sh index 82f6cff53..33283062d 100755 --- a/database/reset_database.sh +++ b/database/reset_database.sh @@ -22,12 +22,12 @@ if [ "$1" == "from-backup" ]; then fi echo "Dropping databases" -mysql -u root -proot --execute="DROP DATABASE \`lsf\`; DROP USER 'lsf_user';" -mysql -u root -proot --execute="DROP DATABASE \`UTE\`; DROP USER 'tracy_user';" +mysql -u root -proot --skip-ssl --execute="DROP DATABASE \`lsf\`; DROP USER 'lsf_user';" +mysql -u root -proot --skip-ssl --execute="DROP DATABASE \`UTE\`; DROP USER 'tracy_user';" 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'@'%';" +mysql -u root -proot --skip-ssl --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 --skip-ssl --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 @@ -38,7 +38,7 @@ rm -rf migrations.json echo "Creating database objects" if [ $BACKUP -eq 1 ]; then echo " from backup" - mysql -u root -proot lsf < prod-backup.sql + mysql -u root -proot --skip-ssl lsf < prod-backup.sql else echo " empty" ./migrate_db.sh From 636dd71ddf6a39584c9dc93e237cc99d244622ce Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Wed, 8 Jul 2026 18:08:21 +0000 Subject: [PATCH 08/92] Fixed brake hours given: removed double loop and filtered by AY --- .../admin_routes/manage_departments.py | 26 ++++++++++++++++--- app/templates/admin/manageDepartments.html | 8 ++---- database/demo_data.py | 5 +++- 3 files changed, 28 insertions(+), 11 deletions(-) diff --git a/app/controllers/admin_routes/manage_departments.py b/app/controllers/admin_routes/manage_departments.py index 44ef9d938..1031b8fc0 100644 --- a/app/controllers/admin_routes/manage_departments.py +++ b/app/controllers/admin_routes/manage_departments.py @@ -10,6 +10,7 @@ from flask_bootstrap import bootstrap_find_resource from app.models.department import * from app.models.allocation import * +from app.models.laborStatusForm import * #Do we need to import all? from flask import request, redirect from flask import jsonify from playhouse.shortcuts import model_to_dict @@ -33,11 +34,28 @@ def manage_departments(): elif currentUser.supervisor: return render_template('errors/403.html'), 403 + #FIXME: hardcoded term year for the testing purposes + currentTerm = "202500" - activeDepartments = Department.select().where(Department.isActive == True) + print("Current term: ", currentTerm) + + + # activeDepartments = Department.select().where(Department.isActive == True) + # allAllocations = Allocation.select().where(Allocation.termCode == currentTerm) inactiveDepartments = Department.select().where(Department.isActive == False) - allAllocations = Allocation.select() - print("Something",allAllocations, "\n\n\n\n\n","Something") + + + activeDepartments = ( + Department + .select(Department, Allocation) + .join(Allocation) + .where( + Department.isActive == True, + Allocation.termCode == currentTerm + ) + + +) allSupervisors= Supervisor.select().order_by(Supervisor.LAST_NAME) return render_template( 'admin/manageDepartments.html', @@ -45,7 +63,7 @@ def manage_departments(): activeDepartments = activeDepartments, inactiveDepartments = inactiveDepartments, allSupervisors = allSupervisors, - allAllocations = allAllocations + currentTerm = currentTerm ) except Exception as e: print("Error Loading all Departments", e) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 86d42a6ac..1b7ec91c1 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -69,7 +69,7 @@

    Manage Departments

    - + @@ -156,7 +159,7 @@

    Manage Departments

    + +
    + + + + + + + + {% for department in inactiveDepartments %} + + + + {% endfor %} + +
    Department
    + {{department.DEPT_NAME}}({{department.ORG}}, + {{department.ACCOUNT}}) +
    -
    +
    +
    - -
    +
    +
    @@ -154,7 +154,7 @@

    Manage Departments

    {{ breakHoursByDepartment.get(department.departmentID, 0) }} / {{ department.allocation.breakHours }} @@ -181,31 +181,31 @@

    Manage Departments

    {% endfor %} - + -
    +
    - - - - - - - {% for department in inactiveDepartments %} - - + + + + + + {% for department in inactiveDepartments %} + + - - {% endfor %} - -
    Department
    +
    Department
    {{department.DEPT_NAME}}({{department.ORG}}, {{department.ACCOUNT}}) -
    + + + {% endfor %} + + +
    - -
    +
    From d77c410cb6c761de7008e21f60257bde64c5a3c6 Mon Sep 17 00:00:00 2001 From: ACBerea Date: Tue, 21 Jul 2026 11:55:39 -0400 Subject: [PATCH 38/92] Improved termManagement.py to all for creating terms via a list based on the selected academic year. Implemented this into manage_departments for all relevent term types. This will ensure that, if a term isn't created already, that it will still exist so that future allocations can be implemented. Added comments for future groups working on this issue and implemented these dates into the HTML. --- .../admin_routes/manage_departments.py | 77 +++++++++++-------- .../admin_routes/termManagement.py | 34 +++++--- app/templates/admin/manageDepartments.html | 23 ++++-- 3 files changed, 86 insertions(+), 48 deletions(-) diff --git a/app/controllers/admin_routes/manage_departments.py b/app/controllers/admin_routes/manage_departments.py index 94fb374cf..0f2dd2993 100644 --- a/app/controllers/admin_routes/manage_departments.py +++ b/app/controllers/admin_routes/manage_departments.py @@ -19,11 +19,11 @@ from datetime import date from flask import g from app.logic.manageDepartments import * # Reorganize imports to avoid circular import issues. This is a temporary fix, but it works for now. - +from app.controllers.admin_routes.termManagement import createTerms @admin.route('/admin/manageDepartments/', methods=['GET']) -@admin.route('/admin/manageDepartments/', methods=['GET']) +@admin.route('/admin/manageDepartments/', methods=['GET']) # FIXME: The default value year should be the current academic year (Rather than waiting to be clicked it should be on the current year by default). # @login_required -def manage_departments(academic_year = None): # FIXME +def manage_departments(academic_year = None): """ Updates the Labor Status Forms database with any new departments in the Tracy database on page load. Returns the departments to be used in the HTML for the manage departments page. @@ -42,54 +42,63 @@ def manage_departments(academic_year = None): # FIXME # Sets academic_year to the current open term if no academic year is provided in the URL. Current solution. WILL change in the future. if not academic_year: academic_year = g.openTerm.termCode - - fall_suffix = 11 # Ex.) Fall 2025 = 202511 - spring_suffix = 12 # Ex.) Spring 2026 = 202512 - summer_suffix = 13 # Ex.) Summer 2026 = 202513 - - currentTerm = Term.get(Term.termCode == academic_year) - previousTerm = Term.get(Term.termCode == academic_year - 100) - nextTerm = Term.get(Term.termCode == academic_year + 100) - - - - # Works. Just use production data to test. Add demo data for this later. (This was a request from Labor Office to have the ability to view based on term.) - # fallTerm = Term.get(Term.termCode == academic_year + fall_suffix) - # springTerm = Term.get(Term.termCode == academic_year + spring_suffix) - # summerTerm = Term.get(Term.termCode == academic_year + summer_suffix) - - # print("******************",fallTerm.termName, springTerm.termName, summerTerm.termName, "**********************") - # print("******************",previousTerm.termName, nextTerm.termName, currentTerm.termName, "**********************") + else: + academic_year = int(academic_year) - # totalBreakSum = getUsedBreakHours(currentTerm) + currentAY = Term.get(Term.termCode == academic_year) + print("Current Term:", currentAY.termName) - # print("Something\n\n") + previousAcademicYear = (academic_year - 100) // 100 + createPreviousAY = createTerms(previousAcademicYear) + previousAY = createPreviousAY[0] + print("Previous Term:", previousAY.termName) + nextAcademicYear = (academic_year + 100) // 100 + createNextAY = createTerms(nextAcademicYear) + nextAY = createNextAY[0] + print("Next Term:", nextAY.termName) + # Works. Should work without production data now. + # We've also thought about having a drop down menu to select the term once the academic year is selected. This should also include the ability to view the entire academic year. + # Given the new implementation of the term management page, we can now use the term management page to create terms for the academic year and then use this page to view the departments for that academic year. This will be a much more efficient way to manage the terms and departments. + # A concept of Currently Selected Term does not exist, yet. Implementing it here will make it so that the user can select a term and then view the departments for that term. This will be a much more efficient way to manage the terms and departments. + plainAcademicYear = academic_year // 100 # Might be a good idea to create a function for // 100 since it appears in multiple places. This will make it easier to change the implementation in the future if needed. + createdTerms = createTerms(plainAcademicYear) #FIXME: Use the selected academic year to create the terms for that year. This will be a much more efficient way to manage the terms and departments. + fallTerm = createdTerms[1] + springTerm = createdTerms[4] + summerTerm = createdTerms[6] + + print("******************",fallTerm.termName, springTerm.termName, summerTerm.termName, "**********************") + print("******************",previousAY.termName, nextAY.termName, currentAY.termName, "**********************") + + # For Testing Purposes. This will be removed once the term management page is fully implemented and the terms are created for the academic year. + # totalBreakSum = getUsedBreakHours(currentAY) # for row in totalBreakSum: # print(row['department'],int(row['totalHours']),row['termCode']) # print(totalBreakSum) - breakHoursByDepartment = {row["department"]: str(row["totalHours"] if row["totalHours"] is not None else 0) for row in getUsedBreakHours(currentTerm)} # I think Scott wanted this to say NULL not zero, unsure. + breakHoursByDepartment = {row["department"]: str(row["totalHours"] if row["totalHours"] is not None else 0) for row in getUsedBreakHours(currentAY)} # I think Scott wanted this to say NULL not zero, unsure. # print(breakHoursByDepartment) # print("\n\nSomething") + # This was left just incase anything went wrong. Delete this if everything works as expected. Not nessicary in current implementation. # activeDepartments = Department.select().where(Department.isActive == True) - # allAllocations = Allocation.select().where(Allocation.termCode == currentTerm) + # allAllocations = Allocation.select().where(Allocation.termCode == currentAY) + inactiveDepartments = Department.select().where(Department.isActive == False) - activeDepartments = getActiveDepartmentsWithAllocation(currentTerm) + activeDepartments = getActiveDepartmentsWithAllocation(currentAY) - # Move some of this to Logic (maybe). + # Move some of this to Logic. 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) - lsfCountPrimaries = getLSFCountPrimaries(currentTerm, dept) - lsfCountSecondaries = getLSFCountSecondaries(currentTerm, dept) + lsfCountPrimaries = getLSFCountPrimaries(currentAY, dept) + lsfCountSecondaries = getLSFCountSecondaries(currentAY, dept) dept.lsfCountPrimaries = lsfCountPrimaries dept.lsfCountSecondaries = lsfCountSecondaries # print("######################") @@ -98,7 +107,7 @@ def manage_departments(academic_year = None): # FIXME # print([lsf.formID for lsf in lsfCountSecondaries]) allocationStatus = { - department.departmentID: getAllocationStatus(currentTerm, department) + department.departmentID: getAllocationStatus(currentAY, department) for department in activeDepartments } @@ -114,10 +123,10 @@ def manage_departments(academic_year = None): # FIXME activeDepartments = activeDepartments, inactiveDepartments = inactiveDepartments, allSupervisors = allSupervisors, - currentTerm = currentTerm.termName, - previousTerm = previousTerm.termName, - nextTerm = nextTerm.termName, - academicYear = currentTerm.termName, + currentAY = currentAY.termName, + previousAY = previousAY.termName, + nextAY = nextAY.termName, + academicYear = currentAY.termName, # totalBreakSum = totalBreakSum breakHoursByDepartment = breakHoursByDepartment, allocationStatus = allocationStatus diff --git a/app/controllers/admin_routes/termManagement.py b/app/controllers/admin_routes/termManagement.py index 8a17d169f..30e7673f1 100644 --- a/app/controllers/admin_routes/termManagement.py +++ b/app/controllers/admin_routes/termManagement.py @@ -37,26 +37,42 @@ def createTerms(termYear): This function creates the terms for the given Academic Year """ code = termYear * 100 + createdTerms = [] for i in range(8): try: if i == 0: - Term.create(termCode = code, termName = "AY {}-{}".format(termYear, termYear + 1), isAcademicYear=True) + term = Term.create(termCode = code, termName = "AY {}-{}".format(termYear, termYear + 1), isAcademicYear=True) elif i == 1: - Term.create(termCode = (code + 11), termName = "Fall {}".format(termYear)) + term = Term.create(termCode = (code + 11), termName = "Fall {}".format(termYear)) elif i == 7: - Term.create(termCode = (code + 4), termName = "Fall Break {}".format(termYear), isBreak=True) + term = Term.create(termCode = (code + 4), termName = "Fall Break {}".format(termYear), isBreak=True) elif i == 2: - Term.create(termCode = (code + 1), termName = "Thanksgiving Break {}".format(termYear), isBreak=True) + term = Term.create(termCode = (code + 1), termName = "Thanksgiving Break {}".format(termYear), isBreak=True) elif i == 3: - Term.create(termCode = (code + 2), termName = "Christmas Break {}".format( termYear), isBreak=True) + term = Term.create(termCode = (code + 2), termName = "Christmas Break {}".format( termYear), isBreak=True) elif i == 4: - Term.create(termCode = (code + 12), termName = "Spring {}".format(termYear + 1)) + term = Term.create(termCode = (code + 12), termName = "Spring {}".format(termYear + 1)) elif i == 5: - Term.create(termCode = (code + 3), termName = "Spring Break {}".format(termYear + 1), isBreak=True) + term = Term.create(termCode = (code + 3), termName = "Spring Break {}".format(termYear + 1), isBreak=True) elif i == 6: - Term.create(termCode = (code + 13), termName = "Summer {}".format(termYear + 1), isBreak=True, isSummer=True) + term = Term.create(termCode = (code + 13), termName = "Summer {}".format(termYear + 1), isBreak=True, isSummer=True) except IntegrityError as e: - pass + termCodeMap = { + 0: code, + 1: code + 11, + 2: code + 1, + 3: code + 2, + 4: code + 12, + 5: code + 3, + 6: code + 13, + 7: code + 4, + } + term = Term.get_or_none(Term.termCode == termCodeMap[i]) + + if term is not None: + createdTerms.append(term) + + return createdTerms @admin.route("/termManagement/setDate/", methods=['POST']) def ourDate(): diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 48e3f5e68..ba0f1e0a6 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -57,20 +57,33 @@

    Manage Departments

    Download Allocation History - + +
    From bd5c4e6d8c30498c4f8547b55ed5a660cffd0dcb Mon Sep 17 00:00:00 2001 From: kurasova Date: Tue, 21 Jul 2026 15:56:25 -0400 Subject: [PATCH 39/92] Moved some of the code into logic + replaced eval() with get_json() in the complianceStatusCheck function for safety --- .../admin_routes/manage_departments.py | 63 ++++++------------- app/logic/manageDepartments.py | 40 ++++++++++-- 2 files changed, 52 insertions(+), 51 deletions(-) diff --git a/app/controllers/admin_routes/manage_departments.py b/app/controllers/admin_routes/manage_departments.py index 0f2dd2993..b59a5409c 100644 --- a/app/controllers/admin_routes/manage_departments.py +++ b/app/controllers/admin_routes/manage_departments.py @@ -31,30 +31,30 @@ def manage_departments(academic_year = None): print ("######################", g.openTerm.termName, "######################") try: currentUser = require_login() - if not currentUser: # Not logged in + if not currentUser: # If the current user is not logged in return render_template('errors/403.html') - if not currentUser.isLaborAdmin: # Not an admin - if currentUser.student: # logged in as a student + if not currentUser.isLaborAdmin: # If the currrent user is not an admin + if currentUser.student: # If the currrent user is logged in as a student return redirect('/laborHistory/' + currentUser.student.ID) elif currentUser.supervisor: return render_template('errors/403.html'), 403 - # Sets academic_year to the current open term if no academic year is provided in the URL. Current solution. WILL change in the future. - if not academic_year: - academic_year = g.openTerm.termCode - else: + + if academic_year: # If there is an academic year (term code) in the URL academic_year = int(academic_year) + else: + academic_year = g.openTerm.termCode + # Sets academic_year to the current open term if no academic year is provided in the URL. + # Current solution. WILL change in the future. currentAY = Term.get(Term.termCode == academic_year) print("Current Term:", currentAY.termName) - previousAcademicYear = (academic_year - 100) // 100 - createPreviousAY = createTerms(previousAcademicYear) + createPreviousAY = generateTerms(academic_year - 100) previousAY = createPreviousAY[0] print("Previous Term:", previousAY.termName) - nextAcademicYear = (academic_year + 100) // 100 - createNextAY = createTerms(nextAcademicYear) + createNextAY = generateTerms(academic_year + 100) nextAY = createNextAY[0] print("Next Term:", nextAY.termName) @@ -62,8 +62,8 @@ def manage_departments(academic_year = None): # We've also thought about having a drop down menu to select the term once the academic year is selected. This should also include the ability to view the entire academic year. # Given the new implementation of the term management page, we can now use the term management page to create terms for the academic year and then use this page to view the departments for that academic year. This will be a much more efficient way to manage the terms and departments. # A concept of Currently Selected Term does not exist, yet. Implementing it here will make it so that the user can select a term and then view the departments for that term. This will be a much more efficient way to manage the terms and departments. - plainAcademicYear = academic_year // 100 # Might be a good idea to create a function for // 100 since it appears in multiple places. This will make it easier to change the implementation in the future if needed. - createdTerms = createTerms(plainAcademicYear) #FIXME: Use the selected academic year to create the terms for that year. This will be a much more efficient way to manage the terms and departments. + + createdTerms = createTerms(academic_year) #FIXME: Use the selected academic year to create the terms for that year. This will be a much more efficient way to manage the terms and departments. fallTerm = createdTerms[1] springTerm = createdTerms[4] summerTerm = createdTerms[6] @@ -77,49 +77,22 @@ def manage_departments(academic_year = None): # print(row['department'],int(row['totalHours']),row['termCode']) # print(totalBreakSum) - breakHoursByDepartment = {row["department"]: str(row["totalHours"] if row["totalHours"] is not None else 0) for row in getUsedBreakHours(currentAY)} # I think Scott wanted this to say NULL not zero, unsure. + breakHoursByDepartment = {row["department"]: str(row["totalHours"] if row["totalHours"] is not None else 0) for row in getUsedBreakHours(currentAY)} + # I think Scott wanted this to say NULL not zero, unsure. - # print(breakHoursByDepartment) - # print("\n\nSomething") - - # This was left just incase anything went wrong. Delete this if everything works as expected. Not nessicary in current implementation. - # activeDepartments = Department.select().where(Department.isActive == True) - # allAllocations = Allocation.select().where(Allocation.termCode == currentAY) - - inactiveDepartments = Department.select().where(Department.isActive == False) - - activeDepartments = getActiveDepartmentsWithAllocation(currentAY) + inactiveDepartments = Department.select().where(Department.isActive == False) - # Move some of this to Logic. - 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) - - lsfCountPrimaries = getLSFCountPrimaries(currentAY, dept) - lsfCountSecondaries = getLSFCountSecondaries(currentAY, dept) - dept.lsfCountPrimaries = lsfCountPrimaries - dept.lsfCountSecondaries = lsfCountSecondaries - # print("######################") - # print(f"COUNTS: {lsfCountSecondaries} ") - # print([lsf.formID for lsf in lsfCountPrimaries]) - # print([lsf.formID for lsf in lsfCountSecondaries]) allocationStatus = { department.departmentID: getAllocationStatus(currentAY, department) for department in activeDepartments } - # print("Pizza\n\n\n\n") - # print ("Allocation Status:", allocationStatus) - # print("\n\n\n\nPotato") - - - allSupervisors= Supervisor.select().order_by(Supervisor.LAST_NAME) return render_template( 'admin/manageDepartments.html', - title = ("Manage Departments"), + #title = ("Manage Departments"), activeDepartments = activeDepartments, inactiveDepartments = inactiveDepartments, allSupervisors = allSupervisors, @@ -183,7 +156,7 @@ def complianceStatusCheck(): This function changes the compliance status in the database for labor status forms. It works in collaboration with the ajax call in manageDepartments.js """ try: - rsp = eval(request.data.decode("utf-8")) # This fixes byte indices must be intergers or slices error + rsp = request.get_json() # This fixes byte indices must be intergers or slices error if rsp: department = Department.get(int(rsp['deptName'])) department.departmentCompliance = not department.departmentCompliance diff --git a/app/logic/manageDepartments.py b/app/logic/manageDepartments.py index b16a7ab0b..17e49de2d 100644 --- a/app/logic/manageDepartments.py +++ b/app/logic/manageDepartments.py @@ -1,3 +1,4 @@ +from app.controllers.admin_routes.termManagement import createTerms from app.models.laborStatusForm import * from app.models.formHistory import * from app.models.allocation import * @@ -46,19 +47,37 @@ def getUsedBreakHours(term): return totalBreakSum -def getActiveDepartmentsWithAllocation(currentTerm): +def getActiveDepartmentsWithAllocation(term): """ - Returns a list of active departments that have an allocation for the given term. + Returns a list of active departments with allocations for the given term. """ - activeDep = (Department + + # This was left just incase anything went wrong. Delete this if everything works as expected. Not necessary in current implementation. + # activeDepartments = Department.select().where(Department.isActive == True) + # allAllocations = Allocation.select().where(Allocation.termCode == currentAY) + + activeDepartments = (Department .select(Department, Allocation) .join(Allocation) .where( Department.isActive == True, - Allocation.termCode == currentTerm.termCode + Allocation.termCode == term.termCode ) ) - return activeDep + + 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) + + dept.lsfCountPrimaries = getLSFCountPrimaries(term, dept) + dept.lsfCountSecondaries = getLSFCountSecondaries(term, dept) + + # # print("######################") + # print(f"COUNTS: {lsfCountSecondaries} ") + # print([lsf.formID for lsf in lsfCountPrimaries]) + # print([lsf.formID for lsf in lsfCountSecondaries]) + + return activeDepartments def getAllocationStatus(term, department): """ @@ -93,4 +112,13 @@ def getLSFCountSecondaries(currentTerm, department): #Returns the current term code based on a the selected term from a dropdown menu in the manage departments page. #Should only contain the current term, the next term, and the previous term. #''' - \ No newline at end of file + +def generateTerms(termCode): + """ + Generates all the terms in an academic year. + """ + + # Truncating term codes to hundreds. That's how we get the academic year. + academicYearCode = (termCode // 100) + + return createTerms(academicYearCode) \ No newline at end of file From f03cc59920b671f490d991ee88c5103f6edb26f3 Mon Sep 17 00:00:00 2001 From: kurasova Date: Tue, 21 Jul 2026 17:24:47 -0400 Subject: [PATCH 40/92] Users can now choose the previous and the following years --- .../admin_routes/manage_departments.py | 41 +++++++++++-------- app/templates/admin/manageDepartments.html | 8 ++-- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/app/controllers/admin_routes/manage_departments.py b/app/controllers/admin_routes/manage_departments.py index b59a5409c..535471aff 100644 --- a/app/controllers/admin_routes/manage_departments.py +++ b/app/controllers/admin_routes/manage_departments.py @@ -38,32 +38,39 @@ def manage_departments(academic_year = None): return redirect('/laborHistory/' + currentUser.student.ID) elif currentUser.supervisor: return render_template('errors/403.html'), 403 - - if academic_year: # If there is an academic year (term code) in the URL - academic_year = int(academic_year) - else: + if academic_year == None: academic_year = g.openTerm.termCode - # Sets academic_year to the current open term if no academic year is provided in the URL. - # Current solution. WILL change in the future. - currentAY = Term.get(Term.termCode == academic_year) + academic_year = int(academic_year) + + if (academic_year != g.openTerm.termCode - 100) and (academic_year != g.openTerm.termCode) and (academic_year != g.openTerm.termCode + 100): + return "", 400 + + createdTerms = generateTerms(g.openTerm.termCode) #FIXME: Use the selected academic year to create the terms for that year. This will be a much more efficient way to manage the terms and departments. + currentAY = Term.get(Term.termCode == g.openTerm.termCode) print("Current Term:", currentAY.termName) - createPreviousAY = generateTerms(academic_year - 100) + createPreviousAY = generateTerms(g.openTerm.termCode - 100) previousAY = createPreviousAY[0] print("Previous Term:", previousAY.termName) - createNextAY = generateTerms(academic_year + 100) + createNextAY = generateTerms(g.openTerm.termCode + 100) nextAY = createNextAY[0] print("Next Term:", nextAY.termName) + + if academic_year == g.openTerm.termCode - 100: + usedAY = previousAY + elif academic_year == g.openTerm.termCode: + usedAY = currentAY + else: + usedAY = nextAY # Works. Should work without production data now. # We've also thought about having a drop down menu to select the term once the academic year is selected. This should also include the ability to view the entire academic year. # Given the new implementation of the term management page, we can now use the term management page to create terms for the academic year and then use this page to view the departments for that academic year. This will be a much more efficient way to manage the terms and departments. # A concept of Currently Selected Term does not exist, yet. Implementing it here will make it so that the user can select a term and then view the departments for that term. This will be a much more efficient way to manage the terms and departments. - createdTerms = createTerms(academic_year) #FIXME: Use the selected academic year to create the terms for that year. This will be a much more efficient way to manage the terms and departments. fallTerm = createdTerms[1] springTerm = createdTerms[4] summerTerm = createdTerms[6] @@ -77,16 +84,16 @@ def manage_departments(academic_year = None): # print(row['department'],int(row['totalHours']),row['termCode']) # print(totalBreakSum) - breakHoursByDepartment = {row["department"]: str(row["totalHours"] if row["totalHours"] is not None else 0) for row in getUsedBreakHours(currentAY)} + breakHoursByDepartment = {row["department"]: str(row["totalHours"] if row["totalHours"] is not None else 0) for row in getUsedBreakHours(usedAY)} # I think Scott wanted this to say NULL not zero, unsure. - activeDepartments = getActiveDepartmentsWithAllocation(currentAY) + activeDepartments = getActiveDepartmentsWithAllocation(usedAY) inactiveDepartments = Department.select().where(Department.isActive == False) allocationStatus = { - department.departmentID: getAllocationStatus(currentAY, department) + department.departmentID: getAllocationStatus(usedAY, department) for department in activeDepartments } @@ -96,10 +103,10 @@ def manage_departments(academic_year = None): activeDepartments = activeDepartments, inactiveDepartments = inactiveDepartments, allSupervisors = allSupervisors, - currentAY = currentAY.termName, - previousAY = previousAY.termName, - nextAY = nextAY.termName, - academicYear = currentAY.termName, + currentAY = currentAY, + previousAY = previousAY, + nextAY = nextAY, + academicYear = usedAY.termName, # totalBreakSum = totalBreakSum breakHoursByDepartment = breakHoursByDepartment, allocationStatus = allocationStatus diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index ba0f1e0a6..4b9b8636e 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -64,22 +64,22 @@

    Manage Departments

    -

    - - -
    -
    - -
    - - +
    +
    + +
    - - -
    -
    + +
    -
    + +
    -
    + +
    + + + + + + + + +
    @@ -152,7 +180,8 @@

    Manage Departments

    Prim: {{department.lsfCountPrimaries}} of {{department.totalPrimaries}}

    -

    Sec: {{department.lsfCountSecondaries}} of {{department.totalSecondaries}} +

    Sec: {{department.lsfCountSecondaries}} of + {{department.totalSecondaries}}

    @@ -162,7 +191,7 @@

    Manage Departments

    style="pointer-events: none; cursor: default;"> {{ "Approved" if allocationStatus.get(department.departmentID) else "Pending" }} - + + \ No newline at end of file diff --git a/app/templates/snips/annualPositionReview.html b/app/templates/snips/annualPositionReview.html new file mode 100644 index 000000000..af63ed33a --- /dev/null +++ b/app/templates/snips/annualPositionReview.html @@ -0,0 +1,22 @@ + + \ No newline at end of file From 66fa1496f2ec9c0a692af64d0c16056be3e49a14 Mon Sep 17 00:00:00 2001 From: kurasova Date: Mon, 27 Jul 2026 17:22:43 -0400 Subject: [PATCH 47/92] Added the Term dropdown + Made a lot of changes to the look of the Manage Departments page --- app/static/css/manageDepartments.css | 2 +- app/templates/admin/manageDepartments.html | 99 ++++++++++++++-------- 2 files changed, 65 insertions(+), 36 deletions(-) diff --git a/app/static/css/manageDepartments.css b/app/static/css/manageDepartments.css index df77648f9..c76d513f9 100755 --- a/app/static/css/manageDepartments.css +++ b/app/static/css/manageDepartments.css @@ -29,7 +29,7 @@ h1 { width:20px; } .complianceBtn{ - width:150px; + width:140px; } #flasher{ diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 2a2eb21ef..feecadd84 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -47,47 +47,72 @@

    Manage Departments

    {% include "snips/annualAllocationReview.html" %} {% include "snips/annualPositionReview.html" %} - - + +
    - -
    - -
    @@ -134,7 +159,7 @@

    Manage Departments

    -
    +
    diff --git a/app/templates/snips/allocationReview.html b/app/templates/snips/allocationReview.html deleted file mode 100644 index e3d4cb221..000000000 --- a/app/templates/snips/allocationReview.html +++ /dev/null @@ -1,94 +0,0 @@ - - - \ No newline at end of file diff --git a/tests/code/test_manageDepartments.py b/tests/code/test_manageDepartments.py index 09e06e3c1..ca227091a 100644 --- a/tests/code/test_manageDepartments.py +++ b/tests/code/test_manageDepartments.py @@ -30,12 +30,14 @@ def test_generateAdjacentYears(): with app.app_context(): with mainDB.atomic() as transaction: - # THE FIRST TEST + ################ THE FIRST TEST ################ + ################ TESTING WHETHER THE generateAdjacentYear() FUNCTION WORKS AT ALL g.openTerm, _ = Term.get_or_create( termCode = 202500, defaults={"termName": "AY 2025-2026", "isAcademicYear": True} ) + # currentYear, previousYear, followingYear = generateAdjacentYears(202500) assert currentYear.termCode == 202500 @@ -47,10 +49,9 @@ def test_generateAdjacentYears(): assert followingYear.termCode == 202600 assert followingYear.termName == "AY 2026-2027" - transaction.rollback() - - # THE SECOND TEST + ################ THE SECOND TEST ################ + ######### TESTING VARIOUS EDGE CASES ############ with pytest.raises(BadRequest): generateAdjacentYears(202300) transaction.rollback() @@ -76,12 +77,14 @@ def test_generateAdjacentYears(): transaction.rollback() - #THE THIRD TEST + ################ THE THIRD TEST ################ + ############# MISCELLANEOUS TESTS ############# g.openTerm, _ = Term.get_or_create( termCode = 198200, defaults={"termName": "AY 1982-1983", "isAcademicYear": True} ) + # Testing different years currentYear, previousYear, followingYear = generateAdjacentYears(198200) assert currentYear.termCode == 198200 @@ -93,10 +96,12 @@ def test_generateAdjacentYears(): assert followingYear.termCode == 198300 assert followingYear.termName == "AY 1983-1984" + # Testing data types assert isinstance(currentYear.termCode, int) assert isinstance(previousYear.termCode, int) assert isinstance(followingYear.termCode, int) - + + # Testing whether currentYear.termName is formatted correctly assert currentYear.termName.split(" ")[0] == "AY" assert previousYear.termName.split(" ")[0] == "AY" assert followingYear.termName.split(" ")[0] == "AY" @@ -105,85 +110,12 @@ def test_generateAdjacentYears(): assert previousYear.termName.split(" ")[1] == "1981-1982" assert followingYear.termName.split(" ")[1] == "1983-1984" - transaction.rollback() - - - - - -# @pytest.mark.integration -# def test_ManageDepartmentsPrimaryandSecondary(): -# with mainDB.atomic() as transaction: - -# assert True - -# testingDept = Department.get_or_create(DEPT_NAME="Computer Science", ACCOUNT="6740", ORG="2114") -# testingTerm = Term.get_or_create( -# termCode=f"{2028}00", -# termName=f"AY {2028}-{2029}", -# termStart=f"{2028}-08-01", -# termEnd=f"{2029}-05-01", -# termState=0, -# primaryCutOff=f"{2028}-09-01", -# adjustmentCutOff=f"{2029}-10-01" -# ) -# # Might need an additional allocation wherer isFinal is True for testing purposes. -# testingAllocation = Allocation.get_or_create( -# termCode=testingTerm.termCode, -# department=testingDept.departmentID, -# 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, -# ) -# # Might need to create different test data for different lsf statuses. -# testingLSF = LaborStatusForm.get_or_create( -# laborStatusFormID=2, -# termCode_id=testingTerm.termCode, -# studentName="Alex Bryant", -# studentSupervisee_id="B00841417", -# supervisor_id="B12361006", -# department_id=testingDept.departmentID, -# jobType="Primary", -# WLS=1, -# POSN_TITLE="Student Programmer", -# POSN_CODE="S61407", -# weeklyHours=10, -# startDate=f"2028-04-01", -# endDate=f"2029-09-01", -# studentConfirmation=True -# ) -# # Might need to create different test data for different form statuses. -# testingFormHistory = FormHistory.get_or_create( -# formHistoryID=2, -# formID_id="2", -# historyType_id="Labor Status Form", -# createdBy_id=1, -# createdDate=f"2025-04-14", -# status="Active" -# ) - -# assert True - -# # transaction.rollback() - -# testCreation = {"", -# "",} - -# testReset = {"resetConfirmation": True} - -# with app.test_request_context( "/manage_departments", method="POST", data=testCreation): -# app.config['WTF_CSRF_ENABLED'] = False -# app.config['show_queries'] = False - - -# with app.test_request_context( "/manage_departments", method="POST", data=testReset): -# app.config['WTF_CSRF_ENABLED'] = False -# app.config['show_queries'] = False \ No newline at end of file + + # Testing the generateAdjacentYears() function without any parameters + currentYear, previousYear, followingYear = generateAdjacentYears() + + assert currentYear.termCode == 198200 + assert previousYear.termCode == 198100 + assert followingYear.termCode == 198300 + + transaction.rollback() \ No newline at end of file From 226d3ff08f8b681c339a657185bbb854b892d491 Mon Sep 17 00:00:00 2001 From: kurasova Date: Thu, 30 Jul 2026 11:47:46 -0400 Subject: [PATCH 56/92] Moved the styles and js code for the Allocation Review page into their own files + Cleaned up the manageDepartments.js file --- app/static/css/allocationReview.css | 52 ++++++++++++++++++++ app/static/css/manageDepartments.css | 57 --------------------- app/static/js/allocationReview.js | 6 +++ app/static/js/manageDepartments.js | 60 ++++------------------- app/templates/admin/allocationReview.html | 6 +-- 5 files changed, 69 insertions(+), 112 deletions(-) create mode 100644 app/static/css/allocationReview.css create mode 100644 app/static/js/allocationReview.js diff --git a/app/static/css/allocationReview.css b/app/static/css/allocationReview.css new file mode 100644 index 000000000..2d72c7067 --- /dev/null +++ b/app/static/css/allocationReview.css @@ -0,0 +1,52 @@ +@media(min-width:970px) and (max-width:1240px) { + .container { + width: 80%; + } +} + +@media(min-width:1240px) and (max-width:1800px) { + .container { + width: 55%; + } +} + +@media(min-width:1800px) { + .container { + width: 40%; + } +} + +#allocationReviewSubtitle{ + margin-bottom: 30px; +} + +#breakHours { + margin-top: 0px; +} + +.numericSpinner { + width: 55px; +} + +#requestedPositions { + margin-top: 30px; + margin-bottom: -10px; +} + +.noBorders { + border: none !important; +} + + +#allocationJustification { + margin-top: 30px; +} + +.unresizeable { + resize: none; +} + +#allocationReviewNote { + max-width:70%; + color: grey; +} \ No newline at end of file diff --git a/app/static/css/manageDepartments.css b/app/static/css/manageDepartments.css index 7ac305886..2d2a2f51f 100755 --- a/app/static/css/manageDepartments.css +++ b/app/static/css/manageDepartments.css @@ -1,5 +1,3 @@ -/* Styles for the Manage Departments page */ - h1 { text-align: center; padding-bottom: 5px; @@ -31,59 +29,4 @@ h1 { #activeDepartmentsTable td { vertical-align: middle; text-align: center; -} - -/* Styles for the allocationReview.html file (Allocation Review Modal) */ - -@media(min-width:970px) and (max-width:1240px) { - .container { - width: 80%; - } -} - -@media(min-width:1240px) and (max-width:1800px) { - .container { - width: 55%; - } -} - -@media(min-width:1800px) { - .container { - width: 40%; - } -} - -#allocationReviewSubtitle{ - margin-bottom: 30px; -} - -#breakHours { - margin-top: 0px; -} - -.numericSpinner { - width: 55px; -} - -#requestedPositions { - margin-top: 30px; - margin-bottom: -10px; -} - -.noBorders { - border: none !important; -} - - -#allocationJustification { - margin-top: 30px; -} - -.unresizeable { - resize: none; -} - -#allocationReviewNote { - max-width:70%; - color: grey; } \ No newline at end of file diff --git a/app/static/js/allocationReview.js b/app/static/js/allocationReview.js new file mode 100644 index 000000000..49c14f105 --- /dev/null +++ b/app/static/js/allocationReview.js @@ -0,0 +1,6 @@ +$(document).ready( function(){ + // not allowing users to type anything in a numeric spinner + $("input[type='number'].numericSpinner").keypress(function (evt) { + evt.preventDefault(); + }); +}); \ No newline at end of file diff --git a/app/static/js/manageDepartments.js b/app/static/js/manageDepartments.js index 44eb9e21a..add7a8d62 100755 --- a/app/static/js/manageDepartments.js +++ b/app/static/js/manageDepartments.js @@ -1,6 +1,8 @@ // Opens collapse menu for this page $("#admin").collapse("show"); + + $(document).ready( function(){ activeDepartmentsTable = $('#activeDepartmentsTable'); activeDepartmentsTable.DataTable({ @@ -24,6 +26,7 @@ $(document).ready( function(){ }); $("#inactiveTable").hide(); + $("#activeTab").on("click", function() { $("#activeTab").addClass("active"); $("#activeTable").show(); @@ -31,6 +34,7 @@ $(document).ready( function(){ $("#inactiveTable").hide(); }) + $("#inactiveTab").on("click", function() { $("#activeTab").removeClass("active"); $("#activeTable").hide(); @@ -38,21 +42,20 @@ $(document).ready( function(){ $("#inactiveTable").show(); }) + attachModalToDepartment() $('.deptTable').on('draw.dt', function() { attachModalToDepartment() }) + + $('#manageDepartmentSupervisorModal').on('hidden.bs.modal', function() { clearDropdowns() }) - - // not allowing users to type anything in a numeric spinner - $("input[type='number'].numericSpinner").keypress(function (evt) { - evt.preventDefault(); - }); }); + function attachModalToDepartment() { $('.deptTable .departmentName').off('click') $('.deptTable .departmentName').on('click', function() { @@ -65,6 +68,7 @@ function attachModalToDepartment() { } + $("#supervisorModalSelect").on('change', function() { let supervisorID = $('#supervisorModalSelect :selected').val() let departmentID = $('#departmentModalSelect').data('department-id') @@ -74,52 +78,6 @@ $("#supervisorModalSelect").on('change', function() { -function showSupervisorsInDepartment(departmentID) { - $.ajax({ - method: "GET", - url: `/admin/manageDepartments/${departmentID}`, - success: function(supervisors) { - let supervisorContent = '
    ' - for (let i=0; i -
    ${supervisors[i]['ID']} ${supervisorFirstName} ${supervisors[i]['LAST_NAME']}
    -
    Remove
    - `)} - supervisorContent += ("
    ") - $('#manageSupervisorContent .modal-body .changing-content').replaceWith(supervisorContent) - - $('#manageDepartmentSupervisorModal').modal('show') - $('.removeSupervisorFromDepartment').on('click', removeSupervisorFromDepartment) - } - }) - } - -function removeSupervisorFromDepartment () { - let departmentID = $(`#${this.id}`).data('department') - let supervisorID = $(`#${this.id}`).data('supervisor') - let data = {"supervisorID": supervisorID, "departmentID": departmentID} - $.ajax({ - method: "POST", - url: "/admin/manageDepartments/removeSupervisorFromDepartment", - data: data, - success: function(response) { - if (response == "True") { - msgFlash("Supervisor has been removed from department.", 'success') - showSupervisorsInDepartment(departmentID) - } else { - msgFlash("Supervisor is not a member of this department.", "warning") - } - }, - error: function() { - msgFlash("Failed to remove supervisor, please try again.", "fail") - }, -}) -} - function status(department, dept_name) { /* POSTs the compliance status change for the department. Updates UI with correct button and feedback to user. diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index 1b770290d..2ee5428e3 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -1,12 +1,10 @@ {% extends "base.html" %} {% block styles %} {{super()}} - + {% endblock %} {% block scripts %} {{super()}} - + src="{{url_for('static', filename='js/allocationReview.js') }}?u={{lastStaticUpdate}}"> {% endblock %} {% block app_content %}

    From 4c6c659c85d59f5031bdf74211399f2828c7123b Mon Sep 17 00:00:00 2001 From: kurasova Date: Thu, 30 Jul 2026 11:59:37 -0400 Subject: [PATCH 57/92] Made changes in accordance with Bhushan's comments --- app/logic/manageDepartments.py | 2 -- tests/code/test_manageDepartments.py | 8 -------- 2 files changed, 10 deletions(-) diff --git a/app/logic/manageDepartments.py b/app/logic/manageDepartments.py index aa0ba9322..41e96bd50 100644 --- a/app/logic/manageDepartments.py +++ b/app/logic/manageDepartments.py @@ -91,8 +91,6 @@ def getUsedBreakHours(term): ) .group_by(LaborStatusForm.department, LaborStatusForm.termCode).dicts() ) - - print(list(totalBreakSum)) return totalBreakSum diff --git a/tests/code/test_manageDepartments.py b/tests/code/test_manageDepartments.py index ca227091a..77cb2ee38 100644 --- a/tests/code/test_manageDepartments.py +++ b/tests/code/test_manageDepartments.py @@ -1,21 +1,13 @@ import pytest import json - from werkzeug.exceptions import BadRequest -from unittest.mock import patch from flask import g from flask_wtf.csrf import CSRFProtect from app import app - from app.models import mainDB -from app.models.laborStatusForm import LaborStatusForm -from app.models.department import Department from app.models.term import Term -from app.models.formHistory import FormHistory -from app.models.allocation import Allocation - from app.controllers.admin_routes import manageDepartments from app.logic.manageDepartments import * From 2184f5d2aa40608bde275632c19e54c8a1ec4f3c Mon Sep 17 00:00:00 2001 From: kurasova Date: Thu, 30 Jul 2026 13:54:06 -0400 Subject: [PATCH 58/92] Added a popup for the Upload Allocations button + Fixed some of the language --- app/templates/admin/allocationReview.html | 15 ++++++------- app/templates/admin/manageDepartments.html | 19 +++++++++++----- .../snips/annualAllocationReview.html | 2 +- app/templates/snips/annualPositionReview.html | 2 +- app/templates/snips/uploadAllocations.html | 22 +++++++++++++++++++ 5 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 app/templates/snips/uploadAllocations.html diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index 2ee5428e3..981c703ca 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -33,9 +33,8 @@

    - requesting - - (currently: 120) + + (requested: 120)

    @@ -53,19 +52,19 @@

    10 hours:  -  (currently: 120) +  (requested: 120)

    12 hours:  -  (currently: 120) +  (requested: 120)

    15 hours:  -  (currently: 120) +  (requested: 120)

    @@ -78,13 +77,13 @@

    5 hours:    -  (currently: 120) +  (requested: 120)

    10 hours:  -  (currently: 120) +  (requested: 120)

    diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index f629b9beb..7708ededc 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -23,6 +23,8 @@

    Manage Departments

    across campus.

    +
    +

    - Position descriptions are not up to date + Position descriptions are not up to date.


    @@ -44,10 +46,13 @@

    Manage Departments


    - {% include "snips/annualAllocationReview.html" %} - {% include "snips/annualPositionReview.html" %} - - + {% include "snips/uploadAllocations.html" %} + +
    @@ -75,11 +80,13 @@

    Manage Departments

    - +
    + {% include "snips/annualAllocationReview.html" %} + {% include "snips/annualPositionReview.html" %} diff --git a/app/templates/snips/annualAllocationReview.html b/app/templates/snips/annualAllocationReview.html index 331b37791..11255cfbe 100644 --- a/app/templates/snips/annualAllocationReview.html +++ b/app/templates/snips/annualAllocationReview.html @@ -10,7 +10,7 @@
    From 51d34b843b8070d35028f30d3706f381f21477be Mon Sep 17 00:00:00 2001 From: kurasova Date: Mon, 3 Aug 2026 10:17:41 -0400 Subject: [PATCH 62/92] Made the Allocation Review page reflect actual requested allocations --- .../admin_routes/manageDepartments.py | 8 ++++++- app/templates/admin/allocationReview.html | 24 +++++++++---------- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 308af4942..14830dd81 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -120,5 +120,11 @@ def allocationReview(org=None, account=None): # DON'T DELETE THE UNDERSCORES _, _, nextAY = generateAdjacentYears() # The generateAdjacentYears() function returns a tuple of three elements, and we only need the third value + + testing = Allocation.get(Allocation.department == dept) - return render_template('admin/allocationReview.html', department = dept, nextAY = nextAY) \ No newline at end of file + return render_template('admin/allocationReview.html', + department = dept, + nextAY = nextAY, + allocation = testing + ) \ No newline at end of file diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index 73b929e3a..682a3d392 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -33,8 +33,8 @@

    - - (requested: 120) + + (requested: {{allocation.breakHours}})

    @@ -51,20 +51,20 @@

    10 hours:  - -  (requested: 120) + +  (requested: {{allocation.primary_10}})

    12 hours:  - -  (requested: 120) + +  (requested: {{allocation.primary_12}})

    15 hours:  - -  (requested: 120) + +  (requested: {{allocation.primary_15}})

    @@ -76,14 +76,14 @@

    5 hours:    - -  (requested: 120) + +  (requested: {{allocation.secondary_5}})

    10 hours:  - -  (requested: 120) + +  (requested: {{allocation.secondary_10}})

    From 09132b1f96a956cd9a1833a380e171733b25b7de Mon Sep 17 00:00:00 2001 From: kurasova Date: Mon, 3 Aug 2026 11:45:25 -0400 Subject: [PATCH 63/92] Updated the layout of the Allocation Review Form + updated the database --- .../admin_routes/manageDepartments.py | 9 +- app/models/allocation.py | 2 +- app/static/css/allocationReview.css | 27 +++- app/templates/admin/allocationReview.html | 46 +++--- database/demo_data.py | 138 +++++++++++++++--- 5 files changed, 173 insertions(+), 49 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 14830dd81..29112ef46 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -118,13 +118,14 @@ def allocationReview(org=None, account=None): # Retrieving the next year # DON'T DELETE THE UNDERSCORES - _, _, nextAY = generateAdjacentYears() - # The generateAdjacentYears() function returns a tuple of three elements, and we only need the third value + currentAY, _, nextAY = generateAdjacentYears() - testing = Allocation.get(Allocation.department == dept) + requestedAlloc = Allocation.get(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == False) + currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) return render_template('admin/allocationReview.html', department = dept, nextAY = nextAY, - allocation = testing + requestedAlloc = requestedAlloc, + currentAlloc = currentAlloc ) \ No newline at end of file diff --git a/app/models/allocation.py b/app/models/allocation.py index eb83877a0..02c53aae7 100644 --- a/app/models/allocation.py +++ b/app/models/allocation.py @@ -9,7 +9,7 @@ class Allocation(baseModel): isFinal = BooleanField(default=False) approvedOn = DateField(null=True) approvedBy = ForeignKeyField(Supervisor, null=True) - justification = TextField() + justification = TextField(default="", null=False) primary_10 = IntegerField() primary_12 = IntegerField() primary_15 = IntegerField() diff --git a/app/static/css/allocationReview.css b/app/static/css/allocationReview.css index 2d72c7067..184357c3a 100644 --- a/app/static/css/allocationReview.css +++ b/app/static/css/allocationReview.css @@ -20,16 +20,31 @@ margin-bottom: 30px; } +.separationLine { + border: 0; + border-top: 1px solid black; +} + #breakHours { margin-top: 0px; + margin-bottom: 30px; +} + +.allocationReviewSection{ + display: flex; + flex-direction: row; + justify-content: space-between; } -.numericSpinner { - width: 55px; +.positionNumericSpinner { + width: 45px; +} + +.breakHoursNumericSpinner { + width: 65px; } #requestedPositions { - margin-top: 30px; margin-bottom: -10px; } @@ -38,12 +53,16 @@ } -#allocationJustification { +/* #allocationJustification { margin-top: 30px; } .unresizeable { resize: none; +} */ + +#allocationReviewFooter{ + margin-top: 30px; } #allocationReviewNote { diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index 682a3d392..d56975699 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -28,20 +28,24 @@

    {{nextAY.termName.split(" ")[1]}}

    + +

    + {{requestedAlloc.justification}} +

    -
    +

    - - (requested: {{allocation.breakHours}}) + + (requested: {{requestedAlloc.breakHours}}; current: {{currentAlloc.breakHours}})

    - +

    -
    +

    @@ -51,20 +55,20 @@

    10 hours:  - -  (requested: {{allocation.primary_10}}) + +  (requested: {{requestedAlloc.primary_10}}; current: {{currentAlloc.primary_10}})

    12 hours:  - -  (requested: {{allocation.primary_12}}) + +  (requested: {{requestedAlloc.primary_12}}; current: {{currentAlloc.primary_12}})

    15 hours:  - -  (requested: {{allocation.primary_15}}) + +  (requested: {{requestedAlloc.primary_15}}; current: {{currentAlloc.primary_15}})

    @@ -76,32 +80,34 @@

    5 hours:    - -  (requested: {{allocation.secondary_5}}) + +  (requested: {{requestedAlloc.secondary_5}}; current: {{currentAlloc.secondary_5}})

    10 hours:  - -  (requested: {{allocation.secondary_10}}) + +  (requested: {{requestedAlloc.secondary_10}}; current: {{currentAlloc.secondary_10}})

    - + + -
    +
    - *To submit the form, you must either specify the number of extra break hours or fill in one of the fields in the Requested Positions section. + *After approving the request, you will not be able to approve it again over the next academic year ({{nextAY.termName.split(" ")[1]}}).
    -
    +
    diff --git a/database/demo_data.py b/database/demo_data.py index b7a59297e..afcf9672b 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": 1, + }, ] Term.insert_many(terms).on_conflict_replace().execute() @@ -1108,26 +1118,41 @@ allocations = [ { "termCode": 202500, - "department": 3, - "isFinal": False, + "department": 1, + "isFinal": True, "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, + "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": 260, + "breakHours": 550, }, { - "termCode": 202500, + "termCode": 202600, + "department": 1, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "We need even more students to help with the increased workload in the department", + "primary_10": 8, + "primary_12": 12, + "primary_15": 5, + "primary_20": 2, + "secondary_5": 8, + "secondary_10": 1, + "breakHours": 560, + }, + { + "termCode": 202500, "department": 2, "isFinal": True, "approvedOn": None, "approvedBy": None, - "justification": "Increase in student enrollment due to exodous from CS department", + "justification": "Increase in student enrollment due to an exodus from the CS department", "primary_10": 4, "primary_12": 2, "primary_15": 7, @@ -1137,24 +1162,68 @@ "breakHours": 750, }, { + "termCode": 202600, + "department": 2, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "We need more students than last year", + "primary_10": 5, + "primary_12": 3, + "primary_15": 8, + "primary_20": 5, + "secondary_5": 3, + "secondary_10": 0, + "breakHours": 900, + }, + { "termCode": 202500, - "department": 1, + "department": 3, "isFinal": True, "approvedOn": None, "approvedBy": None, - "justification": "We are hiring more students to help with the increased workload in the department", + "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": 202600, + "department": 3, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Having more students, as economic conditions seem to improve", "primary_10": 5, - "primary_12": 6, - "primary_15": 4, - "primary_20": 1, - "secondary_5": 7, + "primary_12": 3, + "primary_15": 3, + "primary_20": 0, + "secondary_5": 2, "secondary_10": 0, - "breakHours": 550, + "breakHours": 360, + }, + { + "termCode": 202600, + "department": 3, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "primary_10": 2, + "primary_12": 2, + "primary_15": 1, + "primary_20": 0, + "secondary_5": 1, + "secondary_10": 0, + "breakHours": 260, }, { "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", @@ -1167,6 +1236,21 @@ "breakHours": 300, }, { + "termCode": 202600, + "department": 4, + "isFinal": False, + "approvedOn": None, + "approvedBy": None, + "justification": "Having more students! Yay!!!", + "primary_10": 11, + "primary_12": 7, + "primary_15": 0, + "primary_20": 0, + "secondary_5": 6, + "secondary_10": 0, + "breakHours": 301, + }, + { "termCode": 202500, "department": 5, "isFinal": True, @@ -1181,7 +1265,21 @@ "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 even more students to help with the increased workload", + "primary_10": 9, + "primary_12": 11, + "primary_15": 9, + "primary_20": 12, + "secondary_5": 3, + "secondary_10": 9, + "breakHours": 1200, + }, ] Allocation.insert_many(allocations).on_conflict_replace().execute() From fc218630c4d913fe88af8c43a33344c6c8b1e250 Mon Sep 17 00:00:00 2001 From: kurasova Date: Mon, 3 Aug 2026 13:43:09 -0400 Subject: [PATCH 64/92] Add popovers for the Allocation Review page --- app/static/css/allocationReview.css | 12 ++++++++---- app/static/js/allocationReview.js | 8 +++++++- app/templates/admin/allocationReview.html | 12 ++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/app/static/css/allocationReview.css b/app/static/css/allocationReview.css index 184357c3a..5de5ca062 100644 --- a/app/static/css/allocationReview.css +++ b/app/static/css/allocationReview.css @@ -1,10 +1,10 @@ -@media(min-width:970px) and (max-width:1240px) { +@media(min-width:970px) and (max-width:1340px) { .container { width: 80%; } } -@media(min-width:1240px) and (max-width:1800px) { +@media(min-width:1340px) and (max-width:1800px) { .container { width: 55%; } @@ -48,8 +48,12 @@ margin-bottom: -10px; } -.noBorders { - border: none !important; +.currentAndAllocated { + transition-duration: 150ms; +} + +.currentAndAllocated:hover { + color: grey; } diff --git a/app/static/js/allocationReview.js b/app/static/js/allocationReview.js index 49c14f105..d661f5a39 100644 --- a/app/static/js/allocationReview.js +++ b/app/static/js/allocationReview.js @@ -1,6 +1,12 @@ $(document).ready( function(){ + + $('[data-toggle="popover"]').popover(); + // not allowing users to type anything in a numeric spinner - $("input[type='number'].numericSpinner").keypress(function (evt) { + $("input[type='number'].breakHoursNumericSpinner").keypress(function (evt) { + evt.preventDefault(); + }); + $("input[type='number'].positionNumericSpinner").keypress(function (evt) { evt.preventDefault(); }); }); \ No newline at end of file diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index d56975699..a1bea09a6 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -38,7 +38,7 @@

    - (requested: {{requestedAlloc.breakHours}}; current: {{currentAlloc.breakHours}}) + (requested: {{requestedAlloc.breakHours}}; current: {{currentAlloc.breakHours}})

    @@ -56,19 +56,19 @@

    10 hours:  -  (requested: {{requestedAlloc.primary_10}}; current: {{currentAlloc.primary_10}}) +  (requested: {{requestedAlloc.primary_10}}; current: {{currentAlloc.primary_10}})

    12 hours:  -  (requested: {{requestedAlloc.primary_12}}; current: {{currentAlloc.primary_12}}) +  (requested: {{requestedAlloc.primary_12}}; current: {{currentAlloc.primary_12}})

    15 hours:  -  (requested: {{requestedAlloc.primary_15}}; current: {{currentAlloc.primary_15}}) +  (requested: {{requestedAlloc.primary_15}}; current: {{currentAlloc.primary_15}})

    @@ -81,13 +81,13 @@

    5 hours:    -  (requested: {{requestedAlloc.secondary_5}}; current: {{currentAlloc.secondary_5}}) +  (requested: {{requestedAlloc.secondary_5}}; current: {{currentAlloc.secondary_5}})

    10 hours:  -  (requested: {{requestedAlloc.secondary_10}}; current: {{currentAlloc.secondary_10}}) +  (requested: {{requestedAlloc.secondary_10}}; current: {{currentAlloc.secondary_10}})

    From 4d1bd2e7f674adc429970be0b402e0fe1b9cdafd Mon Sep 17 00:00:00 2001 From: kurasova Date: Mon, 3 Aug 2026 14:08:05 -0400 Subject: [PATCH 65/92] Forbade opening the Allocation Review page if the allocation is already approved --- app/controllers/admin_routes/manageDepartments.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 29112ef46..d1b00f38a 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -1,6 +1,6 @@ from datetime import date -from flask import g, request, redirect, jsonify, abort +from flask import g, request, redirect, jsonify, abort, flash from app.controllers.admin_routes import * from app.login_manager import require_login @@ -100,12 +100,14 @@ def allocationReview(org=None, account=None): the Manage Departments page. """ + # Retrieving the departments based on the org and account numbers try: dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) except (NameError, DoesNotExist): abort(404) + # Checking admin rights currentUser = require_login() if not currentUser: # If the current user is not logged in @@ -116,13 +118,23 @@ def allocationReview(org=None, account=None): elif currentUser.supervisor: return render_template('errors/403.html'), 403 + # Retrieving the next year # DON'T DELETE THE UNDERSCORES currentAY, _, nextAY = generateAdjacentYears() + requestedAlloc = Allocation.get(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == False) currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) + + # checking if the allocation has already been approved + isApproved = bool(Allocation.get_or_none(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == True)) + if isApproved: + flash("You cannot reapprove an allocation request.", "danger") + return redirect('/admin/manageDepartments/') + + return render_template('admin/allocationReview.html', department = dept, nextAY = nextAY, From f3e0339e9352d438321dfa5ee44d5a3738848be4 Mon Sep 17 00:00:00 2001 From: kurasova Date: Mon, 3 Aug 2026 15:55:16 -0400 Subject: [PATCH 66/92] Made the Approve button save the Allocation Review form --- .../admin_routes/manageDepartments.py | 37 ++++++++++++++++++- app/templates/admin/allocationReview.html | 30 +++++++++------ 2 files changed, 55 insertions(+), 12 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index d1b00f38a..dcc05b795 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -140,4 +140,39 @@ def allocationReview(org=None, account=None): nextAY = nextAY, requestedAlloc = requestedAlloc, currentAlloc = currentAlloc - ) \ No newline at end of file + ) + + + +@admin.route('/admin/allocationReview/approve', methods=['POST']) +def approveAllocationReview(): + + # Retrieving the next year + # DON'T DELETE THE UNDERSCORES + currentAY, _, nextAY = generateAdjacentYears() + + + currentAlloc = Allocation.get( + Allocation.termCode == currentAY.termCode, + Allocation.department == request.form.get("requester", type=int, default=None), + Allocation.isFinal == True + ) + + + newApprovedAlloc = Allocation.create(termCode = nextAY.termCode, + department = request.form.get("requester", type=int, default=None), + isFinal = True, + primary_10 = request.form.get("primary_10", type=int, default=currentAlloc.primary_10), + primary_12 = request.form.get("primary_12", type=int, default=currentAlloc.primary_12), + primary_15 = request.form.get("primary_15", type=int, default=currentAlloc.primary_15), + primary_20 = request.form.get("primary_20", type=int, default=currentAlloc.primary_20), + secondary_5 = request.form.get("secondary_5", type=int, default=currentAlloc.secondary_5), + secondary_10 = request.form.get("secondary_10", type=int, default=currentAlloc.secondary_10), + breakHours = request.form.get("breakHours", type=int, default=currentAlloc.breakHours) + ) + + + newApprovedAlloc.save() + + + return redirect("/admin/manageDepartments") \ No newline at end of file diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index a1bea09a6..e2334cd79 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -19,18 +19,20 @@

    -
    +

    {{department.DEPT_NAME}} Department +

    - {{nextAY.termName.split(" ")[1]}} + {{nextAY.termName.split(" ")[1]}} +

    - {{requestedAlloc.justification}} + {{requestedAlloc.justification}}


    @@ -55,21 +57,27 @@

    10 hours:  - +  (requested: {{requestedAlloc.primary_10}}; current: {{currentAlloc.primary_10}})

    12 hours:  - +  (requested: {{requestedAlloc.primary_12}}; current: {{currentAlloc.primary_12}})

    15 hours:  - +  (requested: {{requestedAlloc.primary_15}}; current: {{currentAlloc.primary_15}})

    + +

    + 20 hours:  + +  (requested: {{requestedAlloc.primary_20}}; current: {{currentAlloc.primary_20}}) +

    @@ -80,13 +88,13 @@

    5 hours:    - +  (requested: {{requestedAlloc.secondary_5}}; current: {{currentAlloc.secondary_5}})

    10 hours:  - +  (requested: {{requestedAlloc.secondary_10}}; current: {{currentAlloc.secondary_10}})

    @@ -108,11 +116,11 @@

    - - + +
    -

    + {% endblock %} \ No newline at end of file From 37e6fbb468883a5caebf2ce4643bcc8cfb5b8dbe Mon Sep 17 00:00:00 2001 From: kurasova Date: Tue, 4 Aug 2026 10:07:16 -0400 Subject: [PATCH 67/92] THe approvedBy and approvedOn are now saved with the submission of an allocation review + admins can no longer review allocations that has not been requested --- .../admin_routes/manageDepartments.py | 22 +++++++++++++++---- app/templates/admin/allocationReview.html | 2 +- database/demo_data.py | 17 +------------- 3 files changed, 20 insertions(+), 21 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index dcc05b795..94d4fb2ca 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -124,10 +124,18 @@ def allocationReview(org=None, account=None): currentAY, _, nextAY = generateAdjacentYears() - requestedAlloc = Allocation.get(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == False) + # getting the current allocation currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) + # checking if the department has requested any allocation review + requestedAlloc = Allocation.get_or_none(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == False) + isRequested = bool(requestedAlloc) + if not isRequested: + flash(f"The {dept.DEPT_NAME} department has not requested an allocation review yet.", "danger") + return redirect('/admin/manageDepartments/') + + # checking if the allocation has already been approved isApproved = bool(Allocation.get_or_none(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == True)) if isApproved: @@ -159,9 +167,17 @@ def approveAllocationReview(): ) + + # getting the name of the user who approves the request + supervisorID = require_login().supervisor + + + # saving the newly approved allocation newApprovedAlloc = Allocation.create(termCode = nextAY.termCode, department = request.form.get("requester", type=int, default=None), isFinal = True, + approvedBy = supervisorID, + approvedOn = date.today(), primary_10 = request.form.get("primary_10", type=int, default=currentAlloc.primary_10), primary_12 = request.form.get("primary_12", type=int, default=currentAlloc.primary_12), primary_15 = request.form.get("primary_15", type=int, default=currentAlloc.primary_15), @@ -169,9 +185,7 @@ def approveAllocationReview(): secondary_5 = request.form.get("secondary_5", type=int, default=currentAlloc.secondary_5), secondary_10 = request.form.get("secondary_10", type=int, default=currentAlloc.secondary_10), breakHours = request.form.get("breakHours", type=int, default=currentAlloc.breakHours) - ) - - + ) newApprovedAlloc.save() diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index e2334cd79..70caa3441 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -111,7 +111,7 @@

    - *After approving the request, you will not be able to approve it again over the next academic year ({{nextAY.termName.split(" ")[1]}}). + Once this request is approved, the {{department.DEPT_NAME}} department can no longer submit any new allocation requests for the year of {{nextAY.termName.split(" ")[1]}}.
    diff --git a/database/demo_data.py b/database/demo_data.py index afcf9672b..201f2ee45 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -1236,21 +1236,6 @@ "breakHours": 300, }, { - "termCode": 202600, - "department": 4, - "isFinal": False, - "approvedOn": None, - "approvedBy": None, - "justification": "Having more students! Yay!!!", - "primary_10": 11, - "primary_12": 7, - "primary_15": 0, - "primary_20": 0, - "secondary_5": 6, - "secondary_10": 0, - "breakHours": 301, - }, - { "termCode": 202500, "department": 5, "isFinal": True, @@ -1279,7 +1264,7 @@ "secondary_5": 3, "secondary_10": 9, "breakHours": 1200, - }, + } ] Allocation.insert_many(allocations).on_conflict_replace().execute() From aa78f71be6c19a4393ae24464d5841e36dd54738 Mon Sep 17 00:00:00 2001 From: kurasova Date: Tue, 4 Aug 2026 11:21:21 -0400 Subject: [PATCH 68/92] Created a new Allocation Request page --- .../admin_routes/manageDepartments.py | 44 ++++++- app/static/css/allocationRequest.css | 62 +++++++++ app/static/js/allocationRequest.js | 9 ++ app/templates/admin/allocationReview.html | 2 +- app/templates/main/allocationRequest.html | 121 ++++++++++++++++++ 5 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 app/static/css/allocationRequest.css create mode 100644 app/static/js/allocationRequest.js create mode 100644 app/templates/main/allocationRequest.html diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 94d4fb2ca..ea4d09283 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -189,4 +189,46 @@ def approveAllocationReview(): newApprovedAlloc.save() - return redirect("/admin/manageDepartments") \ No newline at end of file + return redirect("/admin/manageDepartments") + + + + + + +####################################################################################################################### +### ALL THE CODE BELOW SHOULD BE MOVED TO departmentPortal.py EVENTUALLY ############################################# +##################################################################################################################### + + +@admin.route('/department///allocations/request', 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 + + if not g.currentUser.isLaborAdmin: + if not SupervisorDepartment.select().where( + (SupervisorDepartment.supervisor == g.currentUser.supervisor) & + (SupervisorDepartment.department == dept.departmentID) + ).exists(): + return render_template('errors/403.html'), 403 + + + # Retrieving the next year + # DON'T DELETE THE UNDERSCORES + currentAY, _, nextAY = generateAdjacentYears() + + + # getting the current allocation + currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) + + + return render_template('main/allocationRequest.html', + department = dept, + nextAY = nextAY, + currentAlloc = currentAlloc + ) + + diff --git a/app/static/css/allocationRequest.css b/app/static/css/allocationRequest.css new file mode 100644 index 000000000..c34f1f89c --- /dev/null +++ b/app/static/css/allocationRequest.css @@ -0,0 +1,62 @@ +@media(min-width:970px) and (max-width:1340px) { + .container { + width: 80%; + } +} + +@media(min-width:1340px) and (max-width:1800px) { + .container { + width: 55%; + } +} + +@media(min-width:1800px) { + .container { + width: 40%; + } +} + +#allocationReviewSubtitle{ + margin-bottom: 30px; +} + +.separationLine { + border: 0; + border-top: 1px solid black; +} + +#breakHours { + margin-top: 0px; + margin-bottom: 30px; +} + +.allocationReviewSection{ + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.positionNumericSpinner { + width: 45px; +} + +.breakHoursNumericSpinner { + width: 65px; +} + +#requestedPositions { + margin-bottom: -10px; +} + +#allocationJustification { + margin-top: 30px; +} + +.unresizeable { + resize: none; +} + +#allocationReviewNote { + max-width:70%; + color: grey; +} \ No newline at end of file diff --git a/app/static/js/allocationRequest.js b/app/static/js/allocationRequest.js new file mode 100644 index 000000000..be2a39f29 --- /dev/null +++ b/app/static/js/allocationRequest.js @@ -0,0 +1,9 @@ +$(document).ready( function(){ + // not allowing users to type anything in a numeric spinner + $("input[type='number'].breakHoursNumericSpinner").keypress(function (evt) { + evt.preventDefault(); + }); + $("input[type='number'].positionNumericSpinner").keypress(function (evt) { + evt.preventDefault(); + }); +}); \ No newline at end of file diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index 70caa3441..78cf93fba 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -111,7 +111,7 @@

    - Once this request is approved, the {{department.DEPT_NAME}} department can no longer submit any new allocation requests for the year of {{nextAY.termName.split(" ")[1]}}. + Once this request is approved, the {{department.DEPT_NAME}} department can no longer submit any new allocation requests for {{nextAY.termName.split(" ")[1]}}.
    diff --git a/app/templates/main/allocationRequest.html b/app/templates/main/allocationRequest.html new file mode 100644 index 000000000..42cd8efc2 --- /dev/null +++ b/app/templates/main/allocationRequest.html @@ -0,0 +1,121 @@ +{% extends "base.html" %} {% block styles %} {{super()}} + + +{% endblock %} {% block scripts %} {{super()}} + + +{% endblock %} {% block app_content %} + +

    + + Allocation Request + +

    + +

    + + Submit an allocation request to the Labor Department + +

    + +
    +

    + + {{department.DEPT_NAME}} Department + +

    + +

    + {{nextAY.termName.split(" ")[1]}} + +

    + +
    + +

    + + + (currently allocated: {{currentAlloc.breakHours}}) +

    + +

    + +

    + +
    +
    +

    + + Primary + +

    + +

    + 10 hours:  + +  (currently allocated: {{currentAlloc.primary_10}}) +

    + +

    + 12 hours:  + +  (currently allocated: {{currentAlloc.primary_12}}) +

    + +

    + 15 hours:  + +  (currently allocated: {{currentAlloc.primary_15}}) +

    + +

    + 20 hours:  + +  (currently allocated: {{currentAlloc.primary_20}}) +

    +
    +
    +

    + + Secondary + +

    + +

    + 5 hours:    + +  (currently allocated: {{currentAlloc.secondary_5}}) +

    + +

    + 10 hours:  + +  (currently allocated: {{currentAlloc.secondary_10}}) +

    +
    +
    + +

    + +

    + + + +
    + +
    + + This allocation request for {{nextAY.termName.split(" ")[1]}} can be updated by resubmission. However, once the Labor Office approves it, you can no longer change it. + +
    + +
    + + +
    + +
    +
    + +{% endblock %} \ No newline at end of file From e4c0d29edb44aaf023e305c733c609380b4c2f26 Mon Sep 17 00:00:00 2001 From: kurasova Date: Tue, 4 Aug 2026 13:55:42 -0400 Subject: [PATCH 69/92] Made the Allocation Request form get saved to the database --- .../admin_routes/manageDepartments.py | 55 ++++++++++++++++++- app/templates/admin/allocationReview.html | 8 --- app/templates/main/allocationRequest.html | 7 +-- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index ea4d09283..e910f0f72 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -167,7 +167,6 @@ def approveAllocationReview(): ) - # getting the name of the user who approves the request supervisorID = require_login().supervisor @@ -219,6 +218,13 @@ def managePositions(org, account): # Retrieving the next year # DON'T DELETE THE UNDERSCORES currentAY, _, nextAY = generateAdjacentYears() + + + # checking if the allocation has already been approved + isApproved = bool(Allocation.get_or_none(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == True)) + if isApproved: + flash(f"The allocation for the {nextAY.termName} academic year has already been approved; therefore, you can no longer resubmit it.", "danger") + return redirect('/admin/manageDepartments/') # getting the current allocation @@ -232,3 +238,50 @@ def managePositions(org, account): ) +@admin.route('/allocationRequest/submit', methods=['POST']) +def submitAllocationRequest(): + + # Retrieving the next year + # DON'T DELETE THE UNDERSCORES + currentAY, _, nextAY = generateAdjacentYears() + + + currentAlloc = Allocation.get( + Allocation.termCode == currentAY.termCode, + Allocation.department == request.form.get("submitter", type=int, default=None), + Allocation.isFinal == True + ) + + + # getting the name of the user who approves the request + supervisorID = require_login().supervisor + + updatedFields = { + "termCode": nextAY.termCode, + "department": request.form.get("submitter", type=int, default=None), + "isFinal": False, + "justification": request.form.get("justification", default=""), + "primary_10": request.form.get("primary_10", type=int, default=currentAlloc.primary_10), + "primary_12": request.form.get("primary_12", type=int, default=currentAlloc.primary_12), + "primary_15": request.form.get("primary_15", type=int, default=currentAlloc.primary_15), + "primary_20": request.form.get("primary_20", type=int, default=currentAlloc.primary_20), + "secondary_5": request.form.get("secondary_5", type=int, default=currentAlloc.secondary_5), + "secondary_10": request.form.get("secondary_10", type=int, default=currentAlloc.secondary_10), + "breakHours": request.form.get("breakHours", type=int, default=currentAlloc.breakHours) + } + + + # saving the newly approved allocation + requestedAlloc, wasCreated = Allocation.get_or_create(termCode=nextAY.termCode, + department=request.form.get("submitter", type=int, default=None), + isFinal=False, + defaults={**updatedFields}) + + if not wasCreated: + for key, value in updatedFields.items(): + setattr(requestedAlloc, key, value) + + requestedAlloc.save() + + + return redirect("/admin/manageDepartments") \ No newline at end of file diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index 78cf93fba..b15561171 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -99,14 +99,6 @@

    - - -
    diff --git a/app/templates/main/allocationRequest.html b/app/templates/main/allocationRequest.html index 42cd8efc2..bf5a81e0b 100644 --- a/app/templates/main/allocationRequest.html +++ b/app/templates/main/allocationRequest.html @@ -19,9 +19,8 @@

    -
    +

    - {{department.DEPT_NAME}} Department

    @@ -97,10 +96,10 @@

    - +

    - +
    From f3833f74ed99960329d938c6bc83e03b4960679b Mon Sep 17 00:00:00 2001 From: kurasova Date: Tue, 4 Aug 2026 16:49:00 -0400 Subject: [PATCH 70/92] Cleaned up the code --- app/controllers/admin_routes/manageDepartments.py | 10 ++++++---- app/static/css/allocationRequest.css | 5 ++++- app/static/css/allocationReview.css | 10 +--------- app/templates/admin/allocationReview.html | 4 ++-- app/templates/main/allocationRequest.html | 6 +++--- 5 files changed, 16 insertions(+), 19 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index e910f0f72..c7563d44d 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -201,7 +201,7 @@ def approveAllocationReview(): @admin.route('/department///allocations/request', methods=['GET']) -def managePositions(org, account): +def allocationRequest(org, account): try: dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) except DoesNotExist: @@ -222,7 +222,7 @@ def managePositions(org, account): # checking if the allocation has already been approved isApproved = bool(Allocation.get_or_none(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == True)) - if isApproved: + if isApproved: # if the approved allocation exists (in other words, if it is not None) flash(f"The allocation for the {nextAY.termName} academic year has already been approved; therefore, you can no longer resubmit it.", "danger") return redirect('/admin/manageDepartments/') @@ -256,6 +256,8 @@ def submitAllocationRequest(): # getting the name of the user who approves the request supervisorID = require_login().supervisor + + # the list of the fields updated after submitting the allocation request updatedFields = { "termCode": nextAY.termCode, "department": request.form.get("submitter", type=int, default=None), @@ -277,9 +279,9 @@ def submitAllocationRequest(): isFinal=False, defaults={**updatedFields}) - if not wasCreated: + if not wasCreated: # if the allocation has already existed (it is being resubmitted/updated) for key, value in updatedFields.items(): - setattr(requestedAlloc, key, value) + setattr(requestedAlloc, key, value) # updating all the fields based on updatedFields values requestedAlloc.save() diff --git a/app/static/css/allocationRequest.css b/app/static/css/allocationRequest.css index c34f1f89c..622dc8313 100644 --- a/app/static/css/allocationRequest.css +++ b/app/static/css/allocationRequest.css @@ -52,11 +52,14 @@ margin-top: 30px; } -.unresizeable { +#justificationTextField { resize: none; + width: 100%; + margin-bottom: 40px; } #allocationReviewNote { max-width:70%; + margin-top: -5px; color: grey; } \ No newline at end of file diff --git a/app/static/css/allocationReview.css b/app/static/css/allocationReview.css index 5de5ca062..5c3661243 100644 --- a/app/static/css/allocationReview.css +++ b/app/static/css/allocationReview.css @@ -56,20 +56,12 @@ color: grey; } - -/* #allocationJustification { - margin-top: 30px; -} - -.unresizeable { - resize: none; -} */ - #allocationReviewFooter{ margin-top: 30px; } #allocationReviewNote { max-width:70%; + margin-top: -5px; color: grey; } \ No newline at end of file diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index b15561171..e1a70b132 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -101,8 +101,8 @@

    -
    - +
    + Once this request is approved, the {{department.DEPT_NAME}} department can no longer submit any new allocation requests for {{nextAY.termName.split(" ")[1]}}.
    diff --git a/app/templates/main/allocationRequest.html b/app/templates/main/allocationRequest.html index bf5a81e0b..e7024cd8d 100644 --- a/app/templates/main/allocationRequest.html +++ b/app/templates/main/allocationRequest.html @@ -99,12 +99,12 @@

    - +
    -
    - +
    + This allocation request for {{nextAY.termName.split(" ")[1]}} can be updated by resubmission. However, once the Labor Office approves it, you can no longer change it.
    From 73b3ac492b7aed967a6adfcb2114802abf827d3e Mon Sep 17 00:00:00 2001 From: kurasova Date: Wed, 5 Aug 2026 13:55:21 -0400 Subject: [PATCH 71/92] Reformatted the code for the Allocation Review and Allocation Request pages --- .../admin_routes/manageDepartments.py | 155 ++++-------------- .../main_routes/departmentPortal.py | 55 ++++++- app/logic/allocationManager.py | 14 +- app/logic/allocationRequest.py | 45 +++++ 4 files changed, 144 insertions(+), 125 deletions(-) create mode 100644 app/logic/allocationRequest.py diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index c7563d44d..40b5c3140 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -16,6 +16,11 @@ from app.models.laborStatusForm import * from app.logic.manageDepartments import * +from app.logic.allocationManager import approvedAllocationExists, requestedAllocationExists + + + +### MANAGE DEPARTMENTS PAGE ################################################################################### @@ -93,6 +98,10 @@ def complianceStatusCheck(): +### ALLOCATION REVIEW PAGE #################################################################################### + + + @admin.route('/admin/manageDepartments///allocationReview', methods=['GET']) def allocationReview(org=None, account=None): """ @@ -101,7 +110,7 @@ def allocationReview(org=None, account=None): """ - # Retrieving the departments based on the org and account numbers + # getting the name of the currently chosen department (based on the org and account numbers) try: dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) except (NameError, DoesNotExist): @@ -120,34 +129,32 @@ def allocationReview(org=None, account=None): # Retrieving the next year - # DON'T DELETE THE UNDERSCORES + # DON'T DELETE THE UNDERSCORE currentAY, _, nextAY = generateAdjacentYears() - - # getting the current allocation - currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) + # checking if the allocation has already been approved + if approvedAllocationExists(nextAY.termCode, dept): + flash("You cannot reapprove an allocation request.", "danger") + return redirect('/admin/manageDepartments/') + # checking if the department has requested any allocation review - requestedAlloc = Allocation.get_or_none(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == False) - isRequested = bool(requestedAlloc) - if not isRequested: + if not requestedAllocationExists(nextAY.termCode, dept): flash(f"The {dept.DEPT_NAME} department has not requested an allocation review yet.", "danger") return redirect('/admin/manageDepartments/') - # checking if the allocation has already been approved - isApproved = bool(Allocation.get_or_none(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == True)) - if isApproved: - flash("You cannot reapprove an allocation request.", "danger") - return redirect('/admin/manageDepartments/') + # getting the current and the requested allocations + currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) + requestedAlloc = Allocation.get(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == False) return render_template('admin/allocationReview.html', department = dept, nextAY = nextAY, - requestedAlloc = requestedAlloc, - currentAlloc = currentAlloc + currentAlloc = currentAlloc, + requestedAlloc = requestedAlloc ) @@ -156,26 +163,26 @@ def allocationReview(org=None, account=None): def approveAllocationReview(): # Retrieving the next year - # DON'T DELETE THE UNDERSCORES + # DON'T DELETE THE UNDERSCORE currentAY, _, nextAY = generateAdjacentYears() + # getting the name of the user who approves the request + approverID = require_login().supervisor - currentAlloc = Allocation.get( - Allocation.termCode == currentAY.termCode, - Allocation.department == request.form.get("requester", type=int, default=None), - Allocation.isFinal == True - ) + # getting the name of the requesting department + requester = request.form.get("requester", type=int, default=None) - # getting the name of the user who approves the request - supervisorID = require_login().supervisor + + # getting the current allocation (for default values) + currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == requester, Allocation.isFinal == True) # saving the newly approved allocation newApprovedAlloc = Allocation.create(termCode = nextAY.termCode, - department = request.form.get("requester", type=int, default=None), + department = requester, isFinal = True, - approvedBy = supervisorID, + approvedBy = approverID, approvedOn = date.today(), primary_10 = request.form.get("primary_10", type=int, default=currentAlloc.primary_10), primary_12 = request.form.get("primary_12", type=int, default=currentAlloc.primary_12), @@ -189,101 +196,3 @@ def approveAllocationReview(): return redirect("/admin/manageDepartments") - - - - - - -####################################################################################################################### -### ALL THE CODE BELOW SHOULD BE MOVED TO departmentPortal.py EVENTUALLY ############################################# -##################################################################################################################### - - -@admin.route('/department///allocations/request', methods=['GET']) -def allocationRequest(org, account): - try: - dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) - except DoesNotExist: - return render_template('errors/404.html'), 404 - - if not g.currentUser.isLaborAdmin: - if not SupervisorDepartment.select().where( - (SupervisorDepartment.supervisor == g.currentUser.supervisor) & - (SupervisorDepartment.department == dept.departmentID) - ).exists(): - return render_template('errors/403.html'), 403 - - - # Retrieving the next year - # DON'T DELETE THE UNDERSCORES - currentAY, _, nextAY = generateAdjacentYears() - - - # checking if the allocation has already been approved - isApproved = bool(Allocation.get_or_none(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == True)) - if isApproved: # if the approved allocation exists (in other words, if it is not None) - flash(f"The allocation for the {nextAY.termName} academic year has already been approved; therefore, you can no longer resubmit it.", "danger") - return redirect('/admin/manageDepartments/') - - - # getting the current allocation - currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) - - - return render_template('main/allocationRequest.html', - department = dept, - nextAY = nextAY, - currentAlloc = currentAlloc - ) - - -@admin.route('/allocationRequest/submit', methods=['POST']) -def submitAllocationRequest(): - - # Retrieving the next year - # DON'T DELETE THE UNDERSCORES - currentAY, _, nextAY = generateAdjacentYears() - - - currentAlloc = Allocation.get( - Allocation.termCode == currentAY.termCode, - Allocation.department == request.form.get("submitter", type=int, default=None), - Allocation.isFinal == True - ) - - - # getting the name of the user who approves the request - supervisorID = require_login().supervisor - - - # the list of the fields updated after submitting the allocation request - updatedFields = { - "termCode": nextAY.termCode, - "department": request.form.get("submitter", type=int, default=None), - "isFinal": False, - "justification": request.form.get("justification", default=""), - "primary_10": request.form.get("primary_10", type=int, default=currentAlloc.primary_10), - "primary_12": request.form.get("primary_12", type=int, default=currentAlloc.primary_12), - "primary_15": request.form.get("primary_15", type=int, default=currentAlloc.primary_15), - "primary_20": request.form.get("primary_20", type=int, default=currentAlloc.primary_20), - "secondary_5": request.form.get("secondary_5", type=int, default=currentAlloc.secondary_5), - "secondary_10": request.form.get("secondary_10", type=int, default=currentAlloc.secondary_10), - "breakHours": request.form.get("breakHours", type=int, default=currentAlloc.breakHours) - } - - - # saving the newly approved allocation - requestedAlloc, wasCreated = Allocation.get_or_create(termCode=nextAY.termCode, - department=request.form.get("submitter", type=int, default=None), - isFinal=False, - defaults={**updatedFields}) - - if not wasCreated: # if the allocation has already existed (it is being resubmitted/updated) - for key, value in updatedFields.items(): - setattr(requestedAlloc, key, value) # updating all the fields based on updatedFields values - - requestedAlloc.save() - - - return redirect("/admin/manageDepartments") \ No newline at end of file diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index b0c33f5e7..1618e7ffa 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -1,9 +1,62 @@ -from flask import render_template, g +from flask import render_template, g, request, redirect +from app.login_manager import require_login from app.controllers.main_routes import main_bp from app.logic.getPositions import getPositions from peewee import DoesNotExist from app.models.department import Department +from app.models.allocation import Allocation from app.models.supervisorDepartment import SupervisorDepartment +from app.logic.allocationRequest import belongsToDepartment, getOrUpdateRequestedAllocation +from app.logic.allocationManager import approvedAllocationExists + + +@main_bp.route('/department///allocations/request', methods=['GET']) +def allocationRequest(org, account): + + # an import statement (it was put here to avoid circular imports) + from app.logic.manageDepartments import generateAdjacentYears + + + # getting the name of the currently chosen department (based on the org and account numbers) + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except DoesNotExist: + return render_template('errors/404.html'), 404 + + + # cheching if the user can visit this page + if not g.currentUser.isLaborAdmin: + if not belongsToDepartment(dept): + return render_template('errors/403.html'), 403 + + + # Retrieving the next year + # DON'T DELETE THE UNDERSCORES + currentAY, _, nextAY = generateAdjacentYears() + + + # checking if the allocation has already been approved (in other words, if an approved allocation exists) + if approvedAllocationExists(nextAY.termCode, dept): + flash(f"The allocation for the {nextAY.termName.split(" ")[1]} academic year has already been approved; therefore, you can no longer resubmit it.", "danger") + return redirect('/admin/manageDepartments/') + + + # getting the current approved allocation + currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) + + + return render_template('main/allocationRequest.html', + department = dept, + nextAY = nextAY, + currentAlloc = currentAlloc + ) + + +@main_bp.route('/allocationRequest/submit', methods=['POST']) +def submitAllocationRequest(): + getOrUpdateRequestedAllocation() + return redirect("/admin/manageDepartments") + @main_bp.route('/department///positions', methods=['GET']) def managePositions(org, account): diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index f1c58cbc6..4890aec2e 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -105,4 +105,16 @@ def getContractedAllocations(termCode: int, dept: int): 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 \ No newline at end of file + return usedPositions + +def approvedAllocationExists(termCode: int, dept: int): + """ + Checks if there is an approved allocation. + """ + return bool(Allocation.get_or_none(Allocation.termCode == termCode, Allocation.department == dept, Allocation.isFinal == True)) + +def requestedAllocationExists(termCode: int, dept: int): + """ + Checks if there is a requested allocation. + """ + return bool(Allocation.get_or_none(Allocation.termCode == termCode, Allocation.department == dept, Allocation.isFinal == False)) \ No newline at end of file diff --git a/app/logic/allocationRequest.py b/app/logic/allocationRequest.py new file mode 100644 index 000000000..5fb081532 --- /dev/null +++ b/app/logic/allocationRequest.py @@ -0,0 +1,45 @@ +from flask import request, g +from app.models.allocation import Allocation +from app.logic.allocationManager import * + +def belongsToDepartment(department): + """ + Checks whether the current user is a supervisor who belongs to a certain department. + """ + return SupervisorDepartment.select().where((SupervisorDepartment.supervisor == g.currentUser.supervisor) & (SupervisorDepartment.department == department.departmentID)).exists() + + +def getOrUpdateRequestedAllocation(): + """ + Gets or updates the requested allocation (used for the Allocation Request page specificially). + """ + currentAY = (g.openTerm.termCode // 100) * 100 # current academic year + nextAY = currentAY + 100 # upcoming (next) academic year + + requester = request.form.get("submitter", type=int, default=None) # the requesting department + + currentAlloc = getAllocation(g.openTerm.termCode, requester, True) # the current allocation + + # the list of the fields updated after submitting the allocation request + updatedFields = { + "termCode": nextAY, + "department": request.form.get("submitter", type=int, default=None), + "isFinal": False, + "justification": request.form.get("justification", default=""), + "primary_10": request.form.get("primary_10", type=int, default=currentAlloc["primary_10"]), + "primary_12": request.form.get("primary_12", type=int, default=currentAlloc["primary_12"]), + "primary_15": request.form.get("primary_15", type=int, default=currentAlloc["primary_15"]), + "primary_20": request.form.get("primary_20", type=int, default=currentAlloc["primary_20"]), + "secondary_5": request.form.get("secondary_5", type=int, default=currentAlloc["secondary_5"]), + "secondary_10": request.form.get("secondary_10", type=int, default=currentAlloc["secondary_10"]), + "breakHours": request.form.get("breakHours", type=int, default=currentAlloc["breakHours"]) + } + + # saving the newly approved allocation + requestedAlloc, wasCreated = Allocation.get_or_create(termCode=nextAY, department=requester, isFinal=False, defaults={**updatedFields}) + + if not wasCreated: # if the allocation has already existed (it is being resubmitted/updated) + for key, value in updatedFields.items(): + setattr(requestedAlloc, key, value) # updating all the fields based on updatedFields values + + requestedAlloc.save() \ No newline at end of file From 5585ef0629b3cc2f6e194d10f39362cf41a4c910 Mon Sep 17 00:00:00 2001 From: kurasova Date: Wed, 5 Aug 2026 14:19:56 -0400 Subject: [PATCH 72/92] Updated some of the logic --- .../admin_routes/manageDepartments.py | 6 +++--- .../main_routes/departmentPortal.py | 13 ++++++++----- app/logic/allocationManager.py | 18 ++++++------------ app/logic/allocationRequest.py | 6 ------ 4 files changed, 17 insertions(+), 26 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 40b5c3140..bb9edf9a9 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -16,7 +16,7 @@ from app.models.laborStatusForm import * from app.logic.manageDepartments import * -from app.logic.allocationManager import approvedAllocationExists, requestedAllocationExists +from app.logic.allocationManager import allocationExists @@ -134,13 +134,13 @@ def allocationReview(org=None, account=None): # checking if the allocation has already been approved - if approvedAllocationExists(nextAY.termCode, dept): + if allocationExists(nextAY.termCode, dept, isFinal=True): flash("You cannot reapprove an allocation request.", "danger") return redirect('/admin/manageDepartments/') # checking if the department has requested any allocation review - if not requestedAllocationExists(nextAY.termCode, dept): + if not allocationExists(nextAY.termCode, dept, isFinal=False): flash(f"The {dept.DEPT_NAME} department has not requested an allocation review yet.", "danger") return redirect('/admin/manageDepartments/') diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 1618e7ffa..405a68eb6 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -1,4 +1,4 @@ -from flask import render_template, g, request, redirect +from flask import render_template, g, request, redirect, flash from app.login_manager import require_login from app.controllers.main_routes import main_bp from app.logic.getPositions import getPositions @@ -6,8 +6,8 @@ from app.models.department import Department from app.models.allocation import Allocation from app.models.supervisorDepartment import SupervisorDepartment -from app.logic.allocationRequest import belongsToDepartment, getOrUpdateRequestedAllocation -from app.logic.allocationManager import approvedAllocationExists +from app.logic.allocationRequest import getOrUpdateRequestedAllocation +from app.logic.allocationManager import allocationExists @main_bp.route('/department///allocations/request', methods=['GET']) @@ -26,7 +26,10 @@ def allocationRequest(org, account): # cheching if the user can visit this page if not g.currentUser.isLaborAdmin: - if not belongsToDepartment(dept): + if not SupervisorDepartment.select().where( + (SupervisorDepartment.supervisor == g.currentUser.supervisor) & + (SupervisorDepartment.department == dept.departmentID) + ).exists(): return render_template('errors/403.html'), 403 @@ -36,7 +39,7 @@ def allocationRequest(org, account): # checking if the allocation has already been approved (in other words, if an approved allocation exists) - if approvedAllocationExists(nextAY.termCode, dept): + if allocationExists(nextAY.termCode, dept, isFinal=True): flash(f"The allocation for the {nextAY.termName.split(" ")[1]} academic year has already been approved; therefore, you can no longer resubmit it.", "danger") return redirect('/admin/manageDepartments/') diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index 4890aec2e..16d707532 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -9,15 +9,15 @@ def getAllocation(termCode: int, dept: int, isFinal = True): ''' - This function returns a peewee object containing the selected allocation for given + This function returns a dictionary 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( + allocationDict = Allocation.select().where( Allocation.termCode.in_([termCode,academicYearCode]), Allocation.department == dept, Allocation.isFinal == isFinal).dicts().get() - return allocationObject + return allocationDict def getTotalAllocations(termCode: int, dept: int): @@ -107,14 +107,8 @@ def getContractedAllocations(termCode: int, dept: int): usedPositions["used_total"] = sum(list(usedPositions.values())[:6]) return usedPositions -def approvedAllocationExists(termCode: int, dept: int): - """ - Checks if there is an approved allocation. - """ - return bool(Allocation.get_or_none(Allocation.termCode == termCode, Allocation.department == dept, Allocation.isFinal == True)) - -def requestedAllocationExists(termCode: int, dept: int): +def allocationExists(termCode: int, dept: int, isFinal: bool): """ - Checks if there is a requested allocation. + Checks if there is an allocation that matches certain criteria. """ - return bool(Allocation.get_or_none(Allocation.termCode == termCode, Allocation.department == dept, Allocation.isFinal == False)) \ No newline at end of file + return bool(Allocation.get_or_none(Allocation.termCode == termCode, Allocation.department == dept, Allocation.isFinal == isFinal)) \ No newline at end of file diff --git a/app/logic/allocationRequest.py b/app/logic/allocationRequest.py index 5fb081532..ad15001c6 100644 --- a/app/logic/allocationRequest.py +++ b/app/logic/allocationRequest.py @@ -2,12 +2,6 @@ from app.models.allocation import Allocation from app.logic.allocationManager import * -def belongsToDepartment(department): - """ - Checks whether the current user is a supervisor who belongs to a certain department. - """ - return SupervisorDepartment.select().where((SupervisorDepartment.supervisor == g.currentUser.supervisor) & (SupervisorDepartment.department == department.departmentID)).exists() - def getOrUpdateRequestedAllocation(): """ From e4c78956dcaf7128a3e3ffdf83875910df2457d8 Mon Sep 17 00:00:00 2001 From: kurasova Date: Wed, 5 Aug 2026 16:03:56 -0400 Subject: [PATCH 73/92] Added tests for the newly created logic functions --- tests/code/test_allocationManger.py | 45 ++++++++++++- tests/code/test_allocationRequest.py | 96 ++++++++++++++++++++++++++++ 2 files changed, 140 insertions(+), 1 deletion(-) create mode 100644 tests/code/test_allocationRequest.py diff --git a/tests/code/test_allocationManger.py b/tests/code/test_allocationManger.py index ea7da6782..6d15b58ec 100644 --- a/tests/code/test_allocationManger.py +++ b/tests/code/test_allocationManger.py @@ -204,4 +204,47 @@ 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_allocationExists(testTerm, testDepartment, testAllocation, testPendingAllocation): + + assert allocationExists(testTerm.termCode, testDepartment.departmentID, isFinal=False) == True + assert allocationExists(testTerm.termCode, testDepartment.departmentID, isFinal=True) == True + + assert allocationExists(testTerm.termCode + 100, testDepartment.departmentID, isFinal=False) == False + assert allocationExists(testTerm.termCode + 100, testDepartment.departmentID, isFinal=True) == False + + assert allocationExists(testTerm.termCode, 456, isFinal=False) == False + assert allocationExists(testTerm.termCode, 456, isFinal=True) == False + + testAllocation.delete_instance() + + assert allocationExists(testTerm.termCode, testDepartment.departmentID, isFinal=False) == True + assert allocationExists(testTerm.termCode, testDepartment.departmentID, isFinal=True) == False + + testPendingAllocation.delete_instance() + + assert allocationExists(testTerm.termCode, testDepartment.departmentID, isFinal=False) == False + assert allocationExists(testTerm.termCode, testDepartment.departmentID, isFinal=True) == False + + with mainDB.atomic() as transaction: + allocation = Allocation.create( + termCode = testTerm.termCode, + department = testDepartment.departmentID, + isFinal = True, + approvedOn = None, + approvedBy = None, + justification = "brovich", + primary_10 = 22, + primary_12 = 6, + primary_15 = 7, + primary_20 = 12, + secondary_5 = 45, + secondary_10 = 22, + breakHours = 894) + + assert allocationExists(testTerm.termCode, testDepartment.departmentID, isFinal=False) == False + assert allocationExists(testTerm.termCode, testDepartment.departmentID, isFinal=True) == True + + transaction.rollback() diff --git a/tests/code/test_allocationRequest.py b/tests/code/test_allocationRequest.py new file mode 100644 index 000000000..0323ddd7c --- /dev/null +++ b/tests/code/test_allocationRequest.py @@ -0,0 +1,96 @@ +import pytest + +from flask import request, g +from werkzeug.datastructures import ImmutableMultiDict + +from app import app +from app.models.allocation import Allocation +from app.models import mainDB +from app.models.term import Term + +from app.logic.allocationRequest import * + + +@pytest.fixture +def client(): + app.config['TESTING'] = True + with app.test_client() as client: + yield client + + +@pytest.mark.integration +def test_getOrUpdateRequestedAllocation(client): + with app.test_request_context('/allocationRequest/submit', method='POST', data={ + 'submitter': "2", + 'breakHours': "750", + 'primary_10': "4", + 'primary_12': "13", + 'primary_15': "7", + 'primary_20': "5", + 'secondary_5': "2", + 'secondary_10': "0", + 'breakHours': "100", + 'justification': "" + }): + with mainDB.atomic() as transaction: + g.openTerm, _ = Term.get_or_create( + termCode=200200, + defaults={"termName": "AY 2002-2003", "isAcademicYear": True} + ) + + currentAlloc = Allocation.create( + termCode=200200, + department=2, + isFinal=True, + justification="", + primary_10=12, + primary_12=3, + primary_15=4, + primary_20=5, + secondary_5=1, + secondary_10=3, + breakHours=399 + ) + + nextYear = Term.create(termCode=200300) + + getOrUpdateRequestedAllocation() + + allocation = Allocation.get(Allocation.termCode == g.openTerm.termCode + 100, Allocation.department == request.form.get("submitter", type=int, default=None)) + + assert isinstance(allocation.termCode, Term) + assert isinstance(allocation.termCode.termCode, int) + assert allocation.termCode.termCode == 200300 + + assert isinstance(allocation.department, Department) + assert isinstance(allocation.department.departmentID, int) + assert allocation.department.departmentID == 2 + + assert isinstance(allocation.isFinal, bool) + assert allocation.isFinal == False + + assert isinstance(allocation.justification, str) + assert allocation.justification == "" + + assert isinstance(allocation.primary_10, int) + assert allocation.primary_10 == 4 + + assert isinstance(allocation.primary_12, int) + assert allocation.primary_12 == 13 + + assert isinstance(allocation.primary_15, int) + assert allocation.primary_15 == 7 + + assert isinstance(allocation.primary_20, int) + assert allocation.primary_20 == 5 + + assert isinstance(allocation.secondary_5, int) + assert allocation.secondary_5 == 2 + + assert isinstance(allocation.secondary_10, int) + assert allocation.secondary_10 == 0 + + assert isinstance(allocation.breakHours, int) + assert allocation.breakHours == 100 + + transaction.rollback() \ No newline at end of file From 582a393253f211293d556c396cc31024df5b0d0b Mon Sep 17 00:00:00 2001 From: kurasova Date: Wed, 5 Aug 2026 16:12:30 -0400 Subject: [PATCH 74/92] Made the getOrUpdateRequestedAllocation function not depend on the current allocation --- app/logic/allocationRequest.py | 16 +++++++--------- tests/code/test_allocationRequest.py | 14 -------------- 2 files changed, 7 insertions(+), 23 deletions(-) diff --git a/app/logic/allocationRequest.py b/app/logic/allocationRequest.py index ad15001c6..20feb025c 100644 --- a/app/logic/allocationRequest.py +++ b/app/logic/allocationRequest.py @@ -12,21 +12,19 @@ def getOrUpdateRequestedAllocation(): requester = request.form.get("submitter", type=int, default=None) # the requesting department - currentAlloc = getAllocation(g.openTerm.termCode, requester, True) # the current allocation - # the list of the fields updated after submitting the allocation request updatedFields = { "termCode": nextAY, "department": request.form.get("submitter", type=int, default=None), "isFinal": False, "justification": request.form.get("justification", default=""), - "primary_10": request.form.get("primary_10", type=int, default=currentAlloc["primary_10"]), - "primary_12": request.form.get("primary_12", type=int, default=currentAlloc["primary_12"]), - "primary_15": request.form.get("primary_15", type=int, default=currentAlloc["primary_15"]), - "primary_20": request.form.get("primary_20", type=int, default=currentAlloc["primary_20"]), - "secondary_5": request.form.get("secondary_5", type=int, default=currentAlloc["secondary_5"]), - "secondary_10": request.form.get("secondary_10", type=int, default=currentAlloc["secondary_10"]), - "breakHours": request.form.get("breakHours", type=int, default=currentAlloc["breakHours"]) + "primary_10": request.form.get("primary_10", type=int, default=None), + "primary_12": request.form.get("primary_12", type=int, default=None), + "primary_15": request.form.get("primary_15", type=int, default=None), + "primary_20": request.form.get("primary_20", type=int, default=None), + "secondary_5": request.form.get("secondary_5", type=int, default=None), + "secondary_10": request.form.get("secondary_10", type=int, default=None), + "breakHours": request.form.get("breakHours", type=int, default=None) } # saving the newly approved allocation diff --git a/tests/code/test_allocationRequest.py b/tests/code/test_allocationRequest.py index 0323ddd7c..f06c4557d 100644 --- a/tests/code/test_allocationRequest.py +++ b/tests/code/test_allocationRequest.py @@ -38,20 +38,6 @@ def test_getOrUpdateRequestedAllocation(client): defaults={"termName": "AY 2002-2003", "isAcademicYear": True} ) - currentAlloc = Allocation.create( - termCode=200200, - department=2, - isFinal=True, - justification="", - primary_10=12, - primary_12=3, - primary_15=4, - primary_20=5, - secondary_5=1, - secondary_10=3, - breakHours=399 - ) - nextYear = Term.create(termCode=200300) getOrUpdateRequestedAllocation() From d8a66359d4aff3422e5ad7c5ea4f0232e6825934 Mon Sep 17 00:00:00 2001 From: kurasova Date: Wed, 5 Aug 2026 16:47:44 -0400 Subject: [PATCH 75/92] Added a new getCurrentAndNextYear() function + tests for it --- .../admin_routes/manageDepartments.py | 15 ++---- .../main_routes/departmentPortal.py | 9 ++-- app/logic/academicYearManager.py | 26 ++++++++++ app/logic/allocationRequest.py | 4 +- tests/code/test_academicYearManager.py | 50 +++++++++++++++++++ 5 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 app/logic/academicYearManager.py create mode 100644 tests/code/test_academicYearManager.py diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index bb9edf9a9..ef10f6dac 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -17,6 +17,7 @@ from app.logic.manageDepartments import * from app.logic.allocationManager import allocationExists +from app.logic.academicYearManager import getCurrentAndNextAY @@ -128,9 +129,8 @@ def allocationReview(org=None, account=None): return render_template('errors/403.html'), 403 - # Retrieving the next year - # DON'T DELETE THE UNDERSCORE - currentAY, _, nextAY = generateAdjacentYears() + # Retrieving the current and following academic years + currentAY, nextAY = getCurrentAndNextAY() # checking if the allocation has already been approved @@ -162,22 +162,18 @@ def allocationReview(org=None, account=None): @admin.route('/admin/allocationReview/approve', methods=['POST']) def approveAllocationReview(): - # Retrieving the next year - # DON'T DELETE THE UNDERSCORE - currentAY, _, nextAY = generateAdjacentYears() + # Retrieving the current and following academic years + currentAY, nextAY = getCurrentAndNextAY() # getting the name of the user who approves the request approverID = require_login().supervisor - # getting the name of the requesting department requester = request.form.get("requester", type=int, default=None) - # getting the current allocation (for default values) currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == requester, Allocation.isFinal == True) - # saving the newly approved allocation newApprovedAlloc = Allocation.create(termCode = nextAY.termCode, department = requester, @@ -194,5 +190,4 @@ def approveAllocationReview(): ) newApprovedAlloc.save() - return redirect("/admin/manageDepartments") diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 405a68eb6..6146c6741 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -8,15 +8,12 @@ from app.models.supervisorDepartment import SupervisorDepartment from app.logic.allocationRequest import getOrUpdateRequestedAllocation from app.logic.allocationManager import allocationExists +from app.logic.academicYearManager import getCurrentAndNextAY @main_bp.route('/department///allocations/request', methods=['GET']) def allocationRequest(org, account): - # an import statement (it was put here to avoid circular imports) - from app.logic.manageDepartments import generateAdjacentYears - - # getting the name of the currently chosen department (based on the org and account numbers) try: dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) @@ -33,9 +30,9 @@ def allocationRequest(org, account): return render_template('errors/403.html'), 403 - # Retrieving the next year + # Retrieving the current and following academic years # DON'T DELETE THE UNDERSCORES - currentAY, _, nextAY = generateAdjacentYears() + currentAY, nextAY = getCurrentAndNextAY() # checking if the allocation has already been approved (in other words, if an approved allocation exists) diff --git a/app/logic/academicYearManager.py b/app/logic/academicYearManager.py new file mode 100644 index 000000000..8953f4876 --- /dev/null +++ b/app/logic/academicYearManager.py @@ -0,0 +1,26 @@ +from flask import g +from app.models.term import * + +def getCurrentAndNextAY(): + """ + Returns two Term peewee objects: one is the current academic year, + and the other is the next academic year. + """ + + currentYear = g.openTerm.termCode // 100 + nextYear = currentYear + 1 + + currentAYCode = currentYear * 100 + nextAYCode = nextYear * 100 + + currentAY, _ = Term.get_or_create( + termCode=currentAYCode, + defaults={"termName": "AY {}-{}".format(currentYear, currentYear + 1), "isAcademicYear": True} + ) + + nextAY, _ = Term.get_or_create( + termCode=nextAYCode, + defaults={"termName": "AY {}-{}".format(nextYear, nextYear + 1), "isAcademicYear": True} + ) + + return (currentAY, nextAY) \ No newline at end of file diff --git a/app/logic/allocationRequest.py b/app/logic/allocationRequest.py index 20feb025c..6315a6278 100644 --- a/app/logic/allocationRequest.py +++ b/app/logic/allocationRequest.py @@ -1,14 +1,14 @@ from flask import request, g from app.models.allocation import Allocation from app.logic.allocationManager import * +from app.logic.academicYearManager import getCurrentAndNextAY def getOrUpdateRequestedAllocation(): """ Gets or updates the requested allocation (used for the Allocation Request page specificially). """ - currentAY = (g.openTerm.termCode // 100) * 100 # current academic year - nextAY = currentAY + 100 # upcoming (next) academic year + currentAY, nextAY = getCurrentAndNextAY() requester = request.form.get("submitter", type=int, default=None) # the requesting department diff --git a/tests/code/test_academicYearManager.py b/tests/code/test_academicYearManager.py new file mode 100644 index 000000000..3cd08744a --- /dev/null +++ b/tests/code/test_academicYearManager.py @@ -0,0 +1,50 @@ +import pytest + +from flask import g +from app.models.term import * + +from app.logic.academicYearManager import * + +@pytest.mark.integration +def test_getCurrentAndNextAY(): + with app.app_context(): + g.openTerm, _ = Term.get_or_create( + termCode = 196700, + defaults={"termName": "AY 1967-1968", "isAcademicYear": True} + ) + + currentYear, nextYear = getCurrentAndNextAY() + + assert currentYear.termCode == 196700 + assert currentYear.termName == "AY 1967-1968" + + assert nextYear.termCode == 196800 + assert nextYear.termName == "AY 1968-1969" + + + g.openTerm, _ = Term.get_or_create( + termCode = 210212, + defaults={"termName": "AY 2102-2103", "isAcademicYear": True} + ) + + currentYear, nextYear = getCurrentAndNextAY() + + assert currentYear.termCode == 210200 + assert currentYear.termName == "AY 2102-2103" + + assert nextYear.termCode == 210300 + assert nextYear.termName == "AY 2103-2104" + + # Testing data types + assert isinstance(currentYear.termCode, int) + assert isinstance(nextYear.termCode, int) + + assert isinstance(currentYear.termName, str) + assert isinstance(nextYear.termName, str) + + # Testing whether termName is formatted correctly + assert currentYear.termName.split(" ")[0] == "AY" + assert nextYear.termName.split(" ")[0] == "AY" + + assert currentYear.termName.split(" ")[1] == "2102-2103" + assert nextYear.termName.split(" ")[1] == "2103-2104" \ No newline at end of file From aaa3ab2571d565d28800b4d06ee70c0d0c952566 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Thu, 6 Aug 2026 11:18:30 -0400 Subject: [PATCH 76/92] changed UI to reflect new changes --- app/templates/admin/manageDepartments.html | 83 +++++++++------------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index d8cb2f5ca..21bac6dc7 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -45,49 +45,26 @@

    Manage Departments


    -
    +
    {% include "snips/uploadAllocations.html" %} -
    - - -
    +
    {% include "snips/annualAllocationReview.html" %} - {% include "snips/annualPositionReview.html" %} -
    @@ -109,17 +86,14 @@

    Manage Departments


    - -
    + - - - + @@ -152,20 +126,30 @@

    Manage Departments

    Secondary: {{department.lsfCountSecondaries}} of {{department.totalSecondaries}} +
    + + {{ breakHoursByDepartment.get(department.departmentID, 0) }} / {{ department.allocation.breakHours }} + + - - - + + @@ -128,7 +129,8 @@

    Manage Departments


    - {{ breakHoursByDepartment.get(department.departmentID, 0) }} / {{ department.allocation.breakHours }} + Break: {{ breakHoursByDepartment.get(department.departmentID, 0) }} of {{ + department.allocation.breakHours }} hours @@ -145,7 +147,7 @@

    Manage Departments


    - {{ breakHoursByDepartment.get(department.departmentID, 0) }} / {{ department.allocation.breakHours }} + Break: {{ breakHoursByDepartment.get(department.departmentID, 0) }} of {{ department.allocation.breakHours }} hours From 731ab491370d7cccbfbf38077cef2fd74a19a1c0 Mon Sep 17 00:00:00 2001 From: kurasova Date: Thu, 6 Aug 2026 11:26:44 -0400 Subject: [PATCH 79/92] Updated the demo data and the getCurrentAndNextAY function to use g.currentYear instead of g.openTerm --- app/__init__.py | 47 ++++++++++++++----- .../main_routes/departmentPortal.py | 3 +- app/logic/academicYearManager.py | 5 +- database/demo_data.py | 30 ++++++++---- tests/code/test_academicYearManager.py | 12 +---- 5 files changed, 61 insertions(+), 36 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 11c90a0a9..a131ae4fb 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,9 +1,9 @@ import os - -from flask import Flask -from flask_restful import Api +from datetime import date +from flask import Flask, g, request, session from flask_bootstrap import Bootstrap -from playhouse.shortcuts import model_to_dict, dict_to_model +from flask_restful import Api +from playhouse.shortcuts import dict_to_model, model_to_dict app = Flask(__name__) @@ -55,17 +55,27 @@ def new_execute(*args, **kwargs): from app.controllers.api_routes.routes import initializeApiRoutes initializeApiRoutes(api) -from flask import g from app.models.user import User -from app.login_manager import require_login +from app.login_manager import getUsernameFromEnv, require_login @app.before_request def load_user(): - try: - g.currentUser = dict_to_model(User, session['currentUser']) + requestUsername = getUsernameFromEnv(request.environ) + try: + cachedUser = session['currentUser'] + + if cachedUser.get('username') == requestUsername: + g.currentUser = dict_to_model(User, cachedUser) + return + + session.pop('currentUser', None) + session.pop('username', None) + except Exception as e: - user = require_login() - session['currentUser'] = model_to_dict(user) - g.currentUser = user + pass + + user = require_login() + session['currentUser'] = model_to_dict(user) + g.currentUser = user from app.models.term import Term from app.login_manager import getOpenTerm @@ -79,6 +89,20 @@ def load_openTerm(): session['openTerm'] = model_to_dict(term) g.openTerm = term +def getCurrentYear(): + today = date.today() + year = today.year + + if today.month < 7: + return year - 1, year + + return year, year + 1 + +@app.before_request +def load_currentYear(): + g.currentYear = getCurrentYear() + + @app.context_processor def inject_environment(): return dict(env=app.config['ENV']) @@ -87,4 +111,3 @@ def inject_environment(): def queryCount(): if session: session['querycount'] = 0 - diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 6146c6741..0088d1be2 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -30,8 +30,7 @@ def allocationRequest(org, account): return render_template('errors/403.html'), 403 - # Retrieving the current and following academic years - # DON'T DELETE THE UNDERSCORES + # Retrieving the current and following academic years currentAY, nextAY = getCurrentAndNextAY() diff --git a/app/logic/academicYearManager.py b/app/logic/academicYearManager.py index 8953f4876..f66efd15a 100644 --- a/app/logic/academicYearManager.py +++ b/app/logic/academicYearManager.py @@ -4,10 +4,11 @@ def getCurrentAndNextAY(): """ Returns two Term peewee objects: one is the current academic year, - and the other is the next academic year. + and the other is the next academic year (note that a new academic year + begins from the start of July). """ - currentYear = g.openTerm.termCode // 100 + currentYear = g.currentYear[0] nextYear = currentYear + 1 currentAYCode = currentYear * 100 diff --git a/database/demo_data.py b/database/demo_data.py index 201f2ee45..979f49ce8 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -647,6 +647,16 @@ "adjustmentCutOff": f"2026-09-01", "isBreak": 1, }, + { + "termCode": f"202700", + "termName": f"AY 2027-2028", + "termStart": f"2027-08-01", + "termEnd": f"2028-05-01", + "termState": 0, + "primaryCutOff": f"2027-09-01", + "adjustmentCutOff": f"2027-09-01", + "isBreak": 1, + }, ] Term.insert_many(terms).on_conflict_replace().execute() @@ -1117,7 +1127,7 @@ ########################### allocations = [ { - "termCode": 202500, + "termCode": 202600, "department": 1, "isFinal": True, "approvedOn": None, @@ -1132,7 +1142,7 @@ "breakHours": 550, }, { - "termCode": 202600, + "termCode": 202700, "department": 1, "isFinal": False, "approvedOn": None, @@ -1147,7 +1157,7 @@ "breakHours": 560, }, { - "termCode": 202500, + "termCode": 202600, "department": 2, "isFinal": True, "approvedOn": None, @@ -1162,7 +1172,7 @@ "breakHours": 750, }, { - "termCode": 202600, + "termCode": 202700, "department": 2, "isFinal": False, "approvedOn": None, @@ -1177,7 +1187,7 @@ "breakHours": 900, }, { - "termCode": 202500, + "termCode": 202600, "department": 3, "isFinal": True, "approvedOn": None, @@ -1192,7 +1202,7 @@ "breakHours": 260, }, { - "termCode": 202600, + "termCode": 202700, "department": 3, "isFinal": False, "approvedOn": None, @@ -1207,7 +1217,7 @@ "breakHours": 360, }, { - "termCode": 202600, + "termCode": 202700, "department": 3, "isFinal": True, "approvedOn": None, @@ -1221,7 +1231,7 @@ "breakHours": 260, }, { - "termCode": 202500, + "termCode": 202600, "department": 4, "isFinal": True, "approvedOn": None, @@ -1236,7 +1246,7 @@ "breakHours": 300, }, { - "termCode": 202500, + "termCode": 202600, "department": 5, "isFinal": True, "approvedOn": None, @@ -1251,7 +1261,7 @@ "breakHours": 900, }, { - "termCode": 202600, + "termCode": 202700, "department": 5, "isFinal": False, "approvedOn": None, diff --git a/tests/code/test_academicYearManager.py b/tests/code/test_academicYearManager.py index 3cd08744a..830c4a4e9 100644 --- a/tests/code/test_academicYearManager.py +++ b/tests/code/test_academicYearManager.py @@ -8,11 +8,7 @@ @pytest.mark.integration def test_getCurrentAndNextAY(): with app.app_context(): - g.openTerm, _ = Term.get_or_create( - termCode = 196700, - defaults={"termName": "AY 1967-1968", "isAcademicYear": True} - ) - + g.currentYear = (1967, 1968) currentYear, nextYear = getCurrentAndNextAY() assert currentYear.termCode == 196700 @@ -22,11 +18,7 @@ def test_getCurrentAndNextAY(): assert nextYear.termName == "AY 1968-1969" - g.openTerm, _ = Term.get_or_create( - termCode = 210212, - defaults={"termName": "AY 2102-2103", "isAcademicYear": True} - ) - + g.currentYear = (2102, 2103) currentYear, nextYear = getCurrentAndNextAY() assert currentYear.termCode == 210200 From 5a24ad29faf8abe85bd50b106a0fde74a163798f Mon Sep 17 00:00:00 2001 From: kurasova Date: Thu, 6 Aug 2026 16:12:01 -0400 Subject: [PATCH 80/92] Fixed some backend issues + Allowed inactive departments to submit allocation requests --- .../admin_routes/manageDepartments.py | 27 ++++++------------ .../main_routes/departmentPortal.py | 4 +-- app/templates/admin/allocationReview.html | 14 +++++----- app/templates/main/allocationRequest.html | 28 +++++++++---------- 4 files changed, 31 insertions(+), 42 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index ef10f6dac..921c28557 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -21,10 +21,6 @@ -### MANAGE DEPARTMENTS PAGE ################################################################################### - - - @admin.route('/admin/manageDepartments/', methods=['GET']) @admin.route('/admin/manageDepartments/', methods=['GET']) def manageDepartments(academicYear = None): @@ -99,10 +95,6 @@ def complianceStatusCheck(): -### ALLOCATION REVIEW PAGE #################################################################################### - - - @admin.route('/admin/manageDepartments///allocationReview', methods=['GET']) def allocationReview(org=None, account=None): """ @@ -146,7 +138,7 @@ def allocationReview(org=None, account=None): # getting the current and the requested allocations - currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) + currentAlloc = Allocation.get_or_none(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) requestedAlloc = Allocation.get(Allocation.termCode == nextAY.termCode, Allocation.department == dept, Allocation.isFinal == False) @@ -171,22 +163,19 @@ def approveAllocationReview(): # getting the name of the requesting department requester = request.form.get("requester", type=int, default=None) - # getting the current allocation (for default values) - currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == requester, Allocation.isFinal == True) - # saving the newly approved allocation newApprovedAlloc = Allocation.create(termCode = nextAY.termCode, department = requester, isFinal = True, approvedBy = approverID, approvedOn = date.today(), - primary_10 = request.form.get("primary_10", type=int, default=currentAlloc.primary_10), - primary_12 = request.form.get("primary_12", type=int, default=currentAlloc.primary_12), - primary_15 = request.form.get("primary_15", type=int, default=currentAlloc.primary_15), - primary_20 = request.form.get("primary_20", type=int, default=currentAlloc.primary_20), - secondary_5 = request.form.get("secondary_5", type=int, default=currentAlloc.secondary_5), - secondary_10 = request.form.get("secondary_10", type=int, default=currentAlloc.secondary_10), - breakHours = request.form.get("breakHours", type=int, default=currentAlloc.breakHours) + primary_10 = request.form.get("primary_10", type=int, default=None), + primary_12 = request.form.get("primary_12", type=int, default=None), + primary_15 = request.form.get("primary_15", type=int, default=None), + primary_20 = request.form.get("primary_20", type=int, default=None), + secondary_5 = request.form.get("secondary_5", type=int, default=None), + secondary_10 = request.form.get("secondary_10", type=int, default=None), + breakHours = request.form.get("breakHours", type=int, default=None) ) newApprovedAlloc.save() diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 0088d1be2..7d7547cf9 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -36,12 +36,12 @@ def allocationRequest(org, account): # checking if the allocation has already been approved (in other words, if an approved allocation exists) if allocationExists(nextAY.termCode, dept, isFinal=True): - flash(f"The allocation for the {nextAY.termName.split(" ")[1]} academic year has already been approved; therefore, you can no longer resubmit it.", "danger") + flash(f"The allocation for the {nextAY.termName.split(' ')[1]} academic year has already been approved; therefore, you can no longer resubmit it.", "danger") return redirect('/admin/manageDepartments/') # getting the current approved allocation - currentAlloc = Allocation.get(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) + currentAlloc = Allocation.get_or_none(Allocation.termCode == currentAY.termCode, Allocation.department == dept, Allocation.isFinal == True) return render_template('main/allocationRequest.html', diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index e1a70b132..a0805878e 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -40,7 +40,7 @@

    - (requested: {{requestedAlloc.breakHours}}; current: {{currentAlloc.breakHours}}) + (requested: {{requestedAlloc.breakHours}}; current: {{currentAlloc.breakHours or 0}})

    @@ -58,25 +58,25 @@

    10 hours:  -  (requested: {{requestedAlloc.primary_10}}; current: {{currentAlloc.primary_10}}) +  (requested: {{requestedAlloc.primary_10}}; current: {{currentAlloc.primary_10 or 0}})

    12 hours:  -  (requested: {{requestedAlloc.primary_12}}; current: {{currentAlloc.primary_12}}) +  (requested: {{requestedAlloc.primary_12}}; current: {{currentAlloc.primary_12 or 0}})

    15 hours:  -  (requested: {{requestedAlloc.primary_15}}; current: {{currentAlloc.primary_15}}) +  (requested: {{requestedAlloc.primary_15}}; current: {{currentAlloc.primary_15 or 0}})

    20 hours:  -  (requested: {{requestedAlloc.primary_20}}; current: {{currentAlloc.primary_20}}) +  (requested: {{requestedAlloc.primary_20}}; current: {{currentAlloc.primary_20 or 0}})

    @@ -89,13 +89,13 @@

    5 hours:    -  (requested: {{requestedAlloc.secondary_5}}; current: {{currentAlloc.secondary_5}}) +  (requested: {{requestedAlloc.secondary_5}}; current: {{currentAlloc.secondary_5 or 0}})

    10 hours:  -  (requested: {{requestedAlloc.secondary_10}}; current: {{currentAlloc.secondary_10}}) +  (requested: {{requestedAlloc.secondary_10}}; current: {{currentAlloc.secondary_10 or 0}})

    diff --git a/app/templates/main/allocationRequest.html b/app/templates/main/allocationRequest.html index e7024cd8d..546e151a3 100644 --- a/app/templates/main/allocationRequest.html +++ b/app/templates/main/allocationRequest.html @@ -34,8 +34,8 @@

    - - (currently allocated: {{currentAlloc.breakHours}}) + + (currently allocated: {{currentAlloc.breakHours or 0}})

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

    10 hours:  - -  (currently allocated: {{currentAlloc.primary_10}}) + +  (currently allocated: {{currentAlloc.primary_10 or 0}})

    12 hours:  - -  (currently allocated: {{currentAlloc.primary_12}}) + +  (currently allocated: {{currentAlloc.primary_12 or 0}})

    15 hours:  - -  (currently allocated: {{currentAlloc.primary_15}}) + +  (currently allocated: {{currentAlloc.primary_15 or 0}})

    20 hours:  - -  (currently allocated: {{currentAlloc.primary_20}}) + +  (currently allocated: {{currentAlloc.primary_20 or 0}})

    @@ -83,14 +83,14 @@

    5 hours:    - -  (currently allocated: {{currentAlloc.secondary_5}}) + +  (currently allocated: {{currentAlloc.secondary_5 or 0}})

    10 hours:  - -  (currently allocated: {{currentAlloc.secondary_10}}) + +  (currently allocated: {{currentAlloc.secondary_10 or 0}})

    From 1fef1511f358e38e96ee7d584b4235a1e612f234 Mon Sep 17 00:00:00 2001 From: kurasova Date: Thu, 6 Aug 2026 16:36:50 -0400 Subject: [PATCH 81/92] Updated the styles for the Allocation Request and Allocation Review forms --- app/static/css/allocationRequest.css | 26 +++++++++++++++-------- app/static/css/allocationReview.css | 21 +++++++++++------- app/templates/admin/allocationReview.html | 20 +++++++---------- app/templates/main/allocationRequest.html | 25 ++++++++++------------ 4 files changed, 49 insertions(+), 43 deletions(-) diff --git a/app/static/css/allocationRequest.css b/app/static/css/allocationRequest.css index 622dc8313..b476b79e6 100644 --- a/app/static/css/allocationRequest.css +++ b/app/static/css/allocationRequest.css @@ -1,22 +1,22 @@ @media(min-width:970px) and (max-width:1340px) { .container { - width: 80%; + width: 80%; } } @media(min-width:1340px) and (max-width:1800px) { .container { - width: 55%; + width: 55%; } } @media(min-width:1800px) { .container { - width: 40%; + width: 40%; } } -#allocationReviewSubtitle{ +#allocationRequestSubtitle{ margin-bottom: 30px; } @@ -30,7 +30,7 @@ margin-bottom: 30px; } -.allocationReviewSection{ +.allocationRequestSection{ display: flex; flex-direction: row; justify-content: space-between; @@ -55,11 +55,19 @@ #justificationTextField { resize: none; width: 100%; - margin-bottom: 40px; + margin-bottom: 15px; } -#allocationReviewNote { - max-width:70%; - margin-top: -5px; +#allocationRequestNote { + text-align: center; + margin: 0 auto; + max-width:70%; + margin-bottom: 15px; color: grey; +} + +.cancel-or-submit { + display: flex; + flex-direction: row; + justify-content: space-between; } \ No newline at end of file diff --git a/app/static/css/allocationReview.css b/app/static/css/allocationReview.css index 5c3661243..c7e5dffe4 100644 --- a/app/static/css/allocationReview.css +++ b/app/static/css/allocationReview.css @@ -1,18 +1,18 @@ @media(min-width:970px) and (max-width:1340px) { .container { - width: 80%; + width: 80%; } } @media(min-width:1340px) and (max-width:1800px) { .container { - width: 55%; + width: 55%; } } @media(min-width:1800px) { .container { - width: 40%; + width: 40%; } } @@ -56,12 +56,17 @@ color: grey; } -#allocationReviewFooter{ - margin-top: 30px; -} - #allocationReviewNote { + text-align: center; + margin: 0 auto; max-width:70%; - margin-top: -5px; + margin-top: 15px; + margin-bottom: 15px; color: grey; +} + +.cancel-or-approve { + display: flex; + flex-direction: row; + justify-content: space-between; } \ No newline at end of file diff --git a/app/templates/admin/allocationReview.html b/app/templates/admin/allocationReview.html index a0805878e..732273a86 100644 --- a/app/templates/admin/allocationReview.html +++ b/app/templates/admin/allocationReview.html @@ -99,19 +99,15 @@

    -
    - -
    - - Once this request is approved, the {{department.DEPT_NAME}} department can no longer submit any new allocation requests for {{nextAY.termName.split(" ")[1]}}. - -
    - -
    - - -
    +
    + + Once this request is approved, the {{department.DEPT_NAME}} department can no longer submit any new allocation requests for {{nextAY.termName.split(" ")[1]}}. + +
    +
    + +
    diff --git a/app/templates/main/allocationRequest.html b/app/templates/main/allocationRequest.html index 546e151a3..15ae67bc8 100644 --- a/app/templates/main/allocationRequest.html +++ b/app/templates/main/allocationRequest.html @@ -13,7 +13,7 @@

    -

    +

    Submit an allocation request to the Labor Department @@ -42,7 +42,7 @@

    -
    +

    @@ -101,20 +101,17 @@

    -
    - -
    - - This allocation request for {{nextAY.termName.split(" ")[1]}} can be updated by resubmission. However, once the Labor Office approves it, you can no longer change it. - -
    - -
    - - -
    +
    + + This allocation request for {{nextAY.termName.split(" ")[1]}} can be updated by resubmission. However, once the Labor Office approves it, you can no longer change it. + +
    +
    + +
    + {% endblock %} \ No newline at end of file From dd23662398019e81cdde603e5462a9193c417209 Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 7 Aug 2026 09:57:45 -0400 Subject: [PATCH 82/92] Allowed labor office students to access the Allocation Review page --- app/controllers/admin_routes/manageDepartments.py | 6 +++--- app/models/allocation.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 921c28557..8d65cd3ac 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -114,7 +114,7 @@ def allocationReview(org=None, account=None): currentUser = require_login() if not currentUser: # If the current user is not logged in return render_template('errors/403.html') - if not currentUser.isLaborAdmin: + if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent()): if currentUser.student: return redirect('/laborHistory/' + currentUser.student.ID) elif currentUser.supervisor: @@ -157,8 +157,8 @@ def approveAllocationReview(): # Retrieving the current and following academic years currentAY, nextAY = getCurrentAndNextAY() - # getting the name of the user who approves the request - approverID = require_login().supervisor + # getting the ID of the user who approves the request + approverID = require_login().userID # getting the name of the requesting department requester = request.form.get("requester", type=int, default=None) diff --git a/app/models/allocation.py b/app/models/allocation.py index 02c53aae7..79d6426bf 100644 --- a/app/models/allocation.py +++ b/app/models/allocation.py @@ -1,6 +1,6 @@ from app.models import * from app.models.department import Department -from app.models.supervisor import Supervisor +from app.models.user import User from app.models.term import Term class Allocation(baseModel): @@ -8,7 +8,7 @@ class Allocation(baseModel): department = ForeignKeyField(Department) isFinal = BooleanField(default=False) approvedOn = DateField(null=True) - approvedBy = ForeignKeyField(Supervisor, null=True) + approvedBy = ForeignKeyField(User, null=True) justification = TextField(default="", null=False) primary_10 = IntegerField() primary_12 = IntegerField() From 3727d18c78e787246b4383005c20ebaec67d9a32 Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 7 Aug 2026 09:58:50 -0400 Subject: [PATCH 83/92] Allowed labor office students to access the Allocation Request page --- app/controllers/main_routes/departmentPortal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 7d7547cf9..ab801645e 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -22,7 +22,7 @@ def allocationRequest(org, account): # cheching if the user can visit this page - if not g.currentUser.isLaborAdmin: + if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent()): if not SupervisorDepartment.select().where( (SupervisorDepartment.supervisor == g.currentUser.supervisor) & (SupervisorDepartment.department == dept.departmentID) From 0e217b9e79b32f5b6135dba7d7a35f843018539e Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 7 Aug 2026 10:04:47 -0400 Subject: [PATCH 84/92] Allowed labor office students to access other pages for admins --- app/controllers/admin_routes/manageDepartments.py | 4 ++-- app/controllers/main_routes/departmentPortal.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 8d65cd3ac..613c57053 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -33,7 +33,7 @@ def manageDepartments(academicYear = None): currentUser = require_login() if not currentUser: # If the current user is not logged in return render_template('errors/403.html') - if not currentUser.isLaborAdmin: + if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent): if currentUser.student: return redirect('/laborHistory/' + currentUser.student.ID) elif currentUser.supervisor: @@ -114,7 +114,7 @@ def allocationReview(org=None, account=None): currentUser = require_login() if not currentUser: # If the current user is not logged in return render_template('errors/403.html') - if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent()): + if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent): if currentUser.student: return redirect('/laborHistory/' + currentUser.student.ID) elif currentUser.supervisor: diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index ab801645e..19a114b77 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -22,7 +22,7 @@ def allocationRequest(org, account): # cheching if the user can visit this page - if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent()): + if not (g.currentUser.isLaborAdmin or g.currentUser.isLaborDepartmentStudent): if not SupervisorDepartment.select().where( (SupervisorDepartment.supervisor == g.currentUser.supervisor) & (SupervisorDepartment.department == dept.departmentID) @@ -64,7 +64,7 @@ def managePositions(org, account): except DoesNotExist: return render_template('errors/404.html'), 404 - if not g.currentUser.isLaborAdmin: + if not (g.currentUser.isLaborAdmin or g.currentUser.isLaborDepartmentStudent): if not SupervisorDepartment.select().where( (SupervisorDepartment.supervisor == g.currentUser.supervisor) & (SupervisorDepartment.department == dept.departmentID) From 3e83e9d6b79810ca246867650d64b604d61928c4 Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 7 Aug 2026 11:08:43 -0400 Subject: [PATCH 85/92] Moved from the generateAdjacentYears() function to getCurrentAndNextAY --- .../admin_routes/manageDepartments.py | 14 +-- app/logic/manageDepartments.py | 80 +-------------- tests/code/test_manageDepartments.py | 98 +------------------ 3 files changed, 5 insertions(+), 187 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index bae2be8d0..5c5cc3e33 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -22,7 +22,7 @@ @admin.route('/admin/manageDepartments/', methods=['GET']) -def manageDepartments(academicYear = None): +def manageDepartments(): """ Returns the Manage Departments page, which allows the admin to view all the departments and their allocations. @@ -38,16 +38,8 @@ 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) + currentAY, nextAY = getCurrentAndNextAY() + chosenAY = Term.get(Term.termCode == currentAY.termCode) breakHoursByDepartment = {row["department"]: str(row["totalHours"] or 0) for row in getUsedBreakHours(chosenAY)} diff --git a/app/logic/manageDepartments.py b/app/logic/manageDepartments.py index 4f3fbb9d8..9f823c784 100644 --- a/app/logic/manageDepartments.py +++ b/app/logic/manageDepartments.py @@ -14,45 +14,6 @@ -def generateAdjacentYears(academicYearTermCode=None): - """ - Generates the current, the previous, and the following academic years. - """ - - currentYear = g.openTerm.termCode // 100 - nextYear = currentYear + 1 - - - currentAYCode = currentYear * 100 - nextAYCode = nextYear * 100 - - # Admins cannot view allocations for the years that are beyond the current, the previous, or the following academic year - if academicYearTermCode not in (None, currentAYCode, nextAYCode): - abort(400) - - - currentAY, _ = Term.get_or_create( - termCode=currentAYCode, - defaults={"termName": "AY {}-{}".format(currentYear, currentYear + 1), "isAcademicYear": True} - ) - - nextAY, _ = Term.get_or_create( - termCode=nextAYCode, - defaults={"termName": "AY {}-{}".format(nextYear, nextYear + 1), "isAcademicYear": True} - ) - - return (currentAY, nextAY) - - - - -#################################################################################################################################### -# 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. @@ -146,43 +107,4 @@ def getAllocationStatus(term, department): (Allocation.termCode == term) & (Allocation.department == department) ) - return allocation.isFinal - - - - - - -# THE FUNCTIONS BELOW ARE NO LONGER USED IN THE CODE (BECAUSE WE CAN ONLY CHOOSE AN ACADEMIC YEAR IN THE CODE). -# IF SOMETHING CHANGES,YOU CAN USE THE CODE BELOW - -# # USED IN THE generateTermsForAdjacentYears() FUNCTION -# def generateTerms(termCode): -# """ -# Generates all the terms in an academic year. -# """ - -# # Truncating term codes to hundreds. That's how we get the academic year. -# academicYearCode = (termCode // 100) - -# return createTerms(academicYearCode) - - - -# def generateTermsForAdjacentYears(academicYear): -# """ -# Generates all the terms for the current, the previous, and the future academic years. -# """ - -# previousAYCode = g.openTerm.termCode - 100 -# currentAYCode = g.openTerm.termCode -# nextATCode = g.openTerm.termCode + 100 - -# if (academicYear != previousAYCode) and (academicYear != currentAYCode) and (academicYear != nextATCode): -# abort(400) - -# PreviousAYTerms = generateTerms(previousAYCode) -# CurrentAYTerms = generateTerms(currentAYCode) -# NextAYTerms = generateTerms(nextATCode) - -# return (PreviousAYTerms, CurrentAYTerms, NextAYTerms) \ No newline at end of file + return allocation.isFinal \ No newline at end of file diff --git a/tests/code/test_manageDepartments.py b/tests/code/test_manageDepartments.py index 77cb2ee38..fc165a826 100644 --- a/tests/code/test_manageDepartments.py +++ b/tests/code/test_manageDepartments.py @@ -14,100 +14,4 @@ # The following test file is for testing the manageDepartments logic file and its associated functions and queries. -# It is designed to ensure that the manageDepartments functionality works as expected and returns the correct data. - - -@pytest.mark.integration -def test_generateAdjacentYears(): - with app.app_context(): - with mainDB.atomic() as transaction: - - ################ THE FIRST TEST ################ - ################ TESTING WHETHER THE generateAdjacentYear() FUNCTION WORKS AT ALL - g.openTerm, _ = Term.get_or_create( - termCode = 202500, - defaults={"termName": "AY 2025-2026", "isAcademicYear": True} - ) - - # - currentYear, previousYear, followingYear = generateAdjacentYears(202500) - - assert currentYear.termCode == 202500 - assert currentYear.termName == "AY 2025-2026" - - assert previousYear.termCode == 202400 - assert previousYear.termName == "AY 2024-2025" - - assert followingYear.termCode == 202600 - assert followingYear.termName == "AY 2026-2027" - - - ################ THE SECOND TEST ################ - ######### TESTING VARIOUS EDGE CASES ############ - with pytest.raises(BadRequest): - generateAdjacentYears(202300) - transaction.rollback() - - with pytest.raises(BadRequest): - generateAdjacentYears(202200) - transaction.rollback() - - with pytest.raises(BadRequest): - generateAdjacentYears(2025) - transaction.rollback() - - with pytest.raises(BadRequest): - generateAdjacentYears(True) - transaction.rollback() - - with pytest.raises(BadRequest): - generateAdjacentYears(False) - transaction.rollback() - - with pytest.raises(BadRequest): - generateAdjacentYears("SELECT lsf DELETE *") - transaction.rollback() - - - ################ THE THIRD TEST ################ - ############# MISCELLANEOUS TESTS ############# - g.openTerm, _ = Term.get_or_create( - termCode = 198200, - defaults={"termName": "AY 1982-1983", "isAcademicYear": True} - ) - - # Testing different years - currentYear, previousYear, followingYear = generateAdjacentYears(198200) - - assert currentYear.termCode == 198200 - assert currentYear.termName == "AY 1982-1983" - - assert previousYear.termCode == 198100 - assert previousYear.termName == "AY 1981-1982" - - assert followingYear.termCode == 198300 - assert followingYear.termName == "AY 1983-1984" - - # Testing data types - assert isinstance(currentYear.termCode, int) - assert isinstance(previousYear.termCode, int) - assert isinstance(followingYear.termCode, int) - - # Testing whether currentYear.termName is formatted correctly - assert currentYear.termName.split(" ")[0] == "AY" - assert previousYear.termName.split(" ")[0] == "AY" - assert followingYear.termName.split(" ")[0] == "AY" - - assert currentYear.termName.split(" ")[1] == "1982-1983" - assert previousYear.termName.split(" ")[1] == "1981-1982" - assert followingYear.termName.split(" ")[1] == "1983-1984" - - - # Testing the generateAdjacentYears() function without any parameters - currentYear, previousYear, followingYear = generateAdjacentYears() - - assert currentYear.termCode == 198200 - assert previousYear.termCode == 198100 - assert followingYear.termCode == 198300 - - transaction.rollback() \ No newline at end of file +# It is designed to ensure that the manageDepartments functionality works as expected and returns the correct data. \ No newline at end of file From 14b9365173fb10c3b3b669e275f46a32accaf06d Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 7 Aug 2026 15:13:47 -0400 Subject: [PATCH 86/92] Reflected on the changes Imran had suggested --- app/controllers/admin_routes/manageDepartments.py | 8 ++++---- app/controllers/main_routes/departmentPortal.py | 9 +++++---- app/logic/allocationRequest.py | 2 +- app/templates/main/allocationRequest.html | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 5c5cc3e33..ec1aef0a4 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -32,7 +32,7 @@ def manageDepartments(): currentUser = require_login() if not currentUser: # If the current user is not logged in return render_template('errors/403.html') - if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent): + if not currentUser.isLaborAdmin: if currentUser.student: return redirect('/laborHistory/' + currentUser.student.ID) elif currentUser.supervisor: @@ -104,7 +104,7 @@ def allocationReview(org=None, account=None): currentUser = require_login() if not currentUser: # If the current user is not logged in return render_template('errors/403.html') - if not (currentUser.isLaborAdmin or currentUser.isLaborDepartmentStudent): + if not currentUser.isLaborAdmin: if currentUser.student: return redirect('/laborHistory/' + currentUser.student.ID) elif currentUser.supervisor: @@ -117,13 +117,13 @@ def allocationReview(org=None, account=None): # checking if the allocation has already been approved if allocationExists(nextAY.termCode, dept, isFinal=True): - flash("You cannot reapprove an allocation request.", "danger") + flash("You cannot reapprove an allocation request.", "info") return redirect('/admin/manageDepartments/') # checking if the department has requested any allocation review if not allocationExists(nextAY.termCode, dept, isFinal=False): - flash(f"The {dept.DEPT_NAME} department has not requested an allocation review yet.", "danger") + flash(f"The {dept.DEPT_NAME} department has not requested an allocation review yet.", "info") return redirect('/admin/manageDepartments/') diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 19a114b77..2673a874b 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -22,7 +22,7 @@ def allocationRequest(org, account): # cheching if the user can visit this page - if not (g.currentUser.isLaborAdmin or g.currentUser.isLaborDepartmentStudent): + if not g.currentUser.isLaborAdmin: if not SupervisorDepartment.select().where( (SupervisorDepartment.supervisor == g.currentUser.supervisor) & (SupervisorDepartment.department == dept.departmentID) @@ -36,7 +36,7 @@ def allocationRequest(org, account): # checking if the allocation has already been approved (in other words, if an approved allocation exists) if allocationExists(nextAY.termCode, dept, isFinal=True): - flash(f"The allocation for the {nextAY.termName.split(' ')[1]} academic year has already been approved; therefore, you can no longer resubmit it.", "danger") + flash(f"The allocation for the {nextAY.termName.split(' ')[1]} academic year has already been approved; therefore, you can no longer resubmit it.", "info") return redirect('/admin/manageDepartments/') @@ -54,7 +54,8 @@ def allocationRequest(org, account): @main_bp.route('/allocationRequest/submit', methods=['POST']) def submitAllocationRequest(): getOrUpdateRequestedAllocation() - return redirect("/admin/manageDepartments") + submitter = Department.get(Department.departmentID == request.form.get("submitter", type=int, default=None)) + return redirect(f"/department/{submitter.ORG}/{submitter.ACCOUNT}") @main_bp.route('/department///positions', methods=['GET']) @@ -64,7 +65,7 @@ def managePositions(org, account): except DoesNotExist: return render_template('errors/404.html'), 404 - if not (g.currentUser.isLaborAdmin or g.currentUser.isLaborDepartmentStudent): + if not g.currentUser.isLaborAdmin: if not SupervisorDepartment.select().where( (SupervisorDepartment.supervisor == g.currentUser.supervisor) & (SupervisorDepartment.department == dept.departmentID) diff --git a/app/logic/allocationRequest.py b/app/logic/allocationRequest.py index 6315a6278..141d26986 100644 --- a/app/logic/allocationRequest.py +++ b/app/logic/allocationRequest.py @@ -15,7 +15,7 @@ def getOrUpdateRequestedAllocation(): # the list of the fields updated after submitting the allocation request updatedFields = { "termCode": nextAY, - "department": request.form.get("submitter", type=int, default=None), + "department": requester, "isFinal": False, "justification": request.form.get("justification", default=""), "primary_10": request.form.get("primary_10", type=int, default=None), diff --git a/app/templates/main/allocationRequest.html b/app/templates/main/allocationRequest.html index 15ae67bc8..48f63ae61 100644 --- a/app/templates/main/allocationRequest.html +++ b/app/templates/main/allocationRequest.html @@ -108,7 +108,7 @@

    - +
    From 215aa5b94b14608c0efc9adc7c7ad617134fa22f Mon Sep 17 00:00:00 2001 From: kurasova Date: Wed, 19 Aug 2026 14:59:21 -0400 Subject: [PATCH 87/92] Fixed the routing --- app/controllers/main_routes/departmentPortal.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 2673a874b..2989b6d64 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -37,7 +37,7 @@ def allocationRequest(org, account): # checking if the allocation has already been approved (in other words, if an approved allocation exists) if allocationExists(nextAY.termCode, dept, isFinal=True): flash(f"The allocation for the {nextAY.termName.split(' ')[1]} academic year has already been approved; therefore, you can no longer resubmit it.", "info") - return redirect('/admin/manageDepartments/') + return redirect(f'/department/{org}/{account}') # getting the current approved allocation From ca967a1d21db3fd7fd8c117bd78e5d44f74f2041 Mon Sep 17 00:00:00 2001 From: kurasova Date: Wed, 19 Aug 2026 16:44:13 -0400 Subject: [PATCH 88/92] Updated the name of the Labor Department --- app/templates/main/allocationRequest.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/templates/main/allocationRequest.html b/app/templates/main/allocationRequest.html index 48f63ae61..681bd3593 100644 --- a/app/templates/main/allocationRequest.html +++ b/app/templates/main/allocationRequest.html @@ -15,7 +15,7 @@

    - Submit an allocation request to the Labor Department + Submit an allocation request to Berea's Work Study Program Department

    @@ -103,7 +103,7 @@

    - This allocation request for {{nextAY.termName.split(" ")[1]}} can be updated by resubmission. However, once the Labor Office approves it, you can no longer change it. + This allocation request for {{nextAY.termName.split(" ")[1]}} can be updated by resubmission. However, once the Path to Purpose Office approves it, you can no longer change it.
    From 8279dc48ce14b6a7ce28a425372e8d2e524a45c8 Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 21 Aug 2026 15:01:26 -0400 Subject: [PATCH 89/92] Updated the getCurrentAndNextAY() function, according to Imran's suggestions --- app/logic/academicYearManager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/logic/academicYearManager.py b/app/logic/academicYearManager.py index f66efd15a..4838e6209 100644 --- a/app/logic/academicYearManager.py +++ b/app/logic/academicYearManager.py @@ -8,8 +8,7 @@ def getCurrentAndNextAY(): begins from the start of July). """ - currentYear = g.currentYear[0] - nextYear = currentYear + 1 + currentYear, nextYear = g.currentYear currentAYCode = currentYear * 100 nextAYCode = nextYear * 100 From 08880b7d952ab0803c0fc76e14c69af28769ce0f Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 21 Aug 2026 15:07:21 -0400 Subject: [PATCH 90/92] Fixed other issues, suggested by Imran --- app/controllers/admin_routes/manageDepartments.py | 2 +- app/controllers/main_routes/departmentPortal.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index ec1aef0a4..238eb2859 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -148,7 +148,7 @@ def approveAllocationReview(): currentAY, nextAY = getCurrentAndNextAY() # getting the ID of the user who approves the request - approverID = require_login().userID + approverID = g.currentUser.userID # getting the name of the requesting department requester = request.form.get("requester", type=int, default=None) diff --git a/app/controllers/main_routes/departmentPortal.py b/app/controllers/main_routes/departmentPortal.py index 2989b6d64..2a23ea140 100644 --- a/app/controllers/main_routes/departmentPortal.py +++ b/app/controllers/main_routes/departmentPortal.py @@ -21,7 +21,7 @@ def allocationRequest(org, account): return render_template('errors/404.html'), 404 - # cheching if the user can visit this page + # checking if the user can visit this page if not g.currentUser.isLaborAdmin: if not SupervisorDepartment.select().where( (SupervisorDepartment.supervisor == g.currentUser.supervisor) & From 2cc573debe3d0fbd2811d625ada10956315054a7 Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 21 Aug 2026 15:23:46 -0400 Subject: [PATCH 91/92] Turned g.currentYear into g.currentAY --- app/__init__.py | 50 ++++++++++++-------------- app/logic/academicYearManager.py | 2 +- tests/code/test_academicYearManager.py | 4 +-- 3 files changed, 25 insertions(+), 31 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index a131ae4fb..354dea139 100755 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,9 +1,10 @@ import os from datetime import date -from flask import Flask, g, request, session -from flask_bootstrap import Bootstrap + +from flask import Flask from flask_restful import Api -from playhouse.shortcuts import dict_to_model, model_to_dict +from flask_bootstrap import Bootstrap +from playhouse.shortcuts import model_to_dict, dict_to_model app = Flask(__name__) @@ -55,27 +56,17 @@ def new_execute(*args, **kwargs): from app.controllers.api_routes.routes import initializeApiRoutes initializeApiRoutes(api) +from flask import g from app.models.user import User -from app.login_manager import getUsernameFromEnv, require_login +from app.login_manager import require_login @app.before_request def load_user(): - requestUsername = getUsernameFromEnv(request.environ) - try: - cachedUser = session['currentUser'] - - if cachedUser.get('username') == requestUsername: - g.currentUser = dict_to_model(User, cachedUser) - return - - session.pop('currentUser', None) - session.pop('username', None) - + try: + g.currentUser = dict_to_model(User, session['currentUser']) except Exception as e: - pass - - user = require_login() - session['currentUser'] = model_to_dict(user) - g.currentUser = user + user = require_login() + session['currentUser'] = model_to_dict(user) + g.currentUser = user from app.models.term import Term from app.login_manager import getOpenTerm @@ -88,8 +79,12 @@ def load_openTerm(): if term: session['openTerm'] = model_to_dict(term) g.openTerm = term - -def getCurrentYear(): + +def getCurrentAY(): + """ + Returns the current academic year as a tuple + (the year when it starts, and the year when it ends) + """ today = date.today() year = today.year @@ -97,12 +92,11 @@ def getCurrentYear(): return year - 1, year return year, year + 1 - -@app.before_request -def load_currentYear(): - g.currentYear = getCurrentYear() - +@app.before_request +def load_currentAY(): + g.currentAY = getCurrentAY() + @app.context_processor def inject_environment(): return dict(env=app.config['ENV']) @@ -110,4 +104,4 @@ def inject_environment(): @app.before_request def queryCount(): if session: - session['querycount'] = 0 + session['querycount'] = 0 \ No newline at end of file diff --git a/app/logic/academicYearManager.py b/app/logic/academicYearManager.py index 4838e6209..400141db8 100644 --- a/app/logic/academicYearManager.py +++ b/app/logic/academicYearManager.py @@ -8,7 +8,7 @@ def getCurrentAndNextAY(): begins from the start of July). """ - currentYear, nextYear = g.currentYear + currentYear, nextYear = g.currentAY currentAYCode = currentYear * 100 nextAYCode = nextYear * 100 diff --git a/tests/code/test_academicYearManager.py b/tests/code/test_academicYearManager.py index 830c4a4e9..d180bf42d 100644 --- a/tests/code/test_academicYearManager.py +++ b/tests/code/test_academicYearManager.py @@ -8,7 +8,7 @@ @pytest.mark.integration def test_getCurrentAndNextAY(): with app.app_context(): - g.currentYear = (1967, 1968) + g.currentAY = (1967, 1968) currentYear, nextYear = getCurrentAndNextAY() assert currentYear.termCode == 196700 @@ -18,7 +18,7 @@ def test_getCurrentAndNextAY(): assert nextYear.termName == "AY 1968-1969" - g.currentYear = (2102, 2103) + g.currentAY = (2102, 2103) currentYear, nextYear = getCurrentAndNextAY() assert currentYear.termCode == 210200 From d5a9f61c52f0b986a3e4974e22d851466bfffbc6 Mon Sep 17 00:00:00 2001 From: kurasova Date: Fri, 21 Aug 2026 15:36:30 -0400 Subject: [PATCH 92/92] Allowed the user to type numbers for break hours --- app/static/js/allocationRequest.js | 4 +++- app/static/js/allocationReview.js | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/static/js/allocationRequest.js b/app/static/js/allocationRequest.js index be2a39f29..81102573a 100644 --- a/app/static/js/allocationRequest.js +++ b/app/static/js/allocationRequest.js @@ -1,7 +1,9 @@ $(document).ready( function(){ // not allowing users to type anything in a numeric spinner $("input[type='number'].breakHoursNumericSpinner").keypress(function (evt) { - evt.preventDefault(); + if (!/[0-9]/.test(evt.key)) { + evt.preventDefault(); + } }); $("input[type='number'].positionNumericSpinner").keypress(function (evt) { evt.preventDefault(); diff --git a/app/static/js/allocationReview.js b/app/static/js/allocationReview.js index d661f5a39..5c6c8428a 100644 --- a/app/static/js/allocationReview.js +++ b/app/static/js/allocationReview.js @@ -4,7 +4,9 @@ $(document).ready( function(){ // not allowing users to type anything in a numeric spinner $("input[type='number'].breakHoursNumericSpinner").keypress(function (evt) { - evt.preventDefault(); + if (!/[0-9]/.test(evt.key)) { + evt.preventDefault(); + } }); $("input[type='number'].positionNumericSpinner").keypress(function (evt) { evt.preventDefault();

    DepartmentCurrent Allocations
    ({{ academicYear }})
    StatusPositionsAllocation Status
    ({{ academicYear }})
    Break Hours
    (Used/Given)
    Requested Allocations
    ({{ nextAY.termName }})
    Actions
    - + + + Primary: + {{department.lsfCountPrimaries}} of {{department.totalPrimaries}} + +
    + + Secondary: + {{department.lsfCountSecondaries}} of {{department.totalSecondaries}} + +
    + + {{ breakHoursByDepartment.get(department.departmentID, 0) }} / {{ department.allocation.breakHours }} +
    - {{ breakHoursByDepartment.get(department.departmentID, 0) }} / {{ department.allocation.breakHours }} - @@ -176,9 +160,12 @@

    Manage Departments

    From 4c825425982291b8ebb380f3b04636d835b5298b Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Thu, 6 Aug 2026 11:21:42 -0400 Subject: [PATCH 77/92] Removed the concept of the previous AY in the backend --- app/controllers/admin_routes/manageDepartments.py | 3 +-- app/logic/manageDepartments.py | 11 ++--------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index 308af4942..d322d37ec 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -20,7 +20,6 @@ @admin.route('/admin/manageDepartments/', methods=['GET']) -@admin.route('/admin/manageDepartments/', methods=['GET']) def manageDepartments(academicYear = None): """ Returns the Manage Departments page, which allows the admin to view all the departments @@ -45,7 +44,7 @@ def manageDepartments(academicYear = None): academicYear = int(academicYear) - currentAY, previousAY, nextAY = generateAdjacentYears(academicYear) + currentAY, nextAY = generateAdjacentYears(academicYear) chosenAY = Term.get(Term.termCode == academicYear) breakHoursByDepartment = {row["department"]: str(row["totalHours"] or 0) for row in getUsedBreakHours(chosenAY)} diff --git a/app/logic/manageDepartments.py b/app/logic/manageDepartments.py index 41e96bd50..4f3fbb9d8 100644 --- a/app/logic/manageDepartments.py +++ b/app/logic/manageDepartments.py @@ -20,16 +20,14 @@ def generateAdjacentYears(academicYearTermCode=None): """ currentYear = g.openTerm.termCode // 100 - previousYear = currentYear - 1 nextYear = currentYear + 1 currentAYCode = currentYear * 100 - previousAYCode = previousYear * 100 nextAYCode = nextYear * 100 # Admins cannot view allocations for the years that are beyond the current, the previous, or the following academic year - if academicYearTermCode not in (None, currentAYCode, previousAYCode, nextAYCode): + if academicYearTermCode not in (None, currentAYCode, nextAYCode): abort(400) @@ -38,17 +36,12 @@ def generateAdjacentYears(academicYearTermCode=None): defaults={"termName": "AY {}-{}".format(currentYear, currentYear + 1), "isAcademicYear": True} ) - previousAY, _ = Term.get_or_create( - termCode=previousAYCode, - defaults={"termName": "AY {}-{}".format(previousYear, previousYear + 1), "isAcademicYear": True} - ) - nextAY, _ = Term.get_or_create( termCode=nextAYCode, defaults={"termName": "AY {}-{}".format(nextYear, nextYear + 1), "isAcademicYear": True} ) - return (currentAY, previousAY, nextAY) + return (currentAY, nextAY) From d98921ddd8890637c1dd81d198399dd7d7656b27 Mon Sep 17 00:00:00 2001 From: zhytkovd Date: Thu, 6 Aug 2026 11:26:40 -0400 Subject: [PATCH 78/92] More changes to the UI and backend related to the previous AY --- app/controllers/admin_routes/manageDepartments.py | 1 - app/templates/admin/manageDepartments.html | 8 +++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/controllers/admin_routes/manageDepartments.py b/app/controllers/admin_routes/manageDepartments.py index d322d37ec..c202513a9 100644 --- a/app/controllers/admin_routes/manageDepartments.py +++ b/app/controllers/admin_routes/manageDepartments.py @@ -64,7 +64,6 @@ def manageDepartments(academicYear = None): inactiveDepartments = inactiveDepartments, allSupervisors = allSupervisors, currentAY = currentAY, - previousAY = previousAY, nextAY = nextAY, academicYear = chosenAY.termName, breakHoursByDepartment = breakHoursByDepartment, diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index 21bac6dc7..88fcf297e 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -91,8 +91,9 @@

    Manage Departments

    DepartmentCurrent Allocations
    ({{ academicYear }})
    StatusCurrent Allocations
    ({{ academicYear }})
    Requested Allocations
    ({{ nextAY.termName }})
    Actions