Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9c5bd0b
Changes to adjustment forms
Kafui123 Sep 10, 2025
3e4b71c
Changes
Kafui123 Sep 11, 2025
e2705c0
Changes
Kafui123 Oct 21, 2025
fb9f27e
LaborStatusAdjustmentChanges
Kafui123 Oct 21, 2025
2a16f1d
Added an email link to adjusted form
Kafui123 Oct 31, 2025
9e40fa9
Changes
Kafui123 Oct 31, 2025
59b2dcd
Labor adjustment form updates
Kafui123 Nov 3, 2025
4be35ea
added logic for labor adjustment form.
Jonimz Nov 3, 2025
c87a088
Updates to the adjustment reason table
Kafui123 Nov 10, 2025
876dfe3
Updates to the adjustment forms
Kafui123 Nov 11, 2025
f39f1df
Changes
Kafui123 Nov 11, 2025
77b0eae
Updates to adjustment reasons
Kafui123 Nov 12, 2025
b03f14f
Got rid of the print statements
Kafui123 Nov 12, 2025
0a5125d
added some print statements to get rid of errors
Kafui123 Nov 13, 2025
c63003b
added some print statements to get rid of errors
Kafui123 Nov 13, 2025
862536c
Resolved the requested changes
Kafui123 Nov 13, 2025
e3c10be
Fixed pr comments
Kafui123 Nov 14, 2025
553a303
Addressed some bugs here
Kafui123 Nov 20, 2025
43a9c17
Brought back print statements
Kafui123 Nov 22, 2025
ecae396
Brought back print statements
Kafui123 Nov 22, 2025
b83bca7
Improved variable naming and removed redundancies.
ojmakinde Nov 24, 2025
0c5fe68
Merge branch 'development' of github.com:BCStudentSoftwareDevTeam/lsf…
ojmakinde Nov 24, 2025
8633056
reasons appearing on laborHistory as well
Kafui123 Nov 25, 2025
1722763
adjustments
Kafui123 Nov 25, 2025
e81926b
Chnaged the overload reason to match the font of the other items
Jonimz Nov 26, 2025
6174b3b
Modifications to how overload form is gotten
Kafui123 Dec 4, 2025
b09c68d
Fixed overload reason logic
Kafui123 Dec 4, 2025
43e9a80
resolved some issues
Kafui123 Dec 6, 2025
22d6a30
addressed problems
Kafui123 Dec 10, 2025
6954adf
added my name in demo_data
Kafui123 Dec 11, 2025
474e04b
Merge branch 'development' of https://github.com/BCStudentSoftwareDev…
MImran2002 Dec 18, 2025
12c8a24
worked on some base data and debugged datetime.date.today() problem.
MImran2002 Dec 19, 2025
65aa567
change some variable name
MImran2002 Dec 23, 2025
9525fdb
worked on debugging on form id mismatch, worked on overloadform being…
MImran2002 Jan 6, 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,4 @@ If you want to test with actual emails, use an email other than outlook to test
1. Set up two factor authentication on your Gmail (Security Settings)
2. Create an App Password through your Gmail. This 16 character password can only be viewed once, so make sure to save it. (NOTE: You won't have the option to create an app password unless step one is completed)
3. Inside of your secret_config.yaml file set the MAIL_USERNAME and MAIL_DEFAULT_SENDER as your Gmail, set the MAIL_PASSWORD as your new app password as, and set ALWAYS_SEND_MAIL as True. If you want emails to go to their real recipients, remove MAIL_OVERRIDE_ALL from your config or set it to "".
4. For testing purposes, change the email of the student and supervisor to match another email that can receive your test emails (or you can use MAIL_OVERRIDE_ALL to send everything to the address specified.
4. For testing purposes, change the email of the student and supervisor to match another email that can receive your test emails (or you can use MAIL_OVERRIDE_ALL to send everything to the address specified.
2 changes: 1 addition & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
PORT = 8080

if __name__ == "__main__":
# Print statements go to your log file in production; to your console while developing
# Print statements go to your log file in production; to your console while developing
print ("Running server at http://{0}:{1}/".format(IP, PORT))
app.run(host = IP, port = PORT, debug = True, threaded = True)

Expand Down
3 changes: 2 additions & 1 deletion app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
if 'use_banner' not in app.config.keys():
app.config['use_banner'] = (app.config['ENV'] in ('production','staging'))


# Record and output queries if requested
from flask import session
from peewee import BaseQuery
Expand All @@ -29,7 +30,7 @@ def new_execute(*args, **kwargs):
session['querycount'] = 0

session['querycount'] += 1
if app.config.get('show_queries'): # in case we selectively disable
if app.config.get('show_queries'):# in case we selectively disable
print("**Running query {}**".format(session['querycount']))
print(args[0])
return old_execute(*args, **kwargs)
Expand Down
2 changes: 2 additions & 0 deletions app/controllers/admin_routes/allPendingForms.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def allPendingForms(formType):

@admin.route('/admin/pendingForms/download', methods=['POST'])
def downloadAllPendingForms():
currentUser = require_login()
searchResult = retrieveFormSearchResult(request.form.get('downloadId'))
if not searchResult:
print(f"[ERROR] Missing or invalid download id was provided by the user.")
Expand Down Expand Up @@ -558,6 +559,7 @@ def sendEmail():
email = emailHandler(historyForm.formHistoryID)
link = makeThirdPartyLink("student", request.host, rsp['formHistoryID'])
email.LaborOverloadFormStudentReminder(link)


else:
if rsp['emailRecipient'] == 'SAASEmail':
Expand Down
5 changes: 3 additions & 2 deletions app/controllers/main_routes/alterLSF.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from app.models.supervisor import Supervisor
from app.login_manager import require_login
from app.logic.alterLSF import modifyLSF, adjustLSF

from app.logic.utils import makeThirdPartyLink

@main_bp.route("/alterLSF/<laborStatusKey>", methods=["GET"])
def alterLSF(laborStatusKey):
Expand Down Expand Up @@ -153,10 +153,11 @@ def submitAlteredLSF(laborStatusKey):
for formHistory in formHistoryIDs:
try:
email = emailHandler(formHistory)
link = makeThirdPartyLink("studentAdjustment", request.host, formHistory)
if "supervisor" in fieldsChanged:
email.laborStatusFormAdjusted(fieldsChanged["supervisor"]["newValue"])
else:
email.laborStatusFormAdjusted()
email.laborStatusFormAdjusted(link)
except Exception as e:
print("An error occured while attempting to send adjustment form emails: ", e)
message = "Your labor adjustment form(s) for {0} {1} have been submitted.".format(student.studentSupervisee.FIRST_NAME, student.studentSupervisee.LAST_NAME)
Expand Down
28 changes: 23 additions & 5 deletions app/controllers/main_routes/laborHistory.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import datetime
from datetime import date
import re
import types
from fpdf import FPDF
from urllib.parse import urlparse

from app.models import status
from flask import render_template , flash, redirect, url_for, request, g, session, jsonify, current_app, send_file, json, make_response
from flask_login import current_user, login_required
from app.controllers.main_routes import *
Expand Down Expand Up @@ -131,11 +132,18 @@ def populateModal(statusKey):
currentUser = require_login()
if not currentUser: # Not logged in
return render_template('errors/403.html'), 403

forms = (FormHistory.select().join(LaborReleaseForm, join_type=JOIN.LEFT_OUTER)
.where(FormHistory.formID == statusKey).order_by(FormHistory.createdDate.desc(), FormHistory.formHistoryID.desc()))
# Find the FormHistory entry that has an overloadForm (same as overload modal behavior)
overload_history = None
for f in forms:
if f.overloadForm is not None:
overload_history = f
break
statusForm = LaborStatusForm.get(LaborStatusForm.laborStatusFormID == statusKey)
student = Student.get(Student.ID == statusForm.studentSupervisee)
currentDate = datetime.date.today()
currentDate = date.today()
pendingformType = None
first = True # temp variable to determine if this is the newest form
for form in forms:
Expand Down Expand Up @@ -178,17 +186,27 @@ def populateModal(statusKey):
# Pending release or adjustment forms need the historyType known
if (form.releaseForm != None or form.adjustedForm != None) and form.status.statusName == "Pending":
pendingformType = form.historyType.historyTypeName

try:
currentPendingForm = FormHistory.select().where(
(FormHistory.formID == statusForm) &
((FormHistory.status == "Pending") | (FormHistory.status == "Pre-Student Approval"))
).get()

approveLink = f"{request.host_url}studentResponse/confirm?token={statusForm.confirmationToken}"

status = currentPendingForm.status.statusName
except Exception:
status = None


resp = make_response(render_template('snips/studentHistoryModal.html',
forms = forms,
currentUser = currentUser,
statusForm = statusForm,
currentDate = currentDate,
pendingformType = pendingformType,
buttonState = buttonState,
approveLink = approveLink,
status = status,
overload_history = overload_history,
))
return (resp)
except Exception as e:
Expand Down
99 changes: 75 additions & 24 deletions app/controllers/main_routes/studentOverloadApp.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from datetime import date, datetime
from app.models import overloadForm
from playhouse.shortcuts import model_to_dict
from flask import json, jsonify, request, redirect, url_for, abort, flash, g

from app.controllers.main_routes import *
from app.logic.emailHandler import*
from app.logic.utils import makeThirdPartyLink
Expand All @@ -17,19 +17,38 @@
@main_bp.route('/studentOverloadApp/<formHistoryId>', methods=['GET'])
def studentOverloadApp(formHistoryId):
currentUser = require_login()
overloadForm = FormHistory.get_by_id(formHistoryId)
# always load the clicked history entry
currentHistory = FormHistory.get_by_id(formHistoryId)

# STEP 2 — find the real FormHistory row that holds the overloadForm (reason)
overloadReasonHistory = (
FormHistory
.select()
.where(
(FormHistory.formID == currentHistory.formID) &
(FormHistory.overloadForm.is_null(False))
)
.order_by(FormHistory.createdDate.desc())
.first()
)

# STEP 3 — use whichever record has the overloadForm, fallback to clicked one
if overloadReasonHistory:
overloadHistory = overloadReasonHistory
else:
overloadHistory = currentHistory
if not currentUser.isLaborAdmin:
if not currentUser: # Not logged in
return render_template('errors/403.html'), 403
if not currentUser.student:
return render_template('errors/403.html'), 403
if currentUser.student.ID != overloadForm.formID.studentSupervisee.ID:
if currentUser.student.ID != overloadHistory.formID.studentSupervisee.ID:
return render_template('errors/403.html'), 403
lsfForm = (LaborStatusForm.select(LaborStatusForm, Student, Term, Department)
.join(Student, attr="studentSupervisee").switch()
.join(Term).switch()
.join(Department)
.where(LaborStatusForm.laborStatusFormID == overloadForm.formID)).get()
.where(LaborStatusForm.laborStatusFormID == overloadHistory.formID)).get()
prefillStudentName = lsfForm.studentSupervisee.FIRST_NAME + " "+ lsfForm.studentSupervisee.LAST_NAME
prefillStudentBnum = lsfForm.studentSupervisee.ID
prefillStudentCPO = lsfForm.studentSupervisee.STU_CPO
Expand All @@ -39,7 +58,6 @@ def studentOverloadApp(formHistoryId):
prefillPosition = lsfForm.POSN_TITLE
prefillHoursOverload = lsfForm.weeklyHours

listOfTerms = []
today = date.today()
termYear = today.year * 100
termsInYear = Term.select(Term).where(Term.termCode.between(termYear-1, termYear + 15))
Expand Down Expand Up @@ -84,10 +102,25 @@ def studentOverloadApp(formHistoryId):
totalCurrentHours += j.formID.weeklyHours
totalFormHours = totalCurrentHours + prefillHoursOverload

adjustedField, oldValue, newValue = (None, None, None)

if overloadHistory.adjustedForm:
adjustmentForm = overloadHistory.adjustedForm

adjustedField = adjustmentForm.fieldAdjusted
oldValue = adjustmentForm.oldValue
newValue = adjustmentForm.newValue

if adjustedField == "department":
oldValue = Department.get(Department.ORG == oldValue).DEPT_NAME
newValue = Department.get(Department.ORG == newValue).DEPT_NAME
return render_template( 'main/studentOverloadApp.html',
title=('student Overload Application'),
username = currentUser,
overloadForm = overloadForm,
overloadHistory = overloadHistory,
adjustedField = adjustedField,
oldValue = oldValue,
newValue = newValue,
prefillStudentName = prefillStudentName,
prefillStudentBnum = prefillStudentBnum,
prefillStudentCPO = prefillStudentCPO,
Expand All @@ -99,14 +132,14 @@ def studentOverloadApp(formHistoryId):
currentPrimary = formIDPrimary,
currentSecondary = formIDSecondary,
totalCurrentHours = totalCurrentHours,
totalFormHours = totalFormHours
totalFormHours = totalFormHours,
)


@main_bp.route('/studentOverloadApp/withdraw/<formHistoryId>', methods=['POST'])
def withdrawRequest(formHistoryId):
formHistory = FormHistory.get_by_id(formHistoryId)
if formHistory.historyType_id != "Labor Overload Form":
print("Somehow we reached a non-overload form history entry ({formHistoryId}) from studentOverloadApp.")
abort(500)

# send a withdrawal notification to student and supervisor
Expand All @@ -129,39 +162,57 @@ def updateDatabase(overloadFormHistoryID):
if not overloadReason:
abort(500)

oldStatus = Status.get(Status.statusName == "Pre-Student Approval")
# if status is pending that means we have an adjustment
# if status is "pre-student then it means we have an overload"
newStatus = Status.get(Status.statusName == "Pending")

overloadFormHistory = FormHistory.get(FormHistory.formHistoryID == overloadFormHistoryID)
originalFormHistory = (FormHistory.select()
.where(FormHistory.formID == overloadFormHistory.formID)
.where(FormHistory.status == oldStatus)
.where(FormHistory.historyType_id == "Labor Status Form")).get()

originalFormHistory = (
FormHistory
.select()
.where(FormHistory.formID == overloadFormHistory.formID.laborStatusFormID)
.where((FormHistory.status == "Pre-Student Approval") | (FormHistory.status == "Approved"))
.where(FormHistory.historyType_id.in_([
"Labor Status Form",
"Labor Overload Form",
"Labor Adjustment Form",
]))
.first())
overloadHistoryType, _ = HistoryType.get_or_create(
historyTypeName="Labor Overload Form"
)
with mainDB.atomic() as transaction:
# Update statuses
overloadForm = OverloadForm.create(studentOverloadReason=request.form.get("studentOverloadReason"))
if overloadFormHistory:
overloadFormHistory.overloadForm = overloadForm
overloadFormHistory.save()
else:
overloadFormHistory = FormHistory.create(
formID=originalFormHistory.formID,
historyType=overloadHistoryType,
overloadForm=overloadForm,
createdBy=g.currentUser,
createdDate=datetime.now().date(),
status=newStatus,
)
overloadFormHistory.status = newStatus
overloadFormHistory.save()
originalFormHistory.status = newStatus
originalFormHistory.save()

# Update base student confirmation
originalFormHistory.formID.studentResponseDate = datetime.now()
originalFormHistory.formID.studentConfirmation = True
originalFormHistory.formID.save()

# Update overload form
overloadForm = overloadFormHistory.overloadForm
overloadForm = overloadFormHistory.overloadForm # should now be non-None
overloadForm.studentOverloadReason = overloadReason
overloadForm.save()

overloadForm.save() # only needed if modified after create
email = emailHandler(overloadFormHistory.formHistoryID)
link = makeThirdPartyLink("Financial Aid", request.host, overloadFormHistory.formHistoryID)
email.overloadVerification("Financial Aid", link)

flash("Overload Request Submitted", "success")
return g.currentUser.student.ID

if g.currentUser.student:
return jsonify({"bnumber": g.currentUser.student.ID}), 200
return jsonify({"bnumber": None}), 200
except Exception as e:
print("ERROR: " + str(e))
abort(500)
1 change: 1 addition & 0 deletions app/controllers/main_routes/studentResponse.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ def confirm():
.join(FormHistory)
.where(LaborStatusForm.confirmationToken == token
,LaborStatusForm.studentSupervisee == g.currentUser.student))

try:
form = forms.get()
except DoesNotExist as e:
Expand Down
6 changes: 4 additions & 2 deletions app/logic/emailHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ def __init__(self, formHistoryKey):
# is the 'AttributeError' error. We expect to get the 'AttributeError',
# but if we get anything else then we want to print the error
if e.__class__.__name__ != "AttributeError":
print (e)
print(e)

def send(self, message: Message):
if app.config['ENV'] == 'production' or app.config['ALWAYS_SEND_MAIL']:
Expand Down Expand Up @@ -201,7 +201,8 @@ def laborStatusFormRejected(self):
self.checkRecipient("Labor Status Form Rejected For Student",
"Primary Position Labor Status Form Rejected")

def laborStatusFormAdjusted(self, newSupervisor=False):
def laborStatusFormAdjusted(self, link, newSupervisor=False):
self.link = link
self.checkRecipient("Labor Status Form Adjusted For Student",
"Labor Status Form Adjusted For Supervisor")
if newSupervisor:
Expand Down Expand Up @@ -430,3 +431,4 @@ def replaceText(self, form):
if self.laborStatusForm.studentExpirationDate: # handle old cases where we might not have a date
form = form.replace("@@StudentConfirmationExpiration@@", self.laborStatusForm.studentExpirationDate.strftime("%B %d, %Y"))
return(form)

3 changes: 1 addition & 2 deletions app/logic/statusFormFunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ def createOverloadFormAndFormHistory(rspFunctional, lsf, creatorID, host=None):
email = emailHandler(formOverload.formHistoryID)
link = makeThirdPartyLink("student", host, formOverload.formHistoryID)
email.LaborOverLoadFormSubmitted(link)


formHistory = FormHistory.create( formID = lsf.laborStatusFormID,
historyType = "Labor Status Form",
Expand Down Expand Up @@ -280,8 +281,6 @@ def createOverloadForm(newWeeklyHours, lsf, currentUser, adjustedForm=None, for
# This will delete an overload form after the hours are changed
elif previousTotalHours > 15 and newTotalHours <= 15: # If we were overloading and now we aren't
print(f"Trying to get formhistory with formID '{lsf.laborStatusFormID}' and history type: 'Labor Overload Form'")
# XXX this breaks if the overload was attached to a different form. ie, this form is the
# primary, but a secondary is what triggered the overload process
deleteOverloadForm = FormHistory.get((FormHistory.formID == lsf.laborStatusFormID) & (FormHistory.historyType == "Labor Overload Form"))
deleteOverloadForm = OverloadForm.get(OverloadForm.overloadFormID == deleteOverloadForm.overloadForm_id)
deleteOverloadForm.delete_instance() # This line also deletes the Form History since it's set to cascade up in the model file
Expand Down
2 changes: 1 addition & 1 deletion app/logic/userInsertFunctions.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def createStudentFromTracy(username=None, bnumber=None):
try:
return Student.get(Student.ID == tracyStudent.ID.strip())
except DoesNotExist:
#print('Could not find {0} {1} in Student table, creating new entry.'.format(tracyStudent.FIRST_NAME, tracyStudent.LAST_NAME))
print('Could not find {0} {1} in Student table, creating new entry.'.format(tracyStudent.FIRST_NAME, tracyStudent.LAST_NAME))
return Student.create(ID = tracyStudent.ID.strip(),
PIDM = tracyStudent.PIDM,
legal_name = tracyStudent.FIRST_NAME,
Expand Down
3 changes: 2 additions & 1 deletion app/logic/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ def makeThirdPartyLink(recipient, host, formHistoryId):
route = "admin/financialAidOverloadApproval"
if recipient == 'student':
route = "studentOverloadApp"

if recipient == "studentAdjustment":
route = "studentOverloadApp"
return f"http://{host}/{route}/{formHistoryId}"

def setReferrerPath():
Expand Down
1 change: 1 addition & 0 deletions app/models/adjustedForm.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ class AdjustedForm(baseModel):
oldValue = CharField()
newValue = CharField()
effectiveDate = DateField()

Loading