Skip to content

refactor: invoice pending lines#3880

Merged
turip merged 2 commits into
mainfrom
refactor/allow-invoice-pending-lines-steps-from-other-services
Feb 19, 2026
Merged

refactor: invoice pending lines#3880
turip merged 2 commits into
mainfrom
refactor/allow-invoice-pending-lines-steps-from-other-services

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented Feb 19, 2026

Overview

This approach gives us more granular control about the invoice pending lines flow when executed in external services.

Summary by CodeRabbit

  • Refactor

    • Restructured the gathering invoice billing workflow to implement a two-step process: preparing billable lines separately from creating standard invoices per currency, enhancing the handling and organization of multi-currency billing operations and improving the overall invoice generation process.
  • Tests

    • Updated test utilities to support the new billing service operations.

@turip turip marked this pull request as ready for review February 19, 2026 09:23
@turip turip requested a review from a team as a code owner February 19, 2026 09:23
@turip turip changed the title refactor: allow invoking parts of the invoice pending lines from othe… refactor: invoice pending lines Feb 19, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 19, 2026

📝 Walkthrough

Walkthrough

Refactors the gathering invoice pending lines workflow by introducing two new public methods—PrepareBillableLines and CreateStandardInvoiceFromGatheringLines—with supporting types. Replaces prior per-currency direct handling with a two-step approach: prepare billable lines grouped by currency, then create standard invoices per currency.

Changes

Cohort / File(s) Summary
Type definitions
openmeter/billing/gatheringinvoice.go
Added PrepareBillableLinesInput type alias and PrepareBillableLinesResult struct for grouping lines by currency.
Interface additions
openmeter/billing/service.go
Added CreateStandardInvoiceFromGatheringLines and PrepareBillableLines method signatures to StandardInvoiceService and GatheringInvoiceService interfaces.
Workflow implementation
openmeter/billing/service/gatheringinvoicependinglines.go
Refactored InvoicePendingLines workflow to use two-step approach; implemented PrepareBillableLines and CreateStandardInvoiceFromGatheringLines methods; removed legacy per-currency handlers; updated line preparation and invoice creation logic.
Input validation
openmeter/billing/stdinvoice.go
Added CreateStandardInvoiceFromGatheringLinesInput struct and Validate method with field-level validation for Customer, Currency, and Lines.
Test helpers
openmeter/server/server_test.go
Added no-op implementations of CreateStandardInvoiceFromGatheringLines and PrepareBillableLines to NoopBillingService for testing.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

release-note/feature, area/billing

Suggested reviewers

  • tothandras
  • chrisgacsal
  • gergely-kurucz-konghq
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'refactor: invoice pending lines' is vague and generic—it doesn't clearly convey what the refactor accomplishes or why it matters. Consider a more descriptive title like 'refactor: extract invoice pending lines steps for service reuse' to clarify the main goal of making invoice processing steps available to other services.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/allow-invoice-pending-lines-steps-from-other-services

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.

@turip turip added the release-note/misc Miscellaneous changes label Feb 19, 2026
Copy link
Copy Markdown
Contributor

@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.

🧹 Nitpick comments (3)
openmeter/billing/service/gatheringinvoicependinglines.go (2)

109-126: Duplicate validation and namespace lockdown checks.

Lines 110-120 duplicate the exact same validation and fsNamespaceLockdown check from InvoicePendingLines (lines 48-58). When called via InvoicePendingLines, these run twice. This is expected for a standalone public method, but wanted to call it out — if there's ever a hot-path concern, a private prepareBillableLinesInternal without the redundant checks could help. Not urgent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/service/gatheringinvoicependinglines.go` around lines 109 -
126, Duplicate input validation and fsNamespaceLockdown checks occur in
PrepareBillableLines and InvoicePendingLines causing redundant work; extract the
core work into a private helper (e.g., prepareBillableLinesInternal) that
accepts the already-validated input and performs the
transactionForInvoiceManipulation callback logic, then have the public
PrepareBillableLines perform the validation and lockdown checks and call the
internal helper, and update InvoicePendingLines to call the internal helper
directly (or to call the public method only if you intentionally want
revalidation); reference PrepareBillableLines, InvoicePendingLines,
transactionForInvoiceManipulation and the new prepareBillableLinesInternal to
locate where to move the logic.

660-714: Consider passing pre-fetched profile and feature meters to reduce redundant DB calls in this hot path.

When CreateStandardInvoiceFromGatheringLines is invoked from InvoicePendingLines with multiple currencies, both GetCustomerOverride and resolveFeatureMeters are called again even though they were already fetched in PrepareBillableLines. For N currencies, this means GetCustomerOverride gets hit N+1 times and resolveFeatureMeters gets hit 2N times.

This is a reasonable trade-off for keeping the method independently callable, but in this hot path you could accept optional profile and feature meters parameters. When present, skip the redundant fetches; when absent, fetch as usual. That way callers like InvoicePendingLines can pass them through and avoid the extra DB round-trips.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/service/gatheringinvoicependinglines.go` around lines 660 -
714, CreateStandardInvoiceFromGatheringLines currently re-fetches
GetCustomerOverride and resolveFeatureMeters on a hot path causing N+1/2N DB
calls; make them optional inputs so callers can supply pre-fetched data. Extend
billing.CreateStandardInvoiceFromGatheringLinesInput to include optional fields
(e.g., PreFetchedProfile *billing.CustomerOverride and PreFetchedFeatureMeters
type matching resolveFeatureMeters output), then in
CreateStandardInvoiceFromGatheringLines use in.PreFetchedProfile when non-nil
instead of calling GetCustomerOverride and use in.PreFetchedFeatureMeters when
present instead of calling resolveFeatureMeters; leave existing fetch logic as
the fallback. Update hot-path callers (notably InvoicePendingLines and
PrepareBillableLines flows) to pass through the already-fetched profile and
featureMeters when available.
openmeter/billing/service.go (1)

103-104: Minor inconsistency: pointer return type vs. value returns in same interface.

The other StandardInvoiceService methods (UpdateStandardInvoice, GetStandardInvoiceById, ListStandardInvoices) all return StandardInvoice by value, but this new method returns *StandardInvoice. This isn't a bug, but it makes the interface inconsistent and forces callers to handle the pointer differently.

Was this intentional (e.g., to signal a nil result case)? Looking at the implementation in gatheringinvoicependinglines.go, it always returns a non-nil result on success, so a value return would align better with the rest of the interface.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/service.go` around lines 103 - 104, The interface method
CreateStandardInvoiceFromGatheringLines returns *StandardInvoice while other
methods (UpdateStandardInvoice, GetStandardInvoiceById, ListStandardInvoices)
return StandardInvoice by value; change the interface signature to return
(StandardInvoice, error) and update the implementation in
gatheringinvoicependinglines.go to match that value return (modify the function
signature and ensure it returns a non-pointer StandardInvoice on success), and
update any callers to accept a value instead of a pointer.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@openmeter/billing/service.go`:
- Around line 103-104: The interface method
CreateStandardInvoiceFromGatheringLines returns *StandardInvoice while other
methods (UpdateStandardInvoice, GetStandardInvoiceById, ListStandardInvoices)
return StandardInvoice by value; change the interface signature to return
(StandardInvoice, error) and update the implementation in
gatheringinvoicependinglines.go to match that value return (modify the function
signature and ensure it returns a non-pointer StandardInvoice on success), and
update any callers to accept a value instead of a pointer.

In `@openmeter/billing/service/gatheringinvoicependinglines.go`:
- Around line 109-126: Duplicate input validation and fsNamespaceLockdown checks
occur in PrepareBillableLines and InvoicePendingLines causing redundant work;
extract the core work into a private helper (e.g., prepareBillableLinesInternal)
that accepts the already-validated input and performs the
transactionForInvoiceManipulation callback logic, then have the public
PrepareBillableLines perform the validation and lockdown checks and call the
internal helper, and update InvoicePendingLines to call the internal helper
directly (or to call the public method only if you intentionally want
revalidation); reference PrepareBillableLines, InvoicePendingLines,
transactionForInvoiceManipulation and the new prepareBillableLinesInternal to
locate where to move the logic.
- Around line 660-714: CreateStandardInvoiceFromGatheringLines currently
re-fetches GetCustomerOverride and resolveFeatureMeters on a hot path causing
N+1/2N DB calls; make them optional inputs so callers can supply pre-fetched
data. Extend billing.CreateStandardInvoiceFromGatheringLinesInput to include
optional fields (e.g., PreFetchedProfile *billing.CustomerOverride and
PreFetchedFeatureMeters type matching resolveFeatureMeters output), then in
CreateStandardInvoiceFromGatheringLines use in.PreFetchedProfile when non-nil
instead of calling GetCustomerOverride and use in.PreFetchedFeatureMeters when
present instead of calling resolveFeatureMeters; leave existing fetch logic as
the fallback. Update hot-path callers (notably InvoicePendingLines and
PrepareBillableLines flows) to pass through the already-fetched profile and
featureMeters when available.

@turip turip enabled auto-merge (squash) February 19, 2026 09:41
@turip turip requested a review from tothandras February 19, 2026 09:47
@turip turip merged commit c85824f into main Feb 19, 2026
25 checks passed
@turip turip deleted the refactor/allow-invoice-pending-lines-steps-from-other-services branch February 19, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

release-note/misc Miscellaneous changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants