docs: design for per-client task generation + preview-before-generate
Adds a clientId targeting mode and a dryRun flag to the admin generate-and-assign tool, plus a confirmation dialog showing summary counts before any tasks are actually created. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
parent
64ea7800ed
commit
270c38ff19
1 changed files with 121 additions and 0 deletions
|
|
@ -0,0 +1,121 @@
|
||||||
|
# Task Generation: Per-Client Mode & Preview-Before-Generate — Design
|
||||||
|
|
||||||
|
**Status:** Approved. Ready for implementation planning.
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
The admin "Generate & Assign Tasks" tool (`/api/tasks/generate-and-assign`,
|
||||||
|
surfaced on the Tasks → Generate & Assign page) only targets clients by
|
||||||
|
advocate + designation, which can silently sweep in every client that
|
||||||
|
advocate/designation pair matches — as discovered this session, that's up to
|
||||||
|
23 clients for a single pair. There's no way to generate tasks for just one
|
||||||
|
specific client. There's also no confirmation step anywhere in this flow:
|
||||||
|
clicking the button immediately writes tasks to the database with no
|
||||||
|
preview of what will be created or a chance to back out.
|
||||||
|
|
||||||
|
Goal: add a per-client targeting mode, and require a preview-and-confirm
|
||||||
|
step before any tasks are actually created, for this admin tool.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
**In scope:** the admin "Generate & Assign Tasks" page/endpoint only — both
|
||||||
|
its existing advocate+designation mode and the new per-client mode.
|
||||||
|
|
||||||
|
**Explicitly out of scope** (decided during brainstorming): the Policy
|
||||||
|
Group Manager's per-group "Generate Tasks" button, the Setup Wizard's
|
||||||
|
silent auto-generation on client-setup completion, and the silent
|
||||||
|
auto-regeneration that fires when a policy is removed from a group. None of
|
||||||
|
these get a preview step in this phase.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
|
||||||
|
| Question | Decision |
|
||||||
|
|---|---|
|
||||||
|
| Preview scope | Admin Generate & Assign page only (existing + new per-client mode) |
|
||||||
|
| Per-client assignee | Always that client's own `claimsAdvocateId` — not an arbitrary admin pick |
|
||||||
|
| Selection mode | Separate mode from advocate+designation (toggle), not a combined filter |
|
||||||
|
| Preview detail level | Summary counts (total + per-client breakdown), not a full task-by-task list, for both modes |
|
||||||
|
| Preview mechanism | Extend the existing endpoint with a `dryRun` flag rather than a separate preview endpoint or client-side estimate |
|
||||||
|
|
||||||
|
**Why `dryRun` on the existing endpoint, not a separate preview endpoint:**
|
||||||
|
a separate endpoint would duplicate the client/group/policy/template-matching
|
||||||
|
logic (including the past-due-date filter added earlier this session) —
|
||||||
|
real risk of the preview drifting out of sync with what actually gets
|
||||||
|
created, which is exactly the class of bug this session spent most of its
|
||||||
|
time fixing. A `dryRun` flag on the same code path guarantees the preview
|
||||||
|
can never lie about what will happen.
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
### API — `POST /api/tasks/generate-and-assign`
|
||||||
|
|
||||||
|
Body accepts **either** targeting mode, plus an optional flag:
|
||||||
|
```
|
||||||
|
{ advocateId, designationId, dryRun? } // existing mode
|
||||||
|
{ clientId, dryRun? } // new mode
|
||||||
|
```
|
||||||
|
Server validates exactly one mode is provided (400 if both or neither).
|
||||||
|
|
||||||
|
**Client mode specifics:**
|
||||||
|
- Assignee is always the target client's own `claimsAdvocateId`.
|
||||||
|
- If that client has no claims advocate set, return an error
|
||||||
|
(`"Client has no claims advocate assigned"`) rather than creating
|
||||||
|
unassigned tasks.
|
||||||
|
|
||||||
|
**Refactor:** the route's three sections (renewal groups / ungrouped
|
||||||
|
policies / client-level templates) already build a `tasksToCreate` array
|
||||||
|
before writing (including the `isRelevantDueDate` past-due filter). Split
|
||||||
|
each section into "compute what would be created" and "write it," so
|
||||||
|
`dryRun: true` runs the exact same computation but skips the
|
||||||
|
`createMany`/`create` calls and the audit log entry (nothing happened, so
|
||||||
|
nothing to log). A real run (`dryRun` false/absent) behaves as today, plus
|
||||||
|
records which mode triggered it (`advocate-designation` vs `client`) in the
|
||||||
|
audit log's `newValues`.
|
||||||
|
|
||||||
|
**Dry-run response shape:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"dryRun": true,
|
||||||
|
"clientsFound": 3,
|
||||||
|
"totalEstimatedTasks": 42,
|
||||||
|
"advocateName": "Jane Smith",
|
||||||
|
"clients": [
|
||||||
|
{ "id": "cl_...", "name": "Acme Corp", "estimatedTasks": 14 }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### UI — "Generate & Assign Tasks" card (`src/app/(dashboard)/tasks/assign/page-client.tsx`)
|
||||||
|
|
||||||
|
- Add a mode toggle: **By Advocate + Designation** (today's UI, unchanged)
|
||||||
|
vs **By Client** (new — a searchable picker using the `clients` prop
|
||||||
|
already passed into this component). In client mode, if the selected
|
||||||
|
client has no `claimsAdvocateId`, disable the action and show a helper
|
||||||
|
message explaining why.
|
||||||
|
- The generate button no longer calls the endpoint for real directly. It
|
||||||
|
first calls with `dryRun: true`:
|
||||||
|
- `totalEstimatedTasks === 0` → just toast the existing "no new tasks"
|
||||||
|
message, no dialog (nothing meaningful to confirm).
|
||||||
|
- Otherwise → open an `AlertDialog` (this codebase's existing pattern for
|
||||||
|
confirm-before-consequential-action) showing total counts + the
|
||||||
|
per-client breakdown table, with **Confirm** (re-calls the endpoint
|
||||||
|
without `dryRun`) and **Cancel** (closes, no side effects — nothing was
|
||||||
|
written during the preview call).
|
||||||
|
|
||||||
|
### Testing plan
|
||||||
|
Route-level tests for `generate-and-assign`:
|
||||||
|
- `dryRun: true` and a real run produce identical counts for the same
|
||||||
|
input (no drift between preview and reality).
|
||||||
|
- `clientId` mode only ever touches that one client.
|
||||||
|
- Missing-advocate error case for client mode.
|
||||||
|
- Mutual-exclusivity validation (400 when both or neither targeting mode
|
||||||
|
is provided).
|
||||||
|
|
||||||
|
## Out of scope / explicitly deferred
|
||||||
|
- Preview/confirm for the per-group manual button, the Setup Wizard's
|
||||||
|
auto-generation, and the remove-from-group auto-regeneration — all still
|
||||||
|
fire immediately with no confirmation, unchanged by this phase.
|
||||||
|
- The separately-discovered gap where policies that stay inside a group
|
||||||
|
never get `POLICY`-level tasks generated by any automatic path, and the
|
||||||
|
`generate-policy-tasks` route's missing idempotency check — both noted
|
||||||
|
during this session's review but not addressed by this design.
|
||||||
Loading…
Add table
Add a link
Reference in a new issue