Skip to content

feat: add invoice pdf download on purchase lists - #1022

Open
tomrndom wants to merge 9 commits into
masterfrom
feature/invoice-purchases-pdf
Open

feat: add invoice pdf download on purchase lists#1022
tomrndom wants to merge 9 commits into
masterfrom
feature/invoice-purchases-pdf

Conversation

@tomrndom

@tomrndom tomrndom commented Jul 24, 2026

Copy link
Copy Markdown

ref: https://app.clickup.com/t/9014802374/86bb31qvd
depends on OpenStackweb/openstack-uicore-foundation#282 (comment)
Signed-off-by: Tomás Castillo tcastilloboireau@gmail.com

Summary by CodeRabbit

  • New Features
    • Added invoice PDF downloads for sponsor purchases with branded invoice details.
    • Added translated “Download Invoice” labels and accessible download controls.
  • Bug Fixes
    • Prevented overlapping invoice downloads and displayed progress indicators.
    • Improved error messages when invoice generation fails.
    • Ensured order details display only for the selected order.
    • Improved handling of purchases with missing form data.
  • Chores / Tests
    • Expanded automated coverage for invoice downloads, loading states, and order normalization.

tomrndom added 2 commits July 24, 2026 17:18
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@coderabbitai

coderabbitai Bot commented Jul 24, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Sponsor purchase views now support invoice PDF downloads. They retrieve sponsor orders with sponsor identifiers, prevent duplicate requests, display translated errors, and expose accessible download controls. Order rendering and normalization also handle mismatched or incomplete data.

Changes

Sponsor invoice downloads

Layer / File(s) Summary
Invoice action and order retrieval
src/actions/sponsor-purchases-actions.js, src/actions/__tests__/*
downloadSponsorInvoice retrieves sponsor orders, generates invoice PDFs, handles request and PDF errors, and completes loading in all paths.
Purchase view download controls
src/pages/sponsors/show-purchase-list-page/index.js, src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js, src/i18n/en.json, src/pages/sponsors/*/__tests__/*
Both purchase views add guarded downloads, progress indicators, disabled controls, translated labels, Redux action wiring, and tests for dispatch and in-flight behavior.
Order rendering and validation support
src/pages/sponsors/sponsor-page/utils.js, src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/sponsor-order-details.js, package.json
Order normalization handles missing forms, order details validates route identity, and Jest configuration and the UI foundation dependency are updated.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant PurchaseView
  participant downloadSponsorInvoice
  participant SponsorOrdersAPI
  participant generateInvoicePDF
  PurchaseView->>downloadSponsorInvoice: Dispatch orderId and sponsorId
  downloadSponsorInvoice->>SponsorOrdersAPI: Fetch sponsor order
  SponsorOrdersAPI-->>downloadSponsorInvoice: Return order data
  downloadSponsorInvoice->>generateInvoicePDF: Generate invoice PDF with logo
  generateInvoicePDF-->>PurchaseView: Settle download
  PurchaseView-->>PurchaseView: Restore download controls
Loading

Possibly related PRs

Suggested reviewers: smarcet

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: adding invoice PDF downloads to purchase lists.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/invoice-purchases-pdf

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

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Comment thread src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js Outdated
Comment thread src/pages/sponsors/show-purchase-list-page/index.js Outdated
@smarcet
smarcet requested a review from Copilot July 27, 2026 21:40

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomrndom please review

tomrndom added 3 commits July 28, 2026 01:16
…oice PDF

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
@tomrndom
tomrndom force-pushed the feature/invoice-purchases-pdf branch from 401f228 to db013e2 Compare July 31, 2026 16:00
@smarcet
smarcet self-requested a review August 3, 2026 16:55
Comment thread src/actions/sponsor-purchases-actions.js Outdated
Comment thread src/pages/sponsors/show-purchase-list-page/index.js Outdated
Comment thread src/pages/sponsors/show-purchase-list-page/index.js Outdated
Comment thread src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js
rejectSponsorPurchase: jest.fn(() => () => Promise.resolve())
}));

jest.mock(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomrndom This mock keeps the existing suite green after the new imports, but no test exercises the feature the PR adds — the PR description lists "Enhanced test coverage for invoice downloads" under Chores/Tests, and there is no such test here. None of the 16 it(...) blocks in this file touch the download path, show-purchase-list-page has no test file at all, and getSponsorOrder's new sponsorId argument (plus its currentSponsorState fallback) has no action test — there is no sponsor-purchases-actions.test.js in src/actions/__tests__/. The concurrency guard and the error path are the parts most likely to regress and are entirely unverified.

Suggested fix: add a test that mocks openstack-uicore-foundation/lib/components/order-invoice-pdf and asserts (a) clicking the download icon calls getSponsorOrder(orderId, sponsorId) and then generateInvoicePDF with the normalized order and currentSummit, (b) a second click while the first is pending does not call it again, and (c) a rejected fetch leaves the row interactive. An action test covering the sponsorId argument and the fallback to currentSponsorState.entity.id would close the other half.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomrndom Closing note: this one landed in full, and two of the sub-asks in my original "Suggested fix" have since gone stale — recording that so nobody chases them.

Against the current tip (5641d86):

  • sponsor-purchases-list.test.js now has an Invoice download block (lines 492–554) covering the dispatch and the concurrency guard — the file goes from 15 to 17 it(...) blocks.
  • show-purchase-list-page has a test file now, with three download tests, including one asserting that only the downloading row swaps to a spinner while the others go disabled.
  • src/actions/__tests__/sponsor-purchases-actions.test.js exists, with four tests covering the request URL, the generateInvoicePDF call, the fetch-rejection path and the PDF-rejection path.

The two stale sub-asks:

  1. "asserts … getSponsorOrder(orderId, sponsorId) … with the normalized order" — both premises are gone. There is a dedicated downloadSponsorInvoice thunk now rather than a widened getSponsorOrder, and passing the raw order is correct: normalizeOrder is inert on the PDF path in openstack-uicore-foundation@5.0.44 (evidence on the duplicated-handler thread, feat: add invoice pdf download on purchase lists #1022 (comment)).
  2. "(c) a rejected fetch leaves the row interactive" — not reachable, so not worth a test. The thunk's internal .catch(() => {}) makes its promise always resolve, so the component's .finally never sees a rejection. The case is covered where it actually lives, in the action test "swallows the order-fetch rejection silently…".

Net: nothing left to do here. Worth noting the direction of travel is now the opposite one — the open thread on sponsor-purchases-actions.test.js:69 asks to prune this set down to the tests carrying real behaviour, which is the right lens to apply here rather than growing it further.

@smarcet

smarcet commented Aug 3, 2026

Copy link
Copy Markdown

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@smarcet
smarcet requested a review from santipalenque August 3, 2026 18:04

@smarcet smarcet left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomrndom please re review

@smarcet

smarcet commented Aug 3, 2026

Copy link
Copy Markdown

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Aug 3, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

…nload icons with loading

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>
… row on download

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 11 out of 13 changed files in this pull request and generated no new comments.

@santipalenque santipalenque left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomrndom changes look good but too many redundant tests, please review tests one by one and only leave the ones meaningful

delete window.PURCHASES_API_URL;
});

it("builds the request URL using currentSponsorState.entity.id", async () => {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomrndom review all tests and only test pieces that have at least some logic or behavior

const params = {
access_token: accessToken,
expand:
"forms,forms.items,forms.items.meta_fields,forms.items.type,refunds,payments,notes,fees"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@tomrndom The order expand list is now duplicated byte-for-byte between getSponsorOrder (line 314) and this thunk (line 342), and the two copies feed different renderers of the same order — the on-screen SponsorOrderGrid and the invoice PDF.

Concrete failure: whoever next changes the detail page's expand (a new relation, or one renamed on the purchases-api side) gets no signal that a second copy exists. In openstack-uicore-foundation@5.0.44, buildRows sources the entire line-item table, fees, payments, refunds and notes from expand-gated relations, while the "Amount Due" comes from getOrderTotal(order)order.amount_due, which is a plain allowed_fields value on PurchaseV2Serializer and is not expand-gated. So the drift yields an invoice with an empty itemization and a still-correct total — a financial document that reads as authoritative after silently losing its line items, with nothing failing loudly.

This is the case .claude/rules/summit-admin-reuse-before-build.md § "Duplicate Mapping Tables" describes: "Two copies of the same mapping drift silently when one gets updated and the other doesn't." Worth flagging that this copy is new — it was created by consolidating the two page-level handlers into this thunk (#1022 (comment)). That consolidation was the right call; it just moved the duplication from the handlers down into the expand string.

Suggested fix — hoist it to a single module-level constant:

const ORDER_DETAIL_EXPAND =
  "forms,forms.items,forms.items.meta_fields,forms.items.type,refunds,payments,notes,fees";

and use expand: ORDER_DETAIL_EXPAND in both getSponsorOrder and downloadSponsorInvoice.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js`:
- Around line 88-93: Update handleInvoiceDownload to pass the current sponsor.id
as the second argument to downloadSponsorInvoice instead of item.sponsor_id.
Update the related test fixture and assertion to use the current sponsor ID.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8ba14953-e8e5-464a-b970-e142118713d6

📥 Commits

Reviewing files that changed from the base of the PR and between db013e2 and 5641d86.

📒 Files selected for processing (9)
  • src/actions/__tests__/sponsor-purchases-actions.test.js
  • src/actions/sponsor-purchases-actions.js
  • src/pages/sponsors/show-purchase-list-page/__tests__/index.test.js
  • src/pages/sponsors/show-purchase-list-page/index.js
  • src/pages/sponsors/sponsor-page/__tests__/utils.test.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/__tests__/sponsor-purchases-list.test.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/sponsor-order-details.js
  • src/pages/sponsors/sponsor-page/utils.js

Comment thread src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js
…es tab, update tests

Signed-off-by: Tomás Castillo <tcastilloboireau@gmail.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/actions/__tests__/sponsor-purchases-actions.test.js (2)

65-85: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Assert loading cleanup for every outcome.

downloadSponsorInvoice dispatches startLoading() before the request and stopLoading() in finally. These tests only assert PDF and snackbar effects. A regression that omits stopLoading() would leave the purchase views stuck in a loading state while all three tests pass. Assert the loading action sequence in the success, fetch-rejection, and PDF-rejection cases. (raw.githubusercontent.com)

Also applies to: 87-99, 101-115

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/__tests__/sponsor-purchases-actions.test.js` around lines 65 -
85, Update the three downloadSponsorInvoice test cases to assert the loading
action sequence for success, fetch rejection, and PDF rejection: startLoading
must be dispatched before stopLoading, with both occurring exactly once. Reuse
the existing store/action-state assertions so the tests verify cleanup without
changing the PDF or snackbar expectations.

65-85: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

Make the getRequest mock exercise the full contract.

The success test name promises that the invoice fetch does not touch shared order-detail state, but the mock never invokes the supplied receive-action creator. It also ignores the request parameters. The test can pass if the thunk dispatches RECEIVE_SPONSOR_ORDER or CLEAR_SPONSOR_ORDER, or stops sending access_token and ORDER_DETAIL_EXPAND. Capture the parameters, dispatch the supplied action creator, and assert the forbidden action types and required fields. (github.com)

Suggested test adjustment
+import {
+  CLEAR_SPONSOR_ORDER,
+  RECEIVE_SPONSOR_ORDER
+} from "../sponsor-purchases-actions";

+let capturedParams;

-getRequest.mockImplementation((reqAC, recAC, url) => {
+getRequest.mockImplementation((reqAC, receiveAction, url) => {
   capturedUrl = url;
-  return () => () => Promise.resolve({ response: fetchedOrder });
+  return (params) => (dispatch) => {
+    capturedParams = params;
+    dispatch(receiveAction({}));
+    return Promise.resolve({ response: fetchedOrder });
+  };
 });

+expect(capturedParams).toEqual(
+  expect.objectContaining({
+    access_token: "TOKEN",
+    expand: expect.stringContaining("forms")
+  })
+);
+const dispatchedTypes = store.getActions().map(({ type }) => type);
+expect(dispatchedTypes).not.toContain(RECEIVE_SPONSOR_ORDER);
+expect(dispatchedTypes).not.toContain(CLEAR_SPONSOR_ORDER);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/actions/__tests__/sponsor-purchases-actions.test.js` around lines 65 -
85, Strengthen the success test around downloadSponsorInvoice by updating the
getRequest mock to capture request parameters, invoke the supplied
receive-action creator, and dispatch the resulting action. Assert the request
includes the required access_token and ORDER_DETAIL_EXPAND fields, and verify no
RECEIVE_SPONSOR_ORDER or CLEAR_SPONSOR_ORDER actions are dispatched while
preserving the existing invoice PDF assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/actions/__tests__/sponsor-purchases-actions.test.js`:
- Around line 65-85: Update the three downloadSponsorInvoice test cases to
assert the loading action sequence for success, fetch rejection, and PDF
rejection: startLoading must be dispatched before stopLoading, with both
occurring exactly once. Reuse the existing store/action-state assertions so the
tests verify cleanup without changing the PDF or snackbar expectations.
- Around line 65-85: Strengthen the success test around downloadSponsorInvoice
by updating the getRequest mock to capture request parameters, invoke the
supplied receive-action creator, and dispatch the resulting action. Assert the
request includes the required access_token and ORDER_DETAIL_EXPAND fields, and
verify no RECEIVE_SPONSOR_ORDER or CLEAR_SPONSOR_ORDER actions are dispatched
while preserving the existing invoice PDF assertions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a22da29f-c627-4829-86d5-1b356f63490a

📥 Commits

Reviewing files that changed from the base of the PR and between 5641d86 and b9d6248.

📒 Files selected for processing (5)
  • src/actions/__tests__/sponsor-purchases-actions.test.js
  • src/actions/sponsor-purchases-actions.js
  • src/pages/sponsors/show-purchase-list-page/__tests__/index.test.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/__tests__/sponsor-purchases-list.test.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js
💤 Files with no reviewable changes (1)
  • src/pages/sponsors/show-purchase-list-page/tests/index.test.js
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/tests/sponsor-purchases-list.test.js
  • src/actions/sponsor-purchases-actions.js
  • src/pages/sponsors/sponsor-page/tabs/sponsor-purchases-tab/index.js

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.

4 participants