Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
51d34b8
Made the Allocation Review page reflect actual requested allocations
ArtemKurasov Aug 3, 2026
e19a298
Merge branch 'dep_portal_ad_ManageDepartments' into Artem-alloc-review
ArtemKurasov Aug 3, 2026
09132b1
Updated the layout of the Allocation Review Form + updated the database
ArtemKurasov Aug 3, 2026
fc21863
Add popovers for the Allocation Review page
ArtemKurasov Aug 3, 2026
4d1bd2e
Forbade opening the Allocation Review page if the allocation is alrea…
ArtemKurasov Aug 3, 2026
f3e0339
Made the Approve button save the Allocation Review form
ArtemKurasov Aug 3, 2026
37e6fbb
THe approvedBy and approvedOn are now saved with the submission of an…
ArtemKurasov Aug 4, 2026
aa78f71
Created a new Allocation Request page
ArtemKurasov Aug 4, 2026
e4c0d29
Made the Allocation Request form get saved to the database
ArtemKurasov Aug 4, 2026
f3833f7
Cleaned up the code
ArtemKurasov Aug 4, 2026
083e556
Merge branch 'dep_portal_ad_ManageDepartments' into Artem-alloc-review
ArtemKurasov Aug 4, 2026
73b3ac4
Reformatted the code for the Allocation Review and Allocation Request…
ArtemKurasov Aug 5, 2026
5585ef0
Updated some of the logic
ArtemKurasov Aug 5, 2026
e4c7895
Added tests for the newly created logic functions
ArtemKurasov Aug 5, 2026
582a393
Made the getOrUpdateRequestedAllocation function not depend on the cu…
ArtemKurasov Aug 5, 2026
d8a6635
Added a new getCurrentAndNextYear() function + tests for it
ArtemKurasov Aug 5, 2026
731ab49
Updated the demo data and the getCurrentAndNextAY function to use g.c…
ArtemKurasov Aug 6, 2026
5a24ad2
Fixed some backend issues + Allowed inactive departments to submit al…
ArtemKurasov Aug 6, 2026
1fef151
Updated the styles for the Allocation Request and Allocation Review f…
ArtemKurasov Aug 6, 2026
dd23662
Allowed labor office students to access the Allocation Review page
ArtemKurasov Aug 7, 2026
3727d18
Allowed labor office students to access the Allocation Request page
ArtemKurasov Aug 7, 2026
0e217b9
Allowed labor office students to access other pages for admins
ArtemKurasov Aug 7, 2026
677adc0
Merge branch 'dep_portal_ad_ManageDepartments' into Artem-alloc-review
ArtemKurasov Aug 7, 2026
3e83e9d
Moved from the generateAdjacentYears() function to getCurrentAndNextAY
ArtemKurasov Aug 7, 2026
14b9365
Reflected on the changes Imran had suggested
ArtemKurasov Aug 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -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__)
Expand Down Expand Up @@ -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
Expand All @@ -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'])
Expand All @@ -87,4 +111,3 @@ def inject_environment():
def queryCount():
if session:
session['querycount'] = 0

86 changes: 68 additions & 18 deletions app/controllers/admin_routes/manageDepartments.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -16,11 +16,13 @@
from app.models.laborStatusForm import *

from app.logic.manageDepartments import *
from app.logic.allocationManager import allocationExists
from app.logic.academicYearManager import getCurrentAndNextAY



@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.
Expand All @@ -36,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)}

Expand Down Expand Up @@ -98,12 +92,14 @@ def allocationReview(org=None, account=None):
the Manage Departments page.
"""

# 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):
abort(404)


# Checking admin rights
currentUser = require_login()
if not currentUser: # If the current user is not logged in
Expand All @@ -114,9 +110,63 @@ 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
_, _, nextAY = generateAdjacentYears()
# The generateAdjacentYears() function returns a tuple of three elements, and we only need the third value

return render_template('admin/allocationReview.html', department = dept, nextAY = nextAY)
# Retrieving the current and following academic years
currentAY, nextAY = getCurrentAndNextAY()


# checking if the allocation has already been approved
if allocationExists(nextAY.termCode, dept, isFinal=True):
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.", "info")
return redirect('/admin/manageDepartments/')


# getting the current and the requested allocations
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)


return render_template('admin/allocationReview.html',
department = dept,
nextAY = nextAY,
currentAlloc = currentAlloc,
requestedAlloc = requestedAlloc
)



@admin.route('/admin/allocationReview/approve', methods=['POST'])
def approveAllocationReview():

# Retrieving the current and following academic years
currentAY, nextAY = getCurrentAndNextAY()

# 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)

# 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=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()

return redirect("/admin/manageDepartments")
55 changes: 54 additions & 1 deletion app/controllers/main_routes/departmentPortal.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,62 @@
from flask import render_template, g
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
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 getOrUpdateRequestedAllocation
from app.logic.allocationManager import allocationExists
from app.logic.academicYearManager import getCurrentAndNextAY


@main_bp.route('/department/<org>/<account>/allocations/request', methods=['GET'])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reroute it to managedepartment instead

def allocationRequest(org, account):

# 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 SupervisorDepartment.select().where(
(SupervisorDepartment.supervisor == g.currentUser.supervisor) &
(SupervisorDepartment.department == dept.departmentID)
).exists():
return render_template('errors/403.html'), 403


# Retrieving the current and following academic years
currentAY, nextAY = getCurrentAndNextAY()


# checking if the allocation has already been approved (in other words, if an approved allocation exists)
if allocationExists(nextAY.termCode, dept, isFinal=True):
flash(f"The allocation for the {nextAY.termName.split(' ')[1]} academic year has already been approved; therefore, you can no longer resubmit it.", "info")
return redirect('/admin/manageDepartments/')


# getting the current approved allocation
currentAlloc = Allocation.get_or_none(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()
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/<org>/<account>/positions', methods=['GET'])
def managePositions(org, account):
Expand Down
27 changes: 27 additions & 0 deletions app/logic/academicYearManager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
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 (note that a new academic year
begins from the start of July).
"""

currentYear = g.currentYear[0]
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)
14 changes: 10 additions & 4 deletions app/logic/allocationManager.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -105,4 +105,10 @@ 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
return usedPositions

def allocationExists(termCode: int, dept: int, isFinal: bool):
"""
Checks if there is an allocation that matches certain criteria.
"""
return bool(Allocation.get_or_none(Allocation.termCode == termCode, Allocation.department == dept, Allocation.isFinal == isFinal))
37 changes: 37 additions & 0 deletions app/logic/allocationRequest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
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, nextAY = getCurrentAndNextAY()

requester = request.form.get("submitter", type=int, default=None) # the requesting department

# the list of the fields updated after submitting the allocation request
updatedFields = {
"termCode": nextAY,
"department": requester,
"isFinal": False,
"justification": request.form.get("justification", default=""),
"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
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()
Loading