Skip to content

fix(billing): repair persisted charge discount IDs - #4892

Merged
turip merged 3 commits into
mainfrom
fix/backfill-persisted-charge-discount-ids
Aug 9, 2026
Merged

fix(billing): repair persisted charge discount IDs#4892
turip merged 3 commits into
mainfrom
fix/backfill-persisted-charge-discount-ids

Conversation

@turip

@turip turip commented Aug 9, 2026

Copy link
Copy Markdown
Member

Summary

  • backfill missing correlation IDs in persisted usage-based and flat-fee charge intents and overrides
  • refresh gathering-line discount snapshots from the effective charge intent when building standard invoice lines
  • add migration and service regressions for legacy persisted charges and stale gathering snapshots

Root cause

The previous repair only updated gathering invoice-line snapshots. Detailed rating reads discounts from the persisted charge's effective intent, so legacy charges could still reach rating without a correlation ID. Create-time normalization does not repair those existing rows, and the rating failure occurs before the later persistence fallback can run.

Impact

Legacy charge-backed invoice lines can be collected and rated after deployment. Existing correlation IDs are preserved; only missing or empty IDs are generated. Standard-line construction also treats the effective charge intent as the authoritative discount identity instead of carrying a stale gathering snapshot forward.

Validation

  • POSTGRES_HOST=127.0.0.1 go test -tags=dynamic ./openmeter/billing/charges/... ./tools/migrate
  • make migrate-check-lint migrate-check-validate
  • nix develop --impure .#ci -c golangci-lint run --config .golangci-fast.yaml ./openmeter/billing/charges/... ./tools/migrate/...
  • pre-commit commitizen check

Summary by CodeRabbit

  • Bug Fixes

    • Invoice generation now applies the latest percentage discounts for flat-fee and usage-based charges.
    • Missing charge references are reported as errors instead of producing incomplete invoice lines.
    • Corrected invoice discount correlation IDs when stored data contains outdated values.
  • Data Integrity

    • Added a migration to restore missing discount correlation IDs while preserving existing values.
    • Verified migration behavior across charge and override records, including repeat-safe execution.

Greptile Summary

This follow-up repairs legacy persisted charge discount identities and refreshes gathering-line discount snapshots from effective charge intents during standard invoice construction.

  • Backfills missing percentage and usage discount correlation IDs across base and override charge records while preserving existing IDs.
  • Reloads flat-fee and usage-based charges when converting gathering lines and copies their effective discounts to the resulting standard lines.
  • Adds migration idempotency coverage and service regressions for stale gathering snapshots.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
openmeter/billing/charges/flatfee/service/lineengine.go Validates collection input, reloads referenced flat-fee charges, and snapshots effective percentage discounts onto standard lines.
openmeter/billing/charges/usagebased/service/lineengine.go Validates collection input, reloads referenced usage-based charges, and snapshots effective discounts onto standard lines.
tools/migrate/migrations/20260809172658_backfill_persisted_charge_discount_correlation_ids.up.sql Idempotently fills empty correlation IDs in persisted base and override discount objects while preserving existing values.
tools/migrate/charge_discount_correlation_ids_test.go Covers missing, null, existing, and repeat-run migration behavior across all affected charge tables.
openmeter/billing/charges/service/pendinglines_test.go Verifies collection replaces stale gathering discount identities with those from effective charge intents.

Sequence Diagram

sequenceDiagram
  participant DB as Persisted charge
  participant G as Gathering line
  participant E as Charge line engine
  participant S as Standard invoice line
  DB->>DB: Migration fills missing correlation IDs
  G->>E: Collect eligible gathering line
  E->>DB: Load charge and effective intent
  DB-->>E: Effective discount snapshot
  E->>S: Build line with authoritative discounts
Loading

Reviews (3): Last reviewed commit: "test(billing): pair frozen clock cleanup" | Re-trigger Greptile

Context used:

@turip
turip requested a review from a team as a code owner August 9, 2026 17:48
@coderabbitai

coderabbitai Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

BuildStandardInvoiceLines now applies effective discounts from associated charges instead of stale gathering-line snapshots. A transactional migration backfills missing discount correlation IDs across charge and override tables, with integration coverage for preservation and idempotency.

Changes

Charge discount correlation handling

Layer / File(s) Summary
Apply effective charge discounts
openmeter/billing/charges/flatfee/service/lineengine.go, openmeter/billing/charges/usagebased/service/lineengine.go, openmeter/billing/charges/service/pendinglines_test.go
The line engines validate input, load associated charges, and copy effective discounts to standard invoice lines. The pending-line test verifies that effective charge discount identities replace stale persisted snapshots.
Backfill persisted correlation IDs
tools/migrate/migrations/20260809172658_backfill_persisted_charge_discount_correlation_ids.up.sql, tools/migrate/charge_discount_correlation_ids_test.go
The migration generates missing correlation IDs across charge and override discount tables, preserves existing IDs, validates repaired records, and runs within a transaction. Tests verify missing, empty, null, absent, and existing values, plus idempotency.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the primary change: repairing persisted charge discount correlation IDs.
✨ 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 fix/backfill-persisted-charge-discount-ids

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

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

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

172-183: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a flat-fee stale-snapshot regression case.

Line 175 collects only the usage-based line because the flat-fee line has a later InvoiceAt. A regression in the changed flat-fee line builder can pass this test. Persist a stale flat-fee percentage correlation ID, collect that line, and assert the effective flat-fee intent ID.

As per path instructions, “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/billing/charges/service/pendinglines_test.go` around lines 172 -
183, Extend the pending-lines regression coverage around InvoicePendingLines and
the existing usage assertion to persist a stale flat-fee percentage correlation
ID, collect the flat-fee line after its later InvoiceAt, and assert its
generated RateCardDiscounts flat-fee correlation ID matches the effective
flat-fee intent rather than the stale snapshot. Keep the usage-based scenario
and assertions intact.

Source: Path instructions

🤖 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 `@tools/migrate/charge_discount_correlation_ids_test.go`:
- Around line 176-190: Expand the migration fixtures and assertions in the
relevant test to cover discount objects containing an explicit JSON null
correlationID, distinct from SQL NULL discounts. Add this case for the
applicable charge tables and assert the migration generates a non-empty
correlation ID while preserving existing and empty-string behaviors.

---

Outside diff comments:
In `@openmeter/billing/charges/service/pendinglines_test.go`:
- Around line 172-183: Extend the pending-lines regression coverage around
InvoicePendingLines and the existing usage assertion to persist a stale flat-fee
percentage correlation ID, collect the flat-fee line after its later InvoiceAt,
and assert its generated RateCardDiscounts flat-fee correlation ID matches the
effective flat-fee intent rather than the stale snapshot. Keep the usage-based
scenario and assertions intact.
🪄 Autofix

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 Plus

Run ID: eb6f8f6f-3bd8-4a5c-9740-18c446fcee88

📥 Commits

Reviewing files that changed from the base of the PR and between 42574e1 and 362dc8f.

⛔ Files ignored due to path filters (1)
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (6)
  • openmeter/billing/charges/flatfee/service/lineengine.go
  • openmeter/billing/charges/service/pendinglines_test.go
  • openmeter/billing/charges/usagebased/service/lineengine.go
  • tools/migrate/charge_discount_correlation_ids_test.go
  • tools/migrate/migrations/20260809172658_backfill_persisted_charge_discount_correlation_ids.down.sql
  • tools/migrate/migrations/20260809172658_backfill_persisted_charge_discount_correlation_ids.up.sql

Comment thread tools/migrate/charge_discount_correlation_ids_test.go
@turip turip added kind/bug Something isn't working release-note/bug-fix Release note: Bug Fixes labels Aug 9, 2026

@coderabbitai coderabbitai Bot left a comment

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.

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

210-212: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Pair the second clock freeze with its cleanup.

When the test calls clock.FreezeTime(flatLine.InvoiceAt) at Line 211, add defer clock.UnFreeze() immediately after it. The existing defer at Line 49 cleans up at function exit, but it does not follow the required local lifecycle pattern.

Proposed cleanup
-	clock.FreezeTime(flatLine.InvoiceAt)
+	clock.FreezeTime(flatLine.InvoiceAt)
+	defer clock.UnFreeze()

As per coding guidelines, “Pair clock.FreezeTime(...) immediately with defer clock.UnFreeze() in the same scope.”

🤖 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/pendinglines_test.go` around lines 210 -
212, In the test flow around the second clock.FreezeTime call for
flatLine.InvoiceAt, immediately add a matching defer clock.UnFreeze() in the
same scope. Keep the existing function-level cleanup unchanged.

Source: Coding guidelines

🤖 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/billing/charges/service/pendinglines_test.go`:
- Around line 210-212: In the test flow around the second clock.FreezeTime call
for flatLine.InvoiceAt, immediately add a matching defer clock.UnFreeze() in the
same scope. Keep the existing function-level cleanup unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 0c984e31-b8df-495d-a93a-9ca36dc42123

📥 Commits

Reviewing files that changed from the base of the PR and between 362dc8f and 673d003.

📒 Files selected for processing (2)
  • openmeter/billing/charges/service/pendinglines_test.go
  • tools/migrate/charge_discount_correlation_ids_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • tools/migrate/charge_discount_correlation_ids_test.go

@turip
turip merged commit f343514 into main Aug 9, 2026
27 checks passed
@turip
turip deleted the fix/backfill-persisted-charge-discount-ids branch August 9, 2026 18:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/billing Bug kind/bug Something isn't working release-note/bug-fix Release note: Bug Fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants