Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
9bd90f6
added changes from previous branch to avoid all files being commited
conwelld Jul 21, 2026
56d5a29
fixed some pr comments
conwelld Jul 21, 2026
faf1957
fixed some pr comments for css styling
conwelld Jul 21, 2026
1204269
fixed useless code
conwelld Jul 21, 2026
0f166c2
fixed department code
conwelld Jul 21, 2026
6f5850a
We have change the hard coded part with variable able to be flexible …
Jul 22, 2026
813f398
Temporary allocation logic will be replaced by the official shared se…
Jul 22, 2026
7584ffa
fix the review comment from Minran, all of them
DanielRukwasha Jul 27, 2026
e9a4415
Merge branch 'department-portal-base' into new-allocation-card, resol…
DanielRukwasha Jul 28, 2026
20b424c
Fix import after allocationUtilization rename to getAllocation, add i…
DanielRukwasha Jul 29, 2026
ee0e066
Address PR review comments: consolidate getAllocation return dict, ex…
DanielRukwasha Jul 30, 2026
6abd704
Merge branch 'department-portal-base' into new-allocation-card, resol…
DanielRukwasha Jul 30, 2026
8618b68
Adopt approval-status filtering from UsedAllocFunction branch in getA…
DanielRukwasha Jul 30, 2026
a0ba238
Polish the allocation card: dynamic Fall/Spring term label, "X of Y" …
munsakad Aug 3, 2026
86bee45
Merge branch 'department-portal-base' into new-allocation-card
munsakad Aug 3, 2026
45b13e0
Fix duplicate main.managePositions endpoint left over from the depart…
munsakad Aug 3, 2026
74bdd3a
Rework allocation rows to the "N hr: X contracts (out of Y allocation…
munsakad Aug 3, 2026
a7ca474
Address allocation card review: camelCase naming, stacked layout, con…
munsakad Aug 4, 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 app/controllers/main_routes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ def injectGlobalData():
from app.controllers.main_routes import studentLaborEvaluation
from app.controllers.main_routes import search
from app.controllers.main_routes import studentResponse
from app.controllers.main_routes import departmentPortal
from app.controllers.main_routes import departmentPortal
36 changes: 36 additions & 0 deletions app/controllers/main_routes/main_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from app.models.laborStatusForm import LaborStatusForm
from app.models.formHistory import FormHistory
from app.models.term import Term
from app.models.allocation import Allocation
from app.models.positionHistory import PositionHistory

from app.controllers.admin_routes.allPendingForms import checkAdjustment
Expand All @@ -20,6 +21,7 @@
from app.login_manager import require_login, logout
from app.logic.getTableData import getDatatableData
from app.logic.banner import Banner
from app.logic.getAllocation import getDepartmentAllocationSummary
from app.logic.getSupervisors import getSupervisors
from app.logic.getPositions import getActivePositions

Expand Down Expand Up @@ -71,17 +73,51 @@ def departmentPortal(org=None,account=None):

supervisors, laborCoordinators = getSupervisors(dept)

allocationSummary = getDepartmentAllocationSummary(dept)
recentTerm = allocationSummary["term"]

if recentTerm:
try:
allocation = Allocation.select(Allocation, Term).join(Term).where(Allocation.department == dept, Allocation.termCode == recentTerm.termCode).get()
except DoesNotExist:
allocation = None
else:
allocation = None

positionsList, posURL = getActivePositions(dept)

return render_template('main/departmentPortal.html',
departments = departments,
department = dept,
allocation = allocation,
allocated = allocationSummary["allocated"],
used = allocationSummary["used"],
term = recentTerm,
currentSemester = allocationSummary["currentSemester"],
usedPositions = allocationSummary["usedPositions"],
breakHours = allocationSummary["breakHours"],
supervisors = supervisors,
laborCoordinators=laborCoordinators,
currentUser=currentUser,
positions = positionsList,
posURL = posURL)

@main_bp.route('/supervisorPortal/addUserToDept', methods=['GET', 'POST'])
Comment thread
munsakad marked this conversation as resolved.
def addUserToDept():
userDeptData = request.form
supervisorDeptRecord = SupervisorDepartment.get_or_none(supervisor = userDeptData['supervisorID'], department = userDeptData['departmentID'])
try:
if supervisorDeptRecord:
return "False"

else:
SupervisorDepartment.create(supervisor=userDeptData['supervisorID'], department=userDeptData['departmentID'])
return "True"

except Exception as e:
print(f'Could not add user to department: {e}')
return "", 500

@main_bp.route('/supervisorPortal/download', methods=['POST'])
def downloadSupervisorPortalResults():
'''
Expand Down
126 changes: 126 additions & 0 deletions app/logic/getAllocation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
from datetime import date

from peewee import fn
Comment thread
BrianRamsay marked this conversation as resolved.

from app.models.allocation import Allocation
Comment thread
DanielRukwasha marked this conversation as resolved.
from app.models.laborStatusForm import LaborStatusForm
from app.models.term import Term
Comment thread
DanielRukwasha marked this conversation as resolved.
from app.models.formHistory import FormHistory


def getCurrentSemesterLabel(term):
"""Return the Fall/Spring label (e.g. "Fall 2025") for the AY term's
current semester, picking the season from today's month."""
if not term:
return None
academicYear = int(str(term.termCode)[:4])
if date.today().month >= 8:
return f"Fall {academicYear}"
return f"Spring {academicYear + 1}"


def countWorkers(department, termCode, jobType, hoursBucket):
workerCount = (
LaborStatusForm.select()
.join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID))
.where(
LaborStatusForm.department == department,
LaborStatusForm.termCode == termCode,
LaborStatusForm.jobType == jobType,
LaborStatusForm.weeklyHours == hoursBucket,
LaborStatusForm.contractHours.is_null(True),
FormHistory.historyType == "Labor Status Form",
~(FormHistory.status % "Denied%"),
)
.count()
)
return workerCount


def getBreakHours(department, termCode):
breakHoursTotal = (
LaborStatusForm.select(fn.SUM(LaborStatusForm.contractHours))
.join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID))
.where(
LaborStatusForm.department == department,
LaborStatusForm.termCode == termCode,
FormHistory.historyType == "Labor Status Form",
FormHistory.status == "Approved",
)
.scalar()
) or 0
return breakHoursTotal


def getDepartmentAllocationSummary(department):
"""Return allocation-utilization values for a department's most recent term."""
result = {
"term": None,
"currentSemester": None,
"allocated": 0,
"used": 0,
"usedPositions": {
"used10": 0,
"used12": 0,
"used15": 0,
"used20": 0,
"usedSecondary5": 0,
"usedSecondary10": 0,
},
"breakHours": 0,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

when Chris's allocationManager is merge you will have to revisit the logic


departmentAllocations = list(
Allocation.select(Allocation, Term).join(Term).where(Allocation.department == department)
)
if not departmentAllocations:
return result

recentTerm = Term.order_by_term([a.termCode for a in departmentAllocations], reverse=True)[0]
termCode = recentTerm.termCode
result["term"] = recentTerm
result["currentSemester"] = getCurrentSemesterLabel(recentTerm)

totalPositions = (
Allocation.select(
fn.SUM(Allocation.primary_10)
+ fn.SUM(Allocation.primary_12)
+ fn.SUM(Allocation.primary_15)
+ fn.SUM(Allocation.primary_20)
+ fn.SUM(Allocation.secondary_5)
+ fn.SUM(Allocation.secondary_10)
)
.where(
Allocation.department == department,
Allocation.termCode == termCode,
)
.scalar()
)
result["allocated"] = totalPositions or 0

usedAllocation = (
LaborStatusForm.select()
.join(FormHistory, on=(FormHistory.formID == LaborStatusForm.laborStatusFormID))
.where(
LaborStatusForm.department == department,
LaborStatusForm.termCode == termCode,
LaborStatusForm.contractHours.is_null(True),
FormHistory.historyType == "Labor Status Form",
~(FormHistory.status % "Denied%"),
)
.count()
)
result["used"] = usedAllocation

result["usedPositions"] = {
"used10": countWorkers(department, termCode, "Primary", 10),
"used12": countWorkers(department, termCode, "Primary", 12),
"used15": countWorkers(department, termCode, "Primary", 15),
"used20": countWorkers(department, termCode, "Primary", 20),
"usedSecondary5": countWorkers(department, termCode, "Secondary", 5),
"usedSecondary10": countWorkers(department, termCode, "Secondary", 10),
}

result["breakHours"] = getBreakHours(department, termCode)

return result
70 changes: 70 additions & 0 deletions app/static/css/departmentPortal.css
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,64 @@
font-size: 3rem;
color:#6e6e6e;
}
.bi-clock {
Comment thread
munsakad marked this conversation as resolved.
border: 1px solid #c0c0c0;
border-radius: 8px;
padding: 3px 3.5px 1.5px 3.5px;
font-size: 3rem;
color:#6e6e6e;
}
.bi-info-circle {
padding: 3px 3.5px 1.5px 3.5px;
font-size: 1.5rem;
vertical-align: middle;
color:#6e6e6e;
}
.allocation-table-wrapper {
overflow-x: auto;
margin: 10px 0;
}
.allocation-summary {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: baseline;
gap: 0 1rem;
}
.allocation-summary h4 {
margin: 10px 0;
}
.allocation-columns {
display: flex;
flex-wrap: wrap;
gap: 0 2rem;
}
.allocation-table {
/* wraps onto its own line when the card is too narrow, instead of shrinking */
flex: 1 1 180px;
border-collapse: collapse;
font-size: 1.2em;
}
.allocation-table th,
.allocation-table td {
text-align: left;
white-space: nowrap;
padding: 4px 10px 4px 0;
line-height: 1.3;
}
.allocation-table th {
font-weight: 700;
padding-top: 10px;
}

.bi-people-fill { /* Bootstrap Icon for Members Card */
border: 1px solid #c0c0c0;
border-radius: 8px;
padding: 3px 3.5px 1.5px 3.5px;
font-size: 3rem;
color:#6e6e6e;
}

.card-group {
gap: 1rem;
}
Expand All @@ -52,3 +110,15 @@
flex-direction: column;
}
}

/* Narrow card: stack Secondary below Primary, and the position count below the
term, rather than shrinking the text to keep them side by side. */
@media (min-width: 1200px) and (max-width: 1450px), (max-width: 480px) {
.allocation-summary {
flex-direction: column;
gap: 0;
}
.allocation-columns .allocation-table {
flex-basis: 100%;
}
}
4 changes: 4 additions & 0 deletions app/static/js/departmentPortal.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ $(document).ready(function() {
window.location = `/department/${deptData.org}/${deptData.account}`;
});
});

$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
48 changes: 45 additions & 3 deletions app/templates/main/departmentPortal.html
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,52 @@ <h2 class="text-center">{% if department %} {{department.DEPT_NAME}} Portal {% e
{% if department %}
<div class="card-row g-3">
<div class="col-12 col-lg-4 ">
<section class="card" aria-labelledby="current_allocation-title">
<p> Insert Allocations Card Here </p>
<section class="card" aria-labelledby="allocationTitle">
<div class="card-body">
<div class="media">
<div class="media-left media-middle">
<span aria-hidden="true" style="color: #424242; font-size: 24px;">
<i class="bi bi-clock"></i>
</span>
</div>
<div class="media-body media-middle">
<h3 id="allocationTitle">Current Allocations</h3>
</div>
{% macro allocationRow(hours, used, allocated) -%}
<tr><td>{{ hours }} hr: {{ used }} contract{{ 's' if used != 1 else '' }}<br>(out of {{ allocated }} allocation{{ 's' if allocated != 1 else '' }})</td></tr>
{%- endmacro %}
<div class="allocation-table-wrapper">
<div class="allocation-summary">
<h4>{{ currentSemester if currentSemester else "No term data" }} <i class="bi bi-info-circle" data-toggle="tooltip" data-placement="top" title="All values are shown as Contracted/Allocated"></i></h4>
<h4>{{used}} contracted of {{allocated or 0}} allocated Positions</h4>
</div>
<div class="allocation-columns">
<table class="allocation-table">
<thead>
<tr><th>Primary</th></tr>
</thead>
<tbody>
{{ allocationRow(10, usedPositions.used10, allocation.primary_10) }}
{{ allocationRow(12, usedPositions.used12, allocation.primary_12) }}
{{ allocationRow(15, usedPositions.used15, allocation.primary_15) }}
{{ allocationRow(20, usedPositions.used20, allocation.primary_20) }}
</tbody>
</table>
<table class="allocation-table">
<thead>
<tr><th>Secondary</th></tr>
</thead>
<tbody>
{{ allocationRow(5, usedPositions.usedSecondary5, allocation.secondary_5) }}
{{ allocationRow(10, usedPositions.usedSecondary10, allocation.secondary_10) }}
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="card-footer text-center">
<a href="/department/{{ department.ORG }}/{{ department.ACCOUNT }}/REPLACEME" class="btn btn-primary">View Allocations<span class="glyphicon glyphicon-chevron-right"></span></a>
<a href="#" class="btn btn-primary">View Allocations<span class="glyphicon glyphicon-chevron-right"></span></a>
</div>
</section>
</div>
Expand Down
Loading