From 51d34b843b8070d35028f30d3706f381f21477be Mon Sep 17 00:00:00 2001 From: kurasova Date: Mon, 3 Aug 2026 10:17:41 -0400 Subject: [PATCH 01/22] 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 308af494..14830dd8 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 73b929e3..682a3d39 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 02/22] 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 14830dd8..29112ef4 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 eb83877a..02c53aae 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 2d72c706..184357c3 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 682a3d39..d5697569 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 b7a59297..afcf9672 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 03/22] 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 184357c3..5de5ca06 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 49c14f10..d661f5a3 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 d5697569..a1bea09a 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 04/22] 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 29112ef4..d1b00f38 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 05/22] 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 d1b00f38..dcc05b79 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 a1bea09a..e2334cd7 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 06/22] 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 dcc05b79..94d4fb2c 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 e2334cd7..70caa344 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 afcf9672..201f2ee4 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 07/22] 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 94d4fb2c..ea4d0928 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 00000000..c34f1f89 --- /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 00000000..be2a39f2 --- /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 70caa344..78cf93fb 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 00000000..42cd8efc --- /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 08/22] 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 ea4d0928..e910f0f7 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 78cf93fb..b1556117 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 42cd8efc..bf5a81e0 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 09/22] 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 e910f0f7..c7563d44 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 c34f1f89..622dc831 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 5de5ca06..5c366124 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 b1556117..e1a70b13 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 bf5a81e0..e7024cd8 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 10/22] 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 c7563d44..40b5c314 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 b0c33f5e..1618e7ff 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 f1c58cbc..4890aec2 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 00000000..5fb08153 --- /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 11/22] 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 40b5c314..bb9edf9a 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 1618e7ff..405a68eb 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 4890aec2..16d70753 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 5fb08153..ad15001c 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 12/22] 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 ea7da678..6d15b58e 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 00000000..0323ddd7 --- /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 13/22] 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 ad15001c..20feb025 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 0323ddd7..f06c4557 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 14/22] 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 bb9edf9a..ef10f6da 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 405a68eb..6146c674 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 00000000..8953f487 --- /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 20feb025..6315a627 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 00000000..3cd08744 --- /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 731ab491370d7cccbfbf38077cef2fd74a19a1c0 Mon Sep 17 00:00:00 2001 From: kurasova Date: Thu, 6 Aug 2026 11:26:44 -0400 Subject: [PATCH 15/22] 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 11c90a0a..a131ae4f 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 6146c674..0088d1be 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 8953f487..f66efd15 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 201f2ee4..979f49ce 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 3cd08744..830c4a4e 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 16/22] 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 ef10f6da..921c2855 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 0088d1be..7d7547cf 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 e1a70b13..a0805878 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 e7024cd8..546e151a 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 17/22] 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 622dc831..b476b79e 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 5c366124..c7e5dffe 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 a0805878..732273a8 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 546e151a..15ae67bc 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 18/22] 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 921c2855..8d65cd3a 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 02c53aae..79d6426b 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 19/22] 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 7d7547cf..ab801645 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 20/22] 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 8d65cd3a..613c5705 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 ab801645..19a114b7 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 21/22] 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 bae2be8d..5c5cc3e3 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 4f3fbb9d..9f823c78 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 77cb2ee3..fc165a82 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 22/22] 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 5c5cc3e3..ec1aef0a 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 19a114b7..2673a874 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 6315a627..141d2698 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 15ae67bc..48f63ae6 100644 --- a/app/templates/main/allocationRequest.html +++ b/app/templates/main/allocationRequest.html @@ -108,7 +108,7 @@

- +