|
24 | 24 | ) |
25 | 25 | from app.schemas.time_block import TimeBlockEntity, TimeRange |
26 | 26 | from app.schemas.user import UserRole |
| 27 | +from app.utilities.ses_email_service import SESEmailService |
27 | 28 | from app.utilities.timezone_utils import get_timezone_from_abbreviation |
28 | 29 |
|
29 | 30 | SCHEDULE_CLEANUP_STATUSES = { |
@@ -95,6 +96,28 @@ async def create_matches(self, req: MatchCreateRequest) -> MatchCreateResponse: |
95 | 96 | for match in created_matches: |
96 | 97 | self.db.refresh(match) |
97 | 98 |
|
| 99 | + # Send "matches available" email to each volunteer |
| 100 | + ses_service = SESEmailService() |
| 101 | + for match in created_matches: |
| 102 | + try: |
| 103 | + volunteer = self.db.get(User, match.volunteer_id) |
| 104 | + if volunteer and volunteer.email: |
| 105 | + # Get volunteer's language (enum values are already "en" or "fr") |
| 106 | + language = volunteer.language.value if volunteer.language else "en" |
| 107 | + |
| 108 | + first_name = volunteer.first_name if volunteer.first_name else None |
| 109 | + matches_url = "http://localhost:3000/volunteer/dashboard" |
| 110 | + |
| 111 | + ses_service.send_matches_available_email( |
| 112 | + to_email=volunteer.email, |
| 113 | + first_name=first_name, |
| 114 | + matches_url=matches_url, |
| 115 | + language=language, |
| 116 | + ) |
| 117 | + except Exception as e: |
| 118 | + # Log error but don't fail the match creation |
| 119 | + self.logger.error(f"Failed to send matches available email to volunteer {match.volunteer_id}: {e}") |
| 120 | + |
98 | 121 | responses = [self._build_match_response(match) for match in created_matches] |
99 | 122 | return MatchCreateResponse(matches=responses) |
100 | 123 |
|
@@ -258,6 +281,90 @@ async def schedule_match( |
258 | 281 | self.db.commit() |
259 | 282 | self.db.refresh(match) |
260 | 283 |
|
| 284 | + # Send "call scheduled" email to both participant and volunteer |
| 285 | + try: |
| 286 | + # Load participant and volunteer with their data |
| 287 | + participant = ( |
| 288 | + self.db.query(User) |
| 289 | + .options(joinedload(User.user_data)) |
| 290 | + .filter(User.id == match.participant_id) |
| 291 | + .first() |
| 292 | + ) |
| 293 | + volunteer = ( |
| 294 | + self.db.query(User) |
| 295 | + .options(joinedload(User.user_data)) |
| 296 | + .filter(User.id == match.volunteer_id) |
| 297 | + .first() |
| 298 | + ) |
| 299 | + |
| 300 | + if participant and volunteer and match.confirmed_time: |
| 301 | + ses_service = SESEmailService() |
| 302 | + confirmed_time_utc = match.confirmed_time.start_time |
| 303 | + |
| 304 | + # Get participant's timezone and language |
| 305 | + participant_tz = ZoneInfo("America/Toronto") # Default to EST |
| 306 | + if participant.user_data and participant.user_data.timezone: |
| 307 | + tz_result = get_timezone_from_abbreviation(participant.user_data.timezone) |
| 308 | + if tz_result: |
| 309 | + participant_tz = tz_result |
| 310 | + |
| 311 | + participant_language = participant.language.value if participant.language else "en" |
| 312 | + |
| 313 | + # Get volunteer's timezone and language |
| 314 | + volunteer_tz = ZoneInfo("America/Toronto") # Default to EST |
| 315 | + if volunteer.user_data and volunteer.user_data.timezone: |
| 316 | + tz_result = get_timezone_from_abbreviation(volunteer.user_data.timezone) |
| 317 | + if tz_result: |
| 318 | + volunteer_tz = tz_result |
| 319 | + |
| 320 | + volunteer_language = volunteer.language.value if volunteer.language else "en" |
| 321 | + |
| 322 | + # Convert time to participant's timezone |
| 323 | + participant_time = confirmed_time_utc.astimezone(participant_tz) |
| 324 | + participant_date = participant_time.strftime("%B %d, %Y") |
| 325 | + participant_time_str = participant_time.strftime("%I:%M %p") |
| 326 | + participant_tz_abbr = participant_time.strftime("%Z") |
| 327 | + |
| 328 | + # Convert time to volunteer's timezone |
| 329 | + volunteer_time = confirmed_time_utc.astimezone(volunteer_tz) |
| 330 | + volunteer_date = volunteer_time.strftime("%B %d, %Y") |
| 331 | + volunteer_time_str = volunteer_time.strftime("%I:%M %p") |
| 332 | + volunteer_tz_abbr = volunteer_time.strftime("%Z") |
| 333 | + |
| 334 | + # Send to participant |
| 335 | + if participant.email: |
| 336 | + ses_service.send_call_scheduled_email( |
| 337 | + to_email=participant.email, |
| 338 | + match_name=f"{volunteer.first_name} {volunteer.last_name}" |
| 339 | + if volunteer.first_name and volunteer.last_name |
| 340 | + else "Your volunteer", |
| 341 | + date=participant_date, |
| 342 | + time=participant_time_str, |
| 343 | + timezone=participant_tz_abbr, |
| 344 | + first_name=participant.first_name, |
| 345 | + scheduled_calls_url="http://localhost:3000/participant/dashboard", |
| 346 | + language=participant_language, |
| 347 | + ) |
| 348 | + |
| 349 | + # Send to volunteer |
| 350 | + if volunteer.email: |
| 351 | + ses_service.send_call_scheduled_email( |
| 352 | + to_email=volunteer.email, |
| 353 | + match_name=f"{participant.first_name} {participant.last_name}" |
| 354 | + if participant.first_name and participant.last_name |
| 355 | + else "Your participant", |
| 356 | + date=volunteer_date, |
| 357 | + time=volunteer_time_str, |
| 358 | + timezone=volunteer_tz_abbr, |
| 359 | + first_name=volunteer.first_name, |
| 360 | + scheduled_calls_url="http://localhost:3000/volunteer/dashboard", |
| 361 | + language=volunteer_language, |
| 362 | + ) |
| 363 | + |
| 364 | + except Exception as e: |
| 365 | + # Log error but don't fail the scheduling |
| 366 | + self.logger.error(f"Failed to send call scheduled emails for match {match_id}: {e}") |
| 367 | + |
261 | 368 | return self._build_match_detail(match) |
262 | 369 |
|
263 | 370 | except HTTPException: |
@@ -317,6 +424,35 @@ async def request_new_times( |
317 | 424 | self.db.commit() |
318 | 425 | self.db.refresh(match) |
319 | 426 |
|
| 427 | + # Send "participant requested new times" email to volunteer |
| 428 | + try: |
| 429 | + # Load participant and volunteer |
| 430 | + participant = self.db.get(User, match.participant_id) |
| 431 | + volunteer = self.db.get(User, match.volunteer_id) |
| 432 | + |
| 433 | + if participant and volunteer and volunteer.email: |
| 434 | + # Get volunteer's language |
| 435 | + volunteer_language = volunteer.language.value if volunteer.language else "en" |
| 436 | + |
| 437 | + # Get participant's name for email |
| 438 | + participant_name = ( |
| 439 | + f"{participant.first_name} {participant.last_name}" |
| 440 | + if participant.first_name and participant.last_name |
| 441 | + else "A participant" |
| 442 | + ) |
| 443 | + |
| 444 | + ses_service = SESEmailService() |
| 445 | + ses_service.send_participant_requested_new_times_email( |
| 446 | + to_email=volunteer.email, |
| 447 | + participant_name=participant_name, |
| 448 | + first_name=volunteer.first_name, |
| 449 | + matches_url="http://localhost:3000/volunteer/dashboard", |
| 450 | + language=volunteer_language, |
| 451 | + ) |
| 452 | + except Exception as e: |
| 453 | + # Log error but don't fail the request |
| 454 | + self.logger.error(f"Failed to send participant requested new times email for match {match_id}: {e}") |
| 455 | + |
320 | 456 | return self._build_match_detail(match) |
321 | 457 |
|
322 | 458 | except HTTPException: |
@@ -530,6 +666,27 @@ async def volunteer_accept_match( |
530 | 666 | self.db.commit() |
531 | 667 | self.db.refresh(match) |
532 | 668 |
|
| 669 | + # Send "matches available" email to participant |
| 670 | + try: |
| 671 | + participant = match.participant |
| 672 | + if participant and participant.email: |
| 673 | + # Get participant's language (enum values are already "en" or "fr") |
| 674 | + language = participant.language.value if participant.language else "en" |
| 675 | + |
| 676 | + first_name = participant.first_name if participant.first_name else None |
| 677 | + matches_url = "http://localhost:3000/participant/dashboard" |
| 678 | + |
| 679 | + ses_service = SESEmailService() |
| 680 | + ses_service.send_matches_available_email( |
| 681 | + to_email=participant.email, |
| 682 | + first_name=first_name, |
| 683 | + matches_url=matches_url, |
| 684 | + language=language, |
| 685 | + ) |
| 686 | + except Exception as e: |
| 687 | + # Log error but don't fail the match acceptance |
| 688 | + self.logger.error(f"Failed to send matches available email to participant {match.participant_id}: {e}") |
| 689 | + |
533 | 690 | # Return match detail for participant view (includes suggested times) |
534 | 691 | return self._build_match_detail(match) |
535 | 692 | except HTTPException: |
|
0 commit comments