Skip to content

Billable rates (3/3): rate field on Clients, amount + billed total on Reports - #934

Open
devin-ai-integration[bot] wants to merge 7 commits into
devin/1788260799-billable-rates-apifrom
devin/1788261457-billable-rates-ui
Open

devin-ai-integration[bot] wants to merge 7 commits into
devin/1788260799-billable-rates-apifrom
devin/1788261457-billable-rates-ui

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Top layer of the billable-rates stack (on top of PR #933, which added billingRate to clients CRUD and totalAmount/per-entry amount to reports). Frontend only — this PR surfaces what the API already returns.

  • Clients page: a Billing Rate MUI TextField in the add/edit dialog plus a table column (rate > 0 renders toFixed(2), otherwise the same - Chip the other optional columns use). Form state keeps the rate as a string so the field can be empty, and submit converts it: billingRate: formData.billingRate === '' ? undefined : Number(formData.billingRate). The Number(...) matters — the backend Joi rule is .strict(), so a numeric string would 400.
  • Reports page: a fourth Total Billed summary card (the four cards drop to md: 3 to stay on one row) and an Amount column per entry. Both read through ?? 0 so a response without the new fields renders 0.00 instead of throwing on toFixed.
  • Types/api.ts: Client.billingRate is required, but WorkEntry.billable/amount are optional — the work-entries endpoints don't return them, only the report does.

Everything goes through the existing TanStack Query useQuery/useMutation calls in the two pages; no new data layer.

Stack: PR #932 (data model) → PR #933 (service/API) → this PR.

Testing

cd frontend && npm run lint and npm run build clean; cd backend && npm test still 179 green (backend untouched here). frontend/package.json has no test:coverage script, so nothing to run there. Verified end to end in the browser — see the runtime-test comment below (that run predates the hourlyRatebillingRate rename; the flow was re-checked after it).

Link to Devin session: https://partner-workshops.devinenterprise.com/sessions/af51f484897545a8bc1df69511f47ed4
Open in Devin Desktop: https://partner-workshops.devinenterprise.com/desktop/session/af51f484897545a8bc1df69511f47ed4?variant=devin
Requested by: @sumitshatwara

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Runtime test results — billable rates UI (tested at 74dfc23)

Tested end-to-end in the browser against a local backend (:3001) + frontend (:5173), watching the backend request log for 4xx/5xx. No 400s or 500s occurred during the entire run.

Reports: per-entry Amount + Total Billed (client rate 200/hr, 2.5h + 1h)

Reports page with Amount column and Total Billed

Amounts 500.00 / 200.00, Total Billed 700.00, Total Hours unchanged at 3.50, and the four summary cards fit on one row with no wrapping.

PDF export — Total Amount line + Amount column (rendered, not mocked)

Rendered PDF export

Total Amount: 700.00 is present and the Amount column text does not overlap Description with the new x-offsets.

Minor, pre-existing (not introduced here): the Date column contains raw epoch millis (1788220800000); because Date was narrowed 100→90 it now sits almost flush against the Hours value. Worth a follow-up to format the date in the exports.

Clients page — rate column, edit persistence (highest-risk path)

Clients table with hourly rate column

Created with 125.50 → renders 125.50; created without a rate → renders - chip. Edited a rate to 200PUT /api/clients/1 returned 200 (no strict-Joi string rejection) and 200.00 survived a full reload with no "Failed to update client" alert.

CSV export contents

CSV export

Header is exactly Date,Hours,Billable,Amount,Description,Created At with Yes and correct amounts.

Edge cases
  • Rate -5 and 12.345: blocked client-side, no request sent, no 400/500. Note the browser's native type=number validation fires before the new Hourly rate must be a positive number message, so that custom string is effectively unreachable by typed input.
  • Rate-0 client: Amount 0.00 / Total Billed 0.00, never NaN; empty-state row spans the full 5-column table.
  • billable = false amount rule: not verified at runtime — no UI/API path sets it and the DB is in-memory, so it remains unit-test-only coverage.

Written by Devin

@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

@devin-ai-integration

Copy link
Copy Markdown
Contributor Author

Runtime re-test after the hourly_ratebilling_rate rename (tested at fd29452)

Re-ran the wire-contract-critical paths end-to-end in the browser against a local backend (:3001) + frontend (:5173). No 4xx/5xx across all 26 backend requests. The rename did not break the API boundary.

Reports still resolve the rate from the renamed column (client rate 250, entries 4h + 2h):

Reports page — Amount column and Total Billed after rename

Per-entry Amount 1000.00 / 500.00, Total Billed 1500.00, Total Hours 6.00, four cards on one row.

Clients page — rate renders and persists; edit returns 200 (highest-risk path)

Clients table with Billing Rate column showing 250.00 after reload

Created with 150 → renders 150.00 and survives reload. Re-opening the edit dialog on a freshly loaded page shows it prefilled with 150, which proves the GET response key matches the frontend form key. Edited to 250PUT /api/clients/1 returned 200, no error alert, and 250.00 survived a full reload — no silent field-name drop.

Exports still carry correct non-zero amounts

Rendered PDF export

PDF: Total Amount: 1500.00, Amount column 1000.00 / 500.00, no overlap with Description.

CSV export contents

CSV header is exactly Date,Hours,Billable,Amount,Description,Created At.

Still pre-existing (unrelated to the rename): the exported Date column is raw epoch millis (1788220800000), which sits almost flush against Hours in the PDF. Worth a follow-up to format dates in the exports.

Not re-covered in this focused run
  • Negative / >2-decimal input, zero-rate, and empty-state cases were verified at 74dfc23 and skipped here; nothing looked off. Note the renamed client-side message Billing rate must be a positive number is still effectively unreachable by typed input because Chrome's native type=number validation fires first.
  • billable = false amount rule: not verified at runtime (no UI/API path sets it; in-memory DB), so it remains unit-test-only coverage.

Written by Devin

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant