Skip to content

refactor: split lines #2961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Jun 10, 2025
Merged

refactor: split lines #2961

merged 8 commits into from
Jun 10, 2025

Conversation

turip
Copy link
Contributor

@turip turip commented Jun 7, 2025

Overview

Previously, the line object was used for 4 distinct reasons:

  • Invoice level flat fee lines (now deprecated)
  • Split lines put onto the gathering invoice (as grouping)
  • Usage-based lines
  • Detailed lines

This patch moves the split lines from the gathering invoice into a separate entity. This is required as this will allow us to:

  • Have separate types for detailed and usage based lines (so that we don't have to do assertions all around the code with Status == 'valid'/'detailed' etc).
  • Have dedicated table for those (so that we don't have a lot of nullable fields just because the table describes generic behavior)
  • Get rid of the Parent relation that makes marshaling tricky and justifies the *Line pointer handling (e.g. we don't need to build the line tree in memory anymore).
  • We can close the gap between the API and the backend representation (as this duality is quire errorprone)

The existing tests are passing, the only test removed is the adapter test that handled split line updates.

The migration is validated using the SQLc based fixture setup to ensure that we are correctly migrating to the new setup.

Summary by CodeRabbit

  • New Features

    • Introduced support for split line groups in billing, enabling hierarchical grouping of usage-based invoice lines.
    • Added CRUD operations for split line groups and enhanced invoice line fetching to return individual lines or grouped hierarchies.
    • Added validation and aggregation methods for split line groups, including service period and discount handling.
  • Bug Fixes

    • Improved validation to detect overlapping child references in split line groups.
    • Fixed handling of split lines in invoice recalculations and detailed line processing.
  • Refactor

    • Replaced progressive billing parent/child model with split line group hierarchy.
    • Updated interfaces, services, and internal logic to use split line group IDs and hierarchies.
    • Simplified invoice line retrieval, patch generation, and test code to align with split line groups.
    • Removed obsolete split line status and related code.
  • Database Migration

    • Migrated existing split invoice lines to split line groups, removing split line statuses and updating schema constraints.
  • Documentation

    • Enhanced migration and test documentation to reflect split line group changes.
  • Tests

    • Added tests for split line group migration and hierarchy validation.
    • Updated existing tests to use split line group structures.
    • Removed tests related to the old line splitting model.

Copy link

coderabbitai bot commented Jun 7, 2025

📝 Walkthrough

Walkthrough

This change removes the legacy "split" invoice line status and parent/child split line model, replacing it with a new "split line group" structure for usage-based billing lines. It introduces new domain types, interfaces, service methods, adapter logic, and migration scripts to support split line groups and their hierarchies. Corresponding updates are made to code, tests, and database schema to use split line groups, with removals of the old progressive/split line logic.

Changes

Files / Group Change Summary
openmeter/billing/adapter.go, openmeter/billing/service.go Extended Adapter and Service interfaces to add split line group support; updated method signatures to use LineOrHierarchy.
openmeter/billing/adapter/invoice.go, openmeter/billing/adapter/invoicelinemapper.go Updated invoice line expansion logic to use split line hierarchies; mapped new SplitLineGroupID field.
openmeter/billing/adapter/invoicelineprogressive.go Removed all legacy progressive/split line hierarchy logic and types.
openmeter/billing/adapter/invoicelines.go Added split line group support in upsert and fetch; changed return type to LineOrHierarchy; removed split child update restriction.
openmeter/billing/adapter/invoicelinesplitgroup.go New: Implements CRUD and expansion logic for split line groups and hierarchies.
openmeter/billing/discount.go Changed ValidateForPrice to accept productcatalog.Price.
openmeter/billing/httpdriver/invoiceline.go Updated split line checks to use SplitLineGroupID instead of parent/status.
openmeter/billing/invoice.go Removed SplitLines field and setter from InvoiceExpand.
openmeter/billing/invoiceline.go Removed split status, parent fields, and progressive hierarchy; added SplitLineGroupID, SplitLineHierarchy, and validation for subscription reference.
openmeter/billing/invoicelinesplitgroup.go New: Defines split line group, hierarchy, aggregation, and LineOrHierarchy types and validation methods.
openmeter/billing/service/invoice.go Removed split line filtering and expansion logic.
openmeter/billing/service/invoicecalc/details.go Now processes split lines in totals and details calculations.
openmeter/billing/service/invoiceline.go Changed return type to LineOrHierarchy; wrapped adapter call in transaction.
openmeter/billing/service/invoicelinesplitgroup.go New: Service methods for split line group CRUD and retrieval.
openmeter/billing/service/lineservice/commitments.go, openmeter/billing/service/lineservice/discountusage.go, openmeter/billing/service/lineservice/pricer.go Updated all logic to use split line groups and hierarchies instead of parent/progressive split lines.
openmeter/billing/service/lineservice/linebase.go Refactored split logic to use split line groups; removed parent/split status; updated interface and methods.
openmeter/billing/service/lineservice/meters.go Removed parent line references; now uses split line hierarchy for split context.
openmeter/billing/service/lineservice/usagebasedline.go Stopped passing parent line to feature usage input.
openmeter/billing/service/lineservice/usagebasedline_test.go Refactored test setup to use split line groups/hierarchies instead of parent/progressive.
openmeter/billing/worker/subscription/feehelper.go Added generic setIfDoesNotEqual helper.
openmeter/billing/worker/subscription/invoiceupdate.go New: Implements patch application logic for lines and split line groups, handling mutable/immutable invoices.
openmeter/billing/worker/subscription/patch.go Refactored patch types to support split line group operations; added delete patch generation for hierarchies.
openmeter/billing/worker/subscription/sync.go Refactored sync logic to use LineOrHierarchy; modularized patch generation and application.
openmeter/billing/worker/subscription/sync_test.go Updated tests to assert split line hierarchies instead of split lines by status.
openmeter/ent/db/billinginvoiceline/billinginvoiceline.go, openmeter/ent/db/migrate/schema.go Removed "split" status from allowed enum values for invoice lines.
openmeter/ent/db/billinginvoicesplitlinegroup_create.go, openmeter/ent/db/billinginvoicesplitlinegroup_update.go, openmeter/ent/db/setorclear.go Removed all setters and updaters for unique_reference_id on split line groups.
openmeter/ent/schema/billing.go Made unique_reference_id immutable for split line groups.
openmeter/productcatalog/tax.go Added Clone methods for TaxConfig and StripeTaxConfig.
openmeter/server/server_test.go Updated NoopBillingService to new interface and return types; added split line group methods.
test/billing/adapter_test.go Removed legacy split line test.
test/billing/discount_test.go, test/billing/tax_test.go Updated tests to use split line hierarchies and group IDs.
test/billing/invoice_test.go Refactored helpers and assertions to use split line groups and hierarchies.
tools/migrate/billing_test.go Added migration test for split line group migration.
tools/migrate/migrations/20250609204117_billing-migrate-split-line-groups.up.sql Migration: Moves split lines to split line groups, updates references, deletes old split lines.
tools/migrate/testdata/billing/README.md Documented schema dump command for test data.
tools/migrate/testdata/billing/removesplitlines/db/db.go, models.go, queries.sql.go New: sqlc-generated DB access and models for migration test.
tools/migrate/testdata/billing/removesplitlines/fixture.sql, sqlc.yaml, sqlc/db-schema.sql, sqlc/queries.sql New: Fixture data, SQLC config, schema, and queries for migration testing.

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (1.64.8)

Error: you are using a configuration file for golangci-lint v2 with golangci-lint v1: please use golangci-lint v2
Failed executing command with error: you are using a configuration file for golangci-lint v2 with golangci-lint v1: please use golangci-lint v2


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5cfe0c8 and a56af23.

📒 Files selected for processing (2)
  • openmeter/billing/invoicelinesplitgroup.go (1 hunks)
  • openmeter/billing/worker/subscription/invoiceupdate.go (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • openmeter/billing/worker/subscription/invoiceupdate.go
  • openmeter/billing/invoicelinesplitgroup.go
⏰ Context from checks skipped due to timeout of 90000ms (11)
  • GitHub Check: Quickstart
  • GitHub Check: E2E
  • GitHub Check: Analyze (go)
  • GitHub Check: CI
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Build
  • GitHub Check: Developer environment
  • GitHub Check: Commit hooks
  • GitHub Check: Test
  • GitHub Check: Lint
  • GitHub Check: Migration Checks
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@turip turip force-pushed the refactor/split-lines-v2 branch from a822096 to 6c2adcd Compare June 7, 2025 21:42
@turip turip changed the base branch from main to refactor/split-lines-v2-ddl June 7, 2025 21:43
@turip turip force-pushed the refactor/split-lines-v2 branch 5 times, most recently from 9153163 to 39f9d3c Compare June 9, 2025 06:09
@turip turip force-pushed the refactor/split-lines-v2-ddl branch from a83f392 to 9871466 Compare June 9, 2025 10:50
@turip turip force-pushed the refactor/split-lines-v2 branch 4 times, most recently from 069387e to fc1d56e Compare June 9, 2025 12:49
@turip turip added release-note/misc Miscellaneous changes area/billing labels Jun 9, 2025
@turip turip mentioned this pull request Jun 9, 2025
@turip turip marked this pull request as ready for review June 9, 2025 12:57
@turip turip requested a review from a team as a code owner June 9, 2025 12:57
hekike
hekike previously approved these changes Jun 9, 2025
Base automatically changed from refactor/split-lines-v2-ddl to main June 9, 2025 20:09
@turip turip dismissed hekike’s stale review June 9, 2025 20:09

The base branch was changed.

@turip turip force-pushed the refactor/split-lines-v2 branch from fc1d56e to fa40904 Compare June 9, 2025 21:15
@turip turip force-pushed the refactor/split-lines-v2 branch from fa40904 to 5cfe0c8 Compare June 9, 2025 21:21
@turip turip requested a review from hekike June 9, 2025 21:22
Copy link

@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

Inline review comments failed to post. This is likely due to GitHub's limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 7

🔭 Outside diff range comments (2)
tools/migrate/testdata/billing/removesplitlines/sqlc/queries.sql (1)

11-15: ⚠️ Potential issue

Add missing semicolon to SQL query.

The CountLinesByStatusType query is missing a semicolon at the end.

 SELECT status, type, count(*)
     FROM public.billing_invoice_lines
-    GROUP BY status, type;
+    GROUP BY status, type;
openmeter/billing/service/lineservice/linebase.go (1)

176-212: 🛠️ Refactor suggestion

Address the design concern about mutation vs cloning

The TODO comment raises a valid concern about the Update method mutating the receiver while returning it as if it's a new object. This could lead to confusion about ownership and side effects.

Consider either:

  1. Making this method clearly mutate in-place and return nothing, or
  2. Cloning the line before updating and returning the clone

For consistency with immutable patterns:

 func (l lineBase) Update(in UpdateInput) Line {
-	// TODO[later]: Either we should clone and update the clone or we should not return the Line as if that's a new
-	// object.
+	// Clone to maintain immutability
+	clonedLine := l.line.CloneWithoutDependencies()
+	clonedLine.ID = l.line.ID
+	clonedLine.CreatedAt = l.line.CreatedAt
+	clonedLine.UpdatedAt = l.line.UpdatedAt
 
-	if !in.PeriodStart.IsZero() {
-		l.line.Period.Start = in.PeriodStart
-	}
+	if !in.PeriodStart.IsZero() {
+		clonedLine.Period.Start = in.PeriodStart
+	}
 	// ... apply other updates to clonedLine ...
 
 	// Let's ignore the error here as we don't allow for any type updates
-	svc, _ := l.service.FromEntity(l.line)
+	svc, _ := l.service.FromEntity(clonedLine)
 
 	return svc
 }
♻️ Duplicate comments (1)
openmeter/billing/invoicelinesplitgroup.go (1)

285-340: Consider interface-based design for LineOrHierarchy

The struct-based discriminated union pattern works, but an interface-based approach might provide better extensibility and clearer API boundaries.

🧹 Nitpick comments (12)
openmeter/billing/service/lineservice/pricer.go (1)

66-74: Update comment to reflect new split line group terminology

The logic change from ParentLineID to SplitLineGroupID correctly maintains the invoicing restriction, but the comment on line 66 still references "parent line" terminology.

Consider updating the comment to reflect the new split line group model:

-	// Invoicing a line that has a parent line is not supported, as that's a progressive billing use-case
+	// Invoicing a line that belongs to a split line group is not supported, as that's a progressive billing use-case
openmeter/billing/service/lineservice/commitments.go (2)

40-46: Update error message terminology for consistency.

The logic correctly uses the new split line group model, but the error message still references "progressive billed line" instead of "split line group".

-				return pricerResult, fmt.Errorf("line[%s] does not have a split line hierarchy, but is a progressive billed line", i.line.ID)
+				return pricerResult, fmt.Errorf("line[%s] does not have a split line hierarchy, but has a split line group ID", i.line.ID)

99-114: Update error message terminology and approve the changes.

The function correctly uses the new split line hierarchy model. However, the error message should be updated for consistency.

-		return alpacadecimal.Zero, fmt.Errorf("line[%s] does not have a progressive line hierarchy, but is a progressive billed line", l.line.ID)
+		return alpacadecimal.Zero, fmt.Errorf("line[%s] does not have a split line hierarchy, but has a split line group ID", l.line.ID)
openmeter/billing/service/lineservice/discountusage.go (1)

130-131: Update error message to reflect the new terminology.

The error message still mentions "progressive billed line" which is inconsistent with the new split line group model.

-		return alpacadecimal.Zero, errors.New("no line hierarchy is available for a progressive billed line")
+		return alpacadecimal.Zero, errors.New("no line hierarchy is available for a split line")
openmeter/billing/service/invoicelinesplitgroup.go (1)

19-29: Fix grammatical error in comment.

The possessive form should be "its" not "it's".

-		// Let's load the split line group and validate that all of it's children are also deleted
+		// Let's load the split line group and validate that all of its children are also deleted

Also applies to line 29:

-		// Let's validate that all of it's children are also deleted
+		// Let's validate that all of its children are also deleted
openmeter/billing/adapter/invoicelines.go (2)

141-143: Track the TODO for nullable field handling.

The TODO indicates that all nullable fields must be listed explicitly in the upsert operation. This could lead to bugs if new nullable fields are added to the schema without updating this code.

Would you like me to open an issue to track implementing a more robust solution that automatically handles all nullable fields?


615-617: Enhance error message with specific overlapping IDs.

The error message could be more helpful by including which entities have the overlapping IDs.

-		return nil, fmt.Errorf("overlapping childUniqueReferenceID: %v", overlappingChildUniqueReferenceIDs)
+		return nil, fmt.Errorf("found overlapping childUniqueReferenceID between split line groups and individual lines: %v", overlappingChildUniqueReferenceIDs)
test/billing/invoice_test.go (1)

922-939: Fix typo in parameter name.

The function logic is correct and properly implements the split line group model, but there's a typo in the parameter name.

-func (s *InvoicingTestSuite) lineInSameSplitLineGroup(lines []*billing.Line, shiblingLineID string) *billing.Line {
+func (s *InvoicingTestSuite) lineInSameSplitLineGroup(lines []*billing.Line, siblingLineID string) *billing.Line {
 	s.T().Helper()
 
 	for _, line := range lines {
 		if line.SplitLineHierarchy == nil {
 			continue
 		}
 
 		for _, child := range line.SplitLineHierarchy.Lines {
-			if child.Line.ID == shiblingLineID {
+			if child.Line.ID == siblingLineID {
 				return line
 			}
 		}
 	}
tools/migrate/testdata/billing/removesplitlines/db/queries.sql.go (2)

29-50: Remove redundant rows.Close() call.

The defer rows.Close() statement on line 34 already ensures the rows are closed. The explicit call on line 43 is redundant.

 	}
-	if err := rows.Close(); err != nil {
-		return nil, err
-	}
 	if err := rows.Err(); err != nil {

132-192: Remove redundant rows.Close() call.

Similar to the previous function, the defer rows.Close() statement already ensures cleanup.

 	}
-	if err := rows.Close(); err != nil {
-		return nil, err
-	}
 	if err := rows.Err(); err != nil {
openmeter/billing/adapter/invoicelinesplitgroup.go (1)

223-225: Update outdated comment referencing progressive line hierarchy.

The comment still references the old "progressive line hierarchy" model. Update it to reflect the new split line hierarchy model.

-// expandSplitLineHierarchy expands the given lines with their progressive line hierarchy
+// expandSplitLineHierarchy expands the given lines with their split line hierarchy
 // This is done by fetching all the lines that are children of the given lines parent lines and then building
 // the hierarchy.
openmeter/billing/worker/subscription/invoiceupdate.go (1)

205-207: Improve error message clarity for missing child reference ID.

The current error message could be confusing when ChildUniqueReferenceID is nil, as it will display the string "nil" rather than indicating absence.

-				return fmt.Errorf("line[%s/%s] not found in the invoice, cannot update", targetState.ID, lo.FromPtrOr(targetState.ChildUniqueReferenceID, "nil"))
+				return fmt.Errorf("line[%s] not found in the invoice, cannot update", targetState.ID)

Alternatively, if the child reference ID is important for debugging:

if targetState.ChildUniqueReferenceID != nil {
    return fmt.Errorf("line[%s/%s] not found in the invoice, cannot update", targetState.ID, *targetState.ChildUniqueReferenceID)
}
return fmt.Errorf("line[%s] not found in the invoice, cannot update", targetState.ID)
🛑 Comments failed to post (7)
tools/migrate/testdata/billing/README.md (1)

15-15: ⚠️ Potential issue

Fix typo and verify command syntax.

There's a typo: "specifiy" should be "specify". Additionally, the command appears to mix -n (schema names) and -t (table names) flags, which may not work as intended.

Apply this diff to fix the typo and clarify the command:

-  - You can also use the `-n` flag to specifiy individual schemas: `pg_dump -Ox -s -t '*billing*' 'postgres://pgtdbuser:[email protected]:5432/testdb_tpl_92d6cc3e2b7979388fd8f7b12aad9c7b_inst_4710adae?sslmode=disable'`
+  - You can also use the `-n` flag to specify individual schemas: `pg_dump -Ox -s -n '*billing*' 'postgres://pgtdbuser:[email protected]:5432/testdb_tpl_92d6cc3e2b7979388fd8f7b12aad9c7b_inst_4710adae?sslmode=disable'`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  - You can also use the `-n` flag to specify individual schemas: `pg_dump -Ox -s -n '*billing*' 'postgres://pgtdbuser:[email protected]:5432/testdb_tpl_92d6cc3e2b7979388fd8f7b12aad9c7b_inst_4710adae?sslmode=disable'`
🤖 Prompt for AI Agents
In tools/migrate/testdata/billing/README.md at line 15, fix the typo by changing
"specifiy" to "specify". Also, correct the command by using the `-n` flag
properly for specifying schemas instead of mixing it with the `-t` flag for
tables. Update the example command to use `-n '*billing*'` if the intention is
to filter schemas, ensuring the syntax matches pg_dump's expected usage.
tools/migrate/migrations/20250609204117_billing-migrate-split-line-groups.up.sql (2)

53-55: ⚠️ Potential issue

Add validation to ensure parent_line_id references a valid split line.

The migration assumes that parent_line_id points to a split line, but doesn't verify this. This could lead to data integrity issues if the assumption is incorrect.

Consider adding a validation step or JOIN condition to ensure the referenced parent line exists and has the expected status:

 UPDATE public.billing_invoice_lines
-SET split_line_group_id = parent_line_id, parent_line_id = NULL
-WHERE "type" = 'usage_based' and "status" = 'valid' and "parent_line_id" IS NOT NULL;
+SET split_line_group_id = parent_line_id, parent_line_id = NULL
+WHERE "type" = 'usage_based' 
+  AND "status" = 'valid' 
+  AND "parent_line_id" IS NOT NULL
+  AND EXISTS (
+    SELECT 1 FROM public.billing_invoice_split_line_groups 
+    WHERE id = billing_invoice_lines.parent_line_id
+  );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

UPDATE public.billing_invoice_lines
SET split_line_group_id = parent_line_id, parent_line_id = NULL
WHERE "type" = 'usage_based'
  AND "status" = 'valid'
  AND "parent_line_id" IS NOT NULL
  AND EXISTS (
    SELECT 1
    FROM public.billing_invoice_split_line_groups
    WHERE id = billing_invoice_lines.parent_line_id
  );
🤖 Prompt for AI Agents
In
tools/migrate/migrations/20250609204117_billing-migrate-split-line-groups.up.sql
around lines 53 to 55, the UPDATE statement sets split_line_group_id based on
parent_line_id without verifying that parent_line_id references a valid split
line. To fix this, modify the UPDATE to include a JOIN or EXISTS clause that
ensures parent_line_id points to a line with the expected status and type,
thereby validating the reference before updating to maintain data integrity.

1-60: ⚠️ Potential issue

Wrap migration in a transaction for atomicity.

The migration performs three critical steps without transaction wrapping. If any step fails, the database could be left in an inconsistent state.

Consider wrapping the entire migration in a transaction:

+BEGIN;
+
 -- Step 1: convert existing split lines into split line groups
 
 INSERT INTO public.billing_invoice_split_line_groups
 ...
 
 -- Step 3: delete the split lines
 
 DELETE FROM public.billing_invoice_lines
 WHERE "type" = 'usage_based' and "status" = 'split';
+
+COMMIT;

Additionally, consider adding a corresponding .down.sql file for rollback capability.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

BEGIN;

-- Step 1: convert existing split lines into split line groups

INSERT INTO public.billing_invoice_split_line_groups
(
    id,
    namespace,
    metadata,
    created_at,
    updated_at,
    deleted_at,
    name,
    description,
    service_period_start,
    service_period_end,
    currency,
    tax_config,
    unique_reference_id,
    ratecard_discounts,
    feature_key,
    price,
    subscription_id,
    subscription_phase_id,
    subscription_item_id
)
    SELECT
      l.id,
      l.namespace,
      l.metadata,
      l.created_at,
      l.updated_at,
      l.deleted_at,
      l.name,
      l.description,
      l.period_start,
      l.period_end,
      l.currency,
      l.tax_config,
      l.child_unique_reference_id,
      l.ratecard_discounts,
      u.feature_key,
      u.price,
      l.subscription_id,
      l.subscription_phase_id,
      l.subscription_item_id
    FROM
        public.billing_invoice_lines l
        JOIN public.billing_invoice_usage_based_line_configs u
          ON l.usage_based_line_config_id = u.id
    WHERE
        l.type = 'usage_based'
        AND l.status = 'split';

-- Step 2: Associate existing lines referencing the split lines to the split line group

UPDATE public.billing_invoice_lines
SET split_line_group_id = parent_line_id,
    parent_line_id       = NULL
WHERE "type"   = 'usage_based'
  AND "status" = 'valid'
  AND parent_line_id IS NOT NULL;

-- Step 3: delete the split lines

DELETE FROM public.billing_invoice_lines
WHERE "type"   = 'usage_based'
  AND "status" = 'split';

COMMIT;
🤖 Prompt for AI Agents
In
tools/migrate/migrations/20250609204117_billing-migrate-split-line-groups.up.sql
lines 1 to 60, the migration executes multiple critical steps without wrapping
them in a transaction, risking partial application and inconsistent database
state if a failure occurs. Wrap the entire migration script in a BEGIN and
COMMIT transaction block to ensure atomicity. Additionally, create a
corresponding down migration script to allow rollback of these changes if
needed.
openmeter/billing/service.go (1)

52-55: ⚠️ Potential issue

Missing GetSplitLineGroup method

According to the AI summary, the SplitLineGroupService interface should include a GetSplitLineGroup method, but it's not present here. This method would be needed for retrieving split line group details.

Consider adding:

GetSplitLineGroup(ctx context.Context, input GetSplitLineGroupInput) (SplitLineGroup, error)
🤖 Prompt for AI Agents
In openmeter/billing/service.go around lines 52 to 55, the SplitLineGroupService
interface is missing the GetSplitLineGroup method required for retrieving split
line group details. Add the method signature GetSplitLineGroup(ctx
context.Context, input GetSplitLineGroupInput) (SplitLineGroup, error) to the
interface to provide this functionality.
openmeter/billing/invoiceline.go (1)

305-307: 💡 Verification agent

🧩 Analysis chain

JSON tag inconsistency with field name.

The field name changed from ProgressiveLineHierarchy to SplitLineHierarchy, but the JSON tag remains progressiveLineHierarchy. This inconsistency could cause confusion and might indicate a backward compatibility concern that should be documented.


🏁 Script executed:

#!/bin/bash
# Description: Check if there are any API clients or tests that depend on the 'progressiveLineHierarchy' JSON field name

# Search for references to the progressiveLineHierarchy JSON field in test files and API docs
rg -A 3 -B 3 'progressiveLineHierarchy' --type go --type json --type yaml --type md

Length of output: 589


Document backward‐compatibility of JSON tag

The SplitLineHierarchy field intentionally keeps the json:"progressiveLineHierarchy,omitempty" tag to preserve API compatibility with existing clients. To avoid confusion, please add a comment noting the legacy name.

• File: openmeter/billing/invoiceline.go
• Lines: ~305

Suggested diff:

-   SplitLineHierarchy *SplitLineHierarchy `json:"progressiveLineHierarchy,omitempty"`
+   // SplitLineHierarchy was renamed from ProgressiveLineHierarchy;
+   // JSON tag is preserved for backward compatibility with existing clients.
+   SplitLineHierarchy *SplitLineHierarchy `json:"progressiveLineHierarchy,omitempty"`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

 	Children           LineChildren        `json:"children,omitempty"`
 	ParentLine         *Line               `json:"parent,omitempty"`
+	// SplitLineHierarchy was renamed from ProgressiveLineHierarchy;
+	// JSON tag is preserved for backward compatibility with existing clients.
 	SplitLineHierarchy *SplitLineHierarchy `json:"progressiveLineHierarchy,omitempty"`
🤖 Prompt for AI Agents
In openmeter/billing/invoiceline.go around lines 305 to 307, the JSON tag for
the SplitLineHierarchy field is still "progressiveLineHierarchy" to maintain
backward compatibility, but this is inconsistent with the field name. Add a
comment above this field explaining that the JSON tag uses the legacy name
intentionally to preserve API compatibility and avoid confusion for future
maintainers.
openmeter/billing/invoicelinesplitgroup.go (2)

173-183: ⚠️ Potential issue

Missing NamespacedID in Clone method

The Clone method is missing the NamespacedID field, which could lead to incorrect behavior when cloning split line groups.

 func (i SplitLineGroup) Clone() SplitLineGroup {
 	return SplitLineGroup{
 		ManagedModel:                i.ManagedModel,
+		NamespacedID:                i.NamespacedID,
 		SplitLineGroupMutableFields: i.SplitLineGroupMutableFields.Clone(),
 		Price:                       i.Price,
 		FeatureKey:                  i.FeatureKey,
 		Subscription:                i.Subscription,
 		Currency:                    i.Currency,
 		UniqueReferenceID:           i.UniqueReferenceID,
 	}
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func (i SplitLineGroup) Clone() SplitLineGroup {
	return SplitLineGroup{
		ManagedModel:                i.ManagedModel,
		NamespacedID:                i.NamespacedID,
		SplitLineGroupMutableFields: i.SplitLineGroupMutableFields.Clone(),
		Price:                       i.Price,
		FeatureKey:                  i.FeatureKey,
		Subscription:                i.Subscription,
		Currency:                    i.Currency,
		UniqueReferenceID:           i.UniqueReferenceID,
	}
}
🤖 Prompt for AI Agents
In openmeter/billing/invoicelinesplitgroup.go around lines 173 to 183, the Clone
method is missing the NamespacedID field, which should be included to ensure the
cloned SplitLineGroup is complete and behaves correctly. Add the NamespacedID
field to the returned SplitLineGroup struct, assigning it from the original
instance.

74-112: ⚠️ Potential issue

Remove duplicate validation of RatecardDiscounts

The RatecardDiscounts are validated twice - once at line 81 through SplitLineGroupMutableFields.ValidateForPrice() and again at line 107.

 func (i SplitLineGroupCreate) Validate() error {
 	var errs []error
 
 	if i.Namespace == "" {
 		errs = append(errs, errors.New("namespace is required"))
 	}
 
 	if err := i.SplitLineGroupMutableFields.ValidateForPrice(i.Price); err != nil {
 		errs = append(errs, err)
 	}
 
 	if i.Price == nil {
 		errs = append(errs, errors.New("price is required"))
 	} else {
 		if err := i.Price.Validate(); err != nil {
 			errs = append(errs, err)
 		}
 	}
 
 	if i.Subscription != nil {
 		if err := i.Subscription.Validate(); err != nil {
 			errs = append(errs, err)
 		}
 	}
 
 	if i.Currency == "" {
 		errs = append(errs, errors.New("currency is required"))
 	}
 
 	if i.UniqueReferenceID != nil && *i.UniqueReferenceID == "" {
 		errs = append(errs, errors.New("unique reference id is required"))
 	}
 
-	if err := i.RatecardDiscounts.ValidateForPrice(i.Price); err != nil {
-		errs = append(errs, err)
-	}
-
 	return errors.Join(errs...)
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func (i SplitLineGroupCreate) Validate() error {
	var errs []error

	if i.Namespace == "" {
		errs = append(errs, errors.New("namespace is required"))
	}

	if err := i.SplitLineGroupMutableFields.ValidateForPrice(i.Price); err != nil {
		errs = append(errs, err)
	}

	if i.Price == nil {
		errs = append(errs, errors.New("price is required"))
	} else {
		if err := i.Price.Validate(); err != nil {
			errs = append(errs, err)
		}
	}

	if i.Subscription != nil {
		if err := i.Subscription.Validate(); err != nil {
			errs = append(errs, err)
		}
	}

	if i.Currency == "" {
		errs = append(errs, errors.New("currency is required"))
	}

	if i.UniqueReferenceID != nil && *i.UniqueReferenceID == "" {
		errs = append(errs, errors.New("unique reference id is required"))
	}

	return errors.Join(errs...)
}
🤖 Prompt for AI Agents
In openmeter/billing/invoicelinesplitgroup.go between lines 74 and 112, the
RatecardDiscounts validation is duplicated: it is called once inside
SplitLineGroupMutableFields.ValidateForPrice() around line 81 and again
explicitly at line 107. To fix this, remove the explicit call to
i.RatecardDiscounts.ValidateForPrice(i.Price) at line 107 to avoid redundant
validation.

@turip turip enabled auto-merge (squash) June 9, 2025 21:26
Copy link

@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: 4

🧹 Nitpick comments (2)
openmeter/billing/worker/subscription/invoiceupdate.go (1)

206-206: Consider using a more descriptive nil representation.

Using "nil" as a string might be ambiguous in logs. Consider using "" or "N/A" for better clarity.

-return fmt.Errorf("line[%s/%s] not found in the invoice, cannot update", targetState.ID, lo.FromPtrOr(targetState.ChildUniqueReferenceID, "nil"))
+return fmt.Errorf("line[%s/%s] not found in the invoice, cannot update", targetState.ID, lo.FromPtrOr(targetState.ChildUniqueReferenceID, "<none>"))
openmeter/billing/invoicelinesplitgroup.go (1)

216-218: Update outdated comment referencing progressive lines.

The comment still references "progressive billed line" but the code now uses split line hierarchies.

-// SumNetAmount returns the sum of the net amount (pre-tax) of the progressive billed line and its children
+// SumNetAmount returns the sum of the net amount (pre-tax) of the split line hierarchy and its children
// containing the values for all lines whose period's end is <= in.UpTo and are not deleted or not part of
// an invoice that has been deleted.
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa40904 and 5cfe0c8.

⛔ Files ignored due to path filters (1)
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum
📒 Files selected for processing (18)
  • openmeter/billing/adapter/invoicelinesplitgroup.go (1 hunks)
  • openmeter/billing/discount.go (1 hunks)
  • openmeter/billing/invoiceline.go (9 hunks)
  • openmeter/billing/invoicelinesplitgroup.go (1 hunks)
  • openmeter/billing/service/invoicelinesplitgroup.go (1 hunks)
  • openmeter/billing/service/lineservice/commitments.go (2 hunks)
  • openmeter/billing/service/lineservice/linebase.go (7 hunks)
  • openmeter/billing/service/lineservice/meters.go (5 hunks)
  • openmeter/billing/service/lineservice/usagebasedline_test.go (3 hunks)
  • openmeter/billing/worker/subscription/invoiceupdate.go (1 hunks)
  • openmeter/billing/worker/subscription/patch.go (1 hunks)
  • openmeter/billing/worker/subscription/sync.go (11 hunks)
  • openmeter/billing/worker/subscription/sync_test.go (6 hunks)
  • openmeter/ent/db/billinginvoicesplitlinegroup_create.go (2 hunks)
  • openmeter/ent/db/billinginvoicesplitlinegroup_update.go (0 hunks)
  • openmeter/ent/db/setorclear.go (0 hunks)
  • openmeter/ent/schema/billing.go (1 hunks)
  • test/billing/invoice_test.go (10 hunks)
💤 Files with no reviewable changes (2)
  • openmeter/ent/db/setorclear.go
  • openmeter/ent/db/billinginvoicesplitlinegroup_update.go
✅ Files skipped from review due to trivial changes (1)
  • openmeter/billing/service/invoicelinesplitgroup.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • openmeter/billing/discount.go
  • openmeter/ent/schema/billing.go
  • openmeter/billing/service/lineservice/usagebasedline_test.go
  • openmeter/billing/service/lineservice/meters.go
  • openmeter/billing/invoiceline.go
  • openmeter/billing/worker/subscription/sync_test.go
  • openmeter/billing/adapter/invoicelinesplitgroup.go
  • test/billing/invoice_test.go
  • openmeter/ent/db/billinginvoicesplitlinegroup_create.go
  • openmeter/billing/service/lineservice/commitments.go
  • openmeter/billing/service/lineservice/linebase.go
🧰 Additional context used
🧬 Code Graph Analysis (1)
openmeter/billing/worker/subscription/patch.go (2)
openmeter/billing/invoiceline.go (2)
  • Line (298-312)
  • LineID (21-21)
openmeter/billing/worker/subscription/sync.go (1)
  • Handler (66-73)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: CI
  • GitHub Check: E2E
  • GitHub Check: Lint
  • GitHub Check: Test
  • GitHub Check: Analyze (go)
🔇 Additional comments (14)
openmeter/billing/worker/subscription/invoiceupdate.go (2)

26-70: Well-structured patch application logic!

The method follows a clear flow with proper separation of concerns and comprehensive error handling.


90-142: Clean patch parsing implementation!

Good use of type assertions and clear error messages for each patch operation type.

openmeter/billing/worker/subscription/patch.go (2)

10-50: Excellent refactoring to structured patch types!

The new structure provides better type safety and clearer separation between different patch operations.


142-180: Well-implemented polymorphic delete patch generation!

Good handling of both line and hierarchy types with proper filtering of already deleted items.

openmeter/billing/invoicelinesplitgroup.go (1)

27-49: Good validation implementation with proper error aggregation!

Nice use of errors.Join for collecting multiple validation errors.

openmeter/billing/worker/subscription/sync.go (9)

147-150: LGTM: Customer ID construction is correct.

The customer ID construction follows the established pattern and is properly scoped for the billing service operations.


154-156: LGTM: Billing profile validation is appropriate.

The customer override retrieval serves as a proper validation step before proceeding with the synchronization.


170-184: LGTM: Clear separation of concerns in the synchronization flow.

The refactored flow properly separates plan calculation, patch generation, and patch application phases, which improves maintainability and testability.


187-202: LGTM: Unified patch application approach.

The introduction of InvoiceUpdater for applying all patches at once is a good architectural improvement over the previous manual per-invoice approach.


208-217: LGTM: Type refactoring aligns with the polymorphic approach.

The migration from concrete *billing.Line types to polymorphic billing.LineOrHierarchy properly supports both single lines and split line groups.


257-262: LGTM: Proper polymorphic interface usage.

The filtering and mapping logic correctly uses the ChildUniqueReferenceID() method from the polymorphic interface to handle both lines and hierarchies uniformly.


310-350: LGTM: Well-structured patch generation logic.

The extracted getPatchesFromPlan function properly handles both deletion and update scenarios, with appropriate error handling and null line filtering for zero-amount lines.


555-584: LGTM: Proper polymorphic dispatch pattern.

The switch-based dispatch to handle both LineOrHierarchyTypeLine and LineOrHierarchyTypeHierarchy cases follows good polymorphic design patterns with appropriate error handling.


586-646: LGTM: Comprehensive single line update logic.

The function properly handles managed line detection, period adjustments, flat fee prorating, and edge cases like empty usage-based periods. The change detection pattern is efficient.

@turip turip requested a review from tothandras June 10, 2025 04:51
@turip turip merged commit 41ed6d9 into main Jun 10, 2025
22 checks passed
@turip turip deleted the refactor/split-lines-v2 branch June 10, 2025 05:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants