Skip to content

chore: flat-fee run support#4350

Merged
turip merged 2 commits into
mainfrom
chore/flat-fee-run-support
May 12, 2026
Merged

chore: flat-fee run support#4350
turip merged 2 commits into
mainfrom
chore/flat-fee-run-support

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented May 12, 2026

Summary

Adds domain-level run support for flat-fee charge realizations.

Flat-fee realizations are now grouped under a current run, with prior runs available for future prorating workflows. Credit allocations, invoice usage, detailed lines, and payment state are represented through the current run instead of directly on the flat-fee charge realization state.

Behavior Changes

  • Flat-fee credit-only charges are created without a run and provision the current run when they become active.
  • Deleting a future credit-only flat-fee charge no longer requires credit correction, because no credits have been allocated yet.
  • Flat-fee credit-then-invoice charges persist invoice line and invoice references on the current run.
  • Accrued usage no longer carries lineID or mutable; line assignment belongs to the realization run.
  • Detailed lines are fetched from the current run.

Migration

Existing flat-fee run data is migrated into the new run shape without backfilling invoice line references, since flat-fee credit-then-invoice was not active for existing production data.

Summary by CodeRabbit

  • Refactor

    • Separate current run data from prior runs for clearer billing history
    • Move invoice line tracking and invoice association into charge runs
    • Payment and credit processing now operate on the run-scoped current run
    • Add explicit provisioning for charge current runs
  • Data model / Migrations

    • Add run-level invoice/line fields and a no-fiat-transaction flag; remove line/mutable from invoiced usage
  • Tests

    • Update tests to validate run-scoped behavior and voided-run handling

Review Change Stack

@turip turip requested a review from a team as a code owner May 12, 2026 15:34
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 12, 2026

📝 Walkthrough

Walkthrough

This PR refactors flat-fee charge realizations into a run-scoped model (CurrentRun + PriorRuns), adds ProvisionCurrentRun and FetchCurrentRunDetailedLines adapter methods, changes invoiced-usage to use input structs, updates DB schema/mapping, and cascades changes through services, ledger, and tests.

Changes

Flat-Fee Realization Run Domain Refactor

Layer / File(s) Summary
Adapter interfaces and input contracts
openmeter/billing/charges/flatfee/adapter.go
New ChargeAdapter.ProvisionCurrentRun, ChargeDetailedLineAdapter.FetchCurrentRunDetailedLines, and ChargeInvoicedUsageAdapter.CreateInvoicedUsage(CreateInvoicedUsageInput) signatures plus ProvisionCurrentRunInput and CreateInvoicedUsageInput with validation; IntentWithInitialStatus adds NoFiatTransactionRequired.
Realization run model & invoiced-usage model
openmeter/billing/charges/flatfee/realizationrun.go, openmeter/billing/charges/models/invoicedusage/*
Add RealizationRunID, RealizationRunBase, RealizationRun, and RealizationRuns with normalization/validation; remove LineID/Mutable from AccruedUsage and adjust mixin mapping/interfaces.
DB schema and migrations
openmeter/ent/schema/chargesflatfee.go, openmeter/ent/schema/billing.go, tools/migrate/*
Add line_id, invoice_id, and no_fiat_transaction_required to ChargeFlatFeeRun, update edges to invoices/invoice lines, and provide up/down migrations to apply/revert the changes and remove invoiced-usage line_id.
Entity mapping and realization loading
openmeter/billing/charges/flatfee/adapter/mapper.go
Refactor MapChargeFlatFeeFromDB to load all runs and partition into CurrentRun/PriorRuns; add mapRealizationRunBaseFromDB and mapRealizationRunFromDB helpers with totals/service-period normalization.
Current run provisioning adapter
openmeter/billing/charges/flatfee/adapter/charge.go, openmeter/billing/charges/flatfee/adapter/realizationrun.go
Implement ProvisionCurrentRun (transactional), update createCurrentRun to persist NoFiatTransactionRequired, and wire provisioning into post-create flows.
Current run detailed lines fetching
openmeter/billing/charges/flatfee/adapter/detailedline.go, openmeter/billing/charges/flatfee/adapter/charge.go
Implement FetchCurrentRunDetailedLines (requires CurrentRun), query by run ID, and attach results to CurrentRun.DetailedLines; switch GetByID(s) expansion accordingly.
Invoiced usage adapter & usage-based changes
openmeter/billing/charges/flatfee/adapter/usage.go, openmeter/billing/charges/usagebased/service/run/invoice.go
CreateInvoicedUsage now accepts CreateInvoicedUsageInput, validates input, updates run totals and run LineID/InvoiceID, and creates run-scoped invoiced usage; usage-based callers stop passing removed LineID/Mutable.
Charge structure refactor
openmeter/billing/charges/flatfee/charge.go
Replace top-level realization fields with Realizations.CurrentRun *RealizationRun and PriorRuns RealizationRuns; update validation and imports.
Service wiring: creation, credits, invoice, payment, realizations
openmeter/billing/charges/flatfee/service/*
Set NoFiatTransactionRequired at creation, lazily provision CurrentRun before credit allocation, require CurrentRun for invoice posting and payment flows, and update CorrectAllCredits to use CurrentRun.CreditRealizations.
Ledger & customer-balance
openmeter/ledger/*
Ledger handlers require CurrentRun and compute amounts from CurrentRun.AccruedUsage.Totals; customer-balance RealizedCredits() skips voided runs and reads from CurrentRun.CreditRealizations.
Tests: adapters, services, integration
openmeter/.../*, test/credits/sanity_test.go
Update detailed-line tests, ledger test helpers, invoicable/lineage/usage-based tests, and integration sanity tests to construct and assert on CurrentRun structures; add tests for missing CurrentRun validation and voided-run behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

kind/refactor

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'chore: flat-fee run support' accurately reflects the main objective of refactoring flat-fee charge realizations to use a run-based structure with current and prior runs.
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.

✏️ 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 chore/flat-fee-run-support

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 area/billing release-note/feature Release note: Exciting New Features labels May 12, 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.

Actionable comments posted: 2

🧹 Nitpick comments (1)
openmeter/billing/charges/service/lineage_test.go (1)

131-132: ⚡ Quick win

Avoid order-coupled assertions on CreditRealizations.

These asserts depend on [0]/[1] staying stable. Small ordering shifts can make this flaky even when behavior is correct. Prefer asserting by realization identity/properties (e.g., build expected-by-origin/amount map first).

🤖 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 `@openmeter/billing/charges/service/lineage_test.go` around lines 131 - 132,
The two assertions in the test are order-dependent because they index
CreditRealizations by [0] and [1]; instead, build a lookup (map) keyed by each
realization's ID or by a stable key like origin+amount from
charge.Realizations.CurrentRun.CreditRealizations, then call
s.assertInitialLineage(...) for each expected realization by retrieving the
matching realization from that map and passing its ID and expected properties;
update the assertions that reference CreditRealizations[0]/[1] to use the lookup
so tests no longer rely on ordering.
🤖 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 `@openmeter/billing/charges/flatfee/adapter/mapper.go`:
- Around line 68-74: The mapping loop can leave realizations.CurrentRun nil when
entity.CurrentRealizationRunID is set but no dbRun matched; after the loop in
the mapper function that builds the realizations (the code referencing
entity.CurrentRealizationRunID, dbRun.ID, realizations.CurrentRun,
realizations.PriorRuns), add a fail-fast check: if
entity.CurrentRealizationRunID != nil && realizations.CurrentRun == nil then
return an explicit error (or propagate one) indicating the current realization
run id was not found; update the mapper's signature to return an error if
necessary and ensure callers handle that error instead of proceeding with a nil
CurrentRun.

In `@openmeter/ledger/customerbalance/calculation.go`:
- Around line 41-45: When computing realized credits in the block referencing
charge.Realizations.CurrentRun and CreditRealizations.Sum(), skip credits from
runs that have been voided (mirror the usage-based logic). Update the
early-return logic so that after checking CurrentRun != nil you also check the
run's void flag or method (e.g., CurrentRun.Voided == true or
CurrentRun.IsVoided()) and return alpacadecimal.Zero for voided runs; otherwise
return charge.Realizations.CurrentRun.CreditRealizations.Sum().

---

Nitpick comments:
In `@openmeter/billing/charges/service/lineage_test.go`:
- Around line 131-132: The two assertions in the test are order-dependent
because they index CreditRealizations by [0] and [1]; instead, build a lookup
(map) keyed by each realization's ID or by a stable key like origin+amount from
charge.Realizations.CurrentRun.CreditRealizations, then call
s.assertInitialLineage(...) for each expected realization by retrieving the
matching realization from that map and passing its ID and expected properties;
update the assertions that reference CreditRealizations[0]/[1] to use the lookup
so tests no longer rely on ordering.
🪄 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: 99637358-9a68-4b1f-bc68-f3ec5425833f

📥 Commits

Reviewing files that changed from the base of the PR and between 5e858a7 and 28b8053.

⛔ Files ignored due to path filters (37)
  • openmeter/ent/db/billinginvoice.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoice/billinginvoice.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoice/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoice_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoice_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoice_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline/billinginvoiceline.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeerun.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeerun/chargeflatfeerun.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeerun/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeerun_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeerun_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeerun_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeeruninvoicedusage.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeeruninvoicedusage/chargeflatfeeruninvoicedusage.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeeruninvoicedusage/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeeruninvoicedusage_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeeruninvoicedusage_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeflatfeeruninvoicedusage_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeusagebasedruninvoicedusage.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeusagebasedruninvoicedusage/chargeusagebasedruninvoicedusage.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeusagebasedruninvoicedusage/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeusagebasedruninvoicedusage_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeusagebasedruninvoicedusage_query.go is excluded by !**/ent/db/**
  • openmeter/ent/db/chargeusagebasedruninvoicedusage_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/client.go is excluded by !**/ent/db/**
  • openmeter/ent/db/entmixinaccessor.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • openmeter/ent/db/runtime.go is excluded by !**/ent/db/**
  • openmeter/ent/db/setorclear.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (30)
  • openmeter/billing/charges/flatfee/adapter.go
  • openmeter/billing/charges/flatfee/adapter/charge.go
  • openmeter/billing/charges/flatfee/adapter/detailedline.go
  • openmeter/billing/charges/flatfee/adapter/detailedline_test.go
  • openmeter/billing/charges/flatfee/adapter/mapper.go
  • openmeter/billing/charges/flatfee/adapter/realizationrun.go
  • openmeter/billing/charges/flatfee/adapter/usage.go
  • openmeter/billing/charges/flatfee/charge.go
  • openmeter/billing/charges/flatfee/realizationrun.go
  • openmeter/billing/charges/flatfee/service/create.go
  • openmeter/billing/charges/flatfee/service/creditsonly.go
  • openmeter/billing/charges/flatfee/service/invoice.go
  • openmeter/billing/charges/flatfee/service/payment.go
  • openmeter/billing/charges/flatfee/service/realizations/service.go
  • openmeter/billing/charges/models/invoicedusage/mixin.go
  • openmeter/billing/charges/models/invoicedusage/stdinvoice.go
  • openmeter/billing/charges/service/invoicable_test.go
  • openmeter/billing/charges/service/lineage_test.go
  • openmeter/billing/charges/usagebased/service/creditheninvoice_test.go
  • openmeter/billing/charges/usagebased/service/run/invoice.go
  • openmeter/billing/charges/usagebased/service/run/payment_test.go
  • openmeter/ent/schema/billing.go
  • openmeter/ent/schema/chargesflatfee.go
  • openmeter/ledger/chargeadapter/flatfee.go
  • openmeter/ledger/chargeadapter/flatfee_test.go
  • openmeter/ledger/chargeadapter/usagebased_test.go
  • openmeter/ledger/customerbalance/calculation.go
  • test/credits/sanity_test.go
  • tools/migrate/migrations/20260512114051_flatfee_run_domain_fields.down.sql
  • tools/migrate/migrations/20260512114051_flatfee_run_domain_fields.up.sql
💤 Files with no reviewable changes (5)
  • openmeter/ledger/chargeadapter/usagebased_test.go
  • openmeter/billing/charges/models/invoicedusage/mixin.go
  • openmeter/billing/charges/usagebased/service/run/invoice.go
  • openmeter/billing/charges/usagebased/service/run/payment_test.go
  • openmeter/billing/charges/usagebased/service/creditheninvoice_test.go

Comment thread openmeter/billing/charges/flatfee/adapter/mapper.go
Comment thread openmeter/ledger/customerbalance/calculation.go
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/ledger/customerbalance/service_test.go (1)

206-231: ⚡ Quick win

Nice coverage—please add the nil CurrentRun case too.

This test covers the voided-run path well, but the paired behavior (CurrentRun == nil => zero realized credits) should get its own regression test in the same block.

As per coding guidelines, "**/*_test.go: Make sure the tests are comprehensive and cover the changes."

🤖 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 `@openmeter/ledger/customerbalance/service_test.go` around lines 206 - 231, Add
a paired test that asserts realized credits are zero when the flatfee charge has
Realizations with CurrentRun == nil: create a new test (e.g.,
TestImpactRealizedCreditsSkipsNilCurrentRun) that constructs the same input via
NewImpact/charges.NewCharge with flatfee.Realizations where CurrentRun is nil
and a positive overall balance (alpacadecimal.NewFromInt(50)), assert no error
from NewImpact and require.True that
impact.RealizedCredits().Equal(alpacadecimal.Zero); reference NewImpact,
charges.NewCharge, flatfee.Realizations and the CurrentRun field when locating
where to add this test.
🤖 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 `@openmeter/ledger/customerbalance/service_test.go`:
- Around line 206-231: Add a paired test that asserts realized credits are zero
when the flatfee charge has Realizations with CurrentRun == nil: create a new
test (e.g., TestImpactRealizedCreditsSkipsNilCurrentRun) that constructs the
same input via NewImpact/charges.NewCharge with flatfee.Realizations where
CurrentRun is nil and a positive overall balance (alpacadecimal.NewFromInt(50)),
assert no error from NewImpact and require.True that
impact.RealizedCredits().Equal(alpacadecimal.Zero); reference NewImpact,
charges.NewCharge, flatfee.Realizations and the CurrentRun field when locating
where to add this test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8236f01a-d83d-43e9-a857-a773ca268156

📥 Commits

Reviewing files that changed from the base of the PR and between 28b8053 and 8621758.

📒 Files selected for processing (4)
  • openmeter/billing/charges/flatfee/adapter/mapper.go
  • openmeter/billing/charges/flatfee/realizationrun.go
  • openmeter/ledger/customerbalance/calculation.go
  • openmeter/ledger/customerbalance/service_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • openmeter/ledger/customerbalance/calculation.go
  • openmeter/billing/charges/flatfee/adapter/mapper.go
  • openmeter/billing/charges/flatfee/realizationrun.go

@turip turip requested a review from tothandras May 12, 2026 16:37
@turip turip merged commit fa9a958 into main May 12, 2026
28 checks passed
@turip turip deleted the chore/flat-fee-run-support branch May 12, 2026 17:38
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