PTR Tax Prep · Phase 3 handoff

§8 · Form specs — the three tracking modals

The tax-prep tracking, IRS transcript handpull, and bookkeeping work-log modals as server-rendered Django form specs: field tables (name, type, required, conditional-on, error copy), copy-paste modal markup on DaisyUI modal + native <dialog> (the §7 collapse, applied), and the HTMX interaction contract. Source of truth is ui_kits/product/app.js — every option list, default, required rule and error string below is lifted from it verbatim; do not invent fields. Companion: reference.html §5 (field vocabulary) · §6 (partial map) · §7 (class budget).

The shared modal + HTMX contract — all three forms follow it; per-form seams are noted on each block:

8.1 · Tax-prep tracking (tk* — retires the Zapier “TP Tracking” Zap · E-003.1)

Field specpartial partials/_tracking_form.htmlsubmit hx-post /tax-prep/cases/{id}/tracking/on success toast Tracking saved — {n} year(s) set to “{status}” · swap #tp-year-table + case row
FieldControl · typeRequiredConditional onError copy (app.js)
caseLocked display (.tp-readonly-inp) — from URL, server-side
yearsMulti checkbox toggles (.tp-ytog), seeded from the case's scope years; “+ other…” select appends any year 2015–2026 not already a chipYes — ≥ 1Select at least one year.
agencySegmented radio (.tp-segchoice): Federal / State / Both — no defaultYesChoose an agency.
statesChip multi-select (.tp-msel), the 51 US states + DC; chips removableWhen Agency is State or Both — ≥ 1; cleared server-side otherwiseagency ∈ {State, Both}Add at least one state for {Agency} filings.
statusSelect — the finalized-17 (FINAL_STATUSES, canonical order; placeholder “Select a status…”)YesSelect a status.
date_filedDate inputNo
applies_toSegmented radio: Personal / Business / Both — default seeded from scope (Both if the case has both return types, else the one it has). Routes the status onto the return type(s) — and can add that return to a year not yet in scopeAlways has a value
follow_upCheckbox “Follow-up required?” (.tp-chkline)No
assign_toSelect — active TP users (kit sample: Allen Rose, Cale Roy, Priya Shah, TPA Pool)When follow_upfollow_upAssign the follow-up.
follow_up_dateDate inputWhen follow_upfollow_upPick a date.
balanceCheckbox “Balance(s) owed?”No
balance_notesTextareaNo (submit without text records “Balance flagged”)balance
What submit writes (submitTracking — the model contract): for each selected year × the return type(s) from Applies-to — if the year isn't in scope it is appended (flagged added, shows the “New” badge) — set the federal status when Agency is Federal/Both, and set/insert a per-state status for every selected state when Agency is State/Both. Also records the tracking caption (agency, states, date filed), the follow-up task, and the balance-owed note. This write is what retires the Zapier “TP Tracking” submission.
Copy-paste markupstate shown agency Both · follow-up + balance checked (conditional sections open) · open attr + .rx-dlg wrapper are specimen-only
Notes on the markup. The <form style="display:contents"> wraps body + footer so the submit button posts while .modal-box keeps its flex scroll chain. The cheat-sheet table is static server-rendered content; its search box filters rows client-side (Alpine) — no HTMX. The year “+ other…” select appends a checked toggle chip client-side; the posted field is always years. The “platform” tag on Applies-to marks a capability the Zapier form never had.

8.2 · IRS transcript handpull (hp* — THS · Form 4506-C)

Field specpartial partials/_handpull_form.htmlsubmit hx-post /tax-prep/cases/{id}/handpulls/on success toast Handpull requested via THS — {n} transcript type(s), {taxpayer} · routed to TP · swap the transcripts card
FieldControl · typeRequiredConditional onError copy (app.js)
caseLocked display — from URL (“Auto-filled from the open case — no CaseID entry, no copy-paste errors.”)
departmentLocked constant: TP · Tax Prep (“Every handpull request routes to Tax Prep.”)
softwareLocked constant: THS (“Transcripts are retrieved via the THS software.”)
transcript_typesMulti option chips (.tp-optchip) — HP_TYPES: Wage & Income (WITS), Account, Return, Record of Account, Non-filing. Default seeds Wage & Income checkedYes — ≥ 1Pick at least one transcript type
taxpayerSegmented radio: Personal / Business / Both — default seeded from scopeAlways has a value
notesTextarea (“Optional — tax years needed, urgency, or anything the TP team should know”)No
What submit writes: a request-log record (HP-{seq}, software THS, dept TP, requester + timestamp) in state pending. Lifecycle: pending → submitted → received, or failed — server-side transitions as the THS pull progresses; the case-detail transcripts card lists records newest-first with the state badge. In app.js the ≥1-types rule surfaces as a toast; server-rendered it is a normal field error on the chips group, same copy.
Request-log states (.hp-status, kit classes) + a log item
pendingreceivedfailed
THSBothpending
W&I, Account · requested just now · Allen Rose · “Need 2022–2024 for the amended returns”
Copy-paste markupstate shown W&I + Account selected · taxpayer Both

8.3 · Bookkeeping work log (bk* — points formula moves server-side)

Field specpartial partials/_bk_form.htmlsubmit hx-post /tax-prep/cases/{id}/bookkeeping/ — upsert: posts the record id when editing (the form opens prefilled from the newest record; header sub shows “· editing BK-1001”)on success toast Bookkeeping saved — {pts} pts · {status} · swap the bookkeeping card / inventory row
FieldControl · typeRequiredConditional onError copy (app.js)
Core case info
caseLocked display — from URL (“Auto-linked to the case record.”)
yearsMulti checkbox toggles, scope-seeded + “+ other…” (as tracking)Yes — ≥ 1Select at least one year.
statusSelect — BK_STATUSES: Not Started, In Progress, Pending QA Review, QA Reviewed, Completed. Default Pending QA ReviewYesSelect a status.
Work product
bookkeeperSelect — BOOKKEEPERS (label: “Which bookkeeper did you review?”)YesSelect a bookkeeper.
total_businessesNumber, min 0 (“Total # of businesses (all years)”) — 0 is valid; blank is notYesEnter a number.
typesMulti option chips — BK_TYPES: Bank Statements, Payroll, Financial Statements, Cleanup / Catch-up (“Metrics below appear for the types you pick.”)Yes — ≥ 1Select at least one type.
Bank statements — # of businesses by transaction volume
bs_simple · bs_standard · bs_complexNumber grid, min 0: ≤ 1,500 txns (simple) · 1,501–3,000 (standard) · ≥ 3,001 (complex)Notypes incl. Bank Statements
Payroll
payroll_preparedSegmented Yes / No — default Notypes incl. Payroll
pr_qb_qtrs · pr_timecard_qtrs · pr_netcheck_qtrs · pr_940s · pr_w2_1_5 · pr_w2_6_10 · pr_w2_11_15Number grid, min 0: QB prepared # qtrs · Timecards # qtrs · Net checks # qtrs · 940(s) prepared · W2/W3 1–5 / 6–10 / 11–15 empNoPayroll type + payroll_prepared = Yes
Adjustments & notes
additional_pointsNumber, signed (“Additional points (manual +/−)”)No
mistake_in_pointsCheckbox “Mistake in points (correcting a prior entry)”No
notesTextarea (“Work completed, exceptions, context for the tax prep team…”)Yes — non-blankNotes are required.
Points move server-side. The client never computes points: the metric inputs hx-post …/bookkeeping/points/ hx-trigger="input delay:300ms" hx-target="#bk-points-bar" hx-swap="outerHTML" and the server returns the recomputed bar (partials/_bk_points_bar.html). The BK_PTS weights in app.js (bank 2/4/6 · payroll qtr 1.5 · 940 2 · W2 tiers 2/3/4) are sample values, backend-configured — label the bar “configured in backend” wherever the total is shown. Stored records keep the points as computed at save time.
Copy-paste markupstate shown edit mode (BK-1001) · Bank Statements + Payroll selected, payroll prepared — both metric grids open

8.4 · Partials, endpoints & error presentation

PieceSuggested partial / endpointNotes
Modal container<div id="tp-modal"></div> in the app base templateEvery form trigger targets it; one dialog at a time
Tracking formpartials/_tracking_form.html · GET/POST /tax-prep/cases/{id}/tracking/Context: case, scope years, US states, finalized-17, assignees. Success swaps #tp-year-table + _case_row.html (OOB outerHTML)
Handpull formpartials/_handpull_form.html · GET/POST /tax-prep/cases/{id}/handpulls/Success swaps the transcripts card; record lifecycle pending/submitted/received/failed is server-driven
Bookkeeping formpartials/_bk_form.html · GET/POST /tax-prep/cases/{id}/bookkeeping/Upsert by record; success swaps the bookkeeping card / inventory row
Points barpartials/_bk_points_bar.html · POST …/bookkeeping/points/The #bk-points-bar recalc swap target — formula + weights live server-side only
Field wrapperpartials/_form_field.htmlRenders label / .req / control / .hint / .errmsg + the .err state — shared by all three forms and the inline-validate swap unit (closest .tp-fld)
Error presentation — field level + form level (states from §5; copy verbatim from app.js)
Add at least one state for State filings.
Please complete the highlighted fields
Open decisions that touch these forms (flagged, not resolved — owners business-side): E-004.1 “via mailed” signature path — Date-filed / signature flows must not assume DocuSign-only; E-004.6/.7 Remind-Client / Request-Docs remain undefined (unrelated buttons stay disabled — these forms don't implement them); E-003.4 overdue threshold (14 vs 30 days) affects the row the tracking success-swap re-renders. Statuses: user-facing lists are the finalized-17 everywhere (E-001.7, reconciled July 17); WORKING_TO_FINAL and WORKING_GROUP in app.js stay intact as single re-mappable tables pending business sign-off. Points weights: backend-configured wherever shown.