Skip to content

feat: invoice calculation hook#4116

Merged
turip merged 4 commits intomainfrom
feat/invoice-calculation-hook
Apr 10, 2026
Merged

feat: invoice calculation hook#4116
turip merged 4 commits intomainfrom
feat/invoice-calculation-hook

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented Apr 9, 2026

Overview

This PR introduces a billing line-engine abstraction for invoice materialization, collection-time processing, and recalculation, and moves
charge-backed invoice-line behavior behind explicit flat-fee, credit-purchase, and usage-based engine implementations.

Billing now owns invoice orchestration and validates all line-engine inputs and outputs, while engines are required to return updated lines with
the exact same IDs so billing can merge them back into the invoice explicitly.

For usage-based credit_then_invoice, the branch replaces generic snapshot-style behavior with explicit lifecycle triggers such as
invoice_created and collection_completed, keeping charge lifecycle ownership inside the charge domain.

The split-line flow was also tightened so billing fetches the current gathering line and merges split results itself, while optional split
outputs are represented with pointers instead of zero-value sentinels.

Collection-time engine failures are now accumulated and surfaced as invoice validation issues through a dedicated line-engine validation path
rather than failing the invoice state machine directly.

Verification included env GOCACHE=/tmp/go-build go test ./openmeter/billing/... and env GOCACHE=/tmp/go-build go test ./test/billing -run TestLineEngine -count=1 -tags=dynamic, both of which passed.

Notes for reviewer

Summary by CodeRabbit

  • New Features

    • Credit-then-invoice settlement for usage-based charges; runtime register/deregister/listing of invoice line engines; usage-based line engine integration.
  • Refactor

    • Invoice line processing split into CalculateLines and OnCollectionCompleted phases with centralized validation and strict exact line-ID matching.
  • Bug Fixes

    • Collection-time engine errors surface as validation issues; improved lifecycle transitions and invoice-state handling for usage-based flows.
  • Tests / Documentation

    • Expanded tests for collection/lifecycle flows and updated guidance on time control and engine wiring.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 506500f7-3d3e-487f-a7df-7ed91408e66b

📥 Commits

Reviewing files that changed from the base of the PR and between 6a99559 and f7403c8.

📒 Files selected for processing (3)
  • openmeter/billing/service.go
  • openmeter/billing/service/lineengine.go
  • openmeter/server/server_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • openmeter/billing/service.go
  • openmeter/server/server_test.go
  • openmeter/billing/service/lineengine.go

📝 Walkthrough

Walkthrough

Adds per-engine lifecycle hooks (CalculateLines, OnCollectionCompleted), a usage-based LineEngine and CreditThenInvoice state machine, centralizes line calculation/validation across engines, and extends the line-engine registry with register/deregister/list plus collection-time invoicing wiring to call engines during collection completion.

Changes

Cohort / File(s) Summary
Docs & Guidance
\.agents/skills/charges/SKILL.md, \.agents/skills/test/SKILL.md, AGENTS.md
Clarified line-engine wiring, collection-time hooks, usage-based advance behavior, test clock guidance, and context propagation recommendations.
Line engine API & validation
openmeter/billing/lineengine.go, openmeter/billing/validationissue.go, openmeter/billing/stdinvoiceline.go, openmeter/billing/stdinvoice.go, openmeter/billing/service.go
Added Validate() inputs, new validation issue code, helper to assert standard-line ID equality, ReplaceLinesByID, and extended LineEngineService with Deregister/GetRegisteredLineEngines.
Core engine/default engine refactor
openmeter/billing/lineengine/engine.go, openmeter/billing/lineengine/splitlinegroup.go, openmeter/billing/lineengine/stdinvoice.go
Implemented Engine.OnCollectionCompleted and CalculateLines; refactored split to operate on provided line and return optional post-split; centralized detailed-line generation into CalculateLines.
Invoice calc & resolver wiring
openmeter/billing/service/invoicecalc/calculator.go, openmeter/billing/service/invoicecalc/details.go, openmeter/billing/service/invoice.go
Added LineEngines resolver to CalculatorDependencies and rewired detailed-line generation to call per-engine CalculateLines via the resolver.
Invoice state / collection flow
openmeter/billing/service/stdinvoicestate.go, openmeter/billing/service/gatheringinvoicependinglines.go, openmeter/billing/service/invoice.go
Replaced snapshot-only collection with per-engine OnCollectionCompleted grouping, per-engine validation/error merging into invoice validation issues, stricter input/output validation, and in-place ReplaceLinesByID updates.
Usage-based implementation
openmeter/billing/charges/usagebased/service/lineengine.go, .../service/creditheninvoice.go, .../service/statemachine.go, .../service/triggers.go, .../service/service.go
Added usage-based LineEngine (BuildStandardInvoiceLines/OnCollectionCompleted/CalculateLines), CreditThenInvoiceStateMachine and actions, moved shared state-machine helpers to base StateMachine, and added newStateMachine dispatcher.
Charge engines: flatfee & creditpurchase
openmeter/billing/charges/flatfee/lineengine/engine.go, openmeter/billing/charges/creditpurchase/lineengine/engine.go
Refactored to delegate rating/merge/validation into new CalculateLines, added OnCollectionCompleted (pass-through) and input validation.
Registry & service wiring
openmeter/billing/service/lineengine.go, app/common/charges.go, openmeter/billing/charges/testutils/service.go, openmeter/billing/charges/meta/triggers.go
Added engine deregister/listing and grouping helpers; register usage-based line engine during service construction/tests; added invoice/collection trigger constants.
Tests & helpers
openmeter/billing/charges/service/base_test.go, openmeter/billing/charges/service/advance_test.go, openmeter/billing/charges/service/invoicable_test.go, test/billing/lineengine_test.go, test/billing/suite.go, test/billing/collection_test.go, openmeter/server/server_test.go
Registered usage-based line engine in tests; added/updated tests for credit‑then‑invoice lifecycle, collection-time errors → validation issues, custom snapshot preservation; added NoopBillingService stubs and test clock teardown.
Misc invoice wiring
openmeter/billing/service/invoice.go, openmeter/billing/service/lineengine.go, openmeter/billing/service/invoicecalc/details.go
Propagated line engine registry into invoice calc and added grouping/resolution of standard lines by engine before per-engine CalculateLines calls.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as Caller
    participant BillingSvc as Billing Service
    participant Registry as Line Engine Registry
    participant Engine as Line Engine (usage-based)
    participant StateMachine as UsageBased StateMachine
    participant Adapter as Charge Adapter/DB

    Caller->>BillingSvc: AdvanceInvoice(invoiceID)
    BillingSvc->>Registry: Group invoice.StandardLines by Engine & Get(engineType)
    loop per engine group
      BillingSvc->>Engine: OnCollectionCompleted(ctx, {Invoice, Lines})
      Engine->>StateMachine: Load/Create state machine for charge(s)
      Engine->>StateMachine: FireTrigger("collection_completed")
      StateMachine->>Adapter: Persist charge/state changes
      Adapter-->>StateMachine: Updated charge
      Engine->>Engine: Populate standard lines from realization run(s) & CalculateLines
      Engine-->>BillingSvc: Return updated lines
    end
    BillingSvc->>BillingSvc: Validate standard-line IDs match exactly
    BillingSvc->>BillingSvc: ReplaceLinesByID(updated lines)
    BillingSvc-->>Caller: Invoice advanced (QuantitySnapshotedAt set / validation issues merged)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • tothandras
  • GAlexIHU
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: invoice calculation hook' accurately describes the primary focus of this PR—introducing a new billing line-engine abstraction for invoice calculation and collection-time processing. It's concise, specific, and directly reflects the main architectural change.

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

✨ 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 feat/invoice-calculation-hook

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 release-note/feature Release note: Exciting New Features release-note/misc Miscellaneous changes area/billing and removed release-note/misc Miscellaneous changes labels Apr 9, 2026
@turip turip force-pushed the feat/invoice-calculation-hook branch from d03972e to 76739b4 Compare April 9, 2026 15:58
@turip turip force-pushed the feat/invoice-calculation-hook branch from 76739b4 to ac990ec Compare April 10, 2026 08:32
@turip turip force-pushed the feat/invoice-calculation-hook branch from ac990ec to ffa753b Compare April 10, 2026 09:08
@turip turip force-pushed the feat/invoice-calculation-hook branch from ffa753b to f867ac8 Compare April 10, 2026 09:16
@turip turip force-pushed the feat/invoice-calculation-hook branch from f867ac8 to b72e47d Compare April 10, 2026 09:16
@turip turip marked this pull request as ready for review April 10, 2026 09:16
@turip turip requested a review from a team as a code owner April 10, 2026 09:16
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.

Actionable comments posted: 9

Caution

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

⚠️ Outside diff range comments (1)
openmeter/billing/service/lineengine.go (1)

97-104: ⚠️ Potential issue | 🟠 Major

Normalize blank StandardLine.Engine before grouping.

If there are already-persisted draft lines with engine="", this new path will fail collection-time advancement instead of falling back to invoicing. We already have populateStandardLineEngine for that defaulting, so I'd either reuse it here or treat the rollout as requiring a backfill for existing standard lines.

Also applies to: 137-155

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

In `@openmeter/billing/service/lineengine.go` around lines 97 - 104, When
grouping/persisting StandardLine instances ensure blank Engine values are
normalized to the default before validation/grouping: call
engineRegistry.populateStandardLineEngine(line) (which handles defaulting to
billing.LineEngineTypeInvoice and delegates to validateLineEngine) for each
StandardLine prior to any collection-time grouping logic (also update the other
grouping path referenced in the diff around the secondary block) so persisted
draft lines with Engine=="" are treated as invoicing instead of failing.
🧹 Nitpick comments (2)
openmeter/billing/lineengine/engine.go (1)

60-63: Use the shared input validator here.

Right now this only checks Invoice.ID, so an empty Lines slice can slip through even though billing.OnCollectionCompletedInput.Validate() requires lines. Reusing the shared validator keeps the hook aligned with the contract in one place.

♻️ Suggested cleanup
 func (e *Engine) OnCollectionCompleted(ctx context.Context, input billing.OnCollectionCompletedInput) (billing.StandardLines, error) {
-	if input.Invoice.ID == "" {
-		return nil, fmt.Errorf("invoice is required")
+	if err := input.Validate(); err != nil {
+		return nil, fmt.Errorf("validate: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/lineengine/engine.go` around lines 60 - 63, Replace the
ad-hoc Invoice.ID check in Engine.OnCollectionCompleted with the shared
validator by calling input.Validate() at the start of the method; if Validate()
returns an error, return that error (or wrap it with context) so the method
enforces the full billing.OnCollectionCompletedInput contract (including
non-empty Lines) rather than only checking Invoice.ID.
openmeter/billing/service/lineengine.go (1)

166-168: Rename this before the API escapes.

GetRegisterdLineEngines bakes a typo into a new public method. Since this surface is still fresh, I'd fix it to GetRegisteredLineEngines now rather than carrying the misspelling into callers and docs.

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

In `@openmeter/billing/service/lineengine.go` around lines 166 - 168, The exported
method name GetRegisterdLineEngines contains a typo; rename the public method to
GetRegisteredLineEngines (update the function declaration and any internal
references to s.GetRegisterdLineEngines) and ensure the returned behavior still
calls s.lineEngines.List(); update any callers, tests, and documentation to use
GetRegisteredLineEngines to avoid breaking the public API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.agents/skills/charges/SKILL.md:
- Around line 187-188: The bullet claiming
`usagebased.Service.AdvanceCharge(...)` supports `CreditOnly` and noops for
`CreditThenInvoice`, and that `flatfee.Service.AdvanceCharge(...)` only supports
`CreditOnly`, is stale; update this text to reflect the new settlement-mode
dispatch where `AdvanceCharge(...)` (both usagebased.Service.AdvanceCharge and
flatfee.Service.AdvanceCharge) can build the
`NewCreditThenInvoiceStateMachine(...)` path for `CreditThenInvoice` rather than
noop, and clarify that invoice creation and collection completion remain
billing-triggered steps (invoice-created / collection-completed) handled
downstream; replace the contradictory wording with a concise statement that
AdvanceCharge routes to the appropriate settlement-mode state machine (e.g.,
CreditOnly vs CreditThenInvoice via NewCreditThenInvoiceStateMachine) and keep
invoice-created/collection-completed described as billing-triggered events.

In `@AGENTS.md`:
- Line 139: Move the guidance about avoiding context.Background()/context.TODO()
out of the "## Testing" section and place it under the "Code conventions"
section (or nearest existing conventions subsection) in AGENTS.md; update the
heading to match the conventions style, merge with any duplicate/stale notes
about context propagation or API parameter design, and remove the original entry
in "## Testing" so the guidance appears only once in the most relevant spot.

In `@openmeter/billing/charges/usagebased/service/creditheninvoice.go`:
- Around line 85-99: The state machine configuration in creditheninvoice.go
misses a transition from usagebased.StatusActiveFinalRealizationCompleted to
usagebased.StatusFinal, causing charges to be stuck; update the
s.Configure(usagebased.StatusActiveFinalRealizationCompleted) block (the one
that currently only calls Permit(meta.TriggerDelete, usagebased.StatusDeleted))
to also Permit(meta.TriggerNext, usagebased.StatusFinal) so the flow can advance
(mirror the behavior in creditsonly.go).

In `@openmeter/billing/charges/usagebased/service/lineengine.go`:
- Around line 122-178: After updating each stdLine from the realization run in
OnCollectionCompleted, re-run the invoice line recalculation to keep
totals/pricing consistent: call the project’s CalculateLines function (e.g. the
service/helper method named CalculateLines) with the same ctx, input.Invoice and
the modified input.Lines before returning; propagate and wrap any error (return
nil, fmt.Errorf(...)) and replace/return the recalculated billing.StandardLines
so the returned lines reflect final usage and pricing.

In `@openmeter/billing/service/gatheringinvoicependinglines.go`:
- Around line 632-649: After validating the shape with stdLines.Validate(),
verify that the set (or ordered list, depending on intended semantics) of IDs in
stdLines exactly matches the IDs in item.Lines before calling
invoice.Lines.Append; if they differ return an error referencing the engine (use
item.Engine.GetLineEngineType()) and include a clear message and the mismatched
IDs so the caller can handle it. Locate this check immediately after
stdLines.Validate() in the BuildStandardInvoiceLines path (where stdLines,
item.Lines and invoice.Lines.Append are used) and fail fast if any ID is
missing, added, or reordered in a way that violates expected replacement
semantics.

In `@openmeter/billing/service/stdinvoicestate.go`:
- Around line 856-865: The current code sets m.Invoice.QuantitySnapshotedAt
regardless of validationErrs and unconditionally overwrites any existing
timestamp; change it so the snapshot timestamp is only set when there are
groupedLines, there were no validationErrs (and mergeLineEngineValidationIssues
returned nil), and we do not overwrite an existing timestamp. Concretely: after
handling validationErrs via mergeLineEngineValidationIssues, only call
clock.Now().UTC() and assign to m.Invoice.QuantitySnapshotedAt when
len(groupedLines) > 0 AND len(validationErrs) == 0 (or after
mergeLineEngineValidationIssues succeeded) and m.Invoice.QuantitySnapshotedAt ==
nil.

In `@openmeter/billing/stdinvoice.go`:
- Around line 563-575: ReplaceLinesByID currently mutates c one-by-one and can
leave partial updates if a later replacement fails; change it to a two-phase
operation: first validate all inputs (ensure none are nil and each line.ID
exists in the receiver by calling ReplaceByID existence check or a dedicated
exists check) and collect any error, and only if validation passes, perform a
second loop that applies replacements by calling ReplaceByID for each line;
reference the ReplaceLinesByID method and the ReplaceByID helper so you validate
existence before applying changes and return early on validation failure without
mutating state.

In `@test/billing/collection_test.go`:
- Around line 364-367: Guard the pointer invoice.QuantitySnapshotedAt before
dereferencing: first assert/require that invoice.QuantitySnapshotedAt is not nil
(e.g., s.Require().NotNil(invoice.QuantitySnapshotedAt)) and only then call
clock.FreezeTime(*invoice.QuantitySnapshotedAt) and assign previousSnapshot :=
*invoice.QuantitySnapshotedAt; also replace the ineffective
s.NotNil(previousSnapshot) (a time.Time value) with a meaningful check such as
asserting previousSnapshot is not zero (e.g., !previousSnapshot.IsZero()) or
another appropriate time comparison.

In `@test/billing/lineengine_test.go`:
- Around line 71-76: The mock methods BuildStandardInvoiceLines and
OnCollectionCompleted currently type-assert args.Get(0) directly to
ombilling.StandardLines, which will panic when tests pass a function via
Return(); update each mock implementation to inspect args.Get(0) with a type
switch: if it's a function (matching the signature your tests use) invoke it
with the same input parameters to produce the ombilling.StandardLines result,
otherwise cast the value to ombilling.StandardLines (handling nil safely);
return the produced value and args.Error(1). Ensure you reference the mock
methods' names (BuildStandardInvoiceLines, OnCollectionCompleted) so the change
is localized to those implementations.

---

Outside diff comments:
In `@openmeter/billing/service/lineengine.go`:
- Around line 97-104: When grouping/persisting StandardLine instances ensure
blank Engine values are normalized to the default before validation/grouping:
call engineRegistry.populateStandardLineEngine(line) (which handles defaulting
to billing.LineEngineTypeInvoice and delegates to validateLineEngine) for each
StandardLine prior to any collection-time grouping logic (also update the other
grouping path referenced in the diff around the secondary block) so persisted
draft lines with Engine=="" are treated as invoicing instead of failing.

---

Nitpick comments:
In `@openmeter/billing/lineengine/engine.go`:
- Around line 60-63: Replace the ad-hoc Invoice.ID check in
Engine.OnCollectionCompleted with the shared validator by calling
input.Validate() at the start of the method; if Validate() returns an error,
return that error (or wrap it with context) so the method enforces the full
billing.OnCollectionCompletedInput contract (including non-empty Lines) rather
than only checking Invoice.ID.

In `@openmeter/billing/service/lineengine.go`:
- Around line 166-168: The exported method name GetRegisterdLineEngines contains
a typo; rename the public method to GetRegisteredLineEngines (update the
function declaration and any internal references to s.GetRegisterdLineEngines)
and ensure the returned behavior still calls s.lineEngines.List(); update any
callers, tests, and documentation to use GetRegisteredLineEngines to avoid
breaking the public API.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 85c627f5-21ad-4e8a-bf5a-674201e05e5a

📥 Commits

Reviewing files that changed from the base of the PR and between 3882c5a and b72e47d.

📒 Files selected for processing (35)
  • .agents/skills/charges/SKILL.md
  • .agents/skills/test/SKILL.md
  • AGENTS.md
  • app/common/charges.go
  • openmeter/billing/charges/creditpurchase/lineengine/engine.go
  • openmeter/billing/charges/flatfee/lineengine/engine.go
  • openmeter/billing/charges/meta/triggers.go
  • openmeter/billing/charges/service/advance_test.go
  • openmeter/billing/charges/service/base_test.go
  • openmeter/billing/charges/service/invoicable_test.go
  • openmeter/billing/charges/service/invoice.go
  • openmeter/billing/charges/testutils/service.go
  • openmeter/billing/charges/usagebased/service.go
  • openmeter/billing/charges/usagebased/service/creditheninvoice.go
  • openmeter/billing/charges/usagebased/service/lineengine.go
  • openmeter/billing/charges/usagebased/service/service.go
  • openmeter/billing/charges/usagebased/service/statemachine.go
  • openmeter/billing/charges/usagebased/service/triggers.go
  • openmeter/billing/lineengine.go
  • openmeter/billing/lineengine/engine.go
  • openmeter/billing/lineengine/splitlinegroup.go
  • openmeter/billing/lineengine/stdinvoice.go
  • openmeter/billing/service.go
  • openmeter/billing/service/gatheringinvoicependinglines.go
  • openmeter/billing/service/invoice.go
  • openmeter/billing/service/invoicecalc/calculator.go
  • openmeter/billing/service/invoicecalc/details.go
  • openmeter/billing/service/lineengine.go
  • openmeter/billing/service/stdinvoicestate.go
  • openmeter/billing/stdinvoice.go
  • openmeter/billing/stdinvoiceline.go
  • openmeter/billing/validationissue.go
  • openmeter/server/server_test.go
  • test/billing/collection_test.go
  • test/billing/lineengine_test.go

Comment thread .agents/skills/charges/SKILL.md Outdated
Comment thread AGENTS.md Outdated
Comment thread openmeter/billing/charges/usagebased/service/creditheninvoice.go
Comment thread openmeter/billing/charges/usagebased/service/lineengine.go
Comment thread openmeter/billing/service/gatheringinvoicependinglines.go
Comment thread openmeter/billing/service/stdinvoicestate.go Outdated
Comment thread openmeter/billing/stdinvoice.go
Comment thread test/billing/collection_test.go
Comment thread test/billing/lineengine_test.go Outdated
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 (1)
openmeter/billing/stdinvoice.go (1)

563-581: Nice two-phase validation-then-mutate pattern!

The previous review comment about partial mutations has been addressed - you're now validating all inputs exist before any replacements happen. That's solid.

One small edge case: if c.IsAbsent() is true, GetByID will return nil for every line, causing the error "line not found" which is technically correct but maybe a bit misleading. Consider adding an early return if lines are absent:

💡 Optional early-out for absent lines
 func (c *StandardInvoiceLines) ReplaceLinesByID(lines ...*StandardLine) error {
+	if c.IsAbsent() {
+		if len(lines) > 0 {
+			return fmt.Errorf("cannot replace lines: invoice lines are absent")
+		}
+		return nil
+	}
+
 	for _, line := range lines {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/stdinvoice.go` around lines 563 - 581, In ReplaceLinesByID,
add an early check for c.IsAbsent() at the top and return a clear error instead
of letting per-line GetByID produce "line not found" errors; update the function
ReplaceLinesByID to return something like fmt.Errorf("lines are absent") when
c.IsAbsent() is true so callers get an explicit, accurate failure; reference the
methods ReplaceLinesByID, IsAbsent, and GetByID when making this change.
🤖 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/stdinvoice.go`:
- Around line 563-581: In ReplaceLinesByID, add an early check for c.IsAbsent()
at the top and return a clear error instead of letting per-line GetByID produce
"line not found" errors; update the function ReplaceLinesByID to return
something like fmt.Errorf("lines are absent") when c.IsAbsent() is true so
callers get an explicit, accurate failure; reference the methods
ReplaceLinesByID, IsAbsent, and GetByID when making this change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c953f0af-5fcd-453e-bfc1-d74040c06b06

📥 Commits

Reviewing files that changed from the base of the PR and between b72e47d and 73e7d54.

📒 Files selected for processing (8)
  • AGENTS.md
  • openmeter/billing/lineengine.go
  • openmeter/billing/service/gatheringinvoicependinglines.go
  • openmeter/billing/service/stdinvoicestate.go
  • openmeter/billing/stdinvoice.go
  • test/billing/collection_test.go
  • test/billing/lineengine_test.go
  • test/billing/suite.go
✅ Files skipped from review due to trivial changes (1)
  • test/billing/collection_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • AGENTS.md
  • test/billing/lineengine_test.go
  • openmeter/billing/lineengine.go

GAlexIHU
GAlexIHU previously approved these changes Apr 10, 2026
Copy link
Copy Markdown
Contributor

@GAlexIHU GAlexIHU left a comment

Choose a reason for hiding this comment

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

lgtm

Comment thread openmeter/billing/service.go Outdated
type LineEngineService interface {
RegisterLineEngine(engine LineEngine) error
DeregisterLineEngine(engineType LineEngineType) error
GetRegisterdLineEngines() []LineEngineType
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

typo

@turip turip enabled auto-merge (squash) April 10, 2026 12:25
@turip turip merged commit 39468cc into main Apr 10, 2026
24 checks passed
@turip turip deleted the feat/invoice-calculation-hook branch April 10, 2026 12:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing release-note/feature Release note: Exciting New Features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants