feat: add chore, kindness, and penalty management components
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m34s
All checks were successful
Chore App Build, Test, and Push Docker Images / build-and-push (push) Successful in 2m34s
- Implemented ChoreAssignView for assigning chores to children. - Created ChoreConfirmDialog for confirming chore completion. - Developed KindnessAssignView for assigning kindness acts. - Added PenaltyAssignView for assigning penalties. - Introduced ChoreEditView and ChoreView for editing and viewing chores. - Created KindnessEditView and KindnessView for managing kindness acts. - Developed PenaltyEditView and PenaltyView for managing penalties. - Added TaskSubNav for navigation between chores, kindness acts, and penalties.
This commit is contained in:
|
Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB |
612
.github/specs/feat-child-confirm-chore.md
vendored
Normal file
612
.github/specs/feat-child-confirm-chore.md
vendored
Normal file
@@ -0,0 +1,612 @@
|
||||
# Feature: Chore Completion Confirmation + Task Refactor
|
||||
|
||||
## Overview
|
||||
|
||||
**Goal:**
|
||||
Refactor the "task" concept into three distinct entity types — **Chore**, **Kindness**, and **Penalty** — and implement a chore completion confirmation flow where children can confirm chores and parents approve them.
|
||||
|
||||
**User Stories:**
|
||||
|
||||
- As a **child**, I can click on a chore and confirm that I completed it. I see a **PENDING** banner (yellow) until a parent confirms.
|
||||
- As a **child**, I can click an already PENDING chore and cancel my confirmation.
|
||||
- As a **child**, I see a **COMPLETED** banner (green) on a chore that a parent has approved. That chore is disabled for the rest of the day.
|
||||
- As a **parent**, I see pending chore confirmations in the **Notifications** tab alongside pending reward requests.
|
||||
- As a **parent**, I can click a PENDING chore to **approve** it (awarding points) or **reject** it (resetting to available).
|
||||
- As a **parent**, I can click a non-pending/non-completed chore and award points directly (current behavior). The child view then shows the COMPLETED banner.
|
||||
- As a **parent**, I can **reset** a completed chore from the kebab menu so the child can confirm it again (points are kept).
|
||||
- As an **admin**, I can view full tracking history in the database/logs for all confirmation lifecycle events.
|
||||
|
||||
**Rules:**
|
||||
.github/copilot-instructions.md
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions (Resolved)
|
||||
|
||||
### Task Refactor → Chore / Kindness / Penalty
|
||||
|
||||
**Decision: Full refactor.**
|
||||
|
||||
| Old Concept | New Concept | Behavior |
|
||||
| -------------------------------------------------------------- | ------------ | ------------------------------------------ |
|
||||
| Task with `is_good=true` (schedulable) | **Chore** | Scheduled, expirable, confirmable by child |
|
||||
| Task with `is_good=true` (ad-hoc, e.g. "Child was good today") | **Kindness** | Parent-only award, not confirmable |
|
||||
| Task with `is_good=false` | **Penalty** | Parent-only deduction |
|
||||
|
||||
- The `is_good` field is **removed**. Entity type itself determines point direction.
|
||||
- The `Task` model is retained in the backend but gains a `type` field: `'chore' | 'kindness' | 'penalty'`.
|
||||
- `task_api.py` is split into `chore_api.py`, `kindness_api.py`, `penalty_api.py`.
|
||||
- Existing `is_good=true` tasks are auto-classified as **chore**; `is_good=false` as **penalty**.
|
||||
- Kindness items must be manually created post-migration (acceptable).
|
||||
|
||||
### Merged Pending Table
|
||||
|
||||
**Decision: Single `PendingConfirmation` model replaces `PendingReward`.**
|
||||
|
||||
- Both pending reward requests and pending chore confirmations live in one `pending_confirmations` table, differentiated by `entity_type`.
|
||||
- The `/pending-rewards` endpoint is replaced by `/pending-confirmations`.
|
||||
- `pending_rewards.json` DB file is replaced by `pending_confirmations.json`.
|
||||
|
||||
### "Completed Today" Tracking
|
||||
|
||||
**Decision: `PendingConfirmation` record with `status='approved'` + `approved_at` timestamp.**
|
||||
|
||||
- An approved `PendingConfirmation` record persists (DB-backed, survives restart) and serves as the "completed today" marker.
|
||||
- The frontend checks if `approved_at` is today to determine the COMPLETED state.
|
||||
- On **reset**, the record is deleted (status returns to available).
|
||||
- **Multi-completion history** is preserved via `TrackingEvent` — each confirm/approve/reset cycle generates tracking entries. Query `TrackingEvent` by `child_id + entity_id + date + action='approved'` to count completions per day.
|
||||
|
||||
### Navigation
|
||||
|
||||
**Decision: Sub-nav under "Tasks" tab.**
|
||||
|
||||
- Top-level nav stays 4 items: **Children | Tasks | Rewards | Notifications**
|
||||
- The "Tasks" tab opens a view with 3 sub-tabs: **Chores | Kindness | Penalties**
|
||||
- Each sub-tab has its own list view, edit view, and assign view.
|
||||
- No mobile layout changes needed to the top bar.
|
||||
|
||||
### Chore Confirmation Scoping
|
||||
|
||||
- Each `PendingConfirmation` is scoped to a **single child**. If a chore is assigned to multiple children, each confirms independently.
|
||||
- Expired chores **cannot** be confirmed.
|
||||
- A chore that is already PENDING or COMPLETED today **cannot** be confirmed again (unless reset by parent).
|
||||
|
||||
---
|
||||
|
||||
## Data Model Changes
|
||||
|
||||
### Backend Models
|
||||
|
||||
#### `Task` Model (Modified)
|
||||
|
||||
File: `backend/models/task.py`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class Task(BaseModel):
|
||||
name: str
|
||||
points: int
|
||||
type: Literal['chore', 'kindness', 'penalty'] # replaces is_good
|
||||
image_id: str | None = None
|
||||
user_id: str | None = None
|
||||
```
|
||||
|
||||
- `is_good: bool` → **removed**
|
||||
- `type: Literal['chore', 'kindness', 'penalty']` → **added**
|
||||
- Migration: `is_good=True` → `type='chore'`, `is_good=False` → `type='penalty'`
|
||||
|
||||
#### `PendingConfirmation` Model (New — replaces `PendingReward`)
|
||||
|
||||
File: `backend/models/pending_confirmation.py`
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class PendingConfirmation(BaseModel):
|
||||
child_id: str
|
||||
entity_id: str # task_id or reward_id
|
||||
entity_type: str # 'chore' | 'reward'
|
||||
user_id: str
|
||||
status: str = "pending" # 'pending' | 'approved' | 'rejected'
|
||||
approved_at: str | None = None # ISO 8601 UTC timestamp, set on approval
|
||||
```
|
||||
|
||||
- Replaces `PendingReward` (which had `child_id`, `reward_id`, `user_id`, `status`)
|
||||
- `entity_id` generalizes `reward_id` to work for both chores and rewards
|
||||
- `entity_type` differentiates between chore confirmations and reward requests
|
||||
- `approved_at` enables "completed today" checks
|
||||
|
||||
#### `TrackingEvent` Model (Extended Types)
|
||||
|
||||
File: `backend/models/tracking_event.py`
|
||||
|
||||
```python
|
||||
EntityType = Literal['task', 'reward', 'penalty', 'chore', 'kindness']
|
||||
ActionType = Literal['activated', 'requested', 'redeemed', 'cancelled', 'confirmed', 'approved', 'rejected', 'reset']
|
||||
```
|
||||
|
||||
New actions:
|
||||
|
||||
- `confirmed` — child marks chore as done
|
||||
- `approved` — parent approves chore completion (points awarded)
|
||||
- `rejected` — parent rejects chore completion (no point change)
|
||||
- `reset` — parent resets a completed chore (no point change)
|
||||
|
||||
#### `ChildOverride` Model (Extended Types)
|
||||
|
||||
File: `backend/models/child_override.py`
|
||||
|
||||
```python
|
||||
entity_type: Literal['task', 'reward'] # → Literal['chore', 'kindness', 'penalty', 'reward']
|
||||
```
|
||||
|
||||
#### `ChildTask` DTO (Modified)
|
||||
|
||||
File: `backend/api/child_tasks.py`
|
||||
|
||||
```python
|
||||
class ChildTask:
|
||||
def __init__(self, name, type, points, image_id, id):
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.type = type # replaces is_good
|
||||
self.points = points
|
||||
self.image_id = image_id
|
||||
```
|
||||
|
||||
#### SSE Event Types (New)
|
||||
|
||||
File: `backend/events/types/event_types.py`
|
||||
|
||||
```python
|
||||
class EventType(Enum):
|
||||
# ... existing ...
|
||||
CHILD_CHORE_CONFIRMATION = "child_chore_confirmation"
|
||||
```
|
||||
|
||||
New payload class — File: `backend/events/types/child_chore_confirmation.py`
|
||||
|
||||
```python
|
||||
class ChildChoreConfirmation(Payload):
|
||||
# child_id: str
|
||||
# task_id: str
|
||||
# operation: 'CONFIRMED' | 'APPROVED' | 'REJECTED' | 'CANCELLED' | 'RESET'
|
||||
```
|
||||
|
||||
#### Error Codes (New)
|
||||
|
||||
File: `backend/api/error_codes.py`
|
||||
|
||||
```python
|
||||
class ErrorCodes:
|
||||
# ... existing ...
|
||||
CHORE_EXPIRED = "CHORE_EXPIRED"
|
||||
CHORE_ALREADY_PENDING = "CHORE_ALREADY_PENDING"
|
||||
CHORE_ALREADY_COMPLETED = "CHORE_ALREADY_COMPLETED"
|
||||
PENDING_NOT_FOUND = "PENDING_NOT_FOUND"
|
||||
INSUFFICIENT_POINTS = "INSUFFICIENT_POINTS"
|
||||
```
|
||||
|
||||
### Frontend Models
|
||||
|
||||
File: `frontend/vue-app/src/common/models.ts`
|
||||
|
||||
```typescript
|
||||
// Task — modified
|
||||
export interface Task {
|
||||
id: string;
|
||||
name: string;
|
||||
points: number;
|
||||
type: "chore" | "kindness" | "penalty"; // replaces is_good
|
||||
image_id: string | null;
|
||||
image_url?: string | null;
|
||||
}
|
||||
|
||||
// ChildTask — modified
|
||||
export interface ChildTask {
|
||||
id: string;
|
||||
name: string;
|
||||
type: "chore" | "kindness" | "penalty"; // replaces is_good
|
||||
points: number;
|
||||
image_id: string | null;
|
||||
image_url?: string | null;
|
||||
custom_value?: number | null;
|
||||
schedule?: ChoreSchedule | null;
|
||||
extension_date?: string | null;
|
||||
}
|
||||
|
||||
// PendingConfirmation — new (replaces PendingReward)
|
||||
export interface PendingConfirmation {
|
||||
id: string;
|
||||
child_id: string;
|
||||
child_name: string;
|
||||
child_image_id: string | null;
|
||||
child_image_url?: string | null;
|
||||
entity_id: string;
|
||||
entity_type: "chore" | "reward";
|
||||
entity_name: string;
|
||||
entity_image_id: string | null;
|
||||
entity_image_url?: string | null;
|
||||
status: "pending" | "approved" | "rejected";
|
||||
approved_at: string | null;
|
||||
}
|
||||
|
||||
// EntityType — extended
|
||||
export type EntityType = "chore" | "kindness" | "penalty" | "reward";
|
||||
|
||||
// ActionType — extended
|
||||
export type ActionType =
|
||||
| "activated"
|
||||
| "requested"
|
||||
| "redeemed"
|
||||
| "cancelled"
|
||||
| "confirmed"
|
||||
| "approved"
|
||||
| "rejected"
|
||||
| "reset";
|
||||
|
||||
// SSE event payload — new
|
||||
export interface ChildChoreConfirmationPayload {
|
||||
child_id: string;
|
||||
task_id: string;
|
||||
operation: "CONFIRMED" | "APPROVED" | "REJECTED" | "CANCELLED" | "RESET";
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Backend Implementation
|
||||
|
||||
### API Changes
|
||||
|
||||
#### New Files
|
||||
|
||||
| File | Description |
|
||||
| -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `backend/api/chore_api.py` | CRUD for chores (type='chore'). Routes: `/chore/add`, `/chore/<id>`, `/chore/<id>/edit`, `/chore/list`, `DELETE /chore/<id>` |
|
||||
| `backend/api/kindness_api.py` | CRUD for kindness acts (type='kindness'). Routes: `/kindness/add`, `/kindness/<id>`, `/kindness/<id>/edit`, `/kindness/list`, `DELETE /kindness/<id>` |
|
||||
| `backend/api/penalty_api.py` | CRUD for penalties (type='penalty'). Routes: `/penalty/add`, `/penalty/<id>`, `/penalty/<id>/edit`, `/penalty/list`, `DELETE /penalty/<id>` |
|
||||
| `backend/models/pending_confirmation.py` | `PendingConfirmation` dataclass |
|
||||
| `backend/events/types/child_chore_confirmation.py` | SSE payload class |
|
||||
| `backend/api/pending_confirmation.py` | Response DTO for hydrated pending confirmation |
|
||||
|
||||
#### Modified Files
|
||||
|
||||
| File | Changes |
|
||||
| ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `backend/models/task.py` | `is_good` → `type` field |
|
||||
| `backend/models/tracking_event.py` | Extend `EntityType` and `ActionType` literals |
|
||||
| `backend/models/child_override.py` | Extend `entity_type` literal |
|
||||
| `backend/api/child_tasks.py` | `is_good` → `type` field in DTO |
|
||||
| `backend/api/child_api.py` | Add chore confirmation endpoints, replace `/pending-rewards` with `/pending-confirmations`, update trigger-task to set COMPLETED state, update all `is_good` references to `type` |
|
||||
| `backend/api/task_api.py` | Deprecate/remove — logic moves to entity-specific API files |
|
||||
| `backend/api/error_codes.py` | Add new error codes |
|
||||
| `backend/events/types/event_types.py` | Add `CHILD_CHORE_CONFIRMATION` |
|
||||
| `backend/db/db.py` | Add `pending_confirmations_db`, remove `pending_reward_db` |
|
||||
| `backend/main.py` | Register new blueprints, remove `task_api` blueprint |
|
||||
|
||||
#### New Endpoints (on `child_api.py`)
|
||||
|
||||
| Method | Route | Actor | Description |
|
||||
| ------ | ---------------------------------- | ------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `POST` | `/child/<id>/confirm-chore` | Child | Body: `{ task_id }`. Creates `PendingConfirmation(entity_type='chore', status='pending')`. Validates: chore assigned, not expired, not already pending/completed today. Tracking: `action='confirmed'`, `delta=0`. SSE: `CHILD_CHORE_CONFIRMATION` (CONFIRMED). |
|
||||
| `POST` | `/child/<id>/cancel-confirm-chore` | Child | Body: `{ task_id }`. Deletes the pending confirmation. Tracking: `action='cancelled'`, `delta=0`. SSE: `CHILD_CHORE_CONFIRMATION` (CANCELLED). |
|
||||
| `POST` | `/child/<id>/approve-chore` | Parent | Body: `{ task_id }`. Sets `status='approved'`, `approved_at=now()`. Awards points (respects overrides). Tracking: `action='approved'`, `delta=+points`. SSE: `CHILD_CHORE_CONFIRMATION` (APPROVED) + `CHILD_TASK_TRIGGERED`. |
|
||||
| `POST` | `/child/<id>/reject-chore` | Parent | Body: `{ task_id }`. Deletes the pending confirmation. Tracking: `action='rejected'`, `delta=0`. SSE: `CHILD_CHORE_CONFIRMATION` (REJECTED). |
|
||||
| `POST` | `/child/<id>/reset-chore` | Parent | Body: `{ task_id }`. Deletes the approved confirmation record. Tracking: `action='reset'`, `delta=0`. SSE: `CHILD_CHORE_CONFIRMATION` (RESET). |
|
||||
| `GET` | `/pending-confirmations` | Parent | Returns all pending `PendingConfirmation` records for the user, hydrated with child/entity names and images. Replaces `/pending-rewards`. |
|
||||
|
||||
#### Modified Endpoints
|
||||
|
||||
| Endpoint | Change |
|
||||
| ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- |
|
||||
| `POST /child/<id>/trigger-task` | When a parent triggers a chore directly (no pending), create an approved `PendingConfirmation` so child view shows COMPLETED. Update entity_type references from `'task'` to the actual type. |
|
||||
| `POST /child/<id>/request-reward` | Create `PendingConfirmation(entity_type='reward')` instead of `PendingReward`. |
|
||||
| `POST /child/<id>/cancel-request-reward` | Query `PendingConfirmation` by `entity_type='reward'` instead of `PendingReward`. |
|
||||
| `POST /child/<id>/trigger-reward` | Query/remove `PendingConfirmation` by `entity_type='reward'` instead of `PendingReward`. |
|
||||
| `GET /child/<id>/list-tasks` | Add `pending_status` and `approved_at` fields to each chore in the response (from `PendingConfirmation` lookup). |
|
||||
| `PUT /child/<id>/set-tasks` | Accept `type` parameter instead of `type: 'good' | 'bad'`. |
|
||||
|
||||
#### Database Migration
|
||||
|
||||
A one-time migration script (`backend/scripts/migrate_tasks_to_types.py`):
|
||||
|
||||
1. For each record in `tasks.json`: if `is_good=True` → set `type='chore'`, if `is_good=False` → set `type='penalty'`. Remove `is_good` field.
|
||||
2. For each record in `pending_rewards.json`: convert to `PendingConfirmation` format with `entity_type='reward'`, `entity_id=reward_id`. Write to `pending_confirmations.json`.
|
||||
3. For each record in `tracking_events.json`: update `entity_type='task'` → `'chore'` or `'penalty'` based on the referenced task's old `is_good` value.
|
||||
4. For each record in `child_overrides.json`: update `entity_type='task'` → `'chore'` or `'penalty'` based on the referenced task's old `is_good` value.
|
||||
|
||||
---
|
||||
|
||||
## Backend Tests
|
||||
|
||||
### Test File: `backend/tests/test_chore_api.py` (New)
|
||||
|
||||
- [ ] `test_add_chore` — PUT `/chore/add` with `name`, `points` → 201, type auto-set to `'chore'`
|
||||
- [ ] `test_add_chore_missing_fields` — 400 with `MISSING_FIELDS`
|
||||
- [ ] `test_list_chores` — GET `/chore/list` returns only `type='chore'` tasks
|
||||
- [ ] `test_get_chore` — GET `/chore/<id>` → 200
|
||||
- [ ] `test_get_chore_not_found` — 404
|
||||
- [ ] `test_edit_chore` — PUT `/chore/<id>/edit` → 200
|
||||
- [ ] `test_edit_system_chore_clones_to_user` — editing a `user_id=None` chore creates a user copy
|
||||
- [ ] `test_delete_chore` — DELETE `/chore/<id>` → 200, removed from children's task lists
|
||||
- [ ] `test_delete_chore_not_found` — 404
|
||||
- [ ] `test_delete_chore_removes_from_assigned_children` — cascade cleanup
|
||||
|
||||
### Test File: `backend/tests/test_kindness_api.py` (New)
|
||||
|
||||
- [ ] `test_add_kindness` — PUT `/kindness/add` → 201, type auto-set to `'kindness'`
|
||||
- [ ] `test_list_kindness` — returns only `type='kindness'` tasks
|
||||
- [ ] `test_edit_kindness` — PUT `/kindness/<id>/edit` → 200
|
||||
- [ ] `test_delete_kindness` — cascade removal
|
||||
|
||||
### Test File: `backend/tests/test_penalty_api.py` (New)
|
||||
|
||||
- [ ] `test_add_penalty` — PUT `/penalty/add` → 201, type auto-set to `'penalty'`
|
||||
- [ ] `test_list_penalties` — returns only `type='penalty'` tasks
|
||||
- [ ] `test_edit_penalty` — PUT `/penalty/<id>/edit` → 200
|
||||
- [ ] `test_delete_penalty` — cascade removal
|
||||
|
||||
### Test File: `backend/tests/test_chore_confirmation.py` (New)
|
||||
|
||||
#### Child Confirm Flow
|
||||
|
||||
- [ ] `test_child_confirm_chore_success` — POST `/child/<id>/confirm-chore` with `{ task_id }` → 200, `PendingConfirmation` record created with `status='pending'`, `entity_type='chore'`
|
||||
- [ ] `test_child_confirm_chore_not_assigned` — 400 `ENTITY_NOT_ASSIGNED` when chore is not in child's task list
|
||||
- [ ] `test_child_confirm_chore_not_found` — 404 `TASK_NOT_FOUND` when task_id doesn't exist
|
||||
- [ ] `test_child_confirm_chore_child_not_found` — 404 `CHILD_NOT_FOUND`
|
||||
- [ ] `test_child_confirm_chore_already_pending` — 400 `CHORE_ALREADY_PENDING` when a pending confirmation already exists
|
||||
- [ ] `test_child_confirm_chore_already_completed_today` — 400 `CHORE_ALREADY_COMPLETED` when an approved confirmation exists for today
|
||||
- [ ] `test_child_confirm_chore_expired` — 400 `CHORE_EXPIRED` when chore is past its deadline
|
||||
- [ ] `test_child_confirm_chore_creates_tracking_event` — TrackingEvent with `action='confirmed'`, `delta=0`, `entity_type='chore'`
|
||||
- [ ] `test_child_confirm_chore_wrong_type` — 400 when task is kindness or penalty (not confirmable)
|
||||
|
||||
#### Child Cancel Flow
|
||||
|
||||
- [ ] `test_child_cancel_confirm_success` — POST `/child/<id>/cancel-confirm-chore` → 200, pending record deleted
|
||||
- [ ] `test_child_cancel_confirm_not_pending` — 400 `PENDING_NOT_FOUND`
|
||||
- [ ] `test_child_cancel_confirm_creates_tracking_event` — TrackingEvent with `action='cancelled'`, `delta=0`
|
||||
|
||||
#### Parent Approve Flow
|
||||
|
||||
- [ ] `test_parent_approve_chore_success` — POST `/child/<id>/approve-chore` → 200, points increased, `status='approved'`, `approved_at` set
|
||||
- [ ] `test_parent_approve_chore_with_override` — uses `custom_value` from override instead of base points
|
||||
- [ ] `test_parent_approve_chore_not_pending` — 400 `PENDING_NOT_FOUND`
|
||||
- [ ] `test_parent_approve_chore_creates_tracking_event` — TrackingEvent with `action='approved'`, `delta=+points`
|
||||
- [ ] `test_parent_approve_chore_points_correct` — `points_before` + task points == `points_after` on child
|
||||
|
||||
#### Parent Reject Flow
|
||||
|
||||
- [ ] `test_parent_reject_chore_success` — POST `/child/<id>/reject-chore` → 200, pending record deleted, points unchanged
|
||||
- [ ] `test_parent_reject_chore_not_pending` — 400 `PENDING_NOT_FOUND`
|
||||
- [ ] `test_parent_reject_chore_creates_tracking_event` — TrackingEvent with `action='rejected'`, `delta=0`
|
||||
|
||||
#### Parent Reset Flow
|
||||
|
||||
- [ ] `test_parent_reset_chore_success` — POST `/child/<id>/reset-chore` → 200, approved record deleted, points unchanged
|
||||
- [ ] `test_parent_reset_chore_not_completed` — 400 when no approved record exists
|
||||
- [ ] `test_parent_reset_chore_creates_tracking_event` — TrackingEvent with `action='reset'`, `delta=0`
|
||||
- [ ] `test_parent_reset_then_child_confirm_again` — full cycle: confirm → approve → reset → confirm → approve (two approvals tracked)
|
||||
|
||||
#### Parent Direct Trigger
|
||||
|
||||
- [ ] `test_parent_trigger_chore_directly_creates_approved_confirmation` — POST `/child/<id>/trigger-task` with a chore → creates approved PendingConfirmation so child view shows COMPLETED
|
||||
|
||||
#### Pending Confirmations List
|
||||
|
||||
- [ ] `test_list_pending_confirmations_returns_chores_and_rewards` — GET `/pending-confirmations` returns both types
|
||||
- [ ] `test_list_pending_confirmations_empty` — returns empty list when none exist
|
||||
- [ ] `test_list_pending_confirmations_hydrates_names_and_images` — response includes `child_name`, `entity_name`, image IDs
|
||||
- [ ] `test_list_pending_confirmations_excludes_approved` — only pending status returned
|
||||
- [ ] `test_list_pending_confirmations_filters_by_user` — only returns confirmations for the authenticated user's children
|
||||
|
||||
### Test File: `backend/tests/test_task_api.py` (Modified)
|
||||
|
||||
- [ ] Update all existing tests that use `is_good` to use `type` instead
|
||||
- [ ] `test_add_task` → split into `test_add_chore`, `test_add_kindness`, `test_add_penalty` (or remove if fully migrated to entity-specific APIs)
|
||||
- [ ] `test_list_tasks_sorted` → update sort expectations for `type` field
|
||||
|
||||
### Test File: `backend/tests/test_child_api.py` (Modified)
|
||||
|
||||
- [ ] Update tests referencing `is_good` to use `type`
|
||||
- [ ] Update `set-tasks` tests for new `type` parameter values (`'chore'`, `'kindness'`, `'penalty'`)
|
||||
- [ ] Update `list-tasks` response assertions to check for `pending_status` and `approved_at` fields on chores
|
||||
- [ ] Update `trigger-task` tests to verify `PendingConfirmation` creation for chores
|
||||
- [ ] Update `request-reward` / `cancel-request-reward` / `trigger-reward` tests to use `PendingConfirmation` model
|
||||
- [ ] Replace `pending-rewards` endpoint tests with `pending-confirmations`
|
||||
|
||||
---
|
||||
|
||||
## Frontend Implementation
|
||||
|
||||
### New Files
|
||||
|
||||
| File | Description |
|
||||
| --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `src/components/task/ChoreView.vue` | Admin list of chores (type='chore'). Same pattern as current `TaskView.vue` with blue theme. |
|
||||
| `src/components/task/KindnessView.vue` | Admin list of kindness acts (type='kindness'). Yellow theme. |
|
||||
| `src/components/task/PenaltyView.vue` | Admin list of penalties (type='penalty'). Red theme. |
|
||||
| `src/components/task/ChoreEditView.vue` | Create/edit chore form. Fields: name, points, image. No `is_good` toggle. |
|
||||
| `src/components/task/KindnessEditView.vue` | Create/edit kindness form. Fields: name, points, image. |
|
||||
| `src/components/task/PenaltyEditView.vue` | Create/edit penalty form. Fields: name, points, image. |
|
||||
| `src/components/task/TaskSubNav.vue` | Sub-nav component with Chores / Kindness / Penalties tabs. Renders as a tab bar within the Tasks view area. |
|
||||
| `src/components/child/ChoreAssignView.vue` | Assign chores to child (replaces `TaskAssignView` with `type='good'`). |
|
||||
| `src/components/child/KindnessAssignView.vue` | Assign kindness acts to child. |
|
||||
| `src/components/child/PenaltyAssignView.vue` | Assign penalties to child (replaces `TaskAssignView` with `type='bad'`). |
|
||||
| `src/components/child/ChoreConfirmDialog.vue` | Modal dialog for child to confirm chore completion. "Did you finish [chore name]?" with Confirm / Cancel buttons. |
|
||||
| `src/components/child/ChoreApproveDialog.vue` | Modal dialog for parent to approve/reject pending chore. Shows chore name, child name, points. Approve / Reject buttons. |
|
||||
|
||||
### Modified Files
|
||||
|
||||
| File | Changes |
|
||||
| -------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `src/common/models.ts` | Replace `Task.is_good` with `Task.type`, add `PendingConfirmation` interface, extend `EntityType`/`ActionType`, add `ChildChoreConfirmationPayload`, replace `PendingReward` with `PendingConfirmation`. Add `pending_status` and `approved_at` to `ChildTask`. |
|
||||
| `src/common/backendEvents.ts` | Add `child_chore_confirmation` event listener pattern. |
|
||||
| `src/common/api.ts` | Add `confirmChore()`, `cancelConfirmChore()`, `approveChore()`, `rejectChore()`, `resetChore()`, `fetchPendingConfirmations()`. Remove `fetchPendingRewards()`. |
|
||||
| `src/components/child/ChildView.vue` | Add chore tap handler → show `ChoreConfirmDialog`. Add PENDING (yellow) / COMPLETED (green) banner rendering. Handle cancel-confirm on PENDING tap. Filter kindness acts into new scrolling row. Listen for `child_chore_confirmation` SSE events. |
|
||||
| `src/components/child/ParentView.vue` | Add PENDING/COMPLETED banners on chores. Handle approve/reject on PENDING chore tap. Add "Reset" to kebab menu for completed chores. Add "Assign Kindness" button. Update `trigger-task` to create approved confirmation. Replace `is_good` filters with `type` checks. Listen for `child_chore_confirmation` SSE events. |
|
||||
| `src/components/notification/NotificationView.vue` | Fetch from `/api/pending-confirmations` instead of `/api/pending-rewards`. Render both pending chores and pending rewards with differentiation (icon/label). Listen for `child_chore_confirmation` events in addition to existing `child_reward_request`. |
|
||||
| `src/layout/ParentLayout.vue` | "Tasks" nav icon remains, routes to a view housing `TaskSubNav` with sub-tabs. |
|
||||
| `src/components/task/TaskEditView.vue` | Remove or repurpose. Logic moves to entity-specific edit views (no `is_good` toggle). |
|
||||
| `src/components/task/TaskView.vue` | Remove or repurpose into the sub-nav container view. |
|
||||
| `src/components/child/TaskAssignView.vue` | Remove. Replaced by `ChoreAssignView`, `KindnessAssignView`, `PenaltyAssignView`. |
|
||||
| Router config | Add routes for new views. Update existing task routes to chore/kindness/penalty. |
|
||||
|
||||
### Files to Remove
|
||||
|
||||
| File | Reason |
|
||||
| -------------------------------------- | ----------------------------------------- |
|
||||
| `backend/models/pending_reward.py` | Replaced by `pending_confirmation.py` |
|
||||
| `backend/api/pending_reward.py` | Replaced by `pending_confirmation.py` DTO |
|
||||
| `backend/data/db/pending_rewards.json` | Replaced by `pending_confirmations.json` |
|
||||
|
||||
### ChildView Chore Tap Flow
|
||||
|
||||
```
|
||||
Child taps chore card
|
||||
├─ Chore expired? → No action (grayed out, "TOO LATE" stamp)
|
||||
├─ Chore COMPLETED today? → No action (grayed out, "COMPLETED" stamp)
|
||||
├─ Chore PENDING? → Show ModalDialog "Cancel confirmation?"
|
||||
│ └─ Confirm → POST /child/<id>/cancel-confirm-chore
|
||||
└─ Chore available? → Show ChoreConfirmDialog "Did you finish [name]?"
|
||||
└─ Confirm → POST /child/<id>/confirm-chore
|
||||
```
|
||||
|
||||
### ParentView Chore Tap Flow
|
||||
|
||||
```
|
||||
Parent taps chore card
|
||||
├─ Chore PENDING? → Show ChoreApproveDialog
|
||||
│ ├─ Approve → POST /child/<id>/approve-chore (awards points)
|
||||
│ └─ Reject → POST /child/<id>/reject-chore (resets to available)
|
||||
├─ Chore COMPLETED today? → No tap action. Kebab menu has "Reset"
|
||||
│ └─ Reset → POST /child/<id>/reset-chore
|
||||
└─ Chore available? → Show TaskConfirmDialog (current behavior)
|
||||
└─ Confirm → POST /child/<id>/trigger-task (sets COMPLETED)
|
||||
```
|
||||
|
||||
### Banner Styling
|
||||
|
||||
| State | Banner Text | Text Color | Background | CSS Variable Suggestion |
|
||||
| --------- | ----------- | -------------------------- | ----------------------- | ----------------------- |
|
||||
| Pending | `PENDING` | Yellow (`--color-warning`) | Dark semi-transparent | `--banner-bg-pending` |
|
||||
| Completed | `COMPLETED` | Green (`--color-success`) | Dark semi-transparent | `--banner-bg-completed` |
|
||||
| Expired | `TOO LATE` | Red (existing) | Gray overlay (existing) | (existing styles) |
|
||||
|
||||
---
|
||||
|
||||
## Frontend Tests
|
||||
|
||||
### Test File: `components/__tests__/ChoreConfirmDialog.test.ts` (New)
|
||||
|
||||
- [ ] `renders chore name in dialog`
|
||||
- [ ] `emits confirm event on confirm button click`
|
||||
- [ ] `emits cancel event on cancel button click`
|
||||
|
||||
### Test File: `components/__tests__/ChoreApproveDialog.test.ts` (New)
|
||||
|
||||
- [ ] `renders chore name and points in dialog`
|
||||
- [ ] `emits approve event on approve button click`
|
||||
- [ ] `emits reject event on reject button click`
|
||||
|
||||
### Test File: `components/__tests__/TaskSubNav.test.ts` (New)
|
||||
|
||||
- [ ] `renders three sub-tabs: Chores, Kindness, Penalties`
|
||||
- [ ] `highlights active tab based on route`
|
||||
- [ ] `navigates on tab click`
|
||||
|
||||
### Test File: `components/__tests__/ChoreView.test.ts` (New)
|
||||
|
||||
- [ ] `fetches and renders chore list`
|
||||
- [ ] `navigates to edit on item click`
|
||||
- [ ] `shows delete confirmation modal`
|
||||
- [ ] `refreshes on task_modified SSE event`
|
||||
|
||||
### Test File: `components/__tests__/NotificationView.test.ts` (Modified)
|
||||
|
||||
- [ ] `fetches from /api/pending-confirmations`
|
||||
- [ ] `renders both pending chores and pending rewards`
|
||||
- [ ] `differentiates chore vs reward with label/icon`
|
||||
- [ ] `refreshes on child_chore_confirmation SSE event`
|
||||
- [ ] `refreshes on child_reward_request SSE event`
|
||||
|
||||
### Test File: `components/__tests__/ChildView.test.ts` (Modified / New)
|
||||
|
||||
- [ ] `shows PENDING banner on chore with pending confirmation`
|
||||
- [ ] `shows COMPLETED banner on chore completed today`
|
||||
- [ ] `opens ChoreConfirmDialog on available chore tap`
|
||||
- [ ] `opens cancel dialog on PENDING chore tap`
|
||||
- [ ] `does not allow tap on expired chore`
|
||||
- [ ] `does not allow tap on COMPLETED chore`
|
||||
- [ ] `renders kindness scrolling row`
|
||||
- [ ] `refreshes on child_chore_confirmation SSE event`
|
||||
|
||||
### Test File: `components/__tests__/ParentView.test.ts` (Modified / New)
|
||||
|
||||
- [ ] `shows PENDING banner on chore with pending confirmation`
|
||||
- [ ] `shows COMPLETED banner on approved chore`
|
||||
- [ ] `opens ChoreApproveDialog on PENDING chore tap`
|
||||
- [ ] `opens TaskConfirmDialog on available chore tap`
|
||||
- [ ] `shows Reset in kebab menu for completed chore`
|
||||
- [ ] `renders kindness scrolling row`
|
||||
- [ ] `shows Assign Kindness button`
|
||||
|
||||
---
|
||||
|
||||
## Future Considerations
|
||||
|
||||
- **Recurring chore auto-reset**: Automatically clear completed status on schedule rollover (e.g., daily chores reset at midnight).
|
||||
- **Chore streaks**: Track consecutive days a child completes a chore using `TrackingEvent` history.
|
||||
- **Multi-completion analytics dashboard**: Query `TrackingEvent` to show completion counts per chore per day/week.
|
||||
- **Partial credit**: Allow parents to award fewer points than the chore's value when approving.
|
||||
- **Chore delegation**: Allow one child to reassign a chore to a sibling.
|
||||
- **Photo proof**: Child attaches a photo when confirming a chore.
|
||||
- **Kindness auto-classification**: Suggested classification when creating new items based on name patterns.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Criteria (Definition of Done)
|
||||
|
||||
### Backend
|
||||
|
||||
- [ ] `Task` model uses `type: 'chore' | 'kindness' | 'penalty'` — `is_good` removed
|
||||
- [ ] `PendingConfirmation` model created, `PendingReward` model removed
|
||||
- [ ] `pending_confirmations_db` created in `db.py`, `pending_reward_db` removed
|
||||
- [ ] Migration script converts existing tasks, pending rewards, tracking events, and overrides
|
||||
- [ ] `chore_api.py`, `kindness_api.py`, `penalty_api.py` created with CRUD endpoints
|
||||
- [ ] `task_api.py` removed or deprecated
|
||||
- [ ] Child chore confirmation endpoints: `confirm-chore`, `cancel-confirm-chore`, `approve-chore`, `reject-chore`, `reset-chore`
|
||||
- [ ] `GET /pending-confirmations` returns hydrated pending chores and rewards
|
||||
- [ ] `trigger-task` creates approved `PendingConfirmation` when parent triggers a chore directly
|
||||
- [ ] Reward request/cancel/trigger endpoints migrated to `PendingConfirmation`
|
||||
- [ ] `list-tasks` response includes `pending_status` and `approved_at` for chores
|
||||
- [ ] `TrackingEvent` created for every mutation: confirmed, cancelled, approved, rejected, reset
|
||||
- [ ] Tracking events logged to rotating file logger
|
||||
- [ ] SSE event `CHILD_CHORE_CONFIRMATION` sent for every confirmation lifecycle event
|
||||
- [ ] All new error codes defined and returned with proper HTTP status codes
|
||||
- [ ] All existing tests updated for `type` field (no `is_good` references)
|
||||
- [ ] All new backend tests pass
|
||||
|
||||
### Frontend
|
||||
|
||||
- [ ] `Task` and `ChildTask` interfaces use `type` instead of `is_good`
|
||||
- [ ] `PendingConfirmation` interface replaces `PendingReward`
|
||||
- [ ] Sub-nav under "Tasks" with Chores / Kindness / Penalties tabs
|
||||
- [ ] `ChoreView`, `KindnessView`, `PenaltyView` list views created
|
||||
- [ ] `ChoreEditView`, `KindnessEditView`, `PenaltyEditView` edit/create views created
|
||||
- [ ] `ChoreAssignView`, `KindnessAssignView`, `PenaltyAssignView` assign views created
|
||||
- [ ] `TaskView`, `TaskEditView`, `TaskAssignView` removed or repurposed
|
||||
- [ ] `ChoreConfirmDialog` — child confirmation modal
|
||||
- [ ] `ChoreApproveDialog` — parent approve/reject modal
|
||||
- [ ] `ChildView` — chore tap opens confirm dialog, cancel dialog for pending, banners render correctly
|
||||
- [ ] `ChildView` — expired and completed chores are non-interactive
|
||||
- [ ] `ChildView` — kindness scrolling row added
|
||||
- [ ] `ParentView` — pending chore tap opens approve/reject dialog
|
||||
- [ ] `ParentView` — available chore tap uses existing trigger flow + creates completion record
|
||||
- [ ] `ParentView` — kebab menu "Reset" option for completed chores
|
||||
- [ ] `ParentView` — "Assign Kindness" button added
|
||||
- [ ] `NotificationView` — fetches from `/pending-confirmations`, renders both types
|
||||
- [ ] SSE listeners for `child_chore_confirmation` in all relevant components
|
||||
- [ ] Banner styles: yellow PENDING, green COMPLETED (using CSS variables)
|
||||
- [ ] All `is_good` references removed from frontend code
|
||||
- [ ] All frontend tests pass
|
||||
- [ ] Router updated with new routes
|
||||
Reference in New Issue
Block a user