diff --git a/app/controllers/admin_routes/manage_departments.py b/app/controllers/admin_routes/manage_departments.py index 6b16877c..a6ee6a14 100644 --- a/app/controllers/admin_routes/manage_departments.py +++ b/app/controllers/admin_routes/manage_departments.py @@ -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/', 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/", methods=['GET']) def getSupervisorsInDepartment(departmentID): currentUser = require_login() @@ -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: @@ -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 diff --git a/app/controllers/admin_routes/termManagement.py b/app/controllers/admin_routes/termManagement.py index 8a17d169..30e7673f 100644 --- a/app/controllers/admin_routes/termManagement.py +++ b/app/controllers/admin_routes/termManagement.py @@ -37,26 +37,42 @@ def createTerms(termYear): This function creates the terms for the given Academic Year """ code = termYear * 100 + createdTerms = [] for i in range(8): try: if i == 0: - Term.create(termCode = code, termName = "AY {}-{}".format(termYear, termYear + 1), isAcademicYear=True) + term = Term.create(termCode = code, termName = "AY {}-{}".format(termYear, termYear + 1), isAcademicYear=True) elif i == 1: - Term.create(termCode = (code + 11), termName = "Fall {}".format(termYear)) + term = Term.create(termCode = (code + 11), termName = "Fall {}".format(termYear)) elif i == 7: - Term.create(termCode = (code + 4), termName = "Fall Break {}".format(termYear), isBreak=True) + term = Term.create(termCode = (code + 4), termName = "Fall Break {}".format(termYear), isBreak=True) elif i == 2: - Term.create(termCode = (code + 1), termName = "Thanksgiving Break {}".format(termYear), isBreak=True) + term = Term.create(termCode = (code + 1), termName = "Thanksgiving Break {}".format(termYear), isBreak=True) elif i == 3: - Term.create(termCode = (code + 2), termName = "Christmas Break {}".format( termYear), isBreak=True) + term = Term.create(termCode = (code + 2), termName = "Christmas Break {}".format( termYear), isBreak=True) elif i == 4: - Term.create(termCode = (code + 12), termName = "Spring {}".format(termYear + 1)) + term = Term.create(termCode = (code + 12), termName = "Spring {}".format(termYear + 1)) elif i == 5: - Term.create(termCode = (code + 3), termName = "Spring Break {}".format(termYear + 1), isBreak=True) + term = Term.create(termCode = (code + 3), termName = "Spring Break {}".format(termYear + 1), isBreak=True) elif i == 6: - Term.create(termCode = (code + 13), termName = "Summer {}".format(termYear + 1), isBreak=True, isSummer=True) + term = Term.create(termCode = (code + 13), termName = "Summer {}".format(termYear + 1), isBreak=True, isSummer=True) except IntegrityError as e: - pass + termCodeMap = { + 0: code, + 1: code + 11, + 2: code + 1, + 3: code + 2, + 4: code + 12, + 5: code + 3, + 6: code + 13, + 7: code + 4, + } + term = Term.get_or_none(Term.termCode == termCodeMap[i]) + + if term is not None: + createdTerms.append(term) + + return createdTerms @admin.route("/termManagement/setDate/", methods=['POST']) def ourDate(): diff --git a/app/logic/manageDepartments.py b/app/logic/manageDepartments.py new file mode 100644 index 00000000..75baebc6 --- /dev/null +++ b/app/logic/manageDepartments.py @@ -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) diff --git a/app/static/css/manageDepartments.css b/app/static/css/manageDepartments.css index 81fcfb05..df77648f 100755 --- a/app/static/css/manageDepartments.css +++ b/app/static/css/manageDepartments.css @@ -34,4 +34,10 @@ h1 { #flasher{ z-index: 999999; +} + +#activeDepartmentsTable th, +#activeDepartmentsTable td { + vertical-align: middle; + text-align: center; } \ No newline at end of file diff --git a/app/templates/admin/manageDepartments.html b/app/templates/admin/manageDepartments.html index ddb1b043..46d54408 100755 --- a/app/templates/admin/manageDepartments.html +++ b/app/templates/admin/manageDepartments.html @@ -1,136 +1,291 @@ -{% extends "base.html" %} - -{% block styles %} -{{super()}} - - -{% endblock %} - -{% block scripts %} -{{super()}} - - - -{% endblock %} - -{% block app_content %} +{% extends "base.html" %} {% block styles %} {{super()}} + + +{% endblock %} {% block scripts %} {{super()}} + + + +{% endblock %} {% block app_content %}
Click to Skip
-
- Click to Skip -
-
-

Manage Departments

+
+ Click to Skip +
+
+

Manage Departments

-

Position descriptions are up to date.

-

Position descriptions are not up to date

+

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

-
-
-
+

+ + Position descriptions are up to date. +

+

+ + Position descriptions are not up to date +

+ +
+
+
+ + +
+
+
+ +
-
- -
-
-
-
+
-
- - - - - - - - {% for department in inactiveDepartments %} - - - - {% endfor %} - -
Department
{{department.DEPT_NAME}}({{department.ORG}}, {{department.ACCOUNT}})
-
-
-
+
+ + +
+ +
+ + + + + + + + + + +
+
+
+
+ + +
+ + + +
+ + + + + + + + + + + + + {% for department in activeDepartments %} + + + + + + + + + + + + + + {% endfor %} + +
DepartmentStatusPositionsAllocation Approval Status {{ academicYear }}Break Hours Used/GivenActions
+ {{department.DEPT_NAME}}({{department.ORG}}, + {{department.ACCOUNT}}) + + + +

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

+

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

+
+ + + + + {{ breakHoursByDepartment.get(department.departmentID, 0) }} / {{ department.allocation.breakHours }} + + + +
+ +
+ + + + + + + + {% for department in inactiveDepartments %} + + + + {% endfor %} + +
Department
+ {{department.DEPT_NAME}}({{department.ORG}}, + {{department.ACCOUNT}}) +
+
+
+
+
- + -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/database/demo_data.py b/database/demo_data.py index de604762..92146c10 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -459,34 +459,41 @@ ############################# # Department ############################# +############################# +# Active Departments +############################# departments = [ { "departmentID":1, "DEPT_NAME": "Computer Science", "ACCOUNT": "6740", "ORG": "2114", - "departmentCompliance": 1 + "departmentCompliance": 1, + "isActive": 1 }, { "departmentID":2, "DEPT_NAME": "Technology and Applied Design", "ACCOUNT": "6740", "ORG": "2147", - "departmentCompliance": 1 + "departmentCompliance": 1, + "isActive": 1 }, { "departmentID":3, "DEPT_NAME": "Mathematics", "ACCOUNT": "6740", "ORG": "2150", - "departmentCompliance": 1 + "departmentCompliance": 1, + "isActive": 1 }, { "departmentID":4, "DEPT_NAME": "Biology", "ACCOUNT": "6740", "ORG": "2107", - "departmentCompliance": 1 + "departmentCompliance": 1, + "isActive": 1 }, { "departmentID":5, @@ -495,8 +502,51 @@ "ORG": "4022", "departmentCompliance": 1, "isActive": 1 + }, +############################# +# Inactive Departments +############################# + + { + "departmentID":6, + "DEPT_NAME": "Agriculture and Natural Resources", + "ACCOUNT": "6740", + "ORG": "1441", + "departmentCompliance": 1, + "isActive": 0 + }, + { + "departmentID":7, + "DEPT_NAME": "Art and Art History", + "ACCOUNT": "6740", + "ORG": "2004", + "departmentCompliance": 1, + "isActive": 0 + }, + { + "departmentID":8, + "DEPT_NAME": "Asian Studies", + "ACCOUNT": "6740", + "ORG": "9801", + "departmentCompliance": 1, + "isActive": 0 + }, + { + "departmentID":9, + "DEPT_NAME": "Appalachian Studies", + "ACCOUNT": "6740", + "ORG": "8787", + "departmentCompliance": 1, + "isActive": 0 + }, + { + "departmentID":10, + "DEPT_NAME": "Music", + "ACCOUNT": "6740", + "ORG": "4805", + "departmentCompliance": 1, + "isActive": 0 } - ] Department.insert_many(departments).on_conflict_replace().execute() print(" * departments added") @@ -506,6 +556,8 @@ ############################# +print("Current year:", "termName") + terms = [ { "termCode": f"202000", @@ -594,6 +646,321 @@ }]).on_conflict_replace().execute() +############################# +# Create Active Labor Status Form for the Break Term +############################# + +# cs department + +LaborStatusForm.insert([{ + "laborStatusFormID": 6, + "termCode_id": f"202500", + "studentName": "Pizza Taker", + "studentSupervisee_id": "B12345773", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 15, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 6, + "formID_id": "6", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 7, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 3, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 7, + "formID_id": "7", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + + + +# labor department + +LaborStatusForm.insert([{ + "laborStatusFormID": 4, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 5, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 4, + "formID_id": "4", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 5, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 5, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 5, + "formID_id": "5", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +# Biology Department + +LaborStatusForm.insert([{ + "laborStatusFormID": 8, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 4, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 8, + "formID_id": "8", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 9, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 4, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 9, + "formID_id": "9", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +# Mathematics Department + +LaborStatusForm.insert([{ + "laborStatusFormID": 10, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 3, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 10, + "formID_id": "10", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 11, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 3, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 11, + "formID_id": "11", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +#Technology and Applied Design Department + +LaborStatusForm.insert([{ + "laborStatusFormID": 12, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 2, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 12, + "formID_id": "12", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 13, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 2, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 13, + "formID_id": "13", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 14, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 2, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 14, + "formID_id": "14", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 15, + "termCode_id": f"202500", + "studentName": "Elaheh Jamali", + "studentSupervisee_id": "B00730361", + "supervisor_id": "B12361006", + "department_id": 2, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Media Technician", + "POSN_CODE": "S61409", + "contractHours": 5, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 15, + "formID_id": "15", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() ############################# # admin Notes @@ -682,7 +1049,7 @@ { "termCode": 202500, "department": 2, - "isFinal": False, + "isFinal": True, "approvedOn": None, "approvedBy": None, "justification": "Increase in student enrollment due to exodous from CS department", @@ -697,7 +1064,7 @@ { "termCode": 202500, "department": 1, - "isFinal": False, + "isFinal": True, "approvedOn": None, "approvedBy": None, "justification": "We are hiring more students to help with the increased workload in the department", @@ -727,7 +1094,7 @@ { "termCode": 202500, "department": 5, - "isFinal": False, + "isFinal": True, "approvedOn": None, "approvedBy": None, "justification": "Due to rapid department growth, we need to hire more students to help with the increased workload", diff --git a/tests/code/test_manageDepartments.py b/tests/code/test_manageDepartments.py new file mode 100644 index 00000000..86e20605 --- /dev/null +++ b/tests/code/test_manageDepartments.py @@ -0,0 +1,172 @@ +import pytest +from app import app +from flask_wtf.csrf import CSRFProtect +import json +from app.models import mainDB +from app.models.laborStatusForm import LaborStatusForm +from app.models.department import Department +from app.models.term import Term +from app.models.formHistory import FormHistory +from app.models.allocation import Allocation +from app.logic.manageDepartments import * +from werkzeug.exceptions import BadRequest +from unittest.mock import patch +from app.controllers.admin_routes.manage_departments import manage_departments + + +# 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_ManageDepartmentsPrimaryandSecondary(): + with mainDB.atomic() as transaction: + + assert True + + testingDept = Department.get_or_create(DEPT_NAME="Computer Science", ACCOUNT="6740", ORG="2114") + testingTerm = Term.get_or_create( + termCode=f"{2028}00", + termName=f"AY {2028}-{2029}", + termStart=f"{2028}-08-01", + termEnd=f"{2029}-05-01", + termState=0, + primaryCutOff=f"{2028}-09-01", + adjustmentCutOff=f"{2029}-10-01" + ) + # Might need an additional allocation wherer isFinal is True for testing purposes. + testingAllocation = Allocation.get_or_create( + termCode=testingTerm.termCode, + department=testingDept.departmentID, + isFinal=False, + approvedOn=None, + approvedBy=None, + justification="Downscaling due to decrease in student enrollment caused by current economic conditions", + primary_10= 2, + primary_12= 2, + primary_15= 1, + primary_20= 0, + secondary_5= 1, + secondary_10= 0, + breakHours= 260, + ) + # Might need to create different test data for different lsf statuses. + testingLSF = LaborStatusForm.get_or_create( + laborStatusFormID=2, + termCode_id=testingTerm.termCode, + studentName="Alex Bryant", + studentSupervisee_id="B00841417", + supervisor_id="B12361006", + department_id=testingDept.departmentID, + jobType="Primary", + WLS=1, + POSN_TITLE="Student Programmer", + POSN_CODE="S61407", + weeklyHours=10, + startDate=f"2028-04-01", + endDate=f"2029-09-01", + studentConfirmation=True + ) + # Might need to create different test data for different form statuses. + testingFormHistory = FormHistory.get_or_create( + formHistoryID=2, + formID_id="2", + historyType_id="Labor Status Form", + createdBy_id=1, + createdDate=f"2025-04-14", + status="Active" + ) + + assert True + + # transaction.rollback() + + testCreation = {"", + "",} + + testReset = {"resetConfirmation": True} + + with app.test_request_context( "/manage_departments", method="POST", data=testCreation): + app.config['WTF_CSRF_ENABLED'] = False + app.config['show_queries'] = False + + + with app.test_request_context( "/manage_departments", method="POST", data=testReset): + app.config['WTF_CSRF_ENABLED'] = False + app.config['show_queries'] = False + + + + + + + + + + + + + + + + + + + + + + + + + +@pytest.mark.integration +def test_checkAdmistratorRights(): + with app.test_request_context(), patch("app.login_manager.require_login", return_value="pearcej"): + + response, status = checkAdmistratorRights() + assert status == 403 + + with app.test_request_context(), patch("app.login_manager.require_login", return_value="samantha"): + + response, status = checkAdmistratorRights() + assert status == 403 + +@pytest.mark.integration +def test_getUsedBreakHours(): + with mainDB.atomic() as transaction: + ... + +@pytest.mark.integration +def test_getActiveDepartmentsWithAllocation(): + ... + +@pytest.mark.integration +def test_getAllocationStatus(): + ... + +@pytest.mark.integration +def test_getLSFCountPrimaries(): + ... + +@pytest.mark.integration +def test_getLSFCountSecondaries(): + ... + +@pytest.mark.integration +def test_generateTermsForAdjacentYears(): + with app.app_context(): + assert generateTermsForAdjacentYears(g.openTerm.termCode) == (generateTerms(g.openTerm.termCode - 100), generateTerms(g.openTerm.termCode), generateTerms(g.openTerm.termCode + 100)) + assert generateTermsForAdjacentYears(g.openTerm.termCode-100) == (generateTerms(g.openTerm.termCode - 100), generateTerms(g.openTerm.termCode), generateTerms(g.openTerm.termCode + 100)) + assert generateTermsForAdjacentYears(g.openTerm.termCode+100) == (generateTerms(g.openTerm.termCode - 100), generateTerms(g.openTerm.termCode), generateTerms(g.openTerm.termCode + 100)) + + with pytest.raises(BadRequest): + generateTermsForAdjacentYears(197200) + + with pytest.raises(BadRequest): + generateTermsForAdjacentYears(True) + + with pytest.raises(BadRequest): + generateTermsForAdjacentYears("197200") + + with pytest.raises(BadRequest): + generateTermsForAdjacentYears(1.2) \ No newline at end of file