Aug 3 - Add scheduled task confirm button in notification email, and separate pending/completed scheduled tasks UI
This commit is contained in:
@@ -453,7 +453,9 @@ All are nullable and **legacy phase36 rows keep NULLs**, falling back to `_add_i
|
||||
- **Form validation.** `ScheduledInspectionForm.validate()` rejects an end date on a one-time schedule and one earlier than the due date. That is not sufficient alone: `align_due_date()` can push the picked date forward onto the rule (a Tuesday pick on a Mon/Wed/Fri schedule becomes Wednesday), so `_reject_if_past_end_date()` re-checks after `_apply_recurrence()` in both create and edit. Edit rolls back first — `sched` is persistent and already mutated at that point.
|
||||
- **Three status states** in the list: Active, **Ended** (`is_expired` — ran its course), Inactive (a manager switched it off).
|
||||
- **Assignment notification** (immediate): on **create**, the assigned inspector gets an in-app + email "assigned to you" notification; on **edit**, only when the inspector actually changes (a "reassigned to you" notification to the new assignee). Via `_notify_assignee()` in the blueprint using `event_type=EVENT_SCHEDULED_INSPECTION`.
|
||||
- **Receipt acknowledgement (phase47).** `acknowledged_at` records when the assigned inspector confirms they received the request — a way for the manager to see the assignment was seen, distinct from starting it. `POST /<id>/acknowledge` is **assignee-only** (like Start; a manager cannot confirm on someone's behalf) and idempotent; on first confirm the schedule's **creator** is notified via `_notify_creator_acknowledged()` (`event_type=EVENT_SCHEDULED_INSPECTION`, skipped when creator is inactive or is the inspector). **Once per assignment, not per occurrence:** it is NOT reset when a recurring schedule rolls forward (`fulfill()` leaves it), but the **edit route resets it to NULL when the inspector changes** so a new assignee must re-confirm. Surfaced with a Confirmed/Awaiting badge + a "Confirm receipt" button (assignee only) on both the scheduled-inspections **list** and the **dashboard** panel. `ScheduledInspection.is_acknowledged` is the boolean helper. Exposed **read-only** in the API payload (`acknowledged_at`) — confirming stays on the web per rule 77; an iPad confirm action would be a follow-up.
|
||||
- **Receipt acknowledgement (phase47).** `acknowledged_at` records when the assigned inspector confirms they received the request — a way for the manager to see the assignment was seen, distinct from starting it. `POST /<id>/acknowledge` is **assignee-only** (like Start; a manager cannot confirm on someone's behalf) and idempotent; on first confirm the schedule's **creator** is notified via `_notify_creator_acknowledged()` (`event_type=EVENT_SCHEDULED_INSPECTION`, skipped when creator is inactive or is the inspector). The stamp-log-notify body is factored into `_do_acknowledge(sched, actor_username)`, shared by the POST route and the email-token route. **Once per assignment, not per occurrence:** it is NOT reset when a recurring schedule rolls forward (`fulfill()` leaves it), but the **edit route resets it to NULL when the inspector changes** so a new assignee must re-confirm. Surfaced with a Confirmed/Awaiting badge + a "Confirm receipt" button (assignee only, active schedules) on both the scheduled-inspections **list** and the **dashboard** panel. `ScheduledInspection.is_acknowledged` is the boolean helper. Exposed **read-only** in the API payload (`acknowledged_at`) — confirming stays on the web per rule 77; an iPad confirm action would be a follow-up.
|
||||
- **Confirm from the email (phase47).** The assignment email (and the advance/due reminder emails while still unconfirmed) carry a green **"Confirm receipt"** button beside "View Details". It links to `GET /scheduled-inspections/confirm/<token>` — a **login-free** landing (no `@login_required`, same pattern as the `public` blueprint) authorised by an `itsdangerous.URLSafeTimedSerializer` token (salt `scheduled-inspection-ack`, 30-day max age, signed with `SECRET_KEY` — **no DB column**). The token binds `{sid, iid}` so a schedule **reassigned** to another inspector invalidates the previous assignee's emailed link (the route checks `sched.inspector_id == token iid`). The route renders the standalone `scheduled_inspections/confirm_result.html` with a `status` of confirmed / already / reassigned / inactive / expired / invalid / missing; the acknowledgement is idempotent so a re-click or email-client prefetch is harmless. The email button is built by `_confirm_action(sched)` (returns None once acknowledged), threaded into `notify(..., extra_action={'label','url'})` — an **email-only** second button; `extra_action.url` is absolute and is NOT prefixed with `base_url`. In-app notifications are unchanged.
|
||||
- **List tabs (phase47).** The scheduled-inspections list (`index()`) has two tabs via `?tab=pending|completed` (default `pending`): **Pending** = active schedules (`active == True`, ordered by next due date), **Completed** = closed schedules (`active == False`, ordered by `last_completed_at` desc — fulfilled one-times, ended recurring, and manually-deactivated rows). Exhaustive, non-overlapping partition; the in-row Status badge (Active / Ended / Inactive) disambiguates the closed bucket, and a "last completed" date is shown when set. `pending_count` / `completed_count` drive the tab pill badges.
|
||||
- **Reminders** are dispatched by the cron endpoint (see §11): advance (1 day before) + due-date to the inspector, overdue to admin/director — each fires at most once per occurrence via the `*_notified` flags. Uses `notify()` with `event_type=EVENT_SCHEDULED_INSPECTION`.
|
||||
- Dashboard shows an **upcoming (next 7 days) / overdue** panel for non-customers (inspectors see only their own).
|
||||
- **Instructions (July 2026).** `ScheduledInspectionForm.notes` is labelled **"Instructions"** and `scheduled_inspections/form.html` explains that the text reaches the inspector. The *field name*, `ScheduledInspection.notes`, the `scheduled_inspections.notes` column and the API key `notes` are all unchanged — the rename is a label only (rule 84). The text is surfaced to the inspector in two places: `inspections/execute.html` renders an indigo panel between the header and the form grid, guarded on `inspection.scheduled_inspection and .notes` (NULL for ad-hoc work and for schedules deleted after the start); the iPad shows it on the scheduled row, on the start screen and above the form.
|
||||
@@ -521,7 +523,7 @@ Management (`/scheduled-inspections/new|edit|delete`) is `@project_manager_requi
|
||||
| `audit` | `/audit` | list (admin only), view, purge |
|
||||
| `reports` | `/reports` | index, facility report, scorecard, CSV/PDF/Excel export, issues-aging, sla-compliance, followup-closure, facility summary PDF |
|
||||
| `scheduled_reports` | `/scheduled-reports` | CRUD + manual trigger (accessible via Reports sub-nav) |
|
||||
| `scheduled_inspections` | `/scheduled-inspections` | list, new/edit/delete (PM+), `GET /<id>/start` (**assigned inspector only** → creates linked inspection; 403 for non-assignees incl. managers), `POST /<id>/acknowledge` (**assigned inspector only** → confirms receipt, sets `acknowledged_at`, notifies creator; idempotent — Phase 47), `POST /run` (cron reminders, `token=DIGEST_SECRET`) |
|
||||
| `scheduled_inspections` | `/scheduled-inspections` | list (`?tab=pending\|completed` — Phase 47), new/edit/delete (PM+), `GET /<id>/start` (**assigned inspector only** → creates linked inspection; 403 for non-assignees incl. managers), `POST /<id>/acknowledge` (**assigned inspector only** → confirms receipt, sets `acknowledged_at`, notifies creator; idempotent — Phase 47), `GET /confirm/<token>` (**login-free** one-click email confirm; signed `itsdangerous` token binding schedule+inspector — Phase 47), `POST /run` (cron reminders, `token=DIGEST_SECRET`) |
|
||||
| `support` | `/support` | `GET /chat` (loads latest saved session; `?new=1` to start fresh), `POST /chat/message` (AJAX→Groq; **persists** user+assistant turns, returns `session_id`), `GET /my-conversations`, `GET /my-conversations/<id>` (customer chat history), `GET /admin/conversations`, `GET /admin/conversations/<id>` (staff, read-only), `GET /admin/knowledge` + `/new`, `/<id>/edit`, `/<id>/delete` (admin/director — chatbot knowledge base), `POST /tickets`, `GET /my-tickets`, `GET/POST /my-tickets/<id>`, `GET /admin/tickets`, `GET/POST /admin/tickets/<id>` |
|
||||
| `broadcast` | `/admin/broadcast` | `GET /` (compose + history), `POST /send` (admin-only; fans out one Notification per targeted user) |
|
||||
| `devices` | `/admin/devices` | `GET /` (device list from `api_device_tokens`), `POST /notify` (admin-only) |
|
||||
|
||||
Reference in New Issue
Block a user