Conversation
…iption and payment processes
…scription invoice tests
…itID handling in mutations and queries
… ID in customer creation and update processes
…r hierarchical representation
…ce validation logic in customer creation and update requests
…re it is either the customer or their parent
…esponse and enhance validation for expand fields
…g customer logic and enhance validation for subscription creation
… and update related logic for improved clarity and consistency
…improved null safety
WalkthroughIntroduces the Invoicing Customer ID feature enabling subscriptions to reference a separate invoicing customer for billing operations. Adds customer parent relationships, commitment true-up support, modifies database schema with new fields and relationships, removes the price-unit edge, and updates billing, invoice, and payment processing to use the invoicing customer context. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant SubscriptionService
participant CustomerService
participant BillingService
participant InvoiceService
participant PaymentProcessor
participant Wallet as Wallet Service
Client->>SubscriptionService: CreateSubscription(InvoiceBilling=invoice_to_parent, ParentID)
activate SubscriptionService
SubscriptionService->>CustomerService: GetCustomer(ParentID)
activate CustomerService
CustomerService-->>SubscriptionService: Parent Customer
deactivate CustomerService
SubscriptionService->>SubscriptionService: Set InvoicingCustomerID=ParentID
SubscriptionService-->>Client: Subscription created
deactivate SubscriptionService
Client->>BillingService: CreateInvoiceRequestForCharges(Subscription)
activate BillingService
BillingService->>SubscriptionService: GetInvoicingCustomerID()
SubscriptionService-->>BillingService: InvoicingCustomerID
BillingService->>InvoiceService: CreateInvoice(CustomerID=InvoicingCustomerID)
deactivate BillingService
Client->>PaymentProcessor: ProcessSubscriptionPayment(Subscription)
activate PaymentProcessor
PaymentProcessor->>SubscriptionService: GetInvoicingCustomerID()
SubscriptionService-->>PaymentProcessor: InvoicingCustomerID
PaymentProcessor->>Wallet: GetWallet(InvoicingCustomerID)
activate Wallet
Wallet-->>PaymentProcessor: Wallet funds
deactivate Wallet
PaymentProcessor->>PaymentProcessor: Apply wallet to invoice
PaymentProcessor-->>Client: Payment processed
deactivate PaymentProcessor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Areas requiring extra attention:
Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Important
Looks good to me! 👍
Reviewed everything up to 3cd454c in 4 minutes and 50 seconds. Click for details.
- Reviewed
5587lines of code in52files - Skipped
0files when reviewing. - Skipped posting
3draft comments. View those below. - Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. internal/testutil/inmemory_customer_store.go:31
- Draft comment:
The deep copy performed in copyCustomer does not copy the new ParentCustomerID field. This will cause customer hierarchy data to be lost in tests. - Reason this comment was not posted:
Comment was not on a location in the diff, so it can't be submitted as a review comment.
2. ent/subscription/where.go:2443
- Draft comment:
Typo in comment: "applies the HasEdge predicate on the "invoicing_customer" edge with a given conditions (other predicates)". Consider changing "with a given conditions" to "with given conditions" or "with a given condition". - Reason this comment was not posted:
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This is auto-generated code (line 1 explicitly says "Code generated by ent, DO NOT EDIT"). The comment is pointing out a grammatical error in documentation. However, several rules apply here: 1) This is generated code that shouldn't be manually edited, 2) The comment doesn't suggest a code change that would fix a bug or improve functionality - it's purely about grammar in a comment, 3) The same grammatical error appears throughout the file in other similar functions (HasLineItemsWith, HasPausesWith, etc.), suggesting this is a pattern in the code generator itself. Since this is generated code, the fix would need to be made in the code generator, not in this file. The PR author cannot fix this directly in this file without violating the "DO NOT EDIT" directive. While the grammatical error is real, I might be missing that some teams do manually edit generated code despite the warning. Also, even if it's generated code, pointing out the error could be valuable for fixing the generator. However, the rules say not to comment unless there's clearly a code change required, and this is just a grammar fix in a comment. The rules are clear: "Do NOT comment unless there is clearly a code change required" and "Do NOT make comments that are obvious or unimportant." A grammatical error in a comment is not a required code change - it doesn't affect functionality. Additionally, this is generated code that explicitly says "DO NOT EDIT", so the PR author shouldn't be making manual changes to it anyway. The comment is not actionable in this context. This comment should be deleted. It's pointing out a minor grammatical error in auto-generated code that explicitly says "DO NOT EDIT". The comment is not actionable for the PR author and doesn't represent a functional issue. Grammar fixes in comments of generated code are not important enough to warrant a review comment.
3. internal/api/dto/subscription.go:189
- Draft comment:
Minor typographical suggestion: Consider hyphenating 'True-Up Fee' in the comment on line 189 for clarity (i.e. 'Enable Commitment True-Up Fee'). - Reason this comment was not posted:
Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% This comment is about a very minor typographical/stylistic issue in a code comment. According to the rules, I should "Do NOT make comments that are obvious or unimportant." A suggestion to hyphenate "True Up" is extremely minor and doesn't affect code functionality, readability in any meaningful way, or correctness. While "true-up" is sometimes hyphenated in financial contexts, both "true up" and "true-up" are acceptable. This is the kind of nitpicky comment that doesn't provide real value and would likely be annoying to a developer. The comment is about a change (the new field was added), but it's not a meaningful or important comment about that change. Could this be a domain-specific term where the hyphenation matters for consistency with other documentation or industry standards? Perhaps in financial/billing contexts, "true-up" has a specific meaning that should be consistently formatted. Even if "true-up" is more standard in financial contexts, this is an extremely minor stylistic point that doesn't warrant a PR comment. The meaning is completely clear either way, and this level of nitpicking is not helpful. The rules explicitly state not to make comments that are "obvious or unimportant," and this clearly falls into that category. This comment should be deleted. It's a trivial typographical suggestion that doesn't meaningfully improve the code or documentation. It falls squarely under "obvious or unimportant" comments that should not be made.
Workflow ID: wflow_A4JTybl2huRipZv5
You can customize by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (15)
ent/schema/customer.go (1)
82-87: ParentCustomerID column is defined appropriately; consider indexing or a self-edge if lookups growThe
parent_customer_idfield definition (varchar(50), nillable + optional) is consistent with how you’re modeling it elsewhere in ent. If you expect frequent queries like “get all children of parent X”, it may be worth adding:
- An index on
parent_customer_id(plus tenant/environment if needed), and/or- A self-referential ent edge for more convenient traversal and stronger FK semantics.
These are optional design improvements and can be deferred until usage patterns justify them.
docs/invoicing-customer-id.md (2)
52-66: Clarify howRecalculateTaxesOnInvoicederives its customer IDHere you explain that
RecalculateTaxesOnInvoice()“uses invoice's existingCustomerIDdirectly,” while later in the impact analysis you summarize it as “uses invoicing customer ID.” That’s logically true only becauseinvoice.CustomerIDis set to the invoicing customer at creation.To avoid confusion for future readers, consider tightening the wording to explicitly say that tax recalculation uses
invoice.CustomerID(which for new flows is the invoicing customer), and does not re-fetch the subscription. This keeps behavior crystal‑clear if flows aroundCustomerIDever change.
318-324: Double‑check “No database migration needed” claimThis section states that no DB migration is needed because the field already exists in the schema. Given this PR also wires
invoicing_customer_idinto ent schema/migrate and introduces a new edge, please confirm that:
- The column and FK truly pre‑exist in all supported environments, and
- No new DDL is being introduced in this or a related migration.
If any migration is required (even a lightweight one), it’s worth updating this note so ops/dev teams aren’t surprised.
ent/schema/subscription.go (1)
179-182: Edge definition correctly binds the invoicing customer relationship.The
invoicing_customeredge properly usesUnique()for M2O cardinality andField("invoicing_customer_id")to bind to the foreign key column.Consider adding an index on
invoicing_customer_idin theIndexes()function if you anticipate frequent queries filtering by this field (e.g., "find all subscriptions for a given invoicing customer"). This would improve query performance for such lookups.// Example addition to Indexes(): index.Fields("tenant_id", "environment_id", "invoicing_customer_id", "status"). Annotations(entsql.IndexWhere("invoicing_customer_id IS NOT NULL AND status = 'published'")),internal/repository/ent/customer.go (1)
76-76: Consider adding validation for ParentCustomerID relationships.While the implementation correctly uses
SetNillableParentCustomerID, there's no validation to prevent potential issues such as:
- Circular references (customer A → B → A)
- Referencing a non-existent parent customer
- Setting a customer as its own parent
Consider adding validation in the service layer or as a database constraint. Would you like me to help generate validation logic for these scenarios?
internal/service/subscription.go (1)
293-294: EnableTrueUp propagation into the domain model is straightforwardCopying
req.EnableTrueUpontosub.EnableTrueUpbefore persistence cleanly exposes the new flag to downstream billing logic. Just ensure this behavior matches any ent‑levelDefaultEnableTrueUpso there’s no divergence between API and schema defaults.internal/api/dto/subscription.go (1)
189-190: EnableTrueUp flag is added without unexpected API behaviorIntroducing
enable_true_upas a plain bool (noomitempty) is safe and backward compatible; existing clients that omit it will default tofalse. If the intended default is ever non‑false, you’ll want to mirror that here inValidate()or via a documented API default.internal/service/billing.go (1)
544-591: Significant code duplication in true-up logic.The commitment true-up logic at lines 544-591 is nearly identical to lines 991-1037 in
CalculateUsageChargesForPreview. This violates DRY principles and creates a maintenance burden where future changes need to be applied in both places.Consider extracting the true-up logic into a shared helper method:
+func (s *billingService) addCommitmentTrueUpIfNeeded( + ctx context.Context, + sub *subscription.Subscription, + usage *dto.GetUsageBySubscriptionResponse, + periodStart, periodEnd time.Time, + usageCharges *[]dto.CreateInvoiceLineItemRequest, + totalUsageCost *decimal.Decimal, +) { + commitmentAmount := lo.FromPtr(sub.CommitmentAmount) + overageFactor := lo.FromPtr(sub.OverageFactor) + hasCommitment := commitmentAmount.GreaterThan(decimal.Zero) && overageFactor.GreaterThan(decimal.NewFromInt(1)) + + if !hasCommitment || usage.HasOverage || !sub.EnableTrueUp { + return + } + + remainingCommitment := s.calculateRemainingCommitment(usage, commitmentAmount) + if !remainingCommitment.GreaterThan(decimal.Zero) { + return + } + + planDisplayName := "" + for _, item := range sub.LineItems { + if item.PlanDisplayName != "" { + planDisplayName = item.PlanDisplayName + break + } + } + + precision := types.GetCurrencyPrecision(sub.Currency) + roundedRemainingCommitment := remainingCommitment.Round(precision) + commitmentUtilized := commitmentAmount.Sub(roundedRemainingCommitment) + + trueUpLineItem := dto.CreateInvoiceLineItemRequest{ + EntityID: lo.ToPtr(sub.PlanID), + EntityType: lo.ToPtr(string(types.SubscriptionLineItemEntityTypePlan)), + PriceType: lo.ToPtr(string(types.PRICE_TYPE_FIXED)), + PlanDisplayName: lo.ToPtr(planDisplayName), + DisplayName: lo.ToPtr(fmt.Sprintf("%s True Up", planDisplayName)), + Amount: roundedRemainingCommitment, + Quantity: decimal.NewFromInt(1), + PeriodStart: &periodStart, + PeriodEnd: &periodEnd, + PriceID: lo.ToPtr(types.GenerateUUIDWithPrefix(types.UUID_PREFIX_PRICE)), + Metadata: types.Metadata{ + "is_commitment_trueup": "true", + "description": "Remaining commitment amount for billing period", + "commitment_amount": commitmentAmount.String(), + "commitment_utilized": commitmentUtilized.String(), + }, + } + + *usageCharges = append(*usageCharges, trueUpLineItem) + *totalUsageCost = totalUsageCost.Add(roundedRemainingCommitment) +}Also applies to: 991-1037
internal/service/invoice_test.go (2)
1691-1702: Test assertions inside nil check may silently pass on failures.Both tests wrap assertions in
if got != nil { ... }blocks, but this means ifCreateSubscriptionInvoicereturnsnilfor an unexpected reason, the test will pass silently without verifying the core functionality. The tests calls.NoError(err)before the nil check, but a zero-amount invoice scenario (which returnsnil) would skip all customer ID assertions.Consider making the assertion more explicit about expectations:
s.NoError(err) -if got != nil { +if got == nil { + s.T().Log("Invoice was nil (likely zero-amount), consider adding line items to subscription for full test coverage") + return +} s.NotEmpty(got.ID) // Invoice should have invoicing customer ID, not subscription customer ID s.Equal(invoicingCustomer.ID, got.CustomerID, "Invoice should use invoicing customer ID") // ... remaining assertions -}Also applies to: 1751-1761
1662-1662: Test subscription created without line items may result in zero-amount invoice.The test creates a subscription without line items (
[]*subscription.SubscriptionLineItem{}), which likely results in a zero-amount invoice that returnsnil. This means the core assertion aboutinvoicingCustomer.ID == got.CustomerIDmay never actually execute.Consider adding line items to ensure the invoice is created:
-s.NoError(s.GetStores().SubscriptionRepo.CreateWithLineItems(s.GetContext(), subscriptionWithInvoicing, []*subscription.SubscriptionLineItem{})) +lineItems := []*subscription.SubscriptionLineItem{ + { + ID: types.GenerateUUIDWithPrefix(types.UUID_PREFIX_SUBSCRIPTION_LINE_ITEM), + SubscriptionID: subscriptionWithInvoicing.ID, + CustomerID: subscriptionWithInvoicing.CustomerID, + EntityID: s.testData.plan.ID, + EntityType: types.SubscriptionLineItemEntityTypePlan, + PriceID: s.testData.prices.apiCalls.ID, + PriceType: s.testData.prices.apiCalls.Type, + InvoiceCadence: types.InvoiceCadenceAdvance, // Ensures charges at period start + DisplayName: "API Calls", + Quantity: decimal.NewFromInt(1), + Currency: subscriptionWithInvoicing.Currency, + BillingPeriod: subscriptionWithInvoicing.BillingPeriod, + BaseModel: types.GetDefaultBaseModel(s.GetContext()), + }, +} +s.NoError(s.GetStores().SubscriptionRepo.CreateWithLineItems(s.GetContext(), subscriptionWithInvoicing, lineItems))internal/service/subscription_test.go (1)
4610-4921: Invoicing‑customer tests are directionally good; consider making invoice creation deterministicThe new tests around invoicing customer ID (period processing, auto‑cancellation, billing‑period cron) correctly:
- Create a distinct invoicing customer.
- Attach
InvoicingCustomerIDon subscriptions.- Assert invoices and cancellations respect that ID where invoices exist.
Right now several assertions are guarded by
if len(invoices) > 0and effectively become no‑ops when no invoice is produced, so regressions in invoice generation would still let tests pass. If you want stronger guarantees, consider:
- Adding minimal line items/usage so a subscription invoice is always created in these tests; and/or
- Asserting that at least one invoice exists for these scenarios.
This is optional but would turn these into hard correctness tests rather than best‑effort checks.
internal/service/customer.go (4)
35-58: Parent‑customer assignment rules are coherent; a couple of edge behaviors to be aware ofThe combination of:
CreateCustomerchecks that a chosen parent (by ID or external ID) is not itself a child.UpdateCustomerusingvalidateParentCustomerAssignmentonly whenParentCustomerIDactually changes.validateParentCustomerAssignmentenforcing:
- No hierarchy changes when the customer has non‑cancelled subscriptions.
- No self‑parenting.
- Parent must be top‑level (no parent of its own).
- A customer that already has children cannot become a child.
gives you a clean “two‑level” hierarchy (root + direct children) and prevents re‑parenting when active subscriptions or existing children would make it ambiguous. That all looks logically sound.
Two behavioral notes (not necessarily bugs):
Detaching a parent uses empty string semantics
Because you only enter this block whenreq.ParentCustomerID != nil, callers must pass""(notnil) to clear the parent. IfParentCustomerIDis omitted (nil), no hierarchy change occurs. Make sure the API docs / clients are aligned with this contract.No concurrent‑safety at the DB level
These validations happen via separate reads (SubRepo.List,CustomerRepo.Get,CustomerRepo.List) before the final update. Concurrent updates could, in theory, slip between the checks and the update. If stronger guarantees are required later, this logic might want to live in a transaction and/or be backed by DB‑level constraints.Overall the implementation is fine; consider documenting the “empty string clears parent” behavior externally.
Also applies to: 342-361, 579-633
194-217: GetCustomer parent expansion is correct but recursively calls itselfWrapping the repo result into
dto.CustomerResponseand, whenParentCustomerIDis set, callingGetCustomeragain for the parent produces the expected expanded structure (ParentCustomerfilled).Given your hierarchy rules disallow nested parents and parent‑customers from having parents, recursion depth is effectively bounded to 1, so this is safe. If you ever relax those rules, you may want to introduce a max‑depth or cycle‑detection guard to avoid pathological recursion.
226-302: Bulk parent expansion in GetCustomers is efficient; minor nit on attach pathUsing
filter.GetExpand().Validate(CustomerExpandConfig), collecting uniqueParentCustomerIDs, fetching them with a singleListcall, and then attaching from a lookup map is a good pattern that avoids N+1 queries.One tiny optimization you could consider is to skip the “attach parent customers” loop entirely when
parentCustomersByIDis nil/empty (currently you always iterateresponseand just never hit the map key). It’s negligible, so only worth changing if you touch this code again.
660-707: GetUpcomingCreditGrantApplications flow looks reasonable; verify ListByCustomerID semanticsThe new method correctly:
- Verifies the customer exists.
- Uses
SubscriptionService.ListByCustomerIDto gather all subscriptions.- Returns an empty, well‑formed response when there are none.
- Delegates to
SubscriptionService.GetUpcomingCreditGrantApplicationswith the collected IDs.Two small points:
- This constructs a new
SubscriptionServiceper call; that’s fine for now, but if this endpoint becomes hot you might consider injecting aSubscriptionServiceintocustomerServiceinstead of recreating it.- The code assumes
ListByCustomerIDreturns a flat slice of subscriptions, not a paginated wrapper. If that method’s contract ever changes, this code would silently break, so it’s worth double‑checking that assumption.Functionally this looks good.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (51)
docs/invoicing-customer-id.md(1 hunks)ent/client.go(1 hunks)ent/customer.go(4 hunks)ent/customer/customer.go(3 hunks)ent/customer/where.go(2 hunks)ent/customer_create.go(2 hunks)ent/customer_update.go(4 hunks)ent/migrate/schema.go(6 hunks)ent/mutation.go(38 hunks)ent/price.go(4 hunks)ent/price/price.go(0 hunks)ent/price/where.go(0 hunks)ent/price_create.go(1 hunks)ent/price_query.go(3 hunks)ent/price_update.go(2 hunks)ent/priceunit.go(1 hunks)ent/priceunit/priceunit.go(0 hunks)ent/priceunit/where.go(0 hunks)ent/priceunit_create.go(0 hunks)ent/priceunit_query.go(1 hunks)ent/priceunit_update.go(0 hunks)ent/runtime.go(1 hunks)ent/schema/customer.go(1 hunks)ent/schema/price.go(2 hunks)ent/schema/priceunit.go(1 hunks)ent/schema/subscription.go(2 hunks)ent/subscription.go(8 hunks)ent/subscription/subscription.go(8 hunks)ent/subscription/where.go(3 hunks)ent/subscription_create.go(7 hunks)ent/subscription_query.go(9 hunks)ent/subscription_update.go(11 hunks)internal/api/dto/customer.go(4 hunks)internal/api/dto/subscription.go(4 hunks)internal/domain/customer/model.go(2 hunks)internal/domain/price/model.go(1 hunks)internal/domain/subscription/model.go(2 hunks)internal/repository/ent/customer.go(3 hunks)internal/repository/ent/subscription.go(4 hunks)internal/service/billing.go(3 hunks)internal/service/customer.go(7 hunks)internal/service/invoice.go(3 hunks)internal/service/invoice_test.go(1 hunks)internal/service/subscription.go(2 hunks)internal/service/subscription_payment_processor.go(9 hunks)internal/service/subscription_test.go(3 hunks)internal/service/wallet_payment_test.go(1 hunks)internal/testutil/inmemory_customer_store.go(1 hunks)internal/types/customer.go(1 hunks)internal/types/expand.go(2 hunks)internal/types/subscription.go(2 hunks)
💤 Files with no reviewable changes (6)
- ent/priceunit_create.go
- ent/priceunit/priceunit.go
- ent/price/where.go
- ent/price/price.go
- ent/priceunit/where.go
- ent/priceunit_update.go
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-10-18T10:22:48.348Z
Learnt from: omkar273
Repo: flexprice/flexprice PR: 652
File: internal/service/billing.go:1454-1477
Timestamp: 2025-10-18T10:22:48.348Z
Learning: In `internal/service/billing.go`, the `GetCustomerEntitlements` function passes `subscriptions[0].ID` to `AggregateEntitlements` as a fallback subscription ID. This is acceptable because customers typically have a single subscription, and the subscription ID parameter is only used as fallback metadata in `EntitlementSource` objects, not for business logic or filtering.
Applied to files:
internal/types/subscription.gointernal/service/subscription_test.goent/subscription_query.gointernal/service/billing.gointernal/service/customer.goent/subscription.gointernal/service/subscription.gointernal/service/subscription_payment_processor.gointernal/api/dto/subscription.goent/mutation.goent/subscription_update.gointernal/repository/ent/subscription.goent/subscription_create.goent/subscription/where.gointernal/domain/subscription/model.goent/migrate/schema.go
📚 Learning: 2025-10-17T21:53:42.505Z
Learnt from: omkar273
Repo: flexprice/flexprice PR: 652
File: internal/service/subscription.go:3495-3534
Timestamp: 2025-10-17T21:53:42.505Z
Learning: In `internal/service/subscription.go`, when adding multiple-type addons to a subscription, existing active line items are intentionally reused rather than creating new line items for each addon association. The addon occurrences are tracked via separate `AddonAssociation` records, but billing happens through the shared `SubscriptionLineItem`. This is the expected behavior and should not be flagged as an issue.
Applied to files:
internal/types/subscription.gointernal/service/subscription_test.gointernal/service/billing.goent/subscription.gointernal/service/subscription.gointernal/api/dto/subscription.goent/mutation.gointernal/domain/subscription/model.go
🧬 Code graph analysis (35)
internal/types/subscription.go (3)
internal/domain/entityintegrationmapping/model.go (1)
Validate(84-141)internal/errors/builder.go (1)
NewError(17-19)internal/errors/errors.go (1)
ErrValidation(16-16)
ent/customer/customer.go (2)
ent/subscription/subscription.go (1)
OrderOption(330-330)ent/plan/plan.go (1)
OrderOption(101-101)
internal/service/wallet_payment_test.go (11)
ent/customer/where.go (1)
ExternalID(103-105)internal/types/wallet.go (2)
WalletTypePrePaid(25-25)WalletStatusActive(15-15)ent/wallet/where.go (2)
Balance(124-126)CreditBalance(129-131)internal/domain/wallet/transaction.go (1)
Transaction(12-32)internal/types/uuid.go (2)
GenerateUUIDWithPrefix(19-24)UUID_PREFIX_WALLET_TRANSACTION(83-83)ent/wallettransaction/where.go (3)
WalletID(104-106)CreditAmount(119-121)CreditsAvailable(159-161)internal/types/invoice.go (2)
InvoiceTypeSubscription(91-91)InvoiceStatusFinalized(126-126)internal/types/payment.go (3)
PaymentStatusPending(13-13)PaymentFilter(109-121)PaymentDestinationTypeInvoice(86-86)ent/invoice/where.go (4)
AmountDue(135-137)AmountPaid(140-142)AmountRemaining(145-147)DueDate(185-187)internal/service/wallet_payment.go (2)
WalletPaymentOptions(28-35)PromotionalFirstStrategy(20-20)ent/payment/where.go (2)
DestinationID(115-117)DestinationType(110-112)
internal/testutil/inmemory_customer_store.go (1)
ent/customer/where.go (1)
ParentCustomerID(148-150)
internal/service/invoice_test.go (3)
internal/domain/subscription/model.go (1)
Subscription(13-128)ent/schema/subscription.go (5)
Subscription(17-19)Subscription(22-27)Subscription(30-166)Subscription(169-184)Subscription(187-197)internal/types/invoice.go (3)
InvoiceFlowManual(59-59)InvoiceTypeSubscription(91-91)InvoiceType(87-87)
ent/priceunit.go (1)
ent/priceunit/where.go (1)
Precision(129-131)
ent/customer_create.go (2)
ent/customer/where.go (1)
ParentCustomerID(148-150)ent/customer/customer.go (1)
FieldParentCustomerID(51-51)
ent/schema/subscription.go (1)
ent/schema/customer.go (5)
Customer(14-16)Customer(19-25)Customer(28-89)Customer(92-94)Customer(97-109)
ent/price_query.go (2)
ent/ent.go (2)
QueryContext(67-67)Interceptor(70-70)ent/schema/price.go (5)
Price(15-17)Price(20-25)Price(28-214)Price(217-219)Price(222-231)
internal/domain/customer/model.go (1)
ent/customer/where.go (1)
ParentCustomerID(148-150)
ent/customer_update.go (2)
ent/customer/where.go (1)
ParentCustomerID(148-150)ent/customer/customer.go (1)
FieldParentCustomerID(51-51)
ent/subscription_query.go (5)
internal/repository/ent/subscription.go (1)
SubscriptionQuery(451-451)ent/ent.go (1)
Query(66-66)ent/schema/subscription.go (5)
Subscription(17-19)Subscription(22-27)Subscription(30-166)Subscription(169-184)Subscription(187-197)ent/subscription.go (2)
Subscription(19-105)Subscription(194-215)ent/subscription/where.go (2)
InvoicingCustomerID(245-247)ID(15-17)
internal/api/dto/customer.go (5)
ent/customer/where.go (1)
ParentCustomerID(148-150)internal/errors/builder.go (1)
NewError(17-19)internal/errors/errors.go (1)
ErrValidation(16-16)internal/types/context.go (1)
GetEnvironmentID(54-59)internal/validator/validator.go (1)
ValidateRequest(35-52)
ent/customer.go (2)
ent/customer/where.go (1)
ParentCustomerID(148-150)ent/customer/customer.go (1)
FieldParentCustomerID(51-51)
ent/priceunit_query.go (3)
ent/priceunit.go (2)
PriceUnit(17-48)PriceUnit(51-68)ent/schema/priceunit.go (6)
PriceUnit(14-16)PriceUnit(19-23)PriceUnit(26-31)PriceUnit(34-72)PriceUnit(75-77)PriceUnit(80-87)ent/predicate/predicate.go (1)
PriceUnit(94-94)
internal/types/customer.go (2)
internal/types/search_filter.go (2)
FilterCondition(55-60)SortCondition(138-141)ent/customer/where.go (2)
ExternalID(103-105)
ent/client.go (4)
internal/domain/subscription/model.go (1)
Subscription(13-128)ent/subscription.go (2)
Subscription(19-105)Subscription(194-215)ent/ent.go (1)
Query(66-66)ent/subscription/subscription.go (3)
Table(112-112)InvoicingCustomerTable(156-156)InvoicingCustomerColumn(161-161)
ent/price.go (3)
ent/price/where.go (2)
PriceUnitID(123-125)GroupID(228-230)ent/subscriptionlineitem/where.go (1)
PriceUnitID(150-152)ent/invoicelineitem/where.go (1)
PriceUnitID(155-157)
ent/runtime.go (1)
ent/subscription/subscription.go (1)
DefaultEnableTrueUp(272-272)
internal/service/billing.go (5)
ent/subscription/where.go (5)
CommitmentAmount(215-217)OverageFactor(220-222)Currency(125-127)ID(15-17)CustomerID(110-112)internal/types/currency.go (1)
GetCurrencyPrecision(68-73)internal/api/dto/invoice.go (2)
CreateInvoiceLineItemRequest(441-490)CreateInvoiceRequest(33-118)internal/types/subscription.go (1)
SubscriptionLineItemEntityTypePlan(47-47)internal/types/uuid.go (2)
GenerateUUIDWithPrefix(19-24)UUID_PREFIX_PRICE(73-73)
internal/service/customer.go (7)
ent/customer/where.go (1)
ParentCustomerID(148-150)internal/errors/builder.go (1)
NewError(17-19)internal/api/dto/customer.go (1)
CustomerResponse(116-119)internal/types/expand.go (2)
CustomerExpandConfig(138-143)ExpandParentCustomer(38-38)internal/types/customer.go (2)
NewNoLimitCustomerFilter(33-37)NewCustomerFilter(26-30)internal/types/subscription.go (2)
NewSubscriptionFilter(243-247)SubscriptionStatusCancelled(59-59)ent/subscription/where.go (1)
SubscriptionStatusNotIn(905-907)
ent/price_update.go (2)
ent/price/where.go (1)
PriceUnitID(123-125)ent/price/price.go (1)
FieldPriceUnitID(39-39)
ent/subscription.go (4)
ent/subscription/where.go (2)
EnableTrueUp(240-242)InvoicingCustomerID(245-247)ent/ent.go (2)
NotFoundError(283-285)NotLoadedError(329-331)ent/customer/customer.go (1)
Label(13-13)ent/subscription/subscription.go (3)
Label(16-16)FieldEnableTrueUp(94-94)FieldInvoicingCustomerID(96-96)
internal/service/subscription.go (4)
internal/types/subscription.go (2)
InvoiceBilling(11-11)InvoiceBillingInvoiceToParent(15-15)internal/errors/builder.go (1)
NewError(17-19)ent/subscription/where.go (3)
CustomerID(110-112)InvoicingCustomerID(245-247)EnableTrueUp(240-242)internal/errors/errors.go (1)
ErrValidation(16-16)
internal/api/dto/subscription.go (2)
ent/subscription/where.go (2)
InvoicingCustomerID(245-247)EnableTrueUp(240-242)internal/types/subscription.go (2)
InvoiceBilling(11-11)InvoiceBillingInvoiceToSelf(18-18)
ent/mutation.go (6)
ent/customer/where.go (1)
ParentCustomerID(148-150)ent/customer/customer.go (1)
FieldParentCustomerID(51-51)ent/price/where.go (1)
PriceUnitID(123-125)ent/subscriptionlineitem/where.go (1)
PriceUnitID(150-152)ent/subscription/where.go (2)
EnableTrueUp(240-242)InvoicingCustomerID(245-247)ent/subscription/subscription.go (3)
FieldInvoicingCustomerID(96-96)FieldEnableTrueUp(94-94)EdgeInvoicingCustomer(110-110)
ent/subscription_update.go (2)
ent/subscription/where.go (1)
EnableTrueUp(240-242)ent/subscription/subscription.go (4)
FieldEnableTrueUp(94-94)Table(112-112)InvoicingCustomerTable(156-156)InvoicingCustomerColumn(161-161)
internal/repository/ent/subscription.go (2)
ent/subscription/where.go (3)
EnableTrueUp(240-242)InvoicingCustomerID(245-247)InvoicingCustomerIDIn(2230-2232)ent/subscription/subscription.go (1)
FieldInvoicingCustomerID(96-96)
ent/subscription_create.go (1)
ent/subscription/subscription.go (6)
FieldEnableTrueUp(94-94)Table(112-112)InvoicingCustomerTable(156-156)Columns(165-206)InvoicingCustomerColumn(161-161)FieldID(18-18)
ent/subscription/where.go (1)
ent/subscription/subscription.go (5)
FieldEnableTrueUp(94-94)FieldInvoicingCustomerID(96-96)Table(112-112)InvoicingCustomerTable(156-156)InvoicingCustomerColumn(161-161)
ent/customer/where.go (3)
ent/customer.go (2)
Customer(17-58)Customer(61-76)ent/schema/customer.go (5)
Customer(14-16)Customer(19-25)Customer(28-89)Customer(92-94)Customer(97-109)internal/domain/customer/model.go (1)
Customer(10-51)
ent/subscription/subscription.go (3)
ent/customer/customer.go (3)
OrderOption(109-109)Table(53-53)FieldID(15-15)ent/subscriptionphase/subscriptionphase.go (3)
OrderOption(98-98)Table(42-42)FieldID(16-16)ent/plan/plan.go (3)
OrderOption(101-101)Table(44-44)FieldID(16-16)
internal/domain/subscription/model.go (4)
ent/subscription/where.go (5)
EnableTrueUp(240-242)InvoicingCustomerID(245-247)CustomerID(110-112)CustomerTimezone(230-232)ProrationBehavior(235-237)ent/schema/subscription.go (5)
Subscription(17-19)Subscription(22-27)Subscription(30-166)Subscription(169-184)Subscription(187-197)ent/subscription.go (2)
Subscription(19-105)Subscription(194-215)internal/types/proration.go (1)
ProrationBehavior(29-29)
ent/migrate/schema.go (2)
ent/customer/customer.go (1)
Columns(57-77)ent/subscription/subscription.go (1)
Columns(165-206)
internal/repository/ent/customer.go (1)
ent/customer/where.go (2)
ParentCustomerID(148-150)ParentCustomerIDIn(1263-1265)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Run Tests and Lint
| // QueryInvoicingCustomer queries the invoicing_customer edge of a Subscription. | ||
| func (c *SubscriptionClient) QueryInvoicingCustomer(s *Subscription) *CustomerQuery { | ||
| query := (&CustomerClient{config: c.config}).Query() | ||
| query.path = func(context.Context) (fromV *sql.Selector, _ error) { | ||
| id := s.ID | ||
| step := sqlgraph.NewStep( | ||
| sqlgraph.From(subscription.Table, subscription.FieldID, id), | ||
| sqlgraph.To(customer.Table, customer.FieldID), | ||
| sqlgraph.Edge(sqlgraph.M2O, false, subscription.InvoicingCustomerTable, subscription.InvoicingCustomerColumn), | ||
| ) | ||
| fromV = sqlgraph.Neighbors(s.driver.Dialect(), step) | ||
| return fromV, nil | ||
| } | ||
| return query | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check the file size to understand scope
wc -l ent/client.goRepository: flexprice/flexprice
Length of output: 81
🏁 Script executed:
# Read the target lines 5398-5412
sed -n '5398,5412p' ent/client.goRepository: flexprice/flexprice
Length of output: 719
🏁 Script executed:
# Search for other M2O edge definitions in the file to compare patterns
rg "sqlgraph\.Edge\(sqlgraph\.M2O" ent/client.go -A 1 -B 1Repository: flexprice/flexprice
Length of output: 2770
Fix sqlgraph.Edge inverse flag for QueryInvoicingCustomer
QueryInvoicingCustomer is the only M2O edge helper in this file using sqlgraph.Edge(sqlgraph.M2O, false, ...). Every other M2O edge (CouponApplication, CouponAssociation, CreditGrant, CreditNoteLineItem, InvoiceLineItem, PaymentAttempt, SubscriptionLineItem, SubscriptionPause, SubscriptionPhase) uses true for the inverse flag, which is the correct behavior for child→parent traversal in ent-generated code.
This inconsistency will produce incorrect edge traversal and join logic for the invoicing_customer relationship.
Change to:
- sqlgraph.Edge(sqlgraph.M2O, false, subscription.InvoicingCustomerTable, subscription.InvoicingCustomerColumn),
+ sqlgraph.Edge(sqlgraph.M2O, true, subscription.InvoicingCustomerTable, subscription.InvoicingCustomerColumn),📝 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.
| // QueryInvoicingCustomer queries the invoicing_customer edge of a Subscription. | |
| func (c *SubscriptionClient) QueryInvoicingCustomer(s *Subscription) *CustomerQuery { | |
| query := (&CustomerClient{config: c.config}).Query() | |
| query.path = func(context.Context) (fromV *sql.Selector, _ error) { | |
| id := s.ID | |
| step := sqlgraph.NewStep( | |
| sqlgraph.From(subscription.Table, subscription.FieldID, id), | |
| sqlgraph.To(customer.Table, customer.FieldID), | |
| sqlgraph.Edge(sqlgraph.M2O, false, subscription.InvoicingCustomerTable, subscription.InvoicingCustomerColumn), | |
| ) | |
| fromV = sqlgraph.Neighbors(s.driver.Dialect(), step) | |
| return fromV, nil | |
| } | |
| return query | |
| } | |
| // QueryInvoicingCustomer queries the invoicing_customer edge of a Subscription. | |
| func (c *SubscriptionClient) QueryInvoicingCustomer(s *Subscription) *CustomerQuery { | |
| query := (&CustomerClient{config: c.config}).Query() | |
| query.path = func(context.Context) (fromV *sql.Selector, _ error) { | |
| id := s.ID | |
| step := sqlgraph.NewStep( | |
| sqlgraph.From(subscription.Table, subscription.FieldID, id), | |
| sqlgraph.To(customer.Table, customer.FieldID), | |
| sqlgraph.Edge(sqlgraph.M2O, true, subscription.InvoicingCustomerTable, subscription.InvoicingCustomerColumn), | |
| ) | |
| fromV = sqlgraph.Neighbors(s.driver.Dialect(), step) | |
| return fromV, nil | |
| } | |
| return query | |
| } |
🤖 Prompt for AI Agents
In ent/client.go around lines 5398 to 5412, the QueryInvoicingCustomer M2O edge
is using sqlgraph.Edge(sqlgraph.M2O, false, ...) which is inconsistent with
other M2O child→parent edges and causes incorrect traversal; change the inverse
flag from false to true in the sqlgraph.Edge call for the invoicing_customer
relationship so the edge is treated as child→parent during join/traversal.
Important
Enhance customer and subscription models to support parent-child relationships and invoicing flexibility, with updated filters and tests.
ParentCustomerIDtoCustomermodel incustomer.goand related files to support parent-child relationships.InvoiceBillingtype insubscription.goto determine invoice recipient (parent or self).ProcessInvoicePaymentWithWalletsto use invoicing customer's wallet.ParentCustomerIDsfilter toCustomerFilterincustomer.go.InvoicingCustomerIDsfilter toSubscriptionFilterinsubscription.go.wallet_payment_test.goto verify payment processing with invoicing customer wallets.inmemory_customer_store.goto handle parent customer filtering.This description was created by
for 3cd454c. You can customize this summary. It will automatically update as commits are pushed.
Summary by CodeRabbit
New Features
Documentation
✏️ Tip: You can customize this high-level summary in your review settings.