Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
9d4acbf
Started working on HTML design
zhytkovd Jul 2, 2026
8deecb5
Finished initial design of the department page
zhytkovd Jul 4, 2026
9a14e6d
Fixed current year button position
zhytkovd Jul 6, 2026
6a5fec7
centered data in the table and div container
zhytkovd Jul 6, 2026
20e8718
Merge branch 'PeeWeeAttribute' into dep_portal_ad
ACBerea Jul 6, 2026
cde15a7
Merge branch 'development' into dep_portal_ad
zhytkovd Jul 6, 2026
7341b74
Fixed actions button: made it similar to the other dropdown menus
zhytkovd Jul 7, 2026
25b6251
Finished requested UI changes: renaming some elements
zhytkovd Jul 7, 2026
c6c7293
Added brake hours from the database to the UI
zhytkovd Jul 7, 2026
636dd71
Fixed brake hours given: removed double loop and filtered by AY
zhytkovd Jul 8, 2026
b615974
Added accurate academic year to manage_department.py by allocating te…
ACBerea Jul 8, 2026
ac35d1e
Changed test data to include break hours in the:changed them to secon…
zhytkovd Jul 9, 2026
d68e73f
Created a separate function for calculating total break hours used by…
zhytkovd Jul 9, 2026
d87910c
Began implementing logic for current year. Committing now for merging…
ACBerea Jul 10, 2026
b900ff1
Merge branch 'department-portal-base' into dep_portal_ad_ManageDepart…
ACBerea Jul 10, 2026
22d77f9
Fixed dummy data issues for testing purposes.
ACBerea Jul 10, 2026
e977ecb
Merge branch 'department-portal-base' into dep_portal_ad_ManageDepart…
ACBerea Jul 10, 2026
54021ef
Fixed dummy data error regarding left over current_year variable by r…
ACBerea Jul 10, 2026
56bd437
Added new demo data and improved sql joins.
zhytkovd Jul 10, 2026
978b098
Applied break hours used/give to HTML and fixed extracted data for us…
ACBerea Jul 10, 2026
6119490
Fixed button color for actions button to match other versons on the …
ACBerea Jul 13, 2026
20aa6e8
Added demo data for inactive departments in demo_data.py
ACBerea Jul 13, 2026
5747cd2
Changed isFinal from False to True for some demo-data in demo-data.py…
ACBerea Jul 13, 2026
11dd7ed
Added current year dropdown
zhytkovd Jul 13, 2026
d1844b7
Added functionality to Allocation Approval Status. The status button …
ACBerea Jul 13, 2026
f2d9266
Merge branch 'department-portal-base' of https://github.com/BCStudent…
ACBerea Jul 13, 2026
dfcf6f7
Fixed primary and secondary positions to use database information and…
ACBerea Jul 13, 2026
f90dd7e
Merge branch 'department-portal-base' of https://github.com/BCStudent…
ACBerea Jul 13, 2026
1796ba8
Fixed the bug with the active/inactive departments and changed the po…
zhytkovd Jul 13, 2026
86683f9
Fixed the bug with the active/inactive departments and changed the po…
zhytkovd Jul 13, 2026
582a225
Fixed Inactive Departments appearing outside of the inactive departme…
ACBerea Jul 13, 2026
c5d61d1
Changed the Allocation Approval Status to a label instead of a button…
ACBerea Jul 13, 2026
2417c95
Added additional demo data for testing for positions on the manage de…
ACBerea Jul 13, 2026
9daec6b
Fixed allocation approval status bittons
zhytkovd Jul 14, 2026
28dfb5d
Improved year dropdown label text and commented out print statements.
ACBerea Jul 14, 2026
f87ccce
Merge branch 'department-portal-base' of https://github.com/BCStudent…
ACBerea Jul 14, 2026
924c56e
Merge branch 'department-portal-base' of https://github.com/BCStudent…
ACBerea Jul 14, 2026
fb2356e
Merge branch 'department-portal-base' into dep_portal_ad_ManageDepart…
MImran2002 Jul 14, 2026
4ee93d2
Merge branch 'dep_portal_ad_ManageDepartments' of https://github.com/…
ACBerea Jul 14, 2026
f6fa196
Fixed breakHourByDepartments issue and adjusted duplicate dummy data.
ACBerea Jul 14, 2026
9900bd4
Added an empty test suite for the queries in manageDepartments.py
ACBerea Jul 15, 2026
c18f8a6
Moved queries from manage_departments.py routing file to manageDepart…
ACBerea Jul 15, 2026
acfda64
Merge branch 'department-portal-base' of https://github.com/BCStudent…
ACBerea Jul 15, 2026
0cb3918
Reordered functions in manageDepartments.py
ACBerea Jul 15, 2026
ad67b06
Began working on test suite for Manage Departments. Specifically desi…
ACBerea Jul 15, 2026
16c4cf3
Changed the accademic year to reflect the accurate currentTerm.
ACBerea Jul 16, 2026
90891ef
Removed --skipssl
ACBerea Jul 20, 2026
7cef723
Added terms for fall, spring, summer, previous, and next. These work …
ACBerea Jul 20, 2026
9a3ca62
Commented out seasonal terms for testing and added the proper terms t…
ACBerea Jul 20, 2026
bbc95b4
Merge remote-tracking branch 'origin/department-portal-base' into dep…
ACBerea Jul 20, 2026
d77c410
Improved termManagement.py to all for creating terms via a list based…
ACBerea Jul 21, 2026
bd5c4e6
Moved some of the code into logic + replaced eval() with get_json() i…
ArtemKurasov Jul 21, 2026
f03cc59
Users can now choose the previous and the following years
ArtemKurasov Jul 21, 2026
3c67429
Moved more functionality into logic + Deleted/Commented out all print…
ArtemKurasov Jul 22, 2026
5558a21
Changed buttons to be responsive relative to the screen size
zhytkovd Jul 22, 2026
5a93dfd
Merge branch 'dep_portal_ad_ManageDepartments' of https://github.com/…
zhytkovd Jul 22, 2026
0fd5459
Tried adding tests to the Manage Departments page
ArtemKurasov Jul 22, 2026
15358a4
Merge remote-tracking branch 'refs/remotes/origin/dep_portal_ad_Manag…
ArtemKurasov Jul 22, 2026
0de602b
Fixed default academic year in the url error
zhytkovd Jul 22, 2026
41e0c72
Fixed the issue with the default value of academicYear
ArtemKurasov Jul 22, 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
92 changes: 76 additions & 16 deletions app/controllers/admin_routes/manage_departments.py
Original file line number Diff line number Diff line change
@@ -1,50 +1,106 @@
from app.controllers.admin_routes import *
from app.models import term
from app.models.formHistory import FormHistory
from app.models.user import *
from app.models.supervisorDepartment import SupervisorDepartment
from app.login_manager import require_login
from app.logic.search import getSupervisorsForDepartment
from app.controllers.admin_routes import admin
from app.controllers.errors_routes.handlers import *
#from app.models.manageDepartments import *
from app.models.term import *
from flask_bootstrap import bootstrap_find_resource
from app.models.department import *
from app.models.allocation import *
from app.models.laborStatusForm import * #Do we need to import all?
from flask import request, redirect
from flask import jsonify
from playhouse.shortcuts import model_to_dict
from app.logic.tracy import Tracy
from datetime import date
from flask import g
from app.logic.manageDepartments import * # Reorganize imports to avoid circular import issues. This is a temporary fix, but it works for now.
from app.controllers.admin_routes.termManagement import createTerms



@admin.route('/admin/manageDepartments/', methods=['GET'])
@admin.route('/admin/manageDepartments/<academicYear>', methods=['GET']) # FIXME: The default value year should be the current academic year (Rather than waiting to be clicked it should be on the current year by default).

@admin.route('/admin/manageDepartments', methods=['GET'])
# @login_required
def manage_departments():
def manage_departments(academicYear = None):
"""
Updates the Labor Status Forms database with any new departments in the Tracy database on page load.
Returns the departments to be used in the HTML for the manage departments page.
"""

try:
currentUser = require_login()
if not currentUser: # Not logged in
return render_template('errors/403.html')
if not currentUser.isLaborAdmin: # Not an admin
if currentUser.student: # logged in as a student
return redirect('/laborHistory/' + currentUser.student.ID)
elif currentUser.supervisor:
return render_template('errors/403.html'), 403

checkAdmistratorRights()

if academicYear == None:
academicYear = g.openTerm.termCode
else:
academicYear = int(academicYear)

previousAYTerms, currentAYTerms, nextAYTerms = generateTermsForAdjacentYears(academicYear)
chosenAY = Term.get(Term.termCode == academicYear)


# Works. Should work without production data now.
# We've also thought about having a drop down menu to select the term once the academic year is selected. This should also include the ability to view the entire academic year.
# Given the new implementation of the term management page, we can now use the term management page to create terms for the academic year and then use this page to view the
# departments for that academic year. This will be a much more efficient way to manage the terms and departments.
# A concept of Currently Selected Term does not exist, yet. Implementing it here will make it so that the user can select a term and then view the departments for that term.
# This will be a much more efficient way to manage the terms and departments.


# For Testing Purposes. This will be removed once the term management page is fully implemented and the terms are created for the academic year.
# totalBreakSum = getUsedBreakHours(currentAY)
# for row in totalBreakSum:
# print(row['department'],int(row['totalHours']),row['termCode'])
# print(totalBreakSum)


# fallTerm = createdTerms[1]
# springTerm = createdTerms[4]
# summerTerm = createdTerms[6]
#
# print("******************",fallTerm.termName, springTerm.termName, summerTerm.termName, "**********************")
# print("******************",previousAY.termName, nextAY.termName, currentAY.termName, "**********************")


breakHoursByDepartment = {row["department"]: str(row["totalHours"] if row["totalHours"] is not None else 0) for row in getUsedBreakHours(chosenAY)}
# I think Scott wanted this to say NULL not zero, unsure.


activeDepartments = getActiveDepartmentsWithAllocation(chosenAY)
inactiveDepartments = Department.select().where(Department.isActive == False)


allocationStatus = {
department.departmentID: getAllocationStatus(chosenAY, department)
for department in activeDepartments
}

activeDepartments = Department.select().where(Department.isActive == True)
inactiveDepartments = Department.select().where(Department.isActive == False)
allSupervisors= Supervisor.select().order_by(Supervisor.LAST_NAME)

return render_template( 'admin/manageDepartments.html',
title = ("Manage Departments"),
activeDepartments = activeDepartments,
inactiveDepartments = inactiveDepartments,
allSupervisors = allSupervisors
allSupervisors = allSupervisors,
currentAY = currentAYTerms[0],
previousAY = previousAYTerms[0],
nextAY = nextAYTerms[0],
academicYear = chosenAY.termName,
breakHoursByDepartment = breakHoursByDepartment,
allocationStatus = allocationStatus
)
except Exception as e:
print("Error Loading all Departments", e)
return render_template('errors/500.html'), 500



@admin.route("/admin/manageDepartments/<departmentID>", methods=['GET'])
def getSupervisorsInDepartment(departmentID):
currentUser = require_login()
Expand All @@ -60,6 +116,8 @@ def getSupervisorsInDepartment(departmentID):
supervisors = [model_to_dict(supervisor) for supervisor in supervisors]
return jsonify(supervisors)



@admin.route('/admin/manageDepartments/removeSupervisorFromDepartment', methods=['POST'])
def removeSupervisorFromDepartment():
try:
Expand All @@ -85,13 +143,15 @@ def removeSupervisorFromDepartment():
print(f'Could not remove user from department: {e}')
return "", 500



@admin.route('/admin/complianceStatus', methods=['POST'])
def complianceStatusCheck():
"""
This function changes the compliance status in the database for labor status forms. It works in collaboration with the ajax call in manageDepartments.js
"""
try:
rsp = eval(request.data.decode("utf-8")) # This fixes byte indices must be intergers or slices error
rsp = request.get_json() # This fixes byte indices must be intergers or slices error
if rsp:
department = Department.get(int(rsp['deptName']))
department.departmentCompliance = not department.departmentCompliance
Expand Down
34 changes: 25 additions & 9 deletions app/controllers/admin_routes/termManagement.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,42 @@ def createTerms(termYear):
This function creates the terms for the given Academic Year
"""
code = termYear * 100
createdTerms = []
for i in range(8):
try:
if i == 0:
Term.create(termCode = code, termName = "AY {}-{}".format(termYear, termYear + 1), isAcademicYear=True)
term = Term.create(termCode = code, termName = "AY {}-{}".format(termYear, termYear + 1), isAcademicYear=True)
elif i == 1:
Term.create(termCode = (code + 11), termName = "Fall {}".format(termYear))
term = Term.create(termCode = (code + 11), termName = "Fall {}".format(termYear))
elif i == 7:
Term.create(termCode = (code + 4), termName = "Fall Break {}".format(termYear), isBreak=True)
term = Term.create(termCode = (code + 4), termName = "Fall Break {}".format(termYear), isBreak=True)
elif i == 2:
Term.create(termCode = (code + 1), termName = "Thanksgiving Break {}".format(termYear), isBreak=True)
term = Term.create(termCode = (code + 1), termName = "Thanksgiving Break {}".format(termYear), isBreak=True)
elif i == 3:
Term.create(termCode = (code + 2), termName = "Christmas Break {}".format( termYear), isBreak=True)
term = Term.create(termCode = (code + 2), termName = "Christmas Break {}".format( termYear), isBreak=True)
elif i == 4:
Term.create(termCode = (code + 12), termName = "Spring {}".format(termYear + 1))
term = Term.create(termCode = (code + 12), termName = "Spring {}".format(termYear + 1))
elif i == 5:
Term.create(termCode = (code + 3), termName = "Spring Break {}".format(termYear + 1), isBreak=True)
term = Term.create(termCode = (code + 3), termName = "Spring Break {}".format(termYear + 1), isBreak=True)
elif i == 6:
Term.create(termCode = (code + 13), termName = "Summer {}".format(termYear + 1), isBreak=True, isSummer=True)
term = Term.create(termCode = (code + 13), termName = "Summer {}".format(termYear + 1), isBreak=True, isSummer=True)
except IntegrityError as e:
pass
termCodeMap = {
0: code,
1: code + 11,
2: code + 1,
3: code + 2,
4: code + 12,
5: code + 3,
6: code + 13,
7: code + 4,
}
term = Term.get_or_none(Term.termCode == termCodeMap[i])

if term is not None:
createdTerms.append(term)

return createdTerms

@admin.route("/termManagement/setDate/", methods=['POST'])
def ourDate():
Expand Down
173 changes: 173 additions & 0 deletions app/logic/manageDepartments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
from app.controllers.admin_routes.termManagement import createTerms
from app.models.laborStatusForm import *
from app.models.formHistory import *
from app.models.allocation import *
from app.models.department import *
from app.models.term import *
from app.login_manager import require_login
from app.controllers.main_routes import departmentPortal
from flask import g, abort
from peewee import fn


def checkAdmistratorRights():
"""
Checks whether the current user has administrator rights to view the page.
"""
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 the currrent user is not an admin
if currentUser.student: # If the currrent user is logged in as a student
return redirect('/laborHistory/' + currentUser.student.ID)
elif currentUser.supervisor:
return render_template('errors/403.html'), 403



def getUsedBreakHours(term):
"""
Returns the total number of break hours used by each department for a given term.
"""
# totalBreakSum = FormHistory.select(fn.SUM(LaborStatusForm.contractHours)).where( (FormHistory.historyType_id == "Labor Status Form ") & (FormHistory.status_id == "Approved"))

totalBreakSum = (
FormHistory
.select(
LaborStatusForm.department,
LaborStatusForm.termCode.termCode,
fn.SUM(LaborStatusForm.contractHours).alias('totalHours')

)
.join(
LaborStatusForm,
on=(FormHistory.formID == LaborStatusForm.laborStatusFormID),
)
.join(
Term,
on = (LaborStatusForm.termCode == Term.termCode)
)
.where(
(FormHistory.historyType == "Labor Status Form") &
(FormHistory.status == "Approved") &
(LaborStatusForm.termCode == term)
)
.group_by(LaborStatusForm.department, LaborStatusForm.termCode).dicts()
)

print(list(totalBreakSum))

# correctLSF = LaborStatusForm.select().where(LaborStatusForm.termCode == term)

# print("Something2\n\n\n\n",list(correctLSF))

return totalBreakSum



def getActiveDepartmentsWithAllocation(term):
"""
Returns a list of active departments with allocations for the given term.
"""

# This was left just incase anything went wrong. Delete this if everything works as expected. Not necessary in current implementation.
# activeDepartments = Department.select().where(Department.isActive == True)
# allAllocations = Allocation.select().where(Allocation.termCode == currentAY)

activeDepartments = (Department
.select(Department, Allocation)
.join(Allocation)
.where(
Department.isActive == True,
Allocation.termCode == term.termCode
)
)

for dept in activeDepartments:
dept.totalPrimaries = (dept.allocation.primary_10 + dept.allocation.primary_12 + dept.allocation.primary_15 + dept.allocation.primary_20)
dept.totalSecondaries = (dept.allocation.secondary_5 + dept.allocation.secondary_10)

dept.lsfCountPrimaries = getLSFCountPrimaries(term, dept)
dept.lsfCountSecondaries = getLSFCountSecondaries(term, dept)

# # print("######################")
# print(f"COUNTS: {lsfCountSecondaries} ")
# print([lsf.formID for lsf in lsfCountPrimaries])
# print([lsf.formID for lsf in lsfCountSecondaries])

return activeDepartments



def getAllocationStatus(term, department):
"""
Returns the allocation status for a given department during a given term.
"""
allocation = Allocation.get(
(Allocation.termCode == term) &
(Allocation.department == department)
)
return allocation.isFinal



def getLSFCountPrimaries(currentTerm, department):
"""
Returns the count of primary LSFs for a given department during a given term. (WIP)
"""
lsfCountPrimaries = FormHistory.select().join(LaborStatusForm).join(Department).where(FormHistory.status == "Approved", LaborStatusForm.termCode == currentTerm.termCode, LaborStatusForm.jobType == "Primary", Department.departmentID == department.departmentID).count()
return lsfCountPrimaries



def getLSFCountSecondaries(currentTerm, department):
"""
Returns the count of secondary LSFs for a given department during a given term. (WIP)
"""
lsfCountSecondaries = FormHistory.select().join(LaborStatusForm).join(Department).where(FormHistory.status == "Approved", LaborStatusForm.termCode == currentTerm.termCode, LaborStatusForm.jobType == "Secondary", Department.departmentID == department.departmentID).count()
return lsfCountSecondaries



# def getTotalPositionHours



# def getCurrentSelectedTerm(currentTerm):
#'''
#Returns the current term code based on a the selected term from a dropdown menu in the manage departments page.
#Should only contain the current term, the next term, and the previous term.
#'''



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 ther terms for the current year, the previous year, and the future year.
"""


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)
6 changes: 6 additions & 0 deletions app/static/css/manageDepartments.css
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,10 @@ h1 {

#flasher{
z-index: 999999;
}

#activeDepartmentsTable th,
#activeDepartmentsTable td {
vertical-align: middle;
text-align: center;
}
Loading