Objective
- Stop CSRs from thinking invoice-terms jobs need a manual report “unlock” when clients should already have access.
- Make the work order Reports panel reflect computed client access (invoice terms, agreements, payment)—not only whether gating is enabled on the inspection.
- Reduce unnecessary
lockReport: falseoverrides and confusion when activity shows no one manually locked reports.
Background
- Product feedback from invoice-terms builder jobs (e.g. DW Homes NHF): work order shows To Be Invoiced, Agreements section is empty (no active or archived agreements), but the Reports panel still looks gated and CSRs manually disable gating to “unlock.”
- Example job referenced in investigation: CHI730.
- Activity feed shows no user who locked reports—expected for default gating, but misleading for staff who expect an audit trail when access looks blocked.
- Backend behavior today: when
toBeInvoiced === trueand there are no active agreements in the Agreement collection,checkReportAccessreturns unlocked even iflockReportis stilltrue(see integration tests inlockReport.test.ts). - Work order UI today: the Reports switch reflects
inspection.lockReport !== falseand label copy “Requires Payment & Agreement” (reportGateCopy.ts)—which does not change for invoice-terms jobs and can read as “locked” while clients may already be allowed. toBeInvoicedis set at Attik job creation when a contact hasinvoiceApprovedand a role with portalpaypermission (createInspection.ts), or toggled later via Allow Invoice Terms on Services & Payments (ServicesPayments.tsx).
Scope
Backend
attik-backend/src/util/functions/inspection/checkReportAccess.ts— source of truth for client report access:lockReport === falsebypasses all rules; otherwise unlock whentoBeInvoicedand no active agreements (or agreements signed); payment ignored whentoBeInvoiced.attik-backend/src/models/inspectionSchema.ts—lockReport(default true),toBeInvoiced, syncedagreement.present/agreement.signed.attik-backend/src/routes/reports.ts— client portal report fetch usescheckReportAccess(403 when locked).attik-backend/src/routes/inspection.ts— PATCH supportslockReportandtoBeInvoiced.attik-backend/src/util/functions/agreements/agreementLockSync.ts— agreement present/signed math (stale rows, archived excluded).attik-backend/tests/integration/lockReport.test.ts—toBeInvoiced+ no agreements → unlocked.
Frontend
attik-frontend/src/app/tools/inspections/[id]/components/reports/Reports.tsx— Reports switch PATCHeslockReport;reportGatingEnabled = inspection?.lockReport !== false(does not use computed access).attik-frontend/src/util/functions/report/reportGateCopy.ts— switch labels/toasts always payment + agreement wording.attik-frontend/src/app/tools/inspections/[id]/components/ServicesPayments.tsx— To Be Invoiced chip and Allow Invoice Terms / Disable Invoice Terms toggle.attik-frontend/src/app/client/job/[slug]/components/AttikReportList.tsxandReportList.tsx— client portal lock UI fromtoBeInvoiced, payment, and agreements; falls back toinspection.agreementwhen agreement list is empty (align with backend DB query behavior).attik-frontend/src/util/functions/report/computeAgreementSignedForReportLock.ts— portal agreement lock math (keep aligned with backend).attik-frontend/src/app/tools/inspections/[id]/components/Agreements.tsx— empty state when no agreements on inspection.
Product / decisions for implementer
- Decision needed: Should the work order switch show computed client access state, a separate “clients can access” indicator, or auto-relax display when
toBeInvoiced && no agreements? - Decision needed: Should copy distinguish invoice-terms gating (agreement-only) from standard gating (payment + agreement)?
- Decision needed: Whether to log or surface why gating would block clients (unsigned agreement, unpaid, invoice terms) without implying a user manually locked reports.
- Out of scope unless repro confirms portal block: Agreement templates on other services on the same job (CHI730 was confirmed empty in Agreements UI).
Investigation outcomes (acceptance)
- [ ] Work order clearly communicates client report access for To Be Invoiced + zero agreements without requiring CSR to set
lockReport: false. - [ ] Labels/copy on Reports panel reflect invoice-terms vs standard payment gating where applicable.
- [ ] Client portal and backend
checkReportAccessstay aligned for invoice-terms jobs with empty agreements. - [ ] Repro case CHI730 (or equivalent) documented: before/after behavior for CSR workflow.
- [ ] No regression for jobs that should remain gated (unsigned agreements, unpaid non-invoice jobs).
References
attik-backend/src/util/functions/inspection/checkReportAccess.tsattik-backend/tests/integration/lockReport.test.tsattik-frontend/src/app/tools/inspections/[id]/components/reports/Reports.tsxattik-frontend/src/util/functions/report/reportGateCopy.tsattik-frontend/src/app/tools/inspections/[id]/components/ServicesPayments.tsxattik-frontend/src/app/client/job/[slug]/components/AttikReportList.tsx