feat: separate fiat overage credit realizations from charge currency allocations - #4857
Conversation
📝 WalkthroughWalkthroughThe change separates charge-currency and fiat-overage credit realizations. It adds storage schemas, structured adapter inputs, same-run correction validation, realization-run mapping, service integration, and adapter test coverage for flat-fee and usage-based charges. ChangesCredit realization persistence and service flow
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant RealizationService
participant CreditAllocationAdapter
participant EntDatabase
participant RealizationRun
RealizationService->>CreditAllocationAdapter: Submit charge-currency or fiat-overage input
CreditAllocationAdapter->>EntDatabase: Check same-run correction targets
CreditAllocationAdapter->>EntDatabase: Save realization allocations in a transaction
EntDatabase-->>CreditAllocationAdapter: Return persisted allocations
CreditAllocationAdapter-->>RealizationService: Return credit realizations
RealizationRun->>EntDatabase: Load charge-currency and fiat-overage allocations
EntDatabase-->>RealizationRun: Return mapped realization collections
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
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. Comment |
1c8b7dd to
79cdb5c
Compare
|
Note GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer. |
| correctionTargetIDs := input.CreditRealizations.CorrectionTargetIDs() | ||
| if len(correctionTargetIDs) > 0 { |
There was a problem hiding this comment.
Allocation batches bypass run scoping
When an allocation-only batch carries an existing run ID paired with a different namespace, CorrectionTargetIDs() is empty, so the scoped lookup is skipped and the allocation is persisted with the supplied namespace and run ID. Because the foreign key validates only the run ID, this creates realization history whose namespace differs from its parent run, causing namespace-scoped reads to omit the allocation.
Knowledge Base Used: Data layer: ent schema, migrations, and generated client
Prompt To Fix With AI
This is a comment left during a code review.
Path: openmeter/billing/charges/flatfee/adapter/credits.go
Line: 26-27
Comment:
**Allocation batches bypass run scoping**
When an allocation-only batch carries an existing run ID paired with a different namespace, `CorrectionTargetIDs()` is empty, so the scoped lookup is skipped and the allocation is persisted with the supplied namespace and run ID. Because the foreign key validates only the run ID, this creates realization history whose namespace differs from its parent run, causing namespace-scoped reads to omit the allocation.
**Knowledge Base Used:** [Data layer: ent schema, migrations, and generated client](https://app.greptile.com/openmeter/-/custom-context/knowledge-base/openmeterio/openmeter/-/docs/data-layer.md)
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Ticket: OM-435
Summary
Custom-currency
credit_then_invoicecharges cross two monetary domains during realization:This change represents those domains as separate credit-realization streams for both flat-fee and usage-based runs. Fiat-overage realizations are persisted in dedicated allocation tables, while the existing allocation tables remain the source of truth for charge-currency realizations.
Why
Fiat-overage allocations cannot safely share one realization collection with charge-currency allocations: their amounts have different units, and corrections must never cross between those monetary domains. Keeping them in separate tables gives each stream its own correction lineage. The existing self-referencing foreign keys enforce same-domain target existence, while the adapters enforce that corrections target persisted allocations from the same realization run.
The adapter boundary now makes the monetary domain explicit when writing realizations, and realization runs load charge-currency and fiat-overage allocations independently.
Domain impact
This PR does not yet consume settlement-fiat credits for an overage; it provides the domain and adapter foundation for that behavior.
Validation
make migrate-checkmake lintmake test— 6,778 tests passed, 9 skippedSummary by CodeRabbit
New Features
Bug Fixes
Greptile Summary
The PR separates charge-currency and settlement-fiat credit realizations into independent persistence streams for flat-fee and usage-based runs.
Confidence Score: 4/5
The PR is not yet safe to merge because allocation-only writes can persist realizations under a namespace different from their parent run.
The new correction queries enforce same-run ownership only when corrections are present, while ordinary allocation batches proceed directly to insertion using independently supplied namespace and run-ID values that the database does not constrain as a pair.
Files Needing Attention: openmeter/billing/charges/flatfee/adapter/credits.go and openmeter/billing/charges/usagebased/adapter/credits.go
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart LR Run[Realization run] Service[Realization service] ChargeAdapter[Charge-currency adapter] FiatAdapter[Fiat-overage adapter] ChargeTable[(Charge-currency allocations)] FiatTable[(Fiat-overage allocations)] Service --> ChargeAdapter --> ChargeTable Service --> FiatAdapter --> FiatTable Run --> ChargeAdapter Run --> FiatAdapterPrompt To Fix All With AI
Reviews (3): Last reviewed commit: "fix(billing): scope credit corrections t..." | Re-trigger Greptile
Context used: