diff --git a/app/logic/volunteerSpreadsheet.py b/app/logic/volunteerSpreadsheet.py index 255cbc97b..abae1cd76 100644 --- a/app/logic/volunteerSpreadsheet.py +++ b/app/logic/volunteerSpreadsheet.py @@ -5,6 +5,7 @@ from datetime import date, datetime,time from app import app from app.models import mainDB +from app.models.celtsLabor import CeltsLabor from app.models.eventParticipant import EventParticipant from app.models.user import User from app.models.program import Program @@ -225,6 +226,74 @@ def calculateRetentionRate(fallDict, springDict): return retentionDict +def laborAttendanceByTerm(term): + laborQuery = ( #so that all Celts Labor students appear even if they didn't attend anything + CeltsLabor + .select( + fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), + User.bnumber, + fn.CONCAT(User.username, '@berea.edu').alias('email'), + fn.COUNT(fn.DISTINCT(Event.id)).alias('meetingsAttended') + ) + .join(User) + .switch(CeltsLabor) + .join( + EventParticipant, + JOIN.LEFT_OUTER, + on=(CeltsLabor.user == EventParticipant.user) + ) + .join( + Event, + JOIN.LEFT_OUTER, + on=( + (EventParticipant.event == Event.id) & + (Event.term == term) & + (Event.isLaborOnly == True) & + (Event.deletionDate.is_null()) & + (Event.isCanceled == False) + ) + ) + .where( + (CeltsLabor.term == term) + ) + .group_by(CeltsLabor.user) + ) + + nonLaborQuery = ( #so that non-labor attendees who are not in CeltsLabor also appear + EventParticipant + .select( + fn.CONCAT(User.firstName, ' ', User.lastName).alias('fullName'), + User.bnumber, + fn.CONCAT(User.username, '@berea.edu').alias('email'), + fn.COUNT(fn.DISTINCT(EventParticipant.event_id)).alias('meetingsAttended') + ) + .join(User) + .switch(EventParticipant) + .join( + Event, + on=( + (EventParticipant.event == Event.id) & + (Event.term == term) & + (Event.isLaborOnly == True) & + (Event.deletionDate.is_null()) & + (Event.isCanceled == False) + ) + ) + .join( + CeltsLabor, + JOIN.LEFT_OUTER, + on=(EventParticipant.user == CeltsLabor.user) & + (CeltsLabor.term == term) + ) + .where(CeltsLabor.user.is_null()) + .group_by(EventParticipant.user) + ) + + query = laborQuery.union(nonLaborQuery).order_by(SQL('fullName')) + columns = ("Full Name", "B-Number", "Email", "Meetings Attended") + + return (columns, query.tuples()) + def makeDataXls(sheetName, sheetData, workbook, sheetDesc=None): # assumes the length of the column titles matches the length of the data @@ -271,9 +340,12 @@ def createSpreadsheet(academicYear): makeDataXls("Unique Volunteers", getUniqueVolunteers(academicYear), workbook, sheetDesc=f"All students who participated in at least one service event during {academicYear}.") makeDataXls("Only All Volunteer Training", onlyCompletedAllVolunteer(academicYear), workbook, sheetDesc="Students who participated in an All Volunteer Training, but did not participate in any service events.") makeDataXls("Retention Rate By Semester", getRetentionRate(academicYear), workbook, sheetDesc="The percentage of students who participated in service events in the fall semester who also participated in a service event in the spring semester. Does not currently account for fall graduations.") - + fallTerm = getFallTerm(academicYear) springTerm = getSpringTerm(academicYear) + makeDataXls(f"Labor Attendance {fallTerm.description}", laborAttendanceByTerm(fallTerm), workbook,sheetDesc=f"Number of labor-only events attended in {fallTerm.description} for each labor student and non-labor attendees, including zero attendance (for labor students).") + makeDataXls(f"Labor Attendance {springTerm.description}", laborAttendanceByTerm(springTerm), workbook, sheetDesc=f"Number of labor-only events attended in {springTerm.description} for each labor student and non-labor attendees, including zero attendance (for labor students).") + makeDataXls(fallTerm.description, getAllTermData(fallTerm), workbook, sheetDesc= "All event participation for the term, excluding deleted or canceled events.") makeDataXls(springTerm.description, getAllTermData(springTerm), workbook, sheetDesc="All event participation for the term, excluding deleted or canceled events.") diff --git a/tests/code/test_spreadsheet.py b/tests/code/test_spreadsheet.py index d66139c33..a7d5ff5ab 100644 --- a/tests/code/test_spreadsheet.py +++ b/tests/code/test_spreadsheet.py @@ -4,6 +4,7 @@ from app.models.user import User from app.models.term import Term from app.models.eventParticipant import EventParticipant +from app.models.celtsLabor import CeltsLabor from app.logic.volunteerSpreadsheet import * from app.models.program import Program from app.models.event import Event @@ -32,7 +33,8 @@ def fixture_info(): startDate=date(2023, 9, 1), isCanceled=False, deletionDate=None, - isService=True + isService=True, + isLaborOnly=True ) event2 = Event.create( name='Event2', @@ -41,7 +43,8 @@ def fixture_info(): startDate=date(2023, 9, 10), isCanceled=False, deletionDate=None, - isService=True + isService=True, + isLaborOnly=True ) event3 = Event.create( name='Event3', @@ -62,6 +65,10 @@ def fixture_info(): isService=True ) + labor1 = CeltsLabor.create(user=user1, term=term1, positionTitle="test position 1") + labor2 = CeltsLabor.create(user=user2, term=term1, positionTitle="test position 2") + labor3 = CeltsLabor.create(user=user1, term=term2, positionTitle="test position 3") + labor4 = CeltsLabor.create(user=user2, term=term2, positionTitle="test position 4") eventparticipant1 = EventParticipant.create(event=event1, user=user1, hoursEarned=5) eventparticipant2 = EventParticipant.create(event=event1, user=user2, hoursEarned=3) @@ -86,6 +93,10 @@ def fixture_info(): 'eventparticipant1': eventparticipant1, 'eventparticipant2': eventparticipant2, 'eventparticipant4': eventparticipant4, + 'labor1': labor1, + 'labor2': labor2, + 'labor3': labor3, + 'labor4': labor4 } transaction.rollback() @@ -683,5 +694,46 @@ def test_getUniqueVolunteers(fixture_info): ("Test Tester", "testt@berea.edu", "B55555"), ]) +@pytest.mark.integration +def test_laborAttendanceByTerm(fixture_info): + columns, results = laborAttendanceByTerm(fixture_info['term1']) + results = list(results) + + assert columns == ("Full Name", "B-Number", "Email", "Meetings Attended") + + assert len(results) == 2 + assert ("John Doe", "B774377", "doej@berea.edu", 1) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 1) in results + + columns, results = laborAttendanceByTerm(fixture_info['term2']) + results = list(results) + + assert len(results) == 2 + assert ("John Doe", "B774377", "doej@berea.edu", 0) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 0) in results + + EventParticipant.create(event=fixture_info['event2'], user=fixture_info['user1'], hoursEarned=1) + + columns, results = laborAttendanceByTerm(fixture_info['term1']) + results = list(results) + + assert len(results) == 2 + assert ("John Doe", "B774377", "doej@berea.edu", 2) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 1) in results + + EventParticipant.create(event=fixture_info['event1'], user=fixture_info['user3'], hoursEarned=1) + + columns, results = laborAttendanceByTerm(fixture_info['term1']) + results = list(results) + + assert len(results) == 3 + assert ("John Doe", "B774377", "doej@berea.edu", 2) in results + assert ("Jane Doe", "B888828", "doej2@berea.edu", 1) in results + assert ("Bob Builder", "B00700932", "builderb@berea.edu", 1) in results + + EventParticipant.create(event=fixture_info['event3'], user=fixture_info['user1'], hoursEarned=2) + columns, results = laborAttendanceByTerm(fixture_info['term1']) + results = list(results) + assert ("John Doe", "B774377", "doej@berea.edu", 2) in results \ No newline at end of file