Skip to content

Commit fb50be3

Browse files
committed
one last fix (hopefully last one)
1 parent 39ed2f9 commit fb50be3

File tree

2 files changed

+102
-3
lines changed

2 files changed

+102
-3
lines changed

backend/app/services/implementations/match_service.py

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,19 +91,31 @@ async def update_match(self, match_id: int, req: MatchUpdateRequest) -> MatchRes
9191
if not match or match.deleted_at is not None:
9292
raise HTTPException(404, f"Match {match_id} not found")
9393

94-
if req.volunteer_id is not None:
95-
volunteer: User | None = self.db.get(User, req.volunteer_id)
94+
volunteer_changed = False
95+
if req.volunteer_id is not None and req.volunteer_id != match.volunteer_id:
96+
volunteer: User | None = (
97+
self.db.query(User)
98+
.options(joinedload(User.availability))
99+
.filter(User.id == req.volunteer_id)
100+
.first()
101+
)
96102
if not volunteer:
97103
raise HTTPException(404, f"Volunteer {req.volunteer_id} not found")
98104
if volunteer.role is None or volunteer.role.name != UserRole.VOLUNTEER:
99105
raise HTTPException(400, "Match volunteers must have volunteer role")
100-
match.volunteer_id = volunteer.id
106+
self._reassign_volunteer(match, volunteer)
107+
volunteer_changed = True
101108

102109
if req.match_status is not None:
103110
status = self.db.query(MatchStatus).filter_by(name=req.match_status).first()
104111
if not status:
105112
raise HTTPException(400, f"Invalid match status: {req.match_status}")
106113
match.match_status = status
114+
elif volunteer_changed:
115+
pending_status = self.db.query(MatchStatus).filter_by(name="pending").first()
116+
if not pending_status:
117+
raise HTTPException(500, "Match status 'pending' not configured")
118+
match.match_status = pending_status
107119

108120
if req.chosen_time_block_id is not None:
109121
block = self.db.get(TimeBlock, req.chosen_time_block_id)
@@ -521,3 +533,16 @@ def _attach_initial_suggested_times(self, match: Match, volunteer: User) -> None
521533

522534
new_block = TimeBlock(start_time=block.start_time)
523535
match.suggested_time_blocks.append(new_block)
536+
537+
def _reassign_volunteer(self, match: Match, volunteer: User) -> None:
538+
match.volunteer_id = volunteer.id
539+
540+
# Clear confirmed selection
541+
self._clear_confirmed_time(match)
542+
543+
# Remove existing suggested blocks
544+
for block in list(match.suggested_time_blocks):
545+
match.suggested_time_blocks.remove(block)
546+
self.db.delete(block)
547+
548+
self._attach_initial_suggested_times(match, volunteer)

backend/tests/unit/test_match_service.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,34 @@ def volunteer_with_mixed_availability(db_session, another_volunteer):
217217
return another_volunteer
218218

219219

220+
@pytest.fixture
221+
def volunteer_with_alt_availability(db_session):
222+
"""Create a different volunteer with distinct availability."""
223+
now = datetime.now(timezone.utc)
224+
tomorrow = now + timedelta(days=2)
225+
226+
volunteer = User(
227+
first_name="Alt",
228+
last_name="Volunteer",
229+
230+
role_id=2,
231+
auth_id="volunteer_alt_auth_id",
232+
)
233+
db_session.add(volunteer)
234+
db_session.flush()
235+
236+
slots = [
237+
tomorrow.replace(hour=9, minute=0, second=0, microsecond=0),
238+
tomorrow.replace(hour=9, minute=30, second=0, microsecond=0),
239+
]
240+
for slot in slots:
241+
volunteer.availability.append(TimeBlock(start_time=slot))
242+
243+
db_session.commit()
244+
db_session.refresh(volunteer)
245+
return volunteer
246+
247+
220248
@pytest.fixture
221249
def sample_match(db_session, participant_user, volunteer_user):
222250
"""Create a pending match with suggested times."""
@@ -1444,3 +1472,49 @@ async def test_update_match_invalid_match_404(self, db_session):
14441472

14451473
assert exc_info.value.status_code == 404
14461474
assert "Match" in exc_info.value.detail
1475+
1476+
@pytest.mark.asyncio
1477+
async def test_update_match_reassigns_volunteer_resets_suggested_times(
1478+
self,
1479+
db_session,
1480+
participant_user,
1481+
volunteer_with_availability,
1482+
volunteer_with_alt_availability,
1483+
):
1484+
"""Changing the volunteer regenerates suggested times and clears chosen slot"""
1485+
try:
1486+
match_service = MatchService(db_session)
1487+
1488+
# Create match with first volunteer and schedule it
1489+
create_request = MatchCreateRequest(
1490+
participant_id=participant_user.id,
1491+
volunteer_ids=[volunteer_with_availability.id],
1492+
)
1493+
response = await match_service.create_matches(create_request)
1494+
match_id = response.matches[0].id
1495+
1496+
match = db_session.get(Match, match_id)
1497+
time_block_id = match.suggested_time_blocks[0].id
1498+
await match_service.schedule_match(match_id, time_block_id, acting_participant_id=participant_user.id)
1499+
1500+
db_session.refresh(match)
1501+
assert match.match_status.name == "confirmed"
1502+
assert match.chosen_time_block_id is not None
1503+
1504+
# Reassign to alternate volunteer
1505+
update_request = MatchUpdateRequest(volunteer_id=volunteer_with_alt_availability.id)
1506+
await match_service.update_match(match_id, update_request)
1507+
1508+
db_session.refresh(match)
1509+
assert match.volunteer_id == volunteer_with_alt_availability.id
1510+
assert match.chosen_time_block_id is None
1511+
assert match.match_status.name == "pending"
1512+
1513+
starts = sorted(block.start_time for block in match.suggested_time_blocks)
1514+
expected = [slot.start_time for slot in volunteer_with_alt_availability.availability]
1515+
assert starts == expected
1516+
1517+
db_session.commit()
1518+
except Exception:
1519+
db_session.rollback()
1520+
raise

0 commit comments

Comments
 (0)