diff --git a/app/controllers/main_routes/main_routes.py b/app/controllers/main_routes/main_routes.py index 0d24e5ff..560d02fa 100755 --- a/app/controllers/main_routes/main_routes.py +++ b/app/controllers/main_routes/main_routes.py @@ -1,6 +1,7 @@ from flask import render_template, request, json, redirect, url_for, send_file, g, flash, jsonify from peewee import JOIN, DoesNotExist, fn from functools import reduce +from datetime import datetime, date import operator from app.models.department import Department @@ -11,6 +12,7 @@ from app.models.formHistory import FormHistory from app.models.term import Term from app.models.positionHistory import PositionHistory +from app.models.allocation import Allocation from app.controllers.admin_routes.allPendingForms import checkAdjustment from app.controllers.main_routes import main_bp @@ -22,6 +24,7 @@ from app.logic.banner import Banner from app.logic.getSupervisors import getSupervisors from app.logic.getPositions import getActivePositions +from app.logic.allocationManager import getBreakContracts, getContractedAllocations, getTotalAllocations @main_bp.route('/logout', methods=['GET']) @@ -82,6 +85,55 @@ def departmentPortal(org=None,account=None): positions = positionsList, posURL = posURL) +@main_bp.route('/department///allocations', methods=['GET']) +def allocationTable(org=None, account=None): + currentUser = g.currentUser + try: + dept = Department.get(Department.ORG == org, Department.ACCOUNT == account) + except (NameError, DoesNotExist): + return render_template('errors/404.html'), 404 + + if not currentUser.isLaborAdmin: + allowedDepartmentIds = [d.departmentID for d in getDepartmentsForSupervisor(currentUser)] + if dept.departmentID not in allowedDepartmentIds: + return render_template('errors/403.html'), 403 + + + currentDate = date.today() + if currentDate.month <= 6: + # If it is the spring semester, then the term code is 1 year behind. e.g. 2025-2026 term code is 202500. Thus the - 100 in the spring term. + # The (year * 100) turns the year into an AY term code, 2025 -> 202500. The + 12/11 turns it into a fall or spring term. + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12 - 100).get() + currentAY = Term.select().where(Term.termCode == currentDate.year * 100 - 100).get() + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11 - 100).get() + + else: + fallTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 11).get() + currentAY = Term.select().where(Term.termCode == currentDate.year * 100).get() + springTerm = Term.select().where(Term.termCode == currentDate.year * 100 + 12).get() + + allocationDict = getTotalAllocations(currentAY.termCode, dept) + fallContracts = getContractedAllocations(fallTerm.termCode, dept) + springContracts = getContractedAllocations(springTerm.termCode, dept) + + breakContracts = { + "total": 0, + "thanksgiving":getBreakContracts(currentAY.termCode + 1, dept), + "winter": getBreakContracts(currentAY.termCode + 2, dept), + "spring": getBreakContracts(currentAY.termCode + 3, dept), + "fall":getBreakContracts(currentAY.termCode + 4, dept), + "summer": getBreakContracts(currentAY.termCode + 13, dept) + } + breakContracts["total"] = sum(breakContracts.values()) + return render_template('main/allocationTable.html', + department = dept, + currentAY = currentAY, + allocations = allocationDict, + fallContracts = fallContracts, + springContracts = springContracts, + breakContracts = breakContracts) + + @main_bp.route('/supervisorPortal/download', methods=['POST']) def downloadSupervisorPortalResults(): ''' diff --git a/app/logic/allocationManager.py b/app/logic/allocationManager.py index f1c58cbc..794d0cf6 100644 --- a/app/logic/allocationManager.py +++ b/app/logic/allocationManager.py @@ -37,13 +37,36 @@ def getTotalAllocations(termCode: int, dept: int): "totalAllocations": (allocationObject["primary_10"] + allocationObject["primary_12"] + allocationObject["primary_15"] + allocationObject["primary_20"] + allocationObject["secondary_5"] + allocationObject["secondary_10"] )} return allocationDict -def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: int): +def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: int, AYtermCode: int = None): ''' This function counts the number of positions of a given type in a given department. For example, countContracts('secondary', 5, 202511, 1) returns the number of secondary 5-hour positions in the CS department for the 2025 Fall term. ''' academicYearCode = int(str(termCode)[:4] + "00") + + # This sets the date condition to determine whether the form is within the boundaries of the term + # Fall only contracts end before spring, spring contracts start after fall. + fallMonths = ["07","08","09","10","11","12"] + springMonths = ["01","02","03","04","05","06"] + if str(termCode).endswith("11"): + dateCondition = ( + (LaborStatusForm.endDate.month.in_(fallMonths)) | + (LaborStatusForm.startDate.month.in_(fallMonths) & # Reused check for year-long positions + LaborStatusForm.endDate.month.in_(springMonths)) + ) + elif str(termCode).endswith("12"): + dateCondition = ( + (LaborStatusForm.startDate.month.in_(springMonths)) | + (LaborStatusForm.startDate.month.in_(fallMonths) & + LaborStatusForm.endDate.month.in_(springMonths)) + ) + else: + dateCondition = ( + (LaborStatusForm.startDate.month.in_(fallMonths) & + LaborStatusForm.endDate.month.in_(springMonths)) + ) + lsfCountPositions = FormHistory.select( ).join(LaborStatusForm ).join(Department @@ -54,6 +77,7 @@ def countContracts(jobType: str, weeklyContractHours: int, termCode: int, dept: LaborStatusForm.jobType == jobType, # 'primary' or 'secondary' LaborStatusForm.weeklyHours == weeklyContractHours, # 5, 10, 12, 15, or 20 Department.departmentID == dept, + dateCondition, ).count() return lsfCountPositions @@ -105,4 +129,18 @@ 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 getBreakContracts(termCode, dept): + break_allocation = FormHistory.select(fn.SUM(LaborStatusForm.contractHours) + ).join(LaborStatusForm + ).where( + FormHistory.historyType == "Labor Status Form", + FormHistory.status.in_(["Approved", "Pending", "Pre-Student Approval"]), + LaborStatusForm.termCode == termCode, + LaborStatusForm.department == dept, + LaborStatusForm.contractHours.is_null(False)).scalar() + if break_allocation != None: + return break_allocation + else: + return 0 \ No newline at end of file diff --git a/app/static/css/allocationTable.css b/app/static/css/allocationTable.css new file mode 100644 index 00000000..dd1812c1 --- /dev/null +++ b/app/static/css/allocationTable.css @@ -0,0 +1,63 @@ +.grid-container { + display: grid; + grid-template-columns: 1fr 1fr; +} +.btn-success{ + align-self: center; + justify-self: end; +} +.card-header { + background-color: #efebeb; + margin-bottom: 7px; + height: 6rem; +} +.mb-0, .collapsed { + outline-color: none; + color: black; + font-size: 18px; + font-weight: 525; +} +.collapse{ + padding-left: 5% ; + padding-right: 5%; +} +.accordion{ + padding-left: 5%; + padding-right: 5%; +} +.table-striped { + table-layout:fixed; + width:100%; +} +.table-striped tbody tr:nth-of-type(odd) { + background-color: #f2f2f2; +} +.btn-info{ + justify-self:flex-end; + align-self: center; + margin-left: 4px +} +.accordion-arrow { + display: inline-block; + transition: transform 0.2s ease-in-out; +} +.btn.collapsed .accordion-arrow { + transform: rotate(-90deg); +} +.btn:not(.collapsed) .accordion-arrow { + transform: rotate(0deg); +} +.button-container { + display:flex; + justify-content:end; +} +.btn-block{ + align-items: center; + align-self: center; + text-align: center; + justify-content: center; + height: 100% +} +.termDisplay{ + margin-right:auto; +} \ No newline at end of file diff --git a/app/static/js/allocationTable.js b/app/static/js/allocationTable.js new file mode 100644 index 00000000..51a344a2 --- /dev/null +++ b/app/static/js/allocationTable.js @@ -0,0 +1,18 @@ +$(document).ready( function(){ + function initTable(selector) { + return $(selector).DataTable({ + pageLength: 25, + info: false, + lengthChange: false, + searching: false, + paging: false, + order: [] + }); + } + + const fallTermPrimaries = initTable('#fallTermPrimaries'); + const fallTermSecondaries = initTable('#fallTermSecondaries'); + const springTermPrimaries = initTable('#springTermPrimaries'); + const springTermSecondaries = initTable('#springTermSecondaries'); + const breakTable = initTable('#breakTable'); +}); \ No newline at end of file diff --git a/app/templates/main/allocationTable.html b/app/templates/main/allocationTable.html new file mode 100644 index 00000000..0e8c8650 --- /dev/null +++ b/app/templates/main/allocationTable.html @@ -0,0 +1,257 @@ +{% extends "base.html" %} +{% block styles %} + {{super()}} + + +{% endblock %} + +{% block scripts %} + {{super()}} + + + +{% endblock %} + +{% block app_content %} + +

{% if department %} {{department.DEPT_NAME}} Allocations {% else %} Choose a Department: {% endif %}

+ +
+

Current Term: {{currentAY.termName}}

+ Download Allocation History + Request Allocation +
+ +
+ + +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrimariesContractedAllocated
Total Contracts{{fallContracts["used_total"]}}{{allocations["totalAllocations"]}}
Total Primaries{{fallContracts["used_primaries"]}}{{allocations["totalPrimaries"]}}
10 Hour{{fallContracts["used_10"]}}{{allocations["primary_10"]}}
12 Hour{{fallContracts["used_12"]}}{{allocations["primary_12"]}}
15 Hour{{fallContracts["used_15"]}}{{allocations["primary_15"]}}
20 Hour{{fallContracts["used_20"]}}{{allocations["primary_20"]}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SecondariesContractedAllocated
Total Contracts{{fallContracts["used_total"]}}{{allocations["totalAllocations"]}}
Total Secondaries{{fallContracts["used_secondaries"]}}{{allocations["totalSecondaries"]}}
5 Hour{{fallContracts["used_5_sec"]}}{{allocations["secondary_5"]}}
10 Hour{{fallContracts["used_10_sec"]}}{{allocations["secondary_10"]}}
+
+
+ + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrimariesContractedAllocated
Total Contracts{{springContracts["used_total"]}}{{allocations["totalAllocations"]}}
Total Primaries{{springContracts["used_primaries"]}}{{allocations["totalPrimaries"]}}
10 Hour{{springContracts["used_10"]}}{{allocations["primary_10"]}}
12 Hour{{springContracts["used_12"]}}{{allocations["primary_12"]}}
15 Hour{{springContracts["used_15"]}}{{allocations["primary_15"]}}
20 Hour{{springContracts["used_20"]}}{{allocations["primary_20"]}}
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SecondariesContractedAllocated
Total Contracts{{springContracts["used_total"]}}{{allocations["totalAllocations"]}}
Total Secondaries{{springContracts["used_secondaries"]}}{{allocations["totalSecondaries"]}}
5 Hour{{springContracts["used_5_sec"]}}{{allocations["secondary_5"]}}
10 Hour{{springContracts["used_10_sec"]}}{{allocations["secondary_10"]}}
+
+
+ + +
+
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PrimariesContractedAllocated
Total Break Hours{{breakContracts['total']}}{{allocations["breakHours"]}}
Fall Break{{breakContracts['fall']}}
Thanksgiving{{breakContracts['thanksgiving']}}
Winter Break{{breakContracts['winter']}}
Spring{{breakContracts['spring']}}
Summer Term{{breakContracts['summer']}}
+
+
+ +
+{% endblock %} diff --git a/app/templates/main/departmentPortal.html b/app/templates/main/departmentPortal.html index e7786c85..a9b56e29 100644 --- a/app/templates/main/departmentPortal.html +++ b/app/templates/main/departmentPortal.html @@ -36,7 +36,7 @@

{% if department %} {{department.DEPT_NAME}} Portal {% e

Insert Allocations Card Here

diff --git a/database/demo_data.py b/database/demo_data.py index 1b1e7978..d1e500bb 100644 --- a/database/demo_data.py +++ b/database/demo_data.py @@ -41,8 +41,37 @@ "STU_CPO":"700", "LAST_POSN":"Media Technician", "LAST_SUP_PIDM":"7" + }, + { + "ID":"B00741361", + "PIDM":"99", + "FIRST_NAME":"Antonia", + "LAST_NAME":"Schmith", + "CLASS_LEVEL":"Freshman", + "ACADEMIC_FOCUS":"Computer Science", + "MAJOR":"Computer Science", + "PROBATION":"0", + "ADVISOR":"Scott Heggen", + "STU_EMAIL":"schmitha@berea.edu", + "STU_CPO":"777", + "LAST_POSN":"TA", + "LAST_SUP_PIDM":"7" + }, + { + "ID":"B00732363", + "PIDM":"58", + "FIRST_NAME":"Barbara", + "LAST_NAME":"Williams", + "CLASS_LEVEL":"Junior", + "ACADEMIC_FOCUS":"Computer Science", + "MAJOR":"Computer Science", + "PROBATION":"0", + "ADVISOR":"Jasmine Jones", + "STU_EMAIL":"williamsb@berea.edu", + "STU_CPO":"118", + "LAST_POSN":"TA", + "LAST_SUP_PIDM":"7" }, - { "ID":"B00730361", "PIDM":"1", @@ -105,7 +134,26 @@ "LAST_POSN":"Student Manager", "LAST_SUP_PIDM":"7" }, - ] + {"ID": "B00811617", "legal_name": "Chris Georgiev", "isActive": True, "PIDM": "8", "FIRST_NAME": "Chris", "LAST_NAME": "Georgiev"}, + {"ID": "B00815474", "legal_name": "Julius Fritz", "isActive": True, "PIDM": "9", "FIRST_NAME": "Julius", "LAST_NAME": "Fritz"}, + {"ID": "B12345223", "legal_name": "Subaru Natsuki", "isActive": True, "PIDM": "10", "FIRST_NAME": "Subaru", "LAST_NAME": "Natsuki"}, + {"ID": "B12345003", "legal_name": "Hatsune Miku", "isActive": True, "PIDM": "11", "FIRST_NAME": "Hatsune", "LAST_NAME": "Miku"}, + {"ID": "B12345772", "legal_name": "Michael Jackson", "isActive": True, "PIDM": "12", "FIRST_NAME": "Michael", "LAST_NAME": "Jackson"}, + {"ID": "B12345756", "legal_name": "Genji Overwatch", "isActive": True, "PIDM": "13", "FIRST_NAME": "Genji", "LAST_NAME": "Overwatch"}, + {"ID": "B12345759", "legal_name": "Mister Marlowe", "isActive": True, "PIDM": "14", "FIRST_NAME": "Mister", "LAST_NAME": "Marlowe"}, + {"ID": "B11231123", "legal_name": "Mister Thanksgiving", "isActive": True, "PIDM": "15", "FIRST_NAME": "Mister", "LAST_NAME": "Thanksgiving"}, + {"ID": "B12345762", "legal_name": "Alex Carter", "isActive": True, "PIDM": "16", "FIRST_NAME": "Alex", "LAST_NAME": "Carter"}, + {"ID": "B12345763", "legal_name": "Morgan Hayes", "isActive": True, "PIDM": "17", "FIRST_NAME": "Morgan", "LAST_NAME": "Hayes"}, + {"ID": "B12345764", "legal_name": "Jordan Brooks", "isActive": True, "PIDM": "18", "FIRST_NAME": "Jordan", "LAST_NAME": "Brooks"}, + {"ID": "B12345765", "legal_name": "Taylor Morgan", "isActive": True, "PIDM": "19", "FIRST_NAME": "Taylor", "LAST_NAME": "Morgan"}, + {"ID": "B12345766", "legal_name": "Casey Turner", "isActive": True, "PIDM": "20", "FIRST_NAME": "Casey", "LAST_NAME": "Turner"}, + {"ID": "B12345767", "legal_name": "Jamie Foster", "isActive": True, "PIDM": "21", "FIRST_NAME": "Jamie", "LAST_NAME": "Foster"}, + {"ID": "B12345768", "legal_name": "Riley Cooper", "isActive": True, "PIDM": "22", "FIRST_NAME": "Riley", "LAST_NAME": "Cooper"}, + {"ID": "B12345769", "legal_name": "Drew Bennett", "isActive": True, "PIDM": "23", "FIRST_NAME": "Drew", "LAST_NAME": "Bennett"}, + {"ID": "B12345770", "legal_name": "Logan Price", "isActive": True, "PIDM": "24", "FIRST_NAME": "Logan", "LAST_NAME": "Price"}, + {"ID": "B12345771", "legal_name": "Avery Sullivan", "isActive": True, "PIDM": "25", "FIRST_NAME": "Avery", "LAST_NAME": "Sullivan"}, + + ] tracyStudents = [ { "ID":"B00785329", @@ -586,6 +634,83 @@ "adjustmentCutOff": f"2025-09-01", "isBreak": 1, }, + { + "termCode": "202600", + "termName": "AY 2026-2027", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + }, + { + "termCode": "202601", + "termName": "Thanksgiving Break 2026", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + "isBreak": 1, + }, + { + "termCode": "202602", + "termName": "Christmas Break 2026", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + "isBreak": 1, + }, + { + "termCode": "202603", + "termName": "Spring Break 2027", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + "isBreak": 1, + }, + { + "termCode": "202604", + "termName": "Fall Break 2026", + "termStart": "2026-08-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + "isBreak": 1, + }, + { + "termCode": "202611", + "termName": "Fall 2026", + "termStart": "2026-08-01", + "termEnd": "2026-12-31", + "termState": 0, + "primaryCutOff": "2026-09-01", + "adjustmentCutOff": "2026-10-01", + }, + { + "termCode": "202612", + "termName": "Spring 2027", + "termStart": "2027-01-01", + "termEnd": "2027-05-01", + "termState": 0, + "primaryCutOff": "2027-02-01", + "adjustmentCutOff": "2027-03-01", + }, + { + "termCode": "202613", + "termName": "Summer 2027", + "termStart": "2027-05-02", + "termEnd": "2027-08-01", + "termState": 0, + "primaryCutOff": "2027-06-01", + "adjustmentCutOff": "2027-07-01", + "isSummer": 1, + }, ] Term.insert_many(terms).on_conflict_replace().execute() @@ -642,9 +767,469 @@ "createdBy_id": 1, "createdDate": f"2025-04-14", "status_id": "Approved" - }]).on_conflict_replace().execute() + }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + + "laborStatusFormID": 9, + "termCode_id": f"202500", + "studentName": "Genji Overwatch", + "studentSupervisee_id": "B12345756", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "overwtahc guy", + "POSN_CODE": "S61410", + "contractHours": 15, + "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() +LaborStatusForm.insert([{ + "laborStatusFormID": 60, + "termCode_id": f"202500", + "studentName": "Mister Marlowe", + "studentSupervisee_id": "B12345759", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Break Worker", + "POSN_CODE": "S61412", + "contractHours": 400, + "startDate": f"2025-04-01", + "endDate": "2025-09-01" + }]).on_conflict_replace().execute() +FormHistory.insert([{ + "formHistoryID": 60, + "formID_id": "60", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-04-14", + "status_id": "Approved" + }]).on_conflict_replace().execute() +LaborStatusForm.insert([{ + + "laborStatusFormID": 61, + "termCode_id": f"202501", + "studentName": "Mister Thanksgiving", + "studentSupervisee_id": "B11231123", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Thanksgiving Worker", + "POSN_CODE": "S61412", + "contractHours": 50, + "startDate": f"2025-11-23", + "endDate": "2025-12-01" + + }]).on_conflict_replace().execute() +FormHistory.insert([{ + "formHistoryID": 61, + "formID_id": "61", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": f"2025-11-01", + "status_id": "Approved" + }]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 62, + "termCode_id": "202611", + "studentName": "Alex Carter", + "studentSupervisee_id": "B12345762", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Office Assistant", + "POSN_CODE": "S61413", + "weeklyHours": 10, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 62, + "formID_id": "62", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 63, + "termCode_id": "202611", + "studentName": "Morgan Hayes", + "studentSupervisee_id": "B12345763", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Computer Lab Assistant", + "POSN_CODE": "S61414", + "weeklyHours": 15, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 63, + "formID_id": "63", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 64, + "termCode_id": "202611", + "studentName": "Jordan Brooks", + "studentSupervisee_id": "B12345764", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Help Desk Assistant", + "POSN_CODE": "S61415", + "weeklyHours": 20, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 64, + "formID_id": "64", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 65, + "termCode_id": "202611", + "studentName": "Taylor Morgan", + "studentSupervisee_id": "B12345765", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 0, + "POSN_TITLE": "Reception Assistant", + "POSN_CODE": "S61416", + "weeklyHours": 5, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 65, + "formID_id": "65", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 66, + "termCode_id": "202611", + "studentName": "Casey Turner", + "studentSupervisee_id": "B12345766", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 0, + "POSN_TITLE": "Library Assistant", + "POSN_CODE": "S61417", + "weeklyHours": 10, + "startDate": "2026-08-15", + "endDate": "2026-12-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 66, + "formID_id": "66", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-08-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 72, + "termCode_id": "202612", + "studentName": "Alex Carter", + "studentSupervisee_id": "B12345762", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Office Assistant", + "POSN_CODE": "S61413", + "weeklyHours": 10, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 72, + "formID_id": "72", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 73, + "termCode_id": "202612", + "studentName": "Morgan Hayes", + "studentSupervisee_id": "B12345763", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Computer Lab Assistant", + "POSN_CODE": "S61414", + "weeklyHours": 15, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 73, + "formID_id": "73", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +LaborStatusForm.insert([{ + "laborStatusFormID": 74, + "termCode_id": "202612", + "studentName": "Taylor Morgan", + "studentSupervisee_id": "B12345765", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 0, + "POSN_TITLE": "Reception Assistant", + "POSN_CODE": "S61416", + "weeklyHours": 5, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 74, + "formID_id": "74", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +# Student had a Fall-only position and receives a new Spring assignment. + +LaborStatusForm.insert([{ + "laborStatusFormID": 75, + "termCode_id": "202612", + "studentName": "Jordan Brooks", + "studentSupervisee_id": "B12345764", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Technology Assistant", + "POSN_CODE": "S61423", + "weeklyHours": 12, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 75, + "formID_id": "75", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 76, + "termCode_id": "202600", + "studentName": "Jordan Brooks", + "studentSupervisee_id": "B12345764", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Secondary", + "WLS": 1, + "POSN_TITLE": "Technology Assistant", + "POSN_CODE": "S61423", + "weeklyHours": 10, + "startDate": "2027-01-15", + "endDate": "2027-05-15" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 76, + "formID_id": "76", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-01-05", + "status_id": "Approved" +}]).on_conflict_replace().execute() + + +# Break Positions + +LaborStatusForm.insert([{ + "laborStatusFormID": 67, + "termCode_id": "202601", + "studentName": "Jamie Foster", + "studentSupervisee_id": "B12345767", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Thanksgiving Worker", + "POSN_CODE": "S61418", + "contractHours": 40, + "startDate": "2026-11-22", + "endDate": "2026-11-29" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 68, + "termCode_id": "202602", + "studentName": "Riley Cooper", + "studentSupervisee_id": "B12345768", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Christmas Worker", + "POSN_CODE": "S61419", + "contractHours": 120, + "startDate": "2026-12-20", + "endDate": "2027-01-03" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 69, + "termCode_id": "202603", + "studentName": "Drew Bennett", + "studentSupervisee_id": "B12345769", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Spring Break Worker", + "POSN_CODE": "S61420", + "contractHours": 80, + "startDate": "2027-03-07", + "endDate": "2027-03-14" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 70, + "termCode_id": "202604", + "studentName": "Logan Price", + "studentSupervisee_id": "B12345770", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Fall Break Worker", + "POSN_CODE": "S61421", + "contractHours": 24, + "startDate": "2026-10-11", + "endDate": "2026-10-18" +}]).on_conflict_replace().execute() + +LaborStatusForm.insert([{ + "laborStatusFormID": 71, + "termCode_id": "202613", + "studentName": "Avery Sullivan", + "studentSupervisee_id": "B12345771", + "supervisor_id": "B12361006", + "department_id": 1, + "jobType": "Primary", + "WLS": 1, + "POSN_TITLE": "Summer Worker", + "POSN_CODE": "S61422", + "contractHours": 320, + "startDate": "2027-05-15", + "endDate": "2027-08-01" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 67, + "formID_id": "67", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-11-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 68, + "formID_id": "68", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-12-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 69, + "formID_id": "69", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-02-20", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 70, + "formID_id": "70", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2026-10-01", + "status_id": "Approved" +}]).on_conflict_replace().execute() + +FormHistory.insert([{ + "formHistoryID": 71, + "formID_id": "71", + "historyType_id": "Labor Status Form", + "createdBy_id": 1, + "createdDate": "2027-04-15", + "status_id": "Approved" +}]).on_conflict_replace().execute() ############################# # admin Notes @@ -815,6 +1400,21 @@ "secondary_10": 1, "breakHours": 900, }, + { + "termCode": 202600, + "department": 1, + "isFinal": True, + "approvedOn": None, + "approvedBy": None, + "justification": "Maintaining current staffing levels while allowing for moderate growth in student employment opportunities.", + "primary_10": 6, + "primary_12": 5, + "primary_15": 4, + "primary_20": 2, + "secondary_5": 6, + "secondary_10": 1, + "breakHours": 600, + }, ] Allocation.insert_many(allocations).on_conflict_replace().execute() diff --git a/tests/code/test_allocationManger.py b/tests/code/test_allocationManager.py similarity index 66% rename from tests/code/test_allocationManger.py rename to tests/code/test_allocationManager.py index ea7da678..eb3a149f 100644 --- a/tests/code/test_allocationManger.py +++ b/tests/code/test_allocationManager.py @@ -48,6 +48,15 @@ def testTerm(): #destroy term.delete_instance() +@pytest.fixture +def testBreakTerm(): + #create + term = Term.create(termCode = 200601) + yield term + + #destroy + term.delete_instance() + @pytest.fixture def testAllocation(testDepartment,testTerm): #create @@ -128,8 +137,8 @@ def testLaborStatusForm(testStudent,testSupervisor,testDepartment,testTerm): POSN_CODE = "S61412", contractHours = 500, weeklyHours = 15, - startDate = "2025-04-01", - endDate = "2025-09-01", + startDate = "2006-08-01", + endDate = "2007-5-01", supervisorNotes = None, laborDepartmentNotes = None, studentConfirmation = True, @@ -142,6 +151,50 @@ def testLaborStatusForm(testStudent,testSupervisor,testDepartment,testTerm): #destroy laborStatusForm.delete_instance() +@pytest.fixture +def testBreakLaborStatusForm(testStudent,testSupervisor,testDepartment,testBreakTerm): + breakLaborStatusForm = LaborStatusForm.create( + studentName = "John Doe", + laborStatusFormID = 9898, + termCode = testBreakTerm.termCode, + studentSupervisee = testStudent.ID, + supervisor_id = testSupervisor.ID, + department = testDepartment.departmentID, + jobType = "Secondary", + WLS = 1, + POSN_TITLE = "Vacation Worker", + POSN_CODE = "S61412", + contractHours = 168, + weeklyHours = None, + startDate = "2006-04-01", + endDate = "2006-09-01", + supervisorNotes = None, + laborDepartmentNotes = None, + studentConfirmation = True, + confirmationToken = None, + studentExpirationDate = True, + studentResponseDate = True, + ) + + breakFormHistory = FormHistory.create( + formHistoryID = 9898, + formID_id = "9898", + historyType_id = "Labor Status Form", + releaseForm_id = None, + adjustedForm_id = None, + overloadForm_id = None, + createdBy_id = 1, + createdDate = "2006-02-01", + reviewedDate = "2006-03-01", + reviewedBy_id = 1, + status_id = "Approved", + rejectReason = None + ) + + yield breakLaborStatusForm, breakFormHistory + #destroy + breakLaborStatusForm.delete_instance() + @pytest.fixture def testFormHistory(testLaborStatusForm,testUser): formHistory = FormHistory.create( @@ -204,4 +257,42 @@ 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_getBreakContracts(testBreakLaborStatusForm, testBreakTerm, testDepartment,testTerm): + + # Test that the formHistory object exists + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == 168 + + # Test it with a higher amount of hours + testBreakLaborStatusForm[0].contractHours = 800 + testBreakLaborStatusForm[0].save() + + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == 800 + + # Test that it works even if weeklyHours and contractHours are set + testBreakLaborStatusForm[0].weeklyHours = 9999 + testBreakLaborStatusForm[0].save() + + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == 800 + + # Test that if the form is denied to not show up. + testBreakLaborStatusForm[1].status = "denied by student" + testBreakLaborStatusForm[1].save() + + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == 0 + + # Test if the term changes to a non-break term + testBreakLaborStatusForm[0].termCode = testTerm + testBreakLaborStatusForm[0].save() + testBreakLaborStatusForm[1].status = "Approved" + testBreakLaborStatusForm[1].save() + + breakContractHours = getBreakContracts(testBreakTerm, testDepartment) + assert breakContractHours == 0 + \ No newline at end of file diff --git a/tests/code/test_tracy.py b/tests/code/test_tracy.py index 730547dd..e2616863 100644 --- a/tests/code/test_tracy.py +++ b/tests/code/test_tracy.py @@ -18,8 +18,11 @@ def test_init(self, tracy): def test_getStudents(self, tracy): with app.app_context(): students = tracy.getStudents() - assert ['Elaheh','Guillermo','Jeremiah','Kat', 'Oluwagbayi', 'Test', 'Tyler'] == [s.FIRST_NAME for s in students] - assert ['718','300','420','420', '883', '700', '420'] == [s.STU_CPO for s in students] + for student in ['Elaheh','Guillermo','Jeremiah','Kat', 'Oluwagbayi', 'Test', 'Tyler']: + assert student in [s.FIRST_NAME for s in students] + for cpo in ['718','300','420','420', '883', '700', '420']: + assert cpo in [s.STU_CPO for s in students] + @pytest.mark.integration def test_getStudentFromBNumber(self, tracy):