Skip to content

✨ server: support multiple cards in statement#920

Draft
nfmelendez wants to merge 1 commit intobasefrom
statement-base
Draft

✨ server: support multiple cards in statement#920
nfmelendez wants to merge 1 commit intobasefrom
statement-base

Conversation

@nfmelendez
Copy link
Copy Markdown
Contributor

@nfmelendez nfmelendez commented Mar 26, 2026

Summary by CodeRabbit

  • New Features

    • Statements now support multiple cards, grouping purchases per card and showing per-card totals.
    • Statement PDFs include account header, statement period range, due date, and a computed due balance.
    • Payments are shown separately with improved formatting (USD currency) and conditional discount/penalty indicators.
  • Tests

    • Expanded statement rendering tests covering multiple cards, payments, and conditional badge behaviors.

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 26, 2026

🦋 Changeset detected

Latest commit: aae69c9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@exactly/server Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 26, 2026

Warning

Rate limit exceeded

@nfmelendez has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 1 minutes and 19 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 1 minutes and 19 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3f7e6c8b-5202-42d3-bb84-40f7ac01113f

📥 Commits

Reviewing files that changed from the base of the PR and between bd64f38 and aae69c9.

📒 Files selected for processing (5)
  • .changeset/soft-trains-dig.md
  • server/api/activity.ts
  • server/test/api/activity.test.ts
  • server/test/utils/statement.test.ts
  • server/utils/Statement.tsx

Walkthrough

Reworks activity API querying and PDF statement generation to support multiple cards per account, changes Statement component props/formatting, updates tests, adds a changeset for a patch release, and adds a firehose warmup phase to test DB startup.

Changes

Cohort / File(s) Summary
Changeset
.changeset/soft-trains-dig.md
Added patch release entry for @exactly/server indicating support for multiple cards in statements.
Activity API
server/api/activity.ts
Replaced two-step card/transaction fetch with an eager-loaded query using with.transactions.where: arrayOverlaps(...). Built in-memory card->transaction lookup, attached cardId to rows, and restructured statement payload into { account, maturity, cards[], payments[] }. Removed prior single-card guard and related DB lookup.
Statement Component
server/utils/Statement.tsx
Changed props from flat data/lastFour to { account, cards[], maturity, payments[] }. Updated rendering to iterate cards for purchases, render payments table, compute totals, format dates via format(Date), and adjust payment-chip logic and layout.
Tests — Activity
server/test/api/activity.test.ts
Test fixtures updated to insert two card records and alternate cardId across generated transactions; adjusted failing-transaction linkage; added endpoint test for include: "card", maturity: "0".
Tests — Statement
server/test/utils/statement.test.ts
Updated fixtures and assertions to new statement shape (account, cards, payments), added multiple card/payment scenarios and filesystem side effects persisting PDFs; adjusted header/footer and currency/date expectations.
Test DB Startup
server/test/database.ts
Added a firehose warmup phase that starts a reader-node/merger process, watches for first merged-block artifact, errors with warmup log on failure, and only then starts the main firehose pipeline.

Sequence Diagram(s)

sequenceDiagram
  participant Client as Client/API caller
  participant API as Activity API
  participant DB as Database (Drizzle)
  participant Processor as Statement Builder
  participant Renderer as Statement.tsx (PDF renderer)
  participant FS as File System / Response

  Client->>API: GET /activity?maturity=...&pdf=true
  API->>DB: query cards with with.transactions.where(arrayOverlaps(...))
  DB-->>API: cards + matching transactions
  API->>Processor: build purchases, cardLookup, payments, grouped cards
  Processor-->>Renderer: render Statement({ account, cards, maturity, payments })
  Renderer-->>FS: emit PDF bytes
  FS-->>API: PDF buffer / path
  API-->>Client: 200 + PDF
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~55 minutes

Possibly related PRs

Suggested reviewers

  • cruzdanilo
  • franm91
🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title directly corresponds to the main objective: enabling support for multiple cards in statement generation. The changes across activity.ts, Statement.tsx, and tests implement this feature by restructuring data from single card handling to multi-card support.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch statement-base

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for multiple cards within the generated PDF statements. Key changes include refactoring the activity API to group transactions by card using Map.groupBy, updating the Statement component with a professional table-based layout and SVG branding, and enhancing the test suite to cover multi-card scenarios and financial calculations like discounts and penalties. I have no feedback to provide.

@sentry
Copy link
Copy Markdown

sentry bot commented Mar 26, 2026

✅ All tests passed.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/test/api/activity.test.ts (1)

257-295: 🧹 Nitpick | 🔵 Trivial

Assert the PDF grouping, not just the MIME type.

The new purchasesByCard logic only runs in this PDF path, but these tests still stop at content-type and byteLength. A regression that drops one card or groups every purchase under the same card would still pass.

Please assert both last-fours are present in the rendered output, or mock Statement and inspect the props passed into renderToBuffer.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 141b917e-cca7-4de8-9649-498061fb41a4

📥 Commits

Reviewing files that changed from the base of the PR and between deddc7c and 3639b18.

📒 Files selected for processing (5)
  • .changeset/soft-trains-dig.md
  • server/api/activity.ts
  • server/test/api/activity.test.ts
  • server/test/utils/statement.test.ts
  • server/utils/Statement.tsx

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
server/api/activity.ts (2)

268-272: ⚠️ Potential issue | 🔴 Critical

Replace the iterator-helper chains with Array.from() first.

This reintroduces iterator helpers on Map iterators. .entries()/.values() do not expose .filter(), .map(), or .toArray() on the current Node runtime this repo targets, so both paths will throw before the statement payload is built.

Proposed fix
-            const hashes = borrows
-              .entries()
-              .filter(([_, { events }]) => events.some(({ maturity: m }) => Number(m) === maturity))
-              .map(([hash]) => hash)
-              .toArray();
+            const hashes = Array.from(borrows.entries())
+              .filter(([_, { events }]) => events.some(({ maturity: m }) => Number(m) === maturity))
+              .map(([hash]) => hash);
...
-            const installments = item.operations
-              .reduce((accumulator, operation) => {
+            const installments = Array.from(
+              item.operations.reduce((accumulator, operation) => {
                 if ("borrow" in operation) {
                   if ("installments" in operation.borrow) {
                     const events = borrows?.get(operation.transactionHash)?.events;
                     if (!events) return accumulator;
                     const sortedInstallments = events.toSorted((a, b) => Number(a.maturity) - Number(b.maturity));
@@
                 }
                 return accumulator;
-              }, new Map<string, { amount: number; current: number; total: number }>())
-              .values()
-              .toArray();
+              }, new Map<string, { amount: number; current: number; total: number }>()).values(),
+            );

Run this to confirm the repo runtime pin and the remaining iterator-helper chains:

#!/bin/bash
set -euo pipefail

echo "== runtime configuration =="
fd '^(package\.json|\.nvmrc|\.tool-versions)$' -x sh -c 'echo "--- $1"; sed -n "1,200p" "$1"' sh {}
fd '.*\.(yml|yaml)$' .github/workflows -x sh -c 'echo "--- $1"; rg -n "setup-node|node-version" "$1" || true' sh {}

echo
echo "== iterator-helper usages in server/api/activity.ts =="
nl -ba server/api/activity.ts | sed -n '268,272p;462,463p'

Also applies to: 462-463


517-523: ⚠️ Potential issue | 🟡 Minor

Sort statement.cards before mapping.

findMany() has no explicit order, so multi-card PDFs can flip the summary and per-card section order between requests.

Proposed fix
         cards: purchases
           .filter(({ id }) => purchasesByCard.has(id))
+          .toSorted((a, b) => a.lastFour.localeCompare(b.lastFour) || a.id.localeCompare(b.id))
           .map(({ id, lastFour }) => ({
             id,
             lastFour,
             purchases: (purchasesByCard.get(id) ?? []).map(({ cardId: _, ...rest }) => rest),
           })),

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 122cf4a5-cfe0-4b91-ae26-8c245661aaf7

📥 Commits

Reviewing files that changed from the base of the PR and between 3639b18 and bd64f38.

📒 Files selected for processing (6)
  • .changeset/soft-trains-dig.md
  • server/api/activity.ts
  • server/test/api/activity.test.ts
  • server/test/database.ts
  • server/test/utils/statement.test.ts
  • server/utils/Statement.tsx

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