Skip to content

feat(ledger): add tax code and tax behavior to ledger types#4291

Open
mark-vass-konghq wants to merge 4 commits into
mainfrom
feat/add-tax-code-and-tax-behavior-to-ledger-types
Open

feat(ledger): add tax code and tax behavior to ledger types#4291
mark-vass-konghq wants to merge 4 commits into
mainfrom
feat/add-tax-code-and-tax-behavior-to-ledger-types

Conversation

@mark-vass-konghq
Copy link
Copy Markdown
Contributor

@mark-vass-konghq mark-vass-konghq commented May 5, 2026

Summary by CodeRabbit

  • New Features

    • Added optional tax behavior (inclusive/exclusive) on routes, persisted and incorporated into routing keys; tax code and tax behavior now propagate through routing and transaction flows.
  • Refactor

    • Routing/filter types now preserve presence semantics to distinguish absent vs explicitly null tax fields.
  • Bug Fixes

    • Sub-account resolution and routing correctly differentiate nil vs non-nil tax codes and tax behavior.
  • Tests

    • Added coverage for tax behavior validation, routing-key variants, normalization, pairing keys, and tax-code isolation.
  • Chores

    • DB migration to add nullable tax_behavior and backfill routing keys; down-migration removes added segment.

Review Change Stack

@mark-vass-konghq mark-vass-konghq self-assigned this May 5, 2026
@mark-vass-konghq mark-vass-konghq added release-note/feature Release note: Exciting New Features area/billing labels May 5, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a TaxBehavior domain type (inclusive/exclusive), threads it through Route/RouteFilter/validation/normalization/routing-key generation, persists it in the schema, and updates DB predicates and transaction routing to distinguish absent vs explicitly nil TaxCode and TaxBehavior; tests expanded accordingly.

Changes

Tax Behavior & TaxCode Nil-Distinction

Layer / File(s) Summary
Domain Type & Validation
openmeter/ledger/routing.go, openmeter/ledger/errors.go
Adds TaxBehavior type with Inclusive/Exclusive constants and Validate(); adds ErrCodeTaxBehaviorInvalid / ErrTaxBehaviorInvalid.
Route Shape
openmeter/ledger/routing.go
Route gains TaxBehavior *TaxBehavior; Route.Validate() checks it; Route.Filter() pins it.
Filter Shape
openmeter/ledger/primitives.go
Converts RouteFilter.TaxCode to mo.Option[*string] and adds RouteFilter.TaxBehavior mo.Option[*TaxBehavior]; presence-vs-explicit-nil semantics supported.
Normalization & Routing Key
openmeter/ledger/routing.go, openmeter/ledger/routing_test.go
RouteFilter.Normalize() handles TaxBehavior and preserves absent vs Some(nil); BuildRoutingKeyV1 includes tax_behavior: via optionalTaxBehaviorValue(); tests added/updated for normalization, validation, and routing-key differences.
Parameter Wiring
openmeter/ledger/accounts.go
Adds TaxBehavior to CustomerFBORouteParams and TaxCode to receivable/accrued params; their Route() methods propagate these fields.
Adapter / DB Schema & Predicates
openmeter/ent/schema/ledger_account.go, openmeter/ledger/account/adapter/subaccount.go, openmeter/ledger/historical/adapter/sumentries_query.go
Adds tax_behavior DB field (optional, nillable); adapter sets SetNillableTaxBehavior(...) and maps DB value into ledger.Route; DB predicate builders use presence checks to append TaxCode(...) or TaxCodeIsNil() and TaxBehavior(...) or TaxBehaviorIsNil() appropriately.
Routing Keys & Pairing Keys
openmeter/ledger/transactions/correction_leg.go, openmeter/ledger/transactions/accrual.go
routePairingKey gains taxCode member and string format; accrual uses taxCodeKey(...) to stringify nil as null.
Transaction Templates & Wiring
openmeter/ledger/transactions/customer.go
Adds TaxCode *string to customer receivable templates and propagates TaxCode into sub-account resolution paths for FBO and receivable flows.
Tests & Test Helpers
openmeter/ledger/routing_test.go, openmeter/ledger/transactions/*, openmeter/ledger/testutils/integration.go
Extensive tests added/updated: routing-key expectations, TaxBehavior validation, RouteFilter normalization semantics, pairing-key equality/string, accrual tax-code separation, correction pairing tests, and test helpers that accept optional taxCode/TaxBehavior.
Migrations
tools/migrate/migrations/20260506103900_add_ledger_tax_behavior.up.sql, .down.sql
DB migration adds nullable tax_behavior column and backfills routing_key for v1 keys; down migration strips the segment and drops the column.

Sequence Diagram(s)

sequenceDiagram
  participant Template as Transaction Template
  participant Router as Route/Normalize/Validate
  participant Adapter as SubAccount Adapter
  participant DB as Database (ledger_sub_account_routes)
  participant Accrual as Accrual/Correction Logic

  Template->>Router: Build Route (currency, costBasis, taxCode?, taxBehavior?)
  Router->>Router: Normalize & Validate (preserve Some(nil) vs absent)
  Router->>Adapter: ResolveOrCreateRoute (includes nillable taxBehavior/taxCode)
  Adapter->>DB: Query/Insert with predicates (TaxCode/TaxBehavior or IsNil)
  DB-->>Adapter: Route row (tax_behavior included)
  Adapter-->>Router: Mapped ledger.Route (includes TaxBehavior)
  Router->>Accrual: Provide routePairingKey (includes taxCode)
  Accrual->>Accrual: Separate buckets by taxCode / taxBehavior
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 accurately captures the main feature added across the changeset: introduction of tax code and tax behavior fields to ledger types, including routing, filtering, validation, and database schema updates.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/add-tax-code-and-tax-behavior-to-ledger-types

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.

@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 2292b60 to 198e123 Compare May 5, 2026 15:12
@mark-vass-konghq mark-vass-konghq changed the base branch from main to feat/test-charge-tax-config-carryover May 5, 2026 15:13
@mark-vass-konghq mark-vass-konghq changed the base branch from feat/test-charge-tax-config-carryover to main May 5, 2026 15:14
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 198e123 to d11aa78 Compare May 5, 2026 15:15
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from d11aa78 to 6cb0134 Compare May 5, 2026 15:19
@mark-vass-konghq mark-vass-konghq marked this pull request as ready for review May 5, 2026 15:26
@mark-vass-konghq mark-vass-konghq requested a review from a team as a code owner May 5, 2026 15:26
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.

Caution

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

⚠️ Outside diff range comments (1)
openmeter/ledger/account/adapter/subaccount.go (1)

82-92: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

TaxBehavior is in the routing key but never persisted or read back.

The new Route.TaxBehavior flows into the routing key (good — guarantees row uniqueness), but it's missing from the actual route persistence/read path:

  • resolveOrCreateRoute here doesn't call SetNillableTaxBehavior(normalizedRoute.TaxBehavior).
  • MapSubAccountData (Lines 234‑241) reconstructs ledger.Route without TaxBehavior, so any sub-account loaded from the DB will always have Route.TaxBehavior == nil, even if it was set when the route was created.
  • ListSubAccounts (Lines 166‑173) handles TaxCode with IsPresent()/Get() but doesn't add a TaxBehavior predicate, so filter callers asking for a specific tax behavior get unfiltered results.

Per openmeter/ent/schema/ledger_account.go (LedgerSubAccountRoute.Fields), there's also no tax_behavior column on the schema, so this needs a schema field + migration before the persistence/filter wiring will work.

🛠️ Sketch of what needs to land together

In the schema (openmeter/ent/schema/ledger_account.go):

field.String("tax_behavior").
    GoType(ledger.TaxBehavior("")).
    Optional().Nillable().Immutable(),

In resolveOrCreateRoute:

         SetCurrency(string(normalizedRoute.Currency)).
         SetNillableTaxCode(normalizedRoute.TaxCode).
+        SetNillableTaxBehavior(normalizedRoute.TaxBehavior).
         SetFeatures(normalizedRoute.Features).

In MapSubAccountData:

         Route: ledger.Route{
             Currency:                       currencyx.Code(dbRoute.Currency),
             TaxCode:                        dbRoute.TaxCode,
+            TaxBehavior:                    dbRoute.TaxBehavior,
             Features:                       dbRoute.Features,

In ListSubAccounts (and the equivalent block in historical/adapter/sumentries_query.go):

         if normalizedRoute.TaxCode.IsPresent() { ... }
+        if normalizedRoute.TaxBehavior.IsPresent() {
+            tb, _ := normalizedRoute.TaxBehavior.Get()
+            if tb != nil {
+                routePredicates = append(routePredicates, dbledgersubaccountroute.TaxBehavior(*tb))
+            } else {
+                routePredicates = append(routePredicates, dbledgersubaccountroute.TaxBehaviorIsNil())
+            }
+        }

Happy to draft the schema field + migration + adapter wiring as a follow-up if helpful — just say the word.

🤖 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/account/adapter/subaccount.go` around lines 82 - 92, The
code forgets to persist, read back, and filter on Route.TaxBehavior: add a new
nullable/optional tax_behavior field to the ent schema
(LedgerSubAccountRoute.Fields) and create the migration, then in
resolveOrCreateRoute call SetNillableTaxBehavior(normalizedRoute.TaxBehavior)
when building the create, update MapSubAccountData to populate
ledger.Route.TaxBehavior from the DB row, and add a TaxBehavior predicate in
ListSubAccounts (and the analogous historical sumentries query) so queries honor
requested tax behavior filters.
🧹 Nitpick comments (1)
openmeter/ledger/routing_test.go (1)

240-248: 💤 Low value

Tiny test naming + literal nits.

Two small things:

  • Function name has a casing typo: TestBuildroutingKeyV1_...TestBuildRoutingKeyV1_... to match the rest of the file.
  • The literal TaxBehavior("exclusive") on Line 244 could use the typed const for consistency with TestBuildRoutingKeyV1_DifferentTaxBehavior_DifferentKey above.
✏️ Suggested cleanup
-func TestBuildroutingKeyV1_WithTaxBehaviorAndTaxCode(t *testing.T) {
+func TestBuildRoutingKeyV1_WithTaxBehaviorAndTaxCode(t *testing.T) {
 	key, err := BuildRoutingKeyV1(Route{
 		Currency:    currencyx.Code("USD"),
 		TaxCode:     lo.ToPtr("GST10"),
-		TaxBehavior: lo.ToPtr(TaxBehavior("exclusive")),
+		TaxBehavior: lo.ToPtr(TaxBehaviorExclusive),
 	})
🤖 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/routing_test.go` around lines 240 - 248, Rename the test
function TestBuildroutingKeyV1_WithTaxBehaviorAndTaxCode to
TestBuildRoutingKeyV1_WithTaxBehaviorAndTaxCode (fix casing) and replace the
literal TaxBehavior("exclusive") with the package's typed const used elsewhere
(e.g. TaxBehaviorExclusive) when constructing the Route passed to
BuildRoutingKeyV1; this keeps naming consistent and uses the typed constant for
TaxBehavior.
🤖 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.

Outside diff comments:
In `@openmeter/ledger/account/adapter/subaccount.go`:
- Around line 82-92: The code forgets to persist, read back, and filter on
Route.TaxBehavior: add a new nullable/optional tax_behavior field to the ent
schema (LedgerSubAccountRoute.Fields) and create the migration, then in
resolveOrCreateRoute call SetNillableTaxBehavior(normalizedRoute.TaxBehavior)
when building the create, update MapSubAccountData to populate
ledger.Route.TaxBehavior from the DB row, and add a TaxBehavior predicate in
ListSubAccounts (and the analogous historical sumentries query) so queries honor
requested tax behavior filters.

---

Nitpick comments:
In `@openmeter/ledger/routing_test.go`:
- Around line 240-248: Rename the test function
TestBuildroutingKeyV1_WithTaxBehaviorAndTaxCode to
TestBuildRoutingKeyV1_WithTaxBehaviorAndTaxCode (fix casing) and replace the
literal TaxBehavior("exclusive") with the package's typed const used elsewhere
(e.g. TaxBehaviorExclusive) when constructing the Route passed to
BuildRoutingKeyV1; this keeps naming consistent and uses the typed constant for
TaxBehavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: e6014707-5187-4e08-b0e7-110d9c6b5520

📥 Commits

Reviewing files that changed from the base of the PR and between 5f7ad28 and 6cb0134.

📒 Files selected for processing (7)
  • openmeter/ledger/account/adapter/subaccount.go
  • openmeter/ledger/accounts.go
  • openmeter/ledger/errors.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/primitives.go
  • openmeter/ledger/routing.go
  • openmeter/ledger/routing_test.go

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.

thx, if you don't mind I'd prefer if a bit more stuff was collected on the branch that actually do something (ik unpopular opinion)

Comment thread openmeter/ledger/routing.go Outdated
Comment thread openmeter/ledger/primitives.go Outdated
@mark-vass-konghq
Copy link
Copy Markdown
Contributor Author

thx, if you don't mind I'd prefer if a bit more stuff was collected on the branch that actually do something (ik unpopular opinion)

What changes needed to be done to not break our env?

@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 6cb0134 to 1427ce7 Compare May 6, 2026 07:33
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: 1

Caution

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

⚠️ Outside diff range comments (4)
openmeter/ledger/routing.go (2)

313-324: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🚨 This is the env-breaking bit Mark asked about.

Appending tax_behavior: to the V1 string changes the routing key value for every existing route, not just routes that opt into a TaxBehavior. Concretely:

  • Existing rows in ledger_sub_account_routes were stored with routing_key_version = "v1" and a value that has no tax_behavior: segment.
  • After this change, BuildRoutingKeyV1 for the same logical Route produces …|tax_behavior:null|….
  • resolveOrCreateRoute looks up by (namespace, account_id, routing_key_version, routing_key) — so post-deploy it will miss the existing row and try to insert a new one (or hit OnConflict with mismatched key set), and any code that resolves a sub-account from a previously persisted key will fail.

To avoid breaking deployed envs, please pick one of:

  1. Bump the routing key version (e.g., add RoutingKeyVersionV2) and keep BuildRoutingKeyV1 byte-identical to today. New routes get v2; existing v1 routes keep resolving. Best for a clean cutover.
  2. Backfill migration that recomputes & rewrites routing_key for all existing rows under v1 before the new code is live, gated to roll out atomically with the binary.
  3. Skip emitting the tax_behavior: segment entirely when TaxBehavior == nil (so existing routes hash identically), and only ever pin/normalize TaxBehavior into the key when explicitly set. This loses the "Some(nil) is meaningful" property in the key, but preserves it in RouteFilter.

Option 1 is the lowest-risk path and matches the version field's reason for existing.

🤖 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/routing.go` around lines 313 - 324, The change added a new
`tax_behavior:` segment to the V1 routing key which will break existing rows;
instead add a new version constant (e.g., RoutingKeyVersionV2) and implement
BuildRoutingKeyV2 that includes the `tax_behavior:` segment while leaving
BuildRoutingKeyV1 byte-identical to its current output, then switch
route-creation paths to emit RoutingKeyVersionV2/NewRoutingKey(..., value) for
new routes (keep resolveOrCreateRoute able to read v1 rows unchanged). Ensure
any helper called here (BuildRoutingKeyV1, BuildRoutingKeyV2, NewRoutingKey) are
updated so V1 serialization is not modified.

242-285: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add RouteFieldTaxBehavior case to sameRouteField in routingrules.go.

Your normalization refactor in routing.go correctly added TaxBehavior handling alongside the existing fast-path checks. However, the sameRouteField function in openmeter/ledger/routingrules/routingrules.go doesn't have a corresponding case for it. If a rule ever tries to compare TaxBehavior fields, it'll return an error ("unknown route field: tax_behavior").

Add the constant and switch case to keep them in sync:

Example fix pattern
const RouteFieldTaxBehavior RouteField = "tax_behavior"  // Add to enum around line 168

// In sameRouteField, add:
case RouteFieldTaxBehavior:
	return optionalTaxBehaviorEqual(left.TaxBehavior, right.TaxBehavior), nil
🤖 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/routing.go` around lines 242 - 285, Add a new RouteField
constant for TaxBehavior and handle it in sameRouteField: define
RouteFieldTaxBehavior as "tax_behavior" alongside the other RouteField
constants, then in the sameRouteField switch add a case RouteFieldTaxBehavior
that returns optionalTaxBehaviorEqual(left.TaxBehavior, right.TaxBehavior), nil;
ensure you reference the TaxBehavior field on the Route struct and the helper
optionalTaxBehaviorEqual in routingrules.go so comparisons for tax_behavior no
longer fall through to the unknown-field error.
openmeter/ledger/account/adapter/subaccount.go (2)

234-241: ⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Don't forget TaxBehavior in the read path.

Once the persistence wiring lands (see the comment on lines 82-92), this struct literal also needs TaxBehavior: dbRoute.TaxBehavior so hydrated routes match what was written. Without it, SubAccountData.Route.TaxBehavior is permanently nil.

🤖 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/account/adapter/subaccount.go` around lines 234 - 241, The
ledger.Route struct literal in the read/hydration path is missing TaxBehavior,
causing SubAccountData.Route.TaxBehavior to stay nil; update the Route
initialization (where ledger.Route is constructed from dbRoute) to include
TaxBehavior: dbRoute.TaxBehavior so the hydrated route mirrors what was
persisted (ensure the change is applied alongside the persistence wiring
referenced in the earlier comment).

82-92: ⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

Add tax_behavior field to LedgerSubAccountRoute persistence and wire it through all read/write paths.

BuildRoutingKeyV1 hashes TaxBehavior into the routing key, but the database schema has no tax_behavior column and the builder never calls SetNillableTaxBehavior(...). Two routes differing only by TaxBehavior end up with different keys but identical persisted rows — they look the same on read-back. When code recomputes a routing key from a hydrated route, it'll produce a different key than what's stored because Route.TaxBehavior is always nil, breaking the round-trip guarantee.

This needs:

  • Add tax_behavior field to the ent schema (with migration)
  • Call SetNillableTaxBehavior(normalizedRoute.TaxBehavior) in the Create builder (lines 82–92)
  • Read it back in MapSubAccountData when constructing the Route
  • Fix the same omission in openmeter/ledger/historical/adapter/ledger.go

Pair this fix with the routing-key version concern in routing.go before landing.

🤖 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/account/adapter/subaccount.go` around lines 82 - 92, Add a
nullable tax_behavior column to the ent schema and create the DB migration, then
wire TaxBehavior through persistence: in subaccount.go update the Create builder
for LedgerSubAccountRoute (the create variable built with
r.db.LedgerSubAccountRoute.Create()) to call
SetNillableTaxBehavior(normalizedRoute.TaxBehavior); update MapSubAccountData to
read the tax_behavior field into the Route struct when hydrating routes; apply
the same SetNillableTaxBehavior and read-back fix in
openmeter/ledger/historical/adapter/ledger.go so historical writes/reads include
TaxBehavior; finally ensure routing-key versioning in routing.go is addressed in
the same change set so routing keys remain consistent.
🧹 Nitpick comments (3)
openmeter/ledger/routing.go (1)

28-33: 💤 Low value

Quick thought on the type.

TaxBehavior is a string-backed enum like TransactionAuthorizationStatus — consistent and easy to reason about. One small consideration: there's no normalization (e.g., lower-casing) before Validate(), so "Inclusive" becomes invalid even though many APIs accept case-insensitive variants. If callers always use the constants, ignore this; if it ever flows in from user input or external systems, you'll want a Normalize/ParseTaxBehavior helper. Up to you.

🤖 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/routing.go` around lines 28 - 33, The TaxBehavior string
enum lacks normalization, so inputs like "Inclusive" will fail Validate(); add a
ParseTaxBehavior (or TaxBehavior.Normalize) helper that trims and lowercases the
incoming string and returns the corresponding TaxBehavior constant
(TaxBehaviorInclusive/TaxBehaviorExclusive) or an error for invalid values, and
update any usages (including the TaxBehavior.Validate method) to call this
parser/normalizer when accepting external or user-provided input.
openmeter/ledger/historical/adapter/sumentries_query.go (1)

124-131: 💤 Low value

Logic looks correct — minor DRY thought.

Predicate dispatch (TaxCode(*tc) vs TaxCodeIsNil()) lines up perfectly with the ent definitions, and the surrounding CostBasis block follows exactly the same shape. Since the same pattern now lives here and in openmeter/ledger/account/adapter/subaccount.go (lines 166-173), it might be nice to extract a tiny generic helper like:

♻️ Optional helper sketch
// optionalEqOrIsNil returns the predicate for an mo.Option[*T] filter
// that supports absent / Some(nil) / Some(&v) semantics.
func optionalEqOrIsNil[T any, P ~func(*sql.Selector)](
    opt mo.Option[*T],
    eq func(T) P,
    isNil func() P,
) (P, bool) {
    if !opt.IsPresent() {
        var zero P
        return zero, false
    }
    v, _ := opt.Get()
    if v != nil {
        return eq(*v), true
    }
    return isNil(), true
}

Totally optional — happy to skip if you prefer to keep the call sites explicit.

🤖 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/historical/adapter/sumentries_query.go` around lines 124 -
131, The duplicated pattern for handling mo.Option[*T] filters
(present/Some(nil)/Some(&v)) in the TaxCode and CostBasis predicate building
should be extracted into a small generic helper (suggested name
optionalEqOrIsNil) that returns a predicate and a bool indicating presence;
implement optionalEqOrIsNil[T any, P ~func(*sql.Selector)](opt mo.Option[*T], eq
func(T) P, isNil func() P) and replace the duplicated blocks in the functions
that append ledgersubaccountroutedb.TaxCode/TaxCodeIsNil and the analogous
CostBasis predicates to call the helper and append the returned predicate when
present.
openmeter/ledger/routing_test.go (1)

116-228: ⚡ Quick win

Solid coverage on the new tax-routing surface. 🎯

Nice spread across: TaxBehavior.Validate, Route.Validate rejecting bogus values, RouteFilter.Normalize preserving Some(&v) and Some(nil), absent-stays-absent fast path, and Route.Filter() pinning semantics. The Some(nil) round-trip case in particular is exactly what the new option semantics need to be locked down with.

One follow-up to consider after the persistence/version concerns from routing.go and subaccount.go are addressed: an in-code integration test that round-trips a Route with TaxBehavior through EnsureSubAccountGetSubAccountByID would catch the read/write gap if it ever regresses. Not blocking on this PR.

🤖 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/routing_test.go` around lines 116 - 228, Add an integration
test that round-trips a Route containing a non-nil TaxBehavior through your
persistence layer by calling EnsureSubAccount (to write) then GetSubAccountByID
(to read) and asserting the returned SubAccount/Route still has the same
TaxBehavior value (and nil vs present semantics); target the Route struct with a
pinned TaxBehavior value (e.g., TaxBehaviorInclusive), use EnsureSubAccount and
GetSubAccountByID to perform the write/read, and assert equality of the
TaxBehavior pointer/presence to detect any serialization/versioning regressions.
🤖 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/ledger/routing_test.go`:
- Around line 240-248: Rename the test function
TestBuildroutingKeyV1_WithTaxBehaviorAndTaxCode to
TestBuildRoutingKeyV1_WithTaxBehaviorAndTaxCode (capitalize the "r" to match
other tests) and replace the string literal TaxBehavior("exclusive") with the
constant TaxBehaviorExclusive used elsewhere; update the Route construction in
TestBuildRoutingKeyV1_WithTaxBehaviorAndTaxCode to use
lo.ToPtr(TaxBehaviorExclusive) so the test name and the TaxBehavior value are
consistent with the rest of the file.

---

Outside diff comments:
In `@openmeter/ledger/account/adapter/subaccount.go`:
- Around line 234-241: The ledger.Route struct literal in the read/hydration
path is missing TaxBehavior, causing SubAccountData.Route.TaxBehavior to stay
nil; update the Route initialization (where ledger.Route is constructed from
dbRoute) to include TaxBehavior: dbRoute.TaxBehavior so the hydrated route
mirrors what was persisted (ensure the change is applied alongside the
persistence wiring referenced in the earlier comment).
- Around line 82-92: Add a nullable tax_behavior column to the ent schema and
create the DB migration, then wire TaxBehavior through persistence: in
subaccount.go update the Create builder for LedgerSubAccountRoute (the create
variable built with r.db.LedgerSubAccountRoute.Create()) to call
SetNillableTaxBehavior(normalizedRoute.TaxBehavior); update MapSubAccountData to
read the tax_behavior field into the Route struct when hydrating routes; apply
the same SetNillableTaxBehavior and read-back fix in
openmeter/ledger/historical/adapter/ledger.go so historical writes/reads include
TaxBehavior; finally ensure routing-key versioning in routing.go is addressed in
the same change set so routing keys remain consistent.

In `@openmeter/ledger/routing.go`:
- Around line 313-324: The change added a new `tax_behavior:` segment to the V1
routing key which will break existing rows; instead add a new version constant
(e.g., RoutingKeyVersionV2) and implement BuildRoutingKeyV2 that includes the
`tax_behavior:` segment while leaving BuildRoutingKeyV1 byte-identical to its
current output, then switch route-creation paths to emit
RoutingKeyVersionV2/NewRoutingKey(..., value) for new routes (keep
resolveOrCreateRoute able to read v1 rows unchanged). Ensure any helper called
here (BuildRoutingKeyV1, BuildRoutingKeyV2, NewRoutingKey) are updated so V1
serialization is not modified.
- Around line 242-285: Add a new RouteField constant for TaxBehavior and handle
it in sameRouteField: define RouteFieldTaxBehavior as "tax_behavior" alongside
the other RouteField constants, then in the sameRouteField switch add a case
RouteFieldTaxBehavior that returns optionalTaxBehaviorEqual(left.TaxBehavior,
right.TaxBehavior), nil; ensure you reference the TaxBehavior field on the Route
struct and the helper optionalTaxBehaviorEqual in routingrules.go so comparisons
for tax_behavior no longer fall through to the unknown-field error.

---

Nitpick comments:
In `@openmeter/ledger/historical/adapter/sumentries_query.go`:
- Around line 124-131: The duplicated pattern for handling mo.Option[*T] filters
(present/Some(nil)/Some(&v)) in the TaxCode and CostBasis predicate building
should be extracted into a small generic helper (suggested name
optionalEqOrIsNil) that returns a predicate and a bool indicating presence;
implement optionalEqOrIsNil[T any, P ~func(*sql.Selector)](opt mo.Option[*T], eq
func(T) P, isNil func() P) and replace the duplicated blocks in the functions
that append ledgersubaccountroutedb.TaxCode/TaxCodeIsNil and the analogous
CostBasis predicates to call the helper and append the returned predicate when
present.

In `@openmeter/ledger/routing_test.go`:
- Around line 116-228: Add an integration test that round-trips a Route
containing a non-nil TaxBehavior through your persistence layer by calling
EnsureSubAccount (to write) then GetSubAccountByID (to read) and asserting the
returned SubAccount/Route still has the same TaxBehavior value (and nil vs
present semantics); target the Route struct with a pinned TaxBehavior value
(e.g., TaxBehaviorInclusive), use EnsureSubAccount and GetSubAccountByID to
perform the write/read, and assert equality of the TaxBehavior pointer/presence
to detect any serialization/versioning regressions.

In `@openmeter/ledger/routing.go`:
- Around line 28-33: The TaxBehavior string enum lacks normalization, so inputs
like "Inclusive" will fail Validate(); add a ParseTaxBehavior (or
TaxBehavior.Normalize) helper that trims and lowercases the incoming string and
returns the corresponding TaxBehavior constant
(TaxBehaviorInclusive/TaxBehaviorExclusive) or an error for invalid values, and
update any usages (including the TaxBehavior.Validate method) to call this
parser/normalizer when accepting external or user-provided input.
🪄 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: bb9a8da1-0baa-443f-a9c3-cd7dcf836c6e

📥 Commits

Reviewing files that changed from the base of the PR and between 6cb0134 and 1427ce7.

📒 Files selected for processing (7)
  • openmeter/ledger/account/adapter/subaccount.go
  • openmeter/ledger/accounts.go
  • openmeter/ledger/errors.go
  • openmeter/ledger/historical/adapter/sumentries_query.go
  • openmeter/ledger/primitives.go
  • openmeter/ledger/routing.go
  • openmeter/ledger/routing_test.go

Comment thread openmeter/ledger/routing_test.go Outdated
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch 2 times, most recently from 3640692 to 619e85f Compare May 6, 2026 15:53
@mark-vass-konghq mark-vass-konghq requested a review from GAlexIHU May 6, 2026 16:00
Comment thread openmeter/ent/schema/ledger_account.go
Comment thread openmeter/ledger/transactions/testenv_test.go
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 619e85f to 6524242 Compare May 11, 2026 14:30
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 6524242 to 6c21be8 Compare May 11, 2026 14:30
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.

ai was (not) (ab)used when writing these comments

Comment thread test/credits/sanity_test.go
Comment thread openmeter/ledger/chargeadapter/usagebased.go
Comment thread test/credits/sanity_test.go
Comment thread openmeter/ledger/transactions/earnings.go
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 7508a11 to a0430cf Compare May 19, 2026 08:22
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from a0430cf to 9b9a156 Compare May 19, 2026 08:27
@mark-vass-konghq mark-vass-konghq requested a review from GAlexIHU May 19, 2026 08:27
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 9b9a156 to 805d185 Compare May 19, 2026 08:43
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 805d185 to c6826e2 Compare May 19, 2026 09:39
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.

I think you forgot about TaxBehavior. I didn't go to find the exact lines in sanity where you miss asserting for it, but its missed. Please add & fix

At time.Time
Amount alpacadecimal.Decimal
Currency currencyx.Code
TaxCode *string
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.

wouldn't you also need TaxBehavior?

Comment thread openmeter/ledger/transactions/accrual.go Outdated
@mark-vass-konghq mark-vass-konghq force-pushed the feat/add-tax-code-and-tax-behavior-to-ledger-types branch from 1133d75 to 911d360 Compare May 19, 2026 14:14
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