From bc481527c83cc24c6ab2d9779c94f19f58e93c45 Mon Sep 17 00:00:00 2001 From: Ryan Kegel Date: Tue, 21 Apr 2026 16:52:06 -0400 Subject: [PATCH] feat: enhance task and reward management by clearing pending confirmations on unassignment --- backend/api/child_action_helpers.py | 26 +++++++++++++++++++++----- backend/api/child_api.py | 28 ++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 7 deletions(-) diff --git a/backend/api/child_action_helpers.py b/backend/api/child_action_helpers.py index 1e3a585..d65590f 100644 --- a/backend/api/child_action_helpers.py +++ b/backend/api/child_action_helpers.py @@ -42,6 +42,10 @@ def approve_chore(user_id: str, child_id: str, task_id: str) -> dict | None: raise ValueError(f'Child {child_id} not found for user {user_id}') child = Child.from_dict(child_result) + if task_id not in child.tasks: + logger.info(f'Task {task_id} no longer assigned to child {child_id}; skipping approve') + return None + PendingQ = Query() existing = pending_confirmations_db.get( (PendingQ.child_id == child_id) & (PendingQ.entity_id == task_id) & @@ -161,6 +165,10 @@ def approve_reward_request(user_id: str, child_id: str, reward_id: str) -> dict: raise ValueError(f'Child {child_id} not found for user {user_id}') child = Child.from_dict(child_result) + if reward_id not in child.rewards: + logger.info(f'Reward {reward_id} no longer assigned to child {child_id}; skipping approve') + return None + RewardQ = Query() reward_result = reward_db.get( (RewardQ.id == reward_id) & ((RewardQ.user_id == user_id) | (RewardQ.user_id == None)) @@ -176,13 +184,21 @@ def approve_reward_request(user_id: str, child_id: str, reward_id: str) -> dict: raise ValueError(f'Child {child_id} has insufficient points for reward {reward_id}') PendingQ = Query() - removed = pending_confirmations_db.remove( + existing = pending_confirmations_db.get( (PendingQ.child_id == child_id) & (PendingQ.entity_id == reward_id) & - (PendingQ.entity_type == 'reward') + (PendingQ.entity_type == 'reward') & (PendingQ.status == 'pending') & + (PendingQ.user_id == user_id) ) - if removed: - send_event_to_user(user_id, Event(EventType.CHILD_REWARD_REQUEST.value, - ChildRewardRequest(child_id, reward_id, ChildRewardRequest.REQUEST_GRANTED))) + if not existing: + logger.info(f'No pending reward for child {child_id}, reward {reward_id} — already resolved') + return None + + pending_confirmations_db.remove( + (PendingQ.child_id == child_id) & (PendingQ.entity_id == reward_id) & + (PendingQ.entity_type == 'reward') & (PendingQ.user_id == user_id) + ) + send_event_to_user(user_id, Event(EventType.CHILD_REWARD_REQUEST.value, + ChildRewardRequest(child_id, reward_id, ChildRewardRequest.REQUEST_GRANTED))) points_before = child.points child.points -= cost_value diff --git a/backend/api/child_api.py b/backend/api/child_api.py index 0048168..dd8dc45 100644 --- a/backend/api/child_api.py +++ b/backend/api/child_api.py @@ -211,7 +211,7 @@ def set_child_tasks(id): # Convert back to list if needed new_tasks = list(new_task_ids) - # Identify unassigned tasks and delete their overrides + # Identify unassigned tasks and delete their overrides and pending confirmations old_task_ids = set(child.tasks) unassigned_task_ids = old_task_ids - new_task_ids for task_id in unassigned_task_ids: @@ -220,6 +220,12 @@ def set_child_tasks(id): if override and override.entity_type == 'task': delete_override(id, task_id) logger.info(f"Deleted override for unassigned task: child={id}, task={task_id}") + # Clear any pending chore confirmation + PendingQ = Query() + pending_confirmations_db.remove( + (PendingQ.child_id == id) & (PendingQ.entity_id == task_id) & + (PendingQ.entity_type == 'chore') & (PendingQ.user_id == user_id) + ) # Replace tasks with validated IDs child_db.update({'tasks': new_tasks}, ChildQuery.id == id) @@ -253,6 +259,12 @@ def remove_task_from_child(id): if task_id in child.get('tasks', []): child['tasks'].remove(task_id) child_db.update({'tasks': child['tasks']}, ChildQuery.id == id) + # Clear any pending chore confirmation for this task + PendingQ = Query() + pending_confirmations_db.remove( + (PendingQ.child_id == id) & (PendingQ.entity_id == task_id) & + (PendingQ.entity_type == 'chore') & (PendingQ.user_id == user_id) + ) return jsonify({'message': f'Task {task_id} removed from {child["name"]}.'}), 200 return jsonify({'error': 'Task not assigned to child'}), 400 @@ -602,7 +614,7 @@ def set_child_rewards(id): if reward_db.get((RewardQuery.id == rid) & ((RewardQuery.user_id == user_id) | (RewardQuery.user_id == None))): valid_reward_ids.append(rid) - # Identify unassigned rewards and delete their overrides + # Identify unassigned rewards and delete their overrides and pending confirmations new_reward_ids_set = set(valid_reward_ids) unassigned_reward_ids = old_reward_ids - new_reward_ids_set for reward_id in unassigned_reward_ids: @@ -610,6 +622,12 @@ def set_child_rewards(id): if override and override.entity_type == 'reward': delete_override(id, reward_id) logger.info(f"Deleted override for unassigned reward: child={id}, reward={reward_id}") + # Clear any pending reward confirmation + PendingQ = Query() + pending_confirmations_db.remove( + (PendingQ.child_id == id) & (PendingQ.entity_id == reward_id) & + (PendingQ.entity_type == 'reward') & (PendingQ.user_id == user_id) + ) # Replace rewards with validated IDs child_db.update({'rewards': valid_reward_ids}, ChildQuery.id == id) @@ -640,6 +658,12 @@ def remove_reward_from_child(id): if reward_id in child.get('rewards', []): child['rewards'].remove(reward_id) child_db.update({'rewards': child['rewards']}, ChildQuery.id == id) + # Clear any pending reward confirmation for this reward + PendingQ = Query() + pending_confirmations_db.remove( + (PendingQ.child_id == id) & (PendingQ.entity_id == reward_id) & + (PendingQ.entity_type == 'reward') & (PendingQ.user_id == user_id) + ) return jsonify({'message': f'Reward {reward_id} removed from {child["name"]}.'}), 200 return jsonify({'error': 'Reward not assigned to child'}), 400