Add design spec: programmer oversight (day-sheet approvals, monitor, report, dashboard)
Co-Authored-By: claude-flow <ruv@ruv.net>
This commit is contained in:
@@ -0,0 +1,448 @@
|
|||||||
|
# Programmer Oversight — Design Spec
|
||||||
|
|
||||||
|
**Date:** 2026-09-27 · **Branch:** `feat/programmer-tasks` · **Status:** Draft for review
|
||||||
|
|
||||||
|
Adds an oversight layer to the Programmer Tasks module: daily **Day Sheet** approvals with a
|
||||||
|
remarks ↔ justification loop, a live **admin Monitor**, a PDF **Accomplishment Report**,
|
||||||
|
and a gamified **Mine dashboard**.
|
||||||
|
|
||||||
|
## 1. Background
|
||||||
|
|
||||||
|
The Programmer Tasks module records programmers' dev work: tasks with a pause-aware timer,
|
||||||
|
per-contributor work logs (assignee progress notes + helper-entered minutes), comments, and
|
||||||
|
projects. Today:
|
||||||
|
|
||||||
|
- The admin cannot see who is working on what right now, or who is helping whom.
|
||||||
|
- Nobody reviews or approves programmers' daily work.
|
||||||
|
- There is no Accomplishment Report.
|
||||||
|
- The "Mine" tab is a flat list of cards.
|
||||||
|
|
||||||
|
## 2. Goals
|
||||||
|
|
||||||
|
1. Admin sees each programmer's live state (working / paused / idle / away), current task,
|
||||||
|
today's time, and help given/received.
|
||||||
|
2. Each programmer's work is reviewed per day: approve, or disapprove with remarks; the
|
||||||
|
programmer justifies (and may append corrections) until approved.
|
||||||
|
3. Programmers generate their own Accomplishment Report (PDF, date-filtered); the admin
|
||||||
|
generates one for any programmer.
|
||||||
|
4. The Mine tab becomes a motivating dashboard with stats, a streak, badges, and tasks
|
||||||
|
separated into In progress / Paused / Not started / Completed / Cancelled.
|
||||||
|
|
||||||
|
### Non-goals
|
||||||
|
|
||||||
|
- Live "help sessions" (help is derived from work logs after the fact).
|
||||||
|
- XP/levels and team leaderboards.
|
||||||
|
- Editing or deleting existing work-log entries (they stay append-only).
|
||||||
|
- Migrating the existing DTR / task PDFs to the new shared PDF helpers.
|
||||||
|
|
||||||
|
## 3. Decisions
|
||||||
|
|
||||||
|
| Topic | Decision |
|
||||||
|
|---|---|
|
||||||
|
| Approval unit | **Day Sheet** — one per programmer per day; admin can **flag task rows** in it |
|
||||||
|
| Submission | **Auto-submit at day end**, plus **manual early submit** |
|
||||||
|
| Same-day approval | **Allowed**; later work that day reopens the sheet as "amended after approval" |
|
||||||
|
| On disapprove | Programmer **justifies** and **may append corrective entries**; originals are immutable |
|
||||||
|
| Late entries | Back-dated work logs allowed on **pending or disapproved** days, never on approved days; no backfilling days that have no sheet |
|
||||||
|
| Helping | **Derived from work logs** (helper = work-log author ≠ task assignee) |
|
||||||
|
| Report scope | **Toggle when generating**: approved days only (default) or include unapproved days (marked) |
|
||||||
|
| Report layout | **By day**: Date · Task/Project · Accomplishment · Time, then summary and signatures |
|
||||||
|
| Gamification | **Stat tiles + streak + badges** |
|
||||||
|
| Notifications | **In-app + push** |
|
||||||
|
| Monitor states | Working / Paused / Idle / **On leave** / **Pass slip** |
|
||||||
|
| Tabs | Programmer: **Mine · All · Day Sheets** — Admin: **Monitor · Approvals · Mine · All** |
|
||||||
|
| Core approach | Day-sheet table + append-only review thread + SECURITY DEFINER RPCs + pg_cron auto-submit + approval snapshot |
|
||||||
|
|
||||||
|
**"Admin" means `profiles.role = 'admin'` only.** The existing `isAdminProvider`
|
||||||
|
(`lib/providers/profile_provider.dart:305`) also returns true for programmers, and the
|
||||||
|
module's RLS treats both roles alike. All approval and monitor gating uses a new
|
||||||
|
`isStrictAdminProvider` on the client and `role = 'admin'` in SQL.
|
||||||
|
|
||||||
|
## 4. Existing code this builds on
|
||||||
|
|
||||||
|
| Fact | Where |
|
||||||
|
|---|---|
|
||||||
|
| Task status is `queued \| in_progress \| completed \| cancelled`. **Paused** = `in_progress` whose latest lifecycle activity-log event is `paused`. **Not started** = `queued`. | `lib/models/programmer_task.model.dart:39-47` |
|
||||||
|
| Work logs are append-only; the day is known only from `created_at`; assignee rows have `minutes = null`, helper rows have minutes. | `supabase/migrations/20260927140000_add_programmer_projects_worklogs_comments.sql:90-111` |
|
||||||
|
| Duration core `computeEffectiveDuration` | `lib/utils/task_duration.dart:38-75` |
|
||||||
|
| Ledger `computeTaskLedger` credits **all** clock time to the *current* assignee — the per-day split must not do this. | `lib/utils/programmer_task_ledger.dart:46-78` |
|
||||||
|
| `ReportDateFilter` is hard-wired to `reportDateRangeProvider`; `ReportDateRange.end` is exclusive. | `lib/screens/reports/report_date_filter.dart`, `lib/providers/reports_provider.dart:13-51` |
|
||||||
|
| Per-person PDF with letterhead + preview dialog (period label is off by one day at :211). | `lib/screens/attendance/dtr_pdf.dart` |
|
||||||
|
| DTO + pure PDF builder pattern | `lib/screens/attendance/work_log_pdf.dart` |
|
||||||
|
| Notification `type` is free text; render + tap switch; push via `send_fcm`. | `lib/screens/notifications/notifications_screen.dart:63-140`, `lib/services/notification_bridge.dart:97,152`, `lib/providers/notifications_provider.dart:376` |
|
||||||
|
| Scheduled push: pg_cron → `scheduled_notifications` (`schedule_id` nullable) → edge function `notify_type` switch. | `supabase/functions/process_scheduled_notifications/index.ts` |
|
||||||
|
| SECURITY DEFINER RPC that writes status + notifications atomically | `respond_shift_swap`, `supabase/migrations/20260322190000_pm_oncall_companion_swap.sql` |
|
||||||
|
| Per-user "on leave / pass slip today" logic | `lib/screens/dashboard/dashboard_screen.dart:371-433` |
|
||||||
|
|
||||||
|
## 5. Day Sheets
|
||||||
|
|
||||||
|
### 5.1 What a Day Sheet contains
|
||||||
|
|
||||||
|
One **row per task** the programmer worked on that day:
|
||||||
|
|
||||||
|
- task title, category, project;
|
||||||
|
- **time that day**:
|
||||||
|
- *Assignee rows*: timer intervals (started/resumed → paused/completed/cancelled)
|
||||||
|
clipped to Manila day boundaries, credited to **whoever was assignee at that moment**
|
||||||
|
(walk `created` / `assigned` / `reassigned` events). A later reassignment never changes
|
||||||
|
a past day. `adjustment` events count against the day they were created.
|
||||||
|
- *Helper rows*: the helper's entered minutes, labelled "Helped <assignee>".
|
||||||
|
- **notes**: that day's work-log entries for the task.
|
||||||
|
|
||||||
|
The admin flags **rows** when disapproving.
|
||||||
|
|
||||||
|
### 5.2 Schema
|
||||||
|
|
||||||
|
New migration `supabase/migrations/20260927150000_add_programmer_day_sheets.sql`.
|
||||||
|
|
||||||
|
**`programmer_day_sheets`**
|
||||||
|
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | uuid pk | |
|
||||||
|
| `programmer_id` | uuid → profiles | |
|
||||||
|
| `work_date` | date | Manila calendar day |
|
||||||
|
| `status` | text | check `draft \| pending \| disapproved \| approved`, default `draft` |
|
||||||
|
| `submitted_at` | timestamptz | |
|
||||||
|
| `auto_submitted` | bool | default false |
|
||||||
|
| `reviewed_by` | uuid → profiles | last admin to approve/disapprove |
|
||||||
|
| `reviewed_at` | timestamptz | |
|
||||||
|
| `resubmissions` | int | default 0; incremented by justify |
|
||||||
|
| `approved_snapshot` | jsonb | rows as approved (see 5.5) |
|
||||||
|
| `created_at`, `updated_at` | timestamptz | |
|
||||||
|
|
||||||
|
Unique `(programmer_id, work_date)`.
|
||||||
|
|
||||||
|
**`programmer_day_sheet_events`** — append-only review thread
|
||||||
|
|
||||||
|
| Column | Type | Notes |
|
||||||
|
|---|---|---|
|
||||||
|
| `id` | uuid pk | |
|
||||||
|
| `sheet_id` | uuid → sheets, on delete cascade | |
|
||||||
|
| `actor_id` | uuid null | null = system |
|
||||||
|
| `kind` | text | check `submitted \| auto_submitted \| disapproved \| justified \| approved \| amended` |
|
||||||
|
| `body` | text | remarks / justification; check non-empty for `disapproved` and `justified` |
|
||||||
|
| `flagged_task_ids` | uuid[] | default `'{}'` |
|
||||||
|
| `created_at` | timestamptz | |
|
||||||
|
|
||||||
|
**`programmer_task_work_logs.work_date`** — `date not null default (now() at time zone
|
||||||
|
'Asia/Manila')::date`. Existing rows are backfilled from `created_at`. A BEFORE INSERT
|
||||||
|
guard trigger allows:
|
||||||
|
|
||||||
|
- `work_date` = today (Manila); or
|
||||||
|
- a past `work_date` only if the author's sheet for that date is `pending` or
|
||||||
|
`disapproved`.
|
||||||
|
|
||||||
|
Anything else raises an error.
|
||||||
|
|
||||||
|
**`notifications.day_sheet_id`** — `uuid null references programmer_day_sheets on delete
|
||||||
|
cascade`.
|
||||||
|
|
||||||
|
**RLS:** sheets and events are readable by the owner and by `role = 'admin'`. There are no
|
||||||
|
client INSERT/UPDATE/DELETE policies; every write goes through the RPCs or triggers below.
|
||||||
|
Both tables are added to the `supabase_realtime` publication.
|
||||||
|
|
||||||
|
### 5.3 Status transitions
|
||||||
|
|
||||||
|
Each transition is a SECURITY DEFINER RPC with `set search_path = public`, explicit
|
||||||
|
`auth.uid()`, role, and current-status checks. Each one atomically updates the sheet,
|
||||||
|
inserts a thread event, and inserts notification rows, then returns the recipient user ids
|
||||||
|
so the client can call `send_fcm`. A stale status raises `already_reviewed`.
|
||||||
|
|
||||||
|
| RPC | Who | From → To | Rule |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `submit_day_sheet(p_date)` | owner | draft → pending | Any time, including early. Upserts the row. |
|
||||||
|
| `disapprove_day_sheet(p_sheet, p_remarks, p_flagged_task_ids)` | admin | pending → disapproved | Remarks required. |
|
||||||
|
| `justify_day_sheet(p_sheet, p_body)` | owner | disapproved → pending | Justification required; `resubmissions += 1`. |
|
||||||
|
| `approve_day_sheets(p_items)` — `[{sheet_id, snapshot}]` | admin | pending or disapproved → approved | Same-day allowed. Bulk-capable. Stores the snapshot. |
|
||||||
|
|
||||||
|
```
|
||||||
|
draft ──submit / auto──▶ pending ──approve──▶ approved
|
||||||
|
│ ▲ │
|
||||||
|
disapprove justify │ new work on the same day
|
||||||
|
▼ │ │ (system "amended")
|
||||||
|
disapproved ◀────────────┘ (back to pending)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5.4 Automatic behaviour
|
||||||
|
|
||||||
|
- **Draft creation.** Triggers upsert a `draft` sheet when a work log is inserted (for the
|
||||||
|
author, on `work_date`) and when a lifecycle activity-log event is inserted (for the
|
||||||
|
task's assignee, on the event's Manila date). Only for `role = 'programmer'`.
|
||||||
|
- **Amended after approval.** A work log or lifecycle event landing on an **approved
|
||||||
|
sheet for today** sets it back to `pending` and adds a system `amended` event. This is not
|
||||||
|
counted as a disapproval. The Approve button warns the admin when that programmer has a
|
||||||
|
timer running.
|
||||||
|
- **Nightly auto-submit** — `auto_submit_day_sheets()` via pg_cron at 00:05 Manila:
|
||||||
|
1. For assignees of tasks still running, upsert yesterday's sheet (reopening it as
|
||||||
|
`amended` if it was approved).
|
||||||
|
2. Flip every `draft` sheet with `work_date < today` to `pending`
|
||||||
|
(`auto_submitted = true`, `auto_submitted` event).
|
||||||
|
3. If anything is pending, insert one `scheduled_notifications` row per admin with
|
||||||
|
`notify_type = 'day_sheet_digest'`, scheduled for 08:00 Manila.
|
||||||
|
|
||||||
|
The cron registration uses the same `DO $$ … EXCEPTION` guard as existing cron
|
||||||
|
migrations.
|
||||||
|
|
||||||
|
### 5.5 Approval snapshot
|
||||||
|
|
||||||
|
On approval the admin client sends the rows it displayed:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"v": 1,
|
||||||
|
"total_seconds": 27900,
|
||||||
|
"rows": [
|
||||||
|
{
|
||||||
|
"task_id": "…", "title": "…", "category": "Bug Fix", "project_name": "HIS",
|
||||||
|
"kind": "assignee", "helped_name": null,
|
||||||
|
"seconds": 11400, "notes": ["Fixed session timeout…"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Approved days are thereby fixed records: the report and stats read the snapshot, so later
|
||||||
|
renames or reassignments do not change what was approved.
|
||||||
|
|
||||||
|
## 6. Approval UX
|
||||||
|
|
||||||
|
### 6.1 Tabs
|
||||||
|
|
||||||
|
`programmer_tasks_list_screen.dart` shows tabs by role:
|
||||||
|
|
||||||
|
- **Programmer:** Mine · All · Day Sheets
|
||||||
|
- **Admin:** Monitor · Approvals · Mine · All
|
||||||
|
|
||||||
|
### 6.2 Day Sheets tab (programmer)
|
||||||
|
|
||||||
|
- **Today card** — today's rows (live), total time, Draft/Submitted chip, **Submit day**.
|
||||||
|
- **Needs your attention** — disapproved sheets, pinned, warning style.
|
||||||
|
- **History** — sheets by date with status filter chips (`AppStatusSummaryRow`).
|
||||||
|
- App bar: **Accomplishment Report**.
|
||||||
|
|
||||||
|
### 6.3 Sheet detail — `/programmer-tasks/day-sheets/:id` (both roles)
|
||||||
|
|
||||||
|
- Header: date, programmer, status, total time, "Round N" when resubmitted.
|
||||||
|
- Rows table: task · time · notes. Flagged rows are highlighted (`AppStatusColors`
|
||||||
|
warning + flag icon).
|
||||||
|
- Thread timeline, styled like `widgets/activity_timeline.dart`.
|
||||||
|
- **Programmer, when disapproved:**
|
||||||
|
- **Add correction** — the existing work-log dialog with `workDate` preset to that day,
|
||||||
|
flagged tasks listed first.
|
||||||
|
- Justification field (cannot be empty; modeled on the checkout-justification dialog,
|
||||||
|
`attendance_screen.dart:1462-1510`).
|
||||||
|
- **Resubmit**.
|
||||||
|
- **Admin, when pending or disapproved:**
|
||||||
|
- Row checkboxes for flagging.
|
||||||
|
- **Disapprove** — dialog lists the flagged rows; remarks required.
|
||||||
|
- **Approve** — warns if a timer is running. The admin client builds the snapshot.
|
||||||
|
|
||||||
|
### 6.4 Approvals tab (admin)
|
||||||
|
|
||||||
|
- Summary chips: Pending · Awaiting justification · Approved.
|
||||||
|
- Programmer filter and the shared date filter.
|
||||||
|
- Cards grouped by date: programmer, total time, task count, Early/Auto badge, Round N.
|
||||||
|
- Multi-select → **Approve selected**. Disapproval stays per sheet because it needs remarks.
|
||||||
|
|
||||||
|
## 7. Admin Monitor
|
||||||
|
|
||||||
|
- **Summary row:** Working · Paused · Idle · On leave/Pass slip · Sheets pending.
|
||||||
|
- **One card per programmer** (grid: 1 column on mobile, 2–3 on desktop):
|
||||||
|
- State pill: **Working** (success) / **Paused** (warning) / **Idle** (neutral) /
|
||||||
|
**On leave** or **Pass slip** (info; overrides the others).
|
||||||
|
- Current task: title, category/project `TechChip`s, live "running 1h 12m today".
|
||||||
|
- Today's total; in-progress / paused / not-started counts; today's sheet status.
|
||||||
|
- Help chips: "Helped → Ben · task · 45m", "Helped by ← Ana · 30m".
|
||||||
|
- **Drill-down** (tap a card): `ProgrammerDashboard(userId, readOnly: true)`, their recent
|
||||||
|
Day Sheets, and **Generate Accomplishment Report** with the programmer preselected.
|
||||||
|
- **Who's helping whom** panel: helper → assignee · task · minutes · time, with a day
|
||||||
|
stepper (defaults to today). The working/paused state is always live.
|
||||||
|
|
||||||
|
**Data**
|
||||||
|
|
||||||
|
- New `programmerRunStatesProvider`: realtime stream of lifecycle activity logs for all
|
||||||
|
`in_progress` task ids → latest lifecycle event per task → Working vs Paused. It also
|
||||||
|
replaces the Mine tab's `myRunningProgrammerTaskIdProvider` inference, which currently
|
||||||
|
marks every in-progress task as paused when offline.
|
||||||
|
- Today's time: the per-day split (5.1).
|
||||||
|
- Help: work logs with `work_date = day` whose author ≠ task assignee.
|
||||||
|
- Leave / pass slip: extract the per-user "today" logic from
|
||||||
|
`dashboard_screen.dart:371-433` into `lib/utils/staff_presence.dart`, used by both the
|
||||||
|
dashboard and the Monitor.
|
||||||
|
- Admin-only. Programmers keep the existing All tab and get no live view of teammates.
|
||||||
|
|
||||||
|
## 8. Mine dashboard
|
||||||
|
|
||||||
|
A single widget, `ProgrammerDashboard(userId, {readOnly})`, used for the Mine tab and for
|
||||||
|
the Monitor drill-down.
|
||||||
|
|
||||||
|
1. **Now working hero** — the running task with a live timer (today + total) and
|
||||||
|
Pause/Complete. If nothing is running: "Nothing running" and **Start** on the top
|
||||||
|
not-started task (via `startProgrammerTaskWithPausePrompt`).
|
||||||
|
2. **Stat tiles** (`AppMetricCard`):
|
||||||
|
- **Today's focus** — ring against an 8h target (pattern:
|
||||||
|
`reports/widgets/conversion_rate_card.dart:43-55`).
|
||||||
|
- **Completed this week**, with change vs last week.
|
||||||
|
- **Streak** — consecutive weekdays whose sheet is pending or approved. Weekends and
|
||||||
|
approved-leave days are skipped (neither count nor break). A disapproved day breaks
|
||||||
|
the streak until justified. Today counts once submitted. Shows best streak too.
|
||||||
|
- **First-pass approval rate** (last 30 days) — approved sheets with no `disapproved`
|
||||||
|
event ÷ reviewed sheets.
|
||||||
|
- **Help given** this month.
|
||||||
|
- Streak and approval rate are hidden for admins (admins have no sheets).
|
||||||
|
3. **Badges** — computed from data each time (no table). Locked badges are greyed out with
|
||||||
|
progress (e.g. 7/10).
|
||||||
|
|
||||||
|
| Badge | Unlocks at |
|
||||||
|
|---|---|
|
||||||
|
| First Finish | first completed task |
|
||||||
|
| Finisher | 10 / 50 / 100 completed tasks |
|
||||||
|
| Bug Squasher | 10 completed Bug Fix tasks |
|
||||||
|
| Deep Work | 100 / 500 approved hours |
|
||||||
|
| On a Roll | best streak 5 / 10 / 20 |
|
||||||
|
| Team Player | 10h help given |
|
||||||
|
| Clean Week | a Mon–Fri week with every day approved first time |
|
||||||
|
|
||||||
|
4. **Task sections** (`AppSectionHeader` + count): In progress · Paused · Not started ·
|
||||||
|
Completed (collapsed; last 10 + Show all) · Cancelled (collapsed). The list's private
|
||||||
|
`_TaskCard` is extracted to `widgets/programmer_task_card.dart`.
|
||||||
|
|
||||||
|
**Data:** hour-based stats and badges sum approved snapshots; "today" figures come from the
|
||||||
|
live per-day split; counts come from `programmerTasksProvider` filtered to the user.
|
||||||
|
Calculations live in pure functions (`computeProgrammerStats`, `computeBadges`).
|
||||||
|
|
||||||
|
## 9. Accomplishment Report (PDF)
|
||||||
|
|
||||||
|
**Entry points:** Day Sheets tab app bar (programmer, self); Approvals tab app bar and
|
||||||
|
Monitor drill-down (admin).
|
||||||
|
|
||||||
|
**Generate dialog**
|
||||||
|
|
||||||
|
- Programmer picker — admin only; searchable single-select of `role = 'programmer'`
|
||||||
|
from `profilesProvider`.
|
||||||
|
- Date filter — `ReportDateFilter.controlled`, default "This Month".
|
||||||
|
- Toggle — "Include unapproved days (marked)", off by default.
|
||||||
|
- **Generate** → preview dialog with Print and Download.
|
||||||
|
|
||||||
|
**Date filter reuse:** add `ReportDateFilter.controlled({value, onChanged})` to
|
||||||
|
`lib/screens/reports/report_date_filter.dart`. `_DateFilterDialog` returns the chosen
|
||||||
|
`ReportDateRange` instead of writing the provider. The default constructor keeps binding
|
||||||
|
`reportDateRangeProvider`, so the Reports screen is unchanged.
|
||||||
|
|
||||||
|
**Layout** (A4 MultiPage, Roboto, CRMC letterhead):
|
||||||
|
|
||||||
|
1. Info box — name, position, period (**inclusive** end date: `end − 1 day`).
|
||||||
|
2. By-day table — Date | Task/Project | Accomplishment | Time.
|
||||||
|
- Accomplishment = that day's work-log notes (Quill delta → plain text); "(no notes)"
|
||||||
|
when a row has time but no notes.
|
||||||
|
- Help rows read "Helped Ana — <task>".
|
||||||
|
- When unapproved days are included, they carry a Pending/Disapproved tag.
|
||||||
|
3. Footnote (approved-only mode) — "N days in the period are not included (x pending,
|
||||||
|
y disapproved)".
|
||||||
|
4. Summary — days reported, total time, tasks completed in the period, help given, time by
|
||||||
|
category.
|
||||||
|
5. Signatures — "Prepared by" the programmer; "Approved by" the reviewing admin's name if
|
||||||
|
one admin approved every included day, otherwise a blank line.
|
||||||
|
|
||||||
|
**Data:** approved days come from `approved_snapshot`; unapproved days (when included) are
|
||||||
|
computed live. `buildAccomplishmentReportData(...)` (pure) produces a DTO;
|
||||||
|
`buildAccomplishmentReportPdf(dto)` only draws.
|
||||||
|
|
||||||
|
**Shared helpers (used by new code only):** `lib/utils/pdf/pdf_letterhead.dart` (fonts,
|
||||||
|
logo, letterhead) and `lib/widgets/pdf_preview_dialog.dart` (pdfrx preview,
|
||||||
|
`Printing.layoutPdf`, `Printing.sharePdf`, modeled on `_DtrPdfDialog`).
|
||||||
|
|
||||||
|
## 10. Notifications
|
||||||
|
|
||||||
|
| Type | Recipient | Trigger |
|
||||||
|
|---|---|---|
|
||||||
|
| `day_sheet_submitted` | all admins | programmer submits early |
|
||||||
|
| `day_sheet_digest` | each admin, 08:00 | nightly auto-submit left sheets pending; opens Approvals |
|
||||||
|
| `day_sheet_disapproved` | programmer | admin disapproves (includes remarks) |
|
||||||
|
| `day_sheet_justified` | the reviewing admin | programmer justifies and resubmits |
|
||||||
|
| `day_sheet_approved` | programmer | admin approves (single or bulk) |
|
||||||
|
|
||||||
|
RPCs insert the notification rows and return recipients; the client sends push through
|
||||||
|
`NotificationsController.sendPush`. The digest goes through `scheduled_notifications` with
|
||||||
|
a new case in `process_scheduled_notifications`. New render cases and tap routing go into
|
||||||
|
`notifications_screen.dart` and `notification_bridge.dart`, and `day_sheet_id` is added to
|
||||||
|
`notification_item.model.dart`.
|
||||||
|
|
||||||
|
## 11. Error handling
|
||||||
|
|
||||||
|
- **Stale status** (e.g. two admins act at once) → "This sheet was already reviewed" and
|
||||||
|
the view refreshes.
|
||||||
|
- **Empty remarks/justification** → blocked in the dialog and by the DB check.
|
||||||
|
- **Disallowed back-dated entry** → the guard trigger's error is shown as a clear message.
|
||||||
|
- **Push failure** → non-blocking; logged, as in existing flows.
|
||||||
|
- **Offline** → Day Sheets are online-only (like work logs); actions are disabled with a
|
||||||
|
banner.
|
||||||
|
|
||||||
|
## 12. Files
|
||||||
|
|
||||||
|
New (each under 500 lines):
|
||||||
|
|
||||||
|
- `supabase/migrations/20260927150000_add_programmer_day_sheets.sql`
|
||||||
|
- `lib/models/programmer_day_sheet.model.dart`,
|
||||||
|
`lib/models/programmer_day_sheet_event.model.dart` (plain online models)
|
||||||
|
- `lib/providers/programmer_day_sheets_provider.dart` (streams, RPC controller, push)
|
||||||
|
- `lib/providers/programmer_run_states_provider.dart`
|
||||||
|
- `lib/utils/programmer_daily_time.dart` (per-day split with assignee attribution)
|
||||||
|
- `lib/utils/programmer_stats.dart` (stats, streak, badges)
|
||||||
|
- `lib/utils/staff_presence.dart`
|
||||||
|
- `lib/utils/pdf/pdf_letterhead.dart`, `lib/widgets/pdf_preview_dialog.dart`
|
||||||
|
- `lib/screens/programmer_tasks/dashboard/` — dashboard, hero, stat tiles, badges, sections
|
||||||
|
- `lib/screens/programmer_tasks/monitor/` — monitor tab, programmer card, help panel
|
||||||
|
- `lib/screens/programmer_tasks/day_sheets/` — Day Sheets tab, Approvals tab, detail
|
||||||
|
screen, disapprove/justify dialogs
|
||||||
|
- `lib/screens/programmer_tasks/report/` — generate dialog, report data, report PDF
|
||||||
|
- `lib/screens/programmer_tasks/widgets/programmer_task_card.dart`
|
||||||
|
|
||||||
|
Modified:
|
||||||
|
|
||||||
|
- `lib/screens/programmer_tasks/programmer_tasks_list_screen.dart` — role tabs, card extract
|
||||||
|
- `lib/models/programmer_task_work_log.model.dart`,
|
||||||
|
`lib/providers/programmer_task_work_logs_provider.dart`,
|
||||||
|
`lib/screens/programmer_tasks/widgets/work_log_section.dart` — `workDate`
|
||||||
|
- `lib/screens/reports/report_date_filter.dart` — controlled constructor
|
||||||
|
- `lib/providers/profile_provider.dart` — `isStrictAdminProvider`
|
||||||
|
- `lib/routing/app_router.dart` — day-sheet detail route
|
||||||
|
- `lib/screens/notifications/notifications_screen.dart`,
|
||||||
|
`lib/services/notification_bridge.dart`, `lib/models/notification_item.model.dart`
|
||||||
|
- `supabase/functions/process_scheduled_notifications/index.ts` — digest case
|
||||||
|
- `lib/screens/dashboard/dashboard_screen.dart` — use `staff_presence.dart`
|
||||||
|
|
||||||
|
## 13. Delivery slices
|
||||||
|
|
||||||
|
Each slice ships independently on `feat/programmer-tasks`.
|
||||||
|
|
||||||
|
1. **Mine dashboard** — run-state provider, per-day split util, dashboard. No schema
|
||||||
|
change; fixes the offline "everything paused" bug. Approval-based stats appear once
|
||||||
|
slice 3 lands.
|
||||||
|
2. **Admin Monitor** — plus the `staff_presence.dart` extraction.
|
||||||
|
3. **Day Sheets** — migration, RPCs, cron, Day Sheets/Approvals tabs, detail screen,
|
||||||
|
notifications. **The user applies the migration and redeploys
|
||||||
|
`process_scheduled_notifications`** (the Supabase migration tool is blocked in this
|
||||||
|
environment).
|
||||||
|
4. **Accomplishment Report** — date-filter refactor, PDF helpers, report.
|
||||||
|
|
||||||
|
## 14. Testing and verification
|
||||||
|
|
||||||
|
- `flutter analyze lib` clean; `flutter test` passes apart from the three known,
|
||||||
|
unrelated failures (theme_overhaul, profile_screen, user_management).
|
||||||
|
- **Unit (pure Dart, TDD):** per-day split (midnight crossing, reassignment attribution,
|
||||||
|
pauses, adjustments); stats and streak (weekends, leave, disapproved days); badges;
|
||||||
|
report data (approved-only vs marked, snapshot vs live, footnote, inclusive period).
|
||||||
|
- **Controller (mock-first, pattern of `test/programmer_tasks_controller_test.dart`):**
|
||||||
|
RPC calls, push invocation, stale-status error mapping.
|
||||||
|
- **Widget:** Disapprove dialog requires remarks; dashboard sections; tabs by role.
|
||||||
|
- **SQL checklist** after the migration is applied (rolled-back transactions with
|
||||||
|
`request.jwt.claims` set per role): every transition allowed/denied by role and status;
|
||||||
|
back-dated work-log guard; amended-after-approval; `auto_submit_day_sheets()` called
|
||||||
|
directly; `get_advisors` security check.
|
||||||
|
- **End-to-end** via the QA harness (programmer, then admin; light and dark): log work →
|
||||||
|
submit early → admin flags and disapproves → programmer adds a correction and justifies
|
||||||
|
→ admin approves → both generate the PDF (approved-only and marked) → Monitor shows
|
||||||
|
Working/Paused/On leave and help edges → notifications render and route.
|
||||||
|
- `flutter build web` succeeds.
|
||||||
Reference in New Issue
Block a user