Discounted inspection shows negative payment to view report

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): syncTotalsOnInspection in attik-backend/src/util/functions/forecast/syncTotalsOnInspections.ts sets remainingBalance = total - totalPaid and paid = true only when abs(total - totalPaid) ≤ $0.01. If totalPaid > total after a discount, remainingBalance is negative and paid stays false.
  • Report gating uses inspection.paid in attik-backend/src/util/functions/inspection/checkReportAccess.ts; the client portal mirrors that via getReportLockMessage and lock UI on AttikReportList / ReportList when not paid.
  • The portal displays raw inspection.remainingBalance (e.g. JobAccordion.tsx Balance: $…), 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.tssyncTotalsOnInspection.

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.tsremainingBalance and paid when totalPaid >= total (tolerance); avoid negative balance driving paid: 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 call syncTotalsOnInspection; 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.tsxPay for Your Service balance/chip when remainingBalance <= 0.
  • attik-frontend/src/app/client/job/[slug]/components/AttikReportList.tsx, ReportList.tsx, attik-frontend/src/util/functions/report/getReportLockMessage.ts — lock when inspection.paid is false.
  • attik-frontend/src/app/client/reports/components/LockedReportScreen.tsx, attik-frontend/src/app/client/reports/[slug]/page.tsx — backend 403 locked path uses checkReportAccess.
  • 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.tsxOpen 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 paid or negative balance display.

Investigation outcomes (acceptance)

  • [ ] Repro Path A or B: discount after full payment → paid true, remainingBalance 0 (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: false with negative remainingBalance.
  • [ ] 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.ts
  • attik-backend/src/util/functions/inspection/checkReportAccess.ts
  • attik-frontend/src/app/client/job/[slug]/components/JobAccordion.tsx
  • attik-frontend/src/util/functions/report/getReportLockMessage.ts

Please authenticate to join the conversation.

Upvoters
Status

Completed

Board
🏠

Main App

Date

10 days ago

Author

Linear

Subscribe to post

Get notified by email when there are changes.