Skip to content

feat(api): GET /v1/timeentry/export.csv — CSV dump for invoicing flows#68

Merged
CryptoJones merged 1 commit into
masterfrom
feat/timeentry-csv-export
May 18, 2026
Merged

feat(api): GET /v1/timeentry/export.csv — CSV dump for invoicing flows#68
CryptoJones merged 1 commit into
masterfrom
feat/timeentry-csv-export

Conversation

@CryptoJones
Copy link
Copy Markdown
Owner

Summary

Companion to #67. The invoicing flow needs the time-entry side too — filter by customer + date range, export rows, feed into billing system.

GET /v1/timeentry/export.csv?customerId=42&from=2026-04-01T00:00:00Z&to=2026-05-01T00:00:00Z

Same shape as customer/export.csv: 5000-row cap, # truncated... comment when exceeded, RFC 4180 quoting, attachment Content-Disposition. Auth mirrors search/bulk/CSV.

Test plan

  • vitest: 261 passing + 4 integration skipped (was 255 + 4 skipped)
  • Auth contract + query validation + route mounting
  • Live integration with real PG / date-range filters (deferred)

Proudly Made in Nebraska. Go Big Red! 🌽 https://xkcd.com/2347/

Companion to /v1/customer/export.csv (#67). Time-entry invoicing
is the natural billing pipeline:

  GET /v1/timeentry/export.csv?customerId=42&from=...&to=...

Filters mirror the existing /v1/timeentry/bycompany/:id:
customerId, from, to (ISO 8601 date-time range on teStartedAt).
Date-range params are permissive on bad input — silent drop, not
400 — so a long-running export script doesn't break on a typo.

5000-row hard cap with `# truncated...` trailing comment when
exceeded. Field order:
  teId, teCustId, teCompId, teStartedAt, teEndedAt, teMinutes,
  teBillable, teDescription
(matches the JSON shape, Date values serialize to ISO).

Auth mirrors customer/export.csv:
  - master without companyId  -> 400
  - non-master + mismatching companyId  -> 403
  - non-master without companyId  -> auto-scope

Tests: 6 cases covering auth contract, query validation
(unknown param, oversize limit, bad ISO datetime, well-formed
query), and route mounting (not parsed as :id).

Suite: 36 files / 261 passing + 4 integration skipped (was 35 / 255).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@CryptoJones CryptoJones merged commit aae3c7c into master May 18, 2026
1 check was pending
@CryptoJones CryptoJones deleted the feat/timeentry-csv-export branch May 18, 2026 02:12
CryptoJones added a commit that referenced this pull request May 18, 2026
#68 added /v1/timeentry/export.csv but placed the route AFTER the
existing /v1/timeentry/:id block. Express tries patterns top-down,
so a GET to /v1/timeentry/export.csv matched the :id route first,
the intIdParam validator parsed "export.csv" → NaN → 400 with
"expected number". The test that asserts 403 on missing authKey
was flaking on this path. The export handler was never reached.

Mirrors the search-before-:id ordering #64 used for customer.
Added a comment block flagging the rule for future contributors.

All four timeentry CRUD routes still resolve correctly — they
sit AFTER the literals now, which is the correct order.

Suite: 261 / 261 + 4 integration skipped (post-fix).

Co-authored-by: Aaron K. Clark <akclark@thenetwerk.net>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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