Objective
- When a job is fully paid and staff later apply a discount on charges, the client should see the job as settled (balance $0, report unlocked)—not a negative balance with reports still gated behind Complete Payment.
- Align paid status, remaining balance, client portal invoice copy, and report access so post-payment discounts do not block report delivery or imply the client owes a negative amount.
Background
- Production report: after a $50 discount on an already-paid inspection, the client portal indicates she must pay -$50 to view the report; staff see a negative balance on the work order.
- Example inspection: 1008593640
- Slack — discounted inspection / negative pay to view report
- Root cause (code review):
syncTotalsOnInspectioninattik-backend/src/util/functions/forecast/syncTotalsOnInspections.tssetsremainingBalance = total - totalPaidandpaid = trueonly whenabs(total - totalPaid) ≤ $0.01. IftotalPaid > totalafter a discount,remainingBalanceis negative andpaidstays false. - Report gating uses
inspection.paidinattik-backend/src/util/functions/inspection/checkReportAccess.ts; the client portal mirrors that viagetReportLockMessageand lock UI onAttikReportList/ReportListwhen not paid. - The portal displays raw
inspection.remainingBalance(e.g.JobAccordion.tsxBalance: $…), which can render $-50.00 with a Due Now chip even though the customer overpaid relative to the new total. - Charge/discount saves trigger totals sync via
attik-backend/src/events/streamHandlers/chargeStream.ts→syncTotalsOnInspection.
Reproduction (UI)
Ryan was able to repro this issue in https://www.attik.ai/inspections/1008592864
Scope
Backend
attik-backend/src/util/functions/forecast/syncTotalsOnInspections.ts—remainingBalanceandpaidwhentotalPaid >= total(tolerance); avoid negative balance drivingpaid: false.attik-backend/src/util/functions/inspection/checkReportAccess.ts— report lock should treat settled/overpaid jobs as paid for gating.attik-backend/src/events/streamHandlers/chargeStream.ts— discount/charge updates already callsyncTotalsOnInspection; verify behavior after totals fix.- Decision needed: Whether to persist a separate credit/overpayment amount for staff when
totalPaid > total, vs. clamping display to $0 only.
Frontend
attik-frontend/src/app/client/job/[slug]/components/JobAccordion.tsx— Pay for Your Service balance/chip whenremainingBalance <= 0.attik-frontend/src/app/client/job/[slug]/components/AttikReportList.tsx,ReportList.tsx,attik-frontend/src/util/functions/report/getReportLockMessage.ts— lock wheninspection.paidis false.attik-frontend/src/app/client/reports/components/LockedReportScreen.tsx,attik-frontend/src/app/client/reports/[slug]/page.tsx— backend 403 locked path usescheckReportAccess.- Staff:
attik-frontend/src/app/tools/inspections/[id]/components/ServicesPayments.tsx(balance display),EditChargesModal.tsx/DiscountModalButton(discount apply),ManualPaymentModal.tsx(full payment before discount in repro). attik-frontend/src/app/tools/inspections/[id]/components/WorkorderActionsDropdown.tsx— Open Client Portal for verification.
Product / architecture
- Decision needed: Client-facing copy when overpaid (show $0, Paid in full, or Credit on account).
- Interim (not a fix): CSR can set lock report off on the work order; does not correct
paidor negative balance display.
Investigation outcomes (acceptance)
- [ ] Repro Path A or B: discount after full payment →
paidtrue,remainingBalance0 (or product-approved overpayment handling), report unlocked when agreements complete. - [ ] Portal Pay for Your Service does not show negative balance with Due Now when the job is settled.
- [ ] Portal and direct report URL allow View Report when payment + agreement rules are satisfied.
- [ ] Regression test: discount after payment does not leave
paid: falsewith negativeremainingBalance. - [ ] Verify 1008593640 (or clone) after fix; may need totals resync on affected production rows.
References
- Example inspection 1008593640
- Slack thread
attik-backend/src/util/functions/forecast/syncTotalsOnInspections.tsattik-backend/src/util/functions/inspection/checkReportAccess.tsattik-frontend/src/app/client/job/[slug]/components/JobAccordion.tsxattik-frontend/src/util/functions/report/getReportLockMessage.ts