Skip to content

Conversation

@turip
Copy link
Member

@turip turip commented Jan 19, 2026

Overview

The InvoicePendingLines call is one of the most complex ones in the existing invoicing stack, due to algorithmical complexity, but also the code was scattered in multiple places with different layers handling different parts of the flow.

This patch makes sure that the whole logic is:

  • Handled by the service
  • Contained in a single file
  • Well documented

As a side-effect we can remove the adapter dependency from the line service, making it closer to a "calculation" only package.

I have also removed the AssociateLinesToInvoice as extra care was needed to correct the inconsistencies caused by that call.

Summary by CodeRabbit

  • New Features

    • Added InvoicePendingLines workflow to gather and process pending invoice lines across multiple currencies
    • Enhanced validation ensuring pending line selections contain at least one line ID
  • Bug Fixes

    • Improved line cloning mechanism with customizable edit functions
  • Refactor

    • Simplified invoice line management API by removing direct line association method
    • Streamlined public line operation interface

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

📝 Walkthrough

Walkthrough

Removes adapter-based line-association APIs and legacy invoice-pending orchestration; introduces a new, service-level gathering InvoicePendingLines workflow, simplifies line service interfaces, and tightens invoice input validation.

Changes

Cohort / File(s) Summary
Adapter Interface Cleanup
openmeter/billing/adapter.go, openmeter/billing/adapter/invoicelines.go
Removed AssociateLinesToInvoice from InvoiceLineAdapter and deleted its implementation.
Invoice Input Validation
openmeter/billing/invoice.go
InvoicePendingLinesInput.Validate now requires IncludePendingLines to contain at least one ID if present.
Line Type & Cloning Behavior
openmeter/billing/invoiceline.go
Added LineEditFunction type; Line.CloneWithoutDependencies now accepts variadic edits, resets timestamps (CreatedAt, UpdatedAt) and clears DeletedAt; removed AssociateLinesToInvoiceAdapterInput.
New Gathering Invoice Pending Lines Workflow
openmeter/billing/service/gatheringinvoicependinglines.go
Added comprehensive service-level implementation for InvoicePendingLines (per-currency processing, splitting, invoice creation, moving lines, and event publishing).
Old Workflow Removal
openmeter/billing/service/invoice.go
Removed previous InvoicePendingLines implementation and associated helper types.
Line Service Helper Removal
openmeter/billing/service/invoiceline.go
Removed internal associateLinesToInvoice helper (splitting, validation, association, snapshotting, state transitions).
Line Service Interface Simplification
openmeter/billing/service/lineservice/linebase.go
Removed several LineBase methods: IsSplitLineGroupMember, CloneForCreate, Update, Save, Delete, Split, and Service.
Lineservice Config & Validation
openmeter/billing/service/lineservice/service.go, openmeter/billing/service/service.go
Removed BillingAdapter from Config and constructor wiring; removed AssociateLinesToInvoice on Service; added Lines.ValidateForInvoice(ctx, invoice) to validate collections before invoicing.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Service as Service (InvoicePendingLines)
    participant Repo as Repository
    participant LineService as Line Service
    participant EventPub as EventPublisher

    Client->>Service: InvoicePendingLines(ctx, input)
    activate Service
    Service->>Service: Validate input & namespace lock
    Service->>Repo: Resolve customer/profile
    Service->>Repo: Fetch gathering invoices (per currency)
    Service->>LineService: Compute in-scope billable lines
    loop per currency
        Service->>Service: prepareLinesToBill (validate, split decisions)
        alt split required
            Service->>Service: splitGatheringInvoiceLine
            Service->>Repo: Update gathering invoice (post-split)
        end
        Service->>Repo: Create standard invoice
        Service->>Repo: Move/associate lines to new invoice
        Service->>Repo: Update gathering invoice state (or delete if empty)
    end
    Service->>EventPub: Publish InvoiceCreatedEvent(s)
    Service-->>Client: Return created invoices
    deactivate Service
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

kind/refactor, area/billing, release-note/breaking-change

Suggested reviewers

  • tothandras
  • hekike
  • chrisgacsal
🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main refactoring objective: moving line manipulation logic to use the invoice API instead of separate adapter methods.
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/split-lines

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 force-pushed the refactor/split-lines branch 2 times, most recently from 781b6ef to af75b51 Compare January 21, 2026 12:14
@turip turip force-pushed the refactor/split-lines branch from 52c024b to c99a728 Compare January 21, 2026 12:29
@turip turip force-pushed the refactor/split-lines branch from c99a728 to 3a1f998 Compare January 21, 2026 12:30
@turip turip marked this pull request as ready for review January 21, 2026 12:30
@turip turip requested a review from a team as a code owner January 21, 2026 12:30
@turip turip changed the title fix: split line manipulation should use invoice API refactor: split line manipulation should use invoice API Jan 21, 2026
@turip turip added the release-note/misc Miscellaneous changes label Jan 21, 2026
Copy link
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.

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In `@openmeter/billing/service/gatheringinvoicependinglines.go`:
- Around line 292-321: After filtering res by the requested IDs (using
LinesToInclude.OrEmpty(), linesShouldBeIncluded, and lo.Filter), remove any
currency entries that end up with zero lines so empty currencies don't remain in
res; update the loop that sets res[currency] to compute the filtered slice and
if its length is zero delete(res, currency) else assign the filtered slice back
to res[currency], ensuring downstream validation only sees currencies with at
least one line.
- Around line 377-403: The code currently appends a pre‑split line even when
splitGatheringInvoiceLine marks it deleted and also logs the same "period_end"
key twice; update the block handling splitGatheringInvoiceLine so that after the
nil check you also skip adding the pre‑split line if
splitLine.PreSplitAtLine.Deleted is true (i.e., do not append to invoiceLines or
include its ID in LineIDsToBill), and fix the duplicate log key in the
logger.WarnContext call (replace the second "period_end" key with a distinct key
such as "period_original_end" or remove the duplicate) — references:
splitGatheringInvoiceLine, splitGatheringInvoiceLineInput,
splitLine.PreSplitAtLine, invoiceLines, LineIDsToBill, logger.WarnContext.
- Around line 148-157: The transaction callback in gatheringInvoicePendingLines
(inside transaction.Run) currently calls s.publisher.Publish for each invoice,
which can emit events for data that may be rolled back; instead, collect the
created invoices inside the callback (e.g., append to createdInvoices slice
returned from the transaction) and return successfully, then after
transaction.Run completes (i.e., after commit) iterate over the committed
invoices, create events via billing.NewInvoiceCreatedEvent(invoice) and call
s.publisher.Publish(ctx, event) outside the transaction; update the code path
around createdInvoices and the transaction.Run call so publishing only happens
after a successful commit.

In `@openmeter/billing/service/lineservice/service.go`:
- Around line 189-202: ValidateForInvoice currently assumes each element in the
Lines slice is non-nil and will panic if a nil Line is present; update the
mapping function inside ValidateForInvoice to first check if the `line` is nil
(type `Line`) and return a descriptive error (e.g. including the index or
identifier) instead of calling `line.Validate` or `line.ID()`, then proceed to
call `line.Validate(ctx, invoice)` and `line.ID()` only when `line` is non-nil;
reference the ValidateForInvoice function and the Line.Validate / Line.ID
methods when making this change.
🧹 Nitpick comments (2)
openmeter/billing/invoiceline.go (2)

251-252: Consider exporting the edit type (or adding helpers) if external callers should use it.

CloneWithoutDependencies is public, but lineEditFunction isn’t, so packages outside billing can’t pass edits. If you want external customization, exporting the type (or adding exported helper funcs) would keep the API usable. If not, all good.


255-277: Skip nil edits to avoid a panic.

A nil edit function will panic on call. A tiny guard keeps this safe and costs nothing.

🛠️ Suggested tweak
-	for _, edit := range edits {
-		edit(clone)
-	}
+	for _, edit := range edits {
+		if edit == nil {
+			continue
+		}
+		edit(clone)
+	}

@turip turip requested a review from chrisgacsal January 21, 2026 12:57
@turip turip merged commit 9afb3fc into main Jan 21, 2026
30 checks passed
@turip turip deleted the refactor/split-lines branch January 21, 2026 14:04
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