From 8e4110f747591d2ece75a9146ee5d15a7bcbad33 Mon Sep 17 00:00:00 2001 From: Alex Goth <64845621+GAlexIHU@users.noreply.github.com> Date: Fri, 17 Jul 2026 22:38:41 +0200 Subject: [PATCH] feat(customcurrency): support custom currency subscriptions --- api/api.gen.go | 2616 +++++++++-------- api/client/go/client.gen.go | 2154 +++++++------- api/client/javascript/src/client/schemas.ts | 82 +- api/client/javascript/src/zod/index.ts | 40 + .../openmeter/_generated/models/__init__.py | 4 + .../openmeter/_generated/models/_enums.py | 9 + .../openmeter/_generated/models/_models.py | 79 + api/openapi.cloud.yaml | 106 +- api/openapi.yaml | 106 +- .../aip-client-javascript/src/index.ts | 14 +- .../src/models/operations/subscriptions.ts | 9 +- .../src/models/schemas.ts | 196 +- .../aip-client-javascript/src/models/types.ts | 274 +- .../aip/src/subscriptions/subscription.tsp | 66 + .../src/productcatalog/subscription.tsp | 62 +- api/spec/packages/legacy/src/types.tsp | 8 + api/v3/api.gen.go | 1126 +++---- api/v3/client/models_subscriptions.go | 38 + api/v3/handlers/subscriptions/change.go | 2 + api/v3/handlers/subscriptions/convert.go | 33 +- api/v3/handlers/subscriptions/convert_test.go | 65 + api/v3/openapi.yaml | 67 + app/common/subscription.go | 12 +- cmd/billing-worker/wire_gen.go | 10 +- cmd/jobs/internal/wire_gen.go | 10 +- cmd/server/wire_gen.go | 2 +- ...026-07-17-custom-currency-subscriptions.md | 35 + .../subscriptionsync/reconciler/reconciler.go | 2 +- .../reconciler/reconciler_test.go | 44 + .../worker/subscriptionsync/service.go | 21 +- .../subscriptionsync/service/base_test.go | 4 +- .../service/currency_boundary_test.go | 263 ++ .../subscriptionsync/service/handlers.go | 35 +- .../service/reconciler/patchcharge_test.go | 6 +- .../subscriptionsync/service/service.go | 8 +- .../worker/subscriptionsync/service/sync.go | 43 +- .../service/targetstate/targetstate.go | 2 +- openmeter/billing/worker/worker.go | 21 +- openmeter/currencies/adapter/currencies.go | 38 +- .../currencies/adapter/currencies_test.go | 140 + openmeter/currencies/repository.go | 1 + openmeter/currencies/service.go | 35 + openmeter/currencies/service/service.go | 10 + openmeter/currencies/service/service_test.go | 9 + openmeter/customer/customer.go | 28 +- openmeter/customer/customer_test.go | 53 + openmeter/ent/db/client.go | 250 +- openmeter/ent/db/currencycostbasis.go | 18 +- .../db/currencycostbasis/currencycostbasis.go | 30 + openmeter/ent/db/currencycostbasis/where.go | 23 + openmeter/ent/db/currencycostbasis_create.go | 32 + openmeter/ent/db/currencycostbasis_query.go | 103 +- openmeter/ent/db/currencycostbasis_update.go | 163 + openmeter/ent/db/cursor.go | 51 + openmeter/ent/db/customcurrency.go | 18 +- .../ent/db/customcurrency/customcurrency.go | 30 + openmeter/ent/db/customcurrency/where.go | 23 + openmeter/ent/db/customcurrency_create.go | 32 + openmeter/ent/db/customcurrency_query.go | 122 +- openmeter/ent/db/customcurrency_update.go | 163 + openmeter/ent/db/ent.go | 2 + openmeter/ent/db/entmixinaccessor.go | 20 + openmeter/ent/db/expose.go | 2 + openmeter/ent/db/hook/hook.go | 12 + openmeter/ent/db/migrate/schema.go | 83 +- openmeter/ent/db/mutation.go | 1355 ++++++++- openmeter/ent/db/paginate.go | 58 + openmeter/ent/db/predicate/predicate.go | 3 + openmeter/ent/db/runtime.go | 75 +- openmeter/ent/db/setorclear.go | 14 + openmeter/ent/db/subscription.go | 45 +- openmeter/ent/db/subscription/subscription.go | 80 +- openmeter/ent/db/subscription/where.go | 127 +- openmeter/ent/db/subscription_create.go | 98 +- openmeter/ent/db/subscription_query.go | 78 +- openmeter/ent/db/subscription_update.go | 163 + openmeter/ent/db/subscriptioncostbasispin.go | 252 ++ .../subscriptioncostbasispin.go | 196 ++ .../ent/db/subscriptioncostbasispin/where.go | 666 +++++ .../ent/db/subscriptioncostbasispin_create.go | 816 +++++ .../ent/db/subscriptioncostbasispin_delete.go | 88 + .../ent/db/subscriptioncostbasispin_query.go | 793 +++++ .../ent/db/subscriptioncostbasispin_update.go | 298 ++ openmeter/ent/db/tx.go | 3 + openmeter/ent/schema/custom_currencies.go | 4 + openmeter/ent/schema/subscription.go | 18 +- .../ent/schema/subscription_cost_basis_pin.go | 79 + .../currencyresolver/resolver_test.go | 4 + .../subscription/http/change.go | 3 + .../subscription/http/create.go | 2 + .../subscription/http/mapping.go | 53 +- .../subscription/http/mapping_test.go | 71 + .../subscription/service/change.go | 7 + .../subscription/service/change_test.go | 41 +- .../subscription/service/create.go | 7 + .../subscription/service/create_test.go | 164 +- .../subscription/service/migrate.go | 1 + .../subscription/service/migrate_test.go | 33 +- .../subscription/service/plan_test.go | 9 +- .../subscription/service/service.go | 41 +- openmeter/server/server_test.go | 6 +- openmeter/subscription/addon/diff/apply.go | 16 +- .../subscription/addon/diff/apply_test.go | 125 +- openmeter/subscription/addon/extend.go | 3 +- .../subscription/addon/service/change_test.go | 20 +- .../subscription/addon/service/create_test.go | 90 +- .../subscription/addon/service/list_test.go | 20 +- openmeter/subscription/apply.go | 6 +- openmeter/subscription/currency.go | 108 + .../subscription/currency_validation_test.go | 128 + openmeter/subscription/errors_test.go | 22 +- openmeter/subscription/events_test.go | 42 + openmeter/subscription/patch/patch_test.go | 10 +- openmeter/subscription/repo/mapping.go | 45 +- .../repo/subscriptionitemrepo_test.go | 12 +- .../subscription/repo/subscriptionrepo.go | 93 +- .../repo/subscriptionrepo_test.go | 107 + openmeter/subscription/repository.go | 15 +- openmeter/subscription/service.go | 15 +- .../service/cost_basis_persistence_test.go | 255 ++ openmeter/subscription/service/currency.go | 120 + .../service/currency_materialization_test.go | 106 - .../subscription/service/currency_test.go | 154 + openmeter/subscription/service/service.go | 69 +- .../subscription/service/service_test.go | 260 +- .../subscription/service/servicevalidation.go | 126 +- .../service/servicevalidation_test.go | 209 +- openmeter/subscription/service/sync.go | 2 +- openmeter/subscription/service/sync_test.go | 80 +- openmeter/subscription/subscription.go | 10 +- openmeter/subscription/subscriptionspec.go | 105 +- openmeter/subscription/subscriptionview.go | 38 +- openmeter/subscription/testutils/builder.go | 16 +- openmeter/subscription/testutils/compare.go | 6 +- openmeter/subscription/testutils/mock.go | 2 +- .../subscription/testutils/repository.go | 8 +- openmeter/subscription/testutils/service.go | 2 + openmeter/subscription/uniqueness_test.go | 12 +- openmeter/subscription/workflow/service.go | 5 +- .../subscription/workflow/service/addon.go | 2 +- .../workflow/service/addon_test.go | 2 +- .../workflow/service/currency_test.go | 210 ++ .../subscription/workflow/service/service.go | 8 +- .../workflow/service/service_test.go | 1 + .../workflow/service/subscription.go | 157 +- .../workflow/service/subscription_test.go | 125 +- test/billing/subscription_suite.go | 2 + test/customer/customer.go | 20 +- test/customer/testenv.go | 2 + test/subscription/custom_currency_test.go | 267 ++ test/subscription/framework_test.go | 4 +- ...m_currency_subscription_semantics.down.sql | 18 + ...tom_currency_subscription_semantics.up.sql | 32 + tools/migrate/migrations/atlas.sum | 3 +- ...cription_custom_currency_semantics_test.go | 167 ++ 155 files changed, 14475 insertions(+), 3923 deletions(-) create mode 100644 api/v3/handlers/subscriptions/convert_test.go create mode 100644 docs/migration-guides/2026-07-17-custom-currency-subscriptions.md create mode 100644 openmeter/billing/worker/subscriptionsync/reconciler/reconciler_test.go create mode 100644 openmeter/billing/worker/subscriptionsync/service/currency_boundary_test.go create mode 100644 openmeter/currencies/adapter/currencies_test.go create mode 100644 openmeter/customer/customer_test.go create mode 100644 openmeter/ent/db/subscriptioncostbasispin.go create mode 100644 openmeter/ent/db/subscriptioncostbasispin/subscriptioncostbasispin.go create mode 100644 openmeter/ent/db/subscriptioncostbasispin/where.go create mode 100644 openmeter/ent/db/subscriptioncostbasispin_create.go create mode 100644 openmeter/ent/db/subscriptioncostbasispin_delete.go create mode 100644 openmeter/ent/db/subscriptioncostbasispin_query.go create mode 100644 openmeter/ent/db/subscriptioncostbasispin_update.go create mode 100644 openmeter/ent/schema/subscription_cost_basis_pin.go create mode 100644 openmeter/productcatalog/subscription/http/mapping_test.go create mode 100644 openmeter/subscription/currency.go create mode 100644 openmeter/subscription/currency_validation_test.go create mode 100644 openmeter/subscription/events_test.go create mode 100644 openmeter/subscription/repo/subscriptionrepo_test.go create mode 100644 openmeter/subscription/service/cost_basis_persistence_test.go create mode 100644 openmeter/subscription/service/currency.go delete mode 100644 openmeter/subscription/service/currency_materialization_test.go create mode 100644 openmeter/subscription/service/currency_test.go create mode 100644 openmeter/subscription/workflow/service/currency_test.go create mode 100644 test/subscription/custom_currency_test.go create mode 100644 tools/migrate/migrations/20260717195001_custom_currency_subscription_semantics.down.sql create mode 100644 tools/migrate/migrations/20260717195001_custom_currency_subscription_semantics.up.sql create mode 100644 tools/migrate/subscription_custom_currency_semantics_test.go diff --git a/api/api.gen.go b/api/api.gen.go index f4d8c83dd5..26e9927423 100644 --- a/api/api.gen.go +++ b/api/api.gen.go @@ -2449,6 +2449,24 @@ func (e StripeCustomerAppDataCreateOrUpdateItemType) Valid() bool { } } +// Defines values for SubscriptionCostBasisMode. +const ( + SubscriptionCostBasisModeDynamic SubscriptionCostBasisMode = "dynamic" + SubscriptionCostBasisModePinned SubscriptionCostBasisMode = "pinned" +) + +// Valid indicates whether the value is a known member of the SubscriptionCostBasisMode enum. +func (e SubscriptionCostBasisMode) Valid() bool { + switch e { + case SubscriptionCostBasisModeDynamic: + return true + case SubscriptionCostBasisModePinned: + return true + default: + return false + } +} + // Defines values for SubscriptionStatus. const ( SubscriptionStatusActive SubscriptionStatus = "active" @@ -3790,6 +3808,9 @@ type Currency struct { // Custom three-letter currency codes are also supported for convenience. type CurrencyCode = string +// CurrencyCodeOrCustom Fiat or managed custom currency code. +type CurrencyCodeOrCustom = string + // CustomInvoicingApp Custom Invoicing app can be used for interface with any invoicing or payment system. // // This app provides ways to manipulate invoices and payments, however the integration @@ -4020,6 +4041,9 @@ type CustomSubscriptionChange struct { // BillingAnchor The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used. BillingAnchor *time.Time `json:"billingAnchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the new subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"costBasisMode,omitempty"` + // CustomPlan The custom plan description which defines the Subscription. CustomPlan CustomPlanInput `json:"customPlan"` @@ -4033,6 +4057,9 @@ type CustomSubscriptionCreate struct { // BillingAnchor The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the subscription start time will be used. BillingAnchor *time.Time `json:"billingAnchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the new subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"costBasisMode,omitempty"` + // CustomPlan The custom plan description which defines the Subscription. CustomPlan CustomPlanInput `json:"customPlan"` @@ -8241,6 +8268,9 @@ type PlanSubscriptionChange struct { // BillingAnchor The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used. BillingAnchor *time.Time `json:"billingAnchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the new subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"costBasisMode,omitempty"` + // Description Description for the Subscription. Description *string `json:"description,omitempty"` @@ -8273,6 +8303,9 @@ type PlanSubscriptionCreate struct { // BillingAnchor The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the subscription start time will be used. BillingAnchor *time.Time `json:"billingAnchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the new subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"costBasisMode,omitempty"` + // CustomerId The ID of the customer. Provide either the key or ID. Has presedence over the key. CustomerId *string `json:"customerId,omitempty"` @@ -9047,11 +9080,17 @@ type Subscription struct { // Examples: "P1M" (monthly), "P3M" (quarterly), "P1Y" (annually). BillingCadence string `json:"billingCadence"` + // CostBasisMode Controls whether custom-currency cost bases are resolved dynamically or pinned + // when their currency pair is introduced to the subscription. + CostBasisMode SubscriptionCostBasisMode `json:"costBasisMode"` + + // CostBasisPins Cost bases pinned to custom-currency pairs for this subscription. + CostBasisPins []SubscriptionCostBasisPin `json:"costBasisPins"` + // CreatedAt Timestamp of when the resource was created. CreatedAt time.Time `json:"createdAt"` - // Currency The currency code of the subscription. - // Will be revised once we add multi currency support. + // Currency The fiat currency in which the subscription is invoiced. Currency CurrencyCode `json:"currency"` // CustomerId The customer ID of the subscription. @@ -9282,6 +9321,21 @@ type SubscriptionConflictErrorResponse struct { Type string `json:"type"` } +// SubscriptionCostBasisMode Controls how custom-currency cost bases are selected for the subscription. +type SubscriptionCostBasisMode string + +// SubscriptionCostBasisPin A cost basis pinned to a custom-currency pair for the subscription. +type SubscriptionCostBasisPin struct { + // CostBasisId The pinned cost basis resource ID. + CostBasisId string `json:"costBasisId"` + + // CustomCurrencyId The managed custom currency ID. + CustomCurrencyId string `json:"customCurrencyId"` + + // InvoiceCurrency The fiat currency in which the subscription is invoiced. + InvoiceCurrency CurrencyCode `json:"invoiceCurrency"` +} + // SubscriptionCreate Create a subscription. type SubscriptionCreate struct { union json.RawMessage @@ -9329,11 +9383,17 @@ type SubscriptionExpanded struct { // Examples: "P1M" (monthly), "P3M" (quarterly), "P1Y" (annually). BillingCadence string `json:"billingCadence"` + // CostBasisMode Controls whether custom-currency cost bases are resolved dynamically or pinned + // when their currency pair is introduced to the subscription. + CostBasisMode SubscriptionCostBasisMode `json:"costBasisMode"` + + // CostBasisPins Cost bases pinned to custom-currency pairs for this subscription. + CostBasisPins []SubscriptionCostBasisPin `json:"costBasisPins"` + // CreatedAt Timestamp of when the resource was created. CreatedAt time.Time `json:"createdAt"` - // Currency The currency code of the subscription. - // Will be revised once we add multi currency support. + // Currency The fiat currency in which the subscription is invoiced. Currency CurrencyCode `json:"currency"` // CustomerId The customer ID of the subscription. @@ -9391,6 +9451,9 @@ type SubscriptionItem struct { // CreatedAt Timestamp of when the resource was created. CreatedAt time.Time `json:"createdAt"` + // Currency The materialized currency of the item's rate card. Present for priced items. + Currency *CurrencyCodeOrCustom `json:"currency,omitempty"` + // DeletedAt Timestamp of when the resource was permanently deleted. DeletedAt *time.Time `json:"deletedAt,omitempty"` @@ -22803,1278 +22866,1285 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "HwAA///1pDp/u7gMAA==", + "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", + "lpnWl/df/n8AAAD//y19waTexAwA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/client/go/client.gen.go b/api/client/go/client.gen.go index 4fa8e6abd6..d19fb2b659 100644 --- a/api/client/go/client.gen.go +++ b/api/client/go/client.gen.go @@ -2202,6 +2202,24 @@ func (e StripeCustomerAppDataCreateOrUpdateItemType) Valid() bool { } } +// Defines values for SubscriptionCostBasisMode. +const ( + SubscriptionCostBasisModeDynamic SubscriptionCostBasisMode = "dynamic" + SubscriptionCostBasisModePinned SubscriptionCostBasisMode = "pinned" +) + +// Valid indicates whether the value is a known member of the SubscriptionCostBasisMode enum. +func (e SubscriptionCostBasisMode) Valid() bool { + switch e { + case SubscriptionCostBasisModeDynamic: + return true + case SubscriptionCostBasisModePinned: + return true + default: + return false + } +} + // Defines values for SubscriptionStatus. const ( SubscriptionStatusActive SubscriptionStatus = "active" @@ -3425,6 +3443,9 @@ type Currency struct { // Custom three-letter currency codes are also supported for convenience. type CurrencyCode = string +// CurrencyCodeOrCustom Fiat or managed custom currency code. +type CurrencyCodeOrCustom = string + // CustomInvoicingApp Custom Invoicing app can be used for interface with any invoicing or payment system. // // This app provides ways to manipulate invoices and payments, however the integration @@ -3655,6 +3676,9 @@ type CustomSubscriptionChange struct { // BillingAnchor The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used. BillingAnchor *time.Time `json:"billingAnchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the new subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"costBasisMode,omitempty"` + // CustomPlan The custom plan description which defines the Subscription. CustomPlan CustomPlanInput `json:"customPlan"` @@ -3668,6 +3692,9 @@ type CustomSubscriptionCreate struct { // BillingAnchor The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the subscription start time will be used. BillingAnchor *time.Time `json:"billingAnchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the new subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"costBasisMode,omitempty"` + // CustomPlan The custom plan description which defines the Subscription. CustomPlan CustomPlanInput `json:"customPlan"` @@ -7528,6 +7555,9 @@ type PlanSubscriptionChange struct { // BillingAnchor The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used. BillingAnchor *time.Time `json:"billingAnchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the new subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"costBasisMode,omitempty"` + // Description Description for the Subscription. Description *string `json:"description,omitempty"` @@ -7560,6 +7590,9 @@ type PlanSubscriptionCreate struct { // BillingAnchor The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the subscription start time will be used. BillingAnchor *time.Time `json:"billingAnchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the new subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"costBasisMode,omitempty"` + // CustomerId The ID of the customer. Provide either the key or ID. Has presedence over the key. CustomerId *string `json:"customerId,omitempty"` @@ -8263,11 +8296,17 @@ type Subscription struct { // Examples: "P1M" (monthly), "P3M" (quarterly), "P1Y" (annually). BillingCadence string `json:"billingCadence"` + // CostBasisMode Controls whether custom-currency cost bases are resolved dynamically or pinned + // when their currency pair is introduced to the subscription. + CostBasisMode SubscriptionCostBasisMode `json:"costBasisMode"` + + // CostBasisPins Cost bases pinned to custom-currency pairs for this subscription. + CostBasisPins []SubscriptionCostBasisPin `json:"costBasisPins"` + // CreatedAt Timestamp of when the resource was created. CreatedAt time.Time `json:"createdAt"` - // Currency The currency code of the subscription. - // Will be revised once we add multi currency support. + // Currency The fiat currency in which the subscription is invoiced. Currency CurrencyCode `json:"currency"` // CustomerId The customer ID of the subscription. @@ -8498,6 +8537,21 @@ type SubscriptionConflictErrorResponse struct { Type string `json:"type"` } +// SubscriptionCostBasisMode Controls how custom-currency cost bases are selected for the subscription. +type SubscriptionCostBasisMode string + +// SubscriptionCostBasisPin A cost basis pinned to a custom-currency pair for the subscription. +type SubscriptionCostBasisPin struct { + // CostBasisId The pinned cost basis resource ID. + CostBasisId string `json:"costBasisId"` + + // CustomCurrencyId The managed custom currency ID. + CustomCurrencyId string `json:"customCurrencyId"` + + // InvoiceCurrency The fiat currency in which the subscription is invoiced. + InvoiceCurrency CurrencyCode `json:"invoiceCurrency"` +} + // SubscriptionCreate Create a subscription. type SubscriptionCreate struct { union json.RawMessage @@ -8545,11 +8599,17 @@ type SubscriptionExpanded struct { // Examples: "P1M" (monthly), "P3M" (quarterly), "P1Y" (annually). BillingCadence string `json:"billingCadence"` + // CostBasisMode Controls whether custom-currency cost bases are resolved dynamically or pinned + // when their currency pair is introduced to the subscription. + CostBasisMode SubscriptionCostBasisMode `json:"costBasisMode"` + + // CostBasisPins Cost bases pinned to custom-currency pairs for this subscription. + CostBasisPins []SubscriptionCostBasisPin `json:"costBasisPins"` + // CreatedAt Timestamp of when the resource was created. CreatedAt time.Time `json:"createdAt"` - // Currency The currency code of the subscription. - // Will be revised once we add multi currency support. + // Currency The fiat currency in which the subscription is invoiced. Currency CurrencyCode `json:"currency"` // CustomerId The customer ID of the subscription. @@ -8607,6 +8667,9 @@ type SubscriptionItem struct { // CreatedAt Timestamp of when the resource was created. CreatedAt time.Time `json:"createdAt"` + // Currency The materialized currency of the item's rate card. Present for priced items. + Currency *CurrencyCodeOrCustom `json:"currency,omitempty"` + // DeletedAt Timestamp of when the resource was permanently deleted. DeletedAt *time.Time `json:"deletedAt,omitempty"` @@ -45673,1044 +45736,1051 @@ var swaggerSpec = []string{ "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", + "s1/mMsOI18YLJNTx3P4fCzN4XKySBWhCQz6u4zpeIYxq9Ph8eZyIcxYOzH6ulJmWbuI9HXOZnC8Hk/f/", + "KSJ7zuITrl6ct9Ve/3ypmuFQILnoEJYTlpNhKVEpUeldddGJKnAYWHQCjCt65yzza9mf3LV0GSq/kP2X", + "8WUpG0deLAQbBBsEGwQbBBsEG81eLA23LlZpo0n3tf05/HkQv9EvRLGaA0vpN9Iv2MGTzhynkAqZLD8+", + "narHwrPUhZ388uDJG/30E76CoCoh7w4iCiIKOlWkU0VCKkIqQqo1vDuWINVCt446JLkePRFFo7vDtQGl", + "NQ+Vngt7NYdJdHZE8EXwRfBF8EXwRfB1JxwMlpLXYs+CZRoqfPB6aagu7/CuZql/1db5BHEEcQRxBHEE", + "cQRxBHF3zS5/I4eS2zxyO+1cS30HjG5cXOfzJGH4eD1A8ULNWw/zv34HlZvHMN9UgjGCMYIxgjGCMYIx", + "grG7pFHzdFQBsqfKSpuIscD7gc/DZ1m2qh8lROqIueWLHSp7WfaEW34DjkSvzpEzme2ft+5X04H4H1eD", + "j6Fccu8joCSgJKAkoCSgJKC80/6NJdI1KvnarZ6jwwVntkbohsyaDm3dk9eNEC9wdrtS9N+p9qKzxxuN", + "+tUDK8azoYA3f8B7npo214sQkRCREJEQkRCREJEQ8VYfADdz3WJIXEPhuP2ZZ9nBSneurQCXdZ/Va6R+", + "bCgR2k3XpxEdEh0SHRIdEh0SHRId3mgH283SoagcaG9/Hghucy3gF/euibkn1fsj4TaVqZNyHEN8k31e", + "7F6/YCeiuN9hvSRhlfKYYyPDzIhrnAwjbtDkzgFBJrQt/HIgNYPalF2E0DPOEyuzRLDUjoQOiaQwzOWo", + "hc21cjshN5PFYbr8hbaOldP+n6A3rifjTkZtYWFj/umlUEM7au1983W7NZYqfNyp4y3f+rO79ejDv9/7", + "297H8sP9f1vj+jYrx6LVDNm73d0HW92dre7OUXdnD/7rdLs7v7baLbyNr7XXirkVWz6PqRKv8uq2meEn", + "rSxxN3E3cTdxN3E3cfedsgStcuOpZ8HNGYU6usnEQqcdfKSEf1igKi47x+qNSgoPvQYfkQMY64mD99Qd", + "ctKwRKoTfK15pYSFUHwIj10rk4JL4j9s6cxRPUEgQSBBIEEgQSBBIEHgXYLAKQZbN+AOHPKvxnF4d+/m", + "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", + "lpnWXx/++v8DAAD//0ENhofjTgwA", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/client/javascript/src/client/schemas.ts b/api/client/javascript/src/client/schemas.ts index 7600f0aeea..aac73d88b1 100644 --- a/api/client/javascript/src/client/schemas.ts +++ b/api/client/javascript/src/client/schemas.ts @@ -3825,6 +3825,8 @@ export interface components { * @example USD */ CurrencyCode: string + /** @description Fiat or managed custom currency code. */ + CurrencyCodeOrCustom: string /** * @description Custom Invoicing app can be used for interface with any invoicing or payment system. * @@ -4130,6 +4132,11 @@ export interface components { * @example 2023-01-01T01:01:01.001Z */ billingAnchor?: Date + /** + * @description Controls how custom-currency cost bases are selected for the new subscription. + * @default dynamic + */ + costBasisMode?: components['schemas']['SubscriptionCostBasisMode'] /** @description The custom plan description which defines the Subscription. */ customPlan: components['schemas']['CustomPlanInput'] } @@ -4138,6 +4145,11 @@ export interface components { * @description Create a custom subscription. */ CustomSubscriptionCreate: { + /** + * @description Controls how custom-currency cost bases are selected for the new subscription. + * @default dynamic + */ + costBasisMode?: components['schemas']['SubscriptionCostBasisMode'] /** @description The custom plan description which defines the Subscription. */ customPlan: components['schemas']['CustomPlanInput'] /** @@ -9927,6 +9939,11 @@ export interface components { billingAnchor?: Date /** @description The settlement mode of the subscription. */ settlementMode?: components['schemas']['BillingSettlementMode'] + /** + * @description Controls how custom-currency cost bases are selected for the new subscription. + * @default dynamic + */ + costBasisMode?: components['schemas']['SubscriptionCostBasisMode'] } /** * Create from plan @@ -9950,6 +9967,11 @@ export interface components { description?: string /** @description The settlement mode of the subscription. */ settlementMode?: components['schemas']['BillingSettlementMode'] + /** + * @description Controls how custom-currency cost bases are selected for the new subscription. + * @default dynamic + */ + costBasisMode?: components['schemas']['SubscriptionCostBasisMode'] /** * @description Timing configuration for the change, when the change should take effect. * The default is immediate. @@ -11037,11 +11059,22 @@ export interface components { plan?: components['schemas']['PlanReference'] /** * Currency - * @description The currency code of the subscription. - * Will be revised once we add multi currency support. + * @description The fiat currency in which the subscription is invoiced. * @default USD */ currency: components['schemas']['CurrencyCode'] + /** + * Cost basis mode + * @description Controls whether custom-currency cost bases are resolved dynamically or pinned + * when their currency pair is introduced to the subscription. + * @default dynamic + */ + readonly costBasisMode: components['schemas']['SubscriptionCostBasisMode'] + /** + * Pinned cost bases + * @description Cost bases pinned to custom-currency pairs for this subscription. + */ + readonly costBasisPins: components['schemas']['SubscriptionCostBasisPin'][] /** * Billing cadence * Format: duration @@ -11409,6 +11442,26 @@ export interface components { /** @description Additional properties specific to the problem type may be present. */ extensions?: components['schemas']['SubscriptionErrorExtensions'] } + /** + * @description Controls how custom-currency cost bases are selected for the subscription. + * @enum {string} + */ + SubscriptionCostBasisMode: 'dynamic' | 'pinned' + /** @description A cost basis pinned to a custom-currency pair for the subscription. */ + SubscriptionCostBasisPin: { + /** + * @description The managed custom currency ID. + * @example 01G65Z755AFWAKHE12NY0CQ9FH + */ + readonly customCurrencyId: string + /** @description The fiat currency in which the subscription is invoiced. */ + readonly invoiceCurrency: components['schemas']['CurrencyCode'] + /** + * @description The pinned cost basis resource ID. + * @example 01G65Z755AFWAKHE12NY0CQ9FH + */ + readonly costBasisId: string + } /** @description Create a subscription. */ SubscriptionCreate: | components['schemas']['PlanSubscriptionCreate'] @@ -11507,11 +11560,22 @@ export interface components { plan?: components['schemas']['PlanReference'] /** * Currency - * @description The currency code of the subscription. - * Will be revised once we add multi currency support. + * @description The fiat currency in which the subscription is invoiced. * @default USD */ currency: components['schemas']['CurrencyCode'] + /** + * Cost basis mode + * @description Controls whether custom-currency cost bases are resolved dynamically or pinned + * when their currency pair is introduced to the subscription. + * @default dynamic + */ + readonly costBasisMode: components['schemas']['SubscriptionCostBasisMode'] + /** + * Pinned cost bases + * @description Cost bases pinned to custom-currency pairs for this subscription. + */ + readonly costBasisPins: components['schemas']['SubscriptionCostBasisPin'][] /** * Billing cadence * Format: duration @@ -11620,6 +11684,11 @@ export interface components { * We say "referenced by the Price" regardless of how a price itself is referenced, it colloquially makes sense to say "paying the same price for the same thing". In practice this should be derived from what's printed on the invoice line-item. */ key: string + /** + * Currency + * @description The materialized currency of the item's rate card. Present for priced items. + */ + readonly currency?: components['schemas']['CurrencyCodeOrCustom'] /** @description The feature's key (if present). */ featureKey?: string /** @@ -12569,6 +12638,7 @@ export type CreditNoteOriginalInvoiceRef = components['schemas']['CreditNoteOriginalInvoiceRef'] export type Currency = components['schemas']['Currency'] export type CurrencyCode = components['schemas']['CurrencyCode'] +export type CurrencyCodeOrCustom = components['schemas']['CurrencyCodeOrCustom'] export type CustomInvoicingApp = components['schemas']['CustomInvoicingApp'] export type CustomInvoicingAppReplaceUpdate = components['schemas']['CustomInvoicingAppReplaceUpdate'] @@ -13003,6 +13073,10 @@ export type SubscriptionChangeResponseBody = components['schemas']['SubscriptionChangeResponseBody'] export type SubscriptionConflictErrorResponse = components['schemas']['SubscriptionConflictErrorResponse'] +export type SubscriptionCostBasisMode = + components['schemas']['SubscriptionCostBasisMode'] +export type SubscriptionCostBasisPin = + components['schemas']['SubscriptionCostBasisPin'] export type SubscriptionCreate = components['schemas']['SubscriptionCreate'] export type SubscriptionEdit = components['schemas']['SubscriptionEdit'] export type SubscriptionEditOperation = diff --git a/api/client/javascript/src/zod/index.ts b/api/client/javascript/src/zod/index.ts index 2e4458dc5a..9e4dbc2772 100644 --- a/api/client/javascript/src/zod/index.ts +++ b/api/client/javascript/src/zod/index.ts @@ -14323,11 +14323,13 @@ export const createSubscriptionBodyOnePlanOneKeyMax = 64 export const createSubscriptionBodyOnePlanOneKeyRegExp = /^[a-z0-9]+(?:_[a-z0-9]+)*$/ +export const createSubscriptionBodyOneCostBasisModeDefault = 'dynamic' export const createSubscriptionBodyOneTimingDefault = 'immediate' export const createSubscriptionBodyOneCustomerIdRegExp = /^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$/ export const createSubscriptionBodyOneCustomerKeyMax = 256 +export const createSubscriptionBodyTwoCostBasisModeDefault = 'dynamic' export const createSubscriptionBodyTwoCustomPlanOneOneNameMax = 256 export const createSubscriptionBodyTwoCustomPlanOneOneDescriptionMax = 1024 @@ -14474,6 +14476,15 @@ export const CreateSubscriptionBody = zod .describe( 'The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the subscription start time will be used.', ), + costBasisMode: zod + .enum(['dynamic', 'pinned']) + .describe( + 'Controls how custom-currency cost bases are selected for the subscription.', + ) + .default(createSubscriptionBodyOneCostBasisModeDefault) + .describe( + 'Controls how custom-currency cost bases are selected for the new subscription.', + ), customerId: zod.coerce .string() .regex(createSubscriptionBodyOneCustomerIdRegExp) @@ -14565,6 +14576,15 @@ export const CreateSubscriptionBody = zod .describe( 'The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the subscription start time will be used.', ), + costBasisMode: zod + .enum(['dynamic', 'pinned']) + .describe( + 'Controls how custom-currency cost bases are selected for the subscription.', + ) + .default(createSubscriptionBodyTwoCostBasisModeDefault) + .describe( + 'Controls how custom-currency cost bases are selected for the new subscription.', + ), customerId: zod.coerce .string() .regex(createSubscriptionBodyTwoCustomerIdRegExp) @@ -17027,6 +17047,8 @@ export const changeSubscriptionBodyOnePlanOneKeyMax = 64 export const changeSubscriptionBodyOnePlanOneKeyRegExp = /^[a-z0-9]+(?:_[a-z0-9]+)*$/ +export const changeSubscriptionBodyOneCostBasisModeDefault = 'dynamic' +export const changeSubscriptionBodyTwoCostBasisModeDefault = 'dynamic' export const changeSubscriptionBodyTwoCustomPlanOneOneNameMax = 256 export const changeSubscriptionBodyTwoCustomPlanOneOneDescriptionMax = 1024 @@ -17168,6 +17190,15 @@ export const ChangeSubscriptionBody = zod .describe( 'The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used.', ), + costBasisMode: zod + .enum(['dynamic', 'pinned']) + .describe( + 'Controls how custom-currency cost bases are selected for the subscription.', + ) + .default(changeSubscriptionBodyOneCostBasisModeDefault) + .describe( + 'Controls how custom-currency cost bases are selected for the new subscription.', + ), description: zod.coerce .string() .optional() @@ -17245,6 +17276,15 @@ export const ChangeSubscriptionBody = zod .describe( 'The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used.', ), + costBasisMode: zod + .enum(['dynamic', 'pinned']) + .describe( + 'Controls how custom-currency cost bases are selected for the subscription.', + ) + .default(changeSubscriptionBodyTwoCostBasisModeDefault) + .describe( + 'Controls how custom-currency cost bases are selected for the new subscription.', + ), customPlan: zod .object({ alignment: zod diff --git a/api/client/python/openmeter/_generated/models/__init__.py b/api/client/python/openmeter/_generated/models/__init__.py index ff6f038d7f..57a398ab7b 100644 --- a/api/client/python/openmeter/_generated/models/__init__.py +++ b/api/client/python/openmeter/_generated/models/__init__.py @@ -280,6 +280,7 @@ SubscriptionBadRequestErrorResponseExtensions, SubscriptionChangeResponseBody, SubscriptionConflictErrorResponse, + SubscriptionCostBasisPin, SubscriptionEdit, SubscriptionExpanded, SubscriptionItem, @@ -372,6 +373,7 @@ RemovePhaseShifting, SortOrder, StripeCheckoutSessionMode, + SubscriptionCostBasisMode, SubscriptionStatus, SubscriptionTimingEnum, TaxBehavior, @@ -657,6 +659,7 @@ "SubscriptionBadRequestErrorResponseExtensions", "SubscriptionChangeResponseBody", "SubscriptionConflictErrorResponse", + "SubscriptionCostBasisPin", "SubscriptionEdit", "SubscriptionExpanded", "SubscriptionItem", @@ -746,6 +749,7 @@ "RemovePhaseShifting", "SortOrder", "StripeCheckoutSessionMode", + "SubscriptionCostBasisMode", "SubscriptionStatus", "SubscriptionTimingEnum", "TaxBehavior", diff --git a/api/client/python/openmeter/_generated/models/_enums.py b/api/client/python/openmeter/_generated/models/_enums.py index 7035201dd8..283efe130f 100644 --- a/api/client/python/openmeter/_generated/models/_enums.py +++ b/api/client/python/openmeter/_generated/models/_enums.py @@ -840,6 +840,15 @@ class StripeCheckoutSessionMode(str, Enum, metaclass=CaseInsensitiveEnumMeta): """SETUP.""" +class SubscriptionCostBasisMode(str, Enum, metaclass=CaseInsensitiveEnumMeta): + """Controls how custom-currency cost bases are selected for the subscription.""" + + DYNAMIC = "dynamic" + """DYNAMIC.""" + PINNED = "pinned" + """PINNED.""" + + class SubscriptionStatus(str, Enum, metaclass=CaseInsensitiveEnumMeta): """Subscription status.""" diff --git a/api/client/python/openmeter/_generated/models/_models.py b/api/client/python/openmeter/_generated/models/_models.py index 69b864826f..fb5bf006a5 100644 --- a/api/client/python/openmeter/_generated/models/_models.py +++ b/api/client/python/openmeter/_generated/models/_models.py @@ -3899,6 +3899,9 @@ class CustomSubscriptionChange(_Model): normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used. :vartype billing_anchor: ~datetime.datetime + :ivar cost_basis_mode: Controls how custom-currency cost bases are selected for the new + subscription. Known values are: "dynamic" and "pinned". + :vartype cost_basis_mode: str or ~openmeter.models.SubscriptionCostBasisMode :ivar custom_plan: The custom plan description which defines the Subscription. Required. :vartype custom_plan: ~openmeter._generated.models.CustomPlanInput """ @@ -3913,6 +3916,11 @@ class CustomSubscriptionChange(_Model): """The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used.""" + cost_basis_mode: Optional[Union[str, "_models.SubscriptionCostBasisMode"]] = rest_field( + name="costBasisMode", visibility=["read", "create", "update", "delete", "query"] + ) + """Controls how custom-currency cost bases are selected for the new subscription. Known values + are: \"dynamic\" and \"pinned\".""" custom_plan: "_models.CustomPlanInput" = rest_field( name="customPlan", visibility=["read", "create", "update", "delete", "query"] ) @@ -3925,6 +3933,7 @@ def __init__( timing: "_types.SubscriptionTiming", custom_plan: "_models.CustomPlanInput", billing_anchor: Optional[datetime.datetime] = None, + cost_basis_mode: Optional[Union[str, "_models.SubscriptionCostBasisMode"]] = None, ) -> None: ... @overload @@ -3941,6 +3950,9 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class CustomSubscriptionCreate(_Model): """Create custom. + :ivar cost_basis_mode: Controls how custom-currency cost bases are selected for the new + subscription. Known values are: "dynamic" and "pinned". + :vartype cost_basis_mode: str or ~openmeter.models.SubscriptionCostBasisMode :ivar custom_plan: The custom plan description which defines the Subscription. Required. :vartype custom_plan: ~openmeter._generated.models.CustomPlanInput :ivar timing: Timing configuration for the change, when the change should take effect. The @@ -3958,6 +3970,11 @@ class CustomSubscriptionCreate(_Model): :vartype billing_anchor: ~datetime.datetime """ + cost_basis_mode: Optional[Union[str, "_models.SubscriptionCostBasisMode"]] = rest_field( + name="costBasisMode", visibility=["read", "create", "update", "delete", "query"] + ) + """Controls how custom-currency cost bases are selected for the new subscription. Known values + are: \"dynamic\" and \"pinned\".""" custom_plan: "_models.CustomPlanInput" = rest_field( name="customPlan", visibility=["read", "create", "update", "delete", "query"] ) @@ -3988,6 +4005,7 @@ def __init__( self, *, custom_plan: "_models.CustomPlanInput", + cost_basis_mode: Optional[Union[str, "_models.SubscriptionCostBasisMode"]] = None, timing: Optional["_types.SubscriptionTiming"] = None, customer_id: Optional[str] = None, customer_key: Optional[str] = None, @@ -12167,6 +12185,9 @@ class PlanSubscriptionChange(_Model): :ivar settlement_mode: The settlement mode of the subscription. Known values are: "credit_then_invoice" and "credit_only". :vartype settlement_mode: str or ~openmeter.models.BillingSettlementMode + :ivar cost_basis_mode: Controls how custom-currency cost bases are selected for the new + subscription. Known values are: "dynamic" and "pinned". + :vartype cost_basis_mode: str or ~openmeter.models.SubscriptionCostBasisMode """ timing: "_types.SubscriptionTiming" = rest_field(visibility=["read", "create", "update", "delete", "query"]) @@ -12199,6 +12220,11 @@ class PlanSubscriptionChange(_Model): ) """The settlement mode of the subscription. Known values are: \"credit_then_invoice\" and \"credit_only\".""" + cost_basis_mode: Optional[Union[str, "_models.SubscriptionCostBasisMode"]] = rest_field( + name="costBasisMode", visibility=["read", "create", "update", "delete", "query"] + ) + """Controls how custom-currency cost bases are selected for the new subscription. Known values + are: \"dynamic\" and \"pinned\".""" @overload def __init__( @@ -12213,6 +12239,7 @@ def __init__( description: Optional[str] = None, billing_anchor: Optional[datetime.datetime] = None, settlement_mode: Optional[Union[str, "_models.BillingSettlementMode"]] = None, + cost_basis_mode: Optional[Union[str, "_models.SubscriptionCostBasisMode"]] = None, ) -> None: ... @overload @@ -12245,6 +12272,9 @@ class PlanSubscriptionCreate(_Model): :ivar settlement_mode: The settlement mode of the subscription. Known values are: "credit_then_invoice" and "credit_only". :vartype settlement_mode: str or ~openmeter.models.BillingSettlementMode + :ivar cost_basis_mode: Controls how custom-currency cost bases are selected for the new + subscription. Known values are: "dynamic" and "pinned". + :vartype cost_basis_mode: str or ~openmeter.models.SubscriptionCostBasisMode :ivar timing: Timing configuration for the change, when the change should take effect. The default is immediate. Is either a Union[str, "_models.SubscriptionTimingEnum"] type or a datetime.datetime type. @@ -12280,6 +12310,11 @@ class PlanSubscriptionCreate(_Model): ) """The settlement mode of the subscription. Known values are: \"credit_then_invoice\" and \"credit_only\".""" + cost_basis_mode: Optional[Union[str, "_models.SubscriptionCostBasisMode"]] = rest_field( + name="costBasisMode", visibility=["read", "create", "update", "delete", "query"] + ) + """Controls how custom-currency cost bases are selected for the new subscription. Known values + are: \"dynamic\" and \"pinned\".""" timing: Optional["_types.SubscriptionTiming"] = rest_field( visibility=["read", "create", "update", "delete", "query"] ) @@ -12312,6 +12347,7 @@ def __init__( name: Optional[str] = None, description: Optional[str] = None, settlement_mode: Optional[Union[str, "_models.BillingSettlementMode"]] = None, + cost_basis_mode: Optional[Union[str, "_models.SubscriptionCostBasisMode"]] = None, timing: Optional["_types.SubscriptionTiming"] = None, customer_id: Optional[str] = None, customer_key: Optional[str] = None, @@ -13953,6 +13989,10 @@ class Subscription(_Model): :vartype plan: ~openmeter._generated.models.PlanReference :ivar currency: Currency. Required. :vartype currency: str + :ivar cost_basis_mode: Cost basis mode. Required. Known values are: "dynamic" and "pinned". + :vartype cost_basis_mode: str or ~openmeter.models.SubscriptionCostBasisMode + :ivar cost_basis_pins: Pinned cost bases. Required. + :vartype cost_basis_pins: list[~openmeter._generated.models.SubscriptionCostBasisPin] :ivar billing_cadence: Billing cadence. Required. :vartype billing_cadence: ~datetime.timedelta :ivar pro_rating_config: Pro-rating configuration. @@ -13999,6 +14039,12 @@ class Subscription(_Model): """The plan of the subscription.""" currency: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Currency. Required.""" + cost_basis_mode: Union[str, "_models.SubscriptionCostBasisMode"] = rest_field( + name="costBasisMode", visibility=["read"] + ) + """Cost basis mode. Required. Known values are: \"dynamic\" and \"pinned\".""" + cost_basis_pins: list["_models.SubscriptionCostBasisPin"] = rest_field(name="costBasisPins", visibility=["read"]) + """Pinned cost bases. Required.""" billing_cadence: datetime.timedelta = rest_field(name="billingCadence", visibility=["read"]) """Billing cadence. Required.""" pro_rating_config: Optional["_models.ProRatingConfig"] = rest_field(name="proRatingConfig", visibility=["read"]) @@ -14609,6 +14655,25 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) +class SubscriptionCostBasisPin(_Model): + """A cost basis pinned to a custom-currency pair for the subscription. + + :ivar custom_currency_id: The managed custom currency ID. Required. + :vartype custom_currency_id: str + :ivar invoice_currency: The fiat currency in which the subscription is invoiced. Required. + :vartype invoice_currency: str + :ivar cost_basis_id: The pinned cost basis resource ID. Required. + :vartype cost_basis_id: str + """ + + custom_currency_id: str = rest_field(name="customCurrencyId", visibility=["read"]) + """The managed custom currency ID. Required.""" + invoice_currency: str = rest_field(name="invoiceCurrency", visibility=["read"]) + """The fiat currency in which the subscription is invoiced. Required.""" + cost_basis_id: str = rest_field(name="costBasisId", visibility=["read"]) + """The pinned cost basis resource ID. Required.""" + + class SubscriptionEdit(_Model): """Subscription edit input. @@ -14689,6 +14754,10 @@ class SubscriptionExpanded(_Model): :vartype plan: ~openmeter._generated.models.PlanReference :ivar currency: Currency. Required. :vartype currency: str + :ivar cost_basis_mode: Cost basis mode. Required. Known values are: "dynamic" and "pinned". + :vartype cost_basis_mode: str or ~openmeter.models.SubscriptionCostBasisMode + :ivar cost_basis_pins: Pinned cost bases. Required. + :vartype cost_basis_pins: list[~openmeter._generated.models.SubscriptionCostBasisPin] :ivar billing_cadence: Billing cadence. Required. :vartype billing_cadence: ~datetime.timedelta :ivar pro_rating_config: Pro-rating configuration. @@ -14737,6 +14806,12 @@ class SubscriptionExpanded(_Model): """The plan of the subscription.""" currency: str = rest_field(visibility=["read", "create", "update", "delete", "query"]) """Currency. Required.""" + cost_basis_mode: Union[str, "_models.SubscriptionCostBasisMode"] = rest_field( + name="costBasisMode", visibility=["read"] + ) + """Cost basis mode. Required. Known values are: \"dynamic\" and \"pinned\".""" + cost_basis_pins: list["_models.SubscriptionCostBasisPin"] = rest_field(name="costBasisPins", visibility=["read"]) + """Pinned cost bases. Required.""" billing_cadence: datetime.timedelta = rest_field(name="billingCadence", visibility=["read"]) """Billing cadence. Required.""" pro_rating_config: Optional["_models.ProRatingConfig"] = rest_field(name="proRatingConfig", visibility=["read"]) @@ -14821,6 +14896,8 @@ class SubscriptionItem(_Model): colloquially makes sense to say "paying the same price for the same thing". In practice this should be derived from what's printed on the invoice line-item. Required. :vartype key: str + :ivar currency: Currency. + :vartype currency: str :ivar feature_key: The feature's key (if present). :vartype feature_key: str :ivar billing_cadence: Billing cadence. Required. @@ -14880,6 +14957,8 @@ class SubscriptionItem(_Model): We say \"referenced by the Price\" regardless of how a price itself is referenced, it colloquially makes sense to say \"paying the same price for the same thing\". In practice this should be derived from what's printed on the invoice line-item. Required.""" + currency: Optional[str] = rest_field(visibility=["read"]) + """Currency.""" feature_key: Optional[str] = rest_field( name="featureKey", visibility=["read", "create", "update", "delete", "query"] ) diff --git a/api/openapi.cloud.yaml b/api/openapi.cloud.yaml index 33486790cc..1541d1be68 100644 --- a/api/openapi.cloud.yaml +++ b/api/openapi.cloud.yaml @@ -14696,6 +14696,11 @@ components: Three-letter [ISO4217](https://www.iso.org/iso-4217-currency-codes.html) currency code. Custom three-letter currency codes are also supported for convenience. example: USD + CurrencyCodeOrCustom: + type: string + minLength: 3 + maxLength: 24 + description: Fiat or managed custom currency code. CustomInvoicingApp: type: object required: @@ -15095,6 +15100,11 @@ components: format: date-time description: The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used. example: '2023-01-01T01:01:01.001Z' + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the new subscription. + default: dynamic customPlan: allOf: - $ref: '#/components/schemas/CustomPlanInput' @@ -15105,6 +15115,11 @@ components: required: - customPlan properties: + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the new subscription. + default: dynamic customPlan: allOf: - $ref: '#/components/schemas/CustomPlanInput' @@ -22622,6 +22637,11 @@ components: allOf: - $ref: '#/components/schemas/BillingSettlementMode' description: The settlement mode of the subscription. + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the new subscription. + default: dynamic description: Change subscription based on plan. PlanSubscriptionCreate: type: object @@ -22656,6 +22676,11 @@ components: allOf: - $ref: '#/components/schemas/BillingSettlementMode' description: The settlement mode of the subscription. + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the new subscription. + default: dynamic timing: allOf: - $ref: '#/components/schemas/SubscriptionTiming' @@ -23899,6 +23924,8 @@ components: - status - customerId - currency + - costBasisMode + - costBasisPins - billingCadence - billingAnchor - settlementMode @@ -23986,11 +24013,25 @@ components: currency: allOf: - $ref: '#/components/schemas/CurrencyCode' - description: |- - The currency code of the subscription. - Will be revised once we add multi currency support. + description: The fiat currency in which the subscription is invoiced. title: Currency default: USD + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: |- + Controls whether custom-currency cost bases are resolved dynamically or pinned + when their currency pair is introduced to the subscription. + title: Cost basis mode + default: dynamic + readOnly: true + costBasisPins: + type: array + items: + $ref: '#/components/schemas/SubscriptionCostBasisPin' + description: Cost bases pinned to custom-currency pairs for this subscription. + title: Pinned cost bases + readOnly: true billingCadence: type: string format: duration @@ -24428,6 +24469,37 @@ components: description: |- The request could not be completed due to a conflict with the current state of the target resource. Variants with ErrorExtensions specific to subscriptions. + SubscriptionCostBasisMode: + type: string + enum: + - dynamic + - pinned + description: Controls how custom-currency cost bases are selected for the subscription. + SubscriptionCostBasisPin: + type: object + required: + - customCurrencyId + - invoiceCurrency + - costBasisId + properties: + customCurrencyId: + type: string + pattern: ^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$ + description: The managed custom currency ID. + example: 01G65Z755AFWAKHE12NY0CQ9FH + readOnly: true + invoiceCurrency: + allOf: + - $ref: '#/components/schemas/CurrencyCode' + description: The fiat currency in which the subscription is invoiced. + readOnly: true + costBasisId: + type: string + pattern: ^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$ + description: The pinned cost basis resource ID. + example: 01G65Z755AFWAKHE12NY0CQ9FH + readOnly: true + description: A cost basis pinned to a custom-currency pair for the subscription. SubscriptionCreate: oneOf: - $ref: '#/components/schemas/PlanSubscriptionCreate' @@ -24491,6 +24563,8 @@ components: - status - customerId - currency + - costBasisMode + - costBasisPins - billingCadence - billingAnchor - settlementMode @@ -24575,11 +24649,25 @@ components: currency: allOf: - $ref: '#/components/schemas/CurrencyCode' - description: |- - The currency code of the subscription. - Will be revised once we add multi currency support. + description: The fiat currency in which the subscription is invoiced. title: Currency default: USD + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: |- + Controls whether custom-currency cost bases are resolved dynamically or pinned + when their currency pair is introduced to the subscription. + title: Cost basis mode + default: dynamic + readOnly: true + costBasisPins: + type: array + items: + $ref: '#/components/schemas/SubscriptionCostBasisPin' + description: Cost bases pinned to custom-currency pairs for this subscription. + title: Pinned cost bases + readOnly: true billingCadence: type: string format: duration @@ -24711,6 +24799,12 @@ components: 2. If a Feature is not associated with the SubscriptionItem, it is referenced by the Price We say "referenced by the Price" regardless of how a price itself is referenced, it colloquially makes sense to say "paying the same price for the same thing". In practice this should be derived from what's printed on the invoice line-item. + currency: + allOf: + - $ref: '#/components/schemas/CurrencyCodeOrCustom' + description: The materialized currency of the item's rate card. Present for priced items. + title: Currency + readOnly: true featureKey: type: string minLength: 1 diff --git a/api/openapi.yaml b/api/openapi.yaml index ab748cdc46..5c5973c1b6 100644 --- a/api/openapi.yaml +++ b/api/openapi.yaml @@ -14854,6 +14854,11 @@ components: Three-letter [ISO4217](https://www.iso.org/iso-4217-currency-codes.html) currency code. Custom three-letter currency codes are also supported for convenience. example: USD + CurrencyCodeOrCustom: + type: string + minLength: 3 + maxLength: 24 + description: Fiat or managed custom currency code. CustomInvoicingApp: type: object required: @@ -15253,6 +15258,11 @@ components: format: date-time description: The billing anchor of the subscription. The provided date will be normalized according to the billing cadence to the nearest recurrence before start time. If not provided, the previous subscription billing anchor will be used. example: '2023-01-01T01:01:01.001Z' + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the new subscription. + default: dynamic customPlan: allOf: - $ref: '#/components/schemas/CustomPlanInput' @@ -15263,6 +15273,11 @@ components: required: - customPlan properties: + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the new subscription. + default: dynamic customPlan: allOf: - $ref: '#/components/schemas/CustomPlanInput' @@ -23405,6 +23420,11 @@ components: allOf: - $ref: '#/components/schemas/BillingSettlementMode' description: The settlement mode of the subscription. + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the new subscription. + default: dynamic description: Change subscription based on plan. PlanSubscriptionCreate: type: object @@ -23439,6 +23459,11 @@ components: allOf: - $ref: '#/components/schemas/BillingSettlementMode' description: The settlement mode of the subscription. + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the new subscription. + default: dynamic timing: allOf: - $ref: '#/components/schemas/SubscriptionTiming' @@ -24856,6 +24881,8 @@ components: - status - customerId - currency + - costBasisMode + - costBasisPins - billingCadence - billingAnchor - settlementMode @@ -24943,11 +24970,25 @@ components: currency: allOf: - $ref: '#/components/schemas/CurrencyCode' - description: |- - The currency code of the subscription. - Will be revised once we add multi currency support. + description: The fiat currency in which the subscription is invoiced. title: Currency default: USD + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: |- + Controls whether custom-currency cost bases are resolved dynamically or pinned + when their currency pair is introduced to the subscription. + title: Cost basis mode + default: dynamic + readOnly: true + costBasisPins: + type: array + items: + $ref: '#/components/schemas/SubscriptionCostBasisPin' + description: Cost bases pinned to custom-currency pairs for this subscription. + title: Pinned cost bases + readOnly: true billingCadence: type: string format: duration @@ -25385,6 +25426,37 @@ components: description: |- The request could not be completed due to a conflict with the current state of the target resource. Variants with ErrorExtensions specific to subscriptions. + SubscriptionCostBasisMode: + type: string + enum: + - dynamic + - pinned + description: Controls how custom-currency cost bases are selected for the subscription. + SubscriptionCostBasisPin: + type: object + required: + - customCurrencyId + - invoiceCurrency + - costBasisId + properties: + customCurrencyId: + type: string + pattern: ^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$ + description: The managed custom currency ID. + example: 01G65Z755AFWAKHE12NY0CQ9FH + readOnly: true + invoiceCurrency: + allOf: + - $ref: '#/components/schemas/CurrencyCode' + description: The fiat currency in which the subscription is invoiced. + readOnly: true + costBasisId: + type: string + pattern: ^[0-7][0-9A-HJKMNP-TV-Za-hjkmnp-tv-z]{25}$ + description: The pinned cost basis resource ID. + example: 01G65Z755AFWAKHE12NY0CQ9FH + readOnly: true + description: A cost basis pinned to a custom-currency pair for the subscription. SubscriptionCreate: oneOf: - $ref: '#/components/schemas/PlanSubscriptionCreate' @@ -25448,6 +25520,8 @@ components: - status - customerId - currency + - costBasisMode + - costBasisPins - billingCadence - billingAnchor - settlementMode @@ -25532,11 +25606,25 @@ components: currency: allOf: - $ref: '#/components/schemas/CurrencyCode' - description: |- - The currency code of the subscription. - Will be revised once we add multi currency support. + description: The fiat currency in which the subscription is invoiced. title: Currency default: USD + costBasisMode: + allOf: + - $ref: '#/components/schemas/SubscriptionCostBasisMode' + description: |- + Controls whether custom-currency cost bases are resolved dynamically or pinned + when their currency pair is introduced to the subscription. + title: Cost basis mode + default: dynamic + readOnly: true + costBasisPins: + type: array + items: + $ref: '#/components/schemas/SubscriptionCostBasisPin' + description: Cost bases pinned to custom-currency pairs for this subscription. + title: Pinned cost bases + readOnly: true billingCadence: type: string format: duration @@ -25668,6 +25756,12 @@ components: 2. If a Feature is not associated with the SubscriptionItem, it is referenced by the Price We say "referenced by the Price" regardless of how a price itself is referenced, it colloquially makes sense to say "paying the same price for the same thing". In practice this should be derived from what's printed on the invoice line-item. + currency: + allOf: + - $ref: '#/components/schemas/CurrencyCodeOrCustom' + description: The materialized currency of the item's rate card. Present for priced items. + title: Currency + readOnly: true featureKey: type: string minLength: 1 diff --git a/api/spec/packages/aip-client-javascript/src/index.ts b/api/spec/packages/aip-client-javascript/src/index.ts index 32cd5e6e71..589387b108 100644 --- a/api/spec/packages/aip-client-javascript/src/index.ts +++ b/api/spec/packages/aip-client-javascript/src/index.ts @@ -133,6 +133,7 @@ export type { CreditGrantInvoiceReference, BillingCustomerReference, SubscriptionReference, + SubscriptionCostBasisPin, AddonReference, FeatureReference, AppReference, @@ -194,9 +195,8 @@ export type { GetCreditBalanceParamsFilter, ListChargesParamsFilter, ListPlansParamsFilter, - SubscriptionCreate, RateCardProrationConfiguration, - Subscription, + SubscriptionCreate, UnitConfig, TaxCodeAppMapping, AppCapability, @@ -233,6 +233,7 @@ export type { TaxConfig, RateCardTaxConfig, OrganizationDefaultTaxCodes, + Subscription, PlanAddon, ProfileAppReferences, InvoiceWorkflowAppsReferences, @@ -257,8 +258,6 @@ export type { ListCustomerEntitlementAccessResponseData, WorkflowCollectionAlignmentAnchored, ChargeFlatFeeSystemIntent, - SubscriptionPagePaginatedResponse, - SubscriptionChangeResponse, SubscriptionCancel, SubscriptionChange, InvoiceUsageQuantityDetail, @@ -281,6 +280,8 @@ export type { CreditGrant, CreateChargeFlatFeeRequest, WorkflowTaxSettings, + SubscriptionPagePaginatedResponse, + SubscriptionChangeResponse, PlanAddonPagePaginatedResponse, IngestedEventPaginatedResponse, CurrencyPagePaginatedResponse, @@ -388,17 +389,22 @@ export type { RateCardMeteredEntitlementInput, CreditGrantPurchaseInput, VoidCreditGrantRequestInput, + SubscriptionCreateInput, UnitConfigInput, WorkflowInvoicingSettingsInput, GovernanceQueryRequestInput, + SubscriptionInput, IngestedEventInput, SubscriptionCancelInput, + SubscriptionChangeInput, InvoiceUsageQuantityDetailInput, InvoiceWorkflowInput, UpdateBillingInvoiceWorkflowInput, CreateCreditGrantRequestInput, CreditGrantInput, WorkflowTaxSettingsInput, + SubscriptionPagePaginatedResponseInput, + SubscriptionChangeResponseInput, IngestedEventPaginatedResponseInput, MeterQueryRequestInput, AppStripeCreateCheckoutSessionRequestOptionsInput, diff --git a/api/spec/packages/aip-client-javascript/src/models/operations/subscriptions.ts b/api/spec/packages/aip-client-javascript/src/models/operations/subscriptions.ts index 7e575cc6f3..a84f4c5ff5 100644 --- a/api/spec/packages/aip-client-javascript/src/models/operations/subscriptions.ts +++ b/api/spec/packages/aip-client-javascript/src/models/operations/subscriptions.ts @@ -11,13 +11,14 @@ import type { SubscriptionAddon, SubscriptionAddonPagePaginatedResponse, SubscriptionCancelInput, - SubscriptionChange, + SubscriptionChangeInput, SubscriptionChangeResponse, - SubscriptionCreate, + SubscriptionCreateInput, SubscriptionPagePaginatedResponse, } from '../types.js' -export type CreateSubscriptionRequest = AcceptDateStrings +export type CreateSubscriptionRequest = + AcceptDateStrings export type CreateSubscriptionResponse = Subscription export interface ListSubscriptionsQuery { @@ -59,7 +60,7 @@ export type UnscheduleCancelationResponse = Subscription export type ChangeSubscriptionRequest = AcceptDateStrings<{ subscriptionId: string - body: SubscriptionChange + body: SubscriptionChangeInput }> export type ChangeSubscriptionResponse = SubscriptionChangeResponse diff --git a/api/spec/packages/aip-client-javascript/src/models/schemas.ts b/api/spec/packages/aip-client-javascript/src/models/schemas.ts index c36d59952b..345b36c3e9 100644 --- a/api/spec/packages/aip-client-javascript/src/models/schemas.ts +++ b/api/spec/packages/aip-client-javascript/src/models/schemas.ts @@ -674,6 +674,13 @@ export const priceFree = z }) .describe('Free price.') +export const subscriptionCostBasisMode = z + .enum(['dynamic', 'pinned']) + + .describe( + 'Controls how custom-currency cost bases are selected for the subscription.', + ) + export const subscriptionStatus = z .enum(['active', 'inactive', 'canceled', 'scheduled']) .describe('Subscription status.') @@ -1780,6 +1787,17 @@ export const subscriptionReference = z 'Subscription reference represents a reference to the specific subscription item this entity represents.', ) +export const subscriptionCostBasisPin = z + .object({ + customCurrencyId: ulid, + invoiceCurrency: currencyCode, + costBasisId: ulid, + }) + + .describe( + 'A cost basis pinned to a custom-currency pair for the subscription.', + ) + export const addonReference = z .object({ id: ulid, @@ -2800,6 +2818,12 @@ export const voidCreditGrantRequest = z }) .describe('Request body for voiding a credit grant.') +export const rateCardProrationConfiguration = z + .object({ + mode: rateCardProrationMode, + }) + .describe('The proration configuration of the rate card.') + export const subscriptionCreate = z .object({ labels: labels.optional(), @@ -2825,30 +2849,10 @@ export const subscriptionCreate = z }) .describe('The plan reference of the subscription.'), billingAnchor: dateTime.optional(), + costBasisMode: subscriptionCostBasisMode.optional().default('dynamic'), }) .describe('Subscription create request.') -export const rateCardProrationConfiguration = z - .object({ - mode: rateCardProrationMode, - }) - .describe('The proration configuration of the rate card.') - -export const subscription = z - .object({ - id: ulid, - labels: labels.optional(), - createdAt: dateTime, - updatedAt: dateTime, - deletedAt: dateTime.optional(), - customerId: ulid, - planId: ulid.optional(), - billingAnchor: dateTime, - status: subscriptionStatus, - settlementMode: settlementMode.optional(), - }) - .describe('Subscription.') - export const subscriptionEditTiming = z .union([subscriptionEditTimingEnum, dateTime]) @@ -3411,6 +3415,29 @@ export const updateOrganizationDefaultTaxCodesRequest = z }) .describe('OrganizationDefaultTaxCodes update request.') +export const subscription = z + .object({ + id: ulid, + labels: labels.optional(), + createdAt: dateTime, + updatedAt: dateTime, + deletedAt: dateTime.optional(), + customerId: ulid, + planId: ulid.optional(), + invoiceCurrency: currencyCode, + costBasisMode: subscriptionCostBasisMode.default('dynamic'), + costBasisPins: z + .array(subscriptionCostBasisPin) + + .describe( + 'Cost bases pinned to custom-currency pairs for this subscription.', + ), + billingAnchor: dateTime, + status: subscriptionStatus, + settlementMode: settlementMode.optional(), + }) + .describe('Subscription.') + export const planAddon = z .object({ id: ulid, @@ -3842,20 +3869,6 @@ export const chargeFlatFeeSystemIntent = z 'Flat fee intent fields from the system lifecycle controller shadowed by a manual override.', ) -export const subscriptionPagePaginatedResponse = z - .object({ - data: z.array(subscription), - meta: paginatedMeta, - }) - .describe('Page paginated response.') - -export const subscriptionChangeResponse = z - .object({ - current: subscription, - next: subscription, - }) - .describe('Response for changing a subscription.') - export const subscriptionCancel = z .object({ timing: subscriptionEditTiming.optional().default('immediate'), @@ -3887,6 +3900,7 @@ export const subscriptionChange = z }) .describe('The plan reference of the subscription.'), billingAnchor: dateTime.optional(), + costBasisMode: subscriptionCostBasisMode.optional().default('dynamic'), timing: subscriptionEditTiming, }) .describe('Request for changing a subscription.') @@ -4333,6 +4347,20 @@ export const workflowTaxSettings = z }) .describe('Tax settings for a billing workflow.') +export const subscriptionPagePaginatedResponse = z + .object({ + data: z.array(subscription), + meta: paginatedMeta, + }) + .describe('Page paginated response.') + +export const subscriptionChangeResponse = z + .object({ + current: subscription, + next: subscription, + }) + .describe('Response for changing a subscription.') + export const planAddonPagePaginatedResponse = z .object({ data: z.array(planAddon), @@ -7358,6 +7386,13 @@ export const priceFreeWire = z }) .describe('Free price.') +export const subscriptionCostBasisModeWire = z + .enum(['dynamic', 'pinned']) + + .describe( + 'Controls how custom-currency cost bases are selected for the subscription.', + ) + export const subscriptionStatusWire = z .enum(['active', 'inactive', 'canceled', 'scheduled']) .describe('Subscription status.') @@ -8458,6 +8493,17 @@ export const subscriptionReferenceWire = z 'Subscription reference represents a reference to the specific subscription item this entity represents.', ) +export const subscriptionCostBasisPinWire = z + .strictObject({ + custom_currency_id: ulidWire, + invoice_currency: currencyCodeWire, + cost_basis_id: ulidWire, + }) + + .describe( + 'A cost basis pinned to a custom-currency pair for the subscription.', + ) + export const addonReferenceWire = z .strictObject({ id: ulidWire, @@ -9466,6 +9512,12 @@ export const voidCreditGrantRequestWire = z }) .describe('Request body for voiding a credit grant.') +export const rateCardProrationConfigurationWire = z + .strictObject({ + mode: rateCardProrationModeWire, + }) + .describe('The proration configuration of the rate card.') + export const subscriptionCreateWire = z .strictObject({ labels: labelsWire.optional(), @@ -9491,30 +9543,10 @@ export const subscriptionCreateWire = z }) .describe('The plan reference of the subscription.'), billing_anchor: dateTimeWire.optional(), + cost_basis_mode: subscriptionCostBasisModeWire.optional(), }) .describe('Subscription create request.') -export const rateCardProrationConfigurationWire = z - .strictObject({ - mode: rateCardProrationModeWire, - }) - .describe('The proration configuration of the rate card.') - -export const subscriptionWire = z - .strictObject({ - id: ulidWire, - labels: labelsWire.optional(), - created_at: dateTimeWire, - updated_at: dateTimeWire, - deleted_at: dateTimeWire.optional(), - customer_id: ulidWire, - plan_id: ulidWire.optional(), - billing_anchor: dateTimeWire, - status: subscriptionStatusWire, - settlement_mode: settlementModeWire.optional(), - }) - .describe('Subscription.') - export const subscriptionEditTimingWire = z .union([subscriptionEditTimingEnumWire, dateTimeWire]) @@ -10071,6 +10103,29 @@ export const updateOrganizationDefaultTaxCodesRequestWire = z }) .describe('OrganizationDefaultTaxCodes update request.') +export const subscriptionWire = z + .strictObject({ + id: ulidWire, + labels: labelsWire.optional(), + created_at: dateTimeWire, + updated_at: dateTimeWire, + deleted_at: dateTimeWire.optional(), + customer_id: ulidWire, + plan_id: ulidWire.optional(), + invoice_currency: currencyCodeWire, + cost_basis_mode: subscriptionCostBasisModeWire, + cost_basis_pins: z + .array(subscriptionCostBasisPinWire) + + .describe( + 'Cost bases pinned to custom-currency pairs for this subscription.', + ), + billing_anchor: dateTimeWire, + status: subscriptionStatusWire, + settlement_mode: settlementModeWire.optional(), + }) + .describe('Subscription.') + export const planAddonWire = z .strictObject({ id: ulidWire, @@ -10502,20 +10557,6 @@ export const chargeFlatFeeSystemIntentWire = z 'Flat fee intent fields from the system lifecycle controller shadowed by a manual override.', ) -export const subscriptionPagePaginatedResponseWire = z - .strictObject({ - data: z.array(subscriptionWire), - meta: paginatedMetaWire, - }) - .describe('Page paginated response.') - -export const subscriptionChangeResponseWire = z - .strictObject({ - current: subscriptionWire, - next: subscriptionWire, - }) - .describe('Response for changing a subscription.') - export const subscriptionCancelWire = z .strictObject({ timing: subscriptionEditTimingWire.optional(), @@ -10547,6 +10588,7 @@ export const subscriptionChangeWire = z }) .describe('The plan reference of the subscription.'), billing_anchor: dateTimeWire.optional(), + cost_basis_mode: subscriptionCostBasisModeWire.optional(), timing: subscriptionEditTimingWire, }) .describe('Request for changing a subscription.') @@ -10996,6 +11038,20 @@ export const workflowTaxSettingsWire = z }) .describe('Tax settings for a billing workflow.') +export const subscriptionPagePaginatedResponseWire = z + .strictObject({ + data: z.array(subscriptionWire), + meta: paginatedMetaWire, + }) + .describe('Page paginated response.') + +export const subscriptionChangeResponseWire = z + .strictObject({ + current: subscriptionWire, + next: subscriptionWire, + }) + .describe('Response for changing a subscription.') + export const planAddonPagePaginatedResponseWire = z .strictObject({ data: z.array(planAddonWire), diff --git a/api/spec/packages/aip-client-javascript/src/models/types.ts b/api/spec/packages/aip-client-javascript/src/models/types.ts index 41419c3c02..1371566e19 100644 --- a/api/spec/packages/aip-client-javascript/src/models/types.ts +++ b/api/spec/packages/aip-client-javascript/src/models/types.ts @@ -834,6 +834,16 @@ export interface SubscriptionReference { phase: { id: string; item: { id: string } } } +/** A cost basis pinned to a custom-currency pair for the subscription. */ +export interface SubscriptionCostBasisPin { + /** The managed custom currency ID. */ + customCurrencyId: string + /** The fiat currency in which the subscription is invoiced. */ + invoiceCurrency: string + /** The pinned cost basis resource ID. */ + costBasisId: string +} + /** Addon reference. */ export interface AddonReference { id: string @@ -1856,6 +1866,12 @@ export interface VoidCreditGrantRequest { paymentAdjustment: 'none' } +/** The proration configuration of the rate card. */ +export interface RateCardProrationConfiguration { + /** The proration mode of the rate card. */ + mode: 'no_proration' | 'prorate_prices' +} + /** Subscription create request. */ export interface SubscriptionCreate { labels?: Labels @@ -1886,50 +1902,8 @@ export interface SubscriptionCreate { * creation time as the billing anchor. */ billingAnchor?: Date -} - -/** The proration configuration of the rate card. */ -export interface RateCardProrationConfiguration { - /** The proration mode of the rate card. */ - mode: 'no_proration' | 'prorate_prices' -} - -/** Subscription. */ -export interface Subscription { - id: string - labels?: Labels - /** An ISO-8601 timestamp representation of entity creation date. */ - createdAt: Date - /** An ISO-8601 timestamp representation of entity last update date. */ - updatedAt: Date - /** An ISO-8601 timestamp representation of entity deletion date. */ - deletedAt?: Date - /** The customer ID of the subscription. */ - customerId: string - /** The plan ID of the subscription. Set if subscription is created from a plan. */ - planId?: string - /** - * A billing anchor is the fixed point in time that determines the subscription's - * recurring billing cycle. It affects when charges occur and how prorations are - * calculated. Common anchors: - * - * - Calendar month (1st of each month): `2025-01-01T00:00:00Z` - * - Subscription anniversary (day customer signed up) - * - Custom date (customer-specified day) - */ - billingAnchor: Date - /** The status of the subscription. */ - status: 'active' | 'inactive' | 'canceled' | 'scheduled' - /** - * Settlement mode for billing. - * - * Values: - * - * - `credit_then_invoice`: Credits are applied first, then any remainder is - * invoiced. - * - `credit_only`: Usage is settled exclusively against credits. - */ - settlementMode?: 'credit_then_invoice' | 'credit_only' + /** Controls how custom-currency cost bases are selected for the subscription. */ + costBasisMode: 'dynamic' | 'pinned' } /** @@ -2576,6 +2550,53 @@ export interface UpdateOrganizationDefaultTaxCodesRequest { creditGrantTaxCode?: TaxCodeReference } +/** Subscription. */ +export interface Subscription { + id: string + labels?: Labels + /** An ISO-8601 timestamp representation of entity creation date. */ + createdAt: Date + /** An ISO-8601 timestamp representation of entity last update date. */ + updatedAt: Date + /** An ISO-8601 timestamp representation of entity deletion date. */ + deletedAt?: Date + /** The customer ID of the subscription. */ + customerId: string + /** The plan ID of the subscription. Set if subscription is created from a plan. */ + planId?: string + /** The fiat currency in which the subscription is invoiced. */ + invoiceCurrency: string + /** + * Controls whether custom-currency cost bases are resolved dynamically or pinned + * when their currency pair is introduced to the subscription. + */ + costBasisMode: 'dynamic' | 'pinned' + /** Cost bases pinned to custom-currency pairs for this subscription. */ + costBasisPins: SubscriptionCostBasisPin[] + /** + * A billing anchor is the fixed point in time that determines the subscription's + * recurring billing cycle. It affects when charges occur and how prorations are + * calculated. Common anchors: + * + * - Calendar month (1st of each month): `2025-01-01T00:00:00Z` + * - Subscription anniversary (day customer signed up) + * - Custom date (customer-specified day) + */ + billingAnchor: Date + /** The status of the subscription. */ + status: 'active' | 'inactive' | 'canceled' | 'scheduled' + /** + * Settlement mode for billing. + * + * Values: + * + * - `credit_then_invoice`: Credits are applied first, then any remainder is + * invoiced. + * - `credit_only`: Usage is settled exclusively against credits. + */ + settlementMode?: 'credit_then_invoice' | 'credit_only' +} + /** * PlanAddon represents an association between a plan and an add-on, controlling * which add-ons are available for purchase within a plan. @@ -3021,20 +3042,6 @@ export interface ChargeFlatFeeSystemIntent { deletedAt?: Date } -/** Page paginated response. */ -export interface SubscriptionPagePaginatedResponse { - data: Subscription[] - meta: PaginatedMeta -} - -/** Response for changing a subscription. */ -export interface SubscriptionChangeResponse { - /** The current subscription before the change. */ - current: Subscription - /** The new state of the subscription after the change. */ - next: Subscription -} - /** Request for canceling a subscription. */ export interface SubscriptionCancel { /** If not provided the subscription is canceled immediately. */ @@ -3071,6 +3078,8 @@ export interface SubscriptionChange { * creation time as the billing anchor. */ billingAnchor?: Date + /** Controls how custom-currency cost bases are selected for the subscription. */ + costBasisMode: 'dynamic' | 'pinned' /** * Timing configuration for the change, when the change should take effect. For * changing a subscription, the accepted values depend on the subscription @@ -3581,6 +3590,20 @@ export interface WorkflowTaxSettings { defaultTaxConfig?: TaxConfig } +/** Page paginated response. */ +export interface SubscriptionPagePaginatedResponse { + data: Subscription[] + meta: PaginatedMeta +} + +/** Response for changing a subscription. */ +export interface SubscriptionChangeResponse { + /** The current subscription before the change. */ + current: Subscription + /** The new state of the subscription after the change. */ + next: Subscription +} + /** Page paginated response. */ export interface PlanAddonPagePaginatedResponse { data: PlanAddon[] @@ -5750,6 +5773,40 @@ export interface VoidCreditGrantRequestInput { paymentAdjustment?: 'none' } +/** Subscription create request. */ +export interface SubscriptionCreateInput { + labels?: Labels + /** + * Settlement mode for billing. + * + * Values: + * + * - `credit_then_invoice`: Credits are applied first, then any remainder is + * invoiced. + * - `credit_only`: Usage is settled exclusively against credits. + */ + settlementMode?: 'credit_then_invoice' | 'credit_only' + /** The customer to create the subscription for. */ + customer: { id?: string; key?: string } + /** The plan reference of the subscription. */ + plan: { id?: string; key?: string; version?: number } + /** + * A billing anchor is the fixed point in time that determines the subscription's + * recurring billing cycle. It affects when charges occur and how prorations are + * calculated. Common anchors: + * + * - Calendar month (1st of each month): `2025-01-01T00:00:00Z` + * - Subscription anniversary (day customer signed up) + * - Custom date (customer-specified day) + * + * If not provided, the subscription will be created with the subscription's + * creation time as the billing anchor. + */ + billingAnchor?: Date + /** Controls how custom-currency cost bases are selected for the subscription. */ + costBasisMode?: 'dynamic' | 'pinned' +} + /** * Unit conversion configuration. * @@ -5834,6 +5891,53 @@ export interface GovernanceQueryRequestInput { feature?: GovernanceQueryRequestFeatures } +/** Subscription. */ +export interface SubscriptionInput { + id: string + labels?: Labels + /** An ISO-8601 timestamp representation of entity creation date. */ + createdAt: Date + /** An ISO-8601 timestamp representation of entity last update date. */ + updatedAt: Date + /** An ISO-8601 timestamp representation of entity deletion date. */ + deletedAt?: Date + /** The customer ID of the subscription. */ + customerId: string + /** The plan ID of the subscription. Set if subscription is created from a plan. */ + planId?: string + /** The fiat currency in which the subscription is invoiced. */ + invoiceCurrency: string + /** + * Controls whether custom-currency cost bases are resolved dynamically or pinned + * when their currency pair is introduced to the subscription. + */ + costBasisMode?: 'dynamic' | 'pinned' + /** Cost bases pinned to custom-currency pairs for this subscription. */ + costBasisPins: SubscriptionCostBasisPin[] + /** + * A billing anchor is the fixed point in time that determines the subscription's + * recurring billing cycle. It affects when charges occur and how prorations are + * calculated. Common anchors: + * + * - Calendar month (1st of each month): `2025-01-01T00:00:00Z` + * - Subscription anniversary (day customer signed up) + * - Custom date (customer-specified day) + */ + billingAnchor: Date + /** The status of the subscription. */ + status: 'active' | 'inactive' | 'canceled' | 'scheduled' + /** + * Settlement mode for billing. + * + * Values: + * + * - `credit_then_invoice`: Credits are applied first, then any remainder is + * invoiced. + * - `credit_only`: Usage is settled exclusively against credits. + */ + settlementMode?: 'credit_then_invoice' | 'credit_only' +} + /** An ingested metering event with ingestion metadata. */ export interface IngestedEventInput { /** The original event ingested. */ @@ -5854,6 +5958,46 @@ export interface SubscriptionCancelInput { timing?: SubscriptionEditTiming } +/** Request for changing a subscription. */ +export interface SubscriptionChangeInput { + labels?: Labels + /** + * Settlement mode for billing. + * + * Values: + * + * - `credit_then_invoice`: Credits are applied first, then any remainder is + * invoiced. + * - `credit_only`: Usage is settled exclusively against credits. + */ + settlementMode?: 'credit_then_invoice' | 'credit_only' + /** The customer to create the subscription for. */ + customer: { id?: string; key?: string } + /** The plan reference of the subscription. */ + plan: { id?: string; key?: string; version?: number } + /** + * A billing anchor is the fixed point in time that determines the subscription's + * recurring billing cycle. It affects when charges occur and how prorations are + * calculated. Common anchors: + * + * - Calendar month (1st of each month): `2025-01-01T00:00:00Z` + * - Subscription anniversary (day customer signed up) + * - Custom date (customer-specified day) + * + * If not provided, the subscription will be created with the subscription's + * creation time as the billing anchor. + */ + billingAnchor?: Date + /** Controls how custom-currency cost bases are selected for the subscription. */ + costBasisMode?: 'dynamic' | 'pinned' + /** + * Timing configuration for the change, when the change should take effect. For + * changing a subscription, the accepted values depend on the subscription + * configuration. + */ + timing: SubscriptionEditTiming +} + /** * Usage quantity details on an invoice line item when UnitConfig is in effect. * @@ -6063,6 +6207,20 @@ export interface WorkflowTaxSettingsInput { defaultTaxConfig?: TaxConfig } +/** Page paginated response. */ +export interface SubscriptionPagePaginatedResponseInput { + data: SubscriptionInput[] + meta: PaginatedMeta +} + +/** Response for changing a subscription. */ +export interface SubscriptionChangeResponseInput { + /** The current subscription before the change. */ + current: SubscriptionInput + /** The new state of the subscription after the change. */ + next: SubscriptionInput +} + /** Cursor paginated response. */ export interface IngestedEventPaginatedResponseInput { data: IngestedEventInput[] diff --git a/api/spec/packages/aip/src/subscriptions/subscription.tsp b/api/spec/packages/aip/src/subscriptions/subscription.tsp index 014aec1648..e8bc6c34fa 100644 --- a/api/spec/packages/aip/src/subscriptions/subscription.tsp +++ b/api/spec/packages/aip/src/subscriptions/subscription.tsp @@ -17,6 +17,42 @@ enum SubscriptionStatus { Scheduled: "scheduled", } +/** + * Controls how custom-currency cost bases are selected for the subscription. + */ +@friendlyName("BillingSubscriptionCostBasisMode") +enum SubscriptionCostBasisMode { + /** Resolve the cost basis effective at the charge's full service period start. */ + Dynamic: "dynamic", + + /** Pin each custom-currency pair when it is introduced to the subscription. */ + Pinned: "pinned", +} + +/** + * A cost basis pinned to a custom-currency pair for the subscription. + */ +@friendlyName("BillingSubscriptionCostBasisPin") +model SubscriptionCostBasisPin { + /** + * The managed custom currency ID. + */ + @visibility(Lifecycle.Read) + custom_currency_id: Shared.ULID; + + /** + * The fiat currency in which the subscription is invoiced. + */ + @visibility(Lifecycle.Read) + invoice_currency: Shared.CurrencyCode; + + /** + * The pinned cost basis resource ID. + */ + @visibility(Lifecycle.Read) + cost_basis_id: Shared.ULID; +} + /** * Subscription edit timing. When immediate, the requested changes take effect * immediately. When next_billing_cycle, the requested changes take effect at the @@ -125,11 +161,19 @@ model SubscriptionCreate { @visibility(Lifecycle.Create) @summary("Billing anchor") billing_anchor?: Shared.DateTime; + + /** + * Controls how custom-currency cost bases are selected for the subscription. + */ + @visibility(Lifecycle.Create) + @summary("Cost basis mode") + cost_basis_mode?: SubscriptionCostBasisMode = SubscriptionCostBasisMode.Dynamic; } /** * Subscription. */ +#suppress "@openmeter/api-spec-aip/repeated-prefix-grouping" "cost basis mode and pins are separate subscription properties" @friendlyName("BillingSubscription") model Subscription { ...OmitProperties; @@ -148,6 +192,28 @@ model Subscription { @summary("Plan ID") plan_id?: Shared.ULID; + /** + * The fiat currency in which the subscription is invoiced. + */ + @visibility(Lifecycle.Read) + @summary("Invoice currency") + invoice_currency: Shared.CurrencyCode; + + /** + * Controls whether custom-currency cost bases are resolved dynamically or pinned + * when their currency pair is introduced to the subscription. + */ + @visibility(Lifecycle.Read) + @summary("Cost basis mode") + cost_basis_mode: SubscriptionCostBasisMode = SubscriptionCostBasisMode.Dynamic; + + /** + * Cost bases pinned to custom-currency pairs for this subscription. + */ + @visibility(Lifecycle.Read) + @summary("Pinned cost bases") + cost_basis_pins: SubscriptionCostBasisPin[]; + /** * A billing anchor is the fixed point in time that determines the subscription's * recurring billing cycle. It affects when charges occur and how prorations are diff --git a/api/spec/packages/legacy/src/productcatalog/subscription.tsp b/api/spec/packages/legacy/src/productcatalog/subscription.tsp index b9c00000c3..0a07a1f848 100644 --- a/api/spec/packages/legacy/src/productcatalog/subscription.tsp +++ b/api/spec/packages/legacy/src/productcatalog/subscription.tsp @@ -43,6 +43,33 @@ enum SubscriptionStatus { Scheduled: "scheduled", } +/** + * Controls how custom-currency cost bases are selected for the subscription. + */ +@friendlyName("SubscriptionCostBasisMode") +enum SubscriptionCostBasisMode { + Dynamic: "dynamic", + Pinned: "pinned", +} + +/** + * A cost basis pinned to a custom-currency pair for the subscription. + */ +@friendlyName("SubscriptionCostBasisPin") +model SubscriptionCostBasisPin { + /** The managed custom currency ID. */ + @visibility(Lifecycle.Read) + customCurrencyId: ULID; + + /** The fiat currency in which the subscription is invoiced. */ + @visibility(Lifecycle.Read) + invoiceCurrency: CurrencyCode; + + /** The pinned cost basis resource ID. */ + @visibility(Lifecycle.Read) + costBasisId: ULID; +} + /** * Alignment details enriched with the current billing period. */ @@ -93,12 +120,26 @@ model Subscription { plan?: PlanReference; /** - * The currency code of the subscription. - * Will be revised once we add multi currency support. + * The fiat currency in which the subscription is invoiced. */ @summary("Currency") currency: CurrencyCode = "USD"; + /** + * Controls whether custom-currency cost bases are resolved dynamically or pinned + * when their currency pair is introduced to the subscription. + */ + @visibility(Lifecycle.Read) + @summary("Cost basis mode") + costBasisMode: SubscriptionCostBasisMode = SubscriptionCostBasisMode.Dynamic; + + /** + * Cost bases pinned to custom-currency pairs for this subscription. + */ + @visibility(Lifecycle.Read) + @summary("Pinned cost bases") + costBasisPins: SubscriptionCostBasisPin[]; + /** * The billing cadence for the subscriptions. * Defines how often customers are billed using ISO8601 duration format. @@ -252,6 +293,13 @@ model SubscriptionItem { */ key: Key; + /** + * The materialized currency of the item's rate card. Present for priced items. + */ + @visibility(Lifecycle.Read) + @summary("Currency") + currency?: CurrencyCodeOrCustom; + /** * The feature's key (if present). */ @@ -415,6 +463,11 @@ model CustomSubscriptionChange { */ billingAnchor?: DateTime; + /** + * Controls how custom-currency cost bases are selected for the new subscription. + */ + costBasisMode?: SubscriptionCostBasisMode = SubscriptionCostBasisMode.Dynamic; + /** * The custom plan description which defines the Subscription. */ @@ -513,6 +566,11 @@ model PlanSubscriptionChange { * The settlement mode of the subscription. */ settlementMode?: SettlementMode; + + /** + * Controls how custom-currency cost bases are selected for the new subscription. + */ + costBasisMode?: SubscriptionCostBasisMode = SubscriptionCostBasisMode.Dynamic; } /** diff --git a/api/spec/packages/legacy/src/types.tsp b/api/spec/packages/legacy/src/types.tsp index 0e4a2ce93f..67ef19bf87 100644 --- a/api/spec/packages/legacy/src/types.tsp +++ b/api/spec/packages/legacy/src/types.tsp @@ -344,6 +344,14 @@ model Period { @example("USD") scalar CurrencyCode extends string; +/** + * Fiat or managed custom currency code. + */ +@friendlyName("CurrencyCodeOrCustom") +@minLength(3) +@maxLength(24) +scalar CurrencyCodeOrCustom extends string; + /** * [ISO 3166-1](https://www.iso.org/iso-3166-country-codes.html) alpha-2 country code. * Custom two-letter country codes are also supported for convenience. diff --git a/api/v3/api.gen.go b/api/v3/api.gen.go index 5800aa163b..308207c027 100644 --- a/api/v3/api.gen.go +++ b/api/v3/api.gen.go @@ -1061,6 +1061,24 @@ func (e BillingSettlementMode) Valid() bool { } } +// Defines values for BillingSubscriptionCostBasisMode. +const ( + BillingSubscriptionCostBasisModeDynamic BillingSubscriptionCostBasisMode = "dynamic" + BillingSubscriptionCostBasisModePinned BillingSubscriptionCostBasisMode = "pinned" +) + +// Valid indicates whether the value is a known member of the BillingSubscriptionCostBasisMode enum. +func (e BillingSubscriptionCostBasisMode) Valid() bool { + switch e { + case BillingSubscriptionCostBasisModeDynamic: + return true + case BillingSubscriptionCostBasisModePinned: + return true + default: + return false + } +} + // Defines values for BillingSubscriptionEditTimingEnum. const ( BillingSubscriptionEditTimingEnumImmediate BillingSubscriptionEditTimingEnum = "immediate" @@ -4432,6 +4450,13 @@ type BillingSubscription struct { // - Custom date (customer-specified day) BillingAnchor DateTime `json:"billing_anchor"` + // CostBasisMode Controls whether custom-currency cost bases are resolved dynamically or pinned + // when their currency pair is introduced to the subscription. + CostBasisMode BillingSubscriptionCostBasisMode `json:"cost_basis_mode"` + + // CostBasisPins Cost bases pinned to custom-currency pairs for this subscription. + CostBasisPins []BillingSubscriptionCostBasisPin `json:"cost_basis_pins"` + // CreatedAt An ISO-8601 timestamp representation of entity creation date. CreatedAt DateTime `json:"created_at"` @@ -4442,6 +4467,9 @@ type BillingSubscription struct { DeletedAt *DateTime `json:"deleted_at,omitempty"` Id ULID `json:"id"` + // InvoiceCurrency The fiat currency in which the subscription is invoiced. + InvoiceCurrency CurrencyCode `json:"invoice_currency"` + // Labels Labels store metadata of an entity that can be used for filtering an entity list or for searching across entity types. // // Keys must be of length 1-63 characters, and cannot start with "kong", "konnect", "mesh", "kic", or "_". @@ -4486,6 +4514,9 @@ type BillingSubscriptionChange struct { // creation time as the billing anchor. BillingAnchor *DateTime `json:"billing_anchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the subscription. + CostBasisMode *BillingSubscriptionCostBasisMode `json:"cost_basis_mode,omitempty"` + // Customer The customer to create the subscription for. Customer struct { // Id The ID of the customer to create the subscription for. @@ -4550,6 +4581,21 @@ type BillingSubscriptionChangeResponse struct { Next BillingSubscription `json:"next"` } +// BillingSubscriptionCostBasisMode Controls how custom-currency cost bases are selected for the subscription. +type BillingSubscriptionCostBasisMode string + +// BillingSubscriptionCostBasisPin A cost basis pinned to a custom-currency pair for the subscription. +type BillingSubscriptionCostBasisPin struct { + // CostBasisId The pinned cost basis resource ID. + CostBasisId ULID `json:"cost_basis_id"` + + // CustomCurrencyId The managed custom currency ID. + CustomCurrencyId ULID `json:"custom_currency_id"` + + // InvoiceCurrency The fiat currency in which the subscription is invoiced. + InvoiceCurrency CurrencyCode `json:"invoice_currency"` +} + // BillingSubscriptionCreate Subscription create request. type BillingSubscriptionCreate struct { // BillingAnchor A billing anchor is the fixed point in time that determines the subscription's @@ -4564,6 +4610,9 @@ type BillingSubscriptionCreate struct { // creation time as the billing anchor. BillingAnchor *DateTime `json:"billing_anchor,omitempty"` + // CostBasisMode Controls how custom-currency cost bases are selected for the subscription. + CostBasisMode *BillingSubscriptionCostBasisMode `json:"cost_basis_mode,omitempty"` + // Customer The customer to create the subscription for. Customer struct { // Id The ID of the customer to create the subscription for. @@ -13878,8 +13927,8 @@ func HandlerWithOptions(si ServerInterface, options ChiServerOptions) http.Handl // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "H4sIAAAAAAAC/+y97XLcOJYo+CqInNkoKyaVll2u6m53VNxQyXaPdspVGkvujr2d3kyIRCoxZgJsApSU", - "XeGIG/tjH2Dvj/sS+38fYN5knmQD5wAgSIJMMiVV2dW6cSe6rCSAgwPgfH/8PEnkJpeCCa0mL3+esFu6", + "H4sIAAAAAAAC/+y97XLcOJYo+CqInNkoKyaVll2u6m53VNxQyXaPdspVGkvujt1ObyZEIpUYMwE2AUrK", + "rnDEjf2xD7D3x32J/b8PMG8yT3ID5wAgSIJMMiVV2dW6cSe6rCSAgwPgfH/8PEnkJpeCCa0mL3+esFu6", "yTMG//1GFpc8TZl4jX80f7umWQn/kTJNeTZ5Ofk/ZElSSYTUZE2vGclZseFKcSmIluZfK1lsiF5zRWii", "uRST6YQLpalI2OTl5KMUVy91QRP28vnvnn/97JsXf3jxu999+/s//OHZ19+8mEwnSlNdqsnLF0dfTyea", "awNHBdrk06fp5Eep38hSpL1w/ig1ga861//298++ffGHb4+ef/Pi6PfPv37+/Ntvauu/qNavJjPrvxe0", @@ -13939,7 +13988,7 @@ var swaggerSpec = []string{ "U0TWuPMNurfHdhpSK8KE4bGqBmtLPq8ehSwcap1sD+rXBbIgolBhM7Tmkh+iUQ6jeI2qsC6ksOY9klNt", "4CE3a1Yw8lPOxFumWTEXdsdkQwW9MnJ4JXiTjK9Ysk0yRm7WPMMgQP9yERyypiLFDcGYuagFJFGR+rtR", "KRCwh9MQA7L4uMrkDQSGPZuBy9spVHYdDJVRlZgOobAFFYpbXWvNNkSvC1lerSvI5wLchoo8AY84+a//", - "+/8B07OZ2f03Sw/m4jmuGp5LwRLGr5kiN+xyLeVHIqTmK2v4U4ReylJ7XMEyBK2wai6+bk+X0CxT3gBv", + "5/8F07OZ2f03Sw/m4jmuGp5LwRLGr5kiN+xyLeVHIqTmK2v4U4ReylJ7XMEyBK2wai6+bk+X0CxT3gBv", "bawtfJ6+wq1tmKaG6MzFixhkeOwOtagl49zXnKKh2P5mbePHZ6cGuegtbd4OrsBlUkgwb15uidmuQq09", "p2jlxW3mhbwqGKQczkUpNM+ityKRYsWLjWqtZKA7PjsFZBhwv8CsAxsjbSX08aagwGkbDbOkYCOBr0i1", "Fh4GkDseGqLN2bnIyw5wH7MSdmUlAFFeAIVYmBu7MPe/Dc1r+A7zAlo32wwBUCDkHqdMpxW9uuFZZh8T", @@ -14012,7 +14061,7 @@ var swaggerSpec = []string{ "HcXzHA4RYoszG7eL5GI0Bi/oLdKx2E2ht0PJVuvT/nbbobWqmswZrLyhKLCYXGzzqEm0hKyzsalpgSm9", "tZXIx5N/tBJ2XkvtkPoCVToocRdIvC1uFdcKWvaNNvtoSDrd/LzT1OOEkeA2vakLMkPtb69qat9wQ9yr", "uh6XuGo1DXlK2QZbRphHR7kywiRVZHlWyLRMtK3aOvMTLslliQ1+ZYn27yXYS5dIc6ZzgeliXEEyzY7l", - "25VOWJGY2xez/p7537ye2lRTUWR7cvRf/+N/Pjs6cgXEjNptNOsjkHHxX1UlMVFuLo2c/anztCq1eOi5", + "25VOWJGY2xez/p7537ye2lRTUWR7cvRf//1/PDs6cgXEjNptNOsjkHHxX1UlMVFuLo2c/anztCq1eOi5", "1TjDuKPzazu2EaS97+Ieak1TSOa43BJqWYVXRiL9Aez9xWo8D22rrDq81Gl7LzusI2OARbO5yhdn0rxn", "Y1iHFt+HdHvxjORlgQEpYy4qod3uMaHCyRhQ98c3z6NNOcXQA5Rg+rDxCpez5oJHs9tOs9uXbvf6rVms", "Hi01gyw1j/aRX84+0pDKrUB+L5LzWDm5JWfEBOQ+zuReMspVu2SxfZsq+XFYaN4u2vRcQEML1zpWPEzz", @@ -14067,7 +14116,7 @@ var swaggerSpec = []string{ "jvX28Eoe7roowQodPVEbE5NTHRa/I1hKALXNy1KDfpDYIA7wsWNF0+1cuJ7/kWSSFo1+USPRX+++8NUe", "9swca72KZnPxlA0Nlooc3q8mEVUKRYDxneLR/fD+OC833CDk4x7nzZu1LjdUHJpZgerj0WNFlEqbwh79", "sfmmRJXJmlBF5pP35+SVzDJazCfmxcwnr8tCzid7SAZqu7mUHS2c8bed+4pvxEZW2xlwW7Gt/PN8AgzQ", - "bwk39F//1/9rfzBbg40NE3F2sOrgQfjitHinP3QmCHSkv1iRxCXaIpdLR2RXhdR93EP3YnkVlE+r91DL", + "bwk39F//9/9nfzBbg40NE3F2sOrgQfjitHinP3QmCHSkv1iRxCXaIpdLR2RXhdR93EP3YnkVlE+r91DL", "E9VBRYfOXNG7pdc9PpR/jIcCUtAdnwlctQGPIi55+3sPoK5awgKHeJKdDB1zEpocnXiGrjQVKS3SCFOv", "4WIa0I5uhh5USh3zwm28pgt34SLl1zwtoWS1OfsrKqy1V+GVMh/aUmKXECaaZxTCUdO5gChHmiRMgbvS", "B9901pWhaVowNcJ9eWwH9NeUsdPWOTkrZuS9i+bR9BZNs677fVjzwJWW8Yv9AxbrbZpkIji02J72IzOs", @@ -14227,7 +14276,7 @@ var swaggerSpec = []string{ "4JT2CkJzv8+3Qq+ZgiRMKN9MUx+VD91a17JwT/r6GUm3gm54AnGfNPkIojncgpdz4X7DP5ANRZdQ6Niy", "HWJsa+4t/oRgTm2tB5h0LnZPAn0jWDgFekiThOW2Naar3i9Sp/rXmjGFdnRHxoJsC7Snz0XK8kzCvf4j", "pk/fcGWohBFm617D9xUs/bF8+C4GSMbfoxXwdYN7jYvXjXIlzJ/B6cMv9o1TiS0SWs+dPfO+I1faMsPI", - "msmuMX/7tdD6a65jJWdFwoSO1rs8879Vff9rvT4cHXty9F//438+Ozo6sKzERpcfHQWx5kd+78K37Sxd", + "msmuMX/7tdD6a65jJWdFwoSO1rs8879Vff9rvT4cHXty9F///X88Ozo6sKzERpcfHQWx5kd+78K37Sxd", "nc27OvNs33+5sqE06HV0EsuqYIxcspUsKuUFkklUKLiDxWcuQrEdC+Y/EVL7BpLqYFbl22hbcpEq8ndW", "SNtdGvvPuUCDuWC3a1oqeGN93tyYeNY6kphY5I8cH3t43CQUbQzA9COzyRFAEyoFlGvFstWUKGmLj7lq", "ZGtW7Az1dI9iGDeKEAOwLrBiZzSnm+Itfl2fQmmqeTJwhnP4uCYK30/gZg+IAyMm+0AcNUEE0Z8GkKAI", @@ -14243,535 +14292,542 @@ var swaggerSpec = []string{ "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", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/api/v3/client/models_subscriptions.go b/api/v3/client/models_subscriptions.go index 9dd706a51e..f0b9e6c2f1 100644 --- a/api/v3/client/models_subscriptions.go +++ b/api/v3/client/models_subscriptions.go @@ -22,6 +22,13 @@ type BillingSubscription struct { CustomerID string `json:"customer_id"` // The plan ID of the subscription. Set if subscription is created from a plan. PlanID *string `json:"plan_id,omitempty"` + // The fiat currency in which the subscription is invoiced. + InvoiceCurrency string `json:"invoice_currency"` + // Controls whether custom-currency cost bases are resolved dynamically or pinned + // when their currency pair is introduced to the subscription. + CostBasisMode SubscriptionCostBasisMode `json:"cost_basis_mode"` + // Cost bases pinned to custom-currency pairs for this subscription. + CostBasisPins []SubscriptionCostBasisPin `json:"cost_basis_pins"` // A billing anchor is the fixed point in time that determines the subscription's // recurring billing cycle. It affects when charges occur and how prorations are // calculated. Common anchors: @@ -145,6 +152,8 @@ type SubscriptionChange struct { // If not provided, the subscription will be created with the subscription's // creation time as the billing anchor. BillingAnchor *time.Time `json:"billing_anchor,omitempty"` + // Controls how custom-currency cost bases are selected for the subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"cost_basis_mode,omitempty"` // Timing configuration for the change, when the change should take effect. For // changing a subscription, the accepted values depend on the subscription // configuration. @@ -189,6 +198,33 @@ type SubscriptionChangeResponse struct { Next BillingSubscription `json:"next"` } +// Controls how custom-currency cost bases are selected for the subscription. +type SubscriptionCostBasisMode string + +const ( + SubscriptionCostBasisModeDynamic SubscriptionCostBasisMode = "dynamic" + SubscriptionCostBasisModePinned SubscriptionCostBasisMode = "pinned" +) + +func (value SubscriptionCostBasisMode) Valid() bool { + switch value { + case SubscriptionCostBasisModeDynamic, SubscriptionCostBasisModePinned: + return true + default: + return false + } +} + +// A cost basis pinned to a custom-currency pair for the subscription. +type SubscriptionCostBasisPin struct { + // The managed custom currency ID. + CustomCurrencyID string `json:"custom_currency_id"` + // The fiat currency in which the subscription is invoiced. + InvoiceCurrency string `json:"invoice_currency"` + // The pinned cost basis resource ID. + CostBasisID string `json:"cost_basis_id"` +} + // Subscription create request. type SubscriptionCreate struct { Labels *map[string]string `json:"labels,omitempty"` @@ -215,6 +251,8 @@ type SubscriptionCreate struct { // If not provided, the subscription will be created with the subscription's // creation time as the billing anchor. BillingAnchor *time.Time `json:"billing_anchor,omitempty"` + // Controls how custom-currency cost bases are selected for the subscription. + CostBasisMode *SubscriptionCostBasisMode `json:"cost_basis_mode,omitempty"` } // Subscription edit timing defined when the changes should take effect. If the diff --git a/api/v3/handlers/subscriptions/change.go b/api/v3/handlers/subscriptions/change.go index 35c0786407..e9745901c8 100644 --- a/api/v3/handlers/subscriptions/change.go +++ b/api/v3/handlers/subscriptions/change.go @@ -12,6 +12,7 @@ import ( "github.com/openmeterio/openmeter/api/v3/request" "github.com/openmeterio/openmeter/openmeter/productcatalog" plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" + "github.com/openmeterio/openmeter/openmeter/subscription" subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" "github.com/openmeterio/openmeter/pkg/framework/commonhttp" "github.com/openmeterio/openmeter/pkg/framework/transport/httptransport" @@ -114,6 +115,7 @@ func (h *handler) ChangeSubscription() ChangeSubscriptionHandler { Name: curr.Name, Description: curr.Description, BillingAnchor: body.BillingAnchor, + CostBasisMode: subscription.CostBasisMode(lo.FromPtr(body.CostBasisMode)), } return ChangeSubscriptionRequest{ diff --git a/api/v3/handlers/subscriptions/convert.go b/api/v3/handlers/subscriptions/convert.go index b084e49aea..3de95ae263 100644 --- a/api/v3/handlers/subscriptions/convert.go +++ b/api/v3/handlers/subscriptions/convert.go @@ -32,16 +32,28 @@ func FromAPISubscriptionSortField(ctx context.Context, field string) (subscripti } func ToAPIBillingSubscription(subscription subscription.Subscription) api.BillingSubscription { + costBasisPins := make([]api.BillingSubscriptionCostBasisPin, 0, len(subscription.CostBasisPins)) + for _, pin := range subscription.CostBasisPins { + costBasisPins = append(costBasisPins, api.BillingSubscriptionCostBasisPin{ + CustomCurrencyId: pin.CustomCurrencyID, + InvoiceCurrency: pin.InvoiceCurrency.String(), + CostBasisId: pin.CostBasis.ID, + }) + } + subscriptionAPI := api.BillingSubscription{ - Id: subscription.ID, - CustomerId: subscription.CustomerId, - BillingAnchor: subscription.BillingAnchor, - SettlementMode: lo.ToPtr(api.BillingSettlementMode(subscription.SettlementMode)), - Status: api.BillingSubscriptionStatus(subscription.GetStatusAt(clock.Now())), - Labels: labels.FromMetadataAnnotations(subscription.Metadata, subscription.Annotations), - CreatedAt: subscription.CreatedAt, - UpdatedAt: subscription.UpdatedAt, - DeletedAt: subscription.DeletedAt, + Id: subscription.ID, + CustomerId: subscription.CustomerId, + InvoiceCurrency: subscription.InvoiceCurrency.String(), + CostBasisMode: api.BillingSubscriptionCostBasisMode(subscription.CostBasisMode.OrDefault()), + CostBasisPins: costBasisPins, + BillingAnchor: subscription.BillingAnchor, + SettlementMode: lo.ToPtr(api.BillingSettlementMode(subscription.SettlementMode)), + Status: api.BillingSubscriptionStatus(subscription.GetStatusAt(clock.Now())), + Labels: labels.FromMetadataAnnotations(subscription.Metadata, subscription.Annotations), + CreatedAt: subscription.CreatedAt, + UpdatedAt: subscription.UpdatedAt, + DeletedAt: subscription.DeletedAt, } // Only set if the subscription is created from a plan @@ -95,7 +107,8 @@ func FromAPIBillingSubscriptionCreate( CustomerID: customerID.ID, BillingAnchor: createSubscriptionRequest.BillingAnchor, ChangeSubscriptionWorkflowInput: subscriptionworkflow.ChangeSubscriptionWorkflowInput{ - Name: subscriptionName, + Name: subscriptionName, + CostBasisMode: subscription.CostBasisMode(lo.FromPtr(createSubscriptionRequest.CostBasisMode)), Timing: subscription.Timing{ // TODO: accept from request Enum: lo.ToPtr(subscription.TimingImmediate), diff --git a/api/v3/handlers/subscriptions/convert_test.go b/api/v3/handlers/subscriptions/convert_test.go new file mode 100644 index 0000000000..d67c59bc6c --- /dev/null +++ b/api/v3/handlers/subscriptions/convert_test.go @@ -0,0 +1,65 @@ +package subscriptions + +import ( + "testing" + + "github.com/stretchr/testify/require" + + api "github.com/openmeterio/openmeter/api/v3" + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/customer" + "github.com/openmeterio/openmeter/openmeter/subscription" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestToAPIBillingSubscriptionCurrencySemantics(t *testing.T) { + // given: + // - a pinned subscription with a materialized invoice fiat and cost-basis pin + sub := subscription.Subscription{ + InvoiceCurrency: currencyx.Code("USD"), + CostBasisMode: subscription.CostBasisModePinned, + CostBasisPins: []subscription.CostBasisPin{ + { + CustomCurrencyID: "currency_credits", + InvoiceCurrency: currencyx.Code("USD"), + CostBasis: currencies.CostBasis{ + NamespacedID: models.NamespacedID{ID: "cost_basis_credits_usd"}, + }, + }, + }, + } + + // when: + // - the subscription is mapped to the v3 API + result := ToAPIBillingSubscription(sub) + + // then: + // - the invoice currency, mode, and pin resource identities are preserved + require.Equal(t, "USD", result.InvoiceCurrency) + require.Equal(t, "pinned", string(result.CostBasisMode)) + require.Equal(t, "currency_credits", result.CostBasisPins[0].CustomCurrencyId) + require.Equal(t, "USD", result.CostBasisPins[0].InvoiceCurrency) + require.Equal(t, "cost_basis_credits_usd", result.CostBasisPins[0].CostBasisId) +} + +func TestFromAPIBillingSubscriptionCreateCostBasisMode(t *testing.T) { + // given: + // - a v3 create request explicitly selecting pinned cost bases + mode := api.BillingSubscriptionCostBasisMode("pinned") + request := api.BillingSubscriptionCreate{CostBasisMode: &mode} + + // when: + // - the request is mapped to the subscription workflow input + result, err := FromAPIBillingSubscriptionCreate( + "default", + customer.CustomerID{ID: "customer_1", Namespace: "default"}, + "Subscription", + request, + ) + + // then: + // - the mode survives the API boundary + require.NoError(t, err) + require.Equal(t, subscription.CostBasisModePinned, result.CostBasisMode) +} diff --git a/api/v3/openapi.yaml b/api/v3/openapi.yaml index fe709fcd29..617a5c98a3 100644 --- a/api/v3/openapi.yaml +++ b/api/v3/openapi.yaml @@ -7986,6 +7986,9 @@ components: - created_at - updated_at - customer_id + - invoice_currency + - cost_basis_mode + - cost_basis_pins - billing_anchor - status properties: @@ -8022,6 +8025,28 @@ components: description: The plan ID of the subscription. Set if subscription is created from a plan. title: Plan ID readOnly: true + invoice_currency: + allOf: + - $ref: '#/components/schemas/CurrencyCode' + description: The fiat currency in which the subscription is invoiced. + title: Invoice currency + readOnly: true + cost_basis_mode: + allOf: + - $ref: '#/components/schemas/BillingSubscriptionCostBasisMode' + description: |- + Controls whether custom-currency cost bases are resolved dynamically or pinned + when their currency pair is introduced to the subscription. + title: Cost basis mode + default: dynamic + readOnly: true + cost_basis_pins: + type: array + items: + $ref: '#/components/schemas/BillingSubscriptionCostBasisPin' + description: Cost bases pinned to custom-currency pairs for this subscription. + title: Pinned cost bases + readOnly: true billing_anchor: allOf: - $ref: '#/components/schemas/DateTime' @@ -8154,6 +8179,12 @@ components: If not provided, the subscription will be created with the subscription's creation time as the billing anchor. title: Billing anchor + cost_basis_mode: + allOf: + - $ref: '#/components/schemas/BillingSubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the subscription. + title: Cost basis mode + default: dynamic timing: allOf: - $ref: '#/components/schemas/BillingSubscriptionEditTiming' @@ -8179,6 +8210,36 @@ components: description: The new state of the subscription after the change. additionalProperties: false description: Response for changing a subscription. + BillingSubscriptionCostBasisMode: + type: string + enum: + - dynamic + - pinned + description: Controls how custom-currency cost bases are selected for the subscription. + BillingSubscriptionCostBasisPin: + type: object + required: + - custom_currency_id + - invoice_currency + - cost_basis_id + properties: + custom_currency_id: + allOf: + - $ref: '#/components/schemas/ULID' + description: The managed custom currency ID. + readOnly: true + invoice_currency: + allOf: + - $ref: '#/components/schemas/CurrencyCode' + description: The fiat currency in which the subscription is invoiced. + readOnly: true + cost_basis_id: + allOf: + - $ref: '#/components/schemas/ULID' + description: The pinned cost basis resource ID. + readOnly: true + additionalProperties: false + description: A cost basis pinned to a custom-currency pair for the subscription. BillingSubscriptionCreate: type: object required: @@ -8267,6 +8328,12 @@ components: If not provided, the subscription will be created with the subscription's creation time as the billing anchor. title: Billing anchor + cost_basis_mode: + allOf: + - $ref: '#/components/schemas/BillingSubscriptionCostBasisMode' + description: Controls how custom-currency cost bases are selected for the subscription. + title: Cost basis mode + default: dynamic additionalProperties: false description: Subscription create request. BillingSubscriptionEditTiming: diff --git a/app/common/subscription.go b/app/common/subscription.go index 0fc9cd3d9c..410b57eb1d 100644 --- a/app/common/subscription.go +++ b/app/common/subscription.go @@ -5,8 +5,10 @@ import ( "github.com/google/wire" + "github.com/openmeterio/openmeter/openmeter/currencies" "github.com/openmeterio/openmeter/openmeter/customer" entdb "github.com/openmeterio/openmeter/openmeter/ent/db" + "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/openmeter/productcatalog/addon" "github.com/openmeterio/openmeter/openmeter/productcatalog/feature" "github.com/openmeterio/openmeter/openmeter/productcatalog/plan" @@ -49,6 +51,8 @@ func NewSubscriptionServices( featureConnector feature.FeatureConnector, entitlementRegistry *registry.Entitlement, customerService customer.Service, + currencyService currencies.Service, + currencyResolver productcatalog.CurrencyResolver, planService plan.Service, planAddonService planaddon.Service, addonService addon.Service, @@ -79,6 +83,7 @@ func NewSubscriptionServices( FeatureFlags: featureFlags, Lockr: lockr, TaxCode: taxCodeService, + CostBasisService: currencyService, }) if err != nil { return SubscriptionServiceWithWorkflow{}, err @@ -104,6 +109,7 @@ func NewSubscriptionServices( subscriptionWorkflowService, err := subscriptionworkflowservice.NewWorkflowService(subscriptionworkflowservice.WorkflowServiceConfig{ Service: subscriptionService, CustomerService: customerService, + CurrencyResolver: currencyResolver, TransactionManager: subscriptionRepo, AddonService: subAddSvc, Logger: logger.With("subsystem", "subscription.workflow.service"), @@ -114,13 +120,17 @@ func NewSubscriptionServices( return SubscriptionServiceWithWorkflow{}, err } - planSubscriptionService := subscriptionchangeservice.New(subscriptionchangeservice.Config{ + planSubscriptionService, err := subscriptionchangeservice.New(subscriptionchangeservice.Config{ WorkflowService: subscriptionWorkflowService, SubscriptionService: subscriptionService, PlanService: planService, + CurrencyResolver: currencyResolver, CustomerService: customerService, Logger: logger.With("subsystem", "subscription.change.service"), }) + if err != nil { + return SubscriptionServiceWithWorkflow{}, err + } validator, err := subscriptioncustomer.NewValidator(subscriptionService, customerService) if err != nil { diff --git a/cmd/billing-worker/wire_gen.go b/cmd/billing-worker/wire_gen.go index d13ce7257e..69741ef4fd 100644 --- a/cmd/billing-worker/wire_gen.go +++ b/cmd/billing-worker/wire_gen.go @@ -248,7 +248,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl cleanup() return Application{}, nil, err } - featureResolver, err := featureresolver.New(featureConnector) + repository, err := common.NewCurrencyAdapter(client) if err != nil { cleanup7() cleanup6() @@ -259,7 +259,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl cleanup() return Application{}, nil, err } - repository, err := common.NewCurrencyAdapter(client) + currenciesService, err := common.NewCurrencyService(repository) if err != nil { cleanup7() cleanup6() @@ -270,7 +270,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl cleanup() return Application{}, nil, err } - currenciesService, err := common.NewCurrencyService(repository) + currencyResolver, err := currencyresolver.New(currenciesService) if err != nil { cleanup7() cleanup6() @@ -281,7 +281,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl cleanup() return Application{}, nil, err } - currencyResolver, err := currencyresolver.New(currenciesService) + featureResolver, err := featureresolver.New(featureConnector) if err != nil { cleanup7() cleanup6() @@ -348,7 +348,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl return Application{}, nil, err } ffxService := ffx.NewContextService() - subscriptionServiceWithWorkflow, err := common.NewSubscriptionServices(logger, client, featureConnector, entitlement, customerService, planService, planaddonService, addonService, eventbusPublisher, locker, ffxService, taxcodeService) + subscriptionServiceWithWorkflow, err := common.NewSubscriptionServices(logger, client, featureConnector, entitlement, customerService, currenciesService, currencyResolver, planService, planaddonService, addonService, eventbusPublisher, locker, ffxService, taxcodeService) if err != nil { cleanup7() cleanup6() diff --git a/cmd/jobs/internal/wire_gen.go b/cmd/jobs/internal/wire_gen.go index 36b198d1ad..8f23f75a89 100644 --- a/cmd/jobs/internal/wire_gen.go +++ b/cmd/jobs/internal/wire_gen.go @@ -257,7 +257,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl cleanup() return Application{}, nil, err } - featureResolver, err := featureresolver.New(featureConnector) + repository, err := common.NewCurrencyAdapter(client) if err != nil { cleanup7() cleanup6() @@ -268,7 +268,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl cleanup() return Application{}, nil, err } - repository, err := common.NewCurrencyAdapter(client) + currenciesService, err := common.NewCurrencyService(repository) if err != nil { cleanup7() cleanup6() @@ -279,7 +279,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl cleanup() return Application{}, nil, err } - currenciesService, err := common.NewCurrencyService(repository) + currencyResolver, err := currencyresolver.New(currenciesService) if err != nil { cleanup7() cleanup6() @@ -290,7 +290,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl cleanup() return Application{}, nil, err } - currencyResolver, err := currencyresolver.New(currenciesService) + featureResolver, err := featureresolver.New(featureConnector) if err != nil { cleanup7() cleanup6() @@ -357,7 +357,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl return Application{}, nil, err } ffxService := ffx.NewContextService() - subscriptionServiceWithWorkflow, err := common.NewSubscriptionServices(logger, client, featureConnector, entitlement, customerService, planService, planaddonService, addonService, eventbusPublisher, locker, ffxService, taxcodeService) + subscriptionServiceWithWorkflow, err := common.NewSubscriptionServices(logger, client, featureConnector, entitlement, customerService, currenciesService, currencyResolver, planService, planaddonService, addonService, eventbusPublisher, locker, ffxService, taxcodeService) if err != nil { cleanup7() cleanup6() diff --git a/cmd/server/wire_gen.go b/cmd/server/wire_gen.go index 5fbcd589e1..428442d7f4 100644 --- a/cmd/server/wire_gen.go +++ b/cmd/server/wire_gen.go @@ -349,7 +349,7 @@ func initializeApplication(ctx context.Context, conf config.Configuration) (Appl return Application{}, nil, err } ffxService := ffx.NewContextService() - subscriptionServiceWithWorkflow, err := common.NewSubscriptionServices(logger, client, featureConnector, entitlement, customerService, planService, planaddonService, addonService, eventbusPublisher, locker, ffxService, taxcodeService) + subscriptionServiceWithWorkflow, err := common.NewSubscriptionServices(logger, client, featureConnector, entitlement, customerService, currenciesService, currencyResolver, planService, planaddonService, addonService, eventbusPublisher, locker, ffxService, taxcodeService) if err != nil { cleanup7() cleanup6() diff --git a/docs/migration-guides/2026-07-17-custom-currency-subscriptions.md b/docs/migration-guides/2026-07-17-custom-currency-subscriptions.md new file mode 100644 index 0000000000..95235cae74 --- /dev/null +++ b/docs/migration-guides/2026-07-17-custom-currency-subscriptions.md @@ -0,0 +1,35 @@ +# Custom-currency subscriptions + +This release is a breaking database upgrade for installations with existing subscriptions. Deploy the preceding expand-and-write release first: it materializes currency on every newly created or rewritten priced subscription item while continuing to read legacy rows. + +Before applying migration `20260717195001`, backfill the remaining legacy priced items. Those items predate rate-card currency overrides, so their currency is the subscription's existing fiat currency. + +```sql +-- This count must reach zero before the migration is applied. +SELECT count(*) AS priced_items_without_currency +FROM subscription_items +WHERE price IS NOT NULL + AND currency IS NULL + AND custom_currency_id IS NULL; + +UPDATE subscription_items AS item +SET currency = subscription.currency, + updated_at = now() +FROM subscription_phases AS phase +JOIN subscriptions AS subscription + ON subscription.id = phase.subscription_id + AND subscription.namespace = phase.namespace +WHERE item.phase_id = phase.id + AND item.namespace = phase.namespace + AND item.price IS NOT NULL + AND item.currency IS NULL + AND item.custom_currency_id IS NULL; + +SELECT count(*) AS priced_items_without_currency +FROM subscription_items +WHERE price IS NOT NULL + AND currency IS NULL + AND custom_currency_id IS NULL; +``` + +The migration deliberately performs no automatic backfill. It fails while a priced item has neither a fiat nor a custom-currency reference. Unpriced items must keep both currency columns null. diff --git a/openmeter/billing/worker/subscriptionsync/reconciler/reconciler.go b/openmeter/billing/worker/subscriptionsync/reconciler/reconciler.go index 7528ec9771..9e57568ca8 100644 --- a/openmeter/billing/worker/subscriptionsync/reconciler/reconciler.go +++ b/openmeter/billing/worker/subscriptionsync/reconciler/reconciler.go @@ -209,7 +209,7 @@ func (r *Reconciler) mapToSubscriptionWithSyncState(ctx context.Context, subs [] } func (r *Reconciler) ReconcileSubscription(ctx context.Context, subsID models.NamespacedID) error { - return r.subscriptionSync.SyncByID(ctx, subsID, time.Now()) + return r.subscriptionSync.SyncByID(ctx, subsID, time.Now(), subscriptionsync.SkipCustomCurrencySubscriptions()) } type ReconcilerAllInput struct { diff --git a/openmeter/billing/worker/subscriptionsync/reconciler/reconciler_test.go b/openmeter/billing/worker/subscriptionsync/reconciler/reconciler_test.go new file mode 100644 index 0000000000..c520b6a687 --- /dev/null +++ b/openmeter/billing/worker/subscriptionsync/reconciler/reconciler_test.go @@ -0,0 +1,44 @@ +package reconciler + +import ( + "context" + "testing" + "time" + + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/billing/worker/subscriptionsync" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestReconcileSubscriptionSkipsUnsupportedCustomCurrencies(t *testing.T) { + // given: + // - periodic reconciliation delegates to subscription sync + // when: + // - a subscription is reconciled + // then: + // - it opts into the temporary custom-currency skip behavior + spy := &subscriptionSyncSpy{} + reconciler := Reconciler{subscriptionSync: spy} + + err := reconciler.ReconcileSubscription(t.Context(), models.NamespacedID{ + Namespace: "namespace", + ID: "subscription-id", + }) + + require.NoError(t, err) + require.True(t, spy.options.SkipCustomCurrencySubscriptions) +} + +type subscriptionSyncSpy struct { + subscriptionsync.Service + options subscriptionsync.SynchronizeSubscriptionOptions +} + +func (s *subscriptionSyncSpy) SyncByID(_ context.Context, _ models.NamespacedID, _ time.Time, opts ...subscriptionsync.SynchronizeSubscriptionOption) error { + for _, opt := range opts { + opt(&s.options) + } + + return nil +} diff --git a/openmeter/billing/worker/subscriptionsync/service.go b/openmeter/billing/worker/subscriptionsync/service.go index 35c6d0fd52..9b2a4011ab 100644 --- a/openmeter/billing/worker/subscriptionsync/service.go +++ b/openmeter/billing/worker/subscriptionsync/service.go @@ -2,6 +2,7 @@ package subscriptionsync import ( "context" + "errors" "time" "github.com/openmeterio/openmeter/openmeter/billing" @@ -16,8 +17,8 @@ type Service interface { } type SyncService interface { - SyncByViewAndInvoiceCustomer(ctx context.Context, view subscription.SubscriptionView, asOf time.Time) error - SyncByIDAndInvoiceCustomer(ctx context.Context, subscriptionID models.NamespacedID, asOf time.Time) error + SyncByViewAndInvoiceCustomer(ctx context.Context, view subscription.SubscriptionView, asOf time.Time, opts ...SynchronizeSubscriptionOption) error + SyncByIDAndInvoiceCustomer(ctx context.Context, subscriptionID models.NamespacedID, asOf time.Time, opts ...SynchronizeSubscriptionOption) error SyncByView(ctx context.Context, view subscription.SubscriptionView, asOf time.Time, opts ...SynchronizeSubscriptionOption) error SyncByID(ctx context.Context, subscriptionID models.NamespacedID, asOf time.Time, opts ...SynchronizeSubscriptionOption) error } @@ -34,7 +35,8 @@ type SyncStateService interface { } type SynchronizeSubscriptionOptions struct { - DryRun bool + DryRun bool + SkipCustomCurrencySubscriptions bool } type SynchronizeSubscriptionOption func(*SynchronizeSubscriptionOptions) @@ -44,3 +46,16 @@ func EnableDryRun() SynchronizeSubscriptionOption { o.DryRun = true } } + +// SkipCustomCurrencySubscriptions makes automatic reconciliation ignore subscriptions +// that billing cannot represent yet. Explicit sync callers should omit this option so +// the unsupported operation remains visible to them. +func SkipCustomCurrencySubscriptions() SynchronizeSubscriptionOption { + return func(o *SynchronizeSubscriptionOptions) { + o.SkipCustomCurrencySubscriptions = true + } +} + +// ErrCustomCurrencyBillingNotSupported is returned when explicit sync reaches +// a subscription that billing cannot represent without currency conversion. +var ErrCustomCurrencyBillingNotSupported = errors.New("billing sync does not support subscriptions with custom-currency priced items") diff --git a/openmeter/billing/worker/subscriptionsync/service/base_test.go b/openmeter/billing/worker/subscriptionsync/service/base_test.go index 5c876ee2d0..be23549af5 100644 --- a/openmeter/billing/worker/subscriptionsync/service/base_test.go +++ b/openmeter/billing/worker/subscriptionsync/service/base_test.go @@ -502,7 +502,7 @@ func (s *SuiteBase) assertCharge(ctx context.Context, charge charges.Charge, sub s.Equal(expectedCharge.Status, string(usageBasedCharge.Status), "%s: status", childID) s.Equal(subsView.Subscription.SettlementMode, usageBasedCharge.Intent.GetSettlementMode(), "%s: settlement mode", childID) s.Equal(s.Customer.ID, usageBasedCharge.Intent.GetCustomerID(), "%s: customer id", childID) - s.Equal(subsView.Subscription.Currency, usageBasedCharge.Intent.GetCurrency(), "%s: currency", childID) + s.Equal(subsView.Subscription.InvoiceCurrency, usageBasedCharge.Intent.GetCurrency(), "%s: currency", childID) s.Equal(expectedCharge.Periods[idx], baseIntent.ServicePeriod, "%s: service period", childID) if len(expectedCharge.FullServicePeriods) > 0 { s.Equal(expectedCharge.FullServicePeriods[idx], baseIntent.FullServicePeriod, "%s: full service period", childID) @@ -536,7 +536,7 @@ func (s *SuiteBase) assertCharge(ctx context.Context, charge charges.Charge, sub s.Equal(expectedCharge.Status, string(flatFeeCharge.Status), "%s: status", childID) s.Equal(subsView.Subscription.SettlementMode, flatFeeCharge.Intent.GetSettlementMode(), "%s: settlement mode", childID) s.Equal(s.Customer.ID, flatFeeCharge.Intent.GetCustomerID(), "%s: customer id", childID) - s.Equal(subsView.Subscription.Currency, flatFeeCharge.Intent.GetCurrency(), "%s: currency", childID) + s.Equal(subsView.Subscription.InvoiceCurrency, flatFeeCharge.Intent.GetCurrency(), "%s: currency", childID) s.Equal(expectedCharge.Periods[idx], baseIntent.ServicePeriod, "%s: service period", childID) if len(expectedCharge.FullServicePeriods) > 0 { s.Equal(expectedCharge.FullServicePeriods[idx], baseIntent.FullServicePeriod, "%s: full service period", childID) diff --git a/openmeter/billing/worker/subscriptionsync/service/currency_boundary_test.go b/openmeter/billing/worker/subscriptionsync/service/currency_boundary_test.go new file mode 100644 index 0000000000..c95bae49ce --- /dev/null +++ b/openmeter/billing/worker/subscriptionsync/service/currency_boundary_test.go @@ -0,0 +1,263 @@ +package service + +import ( + "context" + "testing" + "time" + + "github.com/alpacahq/alpacadecimal" + "github.com/stretchr/testify/require" + "go.opentelemetry.io/otel/trace/noop" + + "github.com/openmeterio/openmeter/openmeter/billing" + "github.com/openmeterio/openmeter/openmeter/billing/worker/subscriptionsync" + syncreconciler "github.com/openmeterio/openmeter/openmeter/billing/worker/subscriptionsync/service/reconciler" + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/customer" + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/openmeter/subscription" + "github.com/openmeterio/openmeter/openmeter/testutils" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestCustomCurrencySubscriptionBillingBoundary(t *testing.T) { + view := newCustomCurrencySubscriptionView() + syncAdapter := &subscriptionSyncAdapterSpy{} + service := &Service{ + subscriptionService: &subscriptionServiceSpy{view: view}, + subscriptionSyncAdapter: syncAdapter, + logger: testutils.NewDiscardLogger(t), + tracer: noop.NewTracerProvider().Tracer("test"), + } + + t.Run("explicit sync fails before billing mutations", func(t *testing.T) { + // given: + // - a subscription with a priced custom-currency item + // when: + // - an explicit caller requests billing sync + // then: + // - sync fails with a conflict before requiring any billing dependency + err := service.SyncByView(t.Context(), view, time.Now()) + + require.ErrorIs(t, err, subscriptionsync.ErrCustomCurrencyBillingNotSupported) + require.True(t, models.IsGenericConflictError(err)) + }) + + t.Run("automatic sync skips the whole subscription", func(t *testing.T) { + // given: + // - a subscription with a priced custom-currency item + // when: + // - an automatic caller requests sync and customer invoicing + // then: + // - the subscription is ignored and customer invoicing is not attempted + err := service.SyncByViewAndInvoiceCustomer( + t.Context(), + view, + time.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) + + require.NoError(t, err) + require.Empty(t, syncAdapter.upserts) + }) + + t.Run("explicit sync rejects a stale fiat view when current state is custom", func(t *testing.T) { + // given: + // - an event-carried fiat-only view older than the persisted custom-currency state + // when: + // - an explicit caller synchronizes the stale view + // then: + // - persisted state keeps the custom-currency boundary from being bypassed + staleView := newFiatCurrencySubscriptionView() + err := service.SyncByView(t.Context(), staleView, time.Now()) + + require.ErrorIs(t, err, subscriptionsync.ErrCustomCurrencyBillingNotSupported) + require.True(t, models.IsGenericConflictError(err)) + require.Empty(t, syncAdapter.upserts) + }) + + t.Run("automatic sync skips a stale fiat view when current state is custom", func(t *testing.T) { + // given: + // - an event-carried fiat-only view older than the persisted custom-currency state + // when: + // - automatic reconciliation synchronizes the stale view + // then: + // - the whole subscription is skipped without billing mutations + staleView := newFiatCurrencySubscriptionView() + err := service.SyncByViewAndInvoiceCustomer( + t.Context(), + staleView, + time.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) + + require.NoError(t, err) + require.Empty(t, syncAdapter.upserts) + }) + + t.Run("scheduled sync event opts into skipping", func(t *testing.T) { + // given: + // - an automatic subscription-sync event for a custom-currency subscription + // when: + // - the event handler loads and synchronizes the current view + // then: + // - it skips without surfacing an error or mutating sync state + event := subscription.NewSubscriptionSyncEvent(t.Context(), view.Subscription) + + err := service.HandleSubscriptionSyncEvent(t.Context(), &event) + + require.NoError(t, err) + require.Empty(t, syncAdapter.upserts) + }) + + t.Run("deleted subscriptions still reconcile", func(t *testing.T) { + // given: + // - a deleted subscription whose last view contained a custom-currency item + // when: + // - automatic deletion cleanup runs with custom-currency skipping enabled + // then: + // - the deleted entity bypasses the guard and reaches persisted-state reconciliation + deletedAt := time.Now() + deletedView := view + deletedView.Subscription.DeletedAt = &deletedAt + deletedView.Subscription.InvoiceCurrency = currencyx.Code("USD") + + billingService := &billingServiceSpy{} + syncAdapter := &subscriptionSyncAdapterSpy{} + reconciler := &syncReconcilerSpy{} + service := &Service{ + billingService: billingService, + reconciler: reconciler, + subscriptionService: &subscriptionServiceSpy{view: deletedView}, + subscriptionSyncAdapter: syncAdapter, + logger: testutils.NewDiscardLogger(t), + tracer: noop.NewTracerProvider().Tracer("test"), + } + + err := service.SyncByView( + t.Context(), + deletedView, + time.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) + + require.NoError(t, err) + require.True(t, billingService.withLockCalled) + require.True(t, reconciler.planCalled) + require.Len(t, syncAdapter.upserts, 1) + require.False(t, syncAdapter.upserts[0].HasBillables) + }) +} + +func newCustomCurrencySubscriptionView() subscription.SubscriptionView { + customCurrency := ¤cies.Currency{ + NamespacedID: models.NamespacedID{ + Namespace: "namespace", + ID: "custom-currency-id", + }, + Code: "CREDITS", + Name: "Credits", + } + rateCard := &productcatalog.FlatFeeRateCard{ + RateCardMeta: productcatalog.RateCardMeta{ + Key: "custom-fee", + Name: "Custom fee", + Currency: customCurrency, + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ + Amount: alpacadecimal.NewFromInt(1), + }), + }, + } + + subs := subscription.Subscription{ + NamespacedID: models.NamespacedID{ + Namespace: "namespace", + ID: "subscription-id", + }, + CustomerId: "customer-id", + } + + return subscription.SubscriptionView{ + Subscription: subs, + Spec: subscription.SubscriptionSpec{ + Phases: map[string]*subscription.SubscriptionPhaseSpec{ + "phase": { + ItemsByKey: map[string][]*subscription.SubscriptionItemSpec{ + "custom-fee": { + { + CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ + PhaseKey: "phase", + ItemKey: "custom-fee", + RateCard: rateCard, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func newFiatCurrencySubscriptionView() subscription.SubscriptionView { + view := newCustomCurrencySubscriptionView() + view.Subscription.InvoiceCurrency = currencyx.Code("USD") + view.Spec.InvoiceCurrency = currencyx.Code("USD") + + rateCard := view.Spec.Phases["phase"].ItemsByKey["custom-fee"][0].RateCard.(*productcatalog.FlatFeeRateCard) + rateCard.RateCardMeta.Currency = currencyx.Code("USD") + + return view +} + +type billingServiceSpy struct { + billing.Service + withLockCalled bool +} + +func (s *billingServiceSpy) WithLock(ctx context.Context, _ customer.CustomerID, fn func(context.Context) error) error { + s.withLockCalled = true + return fn(ctx) +} + +func (s *billingServiceSpy) GetLinesForSubscription(context.Context, billing.GetLinesForSubscriptionInput) ([]billing.LineOrHierarchy, error) { + return nil, nil +} + +type subscriptionServiceSpy struct { + subscription.Service + view subscription.SubscriptionView +} + +func (s *subscriptionServiceSpy) List(context.Context, subscription.ListSubscriptionsInput) (subscription.SubscriptionList, error) { + return subscription.SubscriptionList{ + Items: []subscription.Subscription{s.view.Subscription}, + }, nil +} + +func (s *subscriptionServiceSpy) GetView(context.Context, models.NamespacedID) (subscription.SubscriptionView, error) { + return s.view, nil +} + +type subscriptionSyncAdapterSpy struct { + subscriptionsync.Adapter + upserts []subscriptionsync.UpsertSyncStateInput +} + +func (s *subscriptionSyncAdapterSpy) UpsertSyncState(_ context.Context, input subscriptionsync.UpsertSyncStateInput) error { + s.upserts = append(s.upserts, input) + return nil +} + +type syncReconcilerSpy struct { + syncreconciler.Reconciler + planCalled bool +} + +func (s *syncReconcilerSpy) Plan(context.Context, syncreconciler.PlanInput) (*syncreconciler.Plan, error) { + s.planCalled = true + return &syncreconciler.Plan{}, nil +} diff --git a/openmeter/billing/worker/subscriptionsync/service/handlers.go b/openmeter/billing/worker/subscriptionsync/service/handlers.go index 744d717a68..bd5a805280 100644 --- a/openmeter/billing/worker/subscriptionsync/service/handlers.go +++ b/openmeter/billing/worker/subscriptionsync/service/handlers.go @@ -8,6 +8,7 @@ import ( "github.com/samber/lo" "github.com/openmeterio/openmeter/openmeter/billing" + "github.com/openmeterio/openmeter/openmeter/billing/worker/subscriptionsync" "github.com/openmeterio/openmeter/openmeter/subscription" "github.com/openmeterio/openmeter/pkg/clock" "github.com/openmeterio/openmeter/pkg/models" @@ -23,7 +24,12 @@ func (s *Service) HandleCancelledEvent(ctx context.Context, event *subscription. if event.Spec.ActiveTo == nil { // Let's do one sync, just to make sure we have at least the new items lined up - err := s.synchronizeSubscriptionAndInvoiceCustomer(ctx, newSubscriptionReferenceOrView(event.SubscriptionView), now) + err := s.synchronizeSubscriptionAndInvoiceCustomer( + ctx, + newSubscriptionReferenceOrView(event.SubscriptionView), + now, + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) if err != nil { return err } @@ -32,7 +38,12 @@ func (s *Service) HandleCancelledEvent(ctx context.Context, event *subscription. } // Let's sync up to the end of the subscription - err := s.synchronizeSubscriptionAndInvoiceCustomer(ctx, newSubscriptionReferenceOrView(event.SubscriptionView), *event.Spec.ActiveTo) + err := s.synchronizeSubscriptionAndInvoiceCustomer( + ctx, + newSubscriptionReferenceOrView(event.SubscriptionView), + *event.Spec.ActiveTo, + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) if err != nil { return err } @@ -64,10 +75,15 @@ func (s *Service) HandleInvoiceCreation(ctx context.Context, event *billing.Stan for _, subscriptionID := range affectedSubscriptions { // We use the current time as reference point instead of the invoice, as if we are delayed // we might want to provision more lines - if err := s.synchronizeSubscriptionAndInvoiceCustomer(ctx, newSubscriptionReferenceOrView(models.NamespacedID{ - Namespace: event.Invoice.Namespace, - ID: subscriptionID, - }), clock.Now()); err != nil { + if err := s.synchronizeSubscriptionAndInvoiceCustomer( + ctx, + newSubscriptionReferenceOrView(models.NamespacedID{ + Namespace: event.Invoice.Namespace, + ID: subscriptionID, + }), + clock.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ); err != nil { return fmt.Errorf("syncing subscription[%s]: %w", subscriptionID, err) } } @@ -78,6 +94,11 @@ func (s *Service) HandleInvoiceCreation(ctx context.Context, event *billing.Stan // HandleDeletedEvent is a handler for the subscription deleted event, it will make sure that // we synchronize the subscription and invoice customer. func (s *Service) HandleDeletedEvent(ctx context.Context, event *subscription.DeletedEvent) error { - _, err := s.synchronizeSubscription(ctx, newSubscriptionReferenceOrView(event.Subscription.NamespacedID), clock.Now()) + _, err := s.synchronizeSubscription( + ctx, + newSubscriptionReferenceOrView(event.Subscription.NamespacedID), + clock.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) return err } diff --git a/openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge_test.go b/openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge_test.go index da92fc0a49..0f403544ce 100644 --- a/openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge_test.go +++ b/openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge_test.go @@ -381,9 +381,9 @@ func newChargePatchTestTarget(t *testing.T, settlementMode productcatalog.Settle Namespace: "namespace", ID: "subscription-id", }, - CustomerId: "customer-id", - Currency: currencyx.Code("USD"), - SettlementMode: settlementMode, + CustomerId: "customer-id", + InvoiceCurrency: currencyx.Code("USD"), + SettlementMode: settlementMode, }, } } diff --git a/openmeter/billing/worker/subscriptionsync/service/service.go b/openmeter/billing/worker/subscriptionsync/service/service.go index 19240b4371..109eb8ebf4 100644 --- a/openmeter/billing/worker/subscriptionsync/service/service.go +++ b/openmeter/billing/worker/subscriptionsync/service/service.go @@ -113,12 +113,12 @@ func (s *Service) GetSyncStates(ctx context.Context, input subscriptionsync.GetS }) } -func (s *Service) SyncByViewAndInvoiceCustomer(ctx context.Context, view subscription.SubscriptionView, asOf time.Time) error { - return s.synchronizeSubscriptionAndInvoiceCustomer(ctx, newSubscriptionReferenceOrView(view), asOf) +func (s *Service) SyncByViewAndInvoiceCustomer(ctx context.Context, view subscription.SubscriptionView, asOf time.Time, opts ...subscriptionsync.SynchronizeSubscriptionOption) error { + return s.synchronizeSubscriptionAndInvoiceCustomer(ctx, newSubscriptionReferenceOrView(view), asOf, opts...) } -func (s *Service) SyncByIDAndInvoiceCustomer(ctx context.Context, subscriptionID models.NamespacedID, asOf time.Time) error { - return s.synchronizeSubscriptionAndInvoiceCustomer(ctx, newSubscriptionReferenceOrView(subscriptionID), asOf) +func (s *Service) SyncByIDAndInvoiceCustomer(ctx context.Context, subscriptionID models.NamespacedID, asOf time.Time, opts ...subscriptionsync.SynchronizeSubscriptionOption) error { + return s.synchronizeSubscriptionAndInvoiceCustomer(ctx, newSubscriptionReferenceOrView(subscriptionID), asOf, opts...) } func (s *Service) SyncByView(ctx context.Context, view subscription.SubscriptionView, asOf time.Time, opts ...subscriptionsync.SynchronizeSubscriptionOption) error { diff --git a/openmeter/billing/worker/subscriptionsync/service/sync.go b/openmeter/billing/worker/subscriptionsync/service/sync.go index e3c88286db..f716c30bf6 100644 --- a/openmeter/billing/worker/subscriptionsync/service/sync.go +++ b/openmeter/billing/worker/subscriptionsync/service/sync.go @@ -60,11 +60,16 @@ func (s *Service) HandleSubscriptionSyncEvent(ctx context.Context, event *subscr return nil } - return s.synchronizeSubscriptionAndInvoiceCustomer(ctx, newSubscriptionReferenceOrView(event.Subscription), time.Now()) + return s.synchronizeSubscriptionAndInvoiceCustomer( + ctx, + newSubscriptionReferenceOrView(event.Subscription), + time.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) } -func (s *Service) synchronizeSubscriptionAndInvoiceCustomer(ctx context.Context, refOrView subscriptionReferenceOrView, asOf time.Time) error { - res, err := s.synchronizeSubscription(ctx, refOrView, asOf) +func (s *Service) synchronizeSubscriptionAndInvoiceCustomer(ctx context.Context, refOrView subscriptionReferenceOrView, asOf time.Time, opts ...subscriptionsync.SynchronizeSubscriptionOption) error { + res, err := s.synchronizeSubscription(ctx, refOrView, asOf, opts...) if err != nil { return fmt.Errorf("synchronize subscription: %w", err) } @@ -108,6 +113,7 @@ func (s *Service) synchronizeSubscription(ctx context.Context, refOrView subscri } var subsView *subscription.SubscriptionView + hasCustomCurrencyBillables := false if subs.IsDeleted() { subsView = nil } else if refOrView.Type() == SubscriptionReferenceTypeView { @@ -117,6 +123,18 @@ func (s *Service) synchronizeSubscription(ctx context.Context, refOrView subscri } subsView = &view + hasCustomCurrencyBillables = view.Spec.HasCustomCurrencyBillables() + if !hasCustomCurrencyBillables { + // Event-carried views can be older than the current subscription state. + // Check persisted state before allowing billing so a pre-edit fiat view + // cannot bypass the custom-currency boundary after a later edit. + currentView, err := s.subscriptionService.GetView(ctx, subscriptionID) + if err != nil { + return nil, err + } + + hasCustomCurrencyBillables = currentView.Spec.HasCustomCurrencyBillables() + } } else { view, err := s.subscriptionService.GetView(ctx, subscriptionID) if err != nil { @@ -124,6 +142,23 @@ func (s *Service) synchronizeSubscription(ctx context.Context, refOrView subscri } subsView = &view + hasCustomCurrencyBillables = view.Spec.HasCustomCurrencyBillables() + } + + if hasCustomCurrencyBillables { + err := fmt.Errorf("%w [namespace=%s subscription_id=%s]", subscriptionsync.ErrCustomCurrencyBillingNotSupported, subscriptionID.Namespace, subscriptionID.ID) + if options.SkipCustomCurrencySubscriptions { + s.logger.InfoContext(ctx, "subscription uses custom-currency priced items, skipping billing sync", + "namespace", subscriptionID.Namespace, + "subscription_id", subscriptionID.ID, + ) + + // A nil result also prevents the wrapping automatic path from invoicing + // unrelated pending lines for this subscription's customer. + return nil, nil + } + + return nil, models.NewGenericConflictError(err) } res := &synchronizeSubscriptionResult{ @@ -175,7 +210,7 @@ func (s *Service) synchronizeSubscription(ctx context.Context, refOrView subscri } cur, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat). - WithCode(subs.Currency). + WithCode(subs.InvoiceCurrency). Build() if err != nil { return nil, fmt.Errorf("getting currency calculator: %w", err) diff --git a/openmeter/billing/worker/subscriptionsync/service/targetstate/targetstate.go b/openmeter/billing/worker/subscriptionsync/service/targetstate/targetstate.go index 63407db206..512fef2144 100644 --- a/openmeter/billing/worker/subscriptionsync/service/targetstate/targetstate.go +++ b/openmeter/billing/worker/subscriptionsync/service/targetstate/targetstate.go @@ -113,7 +113,7 @@ func (b Builder) Build(ctx context.Context, input BuildInput) (State, error) { } currency, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat). - WithCode(subs.Subscription.Currency). + WithCode(subs.Subscription.InvoiceCurrency). Build() if err != nil { return State{}, fmt.Errorf("getting currency calculator: %w", err) diff --git a/openmeter/billing/worker/worker.go b/openmeter/billing/worker/worker.go index 0964044577..3981f56bf9 100644 --- a/openmeter/billing/worker/worker.go +++ b/openmeter/billing/worker/worker.go @@ -163,7 +163,12 @@ func (w *Worker) eventHandler(opts WorkerOptions) (*grouphandler.NoPublishingHan return nil } - return w.subscriptionSync.SyncByViewAndInvoiceCustomer(ctx, event.SubscriptionView, time.Now()) + return w.subscriptionSync.SyncByViewAndInvoiceCustomer( + ctx, + event.SubscriptionView, + time.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) }), grouphandler.NewGroupEventHandler(func(ctx context.Context, event *subscription.CancelledEvent) error { if event != nil && slices.Contains(w.lockdownNamespaces, event.Subscription.Namespace) { @@ -184,14 +189,24 @@ func (w *Worker) eventHandler(opts WorkerOptions) (*grouphandler.NoPublishingHan return nil } - return w.subscriptionSync.SyncByViewAndInvoiceCustomer(ctx, event.SubscriptionView, time.Now()) + return w.subscriptionSync.SyncByViewAndInvoiceCustomer( + ctx, + event.SubscriptionView, + time.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) }), grouphandler.NewGroupEventHandler(func(ctx context.Context, event *subscription.UpdatedEvent) error { if event != nil && slices.Contains(w.lockdownNamespaces, event.UpdatedView.Subscription.Namespace) { return nil } - return w.subscriptionSync.SyncByViewAndInvoiceCustomer(ctx, event.UpdatedView, time.Now()) + return w.subscriptionSync.SyncByViewAndInvoiceCustomer( + ctx, + event.UpdatedView, + time.Now(), + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) }), grouphandler.NewGroupEventHandler(func(ctx context.Context, event *subscription.SubscriptionSyncEvent) error { if event != nil && slices.Contains(w.lockdownNamespaces, event.Subscription.Namespace) { diff --git a/openmeter/currencies/adapter/currencies.go b/openmeter/currencies/adapter/currencies.go index 2b2a001264..c771a53592 100644 --- a/openmeter/currencies/adapter/currencies.go +++ b/openmeter/currencies/adapter/currencies.go @@ -19,7 +19,7 @@ import ( "github.com/openmeterio/openmeter/pkg/sortx" ) -func mapCostBasisFromDB(c *entdb.CurrencyCostBasis) currencies.CostBasis { +func FromDBCurrencyCostBasis(c *entdb.CurrencyCostBasis) currencies.CostBasis { var effectiveTo *time.Time if c.EffectiveTo != nil { t := c.EffectiveTo.In(time.UTC) @@ -106,7 +106,7 @@ func (a *adapter) CreateCostBasis(ctx context.Context, params currencies.CreateC return currencies.CostBasis{}, fmt.Errorf("failed to create cost basis: %w", err) } - return mapCostBasisFromDB(costBasis), nil + return FromDBCurrencyCostBasis(costBasis), nil }) } @@ -128,6 +128,38 @@ func (a *adapter) ListCostBases(ctx context.Context, params currencies.ListCostB return pagination.Result[currencies.CostBasis]{}, fmt.Errorf("failed to list cost bases: %w", err) } - return pagination.MapResult(paged, mapCostBasisFromDB), nil + return pagination.MapResult(paged, FromDBCurrencyCostBasis), nil + }) +} + +func (a *adapter) GetCostBasisAt(ctx context.Context, params currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + return entutils.TransactingRepo(ctx, a, func(ctx context.Context, tx *adapter) (currencies.CostBasis, error) { + costBasis, err := tx.db.CurrencyCostBasis.Query(). + Where( + currencycostbasis.Namespace(params.Namespace), + currencycostbasis.CurrencyID(params.CurrencyID), + currencycostbasis.FiatCode(params.FiatCode), + currencycostbasis.EffectiveFromLTE(params.At), + currencycostbasis.Or( + currencycostbasis.EffectiveToIsNil(), + currencycostbasis.EffectiveToGT(params.At), + ), + currencycostbasis.DeletedAtIsNil(), + ). + Order(currencycostbasis.ByEffectiveFrom(sql.OrderDesc())). + First(ctx) + if entdb.IsNotFound(err) { + return currencies.CostBasis{}, models.NewGenericNotFoundError(fmt.Errorf( + "cost basis not found [currency.id=%s fiat=%s at=%s]", + params.CurrencyID, + params.FiatCode, + params.At.UTC().Format(time.RFC3339), + )) + } + if err != nil { + return currencies.CostBasis{}, fmt.Errorf("querying cost basis: %w", err) + } + + return FromDBCurrencyCostBasis(costBasis), nil }) } diff --git a/openmeter/currencies/adapter/currencies_test.go b/openmeter/currencies/adapter/currencies_test.go new file mode 100644 index 0000000000..0758b75445 --- /dev/null +++ b/openmeter/currencies/adapter/currencies_test.go @@ -0,0 +1,140 @@ +package adapter_test + +import ( + "testing" + "time" + + "github.com/alpacahq/alpacadecimal" + "github.com/oklog/ulid/v2" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/currencies" + currencyadapter "github.com/openmeterio/openmeter/openmeter/currencies/adapter" + "github.com/openmeterio/openmeter/openmeter/testutils" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestGetCostBasisAt(t *testing.T) { + // given: + // - two cost bases for the same custom/fiat pair and a separate bounded pair + // when: + // - the effective cost basis is queried around their interval boundaries + // then: + // - the newest started row is selected and expired rows do not leak through gaps + testDB := testutils.InitPostgresDB(t, testutils.PostgresDBStateEntMigrated) + db := testDB.EntDriver.Client() + t.Cleanup(func() { + _ = db.Close() + testDB.Close(t) + }) + + const namespace = "default" + customCurrencyID := ulid.Make().String() + _, err := db.CustomCurrency.Create(). + SetID(customCurrencyID). + SetNamespace(namespace). + SetCode("CREDITS"). + SetName("Credits"). + SetSymbol("CR"). + Save(t.Context()) + require.NoError(t, err) + + firstStart := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC) + secondStart := time.Date(2026, 2, 1, 0, 0, 0, 0, time.UTC) + firstEnd := secondStart + + first, err := db.CurrencyCostBasis.Create(). + SetID(ulid.Make().String()). + SetNamespace(namespace). + SetCurrencyID(customCurrencyID). + SetFiatCode(currencyx.Code("USD")). + SetRate(alpacadecimal.NewFromFloat(0.5)). + SetEffectiveFrom(firstStart). + SetEffectiveTo(firstEnd). + Save(t.Context()) + require.NoError(t, err) + + second, err := db.CurrencyCostBasis.Create(). + SetID(ulid.Make().String()). + SetNamespace(namespace). + SetCurrencyID(customCurrencyID). + SetFiatCode(currencyx.Code("USD")). + SetRate(alpacadecimal.NewFromFloat(0.75)). + SetEffectiveFrom(secondStart). + Save(t.Context()) + require.NoError(t, err) + + euroEnd := firstStart.Add(24 * time.Hour) + _, err = db.CurrencyCostBasis.Create(). + SetID(ulid.Make().String()). + SetNamespace(namespace). + SetCurrencyID(customCurrencyID). + SetFiatCode(currencyx.Code("EUR")). + SetRate(alpacadecimal.NewFromFloat(0.4)). + SetEffectiveFrom(firstStart). + SetEffectiveTo(euroEnd). + Save(t.Context()) + require.NoError(t, err) + + repo, err := currencyadapter.New(currencyadapter.Config{Client: db}) + require.NoError(t, err) + + tests := []struct { + name string + fiat currencyx.Code + at time.Time + expected string + notFound bool + }{ + { + name: "effective from is inclusive", + fiat: currencyx.Code("USD"), + at: firstStart, + expected: first.ID, + }, + { + name: "newer row wins at its start", + fiat: currencyx.Code("USD"), + at: secondStart, + expected: second.ID, + }, + { + name: "open interval remains effective", + fiat: currencyx.Code("USD"), + at: secondStart.Add(24 * time.Hour), + expected: second.ID, + }, + { + name: "before first interval", + fiat: currencyx.Code("USD"), + at: firstStart.Add(-time.Nanosecond), + notFound: true, + }, + { + name: "effective to is exclusive", + fiat: currencyx.Code("EUR"), + at: euroEnd, + notFound: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := repo.GetCostBasisAt(t.Context(), currencies.GetCostBasisAtInput{ + Namespace: namespace, + CurrencyID: customCurrencyID, + FiatCode: tt.fiat, + At: tt.at, + }) + if tt.notFound { + require.Error(t, err) + require.True(t, models.IsGenericNotFoundError(err)) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expected, result.ID) + }) + } +} diff --git a/openmeter/currencies/repository.go b/openmeter/currencies/repository.go index 02deef31fe..f3f5d6048a 100644 --- a/openmeter/currencies/repository.go +++ b/openmeter/currencies/repository.go @@ -22,4 +22,5 @@ type CurrencyRepository interface { type CostBasisRepository interface { CreateCostBasis(ctx context.Context, params CreateCostBasisInput) (CostBasis, error) ListCostBases(ctx context.Context, params ListCostBasesInput) (pagination.Result[CostBasis], error) + GetCostBasisAt(ctx context.Context, params GetCostBasisAtInput) (CostBasis, error) } diff --git a/openmeter/currencies/service.go b/openmeter/currencies/service.go index 265d622136..4e1e3d5d47 100644 --- a/openmeter/currencies/service.go +++ b/openmeter/currencies/service.go @@ -8,6 +8,7 @@ import ( "github.com/alpacahq/alpacadecimal" + "github.com/openmeterio/openmeter/pkg/currencyx" "github.com/openmeterio/openmeter/pkg/filter" "github.com/openmeterio/openmeter/pkg/models" "github.com/openmeterio/openmeter/pkg/pagination" @@ -27,6 +28,7 @@ type CurrencyService interface { type CostBasisService interface { CreateCostBasis(ctx context.Context, params CreateCostBasisInput) (CostBasis, error) ListCostBases(ctx context.Context, params ListCostBasesInput) (pagination.Result[CostBasis], error) + GetCostBasisAt(ctx context.Context, params GetCostBasisAtInput) (CostBasis, error) } // OrderBy specifies the field to sort currencies by. @@ -163,6 +165,39 @@ type ListCostBasesInput struct { FilterFiatCode *string `json:"filter_fiat_code,omitempty"` } +var _ models.Validator = (*GetCostBasisAtInput)(nil) + +type GetCostBasisAtInput struct { + Namespace string `json:"namespace"` + CurrencyID string `json:"currency_id"` + FiatCode currencyx.Code `json:"fiat_code"` + At time.Time `json:"at"` +} + +func (i GetCostBasisAtInput) Validate() error { + var errs []error + + if i.Namespace == "" { + errs = append(errs, errors.New("namespace is required")) + } + + if i.CurrencyID == "" { + errs = append(errs, errors.New("currency_id is required")) + } + + if err := i.FiatCode.Validate(); err != nil { + errs = append(errs, fmt.Errorf("fiat_code: %w", err)) + } else if !i.FiatCode.IsFiat() { + errs = append(errs, fmt.Errorf("fiat_code %q must be fiat", i.FiatCode)) + } + + if i.At.IsZero() { + errs = append(errs, errors.New("at is required")) + } + + return models.NewNillableGenericValidationError(errors.Join(errs...)) +} + func (i ListCostBasesInput) Validate() error { var errs []error diff --git a/openmeter/currencies/service/service.go b/openmeter/currencies/service/service.go index 5de5679b02..aa6646f6a0 100644 --- a/openmeter/currencies/service/service.go +++ b/openmeter/currencies/service/service.go @@ -175,3 +175,13 @@ func (s *service) ListCostBases(ctx context.Context, params currencies.ListCostB return s.adapter.ListCostBases(ctx, params) }) } + +func (s *service) GetCostBasisAt(ctx context.Context, params currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + if err := params.Validate(); err != nil { + return currencies.CostBasis{}, models.NewGenericValidationError(fmt.Errorf("invalid input parameters: %w", err)) + } + + return transaction.Run(ctx, s.adapter, func(ctx context.Context) (currencies.CostBasis, error) { + return s.adapter.GetCostBasisAt(ctx, params) + }) +} diff --git a/openmeter/currencies/service/service_test.go b/openmeter/currencies/service/service_test.go index 0e869a57a7..90e12592a8 100644 --- a/openmeter/currencies/service/service_test.go +++ b/openmeter/currencies/service/service_test.go @@ -31,6 +31,7 @@ func (noopDriver) SavePoint() error { return nil } type fakeAdapter struct { custom []currencies.Currency createCostBasis func(context.Context, currencies.CreateCostBasisInput) (currencies.CostBasis, error) + getCostBasisAt func(context.Context, currencies.GetCostBasisAtInput) (currencies.CostBasis, error) } func (f *fakeAdapter) Tx(ctx context.Context) (context.Context, transaction.Driver, error) { @@ -67,6 +68,14 @@ func (f *fakeAdapter) ListCostBases(_ context.Context, _ currencies.ListCostBase return pagination.Result[currencies.CostBasis]{}, errors.New("fakeAdapter.ListCostBases is not implemented") } +func (f *fakeAdapter) GetCostBasisAt(ctx context.Context, input currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + if f.getCostBasisAt != nil { + return f.getCostBasisAt(ctx, input) + } + + return currencies.CostBasis{}, errors.New("fakeAdapter.GetCostBasisAt is not implemented") +} + // newTestService creates a Service backed by a fake adapter seeded with custom currencies. func newTestService(custom []currencies.Currency) (currencies.Service, error) { return New(&fakeAdapter{custom: custom}) diff --git a/openmeter/customer/customer.go b/openmeter/customer/customer.go index 663861433c..9df9d296ac 100644 --- a/openmeter/customer/customer.go +++ b/openmeter/customer/customer.go @@ -110,10 +110,8 @@ func (c Customer) Validate() error { return models.NewGenericValidationError(errors.New("key cannot be empty")) } - if c.Currency != nil { - if err := c.Currency.Validate(); err != nil { - return models.NewGenericValidationError(err) - } + if err := validateCustomerCurrency(c.Currency); err != nil { + return models.NewGenericValidationError(err) } // Either key or usageAttribution.subjectKeys must be provided @@ -154,10 +152,8 @@ func (c CustomerMutate) Validate() error { return models.NewGenericValidationError(errors.New("name is required")) } - if c.Currency != nil { - if err := c.Currency.Validate(); err != nil { - return models.NewGenericValidationError(err) - } + if err := validateCustomerCurrency(c.Currency); err != nil { + return models.NewGenericValidationError(err) } // Either key or usageAttribution.subjectKeys must be provided @@ -177,6 +173,22 @@ func (c CustomerMutate) Validate() error { return nil } +func validateCustomerCurrency(code *currencyx.Code) error { + if code == nil { + return nil + } + + if err := code.Validate(); err != nil { + return err + } + + if !code.IsFiat() { + return fmt.Errorf("customer currency %q must be fiat", *code) + } + + return nil +} + // CustomerID represents a customer id type CustomerID models.NamespacedID diff --git a/openmeter/customer/customer_test.go b/openmeter/customer/customer_test.go new file mode 100644 index 0000000000..40f73e9b35 --- /dev/null +++ b/openmeter/customer/customer_test.go @@ -0,0 +1,53 @@ +package customer + +import ( + "testing" + + "github.com/samber/lo" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/pkg/currencyx" +) + +func TestCustomerMutateCurrencyMustBeFiat(t *testing.T) { + tests := []struct { + name string + currency *currencyx.Code + wantErr bool + }{ + { + name: "currency is optional", + }, + { + name: "fiat currency", + currency: lo.ToPtr(currencyx.Code("USD")), + }, + { + name: "custom currency", + currency: lo.ToPtr(currencyx.Code("CREDITS")), + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // given: + // - a structurally valid customer mutation with an optional currency + // when: + // - the customer is validated + // then: + // - only fiat currencies are accepted as the customer's billing default + err := (CustomerMutate{ + Key: lo.ToPtr("customer"), + Name: "Customer", + Currency: tt.currency, + }).Validate() + if tt.wantErr { + require.ErrorContains(t, err, "must be fiat") + return + } + + require.NoError(t, err) + }) + } +} diff --git a/openmeter/ent/db/client.go b/openmeter/ent/db/client.go index 8bf9183017..8a0e60a1fc 100644 --- a/openmeter/ent/db/client.go +++ b/openmeter/ent/db/client.go @@ -94,6 +94,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddonquantity" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionbillingsyncstate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionitem" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionphase" dbtaxcode "github.com/openmeterio/openmeter/openmeter/ent/db/taxcode" @@ -267,6 +268,8 @@ type Client struct { SubscriptionAddonQuantity *SubscriptionAddonQuantityClient // SubscriptionBillingSyncState is the client for interacting with the SubscriptionBillingSyncState builders. SubscriptionBillingSyncState *SubscriptionBillingSyncStateClient + // SubscriptionCostBasisPin is the client for interacting with the SubscriptionCostBasisPin builders. + SubscriptionCostBasisPin *SubscriptionCostBasisPinClient // SubscriptionItem is the client for interacting with the SubscriptionItem builders. SubscriptionItem *SubscriptionItemClient // SubscriptionPhase is the client for interacting with the SubscriptionPhase builders. @@ -366,6 +369,7 @@ func (c *Client) init() { c.SubscriptionAddon = NewSubscriptionAddonClient(c.config) c.SubscriptionAddonQuantity = NewSubscriptionAddonQuantityClient(c.config) c.SubscriptionBillingSyncState = NewSubscriptionBillingSyncStateClient(c.config) + c.SubscriptionCostBasisPin = NewSubscriptionCostBasisPinClient(c.config) c.SubscriptionItem = NewSubscriptionItemClient(c.config) c.SubscriptionPhase = NewSubscriptionPhaseClient(c.config) c.TaxCode = NewTaxCodeClient(c.config) @@ -542,6 +546,7 @@ func (c *Client) Tx(ctx context.Context) (*Tx, error) { SubscriptionAddon: NewSubscriptionAddonClient(cfg), SubscriptionAddonQuantity: NewSubscriptionAddonQuantityClient(cfg), SubscriptionBillingSyncState: NewSubscriptionBillingSyncStateClient(cfg), + SubscriptionCostBasisPin: NewSubscriptionCostBasisPinClient(cfg), SubscriptionItem: NewSubscriptionItemClient(cfg), SubscriptionPhase: NewSubscriptionPhaseClient(cfg), TaxCode: NewTaxCodeClient(cfg), @@ -645,6 +650,7 @@ func (c *Client) BeginTx(ctx context.Context, opts *sql.TxOptions) (*Tx, error) SubscriptionAddon: NewSubscriptionAddonClient(cfg), SubscriptionAddonQuantity: NewSubscriptionAddonQuantityClient(cfg), SubscriptionBillingSyncState: NewSubscriptionBillingSyncStateClient(cfg), + SubscriptionCostBasisPin: NewSubscriptionCostBasisPinClient(cfg), SubscriptionItem: NewSubscriptionItemClient(cfg), SubscriptionPhase: NewSubscriptionPhaseClient(cfg), TaxCode: NewTaxCodeClient(cfg), @@ -707,7 +713,8 @@ func (c *Client) Use(hooks ...Hook) { c.OrganizationDefaultTaxCodes, c.Plan, c.PlanAddon, c.PlanPhase, c.PlanRateCard, c.Subject, c.Subscription, c.SubscriptionAddon, c.SubscriptionAddonQuantity, c.SubscriptionBillingSyncState, - c.SubscriptionItem, c.SubscriptionPhase, c.TaxCode, c.UsageReset, + c.SubscriptionCostBasisPin, c.SubscriptionItem, c.SubscriptionPhase, c.TaxCode, + c.UsageReset, } { n.Use(hooks...) } @@ -746,7 +753,8 @@ func (c *Client) Intercept(interceptors ...Interceptor) { c.OrganizationDefaultTaxCodes, c.Plan, c.PlanAddon, c.PlanPhase, c.PlanRateCard, c.Subject, c.Subscription, c.SubscriptionAddon, c.SubscriptionAddonQuantity, c.SubscriptionBillingSyncState, - c.SubscriptionItem, c.SubscriptionPhase, c.TaxCode, c.UsageReset, + c.SubscriptionCostBasisPin, c.SubscriptionItem, c.SubscriptionPhase, c.TaxCode, + c.UsageReset, } { n.Intercept(interceptors...) } @@ -913,6 +921,8 @@ func (c *Client) Mutate(ctx context.Context, m Mutation) (Value, error) { return c.SubscriptionAddonQuantity.mutate(ctx, m) case *SubscriptionBillingSyncStateMutation: return c.SubscriptionBillingSyncState.mutate(ctx, m) + case *SubscriptionCostBasisPinMutation: + return c.SubscriptionCostBasisPin.mutate(ctx, m) case *SubscriptionItemMutation: return c.SubscriptionItem.mutate(ctx, m) case *SubscriptionPhaseMutation: @@ -10035,6 +10045,22 @@ func (c *CurrencyCostBasisClient) QueryCurrency(_m *CurrencyCostBasis) *CustomCu return query } +// QuerySubscriptionPins queries the subscription_pins edge of a CurrencyCostBasis. +func (c *CurrencyCostBasisClient) QuerySubscriptionPins(_m *CurrencyCostBasis) *SubscriptionCostBasisPinQuery { + query := (&SubscriptionCostBasisPinClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(currencycostbasis.Table, currencycostbasis.FieldID, id), + sqlgraph.To(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, currencycostbasis.SubscriptionPinsTable, currencycostbasis.SubscriptionPinsColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *CurrencyCostBasisClient) Hooks() []Hook { return c.hooks.CurrencyCostBasis @@ -10264,6 +10290,22 @@ func (c *CustomCurrencyClient) QuerySubscriptionItems(_m *CustomCurrency) *Subsc return query } +// QuerySubscriptionCostBasisPins queries the subscription_cost_basis_pins edge of a CustomCurrency. +func (c *CustomCurrencyClient) QuerySubscriptionCostBasisPins(_m *CustomCurrency) *SubscriptionCostBasisPinQuery { + query := (&SubscriptionCostBasisPinClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(customcurrency.Table, customcurrency.FieldID, id), + sqlgraph.To(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, customcurrency.SubscriptionCostBasisPinsTable, customcurrency.SubscriptionCostBasisPinsColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *CustomCurrencyClient) Hooks() []Hook { return c.hooks.CustomCurrency @@ -15263,6 +15305,22 @@ func (c *SubscriptionClient) QueryBillingSyncState(_m *Subscription) *Subscripti return query } +// QueryCostBasisPins queries the cost_basis_pins edge of a Subscription. +func (c *SubscriptionClient) QueryCostBasisPins(_m *Subscription) *SubscriptionCostBasisPinQuery { + query := (&SubscriptionCostBasisPinClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(subscription.Table, subscription.FieldID, id), + sqlgraph.To(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, subscription.CostBasisPinsTable, subscription.CostBasisPinsColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + // Hooks returns the client hooks. func (c *SubscriptionClient) Hooks() []Hook { return c.hooks.Subscription @@ -15767,6 +15825,187 @@ func (c *SubscriptionBillingSyncStateClient) mutate(ctx context.Context, m *Subs } } +// SubscriptionCostBasisPinClient is a client for the SubscriptionCostBasisPin schema. +type SubscriptionCostBasisPinClient struct { + config +} + +// NewSubscriptionCostBasisPinClient returns a client for the SubscriptionCostBasisPin from the given config. +func NewSubscriptionCostBasisPinClient(c config) *SubscriptionCostBasisPinClient { + return &SubscriptionCostBasisPinClient{config: c} +} + +// Use adds a list of mutation hooks to the hooks stack. +// A call to `Use(f, g, h)` equals to `subscriptioncostbasispin.Hooks(f(g(h())))`. +func (c *SubscriptionCostBasisPinClient) Use(hooks ...Hook) { + c.hooks.SubscriptionCostBasisPin = append(c.hooks.SubscriptionCostBasisPin, hooks...) +} + +// Intercept adds a list of query interceptors to the interceptors stack. +// A call to `Intercept(f, g, h)` equals to `subscriptioncostbasispin.Intercept(f(g(h())))`. +func (c *SubscriptionCostBasisPinClient) Intercept(interceptors ...Interceptor) { + c.inters.SubscriptionCostBasisPin = append(c.inters.SubscriptionCostBasisPin, interceptors...) +} + +// Create returns a builder for creating a SubscriptionCostBasisPin entity. +func (c *SubscriptionCostBasisPinClient) Create() *SubscriptionCostBasisPinCreate { + mutation := newSubscriptionCostBasisPinMutation(c.config, OpCreate) + return &SubscriptionCostBasisPinCreate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// CreateBulk returns a builder for creating a bulk of SubscriptionCostBasisPin entities. +func (c *SubscriptionCostBasisPinClient) CreateBulk(builders ...*SubscriptionCostBasisPinCreate) *SubscriptionCostBasisPinCreateBulk { + return &SubscriptionCostBasisPinCreateBulk{config: c.config, builders: builders} +} + +// MapCreateBulk creates a bulk creation builder from the given slice. For each item in the slice, the function creates +// a builder and applies setFunc on it. +func (c *SubscriptionCostBasisPinClient) MapCreateBulk(slice any, setFunc func(*SubscriptionCostBasisPinCreate, int)) *SubscriptionCostBasisPinCreateBulk { + rv := reflect.ValueOf(slice) + if rv.Kind() != reflect.Slice { + return &SubscriptionCostBasisPinCreateBulk{err: fmt.Errorf("calling to SubscriptionCostBasisPinClient.MapCreateBulk with wrong type %T, need slice", slice)} + } + builders := make([]*SubscriptionCostBasisPinCreate, rv.Len()) + for i := 0; i < rv.Len(); i++ { + builders[i] = c.Create() + setFunc(builders[i], i) + } + return &SubscriptionCostBasisPinCreateBulk{config: c.config, builders: builders} +} + +// Update returns an update builder for SubscriptionCostBasisPin. +func (c *SubscriptionCostBasisPinClient) Update() *SubscriptionCostBasisPinUpdate { + mutation := newSubscriptionCostBasisPinMutation(c.config, OpUpdate) + return &SubscriptionCostBasisPinUpdate{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOne returns an update builder for the given entity. +func (c *SubscriptionCostBasisPinClient) UpdateOne(_m *SubscriptionCostBasisPin) *SubscriptionCostBasisPinUpdateOne { + mutation := newSubscriptionCostBasisPinMutation(c.config, OpUpdateOne, withSubscriptionCostBasisPin(_m)) + return &SubscriptionCostBasisPinUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// UpdateOneID returns an update builder for the given id. +func (c *SubscriptionCostBasisPinClient) UpdateOneID(id string) *SubscriptionCostBasisPinUpdateOne { + mutation := newSubscriptionCostBasisPinMutation(c.config, OpUpdateOne, withSubscriptionCostBasisPinID(id)) + return &SubscriptionCostBasisPinUpdateOne{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// Delete returns a delete builder for SubscriptionCostBasisPin. +func (c *SubscriptionCostBasisPinClient) Delete() *SubscriptionCostBasisPinDelete { + mutation := newSubscriptionCostBasisPinMutation(c.config, OpDelete) + return &SubscriptionCostBasisPinDelete{config: c.config, hooks: c.Hooks(), mutation: mutation} +} + +// DeleteOne returns a builder for deleting the given entity. +func (c *SubscriptionCostBasisPinClient) DeleteOne(_m *SubscriptionCostBasisPin) *SubscriptionCostBasisPinDeleteOne { + return c.DeleteOneID(_m.ID) +} + +// DeleteOneID returns a builder for deleting the given entity by its id. +func (c *SubscriptionCostBasisPinClient) DeleteOneID(id string) *SubscriptionCostBasisPinDeleteOne { + builder := c.Delete().Where(subscriptioncostbasispin.ID(id)) + builder.mutation.id = &id + builder.mutation.op = OpDeleteOne + return &SubscriptionCostBasisPinDeleteOne{builder} +} + +// Query returns a query builder for SubscriptionCostBasisPin. +func (c *SubscriptionCostBasisPinClient) Query() *SubscriptionCostBasisPinQuery { + return &SubscriptionCostBasisPinQuery{ + config: c.config, + ctx: &QueryContext{Type: TypeSubscriptionCostBasisPin}, + inters: c.Interceptors(), + } +} + +// Get returns a SubscriptionCostBasisPin entity by its id. +func (c *SubscriptionCostBasisPinClient) Get(ctx context.Context, id string) (*SubscriptionCostBasisPin, error) { + return c.Query().Where(subscriptioncostbasispin.ID(id)).Only(ctx) +} + +// GetX is like Get, but panics if an error occurs. +func (c *SubscriptionCostBasisPinClient) GetX(ctx context.Context, id string) *SubscriptionCostBasisPin { + obj, err := c.Get(ctx, id) + if err != nil { + panic(err) + } + return obj +} + +// QuerySubscription queries the subscription edge of a SubscriptionCostBasisPin. +func (c *SubscriptionCostBasisPinClient) QuerySubscription(_m *SubscriptionCostBasisPin) *SubscriptionQuery { + query := (&SubscriptionClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID, id), + sqlgraph.To(subscription.Table, subscription.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, subscriptioncostbasispin.SubscriptionTable, subscriptioncostbasispin.SubscriptionColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryCustomCurrency queries the custom_currency edge of a SubscriptionCostBasisPin. +func (c *SubscriptionCostBasisPinClient) QueryCustomCurrency(_m *SubscriptionCostBasisPin) *CustomCurrencyQuery { + query := (&CustomCurrencyClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID, id), + sqlgraph.To(customcurrency.Table, customcurrency.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, subscriptioncostbasispin.CustomCurrencyTable, subscriptioncostbasispin.CustomCurrencyColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// QueryCostBasis queries the cost_basis edge of a SubscriptionCostBasisPin. +func (c *SubscriptionCostBasisPinClient) QueryCostBasis(_m *SubscriptionCostBasisPin) *CurrencyCostBasisQuery { + query := (&CurrencyCostBasisClient{config: c.config}).Query() + query.path = func(context.Context) (fromV *sql.Selector, _ error) { + id := _m.ID + step := sqlgraph.NewStep( + sqlgraph.From(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID, id), + sqlgraph.To(currencycostbasis.Table, currencycostbasis.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, subscriptioncostbasispin.CostBasisTable, subscriptioncostbasispin.CostBasisColumn), + ) + fromV = sqlgraph.Neighbors(_m.driver.Dialect(), step) + return fromV, nil + } + return query +} + +// Hooks returns the client hooks. +func (c *SubscriptionCostBasisPinClient) Hooks() []Hook { + return c.hooks.SubscriptionCostBasisPin +} + +// Interceptors returns the client interceptors. +func (c *SubscriptionCostBasisPinClient) Interceptors() []Interceptor { + return c.inters.SubscriptionCostBasisPin +} + +func (c *SubscriptionCostBasisPinClient) mutate(ctx context.Context, m *SubscriptionCostBasisPinMutation) (Value, error) { + switch m.Op() { + case OpCreate: + return (&SubscriptionCostBasisPinCreate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdate: + return (&SubscriptionCostBasisPinUpdate{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpUpdateOne: + return (&SubscriptionCostBasisPinUpdateOne{config: c.config, hooks: c.Hooks(), mutation: m}).Save(ctx) + case OpDelete, OpDeleteOne: + return (&SubscriptionCostBasisPinDelete{config: c.config, hooks: c.Hooks(), mutation: m}).Exec(ctx) + default: + return nil, fmt.Errorf("db: unknown SubscriptionCostBasisPin mutation op: %q", m.Op()) + } +} + // SubscriptionItemClient is a client for the SubscriptionItem schema. type SubscriptionItemClient struct { config @@ -16855,7 +17094,8 @@ type ( NotificationEventDeliveryStatus, NotificationRule, OrganizationDefaultTaxCodes, Plan, PlanAddon, PlanPhase, PlanRateCard, Subject, Subscription, SubscriptionAddon, SubscriptionAddonQuantity, SubscriptionBillingSyncState, - SubscriptionItem, SubscriptionPhase, TaxCode, UsageReset []ent.Hook + SubscriptionCostBasisPin, SubscriptionItem, SubscriptionPhase, TaxCode, + UsageReset []ent.Hook } inters struct { Addon, AddonRateCard, App, AppCustomInvoicing, AppCustomInvoicingCustomer, @@ -16883,8 +17123,8 @@ type ( NotificationEvent, NotificationEventDeliveryStatus, NotificationRule, OrganizationDefaultTaxCodes, Plan, PlanAddon, PlanPhase, PlanRateCard, Subject, Subscription, SubscriptionAddon, SubscriptionAddonQuantity, - SubscriptionBillingSyncState, SubscriptionItem, SubscriptionPhase, TaxCode, - UsageReset []ent.Interceptor + SubscriptionBillingSyncState, SubscriptionCostBasisPin, SubscriptionItem, + SubscriptionPhase, TaxCode, UsageReset []ent.Interceptor } ) diff --git a/openmeter/ent/db/currencycostbasis.go b/openmeter/ent/db/currencycostbasis.go index 786bb0f224..c5232d7e09 100644 --- a/openmeter/ent/db/currencycostbasis.go +++ b/openmeter/ent/db/currencycostbasis.go @@ -48,9 +48,11 @@ type CurrencyCostBasis struct { type CurrencyCostBasisEdges struct { // Currency holds the value of the currency edge. Currency *CustomCurrency `json:"currency,omitempty"` + // SubscriptionPins holds the value of the subscription_pins edge. + SubscriptionPins []*SubscriptionCostBasisPin `json:"subscription_pins,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [1]bool + loadedTypes [2]bool } // CurrencyOrErr returns the Currency value or an error if the edge @@ -64,6 +66,15 @@ func (e CurrencyCostBasisEdges) CurrencyOrErr() (*CustomCurrency, error) { return nil, &NotLoadedError{edge: "currency"} } +// SubscriptionPinsOrErr returns the SubscriptionPins value or an error if the edge +// was not loaded in eager-loading. +func (e CurrencyCostBasisEdges) SubscriptionPinsOrErr() ([]*SubscriptionCostBasisPin, error) { + if e.loadedTypes[1] { + return e.SubscriptionPins, nil + } + return nil, &NotLoadedError{edge: "subscription_pins"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*CurrencyCostBasis) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -170,6 +181,11 @@ func (_m *CurrencyCostBasis) QueryCurrency() *CustomCurrencyQuery { return NewCurrencyCostBasisClient(_m.config).QueryCurrency(_m) } +// QuerySubscriptionPins queries the "subscription_pins" edge of the CurrencyCostBasis entity. +func (_m *CurrencyCostBasis) QuerySubscriptionPins() *SubscriptionCostBasisPinQuery { + return NewCurrencyCostBasisClient(_m.config).QuerySubscriptionPins(_m) +} + // Update returns a builder for updating this CurrencyCostBasis. // Note that you need to call CurrencyCostBasis.Unwrap() before calling this method if this CurrencyCostBasis // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/openmeter/ent/db/currencycostbasis/currencycostbasis.go b/openmeter/ent/db/currencycostbasis/currencycostbasis.go index 2420172ebc..40e5dab067 100644 --- a/openmeter/ent/db/currencycostbasis/currencycostbasis.go +++ b/openmeter/ent/db/currencycostbasis/currencycostbasis.go @@ -34,6 +34,8 @@ const ( FieldEffectiveTo = "effective_to" // EdgeCurrency holds the string denoting the currency edge name in mutations. EdgeCurrency = "currency" + // EdgeSubscriptionPins holds the string denoting the subscription_pins edge name in mutations. + EdgeSubscriptionPins = "subscription_pins" // Table holds the table name of the currencycostbasis in the database. Table = "currency_cost_bases" // CurrencyTable is the table that holds the currency relation/edge. @@ -43,6 +45,13 @@ const ( CurrencyInverseTable = "custom_currencies" // CurrencyColumn is the table column denoting the currency relation/edge. CurrencyColumn = "currency_id" + // SubscriptionPinsTable is the table that holds the subscription_pins relation/edge. + SubscriptionPinsTable = "subscription_cost_basis_pins" + // SubscriptionPinsInverseTable is the table name for the SubscriptionCostBasisPin entity. + // It exists in this package in order to avoid circular dependency with the "subscriptioncostbasispin" package. + SubscriptionPinsInverseTable = "subscription_cost_basis_pins" + // SubscriptionPinsColumn is the table column denoting the subscription_pins relation/edge. + SubscriptionPinsColumn = "cost_basis_id" ) // Columns holds all SQL columns for currencycostbasis fields. @@ -143,6 +152,20 @@ func ByCurrencyField(field string, opts ...sql.OrderTermOption) OrderOption { sqlgraph.OrderByNeighborTerms(s, newCurrencyStep(), sql.OrderByField(field, opts...)) } } + +// BySubscriptionPinsCount orders the results by subscription_pins count. +func BySubscriptionPinsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newSubscriptionPinsStep(), opts...) + } +} + +// BySubscriptionPins orders the results by subscription_pins terms. +func BySubscriptionPins(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSubscriptionPinsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newCurrencyStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -150,3 +173,10 @@ func newCurrencyStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.M2O, true, CurrencyTable, CurrencyColumn), ) } +func newSubscriptionPinsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SubscriptionPinsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SubscriptionPinsTable, SubscriptionPinsColumn), + ) +} diff --git a/openmeter/ent/db/currencycostbasis/where.go b/openmeter/ent/db/currencycostbasis/where.go index cbb673efd3..a702e7afe6 100644 --- a/openmeter/ent/db/currencycostbasis/where.go +++ b/openmeter/ent/db/currencycostbasis/where.go @@ -610,6 +610,29 @@ func HasCurrencyWith(preds ...predicate.CustomCurrency) predicate.CurrencyCostBa }) } +// HasSubscriptionPins applies the HasEdge predicate on the "subscription_pins" edge. +func HasSubscriptionPins() predicate.CurrencyCostBasis { + return predicate.CurrencyCostBasis(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SubscriptionPinsTable, SubscriptionPinsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSubscriptionPinsWith applies the HasEdge predicate on the "subscription_pins" edge with a given conditions (other predicates). +func HasSubscriptionPinsWith(preds ...predicate.SubscriptionCostBasisPin) predicate.CurrencyCostBasis { + return predicate.CurrencyCostBasis(func(s *sql.Selector) { + step := newSubscriptionPinsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.CurrencyCostBasis) predicate.CurrencyCostBasis { return predicate.CurrencyCostBasis(sql.AndPredicates(predicates...)) diff --git a/openmeter/ent/db/currencycostbasis_create.go b/openmeter/ent/db/currencycostbasis_create.go index 98ba67b7c0..d1a71c4d24 100644 --- a/openmeter/ent/db/currencycostbasis_create.go +++ b/openmeter/ent/db/currencycostbasis_create.go @@ -15,6 +15,7 @@ import ( "github.com/alpacahq/alpacadecimal" "github.com/openmeterio/openmeter/openmeter/ent/db/currencycostbasis" "github.com/openmeterio/openmeter/openmeter/ent/db/customcurrency" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/pkg/currencyx" ) @@ -131,6 +132,21 @@ func (_c *CurrencyCostBasisCreate) SetCurrency(v *CustomCurrency) *CurrencyCostB return _c.SetCurrencyID(v.ID) } +// AddSubscriptionPinIDs adds the "subscription_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_c *CurrencyCostBasisCreate) AddSubscriptionPinIDs(ids ...string) *CurrencyCostBasisCreate { + _c.mutation.AddSubscriptionPinIDs(ids...) + return _c +} + +// AddSubscriptionPins adds the "subscription_pins" edges to the SubscriptionCostBasisPin entity. +func (_c *CurrencyCostBasisCreate) AddSubscriptionPins(v ...*SubscriptionCostBasisPin) *CurrencyCostBasisCreate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddSubscriptionPinIDs(ids...) +} + // Mutation returns the CurrencyCostBasisMutation object of the builder. func (_c *CurrencyCostBasisCreate) Mutation() *CurrencyCostBasisMutation { return _c.mutation @@ -301,6 +317,22 @@ func (_c *CurrencyCostBasisCreate) createSpec() (*CurrencyCostBasis, *sqlgraph.C _node.CurrencyID = nodes[0] _spec.Edges = append(_spec.Edges, edge) } + if nodes := _c.mutation.SubscriptionPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: currencycostbasis.SubscriptionPinsTable, + Columns: []string{currencycostbasis.SubscriptionPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/openmeter/ent/db/currencycostbasis_query.go b/openmeter/ent/db/currencycostbasis_query.go index ea1eb9c0cd..eab85ad1d3 100644 --- a/openmeter/ent/db/currencycostbasis_query.go +++ b/openmeter/ent/db/currencycostbasis_query.go @@ -4,6 +4,7 @@ package db import ( "context" + "database/sql/driver" "fmt" "math" @@ -15,17 +16,19 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/currencycostbasis" "github.com/openmeterio/openmeter/openmeter/ent/db/customcurrency" "github.com/openmeterio/openmeter/openmeter/ent/db/predicate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" ) // CurrencyCostBasisQuery is the builder for querying CurrencyCostBasis entities. type CurrencyCostBasisQuery struct { config - ctx *QueryContext - order []currencycostbasis.OrderOption - inters []Interceptor - predicates []predicate.CurrencyCostBasis - withCurrency *CustomCurrencyQuery - modifiers []func(*sql.Selector) + ctx *QueryContext + order []currencycostbasis.OrderOption + inters []Interceptor + predicates []predicate.CurrencyCostBasis + withCurrency *CustomCurrencyQuery + withSubscriptionPins *SubscriptionCostBasisPinQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -84,6 +87,28 @@ func (_q *CurrencyCostBasisQuery) QueryCurrency() *CustomCurrencyQuery { return query } +// QuerySubscriptionPins chains the current query on the "subscription_pins" edge. +func (_q *CurrencyCostBasisQuery) QuerySubscriptionPins() *SubscriptionCostBasisPinQuery { + query := (&SubscriptionCostBasisPinClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(currencycostbasis.Table, currencycostbasis.FieldID, selector), + sqlgraph.To(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, currencycostbasis.SubscriptionPinsTable, currencycostbasis.SubscriptionPinsColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first CurrencyCostBasis entity from the query. // Returns a *NotFoundError when no CurrencyCostBasis was found. func (_q *CurrencyCostBasisQuery) First(ctx context.Context) (*CurrencyCostBasis, error) { @@ -271,12 +296,13 @@ func (_q *CurrencyCostBasisQuery) Clone() *CurrencyCostBasisQuery { return nil } return &CurrencyCostBasisQuery{ - config: _q.config, - ctx: _q.ctx.Clone(), - order: append([]currencycostbasis.OrderOption{}, _q.order...), - inters: append([]Interceptor{}, _q.inters...), - predicates: append([]predicate.CurrencyCostBasis{}, _q.predicates...), - withCurrency: _q.withCurrency.Clone(), + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]currencycostbasis.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.CurrencyCostBasis{}, _q.predicates...), + withCurrency: _q.withCurrency.Clone(), + withSubscriptionPins: _q.withSubscriptionPins.Clone(), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, @@ -294,6 +320,17 @@ func (_q *CurrencyCostBasisQuery) WithCurrency(opts ...func(*CustomCurrencyQuery return _q } +// WithSubscriptionPins tells the query-builder to eager-load the nodes that are connected to +// the "subscription_pins" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *CurrencyCostBasisQuery) WithSubscriptionPins(opts ...func(*SubscriptionCostBasisPinQuery)) *CurrencyCostBasisQuery { + query := (&SubscriptionCostBasisPinClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withSubscriptionPins = query + return _q +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -372,8 +409,9 @@ func (_q *CurrencyCostBasisQuery) sqlAll(ctx context.Context, hooks ...queryHook var ( nodes = []*CurrencyCostBasis{} _spec = _q.querySpec() - loadedTypes = [1]bool{ + loadedTypes = [2]bool{ _q.withCurrency != nil, + _q.withSubscriptionPins != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -403,6 +441,15 @@ func (_q *CurrencyCostBasisQuery) sqlAll(ctx context.Context, hooks ...queryHook return nil, err } } + if query := _q.withSubscriptionPins; query != nil { + if err := _q.loadSubscriptionPins(ctx, query, nodes, + func(n *CurrencyCostBasis) { n.Edges.SubscriptionPins = []*SubscriptionCostBasisPin{} }, + func(n *CurrencyCostBasis, e *SubscriptionCostBasisPin) { + n.Edges.SubscriptionPins = append(n.Edges.SubscriptionPins, e) + }); err != nil { + return nil, err + } + } return nodes, nil } @@ -435,6 +482,36 @@ func (_q *CurrencyCostBasisQuery) loadCurrency(ctx context.Context, query *Custo } return nil } +func (_q *CurrencyCostBasisQuery) loadSubscriptionPins(ctx context.Context, query *SubscriptionCostBasisPinQuery, nodes []*CurrencyCostBasis, init func(*CurrencyCostBasis), assign func(*CurrencyCostBasis, *SubscriptionCostBasisPin)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*CurrencyCostBasis) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(subscriptioncostbasispin.FieldCostBasisID) + } + query.Where(predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(currencycostbasis.SubscriptionPinsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.CostBasisID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "cost_basis_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (_q *CurrencyCostBasisQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() diff --git a/openmeter/ent/db/currencycostbasis_update.go b/openmeter/ent/db/currencycostbasis_update.go index d8ce4aff15..5ecb495172 100644 --- a/openmeter/ent/db/currencycostbasis_update.go +++ b/openmeter/ent/db/currencycostbasis_update.go @@ -13,6 +13,7 @@ import ( "entgo.io/ent/schema/field" "github.com/openmeterio/openmeter/openmeter/ent/db/currencycostbasis" "github.com/openmeterio/openmeter/openmeter/ent/db/predicate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" ) // CurrencyCostBasisUpdate is the builder for updating CurrencyCostBasis entities. @@ -54,11 +55,47 @@ func (_u *CurrencyCostBasisUpdate) ClearDeletedAt() *CurrencyCostBasisUpdate { return _u } +// AddSubscriptionPinIDs adds the "subscription_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_u *CurrencyCostBasisUpdate) AddSubscriptionPinIDs(ids ...string) *CurrencyCostBasisUpdate { + _u.mutation.AddSubscriptionPinIDs(ids...) + return _u +} + +// AddSubscriptionPins adds the "subscription_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *CurrencyCostBasisUpdate) AddSubscriptionPins(v ...*SubscriptionCostBasisPin) *CurrencyCostBasisUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddSubscriptionPinIDs(ids...) +} + // Mutation returns the CurrencyCostBasisMutation object of the builder. func (_u *CurrencyCostBasisUpdate) Mutation() *CurrencyCostBasisMutation { return _u.mutation } +// ClearSubscriptionPins clears all "subscription_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *CurrencyCostBasisUpdate) ClearSubscriptionPins() *CurrencyCostBasisUpdate { + _u.mutation.ClearSubscriptionPins() + return _u +} + +// RemoveSubscriptionPinIDs removes the "subscription_pins" edge to SubscriptionCostBasisPin entities by IDs. +func (_u *CurrencyCostBasisUpdate) RemoveSubscriptionPinIDs(ids ...string) *CurrencyCostBasisUpdate { + _u.mutation.RemoveSubscriptionPinIDs(ids...) + return _u +} + +// RemoveSubscriptionPins removes "subscription_pins" edges to SubscriptionCostBasisPin entities. +func (_u *CurrencyCostBasisUpdate) RemoveSubscriptionPins(v ...*SubscriptionCostBasisPin) *CurrencyCostBasisUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveSubscriptionPinIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (_u *CurrencyCostBasisUpdate) Save(ctx context.Context) (int, error) { _u.defaults() @@ -127,6 +164,51 @@ func (_u *CurrencyCostBasisUpdate) sqlSave(ctx context.Context) (_node int, err if _u.mutation.EffectiveToCleared() { _spec.ClearField(currencycostbasis.FieldEffectiveTo, field.TypeTime) } + if _u.mutation.SubscriptionPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: currencycostbasis.SubscriptionPinsTable, + Columns: []string{currencycostbasis.SubscriptionPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedSubscriptionPinsIDs(); len(nodes) > 0 && !_u.mutation.SubscriptionPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: currencycostbasis.SubscriptionPinsTable, + Columns: []string{currencycostbasis.SubscriptionPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.SubscriptionPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: currencycostbasis.SubscriptionPinsTable, + Columns: []string{currencycostbasis.SubscriptionPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{currencycostbasis.Label} @@ -173,11 +255,47 @@ func (_u *CurrencyCostBasisUpdateOne) ClearDeletedAt() *CurrencyCostBasisUpdateO return _u } +// AddSubscriptionPinIDs adds the "subscription_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_u *CurrencyCostBasisUpdateOne) AddSubscriptionPinIDs(ids ...string) *CurrencyCostBasisUpdateOne { + _u.mutation.AddSubscriptionPinIDs(ids...) + return _u +} + +// AddSubscriptionPins adds the "subscription_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *CurrencyCostBasisUpdateOne) AddSubscriptionPins(v ...*SubscriptionCostBasisPin) *CurrencyCostBasisUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddSubscriptionPinIDs(ids...) +} + // Mutation returns the CurrencyCostBasisMutation object of the builder. func (_u *CurrencyCostBasisUpdateOne) Mutation() *CurrencyCostBasisMutation { return _u.mutation } +// ClearSubscriptionPins clears all "subscription_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *CurrencyCostBasisUpdateOne) ClearSubscriptionPins() *CurrencyCostBasisUpdateOne { + _u.mutation.ClearSubscriptionPins() + return _u +} + +// RemoveSubscriptionPinIDs removes the "subscription_pins" edge to SubscriptionCostBasisPin entities by IDs. +func (_u *CurrencyCostBasisUpdateOne) RemoveSubscriptionPinIDs(ids ...string) *CurrencyCostBasisUpdateOne { + _u.mutation.RemoveSubscriptionPinIDs(ids...) + return _u +} + +// RemoveSubscriptionPins removes "subscription_pins" edges to SubscriptionCostBasisPin entities. +func (_u *CurrencyCostBasisUpdateOne) RemoveSubscriptionPins(v ...*SubscriptionCostBasisPin) *CurrencyCostBasisUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveSubscriptionPinIDs(ids...) +} + // Where appends a list predicates to the CurrencyCostBasisUpdate builder. func (_u *CurrencyCostBasisUpdateOne) Where(ps ...predicate.CurrencyCostBasis) *CurrencyCostBasisUpdateOne { _u.mutation.Where(ps...) @@ -276,6 +394,51 @@ func (_u *CurrencyCostBasisUpdateOne) sqlSave(ctx context.Context) (_node *Curre if _u.mutation.EffectiveToCleared() { _spec.ClearField(currencycostbasis.FieldEffectiveTo, field.TypeTime) } + if _u.mutation.SubscriptionPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: currencycostbasis.SubscriptionPinsTable, + Columns: []string{currencycostbasis.SubscriptionPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedSubscriptionPinsIDs(); len(nodes) > 0 && !_u.mutation.SubscriptionPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: currencycostbasis.SubscriptionPinsTable, + Columns: []string{currencycostbasis.SubscriptionPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.SubscriptionPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: currencycostbasis.SubscriptionPinsTable, + Columns: []string{currencycostbasis.SubscriptionPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &CurrencyCostBasis{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/openmeter/ent/db/cursor.go b/openmeter/ent/db/cursor.go index a4e7145586..ae6b2faa45 100644 --- a/openmeter/ent/db/cursor.go +++ b/openmeter/ent/db/cursor.go @@ -3683,6 +3683,57 @@ func (_m *SubscriptionAddonQuantityQuery) Cursor(ctx context.Context, cursor *pa return result, nil } +// Cursor runs the query and returns a cursor-paginated response. +// Ordering is always by created_at asc, id asc. +func (_m *SubscriptionCostBasisPinQuery) Cursor(ctx context.Context, cursor *pagination.Cursor) (pagination.Result[*SubscriptionCostBasisPin], error) { + if cursor != nil { + if err := cursor.Validate(); err != nil { + return pagination.Result[*SubscriptionCostBasisPin]{}, fmt.Errorf("invalid cursor: %w", err) + } + + _m.Where(func(s *sql.Selector) { + s.Where( + sql.Or( + sql.GT(s.C("created_at"), cursor.Time), + sql.And( + sql.EQ(s.C("created_at"), cursor.Time), + sql.P(func(b *sql.Builder) { + b.WriteString("CAST(") + b.WriteString(s.C("id")) + b.WriteString(" AS TEXT) > ") + b.Args(cursor.ID) + }), + ), + ), + ) + }) + } + + _m.Order(func(s *sql.Selector) { + s.OrderBy(sql.Asc(s.C("created_at")), sql.Asc(s.C("id"))) + }) + + items, err := _m.All(ctx) + if err != nil { + return pagination.Result[*SubscriptionCostBasisPin]{}, err + } + + if items == nil { + items = make([]*SubscriptionCostBasisPin, 0) + } + + result := pagination.Result[*SubscriptionCostBasisPin]{ + Items: items, + } + + if len(items) > 0 { + last := items[len(items)-1] + result.NextCursor = lo.ToPtr(pagination.NewCursor(last.CreatedAt, fmt.Sprint(last.ID))) + } + + return result, nil +} + // Cursor runs the query and returns a cursor-paginated response. // Ordering is always by created_at asc, id asc. func (_m *SubscriptionItemQuery) Cursor(ctx context.Context, cursor *pagination.Cursor) (pagination.Result[*SubscriptionItem], error) { diff --git a/openmeter/ent/db/customcurrency.go b/openmeter/ent/db/customcurrency.go index b6bc8fd74a..e4af94a163 100644 --- a/openmeter/ent/db/customcurrency.go +++ b/openmeter/ent/db/customcurrency.go @@ -51,9 +51,11 @@ type CustomCurrencyEdges struct { AddonRateCards []*AddonRateCard `json:"addon_rate_cards,omitempty"` // SubscriptionItems holds the value of the subscription_items edge. SubscriptionItems []*SubscriptionItem `json:"subscription_items,omitempty"` + // SubscriptionCostBasisPins holds the value of the subscription_cost_basis_pins edge. + SubscriptionCostBasisPins []*SubscriptionCostBasisPin `json:"subscription_cost_basis_pins,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [6]bool + loadedTypes [7]bool } // CostBasisHistoryOrErr returns the CostBasisHistory value or an error if the edge @@ -110,6 +112,15 @@ func (e CustomCurrencyEdges) SubscriptionItemsOrErr() ([]*SubscriptionItem, erro return nil, &NotLoadedError{edge: "subscription_items"} } +// SubscriptionCostBasisPinsOrErr returns the SubscriptionCostBasisPins value or an error if the edge +// was not loaded in eager-loading. +func (e CustomCurrencyEdges) SubscriptionCostBasisPinsOrErr() ([]*SubscriptionCostBasisPin, error) { + if e.loadedTypes[6] { + return e.SubscriptionCostBasisPins, nil + } + return nil, &NotLoadedError{edge: "subscription_cost_basis_pins"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*CustomCurrency) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -226,6 +237,11 @@ func (_m *CustomCurrency) QuerySubscriptionItems() *SubscriptionItemQuery { return NewCustomCurrencyClient(_m.config).QuerySubscriptionItems(_m) } +// QuerySubscriptionCostBasisPins queries the "subscription_cost_basis_pins" edge of the CustomCurrency entity. +func (_m *CustomCurrency) QuerySubscriptionCostBasisPins() *SubscriptionCostBasisPinQuery { + return NewCustomCurrencyClient(_m.config).QuerySubscriptionCostBasisPins(_m) +} + // Update returns a builder for updating this CustomCurrency. // Note that you need to call CustomCurrency.Unwrap() before calling this method if this CustomCurrency // was returned from a transaction, and the transaction was committed or rolled back. diff --git a/openmeter/ent/db/customcurrency/customcurrency.go b/openmeter/ent/db/customcurrency/customcurrency.go index ca1be179e3..df67f034df 100644 --- a/openmeter/ent/db/customcurrency/customcurrency.go +++ b/openmeter/ent/db/customcurrency/customcurrency.go @@ -40,6 +40,8 @@ const ( EdgeAddonRateCards = "addon_rate_cards" // EdgeSubscriptionItems holds the string denoting the subscription_items edge name in mutations. EdgeSubscriptionItems = "subscription_items" + // EdgeSubscriptionCostBasisPins holds the string denoting the subscription_cost_basis_pins edge name in mutations. + EdgeSubscriptionCostBasisPins = "subscription_cost_basis_pins" // Table holds the table name of the customcurrency in the database. Table = "custom_currencies" // CostBasisHistoryTable is the table that holds the cost_basis_history relation/edge. @@ -84,6 +86,13 @@ const ( SubscriptionItemsInverseTable = "subscription_items" // SubscriptionItemsColumn is the table column denoting the subscription_items relation/edge. SubscriptionItemsColumn = "custom_currency_id" + // SubscriptionCostBasisPinsTable is the table that holds the subscription_cost_basis_pins relation/edge. + SubscriptionCostBasisPinsTable = "subscription_cost_basis_pins" + // SubscriptionCostBasisPinsInverseTable is the table name for the SubscriptionCostBasisPin entity. + // It exists in this package in order to avoid circular dependency with the "subscriptioncostbasispin" package. + SubscriptionCostBasisPinsInverseTable = "subscription_cost_basis_pins" + // SubscriptionCostBasisPinsColumn is the table column denoting the subscription_cost_basis_pins relation/edge. + SubscriptionCostBasisPinsColumn = "custom_currency_id" ) // Columns holds all SQL columns for customcurrency fields. @@ -253,6 +262,20 @@ func BySubscriptionItems(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption sqlgraph.OrderByNeighborTerms(s, newSubscriptionItemsStep(), append([]sql.OrderTerm{term}, terms...)...) } } + +// BySubscriptionCostBasisPinsCount orders the results by subscription_cost_basis_pins count. +func BySubscriptionCostBasisPinsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newSubscriptionCostBasisPinsStep(), opts...) + } +} + +// BySubscriptionCostBasisPins orders the results by subscription_cost_basis_pins terms. +func BySubscriptionCostBasisPins(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSubscriptionCostBasisPinsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newCostBasisHistoryStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -295,3 +318,10 @@ func newSubscriptionItemsStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2M, false, SubscriptionItemsTable, SubscriptionItemsColumn), ) } +func newSubscriptionCostBasisPinsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SubscriptionCostBasisPinsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SubscriptionCostBasisPinsTable, SubscriptionCostBasisPinsColumn), + ) +} diff --git a/openmeter/ent/db/customcurrency/where.go b/openmeter/ent/db/customcurrency/where.go index f4b84e5243..f7aa8034c0 100644 --- a/openmeter/ent/db/customcurrency/where.go +++ b/openmeter/ent/db/customcurrency/where.go @@ -628,6 +628,29 @@ func HasSubscriptionItemsWith(preds ...predicate.SubscriptionItem) predicate.Cus }) } +// HasSubscriptionCostBasisPins applies the HasEdge predicate on the "subscription_cost_basis_pins" edge. +func HasSubscriptionCostBasisPins() predicate.CustomCurrency { + return predicate.CustomCurrency(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, SubscriptionCostBasisPinsTable, SubscriptionCostBasisPinsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSubscriptionCostBasisPinsWith applies the HasEdge predicate on the "subscription_cost_basis_pins" edge with a given conditions (other predicates). +func HasSubscriptionCostBasisPinsWith(preds ...predicate.SubscriptionCostBasisPin) predicate.CustomCurrency { + return predicate.CustomCurrency(func(s *sql.Selector) { + step := newSubscriptionCostBasisPinsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.CustomCurrency) predicate.CustomCurrency { return predicate.CustomCurrency(sql.AndPredicates(predicates...)) diff --git a/openmeter/ent/db/customcurrency_create.go b/openmeter/ent/db/customcurrency_create.go index bcd1e6ebbb..d6c0bb0266 100644 --- a/openmeter/ent/db/customcurrency_create.go +++ b/openmeter/ent/db/customcurrency_create.go @@ -18,6 +18,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/customcurrency" "github.com/openmeterio/openmeter/openmeter/ent/db/plan" "github.com/openmeterio/openmeter/openmeter/ent/db/planratecard" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionitem" ) @@ -199,6 +200,21 @@ func (_c *CustomCurrencyCreate) AddSubscriptionItems(v ...*SubscriptionItem) *Cu return _c.AddSubscriptionItemIDs(ids...) } +// AddSubscriptionCostBasisPinIDs adds the "subscription_cost_basis_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_c *CustomCurrencyCreate) AddSubscriptionCostBasisPinIDs(ids ...string) *CustomCurrencyCreate { + _c.mutation.AddSubscriptionCostBasisPinIDs(ids...) + return _c +} + +// AddSubscriptionCostBasisPins adds the "subscription_cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_c *CustomCurrencyCreate) AddSubscriptionCostBasisPins(v ...*SubscriptionCostBasisPin) *CustomCurrencyCreate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddSubscriptionCostBasisPinIDs(ids...) +} + // Mutation returns the CustomCurrencyMutation object of the builder. func (_c *CustomCurrencyCreate) Mutation() *CustomCurrencyMutation { return _c.mutation @@ -448,6 +464,22 @@ func (_c *CustomCurrencyCreate) createSpec() (*CustomCurrency, *sqlgraph.CreateS } _spec.Edges = append(_spec.Edges, edge) } + if nodes := _c.mutation.SubscriptionCostBasisPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: customcurrency.SubscriptionCostBasisPinsTable, + Columns: []string{customcurrency.SubscriptionCostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec } diff --git a/openmeter/ent/db/customcurrency_query.go b/openmeter/ent/db/customcurrency_query.go index df21bfb568..dcabd98b88 100644 --- a/openmeter/ent/db/customcurrency_query.go +++ b/openmeter/ent/db/customcurrency_query.go @@ -20,23 +20,25 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/plan" "github.com/openmeterio/openmeter/openmeter/ent/db/planratecard" "github.com/openmeterio/openmeter/openmeter/ent/db/predicate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionitem" ) // CustomCurrencyQuery is the builder for querying CustomCurrency entities. type CustomCurrencyQuery struct { config - ctx *QueryContext - order []customcurrency.OrderOption - inters []Interceptor - predicates []predicate.CustomCurrency - withCostBasisHistory *CurrencyCostBasisQuery - withPlans *PlanQuery - withAddons *AddonQuery - withPlanRateCards *PlanRateCardQuery - withAddonRateCards *AddonRateCardQuery - withSubscriptionItems *SubscriptionItemQuery - modifiers []func(*sql.Selector) + ctx *QueryContext + order []customcurrency.OrderOption + inters []Interceptor + predicates []predicate.CustomCurrency + withCostBasisHistory *CurrencyCostBasisQuery + withPlans *PlanQuery + withAddons *AddonQuery + withPlanRateCards *PlanRateCardQuery + withAddonRateCards *AddonRateCardQuery + withSubscriptionItems *SubscriptionItemQuery + withSubscriptionCostBasisPins *SubscriptionCostBasisPinQuery + modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector path func(context.Context) (*sql.Selector, error) @@ -205,6 +207,28 @@ func (_q *CustomCurrencyQuery) QuerySubscriptionItems() *SubscriptionItemQuery { return query } +// QuerySubscriptionCostBasisPins chains the current query on the "subscription_cost_basis_pins" edge. +func (_q *CustomCurrencyQuery) QuerySubscriptionCostBasisPins() *SubscriptionCostBasisPinQuery { + query := (&SubscriptionCostBasisPinClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(customcurrency.Table, customcurrency.FieldID, selector), + sqlgraph.To(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, customcurrency.SubscriptionCostBasisPinsTable, customcurrency.SubscriptionCostBasisPinsColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first CustomCurrency entity from the query. // Returns a *NotFoundError when no CustomCurrency was found. func (_q *CustomCurrencyQuery) First(ctx context.Context) (*CustomCurrency, error) { @@ -392,17 +416,18 @@ func (_q *CustomCurrencyQuery) Clone() *CustomCurrencyQuery { return nil } return &CustomCurrencyQuery{ - config: _q.config, - ctx: _q.ctx.Clone(), - order: append([]customcurrency.OrderOption{}, _q.order...), - inters: append([]Interceptor{}, _q.inters...), - predicates: append([]predicate.CustomCurrency{}, _q.predicates...), - withCostBasisHistory: _q.withCostBasisHistory.Clone(), - withPlans: _q.withPlans.Clone(), - withAddons: _q.withAddons.Clone(), - withPlanRateCards: _q.withPlanRateCards.Clone(), - withAddonRateCards: _q.withAddonRateCards.Clone(), - withSubscriptionItems: _q.withSubscriptionItems.Clone(), + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]customcurrency.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.CustomCurrency{}, _q.predicates...), + withCostBasisHistory: _q.withCostBasisHistory.Clone(), + withPlans: _q.withPlans.Clone(), + withAddons: _q.withAddons.Clone(), + withPlanRateCards: _q.withPlanRateCards.Clone(), + withAddonRateCards: _q.withAddonRateCards.Clone(), + withSubscriptionItems: _q.withSubscriptionItems.Clone(), + withSubscriptionCostBasisPins: _q.withSubscriptionCostBasisPins.Clone(), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, @@ -475,6 +500,17 @@ func (_q *CustomCurrencyQuery) WithSubscriptionItems(opts ...func(*SubscriptionI return _q } +// WithSubscriptionCostBasisPins tells the query-builder to eager-load the nodes that are connected to +// the "subscription_cost_basis_pins" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *CustomCurrencyQuery) WithSubscriptionCostBasisPins(opts ...func(*SubscriptionCostBasisPinQuery)) *CustomCurrencyQuery { + query := (&SubscriptionCostBasisPinClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withSubscriptionCostBasisPins = query + return _q +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -553,13 +589,14 @@ func (_q *CustomCurrencyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ( var ( nodes = []*CustomCurrency{} _spec = _q.querySpec() - loadedTypes = [6]bool{ + loadedTypes = [7]bool{ _q.withCostBasisHistory != nil, _q.withPlans != nil, _q.withAddons != nil, _q.withPlanRateCards != nil, _q.withAddonRateCards != nil, _q.withSubscriptionItems != nil, + _q.withSubscriptionCostBasisPins != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -629,6 +666,15 @@ func (_q *CustomCurrencyQuery) sqlAll(ctx context.Context, hooks ...queryHook) ( return nil, err } } + if query := _q.withSubscriptionCostBasisPins; query != nil { + if err := _q.loadSubscriptionCostBasisPins(ctx, query, nodes, + func(n *CustomCurrency) { n.Edges.SubscriptionCostBasisPins = []*SubscriptionCostBasisPin{} }, + func(n *CustomCurrency, e *SubscriptionCostBasisPin) { + n.Edges.SubscriptionCostBasisPins = append(n.Edges.SubscriptionCostBasisPins, e) + }); err != nil { + return nil, err + } + } return nodes, nil } @@ -827,6 +873,36 @@ func (_q *CustomCurrencyQuery) loadSubscriptionItems(ctx context.Context, query } return nil } +func (_q *CustomCurrencyQuery) loadSubscriptionCostBasisPins(ctx context.Context, query *SubscriptionCostBasisPinQuery, nodes []*CustomCurrency, init func(*CustomCurrency), assign func(*CustomCurrency, *SubscriptionCostBasisPin)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*CustomCurrency) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(subscriptioncostbasispin.FieldCustomCurrencyID) + } + query.Where(predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(customcurrency.SubscriptionCostBasisPinsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.CustomCurrencyID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "custom_currency_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (_q *CustomCurrencyQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() diff --git a/openmeter/ent/db/customcurrency_update.go b/openmeter/ent/db/customcurrency_update.go index 7bde76daa0..a25771dbbd 100644 --- a/openmeter/ent/db/customcurrency_update.go +++ b/openmeter/ent/db/customcurrency_update.go @@ -18,6 +18,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/plan" "github.com/openmeterio/openmeter/openmeter/ent/db/planratecard" "github.com/openmeterio/openmeter/openmeter/ent/db/predicate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionitem" ) @@ -178,6 +179,21 @@ func (_u *CustomCurrencyUpdate) AddSubscriptionItems(v ...*SubscriptionItem) *Cu return _u.AddSubscriptionItemIDs(ids...) } +// AddSubscriptionCostBasisPinIDs adds the "subscription_cost_basis_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_u *CustomCurrencyUpdate) AddSubscriptionCostBasisPinIDs(ids ...string) *CustomCurrencyUpdate { + _u.mutation.AddSubscriptionCostBasisPinIDs(ids...) + return _u +} + +// AddSubscriptionCostBasisPins adds the "subscription_cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *CustomCurrencyUpdate) AddSubscriptionCostBasisPins(v ...*SubscriptionCostBasisPin) *CustomCurrencyUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddSubscriptionCostBasisPinIDs(ids...) +} + // Mutation returns the CustomCurrencyMutation object of the builder. func (_u *CustomCurrencyUpdate) Mutation() *CustomCurrencyMutation { return _u.mutation @@ -309,6 +325,27 @@ func (_u *CustomCurrencyUpdate) RemoveSubscriptionItems(v ...*SubscriptionItem) return _u.RemoveSubscriptionItemIDs(ids...) } +// ClearSubscriptionCostBasisPins clears all "subscription_cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *CustomCurrencyUpdate) ClearSubscriptionCostBasisPins() *CustomCurrencyUpdate { + _u.mutation.ClearSubscriptionCostBasisPins() + return _u +} + +// RemoveSubscriptionCostBasisPinIDs removes the "subscription_cost_basis_pins" edge to SubscriptionCostBasisPin entities by IDs. +func (_u *CustomCurrencyUpdate) RemoveSubscriptionCostBasisPinIDs(ids ...string) *CustomCurrencyUpdate { + _u.mutation.RemoveSubscriptionCostBasisPinIDs(ids...) + return _u +} + +// RemoveSubscriptionCostBasisPins removes "subscription_cost_basis_pins" edges to SubscriptionCostBasisPin entities. +func (_u *CustomCurrencyUpdate) RemoveSubscriptionCostBasisPins(v ...*SubscriptionCostBasisPin) *CustomCurrencyUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveSubscriptionCostBasisPinIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (_u *CustomCurrencyUpdate) Save(ctx context.Context) (int, error) { _u.defaults() @@ -657,6 +694,51 @@ func (_u *CustomCurrencyUpdate) sqlSave(ctx context.Context) (_node int, err err } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if _u.mutation.SubscriptionCostBasisPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: customcurrency.SubscriptionCostBasisPinsTable, + Columns: []string{customcurrency.SubscriptionCostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedSubscriptionCostBasisPinsIDs(); len(nodes) > 0 && !_u.mutation.SubscriptionCostBasisPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: customcurrency.SubscriptionCostBasisPinsTable, + Columns: []string{customcurrency.SubscriptionCostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.SubscriptionCostBasisPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: customcurrency.SubscriptionCostBasisPinsTable, + Columns: []string{customcurrency.SubscriptionCostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{customcurrency.Label} @@ -821,6 +903,21 @@ func (_u *CustomCurrencyUpdateOne) AddSubscriptionItems(v ...*SubscriptionItem) return _u.AddSubscriptionItemIDs(ids...) } +// AddSubscriptionCostBasisPinIDs adds the "subscription_cost_basis_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_u *CustomCurrencyUpdateOne) AddSubscriptionCostBasisPinIDs(ids ...string) *CustomCurrencyUpdateOne { + _u.mutation.AddSubscriptionCostBasisPinIDs(ids...) + return _u +} + +// AddSubscriptionCostBasisPins adds the "subscription_cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *CustomCurrencyUpdateOne) AddSubscriptionCostBasisPins(v ...*SubscriptionCostBasisPin) *CustomCurrencyUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddSubscriptionCostBasisPinIDs(ids...) +} + // Mutation returns the CustomCurrencyMutation object of the builder. func (_u *CustomCurrencyUpdateOne) Mutation() *CustomCurrencyMutation { return _u.mutation @@ -952,6 +1049,27 @@ func (_u *CustomCurrencyUpdateOne) RemoveSubscriptionItems(v ...*SubscriptionIte return _u.RemoveSubscriptionItemIDs(ids...) } +// ClearSubscriptionCostBasisPins clears all "subscription_cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *CustomCurrencyUpdateOne) ClearSubscriptionCostBasisPins() *CustomCurrencyUpdateOne { + _u.mutation.ClearSubscriptionCostBasisPins() + return _u +} + +// RemoveSubscriptionCostBasisPinIDs removes the "subscription_cost_basis_pins" edge to SubscriptionCostBasisPin entities by IDs. +func (_u *CustomCurrencyUpdateOne) RemoveSubscriptionCostBasisPinIDs(ids ...string) *CustomCurrencyUpdateOne { + _u.mutation.RemoveSubscriptionCostBasisPinIDs(ids...) + return _u +} + +// RemoveSubscriptionCostBasisPins removes "subscription_cost_basis_pins" edges to SubscriptionCostBasisPin entities. +func (_u *CustomCurrencyUpdateOne) RemoveSubscriptionCostBasisPins(v ...*SubscriptionCostBasisPin) *CustomCurrencyUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveSubscriptionCostBasisPinIDs(ids...) +} + // Where appends a list predicates to the CustomCurrencyUpdate builder. func (_u *CustomCurrencyUpdateOne) Where(ps ...predicate.CustomCurrency) *CustomCurrencyUpdateOne { _u.mutation.Where(ps...) @@ -1330,6 +1448,51 @@ func (_u *CustomCurrencyUpdateOne) sqlSave(ctx context.Context) (_node *CustomCu } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if _u.mutation.SubscriptionCostBasisPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: customcurrency.SubscriptionCostBasisPinsTable, + Columns: []string{customcurrency.SubscriptionCostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedSubscriptionCostBasisPinsIDs(); len(nodes) > 0 && !_u.mutation.SubscriptionCostBasisPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: customcurrency.SubscriptionCostBasisPinsTable, + Columns: []string{customcurrency.SubscriptionCostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.SubscriptionCostBasisPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: customcurrency.SubscriptionCostBasisPinsTable, + Columns: []string{customcurrency.SubscriptionCostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &CustomCurrency{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/openmeter/ent/db/ent.go b/openmeter/ent/db/ent.go index 72c7b11c54..329b6c8e2b 100644 --- a/openmeter/ent/db/ent.go +++ b/openmeter/ent/db/ent.go @@ -96,6 +96,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddonquantity" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionbillingsyncstate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionitem" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionphase" @@ -241,6 +242,7 @@ func checkColumn(t, c string) error { subscriptionaddon.Table: subscriptionaddon.ValidColumn, subscriptionaddonquantity.Table: subscriptionaddonquantity.ValidColumn, subscriptionbillingsyncstate.Table: subscriptionbillingsyncstate.ValidColumn, + subscriptioncostbasispin.Table: subscriptioncostbasispin.ValidColumn, subscriptionitem.Table: subscriptionitem.ValidColumn, subscriptionphase.Table: subscriptionphase.ValidColumn, dbtaxcode.Table: dbtaxcode.ValidColumn, diff --git a/openmeter/ent/db/entmixinaccessor.go b/openmeter/ent/db/entmixinaccessor.go index 1b5b7f0772..5c050c3005 100644 --- a/openmeter/ent/db/entmixinaccessor.go +++ b/openmeter/ent/db/entmixinaccessor.go @@ -2965,6 +2965,26 @@ func (e *SubscriptionBillingSyncState) GetNamespace() string { return e.Namespace } +func (e *SubscriptionCostBasisPin) GetID() string { + return e.ID +} + +func (e *SubscriptionCostBasisPin) GetNamespace() string { + return e.Namespace +} + +func (e *SubscriptionCostBasisPin) GetCreatedAt() time.Time { + return e.CreatedAt +} + +func (e *SubscriptionCostBasisPin) GetUpdatedAt() time.Time { + return e.UpdatedAt +} + +func (e *SubscriptionCostBasisPin) GetDeletedAt() *time.Time { + return e.DeletedAt +} + func (e *SubscriptionItem) GetID() string { return e.ID } diff --git a/openmeter/ent/db/expose.go b/openmeter/ent/db/expose.go index 08ca187a4b..ffb834e2b7 100644 --- a/openmeter/ent/db/expose.go +++ b/openmeter/ent/db/expose.go @@ -249,6 +249,8 @@ func NewTxClientFromRawConfig(ctx context.Context, cfg entutils.RawEntConfig) *T SubscriptionBillingSyncState: NewSubscriptionBillingSyncStateClient(config), + SubscriptionCostBasisPin: NewSubscriptionCostBasisPinClient(config), + SubscriptionItem: NewSubscriptionItemClient(config), SubscriptionPhase: NewSubscriptionPhaseClient(config), diff --git a/openmeter/ent/db/hook/hook.go b/openmeter/ent/db/hook/hook.go index 77aa9eaafc..f2fff900e3 100644 --- a/openmeter/ent/db/hook/hook.go +++ b/openmeter/ent/db/hook/hook.go @@ -957,6 +957,18 @@ func (f SubscriptionBillingSyncStateFunc) Mutate(ctx context.Context, m db.Mutat return nil, fmt.Errorf("unexpected mutation type %T. expect *db.SubscriptionBillingSyncStateMutation", m) } +// The SubscriptionCostBasisPinFunc type is an adapter to allow the use of ordinary +// function as SubscriptionCostBasisPin mutator. +type SubscriptionCostBasisPinFunc func(context.Context, *db.SubscriptionCostBasisPinMutation) (db.Value, error) + +// Mutate calls f(ctx, m). +func (f SubscriptionCostBasisPinFunc) Mutate(ctx context.Context, m db.Mutation) (db.Value, error) { + if mv, ok := m.(*db.SubscriptionCostBasisPinMutation); ok { + return f(ctx, mv) + } + return nil, fmt.Errorf("unexpected mutation type %T. expect *db.SubscriptionCostBasisPinMutation", m) +} + // The SubscriptionItemFunc type is an adapter to allow the use of ordinary // function as SubscriptionItem mutator. type SubscriptionItemFunc func(context.Context, *db.SubscriptionItemMutation) (db.Value, error) diff --git a/openmeter/ent/db/migrate/schema.go b/openmeter/ent/db/migrate/schema.go index c314545cd4..059f81b8aa 100644 --- a/openmeter/ent/db/migrate/schema.go +++ b/openmeter/ent/db/migrate/schema.go @@ -5127,6 +5127,7 @@ var ( {Name: "name", Type: field.TypeString, Default: "Subscription"}, {Name: "description", Type: field.TypeString, Nullable: true}, {Name: "currency", Type: field.TypeString, Size: 3}, + {Name: "cost_basis_mode", Type: field.TypeEnum, Enums: []string{"dynamic", "pinned"}, Default: "dynamic"}, {Name: "billing_anchor", Type: field.TypeTime}, {Name: "billing_cadence", Type: field.TypeString}, {Name: "pro_rating_config", Type: field.TypeString, SchemaType: map[string]string{"postgres": "jsonb"}}, @@ -5142,13 +5143,13 @@ var ( ForeignKeys: []*schema.ForeignKey{ { Symbol: "subscriptions_customers_subscription", - Columns: []*schema.Column{SubscriptionsColumns[16]}, + Columns: []*schema.Column{SubscriptionsColumns[17]}, RefColumns: []*schema.Column{CustomersColumns[0]}, OnDelete: schema.NoAction, }, { Symbol: "subscriptions_plans_subscriptions", - Columns: []*schema.Column{SubscriptionsColumns[17]}, + Columns: []*schema.Column{SubscriptionsColumns[18]}, RefColumns: []*schema.Column{PlansColumns[0]}, OnDelete: schema.SetNull, }, @@ -5182,7 +5183,7 @@ var ( { Name: "subscription_namespace_customer_id", Unique: false, - Columns: []*schema.Column{SubscriptionsColumns[1], SubscriptionsColumns[16]}, + Columns: []*schema.Column{SubscriptionsColumns[1], SubscriptionsColumns[17]}, }, }, } @@ -5319,6 +5320,76 @@ var ( }, }, } + // SubscriptionCostBasisPinsColumns holds the columns for the "subscription_cost_basis_pins" table. + SubscriptionCostBasisPinsColumns = []*schema.Column{ + {Name: "id", Type: field.TypeString, Unique: true, SchemaType: map[string]string{"postgres": "char(26)"}}, + {Name: "namespace", Type: field.TypeString}, + {Name: "created_at", Type: field.TypeTime}, + {Name: "updated_at", Type: field.TypeTime}, + {Name: "deleted_at", Type: field.TypeTime, Nullable: true}, + {Name: "invoice_currency", Type: field.TypeString, Size: 3}, + {Name: "cost_basis_id", Type: field.TypeString, SchemaType: map[string]string{"postgres": "char(26)"}}, + {Name: "custom_currency_id", Type: field.TypeString, SchemaType: map[string]string{"postgres": "char(26)"}}, + {Name: "subscription_id", Type: field.TypeString, SchemaType: map[string]string{"postgres": "char(26)"}}, + } + // SubscriptionCostBasisPinsTable holds the schema information for the "subscription_cost_basis_pins" table. + SubscriptionCostBasisPinsTable = &schema.Table{ + Name: "subscription_cost_basis_pins", + Columns: SubscriptionCostBasisPinsColumns, + PrimaryKey: []*schema.Column{SubscriptionCostBasisPinsColumns[0]}, + ForeignKeys: []*schema.ForeignKey{ + { + Symbol: "subscription_cost_basis_pins_currency_cost_bases_subscription_pins", + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[6]}, + RefColumns: []*schema.Column{CurrencyCostBasesColumns[0]}, + OnDelete: schema.Restrict, + }, + { + Symbol: "subscription_cost_basis_pins_custom_currencies_subscription_cost_basis_pins", + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[7]}, + RefColumns: []*schema.Column{CustomCurrenciesColumns[0]}, + OnDelete: schema.Restrict, + }, + { + Symbol: "subscription_cost_basis_pins_subscriptions_cost_basis_pins", + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[8]}, + RefColumns: []*schema.Column{SubscriptionsColumns[0]}, + OnDelete: schema.Cascade, + }, + }, + Indexes: []*schema.Index{ + { + Name: "subscriptioncostbasispin_id", + Unique: true, + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[0]}, + }, + { + Name: "subscriptioncostbasispin_namespace", + Unique: false, + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[1]}, + }, + { + Name: "subscriptioncostbasispin_namespace_subscription_id_custom_currency_id_invoice_currency", + Unique: true, + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[1], SubscriptionCostBasisPinsColumns[8], SubscriptionCostBasisPinsColumns[7], SubscriptionCostBasisPinsColumns[5]}, + }, + { + Name: "subscriptioncostbasispin_subscription_id", + Unique: false, + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[8]}, + }, + { + Name: "subscriptioncostbasispin_custom_currency_id", + Unique: false, + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[7]}, + }, + { + Name: "subscriptioncostbasispin_cost_basis_id", + Unique: false, + Columns: []*schema.Column{SubscriptionCostBasisPinsColumns[6]}, + }, + }, + } // SubscriptionItemsColumns holds the columns for the "subscription_items" table. SubscriptionItemsColumns = []*schema.Column{ {Name: "id", Type: field.TypeString, Unique: true, SchemaType: map[string]string{"postgres": "char(26)"}}, @@ -5720,6 +5791,7 @@ var ( SubscriptionAddonsTable, SubscriptionAddonQuantitiesTable, SubscriptionBillingSyncStatesTable, + SubscriptionCostBasisPinsTable, SubscriptionItemsTable, SubscriptionPhasesTable, TaxCodesTable, @@ -5922,13 +5994,16 @@ func init() { SubscriptionAddonsTable.ForeignKeys[1].RefTable = SubscriptionsTable SubscriptionAddonQuantitiesTable.ForeignKeys[0].RefTable = SubscriptionAddonsTable SubscriptionBillingSyncStatesTable.ForeignKeys[0].RefTable = SubscriptionsTable + SubscriptionCostBasisPinsTable.ForeignKeys[0].RefTable = CurrencyCostBasesTable + SubscriptionCostBasisPinsTable.ForeignKeys[1].RefTable = CustomCurrenciesTable + SubscriptionCostBasisPinsTable.ForeignKeys[2].RefTable = SubscriptionsTable SubscriptionItemsTable.ForeignKeys[0].RefTable = CustomCurrenciesTable SubscriptionItemsTable.ForeignKeys[1].RefTable = EntitlementsTable SubscriptionItemsTable.ForeignKeys[2].RefTable = SubscriptionPhasesTable SubscriptionItemsTable.ForeignKeys[3].RefTable = TaxCodesTable SubscriptionItemsTable.Annotation = &entsql.Annotation{} SubscriptionItemsTable.Annotation.Checks = map[string]string{ - "subscription_item_currency_has_price": "(price IS NOT NULL) OR ((currency IS NULL) AND (custom_currency_id IS NULL))", + "subscription_item_currency_has_price": "((price IS NULL) AND (currency IS NULL) AND (custom_currency_id IS NULL)) OR ((price IS NOT NULL) AND ((currency IS NOT NULL) OR (custom_currency_id IS NOT NULL)))", "subscription_item_currency_reference": "(currency IS NULL) OR (custom_currency_id IS NULL)", } SubscriptionPhasesTable.ForeignKeys[0].RefTable = SubscriptionsTable diff --git a/openmeter/ent/db/mutation.go b/openmeter/ent/db/mutation.go index 2923f943a5..fbe83bac68 100644 --- a/openmeter/ent/db/mutation.go +++ b/openmeter/ent/db/mutation.go @@ -105,6 +105,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddonquantity" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionbillingsyncstate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionitem" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionphase" dbtaxcode "github.com/openmeterio/openmeter/openmeter/ent/db/taxcode" @@ -210,6 +211,7 @@ const ( TypeSubscriptionAddon = "SubscriptionAddon" TypeSubscriptionAddonQuantity = "SubscriptionAddonQuantity" TypeSubscriptionBillingSyncState = "SubscriptionBillingSyncState" + TypeSubscriptionCostBasisPin = "SubscriptionCostBasisPin" TypeSubscriptionItem = "SubscriptionItem" TypeSubscriptionPhase = "SubscriptionPhase" TypeTaxCode = "TaxCode" @@ -73625,23 +73627,26 @@ func (m *CreditRealizationLineageSegmentMutation) ResetEdge(name string) error { // CurrencyCostBasisMutation represents an operation that mutates the CurrencyCostBasis nodes in the graph. type CurrencyCostBasisMutation struct { config - op Op - typ string - id *string - namespace *string - created_at *time.Time - updated_at *time.Time - deleted_at *time.Time - fiat_code *currencyx.Code - rate *alpacadecimal.Decimal - effective_from *time.Time - effective_to *time.Time - clearedFields map[string]struct{} - currency *string - clearedcurrency bool - done bool - oldValue func(context.Context) (*CurrencyCostBasis, error) - predicates []predicate.CurrencyCostBasis + op Op + typ string + id *string + namespace *string + created_at *time.Time + updated_at *time.Time + deleted_at *time.Time + fiat_code *currencyx.Code + rate *alpacadecimal.Decimal + effective_from *time.Time + effective_to *time.Time + clearedFields map[string]struct{} + currency *string + clearedcurrency bool + subscription_pins map[string]struct{} + removedsubscription_pins map[string]struct{} + clearedsubscription_pins bool + done bool + oldValue func(context.Context) (*CurrencyCostBasis, error) + predicates []predicate.CurrencyCostBasis } var _ ent.Mutation = (*CurrencyCostBasisMutation)(nil) @@ -74125,6 +74130,60 @@ func (m *CurrencyCostBasisMutation) ResetCurrency() { m.clearedcurrency = false } +// AddSubscriptionPinIDs adds the "subscription_pins" edge to the SubscriptionCostBasisPin entity by ids. +func (m *CurrencyCostBasisMutation) AddSubscriptionPinIDs(ids ...string) { + if m.subscription_pins == nil { + m.subscription_pins = make(map[string]struct{}) + } + for i := range ids { + m.subscription_pins[ids[i]] = struct{}{} + } +} + +// ClearSubscriptionPins clears the "subscription_pins" edge to the SubscriptionCostBasisPin entity. +func (m *CurrencyCostBasisMutation) ClearSubscriptionPins() { + m.clearedsubscription_pins = true +} + +// SubscriptionPinsCleared reports if the "subscription_pins" edge to the SubscriptionCostBasisPin entity was cleared. +func (m *CurrencyCostBasisMutation) SubscriptionPinsCleared() bool { + return m.clearedsubscription_pins +} + +// RemoveSubscriptionPinIDs removes the "subscription_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (m *CurrencyCostBasisMutation) RemoveSubscriptionPinIDs(ids ...string) { + if m.removedsubscription_pins == nil { + m.removedsubscription_pins = make(map[string]struct{}) + } + for i := range ids { + delete(m.subscription_pins, ids[i]) + m.removedsubscription_pins[ids[i]] = struct{}{} + } +} + +// RemovedSubscriptionPins returns the removed IDs of the "subscription_pins" edge to the SubscriptionCostBasisPin entity. +func (m *CurrencyCostBasisMutation) RemovedSubscriptionPinsIDs() (ids []string) { + for id := range m.removedsubscription_pins { + ids = append(ids, id) + } + return +} + +// SubscriptionPinsIDs returns the "subscription_pins" edge IDs in the mutation. +func (m *CurrencyCostBasisMutation) SubscriptionPinsIDs() (ids []string) { + for id := range m.subscription_pins { + ids = append(ids, id) + } + return +} + +// ResetSubscriptionPins resets all changes to the "subscription_pins" edge. +func (m *CurrencyCostBasisMutation) ResetSubscriptionPins() { + m.subscription_pins = nil + m.clearedsubscription_pins = false + m.removedsubscription_pins = nil +} + // Where appends a list predicates to the CurrencyCostBasisMutation builder. func (m *CurrencyCostBasisMutation) Where(ps ...predicate.CurrencyCostBasis) { m.predicates = append(m.predicates, ps...) @@ -74409,10 +74468,13 @@ func (m *CurrencyCostBasisMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *CurrencyCostBasisMutation) AddedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) if m.currency != nil { edges = append(edges, currencycostbasis.EdgeCurrency) } + if m.subscription_pins != nil { + edges = append(edges, currencycostbasis.EdgeSubscriptionPins) + } return edges } @@ -74424,28 +74486,48 @@ func (m *CurrencyCostBasisMutation) AddedIDs(name string) []ent.Value { if id := m.currency; id != nil { return []ent.Value{*id} } + case currencycostbasis.EdgeSubscriptionPins: + ids := make([]ent.Value, 0, len(m.subscription_pins)) + for id := range m.subscription_pins { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *CurrencyCostBasisMutation) RemovedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) + if m.removedsubscription_pins != nil { + edges = append(edges, currencycostbasis.EdgeSubscriptionPins) + } return edges } // RemovedIDs returns all IDs (to other nodes) that were removed for the edge with // the given name in this mutation. func (m *CurrencyCostBasisMutation) RemovedIDs(name string) []ent.Value { + switch name { + case currencycostbasis.EdgeSubscriptionPins: + ids := make([]ent.Value, 0, len(m.removedsubscription_pins)) + for id := range m.removedsubscription_pins { + ids = append(ids, id) + } + return ids + } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *CurrencyCostBasisMutation) ClearedEdges() []string { - edges := make([]string, 0, 1) + edges := make([]string, 0, 2) if m.clearedcurrency { edges = append(edges, currencycostbasis.EdgeCurrency) } + if m.clearedsubscription_pins { + edges = append(edges, currencycostbasis.EdgeSubscriptionPins) + } return edges } @@ -74455,6 +74537,8 @@ func (m *CurrencyCostBasisMutation) EdgeCleared(name string) bool { switch name { case currencycostbasis.EdgeCurrency: return m.clearedcurrency + case currencycostbasis.EdgeSubscriptionPins: + return m.clearedsubscription_pins } return false } @@ -74477,6 +74561,9 @@ func (m *CurrencyCostBasisMutation) ResetEdge(name string) error { case currencycostbasis.EdgeCurrency: m.ResetCurrency() return nil + case currencycostbasis.EdgeSubscriptionPins: + m.ResetSubscriptionPins() + return nil } return fmt.Errorf("unknown CurrencyCostBasis edge %s", name) } @@ -74484,38 +74571,41 @@ func (m *CurrencyCostBasisMutation) ResetEdge(name string) error { // CustomCurrencyMutation represents an operation that mutates the CustomCurrency nodes in the graph. type CustomCurrencyMutation struct { config - op Op - typ string - id *string - namespace *string - created_at *time.Time - updated_at *time.Time - deleted_at *time.Time - code *string - name *string - symbol *string - clearedFields map[string]struct{} - cost_basis_history map[string]struct{} - removedcost_basis_history map[string]struct{} - clearedcost_basis_history bool - plans map[string]struct{} - removedplans map[string]struct{} - clearedplans bool - addons map[string]struct{} - removedaddons map[string]struct{} - clearedaddons bool - plan_rate_cards map[string]struct{} - removedplan_rate_cards map[string]struct{} - clearedplan_rate_cards bool - addon_rate_cards map[string]struct{} - removedaddon_rate_cards map[string]struct{} - clearedaddon_rate_cards bool - subscription_items map[string]struct{} - removedsubscription_items map[string]struct{} - clearedsubscription_items bool - done bool - oldValue func(context.Context) (*CustomCurrency, error) - predicates []predicate.CustomCurrency + op Op + typ string + id *string + namespace *string + created_at *time.Time + updated_at *time.Time + deleted_at *time.Time + code *string + name *string + symbol *string + clearedFields map[string]struct{} + cost_basis_history map[string]struct{} + removedcost_basis_history map[string]struct{} + clearedcost_basis_history bool + plans map[string]struct{} + removedplans map[string]struct{} + clearedplans bool + addons map[string]struct{} + removedaddons map[string]struct{} + clearedaddons bool + plan_rate_cards map[string]struct{} + removedplan_rate_cards map[string]struct{} + clearedplan_rate_cards bool + addon_rate_cards map[string]struct{} + removedaddon_rate_cards map[string]struct{} + clearedaddon_rate_cards bool + subscription_items map[string]struct{} + removedsubscription_items map[string]struct{} + clearedsubscription_items bool + subscription_cost_basis_pins map[string]struct{} + removedsubscription_cost_basis_pins map[string]struct{} + clearedsubscription_cost_basis_pins bool + done bool + oldValue func(context.Context) (*CustomCurrency, error) + predicates []predicate.CustomCurrency } var _ ent.Mutation = (*CustomCurrencyMutation)(nil) @@ -75211,6 +75301,60 @@ func (m *CustomCurrencyMutation) ResetSubscriptionItems() { m.removedsubscription_items = nil } +// AddSubscriptionCostBasisPinIDs adds the "subscription_cost_basis_pins" edge to the SubscriptionCostBasisPin entity by ids. +func (m *CustomCurrencyMutation) AddSubscriptionCostBasisPinIDs(ids ...string) { + if m.subscription_cost_basis_pins == nil { + m.subscription_cost_basis_pins = make(map[string]struct{}) + } + for i := range ids { + m.subscription_cost_basis_pins[ids[i]] = struct{}{} + } +} + +// ClearSubscriptionCostBasisPins clears the "subscription_cost_basis_pins" edge to the SubscriptionCostBasisPin entity. +func (m *CustomCurrencyMutation) ClearSubscriptionCostBasisPins() { + m.clearedsubscription_cost_basis_pins = true +} + +// SubscriptionCostBasisPinsCleared reports if the "subscription_cost_basis_pins" edge to the SubscriptionCostBasisPin entity was cleared. +func (m *CustomCurrencyMutation) SubscriptionCostBasisPinsCleared() bool { + return m.clearedsubscription_cost_basis_pins +} + +// RemoveSubscriptionCostBasisPinIDs removes the "subscription_cost_basis_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (m *CustomCurrencyMutation) RemoveSubscriptionCostBasisPinIDs(ids ...string) { + if m.removedsubscription_cost_basis_pins == nil { + m.removedsubscription_cost_basis_pins = make(map[string]struct{}) + } + for i := range ids { + delete(m.subscription_cost_basis_pins, ids[i]) + m.removedsubscription_cost_basis_pins[ids[i]] = struct{}{} + } +} + +// RemovedSubscriptionCostBasisPins returns the removed IDs of the "subscription_cost_basis_pins" edge to the SubscriptionCostBasisPin entity. +func (m *CustomCurrencyMutation) RemovedSubscriptionCostBasisPinsIDs() (ids []string) { + for id := range m.removedsubscription_cost_basis_pins { + ids = append(ids, id) + } + return +} + +// SubscriptionCostBasisPinsIDs returns the "subscription_cost_basis_pins" edge IDs in the mutation. +func (m *CustomCurrencyMutation) SubscriptionCostBasisPinsIDs() (ids []string) { + for id := range m.subscription_cost_basis_pins { + ids = append(ids, id) + } + return +} + +// ResetSubscriptionCostBasisPins resets all changes to the "subscription_cost_basis_pins" edge. +func (m *CustomCurrencyMutation) ResetSubscriptionCostBasisPins() { + m.subscription_cost_basis_pins = nil + m.clearedsubscription_cost_basis_pins = false + m.removedsubscription_cost_basis_pins = nil +} + // Where appends a list predicates to the CustomCurrencyMutation builder. func (m *CustomCurrencyMutation) Where(ps ...predicate.CustomCurrency) { m.predicates = append(m.predicates, ps...) @@ -75455,7 +75599,7 @@ func (m *CustomCurrencyMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *CustomCurrencyMutation) AddedEdges() []string { - edges := make([]string, 0, 6) + edges := make([]string, 0, 7) if m.cost_basis_history != nil { edges = append(edges, customcurrency.EdgeCostBasisHistory) } @@ -75474,6 +75618,9 @@ func (m *CustomCurrencyMutation) AddedEdges() []string { if m.subscription_items != nil { edges = append(edges, customcurrency.EdgeSubscriptionItems) } + if m.subscription_cost_basis_pins != nil { + edges = append(edges, customcurrency.EdgeSubscriptionCostBasisPins) + } return edges } @@ -75517,13 +75664,19 @@ func (m *CustomCurrencyMutation) AddedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case customcurrency.EdgeSubscriptionCostBasisPins: + ids := make([]ent.Value, 0, len(m.subscription_cost_basis_pins)) + for id := range m.subscription_cost_basis_pins { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *CustomCurrencyMutation) RemovedEdges() []string { - edges := make([]string, 0, 6) + edges := make([]string, 0, 7) if m.removedcost_basis_history != nil { edges = append(edges, customcurrency.EdgeCostBasisHistory) } @@ -75542,6 +75695,9 @@ func (m *CustomCurrencyMutation) RemovedEdges() []string { if m.removedsubscription_items != nil { edges = append(edges, customcurrency.EdgeSubscriptionItems) } + if m.removedsubscription_cost_basis_pins != nil { + edges = append(edges, customcurrency.EdgeSubscriptionCostBasisPins) + } return edges } @@ -75585,13 +75741,19 @@ func (m *CustomCurrencyMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case customcurrency.EdgeSubscriptionCostBasisPins: + ids := make([]ent.Value, 0, len(m.removedsubscription_cost_basis_pins)) + for id := range m.removedsubscription_cost_basis_pins { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *CustomCurrencyMutation) ClearedEdges() []string { - edges := make([]string, 0, 6) + edges := make([]string, 0, 7) if m.clearedcost_basis_history { edges = append(edges, customcurrency.EdgeCostBasisHistory) } @@ -75610,6 +75772,9 @@ func (m *CustomCurrencyMutation) ClearedEdges() []string { if m.clearedsubscription_items { edges = append(edges, customcurrency.EdgeSubscriptionItems) } + if m.clearedsubscription_cost_basis_pins { + edges = append(edges, customcurrency.EdgeSubscriptionCostBasisPins) + } return edges } @@ -75629,6 +75794,8 @@ func (m *CustomCurrencyMutation) EdgeCleared(name string) bool { return m.clearedaddon_rate_cards case customcurrency.EdgeSubscriptionItems: return m.clearedsubscription_items + case customcurrency.EdgeSubscriptionCostBasisPins: + return m.clearedsubscription_cost_basis_pins } return false } @@ -75663,6 +75830,9 @@ func (m *CustomCurrencyMutation) ResetEdge(name string) error { case customcurrency.EdgeSubscriptionItems: m.ResetSubscriptionItems() return nil + case customcurrency.EdgeSubscriptionCostBasisPins: + m.ResetSubscriptionCostBasisPins() + return nil } return fmt.Errorf("unknown CustomCurrency edge %s", name) } @@ -108772,7 +108942,8 @@ type SubscriptionMutation struct { active_to *time.Time name *string description *string - currency *currencyx.Code + invoice_currency *currencyx.Code + cost_basis_mode *subscription.CostBasisMode billing_anchor *time.Time billing_cadence *datetime.ISODurationString pro_rating_config *productcatalog.ProRatingConfig @@ -108808,6 +108979,9 @@ type SubscriptionMutation struct { clearedaddons bool billing_sync_state *string clearedbilling_sync_state bool + cost_basis_pins map[string]struct{} + removedcost_basis_pins map[string]struct{} + clearedcost_basis_pins bool done bool oldValue func(context.Context) (*Subscription, error) predicates []predicate.Subscription @@ -109427,40 +109601,76 @@ func (m *SubscriptionMutation) ResetCustomerID() { m.customer = nil } -// SetCurrency sets the "currency" field. -func (m *SubscriptionMutation) SetCurrency(c currencyx.Code) { - m.currency = &c +// SetInvoiceCurrency sets the "invoice_currency" field. +func (m *SubscriptionMutation) SetInvoiceCurrency(c currencyx.Code) { + m.invoice_currency = &c } -// Currency returns the value of the "currency" field in the mutation. -func (m *SubscriptionMutation) Currency() (r currencyx.Code, exists bool) { - v := m.currency +// InvoiceCurrency returns the value of the "invoice_currency" field in the mutation. +func (m *SubscriptionMutation) InvoiceCurrency() (r currencyx.Code, exists bool) { + v := m.invoice_currency if v == nil { return } return *v, true } -// OldCurrency returns the old "currency" field's value of the Subscription entity. +// OldInvoiceCurrency returns the old "invoice_currency" field's value of the Subscription entity. // If the Subscription object wasn't provided to the builder, the object is fetched from the database. // An error is returned if the mutation operation is not UpdateOne, or the database query fails. -func (m *SubscriptionMutation) OldCurrency(ctx context.Context) (v currencyx.Code, err error) { +func (m *SubscriptionMutation) OldInvoiceCurrency(ctx context.Context) (v currencyx.Code, err error) { if !m.op.Is(OpUpdateOne) { - return v, errors.New("OldCurrency is only allowed on UpdateOne operations") + return v, errors.New("OldInvoiceCurrency is only allowed on UpdateOne operations") } if m.id == nil || m.oldValue == nil { - return v, errors.New("OldCurrency requires an ID field in the mutation") + return v, errors.New("OldInvoiceCurrency requires an ID field in the mutation") } oldValue, err := m.oldValue(ctx) if err != nil { - return v, fmt.Errorf("querying old value for OldCurrency: %w", err) + return v, fmt.Errorf("querying old value for OldInvoiceCurrency: %w", err) } - return oldValue.Currency, nil + return oldValue.InvoiceCurrency, nil } -// ResetCurrency resets all changes to the "currency" field. -func (m *SubscriptionMutation) ResetCurrency() { - m.currency = nil +// ResetInvoiceCurrency resets all changes to the "invoice_currency" field. +func (m *SubscriptionMutation) ResetInvoiceCurrency() { + m.invoice_currency = nil +} + +// SetCostBasisMode sets the "cost_basis_mode" field. +func (m *SubscriptionMutation) SetCostBasisMode(sbm subscription.CostBasisMode) { + m.cost_basis_mode = &sbm +} + +// CostBasisMode returns the value of the "cost_basis_mode" field in the mutation. +func (m *SubscriptionMutation) CostBasisMode() (r subscription.CostBasisMode, exists bool) { + v := m.cost_basis_mode + if v == nil { + return + } + return *v, true +} + +// OldCostBasisMode returns the old "cost_basis_mode" field's value of the Subscription entity. +// If the Subscription object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionMutation) OldCostBasisMode(ctx context.Context) (v subscription.CostBasisMode, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCostBasisMode is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCostBasisMode requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCostBasisMode: %w", err) + } + return oldValue.CostBasisMode, nil +} + +// ResetCostBasisMode resets all changes to the "cost_basis_mode" field. +func (m *SubscriptionMutation) ResetCostBasisMode() { + m.cost_basis_mode = nil } // SetBillingAnchor sets the "billing_anchor" field. @@ -110132,6 +110342,60 @@ func (m *SubscriptionMutation) ResetBillingSyncState() { m.clearedbilling_sync_state = false } +// AddCostBasisPinIDs adds the "cost_basis_pins" edge to the SubscriptionCostBasisPin entity by ids. +func (m *SubscriptionMutation) AddCostBasisPinIDs(ids ...string) { + if m.cost_basis_pins == nil { + m.cost_basis_pins = make(map[string]struct{}) + } + for i := range ids { + m.cost_basis_pins[ids[i]] = struct{}{} + } +} + +// ClearCostBasisPins clears the "cost_basis_pins" edge to the SubscriptionCostBasisPin entity. +func (m *SubscriptionMutation) ClearCostBasisPins() { + m.clearedcost_basis_pins = true +} + +// CostBasisPinsCleared reports if the "cost_basis_pins" edge to the SubscriptionCostBasisPin entity was cleared. +func (m *SubscriptionMutation) CostBasisPinsCleared() bool { + return m.clearedcost_basis_pins +} + +// RemoveCostBasisPinIDs removes the "cost_basis_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (m *SubscriptionMutation) RemoveCostBasisPinIDs(ids ...string) { + if m.removedcost_basis_pins == nil { + m.removedcost_basis_pins = make(map[string]struct{}) + } + for i := range ids { + delete(m.cost_basis_pins, ids[i]) + m.removedcost_basis_pins[ids[i]] = struct{}{} + } +} + +// RemovedCostBasisPins returns the removed IDs of the "cost_basis_pins" edge to the SubscriptionCostBasisPin entity. +func (m *SubscriptionMutation) RemovedCostBasisPinsIDs() (ids []string) { + for id := range m.removedcost_basis_pins { + ids = append(ids, id) + } + return +} + +// CostBasisPinsIDs returns the "cost_basis_pins" edge IDs in the mutation. +func (m *SubscriptionMutation) CostBasisPinsIDs() (ids []string) { + for id := range m.cost_basis_pins { + ids = append(ids, id) + } + return +} + +// ResetCostBasisPins resets all changes to the "cost_basis_pins" edge. +func (m *SubscriptionMutation) ResetCostBasisPins() { + m.cost_basis_pins = nil + m.clearedcost_basis_pins = false + m.removedcost_basis_pins = nil +} + // Where appends a list predicates to the SubscriptionMutation builder. func (m *SubscriptionMutation) Where(ps ...predicate.Subscription) { m.predicates = append(m.predicates, ps...) @@ -110166,7 +110430,7 @@ func (m *SubscriptionMutation) Type() string { // order to get all numeric fields that were incremented/decremented, call // AddedFields(). func (m *SubscriptionMutation) Fields() []string { - fields := make([]string, 0, 17) + fields := make([]string, 0, 18) if m.namespace != nil { fields = append(fields, subscription.FieldNamespace) } @@ -110203,8 +110467,11 @@ func (m *SubscriptionMutation) Fields() []string { if m.customer != nil { fields = append(fields, subscription.FieldCustomerID) } - if m.currency != nil { - fields = append(fields, subscription.FieldCurrency) + if m.invoice_currency != nil { + fields = append(fields, subscription.FieldInvoiceCurrency) + } + if m.cost_basis_mode != nil { + fields = append(fields, subscription.FieldCostBasisMode) } if m.billing_anchor != nil { fields = append(fields, subscription.FieldBillingAnchor) @@ -110250,8 +110517,10 @@ func (m *SubscriptionMutation) Field(name string) (ent.Value, bool) { return m.PlanID() case subscription.FieldCustomerID: return m.CustomerID() - case subscription.FieldCurrency: - return m.Currency() + case subscription.FieldInvoiceCurrency: + return m.InvoiceCurrency() + case subscription.FieldCostBasisMode: + return m.CostBasisMode() case subscription.FieldBillingAnchor: return m.BillingAnchor() case subscription.FieldBillingCadence: @@ -110293,8 +110562,10 @@ func (m *SubscriptionMutation) OldField(ctx context.Context, name string) (ent.V return m.OldPlanID(ctx) case subscription.FieldCustomerID: return m.OldCustomerID(ctx) - case subscription.FieldCurrency: - return m.OldCurrency(ctx) + case subscription.FieldInvoiceCurrency: + return m.OldInvoiceCurrency(ctx) + case subscription.FieldCostBasisMode: + return m.OldCostBasisMode(ctx) case subscription.FieldBillingAnchor: return m.OldBillingAnchor(ctx) case subscription.FieldBillingCadence: @@ -110396,12 +110667,19 @@ func (m *SubscriptionMutation) SetField(name string, value ent.Value) error { } m.SetCustomerID(v) return nil - case subscription.FieldCurrency: + case subscription.FieldInvoiceCurrency: v, ok := value.(currencyx.Code) if !ok { return fmt.Errorf("unexpected type %T for field %s", value, name) } - m.SetCurrency(v) + m.SetInvoiceCurrency(v) + return nil + case subscription.FieldCostBasisMode: + v, ok := value.(subscription.CostBasisMode) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCostBasisMode(v) return nil case subscription.FieldBillingAnchor: v, ok := value.(time.Time) @@ -110555,8 +110833,11 @@ func (m *SubscriptionMutation) ResetField(name string) error { case subscription.FieldCustomerID: m.ResetCustomerID() return nil - case subscription.FieldCurrency: - m.ResetCurrency() + case subscription.FieldInvoiceCurrency: + m.ResetInvoiceCurrency() + return nil + case subscription.FieldCostBasisMode: + m.ResetCostBasisMode() return nil case subscription.FieldBillingAnchor: m.ResetBillingAnchor() @@ -110576,7 +110857,7 @@ func (m *SubscriptionMutation) ResetField(name string) error { // AddedEdges returns all edge names that were set/added in this mutation. func (m *SubscriptionMutation) AddedEdges() []string { - edges := make([]string, 0, 11) + edges := make([]string, 0, 12) if m.plan != nil { edges = append(edges, subscription.EdgePlan) } @@ -110610,6 +110891,9 @@ func (m *SubscriptionMutation) AddedEdges() []string { if m.billing_sync_state != nil { edges = append(edges, subscription.EdgeBillingSyncState) } + if m.cost_basis_pins != nil { + edges = append(edges, subscription.EdgeCostBasisPins) + } return edges } @@ -110677,13 +110961,19 @@ func (m *SubscriptionMutation) AddedIDs(name string) []ent.Value { if id := m.billing_sync_state; id != nil { return []ent.Value{*id} } + case subscription.EdgeCostBasisPins: + ids := make([]ent.Value, 0, len(m.cost_basis_pins)) + for id := range m.cost_basis_pins { + ids = append(ids, id) + } + return ids } return nil } // RemovedEdges returns all edge names that were removed in this mutation. func (m *SubscriptionMutation) RemovedEdges() []string { - edges := make([]string, 0, 11) + edges := make([]string, 0, 12) if m.removedphases != nil { edges = append(edges, subscription.EdgePhases) } @@ -110708,6 +110998,9 @@ func (m *SubscriptionMutation) RemovedEdges() []string { if m.removedaddons != nil { edges = append(edges, subscription.EdgeAddons) } + if m.removedcost_basis_pins != nil { + edges = append(edges, subscription.EdgeCostBasisPins) + } return edges } @@ -110763,13 +111056,19 @@ func (m *SubscriptionMutation) RemovedIDs(name string) []ent.Value { ids = append(ids, id) } return ids + case subscription.EdgeCostBasisPins: + ids := make([]ent.Value, 0, len(m.removedcost_basis_pins)) + for id := range m.removedcost_basis_pins { + ids = append(ids, id) + } + return ids } return nil } // ClearedEdges returns all edge names that were cleared in this mutation. func (m *SubscriptionMutation) ClearedEdges() []string { - edges := make([]string, 0, 11) + edges := make([]string, 0, 12) if m.clearedplan { edges = append(edges, subscription.EdgePlan) } @@ -110803,6 +111102,9 @@ func (m *SubscriptionMutation) ClearedEdges() []string { if m.clearedbilling_sync_state { edges = append(edges, subscription.EdgeBillingSyncState) } + if m.clearedcost_basis_pins { + edges = append(edges, subscription.EdgeCostBasisPins) + } return edges } @@ -110832,6 +111134,8 @@ func (m *SubscriptionMutation) EdgeCleared(name string) bool { return m.clearedaddons case subscription.EdgeBillingSyncState: return m.clearedbilling_sync_state + case subscription.EdgeCostBasisPins: + return m.clearedcost_basis_pins } return false } @@ -110890,6 +111194,9 @@ func (m *SubscriptionMutation) ResetEdge(name string) error { case subscription.EdgeBillingSyncState: m.ResetBillingSyncState() return nil + case subscription.EdgeCostBasisPins: + m.ResetCostBasisPins() + return nil } return fmt.Errorf("unknown Subscription edge %s", name) } @@ -113168,6 +113475,884 @@ func (m *SubscriptionBillingSyncStateMutation) ResetEdge(name string) error { return fmt.Errorf("unknown SubscriptionBillingSyncState edge %s", name) } +// SubscriptionCostBasisPinMutation represents an operation that mutates the SubscriptionCostBasisPin nodes in the graph. +type SubscriptionCostBasisPinMutation struct { + config + op Op + typ string + id *string + namespace *string + created_at *time.Time + updated_at *time.Time + deleted_at *time.Time + invoice_currency *currencyx.Code + clearedFields map[string]struct{} + subscription *string + clearedsubscription bool + custom_currency *string + clearedcustom_currency bool + cost_basis *string + clearedcost_basis bool + done bool + oldValue func(context.Context) (*SubscriptionCostBasisPin, error) + predicates []predicate.SubscriptionCostBasisPin +} + +var _ ent.Mutation = (*SubscriptionCostBasisPinMutation)(nil) + +// subscriptioncostbasispinOption allows management of the mutation configuration using functional options. +type subscriptioncostbasispinOption func(*SubscriptionCostBasisPinMutation) + +// newSubscriptionCostBasisPinMutation creates new mutation for the SubscriptionCostBasisPin entity. +func newSubscriptionCostBasisPinMutation(c config, op Op, opts ...subscriptioncostbasispinOption) *SubscriptionCostBasisPinMutation { + m := &SubscriptionCostBasisPinMutation{ + config: c, + op: op, + typ: TypeSubscriptionCostBasisPin, + clearedFields: make(map[string]struct{}), + } + for _, opt := range opts { + opt(m) + } + return m +} + +// withSubscriptionCostBasisPinID sets the ID field of the mutation. +func withSubscriptionCostBasisPinID(id string) subscriptioncostbasispinOption { + return func(m *SubscriptionCostBasisPinMutation) { + var ( + err error + once sync.Once + value *SubscriptionCostBasisPin + ) + m.oldValue = func(ctx context.Context) (*SubscriptionCostBasisPin, error) { + once.Do(func() { + if m.done { + err = errors.New("querying old values post mutation is not allowed") + } else { + value, err = m.Client().SubscriptionCostBasisPin.Get(ctx, id) + } + }) + return value, err + } + m.id = &id + } +} + +// withSubscriptionCostBasisPin sets the old SubscriptionCostBasisPin of the mutation. +func withSubscriptionCostBasisPin(node *SubscriptionCostBasisPin) subscriptioncostbasispinOption { + return func(m *SubscriptionCostBasisPinMutation) { + m.oldValue = func(context.Context) (*SubscriptionCostBasisPin, error) { + return node, nil + } + m.id = &node.ID + } +} + +// Client returns a new `ent.Client` from the mutation. If the mutation was +// executed in a transaction (ent.Tx), a transactional client is returned. +func (m SubscriptionCostBasisPinMutation) Client() *Client { + client := &Client{config: m.config} + client.init() + return client +} + +// Tx returns an `ent.Tx` for mutations that were executed in transactions; +// it returns an error otherwise. +func (m SubscriptionCostBasisPinMutation) Tx() (*Tx, error) { + if _, ok := m.driver.(*txDriver); !ok { + return nil, errors.New("db: mutation is not running in a transaction") + } + tx := &Tx{config: m.config} + tx.init() + return tx, nil +} + +// SetID sets the value of the id field. Note that this +// operation is only accepted on creation of SubscriptionCostBasisPin entities. +func (m *SubscriptionCostBasisPinMutation) SetID(id string) { + m.id = &id +} + +// ID returns the ID value in the mutation. Note that the ID is only available +// if it was provided to the builder or after it was returned from the database. +func (m *SubscriptionCostBasisPinMutation) ID() (id string, exists bool) { + if m.id == nil { + return + } + return *m.id, true +} + +// IDs queries the database and returns the entity ids that match the mutation's predicate. +// That means, if the mutation is applied within a transaction with an isolation level such +// as sql.LevelSerializable, the returned ids match the ids of the rows that will be updated +// or updated by the mutation. +func (m *SubscriptionCostBasisPinMutation) IDs(ctx context.Context) ([]string, error) { + switch { + case m.op.Is(OpUpdateOne | OpDeleteOne): + id, exists := m.ID() + if exists { + return []string{id}, nil + } + fallthrough + case m.op.Is(OpUpdate | OpDelete): + return m.Client().SubscriptionCostBasisPin.Query().Where(m.predicates...).IDs(ctx) + default: + return nil, fmt.Errorf("IDs is not allowed on %s operations", m.op) + } +} + +// SetNamespace sets the "namespace" field. +func (m *SubscriptionCostBasisPinMutation) SetNamespace(s string) { + m.namespace = &s +} + +// Namespace returns the value of the "namespace" field in the mutation. +func (m *SubscriptionCostBasisPinMutation) Namespace() (r string, exists bool) { + v := m.namespace + if v == nil { + return + } + return *v, true +} + +// OldNamespace returns the old "namespace" field's value of the SubscriptionCostBasisPin entity. +// If the SubscriptionCostBasisPin object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionCostBasisPinMutation) OldNamespace(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldNamespace is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldNamespace requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldNamespace: %w", err) + } + return oldValue.Namespace, nil +} + +// ResetNamespace resets all changes to the "namespace" field. +func (m *SubscriptionCostBasisPinMutation) ResetNamespace() { + m.namespace = nil +} + +// SetCreatedAt sets the "created_at" field. +func (m *SubscriptionCostBasisPinMutation) SetCreatedAt(t time.Time) { + m.created_at = &t +} + +// CreatedAt returns the value of the "created_at" field in the mutation. +func (m *SubscriptionCostBasisPinMutation) CreatedAt() (r time.Time, exists bool) { + v := m.created_at + if v == nil { + return + } + return *v, true +} + +// OldCreatedAt returns the old "created_at" field's value of the SubscriptionCostBasisPin entity. +// If the SubscriptionCostBasisPin object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionCostBasisPinMutation) OldCreatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCreatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCreatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCreatedAt: %w", err) + } + return oldValue.CreatedAt, nil +} + +// ResetCreatedAt resets all changes to the "created_at" field. +func (m *SubscriptionCostBasisPinMutation) ResetCreatedAt() { + m.created_at = nil +} + +// SetUpdatedAt sets the "updated_at" field. +func (m *SubscriptionCostBasisPinMutation) SetUpdatedAt(t time.Time) { + m.updated_at = &t +} + +// UpdatedAt returns the value of the "updated_at" field in the mutation. +func (m *SubscriptionCostBasisPinMutation) UpdatedAt() (r time.Time, exists bool) { + v := m.updated_at + if v == nil { + return + } + return *v, true +} + +// OldUpdatedAt returns the old "updated_at" field's value of the SubscriptionCostBasisPin entity. +// If the SubscriptionCostBasisPin object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionCostBasisPinMutation) OldUpdatedAt(ctx context.Context) (v time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldUpdatedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldUpdatedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldUpdatedAt: %w", err) + } + return oldValue.UpdatedAt, nil +} + +// ResetUpdatedAt resets all changes to the "updated_at" field. +func (m *SubscriptionCostBasisPinMutation) ResetUpdatedAt() { + m.updated_at = nil +} + +// SetDeletedAt sets the "deleted_at" field. +func (m *SubscriptionCostBasisPinMutation) SetDeletedAt(t time.Time) { + m.deleted_at = &t +} + +// DeletedAt returns the value of the "deleted_at" field in the mutation. +func (m *SubscriptionCostBasisPinMutation) DeletedAt() (r time.Time, exists bool) { + v := m.deleted_at + if v == nil { + return + } + return *v, true +} + +// OldDeletedAt returns the old "deleted_at" field's value of the SubscriptionCostBasisPin entity. +// If the SubscriptionCostBasisPin object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionCostBasisPinMutation) OldDeletedAt(ctx context.Context) (v *time.Time, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldDeletedAt is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldDeletedAt requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldDeletedAt: %w", err) + } + return oldValue.DeletedAt, nil +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (m *SubscriptionCostBasisPinMutation) ClearDeletedAt() { + m.deleted_at = nil + m.clearedFields[subscriptioncostbasispin.FieldDeletedAt] = struct{}{} +} + +// DeletedAtCleared returns if the "deleted_at" field was cleared in this mutation. +func (m *SubscriptionCostBasisPinMutation) DeletedAtCleared() bool { + _, ok := m.clearedFields[subscriptioncostbasispin.FieldDeletedAt] + return ok +} + +// ResetDeletedAt resets all changes to the "deleted_at" field. +func (m *SubscriptionCostBasisPinMutation) ResetDeletedAt() { + m.deleted_at = nil + delete(m.clearedFields, subscriptioncostbasispin.FieldDeletedAt) +} + +// SetSubscriptionID sets the "subscription_id" field. +func (m *SubscriptionCostBasisPinMutation) SetSubscriptionID(s string) { + m.subscription = &s +} + +// SubscriptionID returns the value of the "subscription_id" field in the mutation. +func (m *SubscriptionCostBasisPinMutation) SubscriptionID() (r string, exists bool) { + v := m.subscription + if v == nil { + return + } + return *v, true +} + +// OldSubscriptionID returns the old "subscription_id" field's value of the SubscriptionCostBasisPin entity. +// If the SubscriptionCostBasisPin object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionCostBasisPinMutation) OldSubscriptionID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldSubscriptionID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldSubscriptionID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldSubscriptionID: %w", err) + } + return oldValue.SubscriptionID, nil +} + +// ResetSubscriptionID resets all changes to the "subscription_id" field. +func (m *SubscriptionCostBasisPinMutation) ResetSubscriptionID() { + m.subscription = nil +} + +// SetCustomCurrencyID sets the "custom_currency_id" field. +func (m *SubscriptionCostBasisPinMutation) SetCustomCurrencyID(s string) { + m.custom_currency = &s +} + +// CustomCurrencyID returns the value of the "custom_currency_id" field in the mutation. +func (m *SubscriptionCostBasisPinMutation) CustomCurrencyID() (r string, exists bool) { + v := m.custom_currency + if v == nil { + return + } + return *v, true +} + +// OldCustomCurrencyID returns the old "custom_currency_id" field's value of the SubscriptionCostBasisPin entity. +// If the SubscriptionCostBasisPin object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionCostBasisPinMutation) OldCustomCurrencyID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCustomCurrencyID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCustomCurrencyID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCustomCurrencyID: %w", err) + } + return oldValue.CustomCurrencyID, nil +} + +// ResetCustomCurrencyID resets all changes to the "custom_currency_id" field. +func (m *SubscriptionCostBasisPinMutation) ResetCustomCurrencyID() { + m.custom_currency = nil +} + +// SetInvoiceCurrency sets the "invoice_currency" field. +func (m *SubscriptionCostBasisPinMutation) SetInvoiceCurrency(c currencyx.Code) { + m.invoice_currency = &c +} + +// InvoiceCurrency returns the value of the "invoice_currency" field in the mutation. +func (m *SubscriptionCostBasisPinMutation) InvoiceCurrency() (r currencyx.Code, exists bool) { + v := m.invoice_currency + if v == nil { + return + } + return *v, true +} + +// OldInvoiceCurrency returns the old "invoice_currency" field's value of the SubscriptionCostBasisPin entity. +// If the SubscriptionCostBasisPin object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionCostBasisPinMutation) OldInvoiceCurrency(ctx context.Context) (v currencyx.Code, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldInvoiceCurrency is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldInvoiceCurrency requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldInvoiceCurrency: %w", err) + } + return oldValue.InvoiceCurrency, nil +} + +// ResetInvoiceCurrency resets all changes to the "invoice_currency" field. +func (m *SubscriptionCostBasisPinMutation) ResetInvoiceCurrency() { + m.invoice_currency = nil +} + +// SetCostBasisID sets the "cost_basis_id" field. +func (m *SubscriptionCostBasisPinMutation) SetCostBasisID(s string) { + m.cost_basis = &s +} + +// CostBasisID returns the value of the "cost_basis_id" field in the mutation. +func (m *SubscriptionCostBasisPinMutation) CostBasisID() (r string, exists bool) { + v := m.cost_basis + if v == nil { + return + } + return *v, true +} + +// OldCostBasisID returns the old "cost_basis_id" field's value of the SubscriptionCostBasisPin entity. +// If the SubscriptionCostBasisPin object wasn't provided to the builder, the object is fetched from the database. +// An error is returned if the mutation operation is not UpdateOne, or the database query fails. +func (m *SubscriptionCostBasisPinMutation) OldCostBasisID(ctx context.Context) (v string, err error) { + if !m.op.Is(OpUpdateOne) { + return v, errors.New("OldCostBasisID is only allowed on UpdateOne operations") + } + if m.id == nil || m.oldValue == nil { + return v, errors.New("OldCostBasisID requires an ID field in the mutation") + } + oldValue, err := m.oldValue(ctx) + if err != nil { + return v, fmt.Errorf("querying old value for OldCostBasisID: %w", err) + } + return oldValue.CostBasisID, nil +} + +// ResetCostBasisID resets all changes to the "cost_basis_id" field. +func (m *SubscriptionCostBasisPinMutation) ResetCostBasisID() { + m.cost_basis = nil +} + +// ClearSubscription clears the "subscription" edge to the Subscription entity. +func (m *SubscriptionCostBasisPinMutation) ClearSubscription() { + m.clearedsubscription = true + m.clearedFields[subscriptioncostbasispin.FieldSubscriptionID] = struct{}{} +} + +// SubscriptionCleared reports if the "subscription" edge to the Subscription entity was cleared. +func (m *SubscriptionCostBasisPinMutation) SubscriptionCleared() bool { + return m.clearedsubscription +} + +// SubscriptionIDs returns the "subscription" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// SubscriptionID instead. It exists only for internal usage by the builders. +func (m *SubscriptionCostBasisPinMutation) SubscriptionIDs() (ids []string) { + if id := m.subscription; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetSubscription resets all changes to the "subscription" edge. +func (m *SubscriptionCostBasisPinMutation) ResetSubscription() { + m.subscription = nil + m.clearedsubscription = false +} + +// ClearCustomCurrency clears the "custom_currency" edge to the CustomCurrency entity. +func (m *SubscriptionCostBasisPinMutation) ClearCustomCurrency() { + m.clearedcustom_currency = true + m.clearedFields[subscriptioncostbasispin.FieldCustomCurrencyID] = struct{}{} +} + +// CustomCurrencyCleared reports if the "custom_currency" edge to the CustomCurrency entity was cleared. +func (m *SubscriptionCostBasisPinMutation) CustomCurrencyCleared() bool { + return m.clearedcustom_currency +} + +// CustomCurrencyIDs returns the "custom_currency" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// CustomCurrencyID instead. It exists only for internal usage by the builders. +func (m *SubscriptionCostBasisPinMutation) CustomCurrencyIDs() (ids []string) { + if id := m.custom_currency; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetCustomCurrency resets all changes to the "custom_currency" edge. +func (m *SubscriptionCostBasisPinMutation) ResetCustomCurrency() { + m.custom_currency = nil + m.clearedcustom_currency = false +} + +// ClearCostBasis clears the "cost_basis" edge to the CurrencyCostBasis entity. +func (m *SubscriptionCostBasisPinMutation) ClearCostBasis() { + m.clearedcost_basis = true + m.clearedFields[subscriptioncostbasispin.FieldCostBasisID] = struct{}{} +} + +// CostBasisCleared reports if the "cost_basis" edge to the CurrencyCostBasis entity was cleared. +func (m *SubscriptionCostBasisPinMutation) CostBasisCleared() bool { + return m.clearedcost_basis +} + +// CostBasisIDs returns the "cost_basis" edge IDs in the mutation. +// Note that IDs always returns len(IDs) <= 1 for unique edges, and you should use +// CostBasisID instead. It exists only for internal usage by the builders. +func (m *SubscriptionCostBasisPinMutation) CostBasisIDs() (ids []string) { + if id := m.cost_basis; id != nil { + ids = append(ids, *id) + } + return +} + +// ResetCostBasis resets all changes to the "cost_basis" edge. +func (m *SubscriptionCostBasisPinMutation) ResetCostBasis() { + m.cost_basis = nil + m.clearedcost_basis = false +} + +// Where appends a list predicates to the SubscriptionCostBasisPinMutation builder. +func (m *SubscriptionCostBasisPinMutation) Where(ps ...predicate.SubscriptionCostBasisPin) { + m.predicates = append(m.predicates, ps...) +} + +// WhereP appends storage-level predicates to the SubscriptionCostBasisPinMutation builder. Using this method, +// users can use type-assertion to append predicates that do not depend on any generated package. +func (m *SubscriptionCostBasisPinMutation) WhereP(ps ...func(*sql.Selector)) { + p := make([]predicate.SubscriptionCostBasisPin, len(ps)) + for i := range ps { + p[i] = ps[i] + } + m.Where(p...) +} + +// Op returns the operation name. +func (m *SubscriptionCostBasisPinMutation) Op() Op { + return m.op +} + +// SetOp allows setting the mutation operation. +func (m *SubscriptionCostBasisPinMutation) SetOp(op Op) { + m.op = op +} + +// Type returns the node type of this mutation (SubscriptionCostBasisPin). +func (m *SubscriptionCostBasisPinMutation) Type() string { + return m.typ +} + +// Fields returns all fields that were changed during this mutation. Note that in +// order to get all numeric fields that were incremented/decremented, call +// AddedFields(). +func (m *SubscriptionCostBasisPinMutation) Fields() []string { + fields := make([]string, 0, 8) + if m.namespace != nil { + fields = append(fields, subscriptioncostbasispin.FieldNamespace) + } + if m.created_at != nil { + fields = append(fields, subscriptioncostbasispin.FieldCreatedAt) + } + if m.updated_at != nil { + fields = append(fields, subscriptioncostbasispin.FieldUpdatedAt) + } + if m.deleted_at != nil { + fields = append(fields, subscriptioncostbasispin.FieldDeletedAt) + } + if m.subscription != nil { + fields = append(fields, subscriptioncostbasispin.FieldSubscriptionID) + } + if m.custom_currency != nil { + fields = append(fields, subscriptioncostbasispin.FieldCustomCurrencyID) + } + if m.invoice_currency != nil { + fields = append(fields, subscriptioncostbasispin.FieldInvoiceCurrency) + } + if m.cost_basis != nil { + fields = append(fields, subscriptioncostbasispin.FieldCostBasisID) + } + return fields +} + +// Field returns the value of a field with the given name. The second boolean +// return value indicates that this field was not set, or was not defined in the +// schema. +func (m *SubscriptionCostBasisPinMutation) Field(name string) (ent.Value, bool) { + switch name { + case subscriptioncostbasispin.FieldNamespace: + return m.Namespace() + case subscriptioncostbasispin.FieldCreatedAt: + return m.CreatedAt() + case subscriptioncostbasispin.FieldUpdatedAt: + return m.UpdatedAt() + case subscriptioncostbasispin.FieldDeletedAt: + return m.DeletedAt() + case subscriptioncostbasispin.FieldSubscriptionID: + return m.SubscriptionID() + case subscriptioncostbasispin.FieldCustomCurrencyID: + return m.CustomCurrencyID() + case subscriptioncostbasispin.FieldInvoiceCurrency: + return m.InvoiceCurrency() + case subscriptioncostbasispin.FieldCostBasisID: + return m.CostBasisID() + } + return nil, false +} + +// OldField returns the old value of the field from the database. An error is +// returned if the mutation operation is not UpdateOne, or the query to the +// database failed. +func (m *SubscriptionCostBasisPinMutation) OldField(ctx context.Context, name string) (ent.Value, error) { + switch name { + case subscriptioncostbasispin.FieldNamespace: + return m.OldNamespace(ctx) + case subscriptioncostbasispin.FieldCreatedAt: + return m.OldCreatedAt(ctx) + case subscriptioncostbasispin.FieldUpdatedAt: + return m.OldUpdatedAt(ctx) + case subscriptioncostbasispin.FieldDeletedAt: + return m.OldDeletedAt(ctx) + case subscriptioncostbasispin.FieldSubscriptionID: + return m.OldSubscriptionID(ctx) + case subscriptioncostbasispin.FieldCustomCurrencyID: + return m.OldCustomCurrencyID(ctx) + case subscriptioncostbasispin.FieldInvoiceCurrency: + return m.OldInvoiceCurrency(ctx) + case subscriptioncostbasispin.FieldCostBasisID: + return m.OldCostBasisID(ctx) + } + return nil, fmt.Errorf("unknown SubscriptionCostBasisPin field %s", name) +} + +// SetField sets the value of a field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *SubscriptionCostBasisPinMutation) SetField(name string, value ent.Value) error { + switch name { + case subscriptioncostbasispin.FieldNamespace: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetNamespace(v) + return nil + case subscriptioncostbasispin.FieldCreatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCreatedAt(v) + return nil + case subscriptioncostbasispin.FieldUpdatedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetUpdatedAt(v) + return nil + case subscriptioncostbasispin.FieldDeletedAt: + v, ok := value.(time.Time) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetDeletedAt(v) + return nil + case subscriptioncostbasispin.FieldSubscriptionID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetSubscriptionID(v) + return nil + case subscriptioncostbasispin.FieldCustomCurrencyID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCustomCurrencyID(v) + return nil + case subscriptioncostbasispin.FieldInvoiceCurrency: + v, ok := value.(currencyx.Code) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetInvoiceCurrency(v) + return nil + case subscriptioncostbasispin.FieldCostBasisID: + v, ok := value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for field %s", value, name) + } + m.SetCostBasisID(v) + return nil + } + return fmt.Errorf("unknown SubscriptionCostBasisPin field %s", name) +} + +// AddedFields returns all numeric fields that were incremented/decremented during +// this mutation. +func (m *SubscriptionCostBasisPinMutation) AddedFields() []string { + return nil +} + +// AddedField returns the numeric value that was incremented/decremented on a field +// with the given name. The second boolean return value indicates that this field +// was not set, or was not defined in the schema. +func (m *SubscriptionCostBasisPinMutation) AddedField(name string) (ent.Value, bool) { + return nil, false +} + +// AddField adds the value to the field with the given name. It returns an error if +// the field is not defined in the schema, or if the type mismatched the field +// type. +func (m *SubscriptionCostBasisPinMutation) AddField(name string, value ent.Value) error { + switch name { + } + return fmt.Errorf("unknown SubscriptionCostBasisPin numeric field %s", name) +} + +// ClearedFields returns all nullable fields that were cleared during this +// mutation. +func (m *SubscriptionCostBasisPinMutation) ClearedFields() []string { + var fields []string + if m.FieldCleared(subscriptioncostbasispin.FieldDeletedAt) { + fields = append(fields, subscriptioncostbasispin.FieldDeletedAt) + } + return fields +} + +// FieldCleared returns a boolean indicating if a field with the given name was +// cleared in this mutation. +func (m *SubscriptionCostBasisPinMutation) FieldCleared(name string) bool { + _, ok := m.clearedFields[name] + return ok +} + +// ClearField clears the value of the field with the given name. It returns an +// error if the field is not defined in the schema. +func (m *SubscriptionCostBasisPinMutation) ClearField(name string) error { + switch name { + case subscriptioncostbasispin.FieldDeletedAt: + m.ClearDeletedAt() + return nil + } + return fmt.Errorf("unknown SubscriptionCostBasisPin nullable field %s", name) +} + +// ResetField resets all changes in the mutation for the field with the given name. +// It returns an error if the field is not defined in the schema. +func (m *SubscriptionCostBasisPinMutation) ResetField(name string) error { + switch name { + case subscriptioncostbasispin.FieldNamespace: + m.ResetNamespace() + return nil + case subscriptioncostbasispin.FieldCreatedAt: + m.ResetCreatedAt() + return nil + case subscriptioncostbasispin.FieldUpdatedAt: + m.ResetUpdatedAt() + return nil + case subscriptioncostbasispin.FieldDeletedAt: + m.ResetDeletedAt() + return nil + case subscriptioncostbasispin.FieldSubscriptionID: + m.ResetSubscriptionID() + return nil + case subscriptioncostbasispin.FieldCustomCurrencyID: + m.ResetCustomCurrencyID() + return nil + case subscriptioncostbasispin.FieldInvoiceCurrency: + m.ResetInvoiceCurrency() + return nil + case subscriptioncostbasispin.FieldCostBasisID: + m.ResetCostBasisID() + return nil + } + return fmt.Errorf("unknown SubscriptionCostBasisPin field %s", name) +} + +// AddedEdges returns all edge names that were set/added in this mutation. +func (m *SubscriptionCostBasisPinMutation) AddedEdges() []string { + edges := make([]string, 0, 3) + if m.subscription != nil { + edges = append(edges, subscriptioncostbasispin.EdgeSubscription) + } + if m.custom_currency != nil { + edges = append(edges, subscriptioncostbasispin.EdgeCustomCurrency) + } + if m.cost_basis != nil { + edges = append(edges, subscriptioncostbasispin.EdgeCostBasis) + } + return edges +} + +// AddedIDs returns all IDs (to other nodes) that were added for the given edge +// name in this mutation. +func (m *SubscriptionCostBasisPinMutation) AddedIDs(name string) []ent.Value { + switch name { + case subscriptioncostbasispin.EdgeSubscription: + if id := m.subscription; id != nil { + return []ent.Value{*id} + } + case subscriptioncostbasispin.EdgeCustomCurrency: + if id := m.custom_currency; id != nil { + return []ent.Value{*id} + } + case subscriptioncostbasispin.EdgeCostBasis: + if id := m.cost_basis; id != nil { + return []ent.Value{*id} + } + } + return nil +} + +// RemovedEdges returns all edge names that were removed in this mutation. +func (m *SubscriptionCostBasisPinMutation) RemovedEdges() []string { + edges := make([]string, 0, 3) + return edges +} + +// RemovedIDs returns all IDs (to other nodes) that were removed for the edge with +// the given name in this mutation. +func (m *SubscriptionCostBasisPinMutation) RemovedIDs(name string) []ent.Value { + return nil +} + +// ClearedEdges returns all edge names that were cleared in this mutation. +func (m *SubscriptionCostBasisPinMutation) ClearedEdges() []string { + edges := make([]string, 0, 3) + if m.clearedsubscription { + edges = append(edges, subscriptioncostbasispin.EdgeSubscription) + } + if m.clearedcustom_currency { + edges = append(edges, subscriptioncostbasispin.EdgeCustomCurrency) + } + if m.clearedcost_basis { + edges = append(edges, subscriptioncostbasispin.EdgeCostBasis) + } + return edges +} + +// EdgeCleared returns a boolean which indicates if the edge with the given name +// was cleared in this mutation. +func (m *SubscriptionCostBasisPinMutation) EdgeCleared(name string) bool { + switch name { + case subscriptioncostbasispin.EdgeSubscription: + return m.clearedsubscription + case subscriptioncostbasispin.EdgeCustomCurrency: + return m.clearedcustom_currency + case subscriptioncostbasispin.EdgeCostBasis: + return m.clearedcost_basis + } + return false +} + +// ClearEdge clears the value of the edge with the given name. It returns an error +// if that edge is not defined in the schema. +func (m *SubscriptionCostBasisPinMutation) ClearEdge(name string) error { + switch name { + case subscriptioncostbasispin.EdgeSubscription: + m.ClearSubscription() + return nil + case subscriptioncostbasispin.EdgeCustomCurrency: + m.ClearCustomCurrency() + return nil + case subscriptioncostbasispin.EdgeCostBasis: + m.ClearCostBasis() + return nil + } + return fmt.Errorf("unknown SubscriptionCostBasisPin unique edge %s", name) +} + +// ResetEdge resets all changes to the edge with the given name in this mutation. +// It returns an error if the edge is not defined in the schema. +func (m *SubscriptionCostBasisPinMutation) ResetEdge(name string) error { + switch name { + case subscriptioncostbasispin.EdgeSubscription: + m.ResetSubscription() + return nil + case subscriptioncostbasispin.EdgeCustomCurrency: + m.ResetCustomCurrency() + return nil + case subscriptioncostbasispin.EdgeCostBasis: + m.ResetCostBasis() + return nil + } + return fmt.Errorf("unknown SubscriptionCostBasisPin edge %s", name) +} + // SubscriptionItemMutation represents an operation that mutates the SubscriptionItem nodes in the graph. type SubscriptionItemMutation struct { config diff --git a/openmeter/ent/db/paginate.go b/openmeter/ent/db/paginate.go index 3ad0f3fd89..ef656a21ab 100644 --- a/openmeter/ent/db/paginate.go +++ b/openmeter/ent/db/paginate.go @@ -4649,6 +4649,64 @@ func (_m *SubscriptionBillingSyncStateQuery) Paginate(ctx context.Context, page // type check var _ pagination.Paginator[*SubscriptionBillingSyncState] = (*SubscriptionBillingSyncStateQuery)(nil) +// Paginate runs the query and returns a paginated response. +// If page is its 0 value then it will return all the items and populate the response page accordingly. +func (_m *SubscriptionCostBasisPinQuery) Paginate(ctx context.Context, page pagination.Page) (pagination.Result[*SubscriptionCostBasisPin], error) { + // Get the limit and offset + limit, offset := page.Limit(), page.Offset() + + // Unset previous pagination settings + zero := 0 + _m.ctx.Offset = &zero + _m.ctx.Limit = &zero + + // Create duplicate of the query to run for + countQuery := _m.Clone() + pagedQuery := _m + + // Unset select for count query + countQuery.ctx.Fields = []string{} + + // Unset ordering for count query + countQuery.order = nil + + pagedResponse := pagination.Result[*SubscriptionCostBasisPin]{ + Page: page, + } + + // Get the total count + count, err := countQuery.Count(ctx) + if err != nil { + return pagedResponse, fmt.Errorf("failed to get count: %w", err) + } + pagedResponse.TotalCount = count + + // If there are no items, return the empty response early + if count == 0 { + // Items should be [] not null. + pagedResponse.Items = make([]*SubscriptionCostBasisPin, 0) + return pagedResponse, nil + } + + // If page is its 0 value then return all the items + if page.IsZero() { + offset = 0 + limit = count + } + + // Set the limit and offset + pagedQuery.ctx.Limit = &limit + pagedQuery.ctx.Offset = &offset + + // Get the paged items + items, err := pagedQuery.All(ctx) + pagedResponse.Items = items + return pagedResponse, err +} + +// type check +var _ pagination.Paginator[*SubscriptionCostBasisPin] = (*SubscriptionCostBasisPinQuery)(nil) + // Paginate runs the query and returns a paginated response. // If page is its 0 value then it will return all the items and populate the response page accordingly. func (_m *SubscriptionItemQuery) Paginate(ctx context.Context, page pagination.Page) (pagination.Result[*SubscriptionItem], error) { diff --git a/openmeter/ent/db/predicate/predicate.go b/openmeter/ent/db/predicate/predicate.go index 0b3d65d810..981eabc34a 100644 --- a/openmeter/ent/db/predicate/predicate.go +++ b/openmeter/ent/db/predicate/predicate.go @@ -477,6 +477,9 @@ type SubscriptionAddonQuantity func(*sql.Selector) // SubscriptionBillingSyncState is the predicate function for subscriptionbillingsyncstate builders. type SubscriptionBillingSyncState func(*sql.Selector) +// SubscriptionCostBasisPin is the predicate function for subscriptioncostbasispin builders. +type SubscriptionCostBasisPin func(*sql.Selector) + // SubscriptionItem is the predicate function for subscriptionitem builders. type SubscriptionItem func(*sql.Selector) diff --git a/openmeter/ent/db/runtime.go b/openmeter/ent/db/runtime.go index 3a4307074e..b36197caa6 100644 --- a/openmeter/ent/db/runtime.go +++ b/openmeter/ent/db/runtime.go @@ -89,6 +89,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddonquantity" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionbillingsyncstate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionitem" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionphase" dbtaxcode "github.com/openmeterio/openmeter/openmeter/ent/db/taxcode" @@ -2846,19 +2847,19 @@ func init() { subscriptionDescCustomerID := subscriptionFields[3].Descriptor() // subscription.CustomerIDValidator is a validator for the "customer_id" field. It is called by the builders before save. subscription.CustomerIDValidator = subscriptionDescCustomerID.Validators[0].(func(string) error) - // subscriptionDescCurrency is the schema descriptor for currency field. - subscriptionDescCurrency := subscriptionFields[4].Descriptor() - // subscription.CurrencyValidator is a validator for the "currency" field. It is called by the builders before save. - subscription.CurrencyValidator = func() func(string) error { - validators := subscriptionDescCurrency.Validators + // subscriptionDescInvoiceCurrency is the schema descriptor for invoice_currency field. + subscriptionDescInvoiceCurrency := subscriptionFields[4].Descriptor() + // subscription.InvoiceCurrencyValidator is a validator for the "invoice_currency" field. It is called by the builders before save. + subscription.InvoiceCurrencyValidator = func() func(string) error { + validators := subscriptionDescInvoiceCurrency.Validators fns := [...]func(string) error{ validators[0].(func(string) error), validators[1].(func(string) error), validators[2].(func(string) error), } - return func(currency string) error { + return func(invoice_currency string) error { for _, fn := range fns { - if err := fn(currency); err != nil { + if err := fn(invoice_currency); err != nil { return err } } @@ -2866,7 +2867,7 @@ func init() { } }() // subscriptionDescProRatingConfig is the schema descriptor for pro_rating_config field. - subscriptionDescProRatingConfig := subscriptionFields[7].Descriptor() + subscriptionDescProRatingConfig := subscriptionFields[8].Descriptor() // subscription.DefaultProRatingConfig holds the default value on creation for the pro_rating_config field. subscription.DefaultProRatingConfig = subscriptionDescProRatingConfig.Default.(func() productcatalog.ProRatingConfig) subscription.ValueScanner.ProRatingConfig = subscriptionDescProRatingConfig.ValueScanner.(field.TypeValueScanner[productcatalog.ProRatingConfig]) @@ -2969,6 +2970,64 @@ func init() { subscriptionbillingsyncstateDescID := subscriptionbillingsyncstateMixinFields0[0].Descriptor() // subscriptionbillingsyncstate.DefaultID holds the default value on creation for the id field. subscriptionbillingsyncstate.DefaultID = subscriptionbillingsyncstateDescID.Default.(func() string) + subscriptioncostbasispinMixin := schema.SubscriptionCostBasisPin{}.Mixin() + subscriptioncostbasispinMixinFields0 := subscriptioncostbasispinMixin[0].Fields() + _ = subscriptioncostbasispinMixinFields0 + subscriptioncostbasispinMixinFields1 := subscriptioncostbasispinMixin[1].Fields() + _ = subscriptioncostbasispinMixinFields1 + subscriptioncostbasispinMixinFields2 := subscriptioncostbasispinMixin[2].Fields() + _ = subscriptioncostbasispinMixinFields2 + subscriptioncostbasispinFields := schema.SubscriptionCostBasisPin{}.Fields() + _ = subscriptioncostbasispinFields + // subscriptioncostbasispinDescNamespace is the schema descriptor for namespace field. + subscriptioncostbasispinDescNamespace := subscriptioncostbasispinMixinFields1[0].Descriptor() + // subscriptioncostbasispin.NamespaceValidator is a validator for the "namespace" field. It is called by the builders before save. + subscriptioncostbasispin.NamespaceValidator = subscriptioncostbasispinDescNamespace.Validators[0].(func(string) error) + // subscriptioncostbasispinDescCreatedAt is the schema descriptor for created_at field. + subscriptioncostbasispinDescCreatedAt := subscriptioncostbasispinMixinFields2[0].Descriptor() + // subscriptioncostbasispin.DefaultCreatedAt holds the default value on creation for the created_at field. + subscriptioncostbasispin.DefaultCreatedAt = subscriptioncostbasispinDescCreatedAt.Default.(func() time.Time) + // subscriptioncostbasispinDescUpdatedAt is the schema descriptor for updated_at field. + subscriptioncostbasispinDescUpdatedAt := subscriptioncostbasispinMixinFields2[1].Descriptor() + // subscriptioncostbasispin.DefaultUpdatedAt holds the default value on creation for the updated_at field. + subscriptioncostbasispin.DefaultUpdatedAt = subscriptioncostbasispinDescUpdatedAt.Default.(func() time.Time) + // subscriptioncostbasispin.UpdateDefaultUpdatedAt holds the default value on update for the updated_at field. + subscriptioncostbasispin.UpdateDefaultUpdatedAt = subscriptioncostbasispinDescUpdatedAt.UpdateDefault.(func() time.Time) + // subscriptioncostbasispinDescSubscriptionID is the schema descriptor for subscription_id field. + subscriptioncostbasispinDescSubscriptionID := subscriptioncostbasispinFields[0].Descriptor() + // subscriptioncostbasispin.SubscriptionIDValidator is a validator for the "subscription_id" field. It is called by the builders before save. + subscriptioncostbasispin.SubscriptionIDValidator = subscriptioncostbasispinDescSubscriptionID.Validators[0].(func(string) error) + // subscriptioncostbasispinDescCustomCurrencyID is the schema descriptor for custom_currency_id field. + subscriptioncostbasispinDescCustomCurrencyID := subscriptioncostbasispinFields[1].Descriptor() + // subscriptioncostbasispin.CustomCurrencyIDValidator is a validator for the "custom_currency_id" field. It is called by the builders before save. + subscriptioncostbasispin.CustomCurrencyIDValidator = subscriptioncostbasispinDescCustomCurrencyID.Validators[0].(func(string) error) + // subscriptioncostbasispinDescInvoiceCurrency is the schema descriptor for invoice_currency field. + subscriptioncostbasispinDescInvoiceCurrency := subscriptioncostbasispinFields[2].Descriptor() + // subscriptioncostbasispin.InvoiceCurrencyValidator is a validator for the "invoice_currency" field. It is called by the builders before save. + subscriptioncostbasispin.InvoiceCurrencyValidator = func() func(string) error { + validators := subscriptioncostbasispinDescInvoiceCurrency.Validators + fns := [...]func(string) error{ + validators[0].(func(string) error), + validators[1].(func(string) error), + validators[2].(func(string) error), + } + return func(invoice_currency string) error { + for _, fn := range fns { + if err := fn(invoice_currency); err != nil { + return err + } + } + return nil + } + }() + // subscriptioncostbasispinDescCostBasisID is the schema descriptor for cost_basis_id field. + subscriptioncostbasispinDescCostBasisID := subscriptioncostbasispinFields[3].Descriptor() + // subscriptioncostbasispin.CostBasisIDValidator is a validator for the "cost_basis_id" field. It is called by the builders before save. + subscriptioncostbasispin.CostBasisIDValidator = subscriptioncostbasispinDescCostBasisID.Validators[0].(func(string) error) + // subscriptioncostbasispinDescID is the schema descriptor for id field. + subscriptioncostbasispinDescID := subscriptioncostbasispinMixinFields0[0].Descriptor() + // subscriptioncostbasispin.DefaultID holds the default value on creation for the id field. + subscriptioncostbasispin.DefaultID = subscriptioncostbasispinDescID.Default.(func() string) subscriptionitemMixin := schema.SubscriptionItem{}.Mixin() subscriptionitemMixinFields0 := subscriptionitemMixin[0].Fields() _ = subscriptionitemMixinFields0 diff --git a/openmeter/ent/db/setorclear.go b/openmeter/ent/db/setorclear.go index c5afd2a01c..89357b5514 100644 --- a/openmeter/ent/db/setorclear.go +++ b/openmeter/ent/db/setorclear.go @@ -5731,6 +5731,20 @@ func (u *SubscriptionBillingSyncStateUpdateOne) SetOrClearNextSyncAfter(value *t return u.SetNextSyncAfter(*value) } +func (u *SubscriptionCostBasisPinUpdate) SetOrClearDeletedAt(value *time.Time) *SubscriptionCostBasisPinUpdate { + if value == nil { + return u.ClearDeletedAt() + } + return u.SetDeletedAt(*value) +} + +func (u *SubscriptionCostBasisPinUpdateOne) SetOrClearDeletedAt(value *time.Time) *SubscriptionCostBasisPinUpdateOne { + if value == nil { + return u.ClearDeletedAt() + } + return u.SetDeletedAt(*value) +} + func (u *SubscriptionItemUpdate) SetOrClearDeletedAt(value *time.Time) *SubscriptionItemUpdate { if value == nil { return u.ClearDeletedAt() diff --git a/openmeter/ent/db/subscription.go b/openmeter/ent/db/subscription.go index b142828bca..b2bbe51e78 100644 --- a/openmeter/ent/db/subscription.go +++ b/openmeter/ent/db/subscription.go @@ -49,8 +49,10 @@ type Subscription struct { PlanID *string `json:"plan_id,omitempty"` // CustomerID holds the value of the "customer_id" field. CustomerID string `json:"customer_id,omitempty"` - // Currency holds the value of the "currency" field. - Currency currencyx.Code `json:"currency,omitempty"` + // InvoiceCurrency holds the value of the "invoice_currency" field. + InvoiceCurrency currencyx.Code `json:"invoice_currency,omitempty"` + // CostBasisMode holds the value of the "cost_basis_mode" field. + CostBasisMode subscription.CostBasisMode `json:"cost_basis_mode,omitempty"` // BillingAnchor holds the value of the "billing_anchor" field. BillingAnchor time.Time `json:"billing_anchor,omitempty"` // The default billing cadence for subscriptions. @@ -89,9 +91,11 @@ type SubscriptionEdges struct { Addons []*SubscriptionAddon `json:"addons,omitempty"` // BillingSyncState holds the value of the billing_sync_state edge. BillingSyncState *SubscriptionBillingSyncState `json:"billing_sync_state,omitempty"` + // CostBasisPins holds the value of the cost_basis_pins edge. + CostBasisPins []*SubscriptionCostBasisPin `json:"cost_basis_pins,omitempty"` // loadedTypes holds the information for reporting if a // type was loaded (or requested) in eager-loading or not. - loadedTypes [11]bool + loadedTypes [12]bool } // PlanOrErr returns the Plan value or an error if the edge @@ -199,6 +203,15 @@ func (e SubscriptionEdges) BillingSyncStateOrErr() (*SubscriptionBillingSyncStat return nil, &NotLoadedError{edge: "billing_sync_state"} } +// CostBasisPinsOrErr returns the CostBasisPins value or an error if the edge +// was not loaded in eager-loading. +func (e SubscriptionEdges) CostBasisPinsOrErr() ([]*SubscriptionCostBasisPin, error) { + if e.loadedTypes[11] { + return e.CostBasisPins, nil + } + return nil, &NotLoadedError{edge: "cost_basis_pins"} +} + // scanValues returns the types for scanning values from sql.Rows. func (*Subscription) scanValues(columns []string) ([]any, error) { values := make([]any, len(columns)) @@ -206,7 +219,7 @@ func (*Subscription) scanValues(columns []string) ([]any, error) { switch columns[i] { case subscription.FieldAnnotations, subscription.FieldMetadata: values[i] = new([]byte) - case subscription.FieldID, subscription.FieldNamespace, subscription.FieldName, subscription.FieldDescription, subscription.FieldPlanID, subscription.FieldCustomerID, subscription.FieldCurrency, subscription.FieldBillingCadence, subscription.FieldSettlementMode: + case subscription.FieldID, subscription.FieldNamespace, subscription.FieldName, subscription.FieldDescription, subscription.FieldPlanID, subscription.FieldCustomerID, subscription.FieldInvoiceCurrency, subscription.FieldCostBasisMode, subscription.FieldBillingCadence, subscription.FieldSettlementMode: values[i] = new(sql.NullString) case subscription.FieldCreatedAt, subscription.FieldUpdatedAt, subscription.FieldDeletedAt, subscription.FieldActiveFrom, subscription.FieldActiveTo, subscription.FieldBillingAnchor: values[i] = new(sql.NullTime) @@ -313,11 +326,17 @@ func (_m *Subscription) assignValues(columns []string, values []any) error { } else if value.Valid { _m.CustomerID = value.String } - case subscription.FieldCurrency: + case subscription.FieldInvoiceCurrency: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field invoice_currency", values[i]) + } else if value.Valid { + _m.InvoiceCurrency = currencyx.Code(value.String) + } + case subscription.FieldCostBasisMode: if value, ok := values[i].(*sql.NullString); !ok { - return fmt.Errorf("unexpected type %T for field currency", values[i]) + return fmt.Errorf("unexpected type %T for field cost_basis_mode", values[i]) } else if value.Valid { - _m.Currency = currencyx.Code(value.String) + _m.CostBasisMode = subscription.CostBasisMode(value.String) } case subscription.FieldBillingAnchor: if value, ok := values[i].(*sql.NullTime); !ok { @@ -411,6 +430,11 @@ func (_m *Subscription) QueryBillingSyncState() *SubscriptionBillingSyncStateQue return NewSubscriptionClient(_m.config).QueryBillingSyncState(_m) } +// QueryCostBasisPins queries the "cost_basis_pins" edge of the Subscription entity. +func (_m *Subscription) QueryCostBasisPins() *SubscriptionCostBasisPinQuery { + return NewSubscriptionClient(_m.config).QueryCostBasisPins(_m) +} + // Update returns a builder for updating this Subscription. // Note that you need to call Subscription.Unwrap() before calling this method if this Subscription // was returned from a transaction, and the transaction was committed or rolled back. @@ -478,8 +502,11 @@ func (_m *Subscription) String() string { builder.WriteString("customer_id=") builder.WriteString(_m.CustomerID) builder.WriteString(", ") - builder.WriteString("currency=") - builder.WriteString(fmt.Sprintf("%v", _m.Currency)) + builder.WriteString("invoice_currency=") + builder.WriteString(fmt.Sprintf("%v", _m.InvoiceCurrency)) + builder.WriteString(", ") + builder.WriteString("cost_basis_mode=") + builder.WriteString(fmt.Sprintf("%v", _m.CostBasisMode)) builder.WriteString(", ") builder.WriteString("billing_anchor=") builder.WriteString(_m.BillingAnchor.Format(time.ANSIC)) diff --git a/openmeter/ent/db/subscription/subscription.go b/openmeter/ent/db/subscription/subscription.go index 5562384e5e..e1e976e509 100644 --- a/openmeter/ent/db/subscription/subscription.go +++ b/openmeter/ent/db/subscription/subscription.go @@ -41,8 +41,10 @@ const ( FieldPlanID = "plan_id" // FieldCustomerID holds the string denoting the customer_id field in the database. FieldCustomerID = "customer_id" - // FieldCurrency holds the string denoting the currency field in the database. - FieldCurrency = "currency" + // FieldInvoiceCurrency holds the string denoting the invoice_currency field in the database. + FieldInvoiceCurrency = "currency" + // FieldCostBasisMode holds the string denoting the cost_basis_mode field in the database. + FieldCostBasisMode = "cost_basis_mode" // FieldBillingAnchor holds the string denoting the billing_anchor field in the database. FieldBillingAnchor = "billing_anchor" // FieldBillingCadence holds the string denoting the billing_cadence field in the database. @@ -73,6 +75,8 @@ const ( EdgeAddons = "addons" // EdgeBillingSyncState holds the string denoting the billing_sync_state edge name in mutations. EdgeBillingSyncState = "billing_sync_state" + // EdgeCostBasisPins holds the string denoting the cost_basis_pins edge name in mutations. + EdgeCostBasisPins = "cost_basis_pins" // Table holds the table name of the subscription in the database. Table = "subscriptions" // PlanTable is the table that holds the plan relation/edge. @@ -152,6 +156,13 @@ const ( BillingSyncStateInverseTable = "subscription_billing_sync_states" // BillingSyncStateColumn is the table column denoting the billing_sync_state relation/edge. BillingSyncStateColumn = "subscription_id" + // CostBasisPinsTable is the table that holds the cost_basis_pins relation/edge. + CostBasisPinsTable = "subscription_cost_basis_pins" + // CostBasisPinsInverseTable is the table name for the SubscriptionCostBasisPin entity. + // It exists in this package in order to avoid circular dependency with the "subscriptioncostbasispin" package. + CostBasisPinsInverseTable = "subscription_cost_basis_pins" + // CostBasisPinsColumn is the table column denoting the cost_basis_pins relation/edge. + CostBasisPinsColumn = "subscription_id" ) // Columns holds all SQL columns for subscription fields. @@ -169,7 +180,8 @@ var Columns = []string{ FieldDescription, FieldPlanID, FieldCustomerID, - FieldCurrency, + FieldInvoiceCurrency, + FieldCostBasisMode, FieldBillingAnchor, FieldBillingCadence, FieldProRatingConfig, @@ -201,8 +213,8 @@ var ( NameValidator func(string) error // CustomerIDValidator is a validator for the "customer_id" field. It is called by the builders before save. CustomerIDValidator func(string) error - // CurrencyValidator is a validator for the "currency" field. It is called by the builders before save. - CurrencyValidator func(string) error + // InvoiceCurrencyValidator is a validator for the "invoice_currency" field. It is called by the builders before save. + InvoiceCurrencyValidator func(string) error // DefaultProRatingConfig holds the default value on creation for the "pro_rating_config" field. DefaultProRatingConfig func() productcatalog.ProRatingConfig // DefaultID holds the default value on creation for the "id" field. @@ -213,6 +225,32 @@ var ( } ) +// CostBasisMode defines the type for the "cost_basis_mode" enum field. +type CostBasisMode string + +// CostBasisModeDynamic is the default value of the CostBasisMode enum. +const DefaultCostBasisMode = CostBasisModeDynamic + +// CostBasisMode values. +const ( + CostBasisModeDynamic CostBasisMode = "dynamic" + CostBasisModePinned CostBasisMode = "pinned" +) + +func (cbm CostBasisMode) String() string { + return string(cbm) +} + +// CostBasisModeValidator is a validator for the "cost_basis_mode" field enum values. It is called by the builders before save. +func CostBasisModeValidator(cbm CostBasisMode) error { + switch cbm { + case CostBasisModeDynamic, CostBasisModePinned: + return nil + default: + return fmt.Errorf("subscription: invalid enum value for cost_basis_mode field: %q", cbm) + } +} + const DefaultSettlementMode productcatalog.SettlementMode = "credit_then_invoice" // SettlementModeValidator is a validator for the "settlement_mode" field enum values. It is called by the builders before save. @@ -283,9 +321,14 @@ func ByCustomerID(opts ...sql.OrderTermOption) OrderOption { return sql.OrderByField(FieldCustomerID, opts...).ToFunc() } -// ByCurrency orders the results by the currency field. -func ByCurrency(opts ...sql.OrderTermOption) OrderOption { - return sql.OrderByField(FieldCurrency, opts...).ToFunc() +// ByInvoiceCurrency orders the results by the invoice_currency field. +func ByInvoiceCurrency(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldInvoiceCurrency, opts...).ToFunc() +} + +// ByCostBasisMode orders the results by the cost_basis_mode field. +func ByCostBasisMode(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCostBasisMode, opts...).ToFunc() } // ByBillingAnchor orders the results by the billing_anchor field. @@ -440,6 +483,20 @@ func ByBillingSyncStateField(field string, opts ...sql.OrderTermOption) OrderOpt sqlgraph.OrderByNeighborTerms(s, newBillingSyncStateStep(), sql.OrderByField(field, opts...)) } } + +// ByCostBasisPinsCount orders the results by cost_basis_pins count. +func ByCostBasisPinsCount(opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborsCount(s, newCostBasisPinsStep(), opts...) + } +} + +// ByCostBasisPins orders the results by cost_basis_pins terms. +func ByCostBasisPins(term sql.OrderTerm, terms ...sql.OrderTerm) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newCostBasisPinsStep(), append([]sql.OrderTerm{term}, terms...)...) + } +} func newPlanStep() *sqlgraph.Step { return sqlgraph.NewStep( sqlgraph.From(Table, FieldID), @@ -517,3 +574,10 @@ func newBillingSyncStateStep() *sqlgraph.Step { sqlgraph.Edge(sqlgraph.O2O, false, BillingSyncStateTable, BillingSyncStateColumn), ) } +func newCostBasisPinsStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(CostBasisPinsInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, CostBasisPinsTable, CostBasisPinsColumn), + ) +} diff --git a/openmeter/ent/db/subscription/where.go b/openmeter/ent/db/subscription/where.go index 8179101bc6..692710c656 100644 --- a/openmeter/ent/db/subscription/where.go +++ b/openmeter/ent/db/subscription/where.go @@ -118,10 +118,10 @@ func CustomerID(v string) predicate.Subscription { return predicate.Subscription(sql.FieldEQ(FieldCustomerID, v)) } -// Currency applies equality check predicate on the "currency" field. It's identical to CurrencyEQ. -func Currency(v currencyx.Code) predicate.Subscription { +// InvoiceCurrency applies equality check predicate on the "invoice_currency" field. It's identical to InvoiceCurrencyEQ. +func InvoiceCurrency(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldEQ(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldEQ(FieldInvoiceCurrency, vc)) } // BillingAnchor applies equality check predicate on the "billing_anchor" field. It's identical to BillingAnchorEQ. @@ -720,88 +720,108 @@ func CustomerIDContainsFold(v string) predicate.Subscription { return predicate.Subscription(sql.FieldContainsFold(FieldCustomerID, v)) } -// CurrencyEQ applies the EQ predicate on the "currency" field. -func CurrencyEQ(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyEQ applies the EQ predicate on the "invoice_currency" field. +func InvoiceCurrencyEQ(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldEQ(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldEQ(FieldInvoiceCurrency, vc)) } -// CurrencyNEQ applies the NEQ predicate on the "currency" field. -func CurrencyNEQ(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyNEQ applies the NEQ predicate on the "invoice_currency" field. +func InvoiceCurrencyNEQ(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldNEQ(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldNEQ(FieldInvoiceCurrency, vc)) } -// CurrencyIn applies the In predicate on the "currency" field. -func CurrencyIn(vs ...currencyx.Code) predicate.Subscription { +// InvoiceCurrencyIn applies the In predicate on the "invoice_currency" field. +func InvoiceCurrencyIn(vs ...currencyx.Code) predicate.Subscription { v := make([]any, len(vs)) for i := range v { v[i] = string(vs[i]) } - return predicate.Subscription(sql.FieldIn(FieldCurrency, v...)) + return predicate.Subscription(sql.FieldIn(FieldInvoiceCurrency, v...)) } -// CurrencyNotIn applies the NotIn predicate on the "currency" field. -func CurrencyNotIn(vs ...currencyx.Code) predicate.Subscription { +// InvoiceCurrencyNotIn applies the NotIn predicate on the "invoice_currency" field. +func InvoiceCurrencyNotIn(vs ...currencyx.Code) predicate.Subscription { v := make([]any, len(vs)) for i := range v { v[i] = string(vs[i]) } - return predicate.Subscription(sql.FieldNotIn(FieldCurrency, v...)) + return predicate.Subscription(sql.FieldNotIn(FieldInvoiceCurrency, v...)) } -// CurrencyGT applies the GT predicate on the "currency" field. -func CurrencyGT(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyGT applies the GT predicate on the "invoice_currency" field. +func InvoiceCurrencyGT(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldGT(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldGT(FieldInvoiceCurrency, vc)) } -// CurrencyGTE applies the GTE predicate on the "currency" field. -func CurrencyGTE(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyGTE applies the GTE predicate on the "invoice_currency" field. +func InvoiceCurrencyGTE(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldGTE(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldGTE(FieldInvoiceCurrency, vc)) } -// CurrencyLT applies the LT predicate on the "currency" field. -func CurrencyLT(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyLT applies the LT predicate on the "invoice_currency" field. +func InvoiceCurrencyLT(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldLT(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldLT(FieldInvoiceCurrency, vc)) } -// CurrencyLTE applies the LTE predicate on the "currency" field. -func CurrencyLTE(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyLTE applies the LTE predicate on the "invoice_currency" field. +func InvoiceCurrencyLTE(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldLTE(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldLTE(FieldInvoiceCurrency, vc)) } -// CurrencyContains applies the Contains predicate on the "currency" field. -func CurrencyContains(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyContains applies the Contains predicate on the "invoice_currency" field. +func InvoiceCurrencyContains(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldContains(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldContains(FieldInvoiceCurrency, vc)) } -// CurrencyHasPrefix applies the HasPrefix predicate on the "currency" field. -func CurrencyHasPrefix(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyHasPrefix applies the HasPrefix predicate on the "invoice_currency" field. +func InvoiceCurrencyHasPrefix(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldHasPrefix(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldHasPrefix(FieldInvoiceCurrency, vc)) } -// CurrencyHasSuffix applies the HasSuffix predicate on the "currency" field. -func CurrencyHasSuffix(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyHasSuffix applies the HasSuffix predicate on the "invoice_currency" field. +func InvoiceCurrencyHasSuffix(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldHasSuffix(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldHasSuffix(FieldInvoiceCurrency, vc)) } -// CurrencyEqualFold applies the EqualFold predicate on the "currency" field. -func CurrencyEqualFold(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyEqualFold applies the EqualFold predicate on the "invoice_currency" field. +func InvoiceCurrencyEqualFold(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldEqualFold(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldEqualFold(FieldInvoiceCurrency, vc)) } -// CurrencyContainsFold applies the ContainsFold predicate on the "currency" field. -func CurrencyContainsFold(v currencyx.Code) predicate.Subscription { +// InvoiceCurrencyContainsFold applies the ContainsFold predicate on the "invoice_currency" field. +func InvoiceCurrencyContainsFold(v currencyx.Code) predicate.Subscription { vc := string(v) - return predicate.Subscription(sql.FieldContainsFold(FieldCurrency, vc)) + return predicate.Subscription(sql.FieldContainsFold(FieldInvoiceCurrency, vc)) +} + +// CostBasisModeEQ applies the EQ predicate on the "cost_basis_mode" field. +func CostBasisModeEQ(v CostBasisMode) predicate.Subscription { + return predicate.Subscription(sql.FieldEQ(FieldCostBasisMode, v)) +} + +// CostBasisModeNEQ applies the NEQ predicate on the "cost_basis_mode" field. +func CostBasisModeNEQ(v CostBasisMode) predicate.Subscription { + return predicate.Subscription(sql.FieldNEQ(FieldCostBasisMode, v)) +} + +// CostBasisModeIn applies the In predicate on the "cost_basis_mode" field. +func CostBasisModeIn(vs ...CostBasisMode) predicate.Subscription { + return predicate.Subscription(sql.FieldIn(FieldCostBasisMode, vs...)) +} + +// CostBasisModeNotIn applies the NotIn predicate on the "cost_basis_mode" field. +func CostBasisModeNotIn(vs ...CostBasisMode) predicate.Subscription { + return predicate.Subscription(sql.FieldNotIn(FieldCostBasisMode, vs...)) } // BillingAnchorEQ applies the EQ predicate on the "billing_anchor" field. @@ -1211,6 +1231,29 @@ func HasBillingSyncStateWith(preds ...predicate.SubscriptionBillingSyncState) pr }) } +// HasCostBasisPins applies the HasEdge predicate on the "cost_basis_pins" edge. +func HasCostBasisPins() predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, CostBasisPinsTable, CostBasisPinsColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasCostBasisPinsWith applies the HasEdge predicate on the "cost_basis_pins" edge with a given conditions (other predicates). +func HasCostBasisPinsWith(preds ...predicate.SubscriptionCostBasisPin) predicate.Subscription { + return predicate.Subscription(func(s *sql.Selector) { + step := newCostBasisPinsStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + // And groups predicates with the AND operator between them. func And(predicates ...predicate.Subscription) predicate.Subscription { return predicate.Subscription(sql.AndPredicates(predicates...)) diff --git a/openmeter/ent/db/subscription_create.go b/openmeter/ent/db/subscription_create.go index 85a69dac66..98f98bf70f 100644 --- a/openmeter/ent/db/subscription_create.go +++ b/openmeter/ent/db/subscription_create.go @@ -23,6 +23,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/subscription" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionbillingsyncstate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionphase" "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/pkg/currencyx" @@ -166,9 +167,23 @@ func (_c *SubscriptionCreate) SetCustomerID(v string) *SubscriptionCreate { return _c } -// SetCurrency sets the "currency" field. -func (_c *SubscriptionCreate) SetCurrency(v currencyx.Code) *SubscriptionCreate { - _c.mutation.SetCurrency(v) +// SetInvoiceCurrency sets the "invoice_currency" field. +func (_c *SubscriptionCreate) SetInvoiceCurrency(v currencyx.Code) *SubscriptionCreate { + _c.mutation.SetInvoiceCurrency(v) + return _c +} + +// SetCostBasisMode sets the "cost_basis_mode" field. +func (_c *SubscriptionCreate) SetCostBasisMode(v subscription.CostBasisMode) *SubscriptionCreate { + _c.mutation.SetCostBasisMode(v) + return _c +} + +// SetNillableCostBasisMode sets the "cost_basis_mode" field if the given value is not nil. +func (_c *SubscriptionCreate) SetNillableCostBasisMode(v *subscription.CostBasisMode) *SubscriptionCreate { + if v != nil { + _c.SetCostBasisMode(*v) + } return _c } @@ -375,6 +390,21 @@ func (_c *SubscriptionCreate) SetBillingSyncState(v *SubscriptionBillingSyncStat return _c.SetBillingSyncStateID(v.ID) } +// AddCostBasisPinIDs adds the "cost_basis_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_c *SubscriptionCreate) AddCostBasisPinIDs(ids ...string) *SubscriptionCreate { + _c.mutation.AddCostBasisPinIDs(ids...) + return _c +} + +// AddCostBasisPins adds the "cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_c *SubscriptionCreate) AddCostBasisPins(v ...*SubscriptionCostBasisPin) *SubscriptionCreate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _c.AddCostBasisPinIDs(ids...) +} + // Mutation returns the SubscriptionMutation object of the builder. func (_c *SubscriptionCreate) Mutation() *SubscriptionMutation { return _c.mutation @@ -422,6 +452,10 @@ func (_c *SubscriptionCreate) defaults() { v := subscription.DefaultName _c.mutation.SetName(v) } + if _, ok := _c.mutation.CostBasisMode(); !ok { + v := subscription.DefaultCostBasisMode + _c.mutation.SetCostBasisMode(v) + } if _, ok := _c.mutation.ProRatingConfig(); !ok { v := subscription.DefaultProRatingConfig() _c.mutation.SetProRatingConfig(v) @@ -471,12 +505,20 @@ func (_c *SubscriptionCreate) check() error { return &ValidationError{Name: "customer_id", err: fmt.Errorf(`db: validator failed for field "Subscription.customer_id": %w`, err)} } } - if _, ok := _c.mutation.Currency(); !ok { - return &ValidationError{Name: "currency", err: errors.New(`db: missing required field "Subscription.currency"`)} + if _, ok := _c.mutation.InvoiceCurrency(); !ok { + return &ValidationError{Name: "invoice_currency", err: errors.New(`db: missing required field "Subscription.invoice_currency"`)} } - if v, ok := _c.mutation.Currency(); ok { - if err := subscription.CurrencyValidator(string(v)); err != nil { - return &ValidationError{Name: "currency", err: fmt.Errorf(`db: validator failed for field "Subscription.currency": %w`, err)} + if v, ok := _c.mutation.InvoiceCurrency(); ok { + if err := subscription.InvoiceCurrencyValidator(string(v)); err != nil { + return &ValidationError{Name: "invoice_currency", err: fmt.Errorf(`db: validator failed for field "Subscription.invoice_currency": %w`, err)} + } + } + if _, ok := _c.mutation.CostBasisMode(); !ok { + return &ValidationError{Name: "cost_basis_mode", err: errors.New(`db: missing required field "Subscription.cost_basis_mode"`)} + } + if v, ok := _c.mutation.CostBasisMode(); ok { + if err := subscription.CostBasisModeValidator(v); err != nil { + return &ValidationError{Name: "cost_basis_mode", err: fmt.Errorf(`db: validator failed for field "Subscription.cost_basis_mode": %w`, err)} } } if _, ok := _c.mutation.BillingAnchor(); !ok { @@ -583,9 +625,13 @@ func (_c *SubscriptionCreate) createSpec() (*Subscription, *sqlgraph.CreateSpec, _spec.SetField(subscription.FieldDescription, field.TypeString, value) _node.Description = &value } - if value, ok := _c.mutation.Currency(); ok { - _spec.SetField(subscription.FieldCurrency, field.TypeString, value) - _node.Currency = value + if value, ok := _c.mutation.InvoiceCurrency(); ok { + _spec.SetField(subscription.FieldInvoiceCurrency, field.TypeString, value) + _node.InvoiceCurrency = value + } + if value, ok := _c.mutation.CostBasisMode(); ok { + _spec.SetField(subscription.FieldCostBasisMode, field.TypeEnum, value) + _node.CostBasisMode = value } if value, ok := _c.mutation.BillingAnchor(); ok { _spec.SetField(subscription.FieldBillingAnchor, field.TypeTime, value) @@ -785,6 +831,22 @@ func (_c *SubscriptionCreate) createSpec() (*Subscription, *sqlgraph.CreateSpec, } _spec.Edges = append(_spec.Edges, edge) } + if nodes := _c.mutation.CostBasisPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: subscription.CostBasisPinsTable, + Columns: []string{subscription.CostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges = append(_spec.Edges, edge) + } return _node, _spec, nil } @@ -1034,8 +1096,11 @@ func (u *SubscriptionUpsertOne) UpdateNewValues() *SubscriptionUpsertOne { if _, exists := u.create.mutation.CustomerID(); exists { s.SetIgnore(subscription.FieldCustomerID) } - if _, exists := u.create.mutation.Currency(); exists { - s.SetIgnore(subscription.FieldCurrency) + if _, exists := u.create.mutation.InvoiceCurrency(); exists { + s.SetIgnore(subscription.FieldInvoiceCurrency) + } + if _, exists := u.create.mutation.CostBasisMode(); exists { + s.SetIgnore(subscription.FieldCostBasisMode) } if _, exists := u.create.mutation.SettlementMode(); exists { s.SetIgnore(subscription.FieldSettlementMode) @@ -1465,8 +1530,11 @@ func (u *SubscriptionUpsertBulk) UpdateNewValues() *SubscriptionUpsertBulk { if _, exists := b.mutation.CustomerID(); exists { s.SetIgnore(subscription.FieldCustomerID) } - if _, exists := b.mutation.Currency(); exists { - s.SetIgnore(subscription.FieldCurrency) + if _, exists := b.mutation.InvoiceCurrency(); exists { + s.SetIgnore(subscription.FieldInvoiceCurrency) + } + if _, exists := b.mutation.CostBasisMode(); exists { + s.SetIgnore(subscription.FieldCostBasisMode) } if _, exists := b.mutation.SettlementMode(); exists { s.SetIgnore(subscription.FieldSettlementMode) diff --git a/openmeter/ent/db/subscription_query.go b/openmeter/ent/db/subscription_query.go index 46c70c71ab..d662fc9d7d 100644 --- a/openmeter/ent/db/subscription_query.go +++ b/openmeter/ent/db/subscription_query.go @@ -25,6 +25,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/subscription" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionbillingsyncstate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionphase" ) @@ -46,6 +47,7 @@ type SubscriptionQuery struct { withChargesFlatFee *ChargeFlatFeeQuery withAddons *SubscriptionAddonQuery withBillingSyncState *SubscriptionBillingSyncStateQuery + withCostBasisPins *SubscriptionCostBasisPinQuery modifiers []func(*sql.Selector) // intermediate query (i.e. traversal path). sql *sql.Selector @@ -325,6 +327,28 @@ func (_q *SubscriptionQuery) QueryBillingSyncState() *SubscriptionBillingSyncSta return query } +// QueryCostBasisPins chains the current query on the "cost_basis_pins" edge. +func (_q *SubscriptionQuery) QueryCostBasisPins() *SubscriptionCostBasisPinQuery { + query := (&SubscriptionCostBasisPinClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(subscription.Table, subscription.FieldID, selector), + sqlgraph.To(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID), + sqlgraph.Edge(sqlgraph.O2M, false, subscription.CostBasisPinsTable, subscription.CostBasisPinsColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + // First returns the first Subscription entity from the query. // Returns a *NotFoundError when no Subscription was found. func (_q *SubscriptionQuery) First(ctx context.Context) (*Subscription, error) { @@ -528,6 +552,7 @@ func (_q *SubscriptionQuery) Clone() *SubscriptionQuery { withChargesFlatFee: _q.withChargesFlatFee.Clone(), withAddons: _q.withAddons.Clone(), withBillingSyncState: _q.withBillingSyncState.Clone(), + withCostBasisPins: _q.withCostBasisPins.Clone(), // clone intermediate query. sql: _q.sql.Clone(), path: _q.path, @@ -655,6 +680,17 @@ func (_q *SubscriptionQuery) WithBillingSyncState(opts ...func(*SubscriptionBill return _q } +// WithCostBasisPins tells the query-builder to eager-load the nodes that are connected to +// the "cost_basis_pins" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SubscriptionQuery) WithCostBasisPins(opts ...func(*SubscriptionCostBasisPinQuery)) *SubscriptionQuery { + query := (&SubscriptionCostBasisPinClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withCostBasisPins = query + return _q +} + // GroupBy is used to group vertices by one or more fields/columns. // It is often used with aggregate functions, like: count, max, mean, min, sum. // @@ -733,7 +769,7 @@ func (_q *SubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([] var ( nodes = []*Subscription{} _spec = _q.querySpec() - loadedTypes = [11]bool{ + loadedTypes = [12]bool{ _q.withPlan != nil, _q.withCustomer != nil, _q.withPhases != nil, @@ -745,6 +781,7 @@ func (_q *SubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([] _q.withChargesFlatFee != nil, _q.withAddons != nil, _q.withBillingSyncState != nil, + _q.withCostBasisPins != nil, } ) _spec.ScanValues = func(columns []string) ([]any, error) { @@ -850,6 +887,15 @@ func (_q *SubscriptionQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([] return nil, err } } + if query := _q.withCostBasisPins; query != nil { + if err := _q.loadCostBasisPins(ctx, query, nodes, + func(n *Subscription) { n.Edges.CostBasisPins = []*SubscriptionCostBasisPin{} }, + func(n *Subscription, e *SubscriptionCostBasisPin) { + n.Edges.CostBasisPins = append(n.Edges.CostBasisPins, e) + }); err != nil { + return nil, err + } + } return nodes, nil } @@ -1200,6 +1246,36 @@ func (_q *SubscriptionQuery) loadBillingSyncState(ctx context.Context, query *Su } return nil } +func (_q *SubscriptionQuery) loadCostBasisPins(ctx context.Context, query *SubscriptionCostBasisPinQuery, nodes []*Subscription, init func(*Subscription), assign func(*Subscription, *SubscriptionCostBasisPin)) error { + fks := make([]driver.Value, 0, len(nodes)) + nodeids := make(map[string]*Subscription) + for i := range nodes { + fks = append(fks, nodes[i].ID) + nodeids[nodes[i].ID] = nodes[i] + if init != nil { + init(nodes[i]) + } + } + if len(query.ctx.Fields) > 0 { + query.ctx.AppendFieldOnce(subscriptioncostbasispin.FieldSubscriptionID) + } + query.Where(predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + s.Where(sql.InValues(s.C(subscription.CostBasisPinsColumn), fks...)) + })) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + fk := n.SubscriptionID + node, ok := nodeids[fk] + if !ok { + return fmt.Errorf(`unexpected referenced foreign-key "subscription_id" returned %v for node %v`, fk, n.ID) + } + assign(node, n) + } + return nil +} func (_q *SubscriptionQuery) sqlCount(ctx context.Context) (int, error) { _spec := _q.querySpec() diff --git a/openmeter/ent/db/subscription_update.go b/openmeter/ent/db/subscription_update.go index 378f83fc25..118b33b5af 100644 --- a/openmeter/ent/db/subscription_update.go +++ b/openmeter/ent/db/subscription_update.go @@ -22,6 +22,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/ent/db/subscription" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionaddon" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionbillingsyncstate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptionphase" "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/pkg/datetime" @@ -351,6 +352,21 @@ func (_u *SubscriptionUpdate) SetBillingSyncState(v *SubscriptionBillingSyncStat return _u.SetBillingSyncStateID(v.ID) } +// AddCostBasisPinIDs adds the "cost_basis_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_u *SubscriptionUpdate) AddCostBasisPinIDs(ids ...string) *SubscriptionUpdate { + _u.mutation.AddCostBasisPinIDs(ids...) + return _u +} + +// AddCostBasisPins adds the "cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *SubscriptionUpdate) AddCostBasisPins(v ...*SubscriptionCostBasisPin) *SubscriptionUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddCostBasisPinIDs(ids...) +} + // Mutation returns the SubscriptionMutation object of the builder. func (_u *SubscriptionUpdate) Mutation() *SubscriptionMutation { return _u.mutation @@ -536,6 +552,27 @@ func (_u *SubscriptionUpdate) ClearBillingSyncState() *SubscriptionUpdate { return _u } +// ClearCostBasisPins clears all "cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *SubscriptionUpdate) ClearCostBasisPins() *SubscriptionUpdate { + _u.mutation.ClearCostBasisPins() + return _u +} + +// RemoveCostBasisPinIDs removes the "cost_basis_pins" edge to SubscriptionCostBasisPin entities by IDs. +func (_u *SubscriptionUpdate) RemoveCostBasisPinIDs(ids ...string) *SubscriptionUpdate { + _u.mutation.RemoveCostBasisPinIDs(ids...) + return _u +} + +// RemoveCostBasisPins removes "cost_basis_pins" edges to SubscriptionCostBasisPin entities. +func (_u *SubscriptionUpdate) RemoveCostBasisPins(v ...*SubscriptionCostBasisPin) *SubscriptionUpdate { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveCostBasisPinIDs(ids...) +} + // Save executes the query and returns the number of nodes affected by the update operation. func (_u *SubscriptionUpdate) Save(ctx context.Context) (int, error) { _u.defaults() @@ -1069,6 +1106,51 @@ func (_u *SubscriptionUpdate) sqlSave(ctx context.Context) (_node int, err error } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if _u.mutation.CostBasisPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: subscription.CostBasisPinsTable, + Columns: []string{subscription.CostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedCostBasisPinsIDs(); len(nodes) > 0 && !_u.mutation.CostBasisPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: subscription.CostBasisPinsTable, + Columns: []string{subscription.CostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.CostBasisPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: subscription.CostBasisPinsTable, + Columns: []string{subscription.CostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { if _, ok := err.(*sqlgraph.NotFoundError); ok { err = &NotFoundError{subscription.Label} @@ -1399,6 +1481,21 @@ func (_u *SubscriptionUpdateOne) SetBillingSyncState(v *SubscriptionBillingSyncS return _u.SetBillingSyncStateID(v.ID) } +// AddCostBasisPinIDs adds the "cost_basis_pins" edge to the SubscriptionCostBasisPin entity by IDs. +func (_u *SubscriptionUpdateOne) AddCostBasisPinIDs(ids ...string) *SubscriptionUpdateOne { + _u.mutation.AddCostBasisPinIDs(ids...) + return _u +} + +// AddCostBasisPins adds the "cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *SubscriptionUpdateOne) AddCostBasisPins(v ...*SubscriptionCostBasisPin) *SubscriptionUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.AddCostBasisPinIDs(ids...) +} + // Mutation returns the SubscriptionMutation object of the builder. func (_u *SubscriptionUpdateOne) Mutation() *SubscriptionMutation { return _u.mutation @@ -1584,6 +1681,27 @@ func (_u *SubscriptionUpdateOne) ClearBillingSyncState() *SubscriptionUpdateOne return _u } +// ClearCostBasisPins clears all "cost_basis_pins" edges to the SubscriptionCostBasisPin entity. +func (_u *SubscriptionUpdateOne) ClearCostBasisPins() *SubscriptionUpdateOne { + _u.mutation.ClearCostBasisPins() + return _u +} + +// RemoveCostBasisPinIDs removes the "cost_basis_pins" edge to SubscriptionCostBasisPin entities by IDs. +func (_u *SubscriptionUpdateOne) RemoveCostBasisPinIDs(ids ...string) *SubscriptionUpdateOne { + _u.mutation.RemoveCostBasisPinIDs(ids...) + return _u +} + +// RemoveCostBasisPins removes "cost_basis_pins" edges to SubscriptionCostBasisPin entities. +func (_u *SubscriptionUpdateOne) RemoveCostBasisPins(v ...*SubscriptionCostBasisPin) *SubscriptionUpdateOne { + ids := make([]string, len(v)) + for i := range v { + ids[i] = v[i].ID + } + return _u.RemoveCostBasisPinIDs(ids...) +} + // Where appends a list predicates to the SubscriptionUpdate builder. func (_u *SubscriptionUpdateOne) Where(ps ...predicate.Subscription) *SubscriptionUpdateOne { _u.mutation.Where(ps...) @@ -2147,6 +2265,51 @@ func (_u *SubscriptionUpdateOne) sqlSave(ctx context.Context) (_node *Subscripti } _spec.Edges.Add = append(_spec.Edges.Add, edge) } + if _u.mutation.CostBasisPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: subscription.CostBasisPinsTable, + Columns: []string{subscription.CostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.RemovedCostBasisPinsIDs(); len(nodes) > 0 && !_u.mutation.CostBasisPinsCleared() { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: subscription.CostBasisPinsTable, + Columns: []string{subscription.CostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Clear = append(_spec.Edges.Clear, edge) + } + if nodes := _u.mutation.CostBasisPinsIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.O2M, + Inverse: false, + Table: subscription.CostBasisPinsTable, + Columns: []string{subscription.CostBasisPinsColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _spec.Edges.Add = append(_spec.Edges.Add, edge) + } _node = &Subscription{config: _u.config} _spec.Assign = _node.assignValues _spec.ScanValues = _node.scanValues diff --git a/openmeter/ent/db/subscriptioncostbasispin.go b/openmeter/ent/db/subscriptioncostbasispin.go new file mode 100644 index 0000000000..d943c40e78 --- /dev/null +++ b/openmeter/ent/db/subscriptioncostbasispin.go @@ -0,0 +1,252 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "fmt" + "strings" + "time" + + "entgo.io/ent" + "entgo.io/ent/dialect/sql" + "github.com/openmeterio/openmeter/openmeter/ent/db/currencycostbasis" + "github.com/openmeterio/openmeter/openmeter/ent/db/customcurrency" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscription" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" + "github.com/openmeterio/openmeter/pkg/currencyx" +) + +// SubscriptionCostBasisPin is the model entity for the SubscriptionCostBasisPin schema. +type SubscriptionCostBasisPin struct { + config `json:"-"` + // ID of the ent. + ID string `json:"id,omitempty"` + // Namespace holds the value of the "namespace" field. + Namespace string `json:"namespace,omitempty"` + // CreatedAt holds the value of the "created_at" field. + CreatedAt time.Time `json:"created_at,omitempty"` + // UpdatedAt holds the value of the "updated_at" field. + UpdatedAt time.Time `json:"updated_at,omitempty"` + // DeletedAt holds the value of the "deleted_at" field. + DeletedAt *time.Time `json:"deleted_at,omitempty"` + // SubscriptionID holds the value of the "subscription_id" field. + SubscriptionID string `json:"subscription_id,omitempty"` + // CustomCurrencyID holds the value of the "custom_currency_id" field. + CustomCurrencyID string `json:"custom_currency_id,omitempty"` + // InvoiceCurrency holds the value of the "invoice_currency" field. + InvoiceCurrency currencyx.Code `json:"invoice_currency,omitempty"` + // CostBasisID holds the value of the "cost_basis_id" field. + CostBasisID string `json:"cost_basis_id,omitempty"` + // Edges holds the relations/edges for other nodes in the graph. + // The values are being populated by the SubscriptionCostBasisPinQuery when eager-loading is set. + Edges SubscriptionCostBasisPinEdges `json:"edges"` + selectValues sql.SelectValues +} + +// SubscriptionCostBasisPinEdges holds the relations/edges for other nodes in the graph. +type SubscriptionCostBasisPinEdges struct { + // Subscription holds the value of the subscription edge. + Subscription *Subscription `json:"subscription,omitempty"` + // CustomCurrency holds the value of the custom_currency edge. + CustomCurrency *CustomCurrency `json:"custom_currency,omitempty"` + // CostBasis holds the value of the cost_basis edge. + CostBasis *CurrencyCostBasis `json:"cost_basis,omitempty"` + // loadedTypes holds the information for reporting if a + // type was loaded (or requested) in eager-loading or not. + loadedTypes [3]bool +} + +// SubscriptionOrErr returns the Subscription value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e SubscriptionCostBasisPinEdges) SubscriptionOrErr() (*Subscription, error) { + if e.Subscription != nil { + return e.Subscription, nil + } else if e.loadedTypes[0] { + return nil, &NotFoundError{label: subscription.Label} + } + return nil, &NotLoadedError{edge: "subscription"} +} + +// CustomCurrencyOrErr returns the CustomCurrency value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e SubscriptionCostBasisPinEdges) CustomCurrencyOrErr() (*CustomCurrency, error) { + if e.CustomCurrency != nil { + return e.CustomCurrency, nil + } else if e.loadedTypes[1] { + return nil, &NotFoundError{label: customcurrency.Label} + } + return nil, &NotLoadedError{edge: "custom_currency"} +} + +// CostBasisOrErr returns the CostBasis value or an error if the edge +// was not loaded in eager-loading, or loaded but was not found. +func (e SubscriptionCostBasisPinEdges) CostBasisOrErr() (*CurrencyCostBasis, error) { + if e.CostBasis != nil { + return e.CostBasis, nil + } else if e.loadedTypes[2] { + return nil, &NotFoundError{label: currencycostbasis.Label} + } + return nil, &NotLoadedError{edge: "cost_basis"} +} + +// scanValues returns the types for scanning values from sql.Rows. +func (*SubscriptionCostBasisPin) scanValues(columns []string) ([]any, error) { + values := make([]any, len(columns)) + for i := range columns { + switch columns[i] { + case subscriptioncostbasispin.FieldID, subscriptioncostbasispin.FieldNamespace, subscriptioncostbasispin.FieldSubscriptionID, subscriptioncostbasispin.FieldCustomCurrencyID, subscriptioncostbasispin.FieldInvoiceCurrency, subscriptioncostbasispin.FieldCostBasisID: + values[i] = new(sql.NullString) + case subscriptioncostbasispin.FieldCreatedAt, subscriptioncostbasispin.FieldUpdatedAt, subscriptioncostbasispin.FieldDeletedAt: + values[i] = new(sql.NullTime) + default: + values[i] = new(sql.UnknownType) + } + } + return values, nil +} + +// assignValues assigns the values that were returned from sql.Rows (after scanning) +// to the SubscriptionCostBasisPin fields. +func (_m *SubscriptionCostBasisPin) assignValues(columns []string, values []any) error { + if m, n := len(values), len(columns); m < n { + return fmt.Errorf("mismatch number of scan values: %d != %d", m, n) + } + for i := range columns { + switch columns[i] { + case subscriptioncostbasispin.FieldID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field id", values[i]) + } else if value.Valid { + _m.ID = value.String + } + case subscriptioncostbasispin.FieldNamespace: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field namespace", values[i]) + } else if value.Valid { + _m.Namespace = value.String + } + case subscriptioncostbasispin.FieldCreatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field created_at", values[i]) + } else if value.Valid { + _m.CreatedAt = value.Time + } + case subscriptioncostbasispin.FieldUpdatedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field updated_at", values[i]) + } else if value.Valid { + _m.UpdatedAt = value.Time + } + case subscriptioncostbasispin.FieldDeletedAt: + if value, ok := values[i].(*sql.NullTime); !ok { + return fmt.Errorf("unexpected type %T for field deleted_at", values[i]) + } else if value.Valid { + _m.DeletedAt = new(time.Time) + *_m.DeletedAt = value.Time + } + case subscriptioncostbasispin.FieldSubscriptionID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field subscription_id", values[i]) + } else if value.Valid { + _m.SubscriptionID = value.String + } + case subscriptioncostbasispin.FieldCustomCurrencyID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field custom_currency_id", values[i]) + } else if value.Valid { + _m.CustomCurrencyID = value.String + } + case subscriptioncostbasispin.FieldInvoiceCurrency: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field invoice_currency", values[i]) + } else if value.Valid { + _m.InvoiceCurrency = currencyx.Code(value.String) + } + case subscriptioncostbasispin.FieldCostBasisID: + if value, ok := values[i].(*sql.NullString); !ok { + return fmt.Errorf("unexpected type %T for field cost_basis_id", values[i]) + } else if value.Valid { + _m.CostBasisID = value.String + } + default: + _m.selectValues.Set(columns[i], values[i]) + } + } + return nil +} + +// Value returns the ent.Value that was dynamically selected and assigned to the SubscriptionCostBasisPin. +// This includes values selected through modifiers, order, etc. +func (_m *SubscriptionCostBasisPin) Value(name string) (ent.Value, error) { + return _m.selectValues.Get(name) +} + +// QuerySubscription queries the "subscription" edge of the SubscriptionCostBasisPin entity. +func (_m *SubscriptionCostBasisPin) QuerySubscription() *SubscriptionQuery { + return NewSubscriptionCostBasisPinClient(_m.config).QuerySubscription(_m) +} + +// QueryCustomCurrency queries the "custom_currency" edge of the SubscriptionCostBasisPin entity. +func (_m *SubscriptionCostBasisPin) QueryCustomCurrency() *CustomCurrencyQuery { + return NewSubscriptionCostBasisPinClient(_m.config).QueryCustomCurrency(_m) +} + +// QueryCostBasis queries the "cost_basis" edge of the SubscriptionCostBasisPin entity. +func (_m *SubscriptionCostBasisPin) QueryCostBasis() *CurrencyCostBasisQuery { + return NewSubscriptionCostBasisPinClient(_m.config).QueryCostBasis(_m) +} + +// Update returns a builder for updating this SubscriptionCostBasisPin. +// Note that you need to call SubscriptionCostBasisPin.Unwrap() before calling this method if this SubscriptionCostBasisPin +// was returned from a transaction, and the transaction was committed or rolled back. +func (_m *SubscriptionCostBasisPin) Update() *SubscriptionCostBasisPinUpdateOne { + return NewSubscriptionCostBasisPinClient(_m.config).UpdateOne(_m) +} + +// Unwrap unwraps the SubscriptionCostBasisPin entity that was returned from a transaction after it was closed, +// so that all future queries will be executed through the driver which created the transaction. +func (_m *SubscriptionCostBasisPin) Unwrap() *SubscriptionCostBasisPin { + _tx, ok := _m.config.driver.(*txDriver) + if !ok { + panic("db: SubscriptionCostBasisPin is not a transactional entity") + } + _m.config.driver = _tx.drv + return _m +} + +// String implements the fmt.Stringer. +func (_m *SubscriptionCostBasisPin) String() string { + var builder strings.Builder + builder.WriteString("SubscriptionCostBasisPin(") + builder.WriteString(fmt.Sprintf("id=%v, ", _m.ID)) + builder.WriteString("namespace=") + builder.WriteString(_m.Namespace) + builder.WriteString(", ") + builder.WriteString("created_at=") + builder.WriteString(_m.CreatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + builder.WriteString("updated_at=") + builder.WriteString(_m.UpdatedAt.Format(time.ANSIC)) + builder.WriteString(", ") + if v := _m.DeletedAt; v != nil { + builder.WriteString("deleted_at=") + builder.WriteString(v.Format(time.ANSIC)) + } + builder.WriteString(", ") + builder.WriteString("subscription_id=") + builder.WriteString(_m.SubscriptionID) + builder.WriteString(", ") + builder.WriteString("custom_currency_id=") + builder.WriteString(_m.CustomCurrencyID) + builder.WriteString(", ") + builder.WriteString("invoice_currency=") + builder.WriteString(fmt.Sprintf("%v", _m.InvoiceCurrency)) + builder.WriteString(", ") + builder.WriteString("cost_basis_id=") + builder.WriteString(_m.CostBasisID) + builder.WriteByte(')') + return builder.String() +} + +// SubscriptionCostBasisPins is a parsable slice of SubscriptionCostBasisPin. +type SubscriptionCostBasisPins []*SubscriptionCostBasisPin diff --git a/openmeter/ent/db/subscriptioncostbasispin/subscriptioncostbasispin.go b/openmeter/ent/db/subscriptioncostbasispin/subscriptioncostbasispin.go new file mode 100644 index 0000000000..4a47b0f44f --- /dev/null +++ b/openmeter/ent/db/subscriptioncostbasispin/subscriptioncostbasispin.go @@ -0,0 +1,196 @@ +// Code generated by ent, DO NOT EDIT. + +package subscriptioncostbasispin + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" +) + +const ( + // Label holds the string label denoting the subscriptioncostbasispin type in the database. + Label = "subscription_cost_basis_pin" + // FieldID holds the string denoting the id field in the database. + FieldID = "id" + // FieldNamespace holds the string denoting the namespace field in the database. + FieldNamespace = "namespace" + // FieldCreatedAt holds the string denoting the created_at field in the database. + FieldCreatedAt = "created_at" + // FieldUpdatedAt holds the string denoting the updated_at field in the database. + FieldUpdatedAt = "updated_at" + // FieldDeletedAt holds the string denoting the deleted_at field in the database. + FieldDeletedAt = "deleted_at" + // FieldSubscriptionID holds the string denoting the subscription_id field in the database. + FieldSubscriptionID = "subscription_id" + // FieldCustomCurrencyID holds the string denoting the custom_currency_id field in the database. + FieldCustomCurrencyID = "custom_currency_id" + // FieldInvoiceCurrency holds the string denoting the invoice_currency field in the database. + FieldInvoiceCurrency = "invoice_currency" + // FieldCostBasisID holds the string denoting the cost_basis_id field in the database. + FieldCostBasisID = "cost_basis_id" + // EdgeSubscription holds the string denoting the subscription edge name in mutations. + EdgeSubscription = "subscription" + // EdgeCustomCurrency holds the string denoting the custom_currency edge name in mutations. + EdgeCustomCurrency = "custom_currency" + // EdgeCostBasis holds the string denoting the cost_basis edge name in mutations. + EdgeCostBasis = "cost_basis" + // Table holds the table name of the subscriptioncostbasispin in the database. + Table = "subscription_cost_basis_pins" + // SubscriptionTable is the table that holds the subscription relation/edge. + SubscriptionTable = "subscription_cost_basis_pins" + // SubscriptionInverseTable is the table name for the Subscription entity. + // It exists in this package in order to avoid circular dependency with the "subscription" package. + SubscriptionInverseTable = "subscriptions" + // SubscriptionColumn is the table column denoting the subscription relation/edge. + SubscriptionColumn = "subscription_id" + // CustomCurrencyTable is the table that holds the custom_currency relation/edge. + CustomCurrencyTable = "subscription_cost_basis_pins" + // CustomCurrencyInverseTable is the table name for the CustomCurrency entity. + // It exists in this package in order to avoid circular dependency with the "customcurrency" package. + CustomCurrencyInverseTable = "custom_currencies" + // CustomCurrencyColumn is the table column denoting the custom_currency relation/edge. + CustomCurrencyColumn = "custom_currency_id" + // CostBasisTable is the table that holds the cost_basis relation/edge. + CostBasisTable = "subscription_cost_basis_pins" + // CostBasisInverseTable is the table name for the CurrencyCostBasis entity. + // It exists in this package in order to avoid circular dependency with the "currencycostbasis" package. + CostBasisInverseTable = "currency_cost_bases" + // CostBasisColumn is the table column denoting the cost_basis relation/edge. + CostBasisColumn = "cost_basis_id" +) + +// Columns holds all SQL columns for subscriptioncostbasispin fields. +var Columns = []string{ + FieldID, + FieldNamespace, + FieldCreatedAt, + FieldUpdatedAt, + FieldDeletedAt, + FieldSubscriptionID, + FieldCustomCurrencyID, + FieldInvoiceCurrency, + FieldCostBasisID, +} + +// ValidColumn reports if the column name is valid (part of the table columns). +func ValidColumn(column string) bool { + for i := range Columns { + if column == Columns[i] { + return true + } + } + return false +} + +var ( + // NamespaceValidator is a validator for the "namespace" field. It is called by the builders before save. + NamespaceValidator func(string) error + // DefaultCreatedAt holds the default value on creation for the "created_at" field. + DefaultCreatedAt func() time.Time + // DefaultUpdatedAt holds the default value on creation for the "updated_at" field. + DefaultUpdatedAt func() time.Time + // UpdateDefaultUpdatedAt holds the default value on update for the "updated_at" field. + UpdateDefaultUpdatedAt func() time.Time + // SubscriptionIDValidator is a validator for the "subscription_id" field. It is called by the builders before save. + SubscriptionIDValidator func(string) error + // CustomCurrencyIDValidator is a validator for the "custom_currency_id" field. It is called by the builders before save. + CustomCurrencyIDValidator func(string) error + // InvoiceCurrencyValidator is a validator for the "invoice_currency" field. It is called by the builders before save. + InvoiceCurrencyValidator func(string) error + // CostBasisIDValidator is a validator for the "cost_basis_id" field. It is called by the builders before save. + CostBasisIDValidator func(string) error + // DefaultID holds the default value on creation for the "id" field. + DefaultID func() string +) + +// OrderOption defines the ordering options for the SubscriptionCostBasisPin queries. +type OrderOption func(*sql.Selector) + +// ByID orders the results by the id field. +func ByID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldID, opts...).ToFunc() +} + +// ByNamespace orders the results by the namespace field. +func ByNamespace(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldNamespace, opts...).ToFunc() +} + +// ByCreatedAt orders the results by the created_at field. +func ByCreatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCreatedAt, opts...).ToFunc() +} + +// ByUpdatedAt orders the results by the updated_at field. +func ByUpdatedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldUpdatedAt, opts...).ToFunc() +} + +// ByDeletedAt orders the results by the deleted_at field. +func ByDeletedAt(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldDeletedAt, opts...).ToFunc() +} + +// BySubscriptionID orders the results by the subscription_id field. +func BySubscriptionID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldSubscriptionID, opts...).ToFunc() +} + +// ByCustomCurrencyID orders the results by the custom_currency_id field. +func ByCustomCurrencyID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCustomCurrencyID, opts...).ToFunc() +} + +// ByInvoiceCurrency orders the results by the invoice_currency field. +func ByInvoiceCurrency(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldInvoiceCurrency, opts...).ToFunc() +} + +// ByCostBasisID orders the results by the cost_basis_id field. +func ByCostBasisID(opts ...sql.OrderTermOption) OrderOption { + return sql.OrderByField(FieldCostBasisID, opts...).ToFunc() +} + +// BySubscriptionField orders the results by subscription field. +func BySubscriptionField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newSubscriptionStep(), sql.OrderByField(field, opts...)) + } +} + +// ByCustomCurrencyField orders the results by custom_currency field. +func ByCustomCurrencyField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newCustomCurrencyStep(), sql.OrderByField(field, opts...)) + } +} + +// ByCostBasisField orders the results by cost_basis field. +func ByCostBasisField(field string, opts ...sql.OrderTermOption) OrderOption { + return func(s *sql.Selector) { + sqlgraph.OrderByNeighborTerms(s, newCostBasisStep(), sql.OrderByField(field, opts...)) + } +} +func newSubscriptionStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(SubscriptionInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, SubscriptionTable, SubscriptionColumn), + ) +} +func newCustomCurrencyStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(CustomCurrencyInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, CustomCurrencyTable, CustomCurrencyColumn), + ) +} +func newCostBasisStep() *sqlgraph.Step { + return sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.To(CostBasisInverseTable, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, CostBasisTable, CostBasisColumn), + ) +} diff --git a/openmeter/ent/db/subscriptioncostbasispin/where.go b/openmeter/ent/db/subscriptioncostbasispin/where.go new file mode 100644 index 0000000000..1f06ace5d0 --- /dev/null +++ b/openmeter/ent/db/subscriptioncostbasispin/where.go @@ -0,0 +1,666 @@ +// Code generated by ent, DO NOT EDIT. + +package subscriptioncostbasispin + +import ( + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "github.com/openmeterio/openmeter/openmeter/ent/db/predicate" + "github.com/openmeterio/openmeter/pkg/currencyx" +) + +// ID filters vertices based on their ID field. +func ID(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldID, id)) +} + +// IDEQ applies the EQ predicate on the ID field. +func IDEQ(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldID, id)) +} + +// IDNEQ applies the NEQ predicate on the ID field. +func IDNEQ(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldID, id)) +} + +// IDIn applies the In predicate on the ID field. +func IDIn(ids ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldID, ids...)) +} + +// IDNotIn applies the NotIn predicate on the ID field. +func IDNotIn(ids ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldID, ids...)) +} + +// IDGT applies the GT predicate on the ID field. +func IDGT(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldID, id)) +} + +// IDGTE applies the GTE predicate on the ID field. +func IDGTE(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldID, id)) +} + +// IDLT applies the LT predicate on the ID field. +func IDLT(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldID, id)) +} + +// IDLTE applies the LTE predicate on the ID field. +func IDLTE(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldID, id)) +} + +// IDEqualFold applies the EqualFold predicate on the ID field. +func IDEqualFold(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEqualFold(FieldID, id)) +} + +// IDContainsFold applies the ContainsFold predicate on the ID field. +func IDContainsFold(id string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContainsFold(FieldID, id)) +} + +// Namespace applies equality check predicate on the "namespace" field. It's identical to NamespaceEQ. +func Namespace(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldNamespace, v)) +} + +// CreatedAt applies equality check predicate on the "created_at" field. It's identical to CreatedAtEQ. +func CreatedAt(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldCreatedAt, v)) +} + +// UpdatedAt applies equality check predicate on the "updated_at" field. It's identical to UpdatedAtEQ. +func UpdatedAt(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// DeletedAt applies equality check predicate on the "deleted_at" field. It's identical to DeletedAtEQ. +func DeletedAt(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldDeletedAt, v)) +} + +// SubscriptionID applies equality check predicate on the "subscription_id" field. It's identical to SubscriptionIDEQ. +func SubscriptionID(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldSubscriptionID, v)) +} + +// CustomCurrencyID applies equality check predicate on the "custom_currency_id" field. It's identical to CustomCurrencyIDEQ. +func CustomCurrencyID(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldCustomCurrencyID, v)) +} + +// InvoiceCurrency applies equality check predicate on the "invoice_currency" field. It's identical to InvoiceCurrencyEQ. +func InvoiceCurrency(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldInvoiceCurrency, vc)) +} + +// CostBasisID applies equality check predicate on the "cost_basis_id" field. It's identical to CostBasisIDEQ. +func CostBasisID(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldCostBasisID, v)) +} + +// NamespaceEQ applies the EQ predicate on the "namespace" field. +func NamespaceEQ(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldNamespace, v)) +} + +// NamespaceNEQ applies the NEQ predicate on the "namespace" field. +func NamespaceNEQ(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldNamespace, v)) +} + +// NamespaceIn applies the In predicate on the "namespace" field. +func NamespaceIn(vs ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldNamespace, vs...)) +} + +// NamespaceNotIn applies the NotIn predicate on the "namespace" field. +func NamespaceNotIn(vs ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldNamespace, vs...)) +} + +// NamespaceGT applies the GT predicate on the "namespace" field. +func NamespaceGT(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldNamespace, v)) +} + +// NamespaceGTE applies the GTE predicate on the "namespace" field. +func NamespaceGTE(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldNamespace, v)) +} + +// NamespaceLT applies the LT predicate on the "namespace" field. +func NamespaceLT(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldNamespace, v)) +} + +// NamespaceLTE applies the LTE predicate on the "namespace" field. +func NamespaceLTE(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldNamespace, v)) +} + +// NamespaceContains applies the Contains predicate on the "namespace" field. +func NamespaceContains(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContains(FieldNamespace, v)) +} + +// NamespaceHasPrefix applies the HasPrefix predicate on the "namespace" field. +func NamespaceHasPrefix(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldHasPrefix(FieldNamespace, v)) +} + +// NamespaceHasSuffix applies the HasSuffix predicate on the "namespace" field. +func NamespaceHasSuffix(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldHasSuffix(FieldNamespace, v)) +} + +// NamespaceEqualFold applies the EqualFold predicate on the "namespace" field. +func NamespaceEqualFold(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEqualFold(FieldNamespace, v)) +} + +// NamespaceContainsFold applies the ContainsFold predicate on the "namespace" field. +func NamespaceContainsFold(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContainsFold(FieldNamespace, v)) +} + +// CreatedAtEQ applies the EQ predicate on the "created_at" field. +func CreatedAtEQ(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldCreatedAt, v)) +} + +// CreatedAtNEQ applies the NEQ predicate on the "created_at" field. +func CreatedAtNEQ(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldCreatedAt, v)) +} + +// CreatedAtIn applies the In predicate on the "created_at" field. +func CreatedAtIn(vs ...time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldCreatedAt, vs...)) +} + +// CreatedAtNotIn applies the NotIn predicate on the "created_at" field. +func CreatedAtNotIn(vs ...time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldCreatedAt, vs...)) +} + +// CreatedAtGT applies the GT predicate on the "created_at" field. +func CreatedAtGT(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldCreatedAt, v)) +} + +// CreatedAtGTE applies the GTE predicate on the "created_at" field. +func CreatedAtGTE(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldCreatedAt, v)) +} + +// CreatedAtLT applies the LT predicate on the "created_at" field. +func CreatedAtLT(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldCreatedAt, v)) +} + +// CreatedAtLTE applies the LTE predicate on the "created_at" field. +func CreatedAtLTE(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldCreatedAt, v)) +} + +// UpdatedAtEQ applies the EQ predicate on the "updated_at" field. +func UpdatedAtEQ(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtNEQ applies the NEQ predicate on the "updated_at" field. +func UpdatedAtNEQ(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldUpdatedAt, v)) +} + +// UpdatedAtIn applies the In predicate on the "updated_at" field. +func UpdatedAtIn(vs ...time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtNotIn applies the NotIn predicate on the "updated_at" field. +func UpdatedAtNotIn(vs ...time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldUpdatedAt, vs...)) +} + +// UpdatedAtGT applies the GT predicate on the "updated_at" field. +func UpdatedAtGT(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldUpdatedAt, v)) +} + +// UpdatedAtGTE applies the GTE predicate on the "updated_at" field. +func UpdatedAtGTE(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldUpdatedAt, v)) +} + +// UpdatedAtLT applies the LT predicate on the "updated_at" field. +func UpdatedAtLT(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldUpdatedAt, v)) +} + +// UpdatedAtLTE applies the LTE predicate on the "updated_at" field. +func UpdatedAtLTE(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldUpdatedAt, v)) +} + +// DeletedAtEQ applies the EQ predicate on the "deleted_at" field. +func DeletedAtEQ(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldDeletedAt, v)) +} + +// DeletedAtNEQ applies the NEQ predicate on the "deleted_at" field. +func DeletedAtNEQ(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldDeletedAt, v)) +} + +// DeletedAtIn applies the In predicate on the "deleted_at" field. +func DeletedAtIn(vs ...time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldDeletedAt, vs...)) +} + +// DeletedAtNotIn applies the NotIn predicate on the "deleted_at" field. +func DeletedAtNotIn(vs ...time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldDeletedAt, vs...)) +} + +// DeletedAtGT applies the GT predicate on the "deleted_at" field. +func DeletedAtGT(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldDeletedAt, v)) +} + +// DeletedAtGTE applies the GTE predicate on the "deleted_at" field. +func DeletedAtGTE(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldDeletedAt, v)) +} + +// DeletedAtLT applies the LT predicate on the "deleted_at" field. +func DeletedAtLT(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldDeletedAt, v)) +} + +// DeletedAtLTE applies the LTE predicate on the "deleted_at" field. +func DeletedAtLTE(v time.Time) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldDeletedAt, v)) +} + +// DeletedAtIsNil applies the IsNil predicate on the "deleted_at" field. +func DeletedAtIsNil() predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIsNull(FieldDeletedAt)) +} + +// DeletedAtNotNil applies the NotNil predicate on the "deleted_at" field. +func DeletedAtNotNil() predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotNull(FieldDeletedAt)) +} + +// SubscriptionIDEQ applies the EQ predicate on the "subscription_id" field. +func SubscriptionIDEQ(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldSubscriptionID, v)) +} + +// SubscriptionIDNEQ applies the NEQ predicate on the "subscription_id" field. +func SubscriptionIDNEQ(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldSubscriptionID, v)) +} + +// SubscriptionIDIn applies the In predicate on the "subscription_id" field. +func SubscriptionIDIn(vs ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldSubscriptionID, vs...)) +} + +// SubscriptionIDNotIn applies the NotIn predicate on the "subscription_id" field. +func SubscriptionIDNotIn(vs ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldSubscriptionID, vs...)) +} + +// SubscriptionIDGT applies the GT predicate on the "subscription_id" field. +func SubscriptionIDGT(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldSubscriptionID, v)) +} + +// SubscriptionIDGTE applies the GTE predicate on the "subscription_id" field. +func SubscriptionIDGTE(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldSubscriptionID, v)) +} + +// SubscriptionIDLT applies the LT predicate on the "subscription_id" field. +func SubscriptionIDLT(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldSubscriptionID, v)) +} + +// SubscriptionIDLTE applies the LTE predicate on the "subscription_id" field. +func SubscriptionIDLTE(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldSubscriptionID, v)) +} + +// SubscriptionIDContains applies the Contains predicate on the "subscription_id" field. +func SubscriptionIDContains(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContains(FieldSubscriptionID, v)) +} + +// SubscriptionIDHasPrefix applies the HasPrefix predicate on the "subscription_id" field. +func SubscriptionIDHasPrefix(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldHasPrefix(FieldSubscriptionID, v)) +} + +// SubscriptionIDHasSuffix applies the HasSuffix predicate on the "subscription_id" field. +func SubscriptionIDHasSuffix(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldHasSuffix(FieldSubscriptionID, v)) +} + +// SubscriptionIDEqualFold applies the EqualFold predicate on the "subscription_id" field. +func SubscriptionIDEqualFold(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEqualFold(FieldSubscriptionID, v)) +} + +// SubscriptionIDContainsFold applies the ContainsFold predicate on the "subscription_id" field. +func SubscriptionIDContainsFold(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContainsFold(FieldSubscriptionID, v)) +} + +// CustomCurrencyIDEQ applies the EQ predicate on the "custom_currency_id" field. +func CustomCurrencyIDEQ(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDNEQ applies the NEQ predicate on the "custom_currency_id" field. +func CustomCurrencyIDNEQ(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDIn applies the In predicate on the "custom_currency_id" field. +func CustomCurrencyIDIn(vs ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldCustomCurrencyID, vs...)) +} + +// CustomCurrencyIDNotIn applies the NotIn predicate on the "custom_currency_id" field. +func CustomCurrencyIDNotIn(vs ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldCustomCurrencyID, vs...)) +} + +// CustomCurrencyIDGT applies the GT predicate on the "custom_currency_id" field. +func CustomCurrencyIDGT(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDGTE applies the GTE predicate on the "custom_currency_id" field. +func CustomCurrencyIDGTE(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDLT applies the LT predicate on the "custom_currency_id" field. +func CustomCurrencyIDLT(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDLTE applies the LTE predicate on the "custom_currency_id" field. +func CustomCurrencyIDLTE(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDContains applies the Contains predicate on the "custom_currency_id" field. +func CustomCurrencyIDContains(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContains(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDHasPrefix applies the HasPrefix predicate on the "custom_currency_id" field. +func CustomCurrencyIDHasPrefix(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldHasPrefix(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDHasSuffix applies the HasSuffix predicate on the "custom_currency_id" field. +func CustomCurrencyIDHasSuffix(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldHasSuffix(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDEqualFold applies the EqualFold predicate on the "custom_currency_id" field. +func CustomCurrencyIDEqualFold(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEqualFold(FieldCustomCurrencyID, v)) +} + +// CustomCurrencyIDContainsFold applies the ContainsFold predicate on the "custom_currency_id" field. +func CustomCurrencyIDContainsFold(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContainsFold(FieldCustomCurrencyID, v)) +} + +// InvoiceCurrencyEQ applies the EQ predicate on the "invoice_currency" field. +func InvoiceCurrencyEQ(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyNEQ applies the NEQ predicate on the "invoice_currency" field. +func InvoiceCurrencyNEQ(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyIn applies the In predicate on the "invoice_currency" field. +func InvoiceCurrencyIn(vs ...currencyx.Code) predicate.SubscriptionCostBasisPin { + v := make([]any, len(vs)) + for i := range v { + v[i] = string(vs[i]) + } + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldInvoiceCurrency, v...)) +} + +// InvoiceCurrencyNotIn applies the NotIn predicate on the "invoice_currency" field. +func InvoiceCurrencyNotIn(vs ...currencyx.Code) predicate.SubscriptionCostBasisPin { + v := make([]any, len(vs)) + for i := range v { + v[i] = string(vs[i]) + } + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldInvoiceCurrency, v...)) +} + +// InvoiceCurrencyGT applies the GT predicate on the "invoice_currency" field. +func InvoiceCurrencyGT(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyGTE applies the GTE predicate on the "invoice_currency" field. +func InvoiceCurrencyGTE(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyLT applies the LT predicate on the "invoice_currency" field. +func InvoiceCurrencyLT(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyLTE applies the LTE predicate on the "invoice_currency" field. +func InvoiceCurrencyLTE(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyContains applies the Contains predicate on the "invoice_currency" field. +func InvoiceCurrencyContains(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldContains(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyHasPrefix applies the HasPrefix predicate on the "invoice_currency" field. +func InvoiceCurrencyHasPrefix(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldHasPrefix(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyHasSuffix applies the HasSuffix predicate on the "invoice_currency" field. +func InvoiceCurrencyHasSuffix(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldHasSuffix(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyEqualFold applies the EqualFold predicate on the "invoice_currency" field. +func InvoiceCurrencyEqualFold(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldEqualFold(FieldInvoiceCurrency, vc)) +} + +// InvoiceCurrencyContainsFold applies the ContainsFold predicate on the "invoice_currency" field. +func InvoiceCurrencyContainsFold(v currencyx.Code) predicate.SubscriptionCostBasisPin { + vc := string(v) + return predicate.SubscriptionCostBasisPin(sql.FieldContainsFold(FieldInvoiceCurrency, vc)) +} + +// CostBasisIDEQ applies the EQ predicate on the "cost_basis_id" field. +func CostBasisIDEQ(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEQ(FieldCostBasisID, v)) +} + +// CostBasisIDNEQ applies the NEQ predicate on the "cost_basis_id" field. +func CostBasisIDNEQ(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNEQ(FieldCostBasisID, v)) +} + +// CostBasisIDIn applies the In predicate on the "cost_basis_id" field. +func CostBasisIDIn(vs ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldIn(FieldCostBasisID, vs...)) +} + +// CostBasisIDNotIn applies the NotIn predicate on the "cost_basis_id" field. +func CostBasisIDNotIn(vs ...string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldNotIn(FieldCostBasisID, vs...)) +} + +// CostBasisIDGT applies the GT predicate on the "cost_basis_id" field. +func CostBasisIDGT(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGT(FieldCostBasisID, v)) +} + +// CostBasisIDGTE applies the GTE predicate on the "cost_basis_id" field. +func CostBasisIDGTE(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldGTE(FieldCostBasisID, v)) +} + +// CostBasisIDLT applies the LT predicate on the "cost_basis_id" field. +func CostBasisIDLT(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLT(FieldCostBasisID, v)) +} + +// CostBasisIDLTE applies the LTE predicate on the "cost_basis_id" field. +func CostBasisIDLTE(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldLTE(FieldCostBasisID, v)) +} + +// CostBasisIDContains applies the Contains predicate on the "cost_basis_id" field. +func CostBasisIDContains(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContains(FieldCostBasisID, v)) +} + +// CostBasisIDHasPrefix applies the HasPrefix predicate on the "cost_basis_id" field. +func CostBasisIDHasPrefix(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldHasPrefix(FieldCostBasisID, v)) +} + +// CostBasisIDHasSuffix applies the HasSuffix predicate on the "cost_basis_id" field. +func CostBasisIDHasSuffix(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldHasSuffix(FieldCostBasisID, v)) +} + +// CostBasisIDEqualFold applies the EqualFold predicate on the "cost_basis_id" field. +func CostBasisIDEqualFold(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldEqualFold(FieldCostBasisID, v)) +} + +// CostBasisIDContainsFold applies the ContainsFold predicate on the "cost_basis_id" field. +func CostBasisIDContainsFold(v string) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.FieldContainsFold(FieldCostBasisID, v)) +} + +// HasSubscription applies the HasEdge predicate on the "subscription" edge. +func HasSubscription() predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, SubscriptionTable, SubscriptionColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasSubscriptionWith applies the HasEdge predicate on the "subscription" edge with a given conditions (other predicates). +func HasSubscriptionWith(preds ...predicate.Subscription) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + step := newSubscriptionStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasCustomCurrency applies the HasEdge predicate on the "custom_currency" edge. +func HasCustomCurrency() predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, CustomCurrencyTable, CustomCurrencyColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasCustomCurrencyWith applies the HasEdge predicate on the "custom_currency" edge with a given conditions (other predicates). +func HasCustomCurrencyWith(preds ...predicate.CustomCurrency) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + step := newCustomCurrencyStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// HasCostBasis applies the HasEdge predicate on the "cost_basis" edge. +func HasCostBasis() predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + step := sqlgraph.NewStep( + sqlgraph.From(Table, FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, CostBasisTable, CostBasisColumn), + ) + sqlgraph.HasNeighbors(s, step) + }) +} + +// HasCostBasisWith applies the HasEdge predicate on the "cost_basis" edge with a given conditions (other predicates). +func HasCostBasisWith(preds ...predicate.CurrencyCostBasis) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(func(s *sql.Selector) { + step := newCostBasisStep() + sqlgraph.HasNeighborsWith(s, step, func(s *sql.Selector) { + for _, p := range preds { + p(s) + } + }) + }) +} + +// And groups predicates with the AND operator between them. +func And(predicates ...predicate.SubscriptionCostBasisPin) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.AndPredicates(predicates...)) +} + +// Or groups predicates with the OR operator between them. +func Or(predicates ...predicate.SubscriptionCostBasisPin) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.OrPredicates(predicates...)) +} + +// Not applies the not operator on the given predicate. +func Not(p predicate.SubscriptionCostBasisPin) predicate.SubscriptionCostBasisPin { + return predicate.SubscriptionCostBasisPin(sql.NotPredicates(p)) +} diff --git a/openmeter/ent/db/subscriptioncostbasispin_create.go b/openmeter/ent/db/subscriptioncostbasispin_create.go new file mode 100644 index 0000000000..aeddd489c6 --- /dev/null +++ b/openmeter/ent/db/subscriptioncostbasispin_create.go @@ -0,0 +1,816 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/openmeterio/openmeter/openmeter/ent/db/currencycostbasis" + "github.com/openmeterio/openmeter/openmeter/ent/db/customcurrency" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscription" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" + "github.com/openmeterio/openmeter/pkg/currencyx" +) + +// SubscriptionCostBasisPinCreate is the builder for creating a SubscriptionCostBasisPin entity. +type SubscriptionCostBasisPinCreate struct { + config + mutation *SubscriptionCostBasisPinMutation + hooks []Hook + conflict []sql.ConflictOption +} + +// SetNamespace sets the "namespace" field. +func (_c *SubscriptionCostBasisPinCreate) SetNamespace(v string) *SubscriptionCostBasisPinCreate { + _c.mutation.SetNamespace(v) + return _c +} + +// SetCreatedAt sets the "created_at" field. +func (_c *SubscriptionCostBasisPinCreate) SetCreatedAt(v time.Time) *SubscriptionCostBasisPinCreate { + _c.mutation.SetCreatedAt(v) + return _c +} + +// SetNillableCreatedAt sets the "created_at" field if the given value is not nil. +func (_c *SubscriptionCostBasisPinCreate) SetNillableCreatedAt(v *time.Time) *SubscriptionCostBasisPinCreate { + if v != nil { + _c.SetCreatedAt(*v) + } + return _c +} + +// SetUpdatedAt sets the "updated_at" field. +func (_c *SubscriptionCostBasisPinCreate) SetUpdatedAt(v time.Time) *SubscriptionCostBasisPinCreate { + _c.mutation.SetUpdatedAt(v) + return _c +} + +// SetNillableUpdatedAt sets the "updated_at" field if the given value is not nil. +func (_c *SubscriptionCostBasisPinCreate) SetNillableUpdatedAt(v *time.Time) *SubscriptionCostBasisPinCreate { + if v != nil { + _c.SetUpdatedAt(*v) + } + return _c +} + +// SetDeletedAt sets the "deleted_at" field. +func (_c *SubscriptionCostBasisPinCreate) SetDeletedAt(v time.Time) *SubscriptionCostBasisPinCreate { + _c.mutation.SetDeletedAt(v) + return _c +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (_c *SubscriptionCostBasisPinCreate) SetNillableDeletedAt(v *time.Time) *SubscriptionCostBasisPinCreate { + if v != nil { + _c.SetDeletedAt(*v) + } + return _c +} + +// SetSubscriptionID sets the "subscription_id" field. +func (_c *SubscriptionCostBasisPinCreate) SetSubscriptionID(v string) *SubscriptionCostBasisPinCreate { + _c.mutation.SetSubscriptionID(v) + return _c +} + +// SetCustomCurrencyID sets the "custom_currency_id" field. +func (_c *SubscriptionCostBasisPinCreate) SetCustomCurrencyID(v string) *SubscriptionCostBasisPinCreate { + _c.mutation.SetCustomCurrencyID(v) + return _c +} + +// SetInvoiceCurrency sets the "invoice_currency" field. +func (_c *SubscriptionCostBasisPinCreate) SetInvoiceCurrency(v currencyx.Code) *SubscriptionCostBasisPinCreate { + _c.mutation.SetInvoiceCurrency(v) + return _c +} + +// SetCostBasisID sets the "cost_basis_id" field. +func (_c *SubscriptionCostBasisPinCreate) SetCostBasisID(v string) *SubscriptionCostBasisPinCreate { + _c.mutation.SetCostBasisID(v) + return _c +} + +// SetID sets the "id" field. +func (_c *SubscriptionCostBasisPinCreate) SetID(v string) *SubscriptionCostBasisPinCreate { + _c.mutation.SetID(v) + return _c +} + +// SetNillableID sets the "id" field if the given value is not nil. +func (_c *SubscriptionCostBasisPinCreate) SetNillableID(v *string) *SubscriptionCostBasisPinCreate { + if v != nil { + _c.SetID(*v) + } + return _c +} + +// SetSubscription sets the "subscription" edge to the Subscription entity. +func (_c *SubscriptionCostBasisPinCreate) SetSubscription(v *Subscription) *SubscriptionCostBasisPinCreate { + return _c.SetSubscriptionID(v.ID) +} + +// SetCustomCurrency sets the "custom_currency" edge to the CustomCurrency entity. +func (_c *SubscriptionCostBasisPinCreate) SetCustomCurrency(v *CustomCurrency) *SubscriptionCostBasisPinCreate { + return _c.SetCustomCurrencyID(v.ID) +} + +// SetCostBasis sets the "cost_basis" edge to the CurrencyCostBasis entity. +func (_c *SubscriptionCostBasisPinCreate) SetCostBasis(v *CurrencyCostBasis) *SubscriptionCostBasisPinCreate { + return _c.SetCostBasisID(v.ID) +} + +// Mutation returns the SubscriptionCostBasisPinMutation object of the builder. +func (_c *SubscriptionCostBasisPinCreate) Mutation() *SubscriptionCostBasisPinMutation { + return _c.mutation +} + +// Save creates the SubscriptionCostBasisPin in the database. +func (_c *SubscriptionCostBasisPinCreate) Save(ctx context.Context) (*SubscriptionCostBasisPin, error) { + _c.defaults() + return withHooks(ctx, _c.sqlSave, _c.mutation, _c.hooks) +} + +// SaveX calls Save and panics if Save returns an error. +func (_c *SubscriptionCostBasisPinCreate) SaveX(ctx context.Context) *SubscriptionCostBasisPin { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SubscriptionCostBasisPinCreate) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SubscriptionCostBasisPinCreate) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_c *SubscriptionCostBasisPinCreate) defaults() { + if _, ok := _c.mutation.CreatedAt(); !ok { + v := subscriptioncostbasispin.DefaultCreatedAt() + _c.mutation.SetCreatedAt(v) + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + v := subscriptioncostbasispin.DefaultUpdatedAt() + _c.mutation.SetUpdatedAt(v) + } + if _, ok := _c.mutation.ID(); !ok { + v := subscriptioncostbasispin.DefaultID() + _c.mutation.SetID(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_c *SubscriptionCostBasisPinCreate) check() error { + if _, ok := _c.mutation.Namespace(); !ok { + return &ValidationError{Name: "namespace", err: errors.New(`db: missing required field "SubscriptionCostBasisPin.namespace"`)} + } + if v, ok := _c.mutation.Namespace(); ok { + if err := subscriptioncostbasispin.NamespaceValidator(v); err != nil { + return &ValidationError{Name: "namespace", err: fmt.Errorf(`db: validator failed for field "SubscriptionCostBasisPin.namespace": %w`, err)} + } + } + if _, ok := _c.mutation.CreatedAt(); !ok { + return &ValidationError{Name: "created_at", err: errors.New(`db: missing required field "SubscriptionCostBasisPin.created_at"`)} + } + if _, ok := _c.mutation.UpdatedAt(); !ok { + return &ValidationError{Name: "updated_at", err: errors.New(`db: missing required field "SubscriptionCostBasisPin.updated_at"`)} + } + if _, ok := _c.mutation.SubscriptionID(); !ok { + return &ValidationError{Name: "subscription_id", err: errors.New(`db: missing required field "SubscriptionCostBasisPin.subscription_id"`)} + } + if v, ok := _c.mutation.SubscriptionID(); ok { + if err := subscriptioncostbasispin.SubscriptionIDValidator(v); err != nil { + return &ValidationError{Name: "subscription_id", err: fmt.Errorf(`db: validator failed for field "SubscriptionCostBasisPin.subscription_id": %w`, err)} + } + } + if _, ok := _c.mutation.CustomCurrencyID(); !ok { + return &ValidationError{Name: "custom_currency_id", err: errors.New(`db: missing required field "SubscriptionCostBasisPin.custom_currency_id"`)} + } + if v, ok := _c.mutation.CustomCurrencyID(); ok { + if err := subscriptioncostbasispin.CustomCurrencyIDValidator(v); err != nil { + return &ValidationError{Name: "custom_currency_id", err: fmt.Errorf(`db: validator failed for field "SubscriptionCostBasisPin.custom_currency_id": %w`, err)} + } + } + if _, ok := _c.mutation.InvoiceCurrency(); !ok { + return &ValidationError{Name: "invoice_currency", err: errors.New(`db: missing required field "SubscriptionCostBasisPin.invoice_currency"`)} + } + if v, ok := _c.mutation.InvoiceCurrency(); ok { + if err := subscriptioncostbasispin.InvoiceCurrencyValidator(string(v)); err != nil { + return &ValidationError{Name: "invoice_currency", err: fmt.Errorf(`db: validator failed for field "SubscriptionCostBasisPin.invoice_currency": %w`, err)} + } + } + if _, ok := _c.mutation.CostBasisID(); !ok { + return &ValidationError{Name: "cost_basis_id", err: errors.New(`db: missing required field "SubscriptionCostBasisPin.cost_basis_id"`)} + } + if v, ok := _c.mutation.CostBasisID(); ok { + if err := subscriptioncostbasispin.CostBasisIDValidator(v); err != nil { + return &ValidationError{Name: "cost_basis_id", err: fmt.Errorf(`db: validator failed for field "SubscriptionCostBasisPin.cost_basis_id": %w`, err)} + } + } + if len(_c.mutation.SubscriptionIDs()) == 0 { + return &ValidationError{Name: "subscription", err: errors.New(`db: missing required edge "SubscriptionCostBasisPin.subscription"`)} + } + if len(_c.mutation.CustomCurrencyIDs()) == 0 { + return &ValidationError{Name: "custom_currency", err: errors.New(`db: missing required edge "SubscriptionCostBasisPin.custom_currency"`)} + } + if len(_c.mutation.CostBasisIDs()) == 0 { + return &ValidationError{Name: "cost_basis", err: errors.New(`db: missing required edge "SubscriptionCostBasisPin.cost_basis"`)} + } + return nil +} + +func (_c *SubscriptionCostBasisPinCreate) sqlSave(ctx context.Context) (*SubscriptionCostBasisPin, error) { + if err := _c.check(); err != nil { + return nil, err + } + _node, _spec := _c.createSpec() + if err := sqlgraph.CreateNode(ctx, _c.driver, _spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + if _spec.ID.Value != nil { + if id, ok := _spec.ID.Value.(string); ok { + _node.ID = id + } else { + return nil, fmt.Errorf("unexpected SubscriptionCostBasisPin.ID type: %T", _spec.ID.Value) + } + } + _c.mutation.id = &_node.ID + _c.mutation.done = true + return _node, nil +} + +func (_c *SubscriptionCostBasisPinCreate) createSpec() (*SubscriptionCostBasisPin, *sqlgraph.CreateSpec) { + var ( + _node = &SubscriptionCostBasisPin{config: _c.config} + _spec = sqlgraph.NewCreateSpec(subscriptioncostbasispin.Table, sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString)) + ) + _spec.OnConflict = _c.conflict + if id, ok := _c.mutation.ID(); ok { + _node.ID = id + _spec.ID.Value = id + } + if value, ok := _c.mutation.Namespace(); ok { + _spec.SetField(subscriptioncostbasispin.FieldNamespace, field.TypeString, value) + _node.Namespace = value + } + if value, ok := _c.mutation.CreatedAt(); ok { + _spec.SetField(subscriptioncostbasispin.FieldCreatedAt, field.TypeTime, value) + _node.CreatedAt = value + } + if value, ok := _c.mutation.UpdatedAt(); ok { + _spec.SetField(subscriptioncostbasispin.FieldUpdatedAt, field.TypeTime, value) + _node.UpdatedAt = value + } + if value, ok := _c.mutation.DeletedAt(); ok { + _spec.SetField(subscriptioncostbasispin.FieldDeletedAt, field.TypeTime, value) + _node.DeletedAt = &value + } + if value, ok := _c.mutation.InvoiceCurrency(); ok { + _spec.SetField(subscriptioncostbasispin.FieldInvoiceCurrency, field.TypeString, value) + _node.InvoiceCurrency = value + } + if nodes := _c.mutation.SubscriptionIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: subscriptioncostbasispin.SubscriptionTable, + Columns: []string{subscriptioncostbasispin.SubscriptionColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(subscription.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.SubscriptionID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.CustomCurrencyIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: subscriptioncostbasispin.CustomCurrencyTable, + Columns: []string{subscriptioncostbasispin.CustomCurrencyColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(customcurrency.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.CustomCurrencyID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + if nodes := _c.mutation.CostBasisIDs(); len(nodes) > 0 { + edge := &sqlgraph.EdgeSpec{ + Rel: sqlgraph.M2O, + Inverse: true, + Table: subscriptioncostbasispin.CostBasisTable, + Columns: []string{subscriptioncostbasispin.CostBasisColumn}, + Bidi: false, + Target: &sqlgraph.EdgeTarget{ + IDSpec: sqlgraph.NewFieldSpec(currencycostbasis.FieldID, field.TypeString), + }, + } + for _, k := range nodes { + edge.Target.Nodes = append(edge.Target.Nodes, k) + } + _node.CostBasisID = nodes[0] + _spec.Edges = append(_spec.Edges, edge) + } + return _node, _spec +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.SubscriptionCostBasisPin.Create(). +// SetNamespace(v). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.SubscriptionCostBasisPinUpsert) { +// SetNamespace(v+v). +// }). +// Exec(ctx) +func (_c *SubscriptionCostBasisPinCreate) OnConflict(opts ...sql.ConflictOption) *SubscriptionCostBasisPinUpsertOne { + _c.conflict = opts + return &SubscriptionCostBasisPinUpsertOne{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.SubscriptionCostBasisPin.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *SubscriptionCostBasisPinCreate) OnConflictColumns(columns ...string) *SubscriptionCostBasisPinUpsertOne { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &SubscriptionCostBasisPinUpsertOne{ + create: _c, + } +} + +type ( + // SubscriptionCostBasisPinUpsertOne is the builder for "upsert"-ing + // one SubscriptionCostBasisPin node. + SubscriptionCostBasisPinUpsertOne struct { + create *SubscriptionCostBasisPinCreate + } + + // SubscriptionCostBasisPinUpsert is the "OnConflict" setter. + SubscriptionCostBasisPinUpsert struct { + *sql.UpdateSet + } +) + +// SetUpdatedAt sets the "updated_at" field. +func (u *SubscriptionCostBasisPinUpsert) SetUpdatedAt(v time.Time) *SubscriptionCostBasisPinUpsert { + u.Set(subscriptioncostbasispin.FieldUpdatedAt, v) + return u +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *SubscriptionCostBasisPinUpsert) UpdateUpdatedAt() *SubscriptionCostBasisPinUpsert { + u.SetExcluded(subscriptioncostbasispin.FieldUpdatedAt) + return u +} + +// SetDeletedAt sets the "deleted_at" field. +func (u *SubscriptionCostBasisPinUpsert) SetDeletedAt(v time.Time) *SubscriptionCostBasisPinUpsert { + u.Set(subscriptioncostbasispin.FieldDeletedAt, v) + return u +} + +// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create. +func (u *SubscriptionCostBasisPinUpsert) UpdateDeletedAt() *SubscriptionCostBasisPinUpsert { + u.SetExcluded(subscriptioncostbasispin.FieldDeletedAt) + return u +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (u *SubscriptionCostBasisPinUpsert) ClearDeletedAt() *SubscriptionCostBasisPinUpsert { + u.SetNull(subscriptioncostbasispin.FieldDeletedAt) + return u +} + +// UpdateNewValues updates the mutable fields using the new values that were set on create except the ID field. +// Using this option is equivalent to using: +// +// client.SubscriptionCostBasisPin.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// sql.ResolveWith(func(u *sql.UpdateSet) { +// u.SetIgnore(subscriptioncostbasispin.FieldID) +// }), +// ). +// Exec(ctx) +func (u *SubscriptionCostBasisPinUpsertOne) UpdateNewValues() *SubscriptionCostBasisPinUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + if _, exists := u.create.mutation.ID(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldID) + } + if _, exists := u.create.mutation.Namespace(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldNamespace) + } + if _, exists := u.create.mutation.CreatedAt(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldCreatedAt) + } + if _, exists := u.create.mutation.SubscriptionID(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldSubscriptionID) + } + if _, exists := u.create.mutation.CustomCurrencyID(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldCustomCurrencyID) + } + if _, exists := u.create.mutation.InvoiceCurrency(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldInvoiceCurrency) + } + if _, exists := u.create.mutation.CostBasisID(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldCostBasisID) + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.SubscriptionCostBasisPin.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *SubscriptionCostBasisPinUpsertOne) Ignore() *SubscriptionCostBasisPinUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *SubscriptionCostBasisPinUpsertOne) DoNothing() *SubscriptionCostBasisPinUpsertOne { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the SubscriptionCostBasisPinCreate.OnConflict +// documentation for more info. +func (u *SubscriptionCostBasisPinUpsertOne) Update(set func(*SubscriptionCostBasisPinUpsert)) *SubscriptionCostBasisPinUpsertOne { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&SubscriptionCostBasisPinUpsert{UpdateSet: update}) + })) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *SubscriptionCostBasisPinUpsertOne) SetUpdatedAt(v time.Time) *SubscriptionCostBasisPinUpsertOne { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *SubscriptionCostBasisPinUpsertOne) UpdateUpdatedAt() *SubscriptionCostBasisPinUpsertOne { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.UpdateUpdatedAt() + }) +} + +// SetDeletedAt sets the "deleted_at" field. +func (u *SubscriptionCostBasisPinUpsertOne) SetDeletedAt(v time.Time) *SubscriptionCostBasisPinUpsertOne { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.SetDeletedAt(v) + }) +} + +// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create. +func (u *SubscriptionCostBasisPinUpsertOne) UpdateDeletedAt() *SubscriptionCostBasisPinUpsertOne { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.UpdateDeletedAt() + }) +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (u *SubscriptionCostBasisPinUpsertOne) ClearDeletedAt() *SubscriptionCostBasisPinUpsertOne { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.ClearDeletedAt() + }) +} + +// Exec executes the query. +func (u *SubscriptionCostBasisPinUpsertOne) Exec(ctx context.Context) error { + if len(u.create.conflict) == 0 { + return errors.New("db: missing options for SubscriptionCostBasisPinCreate.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *SubscriptionCostBasisPinUpsertOne) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} + +// Exec executes the UPSERT query and returns the inserted/updated ID. +func (u *SubscriptionCostBasisPinUpsertOne) ID(ctx context.Context) (id string, err error) { + if u.create.driver.Dialect() == dialect.MySQL { + // In case of "ON CONFLICT", there is no way to get back non-numeric ID + // fields from the database since MySQL does not support the RETURNING clause. + return id, errors.New("db: SubscriptionCostBasisPinUpsertOne.ID is not supported by MySQL driver. Use SubscriptionCostBasisPinUpsertOne.Exec instead") + } + node, err := u.create.Save(ctx) + if err != nil { + return id, err + } + return node.ID, nil +} + +// IDX is like ID, but panics if an error occurs. +func (u *SubscriptionCostBasisPinUpsertOne) IDX(ctx context.Context) string { + id, err := u.ID(ctx) + if err != nil { + panic(err) + } + return id +} + +// SubscriptionCostBasisPinCreateBulk is the builder for creating many SubscriptionCostBasisPin entities in bulk. +type SubscriptionCostBasisPinCreateBulk struct { + config + err error + builders []*SubscriptionCostBasisPinCreate + conflict []sql.ConflictOption +} + +// Save creates the SubscriptionCostBasisPin entities in the database. +func (_c *SubscriptionCostBasisPinCreateBulk) Save(ctx context.Context) ([]*SubscriptionCostBasisPin, error) { + if _c.err != nil { + return nil, _c.err + } + specs := make([]*sqlgraph.CreateSpec, len(_c.builders)) + nodes := make([]*SubscriptionCostBasisPin, len(_c.builders)) + mutators := make([]Mutator, len(_c.builders)) + for i := range _c.builders { + func(i int, root context.Context) { + builder := _c.builders[i] + builder.defaults() + var mut Mutator = MutateFunc(func(ctx context.Context, m Mutation) (Value, error) { + mutation, ok := m.(*SubscriptionCostBasisPinMutation) + if !ok { + return nil, fmt.Errorf("unexpected mutation type %T", m) + } + if err := builder.check(); err != nil { + return nil, err + } + builder.mutation = mutation + var err error + nodes[i], specs[i] = builder.createSpec() + if i < len(mutators)-1 { + _, err = mutators[i+1].Mutate(root, _c.builders[i+1].mutation) + } else { + spec := &sqlgraph.BatchCreateSpec{Nodes: specs} + spec.OnConflict = _c.conflict + // Invoke the actual operation on the latest mutation in the chain. + if err = sqlgraph.BatchCreate(ctx, _c.driver, spec); err != nil { + if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + } + } + if err != nil { + return nil, err + } + mutation.id = &nodes[i].ID + mutation.done = true + return nodes[i], nil + }) + for i := len(builder.hooks) - 1; i >= 0; i-- { + mut = builder.hooks[i](mut) + } + mutators[i] = mut + }(i, ctx) + } + if len(mutators) > 0 { + if _, err := mutators[0].Mutate(ctx, _c.builders[0].mutation); err != nil { + return nil, err + } + } + return nodes, nil +} + +// SaveX is like Save, but panics if an error occurs. +func (_c *SubscriptionCostBasisPinCreateBulk) SaveX(ctx context.Context) []*SubscriptionCostBasisPin { + v, err := _c.Save(ctx) + if err != nil { + panic(err) + } + return v +} + +// Exec executes the query. +func (_c *SubscriptionCostBasisPinCreateBulk) Exec(ctx context.Context) error { + _, err := _c.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_c *SubscriptionCostBasisPinCreateBulk) ExecX(ctx context.Context) { + if err := _c.Exec(ctx); err != nil { + panic(err) + } +} + +// OnConflict allows configuring the `ON CONFLICT` / `ON DUPLICATE KEY` clause +// of the `INSERT` statement. For example: +// +// client.SubscriptionCostBasisPin.CreateBulk(builders...). +// OnConflict( +// // Update the row with the new values +// // the was proposed for insertion. +// sql.ResolveWithNewValues(), +// ). +// // Override some of the fields with custom +// // update values. +// Update(func(u *ent.SubscriptionCostBasisPinUpsert) { +// SetNamespace(v+v). +// }). +// Exec(ctx) +func (_c *SubscriptionCostBasisPinCreateBulk) OnConflict(opts ...sql.ConflictOption) *SubscriptionCostBasisPinUpsertBulk { + _c.conflict = opts + return &SubscriptionCostBasisPinUpsertBulk{ + create: _c, + } +} + +// OnConflictColumns calls `OnConflict` and configures the columns +// as conflict target. Using this option is equivalent to using: +// +// client.SubscriptionCostBasisPin.Create(). +// OnConflict(sql.ConflictColumns(columns...)). +// Exec(ctx) +func (_c *SubscriptionCostBasisPinCreateBulk) OnConflictColumns(columns ...string) *SubscriptionCostBasisPinUpsertBulk { + _c.conflict = append(_c.conflict, sql.ConflictColumns(columns...)) + return &SubscriptionCostBasisPinUpsertBulk{ + create: _c, + } +} + +// SubscriptionCostBasisPinUpsertBulk is the builder for "upsert"-ing +// a bulk of SubscriptionCostBasisPin nodes. +type SubscriptionCostBasisPinUpsertBulk struct { + create *SubscriptionCostBasisPinCreateBulk +} + +// UpdateNewValues updates the mutable fields using the new values that +// were set on create. Using this option is equivalent to using: +// +// client.SubscriptionCostBasisPin.Create(). +// OnConflict( +// sql.ResolveWithNewValues(), +// sql.ResolveWith(func(u *sql.UpdateSet) { +// u.SetIgnore(subscriptioncostbasispin.FieldID) +// }), +// ). +// Exec(ctx) +func (u *SubscriptionCostBasisPinUpsertBulk) UpdateNewValues() *SubscriptionCostBasisPinUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithNewValues()) + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(s *sql.UpdateSet) { + for _, b := range u.create.builders { + if _, exists := b.mutation.ID(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldID) + } + if _, exists := b.mutation.Namespace(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldNamespace) + } + if _, exists := b.mutation.CreatedAt(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldCreatedAt) + } + if _, exists := b.mutation.SubscriptionID(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldSubscriptionID) + } + if _, exists := b.mutation.CustomCurrencyID(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldCustomCurrencyID) + } + if _, exists := b.mutation.InvoiceCurrency(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldInvoiceCurrency) + } + if _, exists := b.mutation.CostBasisID(); exists { + s.SetIgnore(subscriptioncostbasispin.FieldCostBasisID) + } + } + })) + return u +} + +// Ignore sets each column to itself in case of conflict. +// Using this option is equivalent to using: +// +// client.SubscriptionCostBasisPin.Create(). +// OnConflict(sql.ResolveWithIgnore()). +// Exec(ctx) +func (u *SubscriptionCostBasisPinUpsertBulk) Ignore() *SubscriptionCostBasisPinUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWithIgnore()) + return u +} + +// DoNothing configures the conflict_action to `DO NOTHING`. +// Supported only by SQLite and PostgreSQL. +func (u *SubscriptionCostBasisPinUpsertBulk) DoNothing() *SubscriptionCostBasisPinUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.DoNothing()) + return u +} + +// Update allows overriding fields `UPDATE` values. See the SubscriptionCostBasisPinCreateBulk.OnConflict +// documentation for more info. +func (u *SubscriptionCostBasisPinUpsertBulk) Update(set func(*SubscriptionCostBasisPinUpsert)) *SubscriptionCostBasisPinUpsertBulk { + u.create.conflict = append(u.create.conflict, sql.ResolveWith(func(update *sql.UpdateSet) { + set(&SubscriptionCostBasisPinUpsert{UpdateSet: update}) + })) + return u +} + +// SetUpdatedAt sets the "updated_at" field. +func (u *SubscriptionCostBasisPinUpsertBulk) SetUpdatedAt(v time.Time) *SubscriptionCostBasisPinUpsertBulk { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.SetUpdatedAt(v) + }) +} + +// UpdateUpdatedAt sets the "updated_at" field to the value that was provided on create. +func (u *SubscriptionCostBasisPinUpsertBulk) UpdateUpdatedAt() *SubscriptionCostBasisPinUpsertBulk { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.UpdateUpdatedAt() + }) +} + +// SetDeletedAt sets the "deleted_at" field. +func (u *SubscriptionCostBasisPinUpsertBulk) SetDeletedAt(v time.Time) *SubscriptionCostBasisPinUpsertBulk { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.SetDeletedAt(v) + }) +} + +// UpdateDeletedAt sets the "deleted_at" field to the value that was provided on create. +func (u *SubscriptionCostBasisPinUpsertBulk) UpdateDeletedAt() *SubscriptionCostBasisPinUpsertBulk { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.UpdateDeletedAt() + }) +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (u *SubscriptionCostBasisPinUpsertBulk) ClearDeletedAt() *SubscriptionCostBasisPinUpsertBulk { + return u.Update(func(s *SubscriptionCostBasisPinUpsert) { + s.ClearDeletedAt() + }) +} + +// Exec executes the query. +func (u *SubscriptionCostBasisPinUpsertBulk) Exec(ctx context.Context) error { + if u.create.err != nil { + return u.create.err + } + for i, b := range u.create.builders { + if len(b.conflict) != 0 { + return fmt.Errorf("db: OnConflict was set for builder %d. Set it on the SubscriptionCostBasisPinCreateBulk instead", i) + } + } + if len(u.create.conflict) == 0 { + return errors.New("db: missing options for SubscriptionCostBasisPinCreateBulk.OnConflict") + } + return u.create.Exec(ctx) +} + +// ExecX is like Exec, but panics if an error occurs. +func (u *SubscriptionCostBasisPinUpsertBulk) ExecX(ctx context.Context) { + if err := u.create.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/openmeter/ent/db/subscriptioncostbasispin_delete.go b/openmeter/ent/db/subscriptioncostbasispin_delete.go new file mode 100644 index 0000000000..7c228e5225 --- /dev/null +++ b/openmeter/ent/db/subscriptioncostbasispin_delete.go @@ -0,0 +1,88 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/openmeterio/openmeter/openmeter/ent/db/predicate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" +) + +// SubscriptionCostBasisPinDelete is the builder for deleting a SubscriptionCostBasisPin entity. +type SubscriptionCostBasisPinDelete struct { + config + hooks []Hook + mutation *SubscriptionCostBasisPinMutation +} + +// Where appends a list predicates to the SubscriptionCostBasisPinDelete builder. +func (_d *SubscriptionCostBasisPinDelete) Where(ps ...predicate.SubscriptionCostBasisPin) *SubscriptionCostBasisPinDelete { + _d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query and returns how many vertices were deleted. +func (_d *SubscriptionCostBasisPinDelete) Exec(ctx context.Context) (int, error) { + return withHooks(ctx, _d.sqlExec, _d.mutation, _d.hooks) +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SubscriptionCostBasisPinDelete) ExecX(ctx context.Context) int { + n, err := _d.Exec(ctx) + if err != nil { + panic(err) + } + return n +} + +func (_d *SubscriptionCostBasisPinDelete) sqlExec(ctx context.Context) (int, error) { + _spec := sqlgraph.NewDeleteSpec(subscriptioncostbasispin.Table, sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString)) + if ps := _d.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + affected, err := sqlgraph.DeleteNodes(ctx, _d.driver, _spec) + if err != nil && sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + _d.mutation.done = true + return affected, err +} + +// SubscriptionCostBasisPinDeleteOne is the builder for deleting a single SubscriptionCostBasisPin entity. +type SubscriptionCostBasisPinDeleteOne struct { + _d *SubscriptionCostBasisPinDelete +} + +// Where appends a list predicates to the SubscriptionCostBasisPinDelete builder. +func (_d *SubscriptionCostBasisPinDeleteOne) Where(ps ...predicate.SubscriptionCostBasisPin) *SubscriptionCostBasisPinDeleteOne { + _d._d.mutation.Where(ps...) + return _d +} + +// Exec executes the deletion query. +func (_d *SubscriptionCostBasisPinDeleteOne) Exec(ctx context.Context) error { + n, err := _d._d.Exec(ctx) + switch { + case err != nil: + return err + case n == 0: + return &NotFoundError{subscriptioncostbasispin.Label} + default: + return nil + } +} + +// ExecX is like Exec, but panics if an error occurs. +func (_d *SubscriptionCostBasisPinDeleteOne) ExecX(ctx context.Context) { + if err := _d.Exec(ctx); err != nil { + panic(err) + } +} diff --git a/openmeter/ent/db/subscriptioncostbasispin_query.go b/openmeter/ent/db/subscriptioncostbasispin_query.go new file mode 100644 index 0000000000..cb36ea7887 --- /dev/null +++ b/openmeter/ent/db/subscriptioncostbasispin_query.go @@ -0,0 +1,793 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "fmt" + "math" + + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/openmeterio/openmeter/openmeter/ent/db/currencycostbasis" + "github.com/openmeterio/openmeter/openmeter/ent/db/customcurrency" + "github.com/openmeterio/openmeter/openmeter/ent/db/predicate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscription" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" +) + +// SubscriptionCostBasisPinQuery is the builder for querying SubscriptionCostBasisPin entities. +type SubscriptionCostBasisPinQuery struct { + config + ctx *QueryContext + order []subscriptioncostbasispin.OrderOption + inters []Interceptor + predicates []predicate.SubscriptionCostBasisPin + withSubscription *SubscriptionQuery + withCustomCurrency *CustomCurrencyQuery + withCostBasis *CurrencyCostBasisQuery + modifiers []func(*sql.Selector) + // intermediate query (i.e. traversal path). + sql *sql.Selector + path func(context.Context) (*sql.Selector, error) +} + +// Where adds a new predicate for the SubscriptionCostBasisPinQuery builder. +func (_q *SubscriptionCostBasisPinQuery) Where(ps ...predicate.SubscriptionCostBasisPin) *SubscriptionCostBasisPinQuery { + _q.predicates = append(_q.predicates, ps...) + return _q +} + +// Limit the number of records to be returned by this query. +func (_q *SubscriptionCostBasisPinQuery) Limit(limit int) *SubscriptionCostBasisPinQuery { + _q.ctx.Limit = &limit + return _q +} + +// Offset to start from. +func (_q *SubscriptionCostBasisPinQuery) Offset(offset int) *SubscriptionCostBasisPinQuery { + _q.ctx.Offset = &offset + return _q +} + +// Unique configures the query builder to filter duplicate records on query. +// By default, unique is set to true, and can be disabled using this method. +func (_q *SubscriptionCostBasisPinQuery) Unique(unique bool) *SubscriptionCostBasisPinQuery { + _q.ctx.Unique = &unique + return _q +} + +// Order specifies how the records should be ordered. +func (_q *SubscriptionCostBasisPinQuery) Order(o ...subscriptioncostbasispin.OrderOption) *SubscriptionCostBasisPinQuery { + _q.order = append(_q.order, o...) + return _q +} + +// QuerySubscription chains the current query on the "subscription" edge. +func (_q *SubscriptionCostBasisPinQuery) QuerySubscription() *SubscriptionQuery { + query := (&SubscriptionClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID, selector), + sqlgraph.To(subscription.Table, subscription.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, subscriptioncostbasispin.SubscriptionTable, subscriptioncostbasispin.SubscriptionColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryCustomCurrency chains the current query on the "custom_currency" edge. +func (_q *SubscriptionCostBasisPinQuery) QueryCustomCurrency() *CustomCurrencyQuery { + query := (&CustomCurrencyClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID, selector), + sqlgraph.To(customcurrency.Table, customcurrency.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, subscriptioncostbasispin.CustomCurrencyTable, subscriptioncostbasispin.CustomCurrencyColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// QueryCostBasis chains the current query on the "cost_basis" edge. +func (_q *SubscriptionCostBasisPinQuery) QueryCostBasis() *CurrencyCostBasisQuery { + query := (&CurrencyCostBasisClient{config: _q.config}).Query() + query.path = func(ctx context.Context) (fromU *sql.Selector, err error) { + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + selector := _q.sqlQuery(ctx) + if err := selector.Err(); err != nil { + return nil, err + } + step := sqlgraph.NewStep( + sqlgraph.From(subscriptioncostbasispin.Table, subscriptioncostbasispin.FieldID, selector), + sqlgraph.To(currencycostbasis.Table, currencycostbasis.FieldID), + sqlgraph.Edge(sqlgraph.M2O, true, subscriptioncostbasispin.CostBasisTable, subscriptioncostbasispin.CostBasisColumn), + ) + fromU = sqlgraph.SetNeighbors(_q.driver.Dialect(), step) + return fromU, nil + } + return query +} + +// First returns the first SubscriptionCostBasisPin entity from the query. +// Returns a *NotFoundError when no SubscriptionCostBasisPin was found. +func (_q *SubscriptionCostBasisPinQuery) First(ctx context.Context) (*SubscriptionCostBasisPin, error) { + nodes, err := _q.Limit(1).All(setContextOp(ctx, _q.ctx, ent.OpQueryFirst)) + if err != nil { + return nil, err + } + if len(nodes) == 0 { + return nil, &NotFoundError{subscriptioncostbasispin.Label} + } + return nodes[0], nil +} + +// FirstX is like First, but panics if an error occurs. +func (_q *SubscriptionCostBasisPinQuery) FirstX(ctx context.Context) *SubscriptionCostBasisPin { + node, err := _q.First(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return node +} + +// FirstID returns the first SubscriptionCostBasisPin ID from the query. +// Returns a *NotFoundError when no SubscriptionCostBasisPin ID was found. +func (_q *SubscriptionCostBasisPinQuery) FirstID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = _q.Limit(1).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryFirstID)); err != nil { + return + } + if len(ids) == 0 { + err = &NotFoundError{subscriptioncostbasispin.Label} + return + } + return ids[0], nil +} + +// FirstIDX is like FirstID, but panics if an error occurs. +func (_q *SubscriptionCostBasisPinQuery) FirstIDX(ctx context.Context) string { + id, err := _q.FirstID(ctx) + if err != nil && !IsNotFound(err) { + panic(err) + } + return id +} + +// Only returns a single SubscriptionCostBasisPin entity found by the query, ensuring it only returns one. +// Returns a *NotSingularError when more than one SubscriptionCostBasisPin entity is found. +// Returns a *NotFoundError when no SubscriptionCostBasisPin entities are found. +func (_q *SubscriptionCostBasisPinQuery) Only(ctx context.Context) (*SubscriptionCostBasisPin, error) { + nodes, err := _q.Limit(2).All(setContextOp(ctx, _q.ctx, ent.OpQueryOnly)) + if err != nil { + return nil, err + } + switch len(nodes) { + case 1: + return nodes[0], nil + case 0: + return nil, &NotFoundError{subscriptioncostbasispin.Label} + default: + return nil, &NotSingularError{subscriptioncostbasispin.Label} + } +} + +// OnlyX is like Only, but panics if an error occurs. +func (_q *SubscriptionCostBasisPinQuery) OnlyX(ctx context.Context) *SubscriptionCostBasisPin { + node, err := _q.Only(ctx) + if err != nil { + panic(err) + } + return node +} + +// OnlyID is like Only, but returns the only SubscriptionCostBasisPin ID in the query. +// Returns a *NotSingularError when more than one SubscriptionCostBasisPin ID is found. +// Returns a *NotFoundError when no entities are found. +func (_q *SubscriptionCostBasisPinQuery) OnlyID(ctx context.Context) (id string, err error) { + var ids []string + if ids, err = _q.Limit(2).IDs(setContextOp(ctx, _q.ctx, ent.OpQueryOnlyID)); err != nil { + return + } + switch len(ids) { + case 1: + id = ids[0] + case 0: + err = &NotFoundError{subscriptioncostbasispin.Label} + default: + err = &NotSingularError{subscriptioncostbasispin.Label} + } + return +} + +// OnlyIDX is like OnlyID, but panics if an error occurs. +func (_q *SubscriptionCostBasisPinQuery) OnlyIDX(ctx context.Context) string { + id, err := _q.OnlyID(ctx) + if err != nil { + panic(err) + } + return id +} + +// All executes the query and returns a list of SubscriptionCostBasisPins. +func (_q *SubscriptionCostBasisPinQuery) All(ctx context.Context) ([]*SubscriptionCostBasisPin, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryAll) + if err := _q.prepareQuery(ctx); err != nil { + return nil, err + } + qr := querierAll[[]*SubscriptionCostBasisPin, *SubscriptionCostBasisPinQuery]() + return withInterceptors[[]*SubscriptionCostBasisPin](ctx, _q, qr, _q.inters) +} + +// AllX is like All, but panics if an error occurs. +func (_q *SubscriptionCostBasisPinQuery) AllX(ctx context.Context) []*SubscriptionCostBasisPin { + nodes, err := _q.All(ctx) + if err != nil { + panic(err) + } + return nodes +} + +// IDs executes the query and returns a list of SubscriptionCostBasisPin IDs. +func (_q *SubscriptionCostBasisPinQuery) IDs(ctx context.Context) (ids []string, err error) { + if _q.ctx.Unique == nil && _q.path != nil { + _q.Unique(true) + } + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryIDs) + if err = _q.Select(subscriptioncostbasispin.FieldID).Scan(ctx, &ids); err != nil { + return nil, err + } + return ids, nil +} + +// IDsX is like IDs, but panics if an error occurs. +func (_q *SubscriptionCostBasisPinQuery) IDsX(ctx context.Context) []string { + ids, err := _q.IDs(ctx) + if err != nil { + panic(err) + } + return ids +} + +// Count returns the count of the given query. +func (_q *SubscriptionCostBasisPinQuery) Count(ctx context.Context) (int, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryCount) + if err := _q.prepareQuery(ctx); err != nil { + return 0, err + } + return withInterceptors[int](ctx, _q, querierCount[*SubscriptionCostBasisPinQuery](), _q.inters) +} + +// CountX is like Count, but panics if an error occurs. +func (_q *SubscriptionCostBasisPinQuery) CountX(ctx context.Context) int { + count, err := _q.Count(ctx) + if err != nil { + panic(err) + } + return count +} + +// Exist returns true if the query has elements in the graph. +func (_q *SubscriptionCostBasisPinQuery) Exist(ctx context.Context) (bool, error) { + ctx = setContextOp(ctx, _q.ctx, ent.OpQueryExist) + switch _, err := _q.FirstID(ctx); { + case IsNotFound(err): + return false, nil + case err != nil: + return false, fmt.Errorf("db: check existence: %w", err) + default: + return true, nil + } +} + +// ExistX is like Exist, but panics if an error occurs. +func (_q *SubscriptionCostBasisPinQuery) ExistX(ctx context.Context) bool { + exist, err := _q.Exist(ctx) + if err != nil { + panic(err) + } + return exist +} + +// Clone returns a duplicate of the SubscriptionCostBasisPinQuery builder, including all associated steps. It can be +// used to prepare common query builders and use them differently after the clone is made. +func (_q *SubscriptionCostBasisPinQuery) Clone() *SubscriptionCostBasisPinQuery { + if _q == nil { + return nil + } + return &SubscriptionCostBasisPinQuery{ + config: _q.config, + ctx: _q.ctx.Clone(), + order: append([]subscriptioncostbasispin.OrderOption{}, _q.order...), + inters: append([]Interceptor{}, _q.inters...), + predicates: append([]predicate.SubscriptionCostBasisPin{}, _q.predicates...), + withSubscription: _q.withSubscription.Clone(), + withCustomCurrency: _q.withCustomCurrency.Clone(), + withCostBasis: _q.withCostBasis.Clone(), + // clone intermediate query. + sql: _q.sql.Clone(), + path: _q.path, + } +} + +// WithSubscription tells the query-builder to eager-load the nodes that are connected to +// the "subscription" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SubscriptionCostBasisPinQuery) WithSubscription(opts ...func(*SubscriptionQuery)) *SubscriptionCostBasisPinQuery { + query := (&SubscriptionClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withSubscription = query + return _q +} + +// WithCustomCurrency tells the query-builder to eager-load the nodes that are connected to +// the "custom_currency" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SubscriptionCostBasisPinQuery) WithCustomCurrency(opts ...func(*CustomCurrencyQuery)) *SubscriptionCostBasisPinQuery { + query := (&CustomCurrencyClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withCustomCurrency = query + return _q +} + +// WithCostBasis tells the query-builder to eager-load the nodes that are connected to +// the "cost_basis" edge. The optional arguments are used to configure the query builder of the edge. +func (_q *SubscriptionCostBasisPinQuery) WithCostBasis(opts ...func(*CurrencyCostBasisQuery)) *SubscriptionCostBasisPinQuery { + query := (&CurrencyCostBasisClient{config: _q.config}).Query() + for _, opt := range opts { + opt(query) + } + _q.withCostBasis = query + return _q +} + +// GroupBy is used to group vertices by one or more fields/columns. +// It is often used with aggregate functions, like: count, max, mean, min, sum. +// +// Example: +// +// var v []struct { +// Namespace string `json:"namespace,omitempty"` +// Count int `json:"count,omitempty"` +// } +// +// client.SubscriptionCostBasisPin.Query(). +// GroupBy(subscriptioncostbasispin.FieldNamespace). +// Aggregate(db.Count()). +// Scan(ctx, &v) +func (_q *SubscriptionCostBasisPinQuery) GroupBy(field string, fields ...string) *SubscriptionCostBasisPinGroupBy { + _q.ctx.Fields = append([]string{field}, fields...) + grbuild := &SubscriptionCostBasisPinGroupBy{build: _q} + grbuild.flds = &_q.ctx.Fields + grbuild.label = subscriptioncostbasispin.Label + grbuild.scan = grbuild.Scan + return grbuild +} + +// Select allows the selection one or more fields/columns for the given query, +// instead of selecting all fields in the entity. +// +// Example: +// +// var v []struct { +// Namespace string `json:"namespace,omitempty"` +// } +// +// client.SubscriptionCostBasisPin.Query(). +// Select(subscriptioncostbasispin.FieldNamespace). +// Scan(ctx, &v) +func (_q *SubscriptionCostBasisPinQuery) Select(fields ...string) *SubscriptionCostBasisPinSelect { + _q.ctx.Fields = append(_q.ctx.Fields, fields...) + sbuild := &SubscriptionCostBasisPinSelect{SubscriptionCostBasisPinQuery: _q} + sbuild.label = subscriptioncostbasispin.Label + sbuild.flds, sbuild.scan = &_q.ctx.Fields, sbuild.Scan + return sbuild +} + +// Aggregate returns a SubscriptionCostBasisPinSelect configured with the given aggregations. +func (_q *SubscriptionCostBasisPinQuery) Aggregate(fns ...AggregateFunc) *SubscriptionCostBasisPinSelect { + return _q.Select().Aggregate(fns...) +} + +func (_q *SubscriptionCostBasisPinQuery) prepareQuery(ctx context.Context) error { + for _, inter := range _q.inters { + if inter == nil { + return fmt.Errorf("db: uninitialized interceptor (forgotten import db/runtime?)") + } + if trv, ok := inter.(Traverser); ok { + if err := trv.Traverse(ctx, _q); err != nil { + return err + } + } + } + for _, f := range _q.ctx.Fields { + if !subscriptioncostbasispin.ValidColumn(f) { + return &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + } + if _q.path != nil { + prev, err := _q.path(ctx) + if err != nil { + return err + } + _q.sql = prev + } + return nil +} + +func (_q *SubscriptionCostBasisPinQuery) sqlAll(ctx context.Context, hooks ...queryHook) ([]*SubscriptionCostBasisPin, error) { + var ( + nodes = []*SubscriptionCostBasisPin{} + _spec = _q.querySpec() + loadedTypes = [3]bool{ + _q.withSubscription != nil, + _q.withCustomCurrency != nil, + _q.withCostBasis != nil, + } + ) + _spec.ScanValues = func(columns []string) ([]any, error) { + return (*SubscriptionCostBasisPin).scanValues(nil, columns) + } + _spec.Assign = func(columns []string, values []any) error { + node := &SubscriptionCostBasisPin{config: _q.config} + nodes = append(nodes, node) + node.Edges.loadedTypes = loadedTypes + return node.assignValues(columns, values) + } + if len(_q.modifiers) > 0 { + _spec.Modifiers = _q.modifiers + } + for i := range hooks { + hooks[i](ctx, _spec) + } + if err := sqlgraph.QueryNodes(ctx, _q.driver, _spec); err != nil { + return nil, err + } + if len(nodes) == 0 { + return nodes, nil + } + if query := _q.withSubscription; query != nil { + if err := _q.loadSubscription(ctx, query, nodes, nil, + func(n *SubscriptionCostBasisPin, e *Subscription) { n.Edges.Subscription = e }); err != nil { + return nil, err + } + } + if query := _q.withCustomCurrency; query != nil { + if err := _q.loadCustomCurrency(ctx, query, nodes, nil, + func(n *SubscriptionCostBasisPin, e *CustomCurrency) { n.Edges.CustomCurrency = e }); err != nil { + return nil, err + } + } + if query := _q.withCostBasis; query != nil { + if err := _q.loadCostBasis(ctx, query, nodes, nil, + func(n *SubscriptionCostBasisPin, e *CurrencyCostBasis) { n.Edges.CostBasis = e }); err != nil { + return nil, err + } + } + return nodes, nil +} + +func (_q *SubscriptionCostBasisPinQuery) loadSubscription(ctx context.Context, query *SubscriptionQuery, nodes []*SubscriptionCostBasisPin, init func(*SubscriptionCostBasisPin), assign func(*SubscriptionCostBasisPin, *Subscription)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*SubscriptionCostBasisPin) + for i := range nodes { + fk := nodes[i].SubscriptionID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(subscription.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "subscription_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *SubscriptionCostBasisPinQuery) loadCustomCurrency(ctx context.Context, query *CustomCurrencyQuery, nodes []*SubscriptionCostBasisPin, init func(*SubscriptionCostBasisPin), assign func(*SubscriptionCostBasisPin, *CustomCurrency)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*SubscriptionCostBasisPin) + for i := range nodes { + fk := nodes[i].CustomCurrencyID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(customcurrency.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "custom_currency_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} +func (_q *SubscriptionCostBasisPinQuery) loadCostBasis(ctx context.Context, query *CurrencyCostBasisQuery, nodes []*SubscriptionCostBasisPin, init func(*SubscriptionCostBasisPin), assign func(*SubscriptionCostBasisPin, *CurrencyCostBasis)) error { + ids := make([]string, 0, len(nodes)) + nodeids := make(map[string][]*SubscriptionCostBasisPin) + for i := range nodes { + fk := nodes[i].CostBasisID + if _, ok := nodeids[fk]; !ok { + ids = append(ids, fk) + } + nodeids[fk] = append(nodeids[fk], nodes[i]) + } + if len(ids) == 0 { + return nil + } + query.Where(currencycostbasis.IDIn(ids...)) + neighbors, err := query.All(ctx) + if err != nil { + return err + } + for _, n := range neighbors { + nodes, ok := nodeids[n.ID] + if !ok { + return fmt.Errorf(`unexpected foreign-key "cost_basis_id" returned %v`, n.ID) + } + for i := range nodes { + assign(nodes[i], n) + } + } + return nil +} + +func (_q *SubscriptionCostBasisPinQuery) sqlCount(ctx context.Context) (int, error) { + _spec := _q.querySpec() + if len(_q.modifiers) > 0 { + _spec.Modifiers = _q.modifiers + } + _spec.Node.Columns = _q.ctx.Fields + if len(_q.ctx.Fields) > 0 { + _spec.Unique = _q.ctx.Unique != nil && *_q.ctx.Unique + } + return sqlgraph.CountNodes(ctx, _q.driver, _spec) +} + +func (_q *SubscriptionCostBasisPinQuery) querySpec() *sqlgraph.QuerySpec { + _spec := sqlgraph.NewQuerySpec(subscriptioncostbasispin.Table, subscriptioncostbasispin.Columns, sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString)) + _spec.From = _q.sql + if unique := _q.ctx.Unique; unique != nil { + _spec.Unique = *unique + } else if _q.path != nil { + _spec.Unique = true + } + if fields := _q.ctx.Fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, subscriptioncostbasispin.FieldID) + for i := range fields { + if fields[i] != subscriptioncostbasispin.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, fields[i]) + } + } + if _q.withSubscription != nil { + _spec.Node.AddColumnOnce(subscriptioncostbasispin.FieldSubscriptionID) + } + if _q.withCustomCurrency != nil { + _spec.Node.AddColumnOnce(subscriptioncostbasispin.FieldCustomCurrencyID) + } + if _q.withCostBasis != nil { + _spec.Node.AddColumnOnce(subscriptioncostbasispin.FieldCostBasisID) + } + } + if ps := _q.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if limit := _q.ctx.Limit; limit != nil { + _spec.Limit = *limit + } + if offset := _q.ctx.Offset; offset != nil { + _spec.Offset = *offset + } + if ps := _q.order; len(ps) > 0 { + _spec.Order = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + return _spec +} + +func (_q *SubscriptionCostBasisPinQuery) sqlQuery(ctx context.Context) *sql.Selector { + builder := sql.Dialect(_q.driver.Dialect()) + t1 := builder.Table(subscriptioncostbasispin.Table) + columns := _q.ctx.Fields + if len(columns) == 0 { + columns = subscriptioncostbasispin.Columns + } + selector := builder.Select(t1.Columns(columns...)...).From(t1) + if _q.sql != nil { + selector = _q.sql + selector.Select(selector.Columns(columns...)...) + } + if _q.ctx.Unique != nil && *_q.ctx.Unique { + selector.Distinct() + } + for _, m := range _q.modifiers { + m(selector) + } + for _, p := range _q.predicates { + p(selector) + } + for _, p := range _q.order { + p(selector) + } + if offset := _q.ctx.Offset; offset != nil { + // limit is mandatory for offset clause. We start + // with default value, and override it below if needed. + selector.Offset(*offset).Limit(math.MaxInt32) + } + if limit := _q.ctx.Limit; limit != nil { + selector.Limit(*limit) + } + return selector +} + +// ForUpdate locks the selected rows against concurrent updates, and prevent them from being +// updated, deleted or "selected ... for update" by other sessions, until the transaction is +// either committed or rolled-back. +func (_q *SubscriptionCostBasisPinQuery) ForUpdate(opts ...sql.LockOption) *SubscriptionCostBasisPinQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForUpdate(opts...) + }) + return _q +} + +// ForShare behaves similarly to ForUpdate, except that it acquires a shared mode lock +// on any rows that are read. Other sessions can read the rows, but cannot modify them +// until your transaction commits. +func (_q *SubscriptionCostBasisPinQuery) ForShare(opts ...sql.LockOption) *SubscriptionCostBasisPinQuery { + if _q.driver.Dialect() == dialect.Postgres { + _q.Unique(false) + } + _q.modifiers = append(_q.modifiers, func(s *sql.Selector) { + s.ForShare(opts...) + }) + return _q +} + +// SubscriptionCostBasisPinGroupBy is the group-by builder for SubscriptionCostBasisPin entities. +type SubscriptionCostBasisPinGroupBy struct { + selector + build *SubscriptionCostBasisPinQuery +} + +// Aggregate adds the given aggregation functions to the group-by query. +func (_g *SubscriptionCostBasisPinGroupBy) Aggregate(fns ...AggregateFunc) *SubscriptionCostBasisPinGroupBy { + _g.fns = append(_g.fns, fns...) + return _g +} + +// Scan applies the selector query and scans the result into the given value. +func (_g *SubscriptionCostBasisPinGroupBy) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _g.build.ctx, ent.OpQueryGroupBy) + if err := _g.build.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SubscriptionCostBasisPinQuery, *SubscriptionCostBasisPinGroupBy](ctx, _g.build, _g, _g.build.inters, v) +} + +func (_g *SubscriptionCostBasisPinGroupBy) sqlScan(ctx context.Context, root *SubscriptionCostBasisPinQuery, v any) error { + selector := root.sqlQuery(ctx).Select() + aggregation := make([]string, 0, len(_g.fns)) + for _, fn := range _g.fns { + aggregation = append(aggregation, fn(selector)) + } + if len(selector.SelectedColumns()) == 0 { + columns := make([]string, 0, len(*_g.flds)+len(_g.fns)) + for _, f := range *_g.flds { + columns = append(columns, selector.C(f)) + } + columns = append(columns, aggregation...) + selector.Select(columns...) + } + selector.GroupBy(selector.Columns(*_g.flds...)...) + if err := selector.Err(); err != nil { + return err + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _g.build.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} + +// SubscriptionCostBasisPinSelect is the builder for selecting fields of SubscriptionCostBasisPin entities. +type SubscriptionCostBasisPinSelect struct { + *SubscriptionCostBasisPinQuery + selector +} + +// Aggregate adds the given aggregation functions to the selector query. +func (_s *SubscriptionCostBasisPinSelect) Aggregate(fns ...AggregateFunc) *SubscriptionCostBasisPinSelect { + _s.fns = append(_s.fns, fns...) + return _s +} + +// Scan applies the selector query and scans the result into the given value. +func (_s *SubscriptionCostBasisPinSelect) Scan(ctx context.Context, v any) error { + ctx = setContextOp(ctx, _s.ctx, ent.OpQuerySelect) + if err := _s.prepareQuery(ctx); err != nil { + return err + } + return scanWithInterceptors[*SubscriptionCostBasisPinQuery, *SubscriptionCostBasisPinSelect](ctx, _s.SubscriptionCostBasisPinQuery, _s, _s.inters, v) +} + +func (_s *SubscriptionCostBasisPinSelect) sqlScan(ctx context.Context, root *SubscriptionCostBasisPinQuery, v any) error { + selector := root.sqlQuery(ctx) + aggregation := make([]string, 0, len(_s.fns)) + for _, fn := range _s.fns { + aggregation = append(aggregation, fn(selector)) + } + switch n := len(*_s.selector.flds); { + case n == 0 && len(aggregation) > 0: + selector.Select(aggregation...) + case n != 0 && len(aggregation) > 0: + selector.AppendSelect(aggregation...) + } + rows := &sql.Rows{} + query, args := selector.Query() + if err := _s.driver.Query(ctx, query, args, rows); err != nil { + return err + } + defer rows.Close() + return sql.ScanSlice(rows, v) +} diff --git a/openmeter/ent/db/subscriptioncostbasispin_update.go b/openmeter/ent/db/subscriptioncostbasispin_update.go new file mode 100644 index 0000000000..b1cc165f41 --- /dev/null +++ b/openmeter/ent/db/subscriptioncostbasispin_update.go @@ -0,0 +1,298 @@ +// Code generated by ent, DO NOT EDIT. + +package db + +import ( + "context" + "errors" + "fmt" + "time" + + "entgo.io/ent/dialect/sql" + "entgo.io/ent/dialect/sql/sqlgraph" + "entgo.io/ent/schema/field" + "github.com/openmeterio/openmeter/openmeter/ent/db/predicate" + "github.com/openmeterio/openmeter/openmeter/ent/db/subscriptioncostbasispin" +) + +// SubscriptionCostBasisPinUpdate is the builder for updating SubscriptionCostBasisPin entities. +type SubscriptionCostBasisPinUpdate struct { + config + hooks []Hook + mutation *SubscriptionCostBasisPinMutation +} + +// Where appends a list predicates to the SubscriptionCostBasisPinUpdate builder. +func (_u *SubscriptionCostBasisPinUpdate) Where(ps ...predicate.SubscriptionCostBasisPin) *SubscriptionCostBasisPinUpdate { + _u.mutation.Where(ps...) + return _u +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *SubscriptionCostBasisPinUpdate) SetUpdatedAt(v time.Time) *SubscriptionCostBasisPinUpdate { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// SetDeletedAt sets the "deleted_at" field. +func (_u *SubscriptionCostBasisPinUpdate) SetDeletedAt(v time.Time) *SubscriptionCostBasisPinUpdate { + _u.mutation.SetDeletedAt(v) + return _u +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (_u *SubscriptionCostBasisPinUpdate) SetNillableDeletedAt(v *time.Time) *SubscriptionCostBasisPinUpdate { + if v != nil { + _u.SetDeletedAt(*v) + } + return _u +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (_u *SubscriptionCostBasisPinUpdate) ClearDeletedAt() *SubscriptionCostBasisPinUpdate { + _u.mutation.ClearDeletedAt() + return _u +} + +// Mutation returns the SubscriptionCostBasisPinMutation object of the builder. +func (_u *SubscriptionCostBasisPinUpdate) Mutation() *SubscriptionCostBasisPinMutation { + return _u.mutation +} + +// Save executes the query and returns the number of nodes affected by the update operation. +func (_u *SubscriptionCostBasisPinUpdate) Save(ctx context.Context) (int, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SubscriptionCostBasisPinUpdate) SaveX(ctx context.Context) int { + affected, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return affected +} + +// Exec executes the query. +func (_u *SubscriptionCostBasisPinUpdate) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SubscriptionCostBasisPinUpdate) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *SubscriptionCostBasisPinUpdate) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := subscriptioncostbasispin.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SubscriptionCostBasisPinUpdate) check() error { + if _u.mutation.SubscriptionCleared() && len(_u.mutation.SubscriptionIDs()) > 0 { + return errors.New(`db: clearing a required unique edge "SubscriptionCostBasisPin.subscription"`) + } + if _u.mutation.CustomCurrencyCleared() && len(_u.mutation.CustomCurrencyIDs()) > 0 { + return errors.New(`db: clearing a required unique edge "SubscriptionCostBasisPin.custom_currency"`) + } + if _u.mutation.CostBasisCleared() && len(_u.mutation.CostBasisIDs()) > 0 { + return errors.New(`db: clearing a required unique edge "SubscriptionCostBasisPin.cost_basis"`) + } + return nil +} + +func (_u *SubscriptionCostBasisPinUpdate) sqlSave(ctx context.Context) (_node int, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(subscriptioncostbasispin.Table, subscriptioncostbasispin.Columns, sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString)) + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(subscriptioncostbasispin.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := _u.mutation.DeletedAt(); ok { + _spec.SetField(subscriptioncostbasispin.FieldDeletedAt, field.TypeTime, value) + } + if _u.mutation.DeletedAtCleared() { + _spec.ClearField(subscriptioncostbasispin.FieldDeletedAt, field.TypeTime) + } + if _node, err = sqlgraph.UpdateNodes(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{subscriptioncostbasispin.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return 0, err + } + _u.mutation.done = true + return _node, nil +} + +// SubscriptionCostBasisPinUpdateOne is the builder for updating a single SubscriptionCostBasisPin entity. +type SubscriptionCostBasisPinUpdateOne struct { + config + fields []string + hooks []Hook + mutation *SubscriptionCostBasisPinMutation +} + +// SetUpdatedAt sets the "updated_at" field. +func (_u *SubscriptionCostBasisPinUpdateOne) SetUpdatedAt(v time.Time) *SubscriptionCostBasisPinUpdateOne { + _u.mutation.SetUpdatedAt(v) + return _u +} + +// SetDeletedAt sets the "deleted_at" field. +func (_u *SubscriptionCostBasisPinUpdateOne) SetDeletedAt(v time.Time) *SubscriptionCostBasisPinUpdateOne { + _u.mutation.SetDeletedAt(v) + return _u +} + +// SetNillableDeletedAt sets the "deleted_at" field if the given value is not nil. +func (_u *SubscriptionCostBasisPinUpdateOne) SetNillableDeletedAt(v *time.Time) *SubscriptionCostBasisPinUpdateOne { + if v != nil { + _u.SetDeletedAt(*v) + } + return _u +} + +// ClearDeletedAt clears the value of the "deleted_at" field. +func (_u *SubscriptionCostBasisPinUpdateOne) ClearDeletedAt() *SubscriptionCostBasisPinUpdateOne { + _u.mutation.ClearDeletedAt() + return _u +} + +// Mutation returns the SubscriptionCostBasisPinMutation object of the builder. +func (_u *SubscriptionCostBasisPinUpdateOne) Mutation() *SubscriptionCostBasisPinMutation { + return _u.mutation +} + +// Where appends a list predicates to the SubscriptionCostBasisPinUpdate builder. +func (_u *SubscriptionCostBasisPinUpdateOne) Where(ps ...predicate.SubscriptionCostBasisPin) *SubscriptionCostBasisPinUpdateOne { + _u.mutation.Where(ps...) + return _u +} + +// Select allows selecting one or more fields (columns) of the returned entity. +// The default is selecting all fields defined in the entity schema. +func (_u *SubscriptionCostBasisPinUpdateOne) Select(field string, fields ...string) *SubscriptionCostBasisPinUpdateOne { + _u.fields = append([]string{field}, fields...) + return _u +} + +// Save executes the query and returns the updated SubscriptionCostBasisPin entity. +func (_u *SubscriptionCostBasisPinUpdateOne) Save(ctx context.Context) (*SubscriptionCostBasisPin, error) { + _u.defaults() + return withHooks(ctx, _u.sqlSave, _u.mutation, _u.hooks) +} + +// SaveX is like Save, but panics if an error occurs. +func (_u *SubscriptionCostBasisPinUpdateOne) SaveX(ctx context.Context) *SubscriptionCostBasisPin { + node, err := _u.Save(ctx) + if err != nil { + panic(err) + } + return node +} + +// Exec executes the query on the entity. +func (_u *SubscriptionCostBasisPinUpdateOne) Exec(ctx context.Context) error { + _, err := _u.Save(ctx) + return err +} + +// ExecX is like Exec, but panics if an error occurs. +func (_u *SubscriptionCostBasisPinUpdateOne) ExecX(ctx context.Context) { + if err := _u.Exec(ctx); err != nil { + panic(err) + } +} + +// defaults sets the default values of the builder before save. +func (_u *SubscriptionCostBasisPinUpdateOne) defaults() { + if _, ok := _u.mutation.UpdatedAt(); !ok { + v := subscriptioncostbasispin.UpdateDefaultUpdatedAt() + _u.mutation.SetUpdatedAt(v) + } +} + +// check runs all checks and user-defined validators on the builder. +func (_u *SubscriptionCostBasisPinUpdateOne) check() error { + if _u.mutation.SubscriptionCleared() && len(_u.mutation.SubscriptionIDs()) > 0 { + return errors.New(`db: clearing a required unique edge "SubscriptionCostBasisPin.subscription"`) + } + if _u.mutation.CustomCurrencyCleared() && len(_u.mutation.CustomCurrencyIDs()) > 0 { + return errors.New(`db: clearing a required unique edge "SubscriptionCostBasisPin.custom_currency"`) + } + if _u.mutation.CostBasisCleared() && len(_u.mutation.CostBasisIDs()) > 0 { + return errors.New(`db: clearing a required unique edge "SubscriptionCostBasisPin.cost_basis"`) + } + return nil +} + +func (_u *SubscriptionCostBasisPinUpdateOne) sqlSave(ctx context.Context) (_node *SubscriptionCostBasisPin, err error) { + if err := _u.check(); err != nil { + return _node, err + } + _spec := sqlgraph.NewUpdateSpec(subscriptioncostbasispin.Table, subscriptioncostbasispin.Columns, sqlgraph.NewFieldSpec(subscriptioncostbasispin.FieldID, field.TypeString)) + id, ok := _u.mutation.ID() + if !ok { + return nil, &ValidationError{Name: "id", err: errors.New(`db: missing "SubscriptionCostBasisPin.id" for update`)} + } + _spec.Node.ID.Value = id + if fields := _u.fields; len(fields) > 0 { + _spec.Node.Columns = make([]string, 0, len(fields)) + _spec.Node.Columns = append(_spec.Node.Columns, subscriptioncostbasispin.FieldID) + for _, f := range fields { + if !subscriptioncostbasispin.ValidColumn(f) { + return nil, &ValidationError{Name: f, err: fmt.Errorf("db: invalid field %q for query", f)} + } + if f != subscriptioncostbasispin.FieldID { + _spec.Node.Columns = append(_spec.Node.Columns, f) + } + } + } + if ps := _u.mutation.predicates; len(ps) > 0 { + _spec.Predicate = func(selector *sql.Selector) { + for i := range ps { + ps[i](selector) + } + } + } + if value, ok := _u.mutation.UpdatedAt(); ok { + _spec.SetField(subscriptioncostbasispin.FieldUpdatedAt, field.TypeTime, value) + } + if value, ok := _u.mutation.DeletedAt(); ok { + _spec.SetField(subscriptioncostbasispin.FieldDeletedAt, field.TypeTime, value) + } + if _u.mutation.DeletedAtCleared() { + _spec.ClearField(subscriptioncostbasispin.FieldDeletedAt, field.TypeTime) + } + _node = &SubscriptionCostBasisPin{config: _u.config} + _spec.Assign = _node.assignValues + _spec.ScanValues = _node.scanValues + if err = sqlgraph.UpdateNode(ctx, _u.driver, _spec); err != nil { + if _, ok := err.(*sqlgraph.NotFoundError); ok { + err = &NotFoundError{subscriptioncostbasispin.Label} + } else if sqlgraph.IsConstraintError(err) { + err = &ConstraintError{msg: err.Error(), wrap: err} + } + return nil, err + } + _u.mutation.done = true + return _node, nil +} diff --git a/openmeter/ent/db/tx.go b/openmeter/ent/db/tx.go index 91b3b1a346..7c3306533b 100644 --- a/openmeter/ent/db/tx.go +++ b/openmeter/ent/db/tx.go @@ -174,6 +174,8 @@ type Tx struct { SubscriptionAddonQuantity *SubscriptionAddonQuantityClient // SubscriptionBillingSyncState is the client for interacting with the SubscriptionBillingSyncState builders. SubscriptionBillingSyncState *SubscriptionBillingSyncStateClient + // SubscriptionCostBasisPin is the client for interacting with the SubscriptionCostBasisPin builders. + SubscriptionCostBasisPin *SubscriptionCostBasisPinClient // SubscriptionItem is the client for interacting with the SubscriptionItem builders. SubscriptionItem *SubscriptionItemClient // SubscriptionPhase is the client for interacting with the SubscriptionPhase builders. @@ -393,6 +395,7 @@ func (tx *Tx) init() { tx.SubscriptionAddon = NewSubscriptionAddonClient(tx.config) tx.SubscriptionAddonQuantity = NewSubscriptionAddonQuantityClient(tx.config) tx.SubscriptionBillingSyncState = NewSubscriptionBillingSyncStateClient(tx.config) + tx.SubscriptionCostBasisPin = NewSubscriptionCostBasisPinClient(tx.config) tx.SubscriptionItem = NewSubscriptionItemClient(tx.config) tx.SubscriptionPhase = NewSubscriptionPhaseClient(tx.config) tx.TaxCode = NewTaxCodeClient(tx.config) diff --git a/openmeter/ent/schema/custom_currencies.go b/openmeter/ent/schema/custom_currencies.go index 464c1e9078..613076e90b 100644 --- a/openmeter/ent/schema/custom_currencies.go +++ b/openmeter/ent/schema/custom_currencies.go @@ -53,6 +53,8 @@ func (CustomCurrency) Edges() []ent.Edge { Annotations(entsql.OnDelete(entsql.Restrict)), edge.To("subscription_items", SubscriptionItem.Type). Annotations(entsql.OnDelete(entsql.Restrict)), + edge.To("subscription_cost_basis_pins", SubscriptionCostBasisPin.Type). + Annotations(entsql.OnDelete(entsql.Restrict)), } } @@ -114,6 +116,8 @@ func (CurrencyCostBasis) Edges() []ent.Edge { Unique(). Required(). Immutable(), + edge.To("subscription_pins", SubscriptionCostBasisPin.Type). + Annotations(entsql.OnDelete(entsql.Restrict)), } } diff --git a/openmeter/ent/schema/subscription.go b/openmeter/ent/schema/subscription.go index 531ed9d047..51139a5b75 100644 --- a/openmeter/ent/schema/subscription.go +++ b/openmeter/ent/schema/subscription.go @@ -37,7 +37,17 @@ func (Subscription) Fields() []ent.Field { field.String("description").Optional().Nillable(), field.String("plan_id").Optional().Nillable(), field.String("customer_id").NotEmpty().Immutable(), - field.String("currency").GoType(currencyx.Code("")).MinLen(3).MaxLen(3).NotEmpty().Immutable(), + field.String("invoice_currency"). + StorageKey("currency"). + GoType(currencyx.Code("")). + MinLen(3). + MaxLen(3). + NotEmpty(). + Immutable(), + field.Enum("cost_basis_mode"). + Values("dynamic", "pinned"). + Default("dynamic"). + Immutable(), field.Time("billing_anchor"), field.String("billing_cadence"). GoType(datetime.ISODurationString("")). @@ -92,6 +102,10 @@ func (Subscription) Edges() []ent.Edge { Annotations(entsql.Annotation{ OnDelete: entsql.Cascade, }), + edge.To("cost_basis_pins", SubscriptionCostBasisPin.Type). + Annotations(entsql.Annotation{ + OnDelete: entsql.Cascade, + }), } } @@ -282,7 +296,7 @@ func (SubscriptionItem) Annotations() []entschema.Annotation { return []entschema.Annotation{ entsql.Checks(map[string]string{ "subscription_item_currency_reference": `(currency IS NULL) OR (custom_currency_id IS NULL)`, - "subscription_item_currency_has_price": `(price IS NOT NULL) OR ((currency IS NULL) AND (custom_currency_id IS NULL))`, + "subscription_item_currency_has_price": `((price IS NULL) AND (currency IS NULL) AND (custom_currency_id IS NULL)) OR ((price IS NOT NULL) AND ((currency IS NOT NULL) OR (custom_currency_id IS NOT NULL)))`, }), } } diff --git a/openmeter/ent/schema/subscription_cost_basis_pin.go b/openmeter/ent/schema/subscription_cost_basis_pin.go new file mode 100644 index 0000000000..11bff3b384 --- /dev/null +++ b/openmeter/ent/schema/subscription_cost_basis_pin.go @@ -0,0 +1,79 @@ +package schema + +import ( + "entgo.io/ent" + "entgo.io/ent/dialect" + "entgo.io/ent/schema/edge" + "entgo.io/ent/schema/field" + "entgo.io/ent/schema/index" + + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/framework/entutils" +) + +type SubscriptionCostBasisPin struct { + ent.Schema +} + +func (SubscriptionCostBasisPin) Mixin() []ent.Mixin { + return []ent.Mixin{ + entutils.IDMixin{}, + entutils.NamespaceMixin{}, + entutils.TimeMixin{}, + } +} + +func (SubscriptionCostBasisPin) Fields() []ent.Field { + return []ent.Field{ + field.String("subscription_id"). + SchemaType(map[string]string{dialect.Postgres: "char(26)"}). + NotEmpty(). + Immutable(), + field.String("custom_currency_id"). + SchemaType(map[string]string{dialect.Postgres: "char(26)"}). + NotEmpty(). + Immutable(), + field.String("invoice_currency"). + GoType(currencyx.Code("")). + NotEmpty(). + MinLen(3). + MaxLen(3). + Immutable(), + field.String("cost_basis_id"). + SchemaType(map[string]string{dialect.Postgres: "char(26)"}). + NotEmpty(). + Immutable(), + } +} + +func (SubscriptionCostBasisPin) Edges() []ent.Edge { + return []ent.Edge{ + edge.From("subscription", Subscription.Type). + Ref("cost_basis_pins"). + Field("subscription_id"). + Unique(). + Required(). + Immutable(), + edge.From("custom_currency", CustomCurrency.Type). + Ref("subscription_cost_basis_pins"). + Field("custom_currency_id"). + Unique(). + Required(). + Immutable(), + edge.From("cost_basis", CurrencyCostBasis.Type). + Ref("subscription_pins"). + Field("cost_basis_id"). + Unique(). + Required(). + Immutable(), + } +} + +func (SubscriptionCostBasisPin) Indexes() []ent.Index { + return []ent.Index{ + index.Fields("namespace", "subscription_id", "custom_currency_id", "invoice_currency").Unique(), + index.Fields("subscription_id"), + index.Fields("custom_currency_id"), + index.Fields("cost_basis_id"), + } +} diff --git a/openmeter/productcatalog/currencyresolver/resolver_test.go b/openmeter/productcatalog/currencyresolver/resolver_test.go index d5f7196681..122b8adb5f 100644 --- a/openmeter/productcatalog/currencyresolver/resolver_test.go +++ b/openmeter/productcatalog/currencyresolver/resolver_test.go @@ -39,6 +39,10 @@ func (m mockCurrencyService) ListCostBases(context.Context, currencies.ListCostB return pagination.Result[currencies.CostBasis]{}, errors.New("not implemented") } +func (m mockCurrencyService) GetCostBasisAt(context.Context, currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + return currencies.CostBasis{}, errors.New("not implemented") +} + func TestResolverResolveCustomCurrencyIgnoresArchivedResources(t *testing.T) { deletedAt := time.Now() service := mockCurrencyService{ diff --git a/openmeter/productcatalog/subscription/http/change.go b/openmeter/productcatalog/subscription/http/change.go index fe0b5abc59..3e34223d3c 100644 --- a/openmeter/productcatalog/subscription/http/change.go +++ b/openmeter/productcatalog/subscription/http/change.go @@ -11,6 +11,7 @@ import ( "github.com/openmeterio/openmeter/api" "github.com/openmeterio/openmeter/openmeter/productcatalog" plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" + "github.com/openmeterio/openmeter/openmeter/subscription" subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" "github.com/openmeterio/openmeter/pkg/convert" "github.com/openmeterio/openmeter/pkg/framework/commonhttp" @@ -89,6 +90,7 @@ func (h *handler) ChangeSubscription() ChangeSubscriptionHandler { Metadata: req.Metadata, }, BillingAnchor: parsedBody.BillingAnchor, + CostBasisMode: subscription.CostBasisMode(lo.FromPtr(parsedBody.CostBasisMode)), }, RejectUnitConfig: true, }, nil @@ -126,6 +128,7 @@ func (h *handler) ChangeSubscription() ChangeSubscriptionHandler { Name: lo.FromPtr(parsedBody.Name), Description: parsedBody.Description, BillingAnchor: parsedBody.BillingAnchor, + CostBasisMode: subscription.CostBasisMode(lo.FromPtr(parsedBody.CostBasisMode)), }, StartingPhase: parsedBody.StartingPhase, SettlementMode: settlementMode, diff --git a/openmeter/productcatalog/subscription/http/create.go b/openmeter/productcatalog/subscription/http/create.go index de942fa3e8..bc0ba8f222 100644 --- a/openmeter/productcatalog/subscription/http/create.go +++ b/openmeter/productcatalog/subscription/http/create.go @@ -98,6 +98,7 @@ func (h *handler) CreateSubscription() CreateSubscriptionHandler { MetadataModel: models.MetadataModel{ Metadata: req.Metadata, // We map the plan metadata to the subscription metadata }, + CostBasisMode: subscription.CostBasisMode(lo.FromPtr(parsedBody.CostBasisMode)), }, Namespace: ns, CustomerID: cus.ID, @@ -147,6 +148,7 @@ func (h *handler) CreateSubscription() CreateSubscriptionHandler { MetadataModel: models.MetadataModel{ Metadata: convert.DerefHeaderPtr[string](parsedBody.Metadata), }, + CostBasisMode: subscription.CostBasisMode(lo.FromPtr(parsedBody.CostBasisMode)), }, Namespace: ns, CustomerID: customer.ID, diff --git a/openmeter/productcatalog/subscription/http/mapping.go b/openmeter/productcatalog/subscription/http/mapping.go index 80b041249b..079580b7c8 100644 --- a/openmeter/productcatalog/subscription/http/mapping.go +++ b/openmeter/productcatalog/subscription/http/mapping.go @@ -181,21 +181,34 @@ func MapSubscriptionToAPI(sub subscription.Subscription) api.Subscription { annotations = lo.ToPtr(api.Annotations(sub.Annotations)) } + costBasisPins := make([]api.SubscriptionCostBasisPin, 0, len(sub.CostBasisPins)) + for _, pin := range sub.CostBasisPins { + costBasisPins = append(costBasisPins, api.SubscriptionCostBasisPin{ + CustomCurrencyId: pin.CustomCurrencyID, + InvoiceCurrency: pin.InvoiceCurrency.String(), + CostBasisId: pin.CostBasis.ID, + }) + } + return api.Subscription{ - Id: sub.ID, - ActiveFrom: sub.ActiveFrom, - ActiveTo: sub.ActiveTo, - CustomerId: sub.CustomerId, - Currency: string(sub.Currency), - Description: sub.Description, - Name: sub.Name, - Status: api.SubscriptionStatus(sub.GetStatusAt(clock.Now())), - Plan: ref, - Metadata: lo.EmptyableToPtr(api.Metadata(sub.Metadata)), - Annotations: annotations, - CreatedAt: sub.CreatedAt, - UpdatedAt: sub.UpdatedAt, - DeletedAt: sub.DeletedAt, + Id: sub.ID, + ActiveFrom: sub.ActiveFrom, + ActiveTo: sub.ActiveTo, + CustomerId: sub.CustomerId, + Currency: sub.InvoiceCurrency.String(), + CostBasisMode: api.SubscriptionCostBasisMode( + sub.CostBasisMode.OrDefault(), + ), + CostBasisPins: costBasisPins, + Description: sub.Description, + Name: sub.Name, + Status: api.SubscriptionStatus(sub.GetStatusAt(clock.Now())), + Plan: ref, + Metadata: lo.EmptyableToPtr(api.Metadata(sub.Metadata)), + Annotations: annotations, + CreatedAt: sub.CreatedAt, + UpdatedAt: sub.UpdatedAt, + DeletedAt: sub.DeletedAt, Alignment: &api.Alignment{ BillablesMustAlign: lo.ToPtr(true), // TODO(OM-1249): this field is deprecated on API, we'll get rid of it eventually }, @@ -249,7 +262,7 @@ func MapSubscriptionItemToAPI(item subscription.SubscriptionItemView) (api.Subsc pr = &prc } - return api.SubscriptionItem{ + result := api.SubscriptionItem{ ActiveFrom: item.SubscriptionItem.ActiveFrom, ActiveTo: item.SubscriptionItem.ActiveTo, BillingCadence: (*string)(item.SubscriptionItem.RateCard.GetBillingCadence().ISOStringPtrOrNil()), @@ -266,7 +279,13 @@ func MapSubscriptionItemToAPI(item subscription.SubscriptionItemView) (api.Subsc Discounts: productcataloghttp.FromDiscounts(item.SubscriptionItem.RateCard.AsMeta().Discounts), TaxConfig: tx, UpdatedAt: item.SubscriptionItem.UpdatedAt, - }, nil + } + + if currency := item.SubscriptionItem.RateCard.AsMeta().Currency; currency != nil { + result.Currency = lo.ToPtr(currency.GetCode().String()) + } + + return result, nil } func MapAPITimingToTiming(apiTiming api.SubscriptionTiming) (subscription.Timing, error) { @@ -422,6 +441,8 @@ func MapSubscriptionViewToAPI(view subscription.SubscriptionView) (api.Subscript ActiveTo: apiSub.ActiveTo, CreatedAt: apiSub.CreatedAt, Currency: apiSub.Currency, + CostBasisMode: apiSub.CostBasisMode, + CostBasisPins: apiSub.CostBasisPins, CustomerId: apiSub.CustomerId, DeletedAt: apiSub.DeletedAt, Description: apiSub.Description, diff --git a/openmeter/productcatalog/subscription/http/mapping_test.go b/openmeter/productcatalog/subscription/http/mapping_test.go new file mode 100644 index 0000000000..f6a4dbc938 --- /dev/null +++ b/openmeter/productcatalog/subscription/http/mapping_test.go @@ -0,0 +1,71 @@ +package httpdriver + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/openmeter/subscription" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestMapSubscriptionToAPICurrencySemantics(t *testing.T) { + // given: + // - a pinned subscription with a materialized invoice fiat and cost-basis pin + sub := subscription.Subscription{ + InvoiceCurrency: currencyx.Code("USD"), + CostBasisMode: subscription.CostBasisModePinned, + CostBasisPins: []subscription.CostBasisPin{ + { + CustomCurrencyID: "currency_credits", + InvoiceCurrency: currencyx.Code("USD"), + CostBasis: currencies.CostBasis{ + NamespacedID: models.NamespacedID{ID: "cost_basis_credits_usd"}, + }, + }, + }, + } + + // when: + // - the subscription is mapped to the v1 API + result := MapSubscriptionToAPI(sub) + + // then: + // - the existing currency field carries invoice fiat and the new pin fields are exposed + require.Equal(t, "USD", result.Currency) + require.Equal(t, "pinned", string(result.CostBasisMode)) + require.Equal(t, "currency_credits", result.CostBasisPins[0].CustomCurrencyId) + require.Equal(t, "USD", result.CostBasisPins[0].InvoiceCurrency) + require.Equal(t, "cost_basis_credits_usd", result.CostBasisPins[0].CostBasisId) +} + +func TestMapSubscriptionItemToAPIMaterializedCurrency(t *testing.T) { + // given: + // - a subscription item carrying a managed custom currency on its rate card + customCurrency := currencies.Currency{ + NamespacedID: models.NamespacedID{ID: "currency_credits"}, + Code: "CREDITS", + Name: "Credits", + } + item := subscription.SubscriptionItemView{ + SubscriptionItem: subscription.SubscriptionItem{ + RateCard: &productcatalog.FlatFeeRateCard{ + RateCardMeta: productcatalog.RateCardMeta{ + Currency: customCurrency, + }, + }, + }, + } + + // when: + // - the item is mapped to the expanded v1 subscription response + result, err := MapSubscriptionItemToAPI(item) + + // then: + // - the materialized currency code is exposed + require.NoError(t, err) + require.Equal(t, "CREDITS", *result.Currency) +} diff --git a/openmeter/productcatalog/subscription/service/change.go b/openmeter/productcatalog/subscription/service/change.go index 3d031ade0b..ea5ac624b3 100644 --- a/openmeter/productcatalog/subscription/service/change.go +++ b/openmeter/productcatalog/subscription/service/change.go @@ -24,6 +24,13 @@ func (s *service) Change(ctx context.Context, request plansubscription.ChangeSub if request.SettlementMode != nil { planInput.SettlementMode = *request.SettlementMode } + if err := planInput.ResolveCurrencies(ctx, s.CurrencyResolver); err != nil { + return def, fmt.Errorf("invalid plan currencies: %w", err) + } + + if err := planInput.ValidateCurrencies(ctx, s.CurrencyResolver); err != nil { + return def, fmt.Errorf("invalid plan currencies: %w", err) + } p, err := PlanFromPlanInput(planInput) if err != nil { diff --git a/openmeter/productcatalog/subscription/service/change_test.go b/openmeter/productcatalog/subscription/service/change_test.go index 98d22894b1..2f4494a361 100644 --- a/openmeter/productcatalog/subscription/service/change_test.go +++ b/openmeter/productcatalog/subscription/service/change_test.go @@ -12,7 +12,6 @@ import ( "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/openmeter/productcatalog/plan" plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" - "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription/service" "github.com/openmeterio/openmeter/openmeter/subscription" subscriptiontestutils "github.com/openmeterio/openmeter/openmeter/subscription/testutils" subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" @@ -55,13 +54,7 @@ func TestChange(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) // Let's set up the feature & customer cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) @@ -161,13 +154,7 @@ func TestChange(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) // Let's set up the feature & customer cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) @@ -289,13 +276,7 @@ func TestChange(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) // Let's set up the feature & customer cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) @@ -414,13 +395,7 @@ func TestChange(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) deps.subDeps.FeatureConnector.CreateExampleFeatures(t, deps.subDeps.ExampleMeterID) @@ -463,13 +438,7 @@ func TestChange(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) deps.subDeps.FeatureConnector.CreateExampleFeatures(t, deps.subDeps.ExampleMeterID) diff --git a/openmeter/productcatalog/subscription/service/create.go b/openmeter/productcatalog/subscription/service/create.go index ad2d63339a..3749eed852 100644 --- a/openmeter/productcatalog/subscription/service/create.go +++ b/openmeter/productcatalog/subscription/service/create.go @@ -26,6 +26,13 @@ func (s *service) Create(ctx context.Context, request plansubscription.CreateSub if request.SettlementMode != nil { planInput.SettlementMode = *request.SettlementMode } + if err := planInput.ResolveCurrencies(ctx, s.CurrencyResolver); err != nil { + return def, fmt.Errorf("invalid plan currencies: %w", err) + } + + if err := planInput.ValidateCurrencies(ctx, s.CurrencyResolver); err != nil { + return def, fmt.Errorf("invalid plan currencies: %w", err) + } p, err := PlanFromPlanInput(planInput) if err != nil { diff --git a/openmeter/productcatalog/subscription/service/create_test.go b/openmeter/productcatalog/subscription/service/create_test.go index f6181db939..72f26dc9f0 100644 --- a/openmeter/productcatalog/subscription/service/create_test.go +++ b/openmeter/productcatalog/subscription/service/create_test.go @@ -2,12 +2,14 @@ package service_test import ( "context" + "log/slog" "testing" "time" "github.com/samber/lo" "github.com/stretchr/testify/require" + "github.com/openmeterio/openmeter/openmeter/currencies" "github.com/openmeterio/openmeter/openmeter/productcatalog" plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription/service" @@ -16,8 +18,25 @@ import ( subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" "github.com/openmeterio/openmeter/openmeter/testutils" "github.com/openmeterio/openmeter/pkg/clock" + "github.com/openmeterio/openmeter/pkg/currencyx" ) +func newPlanSubscriptionService(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies, logger *slog.Logger) plansubscription.PlanSubscriptionService { + t.Helper() + + svc, err := service.New(service.Config{ + SubscriptionService: deps.SubscriptionService, + WorkflowService: deps.WorkflowService, + Logger: logger, + PlanService: deps.PlanService, + CurrencyResolver: deps.CurrencyResolver, + CustomerService: deps.CustomerService, + }) + require.NoError(t, err) + + return svc +} + func TestCreateSettlementModeOverride(t *testing.T) { logger := testutils.NewLogger(t) @@ -55,13 +74,7 @@ func TestCreateSettlementModeOverride(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) deps.subDeps.FeatureConnector.CreateExampleFeatures(t, deps.subDeps.ExampleMeterID) @@ -108,13 +121,7 @@ func TestCreateSettlementModeOverride(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) deps.subDeps.FeatureConnector.CreateExampleFeatures(t, deps.subDeps.ExampleMeterID) @@ -151,3 +158,132 @@ func TestCreateSettlementModeOverride(t *testing.T) { }) }) } + +func TestCreateInlineCustomCurrencyMaterializesManagedIdentity(t *testing.T) { + // given: + // - an inline plan that identifies a managed custom currency by code + // when: + // - the plan subscription service creates and reloads the subscription + // then: + // - every priced item persists the managed currency identity, not the authoring code + now := time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC) + clock.FreezeTime(now) + defer clock.UnFreeze() + + dbDeps := subscriptiontestutils.SetupDBDeps(t) + defer dbDeps.Cleanup(t) + + deps := subscriptiontestutils.NewService(t, dbDeps) + svc := newPlanSubscriptionService(t, deps, testutils.NewLogger(t)) + customer := deps.CustomerAdapter.CreateExampleCustomer(t) + deps.FeatureConnector.CreateExampleFeatures(t, deps.ExampleMeterID) + + managedCurrency, err := deps.CurrencyService.CreateCurrency(t.Context(), currencies.CreateCurrencyInput{ + Namespace: subscriptiontestutils.ExampleNamespace, + Code: "CREDITS", + Name: "Credits", + Symbol: "CR", + }) + require.NoError(t, err) + + planInput := subscriptiontestutils.GetExamplePlanInput(t) + planInput.Plan.Key = "" + planInput.Plan.Version = 0 + planInput.Plan.Currency = currencyx.Code(managedCurrency.Code) + planInput.Plan.SettlementMode = productcatalog.CreditOnlySettlementMode + + requestPlan := plansubscription.PlanInput{} + requestPlan.FromInput(&planInput) + + created, err := svc.Create(t.Context(), plansubscription.CreateSubscriptionRequest{ + PlanInput: requestPlan, + WorkflowInput: subscriptionworkflow.CreateSubscriptionWorkflowInput{ + ChangeSubscriptionWorkflowInput: subscriptionworkflow.ChangeSubscriptionWorkflowInput{ + Name: "inline custom currency", + Timing: subscription.Timing{ + Enum: lo.ToPtr(subscription.TimingImmediate), + }, + }, + Namespace: customer.Namespace, + CustomerID: customer.ID, + }, + }) + require.NoError(t, err) + + view, err := deps.SubscriptionService.GetView(t.Context(), created.NamespacedID) + require.NoError(t, err) + + pricedItems := 0 + for _, phase := range view.Phases { + for _, items := range phase.ItemsByKey { + for _, item := range items { + meta := item.Spec.RateCard.AsMeta() + if meta.Price == nil { + continue + } + + pricedItems++ + require.NotNil(t, meta.Currency) + currency, ok := meta.Currency.(currencyx.ManagedCurrency) + require.True(t, ok) + require.Equal(t, managedCurrency.ID, currency.GetID()) + } + } + } + require.Positive(t, pricedItems) +} + +func TestCreateInlinePlanValidatesCurrencyCostBasis(t *testing.T) { + // given: + // - a credit-only inline fiat plan with a managed custom-currency rate card + // - no cost basis for that custom-currency to plan-fiat pair + // when: + // - the plan subscription service validates the inline plan + // then: + // - product-catalog validation rejects it before subscription validation can waive cost basis + now := time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC) + clock.FreezeTime(now) + defer clock.UnFreeze() + + dbDeps := subscriptiontestutils.SetupDBDeps(t) + defer dbDeps.Cleanup(t) + + deps := subscriptiontestutils.NewService(t, dbDeps) + svc := newPlanSubscriptionService(t, deps, testutils.NewLogger(t)) + customer := deps.CustomerAdapter.CreateExampleCustomer(t) + + _, err := deps.CurrencyService.CreateCurrency(t.Context(), currencies.CreateCurrencyInput{ + Namespace: subscriptiontestutils.ExampleNamespace, + Code: "CREDITS", + Name: "Credits", + Symbol: "CR", + }) + require.NoError(t, err) + + planInput := subscriptiontestutils.GetExamplePlanInput(t) + planInput.Plan.Key = "" + planInput.Plan.Version = 0 + planInput.Plan.SettlementMode = productcatalog.CreditOnlySettlementMode + require.NoError(t, planInput.Plan.Phases[0].RateCards[0].ChangeMeta(func(meta productcatalog.RateCardMeta) (productcatalog.RateCardMeta, error) { + meta.Currency = currencyx.Code("CREDITS") + return meta, nil + })) + + requestPlan := plansubscription.PlanInput{} + requestPlan.FromInput(&planInput) + + _, err = svc.Create(t.Context(), plansubscription.CreateSubscriptionRequest{ + PlanInput: requestPlan, + WorkflowInput: subscriptionworkflow.CreateSubscriptionWorkflowInput{ + ChangeSubscriptionWorkflowInput: subscriptionworkflow.ChangeSubscriptionWorkflowInput{ + Name: "inline custom currency without cost basis", + Timing: subscription.Timing{ + Enum: lo.ToPtr(subscription.TimingImmediate), + }, + }, + Namespace: customer.Namespace, + CustomerID: customer.ID, + }, + }) + require.ErrorContains(t, err, productcatalog.ErrCurrencyCostBasisNotFound.Error()) +} diff --git a/openmeter/productcatalog/subscription/service/migrate.go b/openmeter/productcatalog/subscription/service/migrate.go index f9f46539dd..1ef32e53d7 100644 --- a/openmeter/productcatalog/subscription/service/migrate.go +++ b/openmeter/productcatalog/subscription/service/migrate.go @@ -105,6 +105,7 @@ func (s *service) Migrate(ctx context.Context, request plansubscription.MigrateS Name: sub.Name, Description: sub.Description, BillingAnchor: request.BillingAnchor, + CostBasisMode: sub.CostBasisMode, } curr, new, err := s.WorkflowService.ChangeToPlan(ctx, request.ID, workflowInput, pp) if err != nil { diff --git a/openmeter/productcatalog/subscription/service/migrate_test.go b/openmeter/productcatalog/subscription/service/migrate_test.go index 2b194e4b8d..4b89a3b852 100644 --- a/openmeter/productcatalog/subscription/service/migrate_test.go +++ b/openmeter/productcatalog/subscription/service/migrate_test.go @@ -11,7 +11,6 @@ import ( "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/openmeter/productcatalog/plan" plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" - "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription/service" "github.com/openmeterio/openmeter/openmeter/subscription" subscriptiontestutils "github.com/openmeterio/openmeter/openmeter/subscription/testutils" subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" @@ -53,13 +52,7 @@ func TestMigrate(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) // Let's set up the feature & customer cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) @@ -135,13 +128,7 @@ func TestMigrate(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) // Let's set up the feature & customer cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) @@ -215,13 +202,7 @@ func TestMigrate(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) // Let's set up the feature & customer cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) @@ -300,13 +281,7 @@ func TestMigrate(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) // Let's set up the feature & customer cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) diff --git a/openmeter/productcatalog/subscription/service/plan_test.go b/openmeter/productcatalog/subscription/service/plan_test.go index f782007d3f..bfb224f979 100644 --- a/openmeter/productcatalog/subscription/service/plan_test.go +++ b/openmeter/productcatalog/subscription/service/plan_test.go @@ -8,7 +8,6 @@ import ( "github.com/stretchr/testify/require" plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" - "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription/service" "github.com/openmeterio/openmeter/openmeter/subscription" subscriptiontestutils "github.com/openmeterio/openmeter/openmeter/subscription/testutils" subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" @@ -45,13 +44,7 @@ func TestDiscountPersisting(t *testing.T) { ctx := context.Background() - svc := service.New(service.Config{ - SubscriptionService: deps.subSvc, - WorkflowService: deps.wfSvc, - Logger: logger, - PlanService: deps.subDeps.PlanService, - CustomerService: deps.subDeps.CustomerService, - }) + svc := newPlanSubscriptionService(t, deps.subDeps, logger) // Let's set up the feature & customer cust := deps.subDeps.CustomerAdapter.CreateExampleCustomer(t) diff --git a/openmeter/productcatalog/subscription/service/service.go b/openmeter/productcatalog/subscription/service/service.go index 2fdf3c888d..cf2d6493b8 100644 --- a/openmeter/productcatalog/subscription/service/service.go +++ b/openmeter/productcatalog/subscription/service/service.go @@ -1,6 +1,7 @@ package service import ( + "errors" "fmt" "log/slog" "time" @@ -8,6 +9,7 @@ import ( "github.com/samber/lo" "github.com/openmeterio/openmeter/openmeter/customer" + "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/openmeter/productcatalog/plan" plansubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" "github.com/openmeterio/openmeter/openmeter/subscription" @@ -21,18 +23,53 @@ type Config struct { WorkflowService subscriptionworkflow.Service SubscriptionService subscription.Service PlanService plan.Service + CurrencyResolver productcatalog.CurrencyResolver Logger *slog.Logger CustomerService customer.Service } +func (c Config) Validate() error { + var errs []error + + if c.WorkflowService == nil { + errs = append(errs, errors.New("subscription workflow service is required")) + } + + if c.SubscriptionService == nil { + errs = append(errs, errors.New("subscription service is required")) + } + + if c.PlanService == nil { + errs = append(errs, errors.New("plan service is required")) + } + + if c.CurrencyResolver == nil { + errs = append(errs, errors.New("currency resolver is required")) + } + + if c.Logger == nil { + errs = append(errs, errors.New("logger is required")) + } + + if c.CustomerService == nil { + errs = append(errs, errors.New("customer service is required")) + } + + return errors.Join(errs...) +} + type service struct { Config } -func New(c Config) plansubscription.PlanSubscriptionService { +func New(c Config) (plansubscription.PlanSubscriptionService, error) { + if err := c.Validate(); err != nil { + return nil, fmt.Errorf("invalid plan subscription service config: %w", err) + } + return &service{ Config: c, - } + }, nil } func (s *service) zeroPhasesBeforeStartingPhase(p *plan.Plan, startingPhase string) error { diff --git a/openmeter/server/server_test.go b/openmeter/server/server_test.go index 2438a81961..e3bbb23eec 100644 --- a/openmeter/server/server_test.go +++ b/openmeter/server/server_test.go @@ -1627,7 +1627,7 @@ func (n NoopSubscriptionService) Create(ctx context.Context, namespace string, s return subscription.Subscription{}, nil } -func (n NoopSubscriptionService) Update(ctx context.Context, subscriptionID models.NamespacedID, target subscription.SubscriptionSpec) (subscription.Subscription, error) { +func (n NoopSubscriptionService) Update(ctx context.Context, subscriptionID models.NamespacedID, target subscription.SubscriptionSpec, options ...subscription.UpdateOption) (subscription.Subscription, error) { return subscription.Subscription{}, nil } @@ -2119,6 +2119,10 @@ func (n NoopCurrencyService) ListCostBases(ctx context.Context, params currencie return pagination.Result[currencies.CostBasis]{}, nil } +func (n NoopCurrencyService) GetCostBasisAt(ctx context.Context, params currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + return currencies.CostBasis{}, nil +} + // NoopLLMCostService implements llmcost.Service with no-op operations var _ llmcost.Service = (*NoopLLMCostService)(nil) diff --git a/openmeter/subscription/addon/diff/apply.go b/openmeter/subscription/addon/diff/apply.go index 580fcbada5..9f9a9d48ce 100644 --- a/openmeter/subscription/addon/diff/apply.go +++ b/openmeter/subscription/addon/diff/apply.go @@ -4,6 +4,7 @@ import ( "fmt" "slices" + "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/openmeter/subscription" subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" "github.com/openmeterio/openmeter/pkg/currencyx" @@ -85,7 +86,9 @@ func (d *diffable) getApplyForRateCard(rc subscriptionaddon.SubscriptionAddonRat // We need to update all items for _, item := range items { itemPer := item.GetCadence(pCad).AsPeriod() - priceIntroducedByAddon := item.RateCard.AsMeta().Price == nil && rc.AddonRateCard.AsMeta().Price != nil + itemMeta := item.RateCard.AsMeta() + addonMeta := rc.AddonRateCard.AsMeta() + priceIntroducedByAddon := itemMeta.Price == nil && addonMeta.Price != nil { // Let's subtract the item from the gaps @@ -116,6 +119,13 @@ func (d *diffable) getApplyForRateCard(rc subscriptionaddon.SubscriptionAddonRat continue } + if itemMeta.Price != nil && addonMeta.Price != nil { + addonCurrency := addonMeta.EffectiveCurrency(d.addon.Addon.Currency) + if itemMeta.Currency == nil || addonCurrency == nil || !itemMeta.Currency.Equal(addonCurrency) { + return fmt.Errorf("add-on rate card %s: %w", rc.AddonRateCard.Key(), productcatalog.ErrPlanAddonCurrencyMismatch) + } + } + // We need to split the item: // - the old shape will be kept for the difference diff := itemPer.Difference(*inter) @@ -161,7 +171,7 @@ func (d *diffable) getApplyForRateCard(rc subscriptionaddon.SubscriptionAddonRat } if priceIntroducedByAddon { addonCurrency := rc.AddonRateCard.AsMeta().EffectiveCurrency(d.addon.Addon.Currency) - if err := materializeAddonRateCardCurrency(&inst, addonCurrency, spec.Currency); err != nil { + if err := materializeAddonRateCardCurrency(&inst, addonCurrency, spec.InvoiceCurrency); err != nil { return fmt.Errorf("failed to materialize currency for extended rate card %s: %w", rc.AddonRateCard.Key(), err) } } @@ -191,7 +201,7 @@ func (d *diffable) getApplyForRateCard(rc subscriptionaddon.SubscriptionAddonRat } } addonCurrency := rc.AddonRateCard.AsMeta().EffectiveCurrency(d.addon.Addon.Currency) - if err := materializeAddonRateCardCurrency(&inst, addonCurrency, spec.Currency); err != nil { + if err := materializeAddonRateCardCurrency(&inst, addonCurrency, spec.InvoiceCurrency); err != nil { return fmt.Errorf("failed to materialize currency for new rate card %s: %w", rc.AddonRateCard.Key(), err) } diff --git a/openmeter/subscription/addon/diff/apply_test.go b/openmeter/subscription/addon/diff/apply_test.go index 2332ad0e59..d5729535a3 100644 --- a/openmeter/subscription/addon/diff/apply_test.go +++ b/openmeter/subscription/addon/diff/apply_test.go @@ -15,6 +15,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/app" "github.com/openmeterio/openmeter/openmeter/currencies" "github.com/openmeterio/openmeter/openmeter/productcatalog" + productcatalogaddon "github.com/openmeterio/openmeter/openmeter/productcatalog/addon" "github.com/openmeterio/openmeter/openmeter/subscription" subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" addondiff "github.com/openmeterio/openmeter/openmeter/subscription/addon/diff" @@ -191,6 +192,110 @@ func TestApply(t *testing.T) { }) }) + t.Run("Should reject a second priced addon with a different currency", func(t *testing.T) { + // given: + // - an unpriced subscription item + // - two add-ons that independently introduce a price for the same key in different custom currencies + // when: + // - the second add-on is layered over the first + // then: + // - its price cannot be silently combined under the first add-on's currency + const itemKey = "shared-addon-price" + + unpricedRateCard := &productcatalog.FlatFeeRateCard{ + RateCardMeta: productcatalog.RateCardMeta{ + Key: itemKey, + Name: itemKey, + }, + BillingCadence: &subscriptiontestutils.ISOMonth, + } + spec := subscription.SubscriptionSpec{ + CreateSubscriptionPlanInput: subscription.CreateSubscriptionPlanInput{ + BillingCadence: subscriptiontestutils.ISOMonth, + SettlementMode: productcatalog.CreditOnlySettlementMode, + }, + CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{ + Name: "currency layering", + CustomerId: "customer", + InvoiceCurrency: currencyx.Code(currency.USD), + ActiveFrom: now, + BillingAnchor: now, + }, + Phases: map[string]*subscription.SubscriptionPhaseSpec{ + "default": { + CreateSubscriptionPhasePlanInput: subscription.CreateSubscriptionPhasePlanInput{ + PhaseKey: "default", + Name: "default", + }, + ItemsByKey: map[string][]*subscription.SubscriptionItemSpec{ + itemKey: { + { + CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ + PhaseKey: "default", + ItemKey: itemKey, + RateCard: unpricedRateCard, + }, + }, + }, + }, + }, + }, + }, + } + require.NoError(t, spec.Validate()) + + newAddon := func(id, code string) subscriptionaddon.SubscriptionAddon { + customCurrency := currencies.Currency{ + NamespacedID: models.NamespacedID{Namespace: "default", ID: id}, + Code: code, + Name: code, + } + pricedRateCard := &productcatalog.FlatFeeRateCard{ + RateCardMeta: productcatalog.RateCardMeta{ + Key: itemKey, + Name: itemKey, + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ + Amount: alpacadecimal.NewFromInt(10), + PaymentTerm: productcatalog.InAdvancePaymentTerm, + }), + }, + BillingCadence: &subscriptiontestutils.ISOMonth, + } + quantity := subscriptionaddon.SubscriptionAddonQuantity{ + ActiveFrom: now, + Quantity: 1, + } + + return subscriptionaddon.SubscriptionAddon{ + Addon: productcatalogaddon.Addon{ + AddonMeta: productcatalog.AddonMeta{ + Currency: customCurrency, + }, + }, + RateCards: []subscriptionaddon.SubscriptionAddonRateCard{ + { + AddonRateCard: productcatalogaddon.RateCard{RateCard: pricedRateCard}, + }, + }, + Quantities: timeutil.NewTimeline([]timeutil.Timed[subscriptionaddon.SubscriptionAddonQuantity]{ + quantity.AsTimed(), + }), + } + } + + creditsDiff, err := addondiff.GetDiffableFromAddon(subscription.SubscriptionView{}, newAddon("credits-id", "CREDITS")) + require.NoError(t, err) + require.NoError(t, spec.Apply(creditsDiff.GetApplies(), subscription.ApplyContext{CurrentTime: now})) + require.Equal(t, currencyx.Code("CREDITS"), spec.Phases["default"].ItemsByKey[itemKey][0].RateCard.AsMeta().Currency.GetCode()) + + pointsAddon := newAddon("points-id", "POINTS") + pointsDiff, err := addondiff.GetDiffableFromAddon(subscription.SubscriptionView{}, pointsAddon) + require.NoError(t, err) + err = spec.Apply(pointsDiff.GetApplies(), subscription.ApplyContext{CurrentTime: now}) + require.ErrorIs(t, err, productcatalog.ErrPlanAddonCurrencyMismatch) + }) + t.Run("Should add multiple instances of new item to the subscription", func(t *testing.T) { runWithDeps(t, func(t *testing.T, deps *tcDeps) { exampleAddonRateCard3, ok := (subscriptiontestutils.ExampleAddonRateCard3.Clone()).(*productcatalog.FlatFeeRateCard) @@ -261,7 +366,7 @@ func TestApply(t *testing.T) { require.Equal(t, int64(300), pr.Amount.IntPart()) // It should have the proper RateCard info, which is price * quantity + bool access - compareRateCardsWithAmountChange(t, exampleAddonRateCard3, 100*3, item.RateCard, "phase %s", p.PhaseKey) + compareRateCardsWithAmountChange(t, withTaxCodeIDAndCurrency(exampleAddonRateCard3, nil, currency.USD), 100*3, item.RateCard, "phase %s", p.PhaseKey) } }) }) @@ -716,6 +821,8 @@ func TestApply(t *testing.T) { // Let's just manipulate the spec object directly ogItem := spec.Phases["test_phase_1"].ItemsByKey[subscriptiontestutils.ExampleRateCard3ForAddons.Key()][0] + ogRateCard, ok := ogItem.RateCard.(*productcatalog.FlatFeeRateCard) + require.True(t, ok) spec.Phases["test_phase_1"].ItemsByKey[subscriptiontestutils.ExampleRateCard3ForAddons.Key()] = []*subscription.SubscriptionItemSpec{ // First, before gap { @@ -723,7 +830,7 @@ func TestApply(t *testing.T) { CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ PhaseKey: spec.Phases["test_phase_1"].PhaseKey, ItemKey: subscriptiontestutils.ExampleRateCard3ForAddons.Key(), - RateCard: subscriptiontestutils.ExampleRateCard3ForAddons.Clone(), + RateCard: ogItem.RateCard.Clone(), }, CreateSubscriptionItemCustomerInput: subscription.CreateSubscriptionItemCustomerInput{ ActiveToOverrideRelativeToPhaseStart: lo.ToPtr(datetime.ISODurationBetween(t0, t2)), @@ -737,7 +844,7 @@ func TestApply(t *testing.T) { CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ PhaseKey: spec.Phases["test_phase_1"].PhaseKey, ItemKey: subscriptiontestutils.ExampleRateCard3ForAddons.Key(), - RateCard: subscriptiontestutils.ExampleRateCard3ForAddons.Clone(), + RateCard: ogItem.RateCard.Clone(), }, CreateSubscriptionItemCustomerInput: subscription.CreateSubscriptionItemCustomerInput{ ActiveFromOverrideRelativeToPhaseStart: lo.ToPtr(datetime.ISODurationBetween(t0, t3)), @@ -763,29 +870,29 @@ func TestApply(t *testing.T) { // [t0-t1]: ExampleRateCard3ForAddons require.True(t, items[0].GetCadence(pCad).ActiveFrom.Equal(t0)) require.True(t, items[0].GetCadence(pCad).ActiveTo.Equal(t1)) - compareRateCardsWithAmountChange(t, &subscriptiontestutils.ExampleRateCard3ForAddons, 100, items[0].RateCard, "phase %s", phase.PhaseKey) + compareRateCardsWithAmountChange(t, ogRateCard, 100, items[0].RateCard, "phase %s", phase.PhaseKey) // [t1-t2]: ExampleRateCard3ForAddons + ExampleAddonRateCard4 require.True(t, items[1].GetCadence(pCad).ActiveFrom.Equal(t1)) require.True(t, items[1].GetCadence(pCad).ActiveTo.Equal(t2)) - compareRateCardsWithAmountChange(t, &subscriptiontestutils.ExampleRateCard3ForAddons, 100*2, items[1].RateCard, "phase %s", phase.PhaseKey) + compareRateCardsWithAmountChange(t, ogRateCard, 100*2, items[1].RateCard, "phase %s", phase.PhaseKey) require.Equal(t, ogItem.Annotations, items[1].Annotations, "annotations should be the same") // [t2-t3]: ExampleAddonRateCard4 require.True(t, items[2].GetCadence(pCad).ActiveFrom.Equal(t2)) require.True(t, items[2].GetCadence(pCad).ActiveTo.Equal(t3)) - compareRateCardsWithAmountChange(t, subsAdd.RateCards[0].AddonRateCard.RateCard.(*productcatalog.FlatFeeRateCard), 100, items[2].RateCard, "phase %s", phase.PhaseKey) + compareRateCardsWithAmountChange(t, withTaxCodeIDAndCurrency(subsAdd.RateCards[0].AddonRateCard.RateCard.(*productcatalog.FlatFeeRateCard), nil, currency.USD), 100, items[2].RateCard, "phase %s", phase.PhaseKey) // [t3-t4]: ExampleRateCard3ForAddons + ExampleAddonRateCard4 require.True(t, items[3].GetCadence(pCad).ActiveFrom.Equal(t3)) require.True(t, items[3].GetCadence(pCad).ActiveTo.Equal(t4)) - compareRateCardsWithAmountChange(t, &subscriptiontestutils.ExampleRateCard3ForAddons, 100*2, items[3].RateCard, "phase %s", phase.PhaseKey) + compareRateCardsWithAmountChange(t, ogRateCard, 100*2, items[3].RateCard, "phase %s", phase.PhaseKey) require.Equal(t, ogItem.Annotations, items[3].Annotations, "annotations should be the same") // [t4-open]: ExampleRateCard3ForAddons require.True(t, items[4].GetCadence(pCad).ActiveFrom.Equal(t4)) require.Nil(t, items[4].GetCadence(pCad).ActiveTo) - compareRateCardsWithAmountChange(t, &subscriptiontestutils.ExampleRateCard3ForAddons, 100, items[4].RateCard, "phase %s", phase.PhaseKey) + compareRateCardsWithAmountChange(t, ogRateCard, 100, items[4].RateCard, "phase %s", phase.PhaseKey) }) }) @@ -859,7 +966,7 @@ func TestApply(t *testing.T) { require.True(t, item.GetCadence(pCad).ActiveFrom.Equal(now)) require.Nil(t, item.GetCadence(pCad).ActiveTo) - compareRateCardsWithAmountChange(t, &subscriptiontestutils.ExampleAddonRateCard3, 100, item.RateCard, "phase %s", p.PhaseKey) + compareRateCardsWithAmountChange(t, withTaxCodeIDAndCurrency(&subscriptiontestutils.ExampleAddonRateCard3, nil, currency.USD), 100, item.RateCard, "phase %s", p.PhaseKey) }) }) } diff --git a/openmeter/subscription/addon/extend.go b/openmeter/subscription/addon/extend.go index 294df4a8e9..3c1345f749 100644 --- a/openmeter/subscription/addon/extend.go +++ b/openmeter/subscription/addon/extend.go @@ -158,8 +158,9 @@ func (a SubscriptionAddonRateCard) Restore(target productcatalog.RateCard, annot PaymentTerm: tFlat.PaymentTerm, }) case instanceType == productcatalog.AddonInstanceTypeSingle: - // Clear UnitConfig with Price: it can't outlive the price it converts. + // Currency and UnitConfig describe the price and cannot outlive it. m.Price = nil + m.Currency = nil m.UnitConfig = nil default: return m, fmt.Errorf("not supported price type: %s", tMeta.Price.Type()) diff --git a/openmeter/subscription/addon/service/change_test.go b/openmeter/subscription/addon/service/change_test.go index 3c505a8d46..1dbc25524e 100644 --- a/openmeter/subscription/addon/service/change_test.go +++ b/openmeter/subscription/addon/service/change_test.go @@ -115,11 +115,11 @@ func TestAddonServiceChangeQuantity(t *testing.T) { Plan: p.AsProductCatalogPlan(), Ref: &p.NamespacedID, }, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -199,11 +199,11 @@ func createExampleSubscriptionAddon(t *testing.T, deps subscriptiontestutils.Sub // Let's create a subscription cust := deps.CustomerAdapter.CreateExampleCustomer(t) spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) diff --git a/openmeter/subscription/addon/service/create_test.go b/openmeter/subscription/addon/service/create_test.go index 0c62f6c8d8..17136ce9f2 100644 --- a/openmeter/subscription/addon/service/create_test.go +++ b/openmeter/subscription/addon/service/create_test.go @@ -43,11 +43,11 @@ func TestAddonServiceCreate(t *testing.T) { // Let's create a subscription cust := deps.CustomerAdapter.CreateExampleCustomer(t) spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -89,11 +89,11 @@ func TestAddonServiceCreate(t *testing.T) { // Let's create a subscription cust := deps.CustomerAdapter.CreateExampleCustomer(t) spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -162,11 +162,11 @@ func TestAddonServiceCreate(t *testing.T) { // Let's create a subscription cust := deps.CustomerAdapter.CreateExampleCustomer(t) spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -216,11 +216,11 @@ func TestAddonServiceCreate(t *testing.T) { // Let's create a subscription cust := deps.CustomerAdapter.CreateExampleCustomer(t) spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -287,11 +287,11 @@ func TestAddonServiceCreate(t *testing.T) { Plan: p.AsProductCatalogPlan(), Ref: &p.NamespacedID, }, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - BillingAnchor: now, - ActiveFrom: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + BillingAnchor: now, + ActiveFrom: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -364,11 +364,11 @@ func TestAddonServiceCreate(t *testing.T) { Plan: p.AsProductCatalogPlan(), Ref: &p.NamespacedID, }, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - BillingAnchor: now, - ActiveFrom: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + BillingAnchor: now, + ActiveFrom: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -407,11 +407,11 @@ func TestAddonServiceCreate(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) spec1, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -464,11 +464,11 @@ func TestAddonServiceCreate(t *testing.T) { // Let's create a subscription cust := deps.CustomerAdapter.CreateExampleCustomer(t) spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - BillingAnchor: now, - ActiveFrom: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + BillingAnchor: now, + ActiveFrom: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -559,11 +559,11 @@ func createExampleSubscription(t *testing.T, deps subscriptiontestutils.Subscrip plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) spec1, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - BillingAnchor: currentTime, - ActiveFrom: currentTime, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + BillingAnchor: currentTime, + ActiveFrom: currentTime, + Name: "Test Subscription", }) require.Nil(t, err) diff --git a/openmeter/subscription/addon/service/list_test.go b/openmeter/subscription/addon/service/list_test.go index 84ce513e53..08663bea53 100644 --- a/openmeter/subscription/addon/service/list_test.go +++ b/openmeter/subscription/addon/service/list_test.go @@ -40,11 +40,11 @@ func TestAddonServiceGet(t *testing.T) { // Let's create a subscription cust := deps.CustomerAdapter.CreateExampleCustomer(t) spec1, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) @@ -169,11 +169,11 @@ func TestAddonServiceList(t *testing.T) { Plan: p.AsProductCatalogPlan(), Ref: &p.NamespacedID, }, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: now, - BillingAnchor: now, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: now, + BillingAnchor: now, + Name: "Test Subscription", }) require.Nil(t, err) diff --git a/openmeter/subscription/apply.go b/openmeter/subscription/apply.go index c42304f6a7..d5f8a82491 100644 --- a/openmeter/subscription/apply.go +++ b/openmeter/subscription/apply.go @@ -57,9 +57,9 @@ func NewAggregateAppliesToSpec(applieses []AppliesToSpec) AppliesToSpec { return models.ErrorWithComponent(models.ComponentName(fmt.Sprintf("patch at idx %d", i)), err) } - issues, err := models.AsValidationIssues(err) - if err != nil { - return wrapError(err) + issues, conversionErr := models.AsValidationIssues(err) + if conversionErr != nil { + return wrapError(conversionErr) } if lo.EveryBy(issues, func(issue models.ValidationIssue) bool { diff --git a/openmeter/subscription/currency.go b/openmeter/subscription/currency.go new file mode 100644 index 0000000000..96c8229cb6 --- /dev/null +++ b/openmeter/subscription/currency.go @@ -0,0 +1,108 @@ +package subscription + +import ( + "errors" + "fmt" + + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" +) + +type CostBasisMode string + +const ( + CostBasisModeDynamic CostBasisMode = "dynamic" + CostBasisModePinned CostBasisMode = "pinned" +) + +func (CostBasisMode) Values() []string { + return []string{ + string(CostBasisModeDynamic), + string(CostBasisModePinned), + } +} + +func (m CostBasisMode) OrDefault() CostBasisMode { + if m == "" { + return CostBasisModeDynamic + } + + return m +} + +func (m CostBasisMode) Validate() error { + switch m.OrDefault() { + case CostBasisModeDynamic, CostBasisModePinned: + return nil + default: + return fmt.Errorf("invalid cost basis mode %q", m) + } +} + +func (m CostBasisMode) IsPinned() bool { + return m.OrDefault() == CostBasisModePinned +} + +type CostBasisPin struct { + models.NamespacedID + models.ManagedModel + + CustomCurrencyID string `json:"customCurrencyId"` + InvoiceCurrency currencyx.Code `json:"invoiceCurrency"` + CostBasis currencies.CostBasis `json:"costBasis"` +} + +func (p CostBasisPin) Validate() error { + var errs []error + + if err := p.NamespacedID.Validate(); err != nil { + errs = append(errs, err) + } + + if p.CustomCurrencyID == "" { + errs = append(errs, errors.New("custom currency ID is required")) + } + + if err := p.InvoiceCurrency.Validate(); err != nil { + errs = append(errs, fmt.Errorf("invoice currency: %w", err)) + } else if !p.InvoiceCurrency.IsFiat() { + errs = append(errs, errors.New("invoice currency must be fiat")) + } + + if p.CostBasis.ID == "" { + errs = append(errs, errors.New("cost basis is required")) + } else { + if p.CostBasis.CurrencyID != p.CustomCurrencyID { + errs = append(errs, errors.New("cost basis custom currency does not match pin")) + } + if currencyx.Code(p.CostBasis.FiatCode) != p.InvoiceCurrency { + errs = append(errs, errors.New("cost basis fiat currency does not match pin")) + } + } + + return models.NewNillableGenericValidationError(errors.Join(errs...)) +} + +func (s SubscriptionSpec) HasCustomCurrencyBillables() bool { + for _, phase := range s.Phases { + if phase == nil { + continue + } + + for _, items := range phase.ItemsByKey { + for _, item := range items { + if item == nil || item.RateCard == nil { + continue + } + + meta := item.RateCard.AsMeta() + if meta.Price != nil && meta.Currency != nil && meta.Currency.IsCustom() { + return true + } + } + } + } + + return false +} diff --git a/openmeter/subscription/currency_validation_test.go b/openmeter/subscription/currency_validation_test.go new file mode 100644 index 0000000000..c210c31e20 --- /dev/null +++ b/openmeter/subscription/currency_validation_test.go @@ -0,0 +1,128 @@ +package subscription + +import ( + "testing" + + "github.com/alpacahq/alpacadecimal" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestSubscriptionSpecValidateCurrencies(t *testing.T) { + managedCustom := currencies.Currency{ + NamespacedID: models.NamespacedID{Namespace: "default", ID: "01J00000000000000000000000"}, + Code: "CREDITS", + Name: "Credits", + } + + newSpec := func(invoiceCurrency currencyx.Code, itemCurrency currencyx.CurrencyIdentity, priced bool) SubscriptionSpec { + meta := productcatalog.RateCardMeta{Key: "fee", Name: "Fee", Currency: itemCurrency} + if priced { + meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{Amount: alpacadecimal.NewFromInt(1)}) + } + + return SubscriptionSpec{ + CreateSubscriptionCustomerInput: CreateSubscriptionCustomerInput{InvoiceCurrency: invoiceCurrency}, + CreateSubscriptionPlanInput: CreateSubscriptionPlanInput{ + SettlementMode: productcatalog.CreditThenInvoiceSettlementMode, + }, + Phases: map[string]*SubscriptionPhaseSpec{ + "default": { + CreateSubscriptionPhasePlanInput: CreateSubscriptionPhasePlanInput{PhaseKey: "default"}, + ItemsByKey: map[string][]*SubscriptionItemSpec{ + "fee": {{ + CreateSubscriptionItemInput: CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: CreateSubscriptionItemPlanInput{ + PhaseKey: "default", + ItemKey: "fee", + RateCard: &productcatalog.FlatFeeRateCard{RateCardMeta: meta}, + }, + }, + }}, + }, + }, + }, + } + } + + tests := []struct { + name string + spec SubscriptionSpec + costBasisMode CostBasisMode + settlementMode productcatalog.SettlementMode + wantErr bool + }{ + { + name: "matching fiat item", + spec: newSpec("USD", currencyx.Code("USD"), true), + }, + { + name: "invoice currency must be fiat", + spec: newSpec("CREDITS", managedCustom, true), + wantErr: true, + }, + { + name: "priced item must have materialized currency", + spec: newSpec("USD", nil, true), + wantErr: true, + }, + { + name: "fiat item must match invoice currency", + spec: newSpec("USD", currencyx.Code("EUR"), true), + wantErr: true, + }, + { + name: "managed custom item", + spec: newSpec("USD", managedCustom, true), + }, + { + name: "code-only custom item is not persisted identity", + spec: newSpec("USD", currencyx.Code("CREDITS"), true), + wantErr: true, + }, + { + name: "unpriced item cannot have currency", + spec: newSpec("USD", currencyx.Code("USD"), false), + wantErr: true, + }, + { + name: "credit only accepts dynamic mode", + spec: newSpec("USD", managedCustom, true), + settlementMode: productcatalog.CreditOnlySettlementMode, + }, + { + name: "credit only rejects pinned mode", + spec: newSpec("USD", managedCustom, true), + settlementMode: productcatalog.CreditOnlySettlementMode, + costBasisMode: CostBasisModePinned, + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // given: + // - a subscription invoice fiat, settlement mode, and materialized item currency + // when: + // - currency-only spec invariants are validated locally + // then: + // - persisted identities and fiat compatibility are enforced without DB access + tt.spec.CostBasisMode = tt.costBasisMode + if tt.settlementMode != "" { + tt.spec.CreateSubscriptionPlanInput.SettlementMode = tt.settlementMode + } + + err := tt.spec.validateCurrencies() + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} diff --git a/openmeter/subscription/errors_test.go b/openmeter/subscription/errors_test.go index 4512ab220c..3948d61f43 100644 --- a/openmeter/subscription/errors_test.go +++ b/openmeter/subscription/errors_test.go @@ -56,11 +56,11 @@ func TestSubscriptionSpecValidation(t *testing.T) { }, }, CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{ - Name: "test", - CustomerId: "test", - Currency: currencyx.Code("USD"), - ActiveFrom: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), - BillingAnchor: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), + Name: "test", + CustomerId: "test", + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), + BillingAnchor: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), }, Phases: map[string]*subscription.SubscriptionPhaseSpec{ "phase1": { @@ -86,6 +86,7 @@ func TestSubscriptionSpecValidation(t *testing.T) { Price: productcatalog.NewPriceFrom(productcatalog.UnitPrice{ Amount: alpacadecimal.NewFromFloat(100), }), + Currency: currencyx.Code("USD"), EntitlementTemplate: func() *productcatalog.EntitlementTemplate { et := productcatalog.EntitlementTemplate{} et.FromMetered(productcatalog.MeteredEntitlementTemplate{ @@ -126,11 +127,11 @@ func TestSubscriptionSpecValidation(t *testing.T) { }, }, CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{ - Name: "test", - CustomerId: "test", - Currency: currencyx.Code("USD"), - ActiveFrom: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), - BillingAnchor: time.Date(2025, 1, 1, 5, 0, 0, 0, time.UTC), // will error + Name: "test", + CustomerId: "test", + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC), + BillingAnchor: time.Date(2025, 1, 1, 5, 0, 0, 0, time.UTC), // will error }, Phases: map[string]*subscription.SubscriptionPhaseSpec{ "phase1": { @@ -156,6 +157,7 @@ func TestSubscriptionSpecValidation(t *testing.T) { Price: productcatalog.NewPriceFrom(productcatalog.UnitPrice{ Amount: alpacadecimal.NewFromFloat(100), }), + Currency: currencyx.Code("USD"), EntitlementTemplate: func() *productcatalog.EntitlementTemplate { et := productcatalog.EntitlementTemplate{} et.FromMetered(productcatalog.MeteredEntitlementTemplate{ diff --git a/openmeter/subscription/events_test.go b/openmeter/subscription/events_test.go new file mode 100644 index 0000000000..cf0ac46896 --- /dev/null +++ b/openmeter/subscription/events_test.go @@ -0,0 +1,42 @@ +package subscription + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/pkg/currencyx" +) + +func TestSubscriptionEventInvoiceCurrencyWireCompatibility(t *testing.T) { + // given: + // - a subscription.created.v1 payload written before Currency was renamed in Go + // when: + // - the current event model replays and rewrites that payload + // then: + // - both the entity and spec retain the invoice fiat under the stable v1 key + const legacyPayload = `{ + "subscription": {"currency": "USD"}, + "spec": {"currency": "USD"} + }` + + var event CreatedEvent + require.NoError(t, json.Unmarshal([]byte(legacyPayload), &event)) + require.Equal(t, currencyx.Code("USD"), event.Subscription.InvoiceCurrency) + require.Equal(t, currencyx.Code("USD"), event.Spec.InvoiceCurrency) + + payload, err := json.Marshal(event) + require.NoError(t, err) + + var serialized map[string]any + require.NoError(t, json.Unmarshal(payload, &serialized)) + + serializedSubscription := serialized["subscription"].(map[string]any) + require.Equal(t, "USD", serializedSubscription["currency"]) + require.NotContains(t, serializedSubscription, "invoiceCurrency") + + serializedSpec := serialized["spec"].(map[string]any) + require.Equal(t, "USD", serializedSpec["currency"]) + require.NotContains(t, serializedSpec, "invoiceCurrency") +} diff --git a/openmeter/subscription/patch/patch_test.go b/openmeter/subscription/patch/patch_test.go index e44ca06641..73a997df88 100644 --- a/openmeter/subscription/patch/patch_test.go +++ b/openmeter/subscription/patch/patch_test.go @@ -204,11 +204,11 @@ func getDefaultSpec(t *testing.T, activeFrom time.Time) (*subscription.Subscript } spec, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - Name: "Test Plan", - CustomerId: "test_customer", - Currency: currencyx.Code("USD"), - ActiveFrom: activeFrom, - BillingAnchor: activeFrom, + Name: "Test Plan", + CustomerId: "test_customer", + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: activeFrom, + BillingAnchor: activeFrom, }) require.Nil(t, err) diff --git a/openmeter/subscription/repo/mapping.go b/openmeter/subscription/repo/mapping.go index c17d579012..ab82f34fa7 100644 --- a/openmeter/subscription/repo/mapping.go +++ b/openmeter/subscription/repo/mapping.go @@ -42,6 +42,15 @@ func MapDBSubscription(sub *db.Subscription) (subscription.Subscription, error) annotations = models.Annotations{} } + costBasisPins := make([]subscription.CostBasisPin, 0, len(sub.Edges.CostBasisPins)) + for _, pin := range sub.Edges.CostBasisPins { + mapped, err := FromDBSubscriptionCostBasisPin(pin) + if err != nil { + return subscription.Subscription{}, fmt.Errorf("mapping subscription cost basis pin: %w", err) + } + costBasisPins = append(costBasisPins, mapped) + } + return subscription.Subscription{ NamespacedID: models.NamespacedID{ ID: sub.ID, @@ -64,7 +73,9 @@ func MapDBSubscription(sub *db.Subscription) (subscription.Subscription, error) Name: sub.Name, Description: sub.Description, CustomerId: sub.CustomerID, - Currency: sub.Currency, + InvoiceCurrency: sub.InvoiceCurrency, + CostBasisMode: subscription.CostBasisMode(sub.CostBasisMode), + CostBasisPins: costBasisPins, BillingCadence: billingCadence, ProRatingConfig: sub.ProRatingConfig, SettlementMode: sub.SettlementMode, @@ -72,6 +83,38 @@ func MapDBSubscription(sub *db.Subscription) (subscription.Subscription, error) }, nil } +func FromDBSubscriptionCostBasisPin(pin *db.SubscriptionCostBasisPin) (subscription.CostBasisPin, error) { + if pin == nil { + return subscription.CostBasisPin{}, fmt.Errorf("unexpected nil subscription cost basis pin") + } + + costBasis, err := pin.Edges.CostBasisOrErr() + if err != nil { + return subscription.CostBasisPin{}, fmt.Errorf("cost basis is not loaded: %w", err) + } + + mapped := subscription.CostBasisPin{ + NamespacedID: models.NamespacedID{ + Namespace: pin.Namespace, + ID: pin.ID, + }, + ManagedModel: models.ManagedModel{ + CreatedAt: pin.CreatedAt.UTC(), + UpdatedAt: pin.UpdatedAt.UTC(), + DeletedAt: convert.SafeToUTC(pin.DeletedAt), + }, + CustomCurrencyID: pin.CustomCurrencyID, + InvoiceCurrency: pin.InvoiceCurrency, + CostBasis: currencyadapter.FromDBCurrencyCostBasis(costBasis), + } + + if err := mapped.Validate(); err != nil { + return subscription.CostBasisPin{}, err + } + + return mapped, nil +} + func MapDBSubscripitonPhase(phase *db.SubscriptionPhase) (subscription.SubscriptionPhase, error) { if phase == nil { return subscription.SubscriptionPhase{}, fmt.Errorf("unexpected nil subscription phase") diff --git a/openmeter/subscription/repo/subscriptionitemrepo_test.go b/openmeter/subscription/repo/subscriptionitemrepo_test.go index ec7259bf13..a54c4c2b77 100644 --- a/openmeter/subscription/repo/subscriptionitemrepo_test.go +++ b/openmeter/subscription/repo/subscriptionitemrepo_test.go @@ -87,14 +87,14 @@ func TestSubscriptionItemCustomCurrencyPersistence(t *testing.T) { customPlan := deps.PlanHelper.CreatePlan(t, customPlanInput) customSpec, err := subscription.NewSpecFromPlan(customPlan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: customer.ID, - Currency: customPlan.Currency().GetCode(), - ActiveFrom: now, - BillingAnchor: now, - Name: customPlan.GetName(), + CustomerId: customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: now, + BillingAnchor: now, + Name: customPlan.GetName(), }) require.NoError(t, err) - require.Equal(t, customCurrency, customSpec.Currency) + require.Equal(t, currencyx.Code("USD"), customSpec.InvoiceCurrency) customPhases := customSpec.GetSortedPhases() require.Len(t, customPhases, 1) diff --git a/openmeter/subscription/repo/subscriptionrepo.go b/openmeter/subscription/repo/subscriptionrepo.go index 6235ef82d3..39e9c6aa1f 100644 --- a/openmeter/subscription/repo/subscriptionrepo.go +++ b/openmeter/subscription/repo/subscriptionrepo.go @@ -2,6 +2,7 @@ package repo import ( "context" + "errors" "fmt" "slices" "time" @@ -33,9 +34,17 @@ func NewSubscriptionRepo(db *db.Client) *subscriptionRepo { } } +func withSubscriptionReferences(q *db.SubscriptionQuery) *db.SubscriptionQuery { + return q. + WithPlan(). + WithCostBasisPins(func(q *db.SubscriptionCostBasisPinQuery) { + q.WithCostBasis() + }) +} + func (r *subscriptionRepo) SetEndOfCadence(ctx context.Context, id models.NamespacedID, at *time.Time) (*subscription.Subscription, error) { return entutils.TransactingRepo(ctx, r, func(ctx context.Context, repo *subscriptionRepo) (*subscription.Subscription, error) { - ent, err := repo.db.Subscription.UpdateOneID(id.ID).SetOrClearActiveTo(at).Where(dbsubscription.Namespace(id.Namespace)).Save(ctx) + _, err := repo.db.Subscription.UpdateOneID(id.ID).SetOrClearActiveTo(at).Where(dbsubscription.Namespace(id.Namespace)).Save(ctx) if db.IsNotFound(err) { return nil, subscription.NewSubscriptionNotFoundError( id.ID, @@ -45,6 +54,13 @@ func (r *subscriptionRepo) SetEndOfCadence(ctx context.Context, id models.Namesp return nil, err } + ent, err := withSubscriptionReferences(repo.db.Subscription.Query()). + Where(dbsubscription.ID(id.ID), dbsubscription.Namespace(id.Namespace)). + Only(ctx) + if err != nil { + return nil, err + } + sub, err := MapDBSubscription(ent) return lo.ToPtr(sub), err @@ -53,7 +69,7 @@ func (r *subscriptionRepo) SetEndOfCadence(ctx context.Context, id models.Namesp func (r *subscriptionRepo) UpdateAnnotations(ctx context.Context, id models.NamespacedID, annotations models.Annotations) (*subscription.Subscription, error) { return entutils.TransactingRepo(ctx, r, func(ctx context.Context, repo *subscriptionRepo) (*subscription.Subscription, error) { - ent, err := repo.db.Subscription.UpdateOneID(id.ID).SetAnnotations(annotations).Where(dbsubscription.Namespace(id.Namespace)).Save(ctx) + _, err := repo.db.Subscription.UpdateOneID(id.ID).SetAnnotations(annotations).Where(dbsubscription.Namespace(id.Namespace)).Save(ctx) if db.IsNotFound(err) { return nil, subscription.NewSubscriptionNotFoundError( id.ID, @@ -63,6 +79,13 @@ func (r *subscriptionRepo) UpdateAnnotations(ctx context.Context, id models.Name return nil, err } + ent, err := withSubscriptionReferences(repo.db.Subscription.Query()). + Where(dbsubscription.ID(id.ID), dbsubscription.Namespace(id.Namespace)). + Only(ctx) + if err != nil { + return nil, err + } + sub, err := MapDBSubscription(ent) return lo.ToPtr(sub), err @@ -71,7 +94,7 @@ func (r *subscriptionRepo) UpdateAnnotations(ctx context.Context, id models.Name func (r *subscriptionRepo) GetByID(ctx context.Context, subscriptionID models.NamespacedID) (subscription.Subscription, error) { return entutils.TransactingRepo(ctx, r, func(ctx context.Context, repo *subscriptionRepo) (subscription.Subscription, error) { - res, err := repo.db.Subscription.Query().WithPlan().Where(dbsubscription.ID(subscriptionID.ID), dbsubscription.Namespace(subscriptionID.Namespace)).Where(SubscriptionNotDeletedAt(clock.Now())...).First(ctx) + res, err := withSubscriptionReferences(repo.db.Subscription.Query()).Where(dbsubscription.ID(subscriptionID.ID), dbsubscription.Namespace(subscriptionID.Namespace)).Where(SubscriptionNotDeletedAt(clock.Now())...).First(ctx) if db.IsNotFound(err) { return subscription.Subscription{}, subscription.NewSubscriptionNotFoundError( @@ -92,7 +115,8 @@ func (r *subscriptionRepo) Create(ctx context.Context, sub subscription.CreateSu command := repo.db.Subscription.Create(). SetNamespace(sub.Namespace). SetCustomerID(sub.CustomerId). - SetCurrency(sub.Currency). + SetInvoiceCurrency(sub.InvoiceCurrency). + SetCostBasisMode(dbsubscription.CostBasisMode(sub.CostBasisMode.OrDefault())). SetBillingCadence(sub.BillingCadence.ISOString()). SetProRatingConfig(sub.ProRatingConfig). SetSettlementMode(sub.SettlementMode). @@ -120,20 +144,60 @@ func (r *subscriptionRepo) Create(ctx context.Context, sub subscription.CreateSu return subscription.Subscription{}, fmt.Errorf("unexpected nil subscription") } - if res.PlanID != nil { - plan, err := repo.db.Plan.Query().Where(dbplan.ID(*res.PlanID)).First(ctx) - if err != nil { - return subscription.Subscription{}, fmt.Errorf("failed to fetch plan: %w", err) - } + res, err = withSubscriptionReferences(repo.db.Subscription.Query()). + Where(dbsubscription.ID(res.ID), dbsubscription.Namespace(res.Namespace)). + Only(ctx) + if err != nil { + return subscription.Subscription{}, fmt.Errorf("failed to reload subscription: %w", err) + } + + return MapDBSubscription(res) + }) +} - if plan == nil { - return subscription.Subscription{}, fmt.Errorf("unexpected nil plan") +func (r *subscriptionRepo) CreateCostBasisPins(ctx context.Context, inputs []subscription.CreateCostBasisPinEntityInput) error { + if len(inputs) == 0 { + return nil + } + + return entutils.TransactingRepoWithNoValue(ctx, r, func(ctx context.Context, repo *subscriptionRepo) error { + builders := make([]*db.SubscriptionCostBasisPinCreate, 0, len(inputs)) + var errs []error + + for idx, input := range inputs { + if input.Namespace == "" { + errs = append(errs, fmt.Errorf("input[%d]: namespace is required", idx)) + } + if input.SubscriptionID == "" { + errs = append(errs, fmt.Errorf("input[%d]: subscription ID is required", idx)) + } + if input.CustomCurrencyID == "" { + errs = append(errs, fmt.Errorf("input[%d]: custom currency ID is required", idx)) + } + if input.CostBasisID == "" { + errs = append(errs, fmt.Errorf("input[%d]: cost basis ID is required", idx)) + } + if err := input.InvoiceCurrency.Validate(); err != nil || !input.InvoiceCurrency.IsFiat() { + errs = append(errs, fmt.Errorf("input[%d]: invalid invoice currency %q", idx, input.InvoiceCurrency)) } - res.Edges.Plan = plan + builders = append(builders, repo.db.SubscriptionCostBasisPin.Create(). + SetNamespace(input.Namespace). + SetSubscriptionID(input.SubscriptionID). + SetCustomCurrencyID(input.CustomCurrencyID). + SetInvoiceCurrency(input.InvoiceCurrency). + SetCostBasisID(input.CostBasisID)) } - return MapDBSubscription(res) + if err := errors.Join(errs...); err != nil { + return err + } + + if _, err := repo.db.SubscriptionCostBasisPin.CreateBulk(builders...).Save(ctx); err != nil { + return fmt.Errorf("creating subscription cost basis pins: %w", err) + } + + return nil }) } @@ -155,8 +219,7 @@ func (r *subscriptionRepo) List(ctx context.Context, in subscription.ListSubscri return entutils.TransactingRepo(ctx, r, func(ctx context.Context, repo *subscriptionRepo) (subscription.SubscriptionList, error) { now := clock.Now() - query := repo.db.Subscription.Query(). - WithPlan() + query := withSubscriptionReferences(repo.db.Subscription.Query()) notDeletedAtNow := SubscriptionNotDeletedAt(now) if !in.IncludeDeleted { diff --git a/openmeter/subscription/repo/subscriptionrepo_test.go b/openmeter/subscription/repo/subscriptionrepo_test.go new file mode 100644 index 0000000000..2f59ec16a9 --- /dev/null +++ b/openmeter/subscription/repo/subscriptionrepo_test.go @@ -0,0 +1,107 @@ +package repo_test + +import ( + "testing" + "time" + + "github.com/alpacahq/alpacadecimal" + "github.com/oklog/ulid/v2" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/openmeter/subscription" + subscriptionrepo "github.com/openmeterio/openmeter/openmeter/subscription/repo" + "github.com/openmeterio/openmeter/openmeter/testutils" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/datetime" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestSubscriptionCostBasisPinPersistence(t *testing.T) { + // given: + // - a pinned subscription, custom currency, and exact cost-basis resource + // when: + // - the pair is pinned and the subscription is loaded again + // then: + // - the hydrated pin preserves the exact resource ID and protects its references + testDB := testutils.InitPostgresDB(t, testutils.PostgresDBStateEntMigrated) + db := testDB.EntDriver.Client() + t.Cleanup(func() { + _ = db.Close() + testDB.Close(t) + }) + + const namespace = "default" + at := time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC) + customerID := ulid.Make().String() + customCurrencyID := ulid.Make().String() + costBasisID := ulid.Make().String() + + _, err := db.Customer.Create(). + SetID(customerID). + SetNamespace(namespace). + SetName("Pin Test Customer"). + SetCurrency(currencyx.Code("USD")). + Save(t.Context()) + require.NoError(t, err) + + _, err = db.CustomCurrency.Create(). + SetID(customCurrencyID). + SetNamespace(namespace). + SetCode("CREDITS"). + SetName("Credits"). + SetSymbol("CR"). + Save(t.Context()) + require.NoError(t, err) + + costBasis, err := db.CurrencyCostBasis.Create(). + SetID(costBasisID). + SetNamespace(namespace). + SetCurrencyID(customCurrencyID). + SetFiatCode(currencyx.Code("USD")). + SetRate(alpacadecimal.NewFromFloat(0.25)). + SetEffectiveFrom(at.Add(-time.Hour)). + Save(t.Context()) + require.NoError(t, err) + + repository := subscriptionrepo.NewSubscriptionRepo(db) + sub, err := repository.Create(t.Context(), subscription.CreateSubscriptionEntityInput{ + NamespacedModel: models.NamespacedModel{Namespace: namespace}, + CadencedModel: models.CadencedModel{ActiveFrom: at}, + Name: "Pinned Subscription", + CustomerId: customerID, + InvoiceCurrency: currencyx.Code("USD"), + CostBasisMode: subscription.CostBasisModePinned, + BillingCadence: datetime.NewISODuration(0, 1, 0, 0, 0, 0, 0), + BillingAnchor: at, + SettlementMode: productcatalog.CreditThenInvoiceSettlementMode, + }) + require.NoError(t, err) + require.Empty(t, sub.CostBasisPins) + + pinInput := subscription.CreateCostBasisPinEntityInput{ + Namespace: namespace, + SubscriptionID: sub.ID, + CustomCurrencyID: customCurrencyID, + InvoiceCurrency: currencyx.Code("USD"), + CostBasisID: costBasisID, + } + require.NoError(t, repository.CreateCostBasisPins(t.Context(), []subscription.CreateCostBasisPinEntityInput{pinInput})) + + loaded, err := repository.GetByID(t.Context(), sub.NamespacedID) + require.NoError(t, err) + require.Len(t, loaded.CostBasisPins, 1) + require.Equal(t, customCurrencyID, loaded.CostBasisPins[0].CustomCurrencyID) + require.Equal(t, currencyx.Code("USD"), loaded.CostBasisPins[0].InvoiceCurrency) + require.Equal(t, costBasisID, loaded.CostBasisPins[0].CostBasis.ID) + require.Equal(t, costBasis.Rate, loaded.CostBasisPins[0].CostBasis.Rate) + + err = repository.CreateCostBasisPins(t.Context(), []subscription.CreateCostBasisPinEntityInput{pinInput}) + require.Error(t, err, "a subscription can only pin one resource for a custom/fiat pair") + + err = db.CurrencyCostBasis.DeleteOneID(costBasisID).Exec(t.Context()) + require.Error(t, err, "a pinned cost basis must remain lifecycle-protected") + + err = db.CustomCurrency.DeleteOneID(customCurrencyID).Exec(t.Context()) + require.Error(t, err, "a currency referenced by a pin must remain lifecycle-protected") +} diff --git a/openmeter/subscription/repository.go b/openmeter/subscription/repository.go index 03e8ce5f4d..72658e6199 100644 --- a/openmeter/subscription/repository.go +++ b/openmeter/subscription/repository.go @@ -24,8 +24,9 @@ type CreateSubscriptionEntityInput struct { Name string `json:"name,omitempty"` Description *string `json:"description,omitempty"` - CustomerId string `json:"customerId,omitempty"` - Currency currencyx.Code + CustomerId string `json:"customerId,omitempty"` + InvoiceCurrency currencyx.Code + CostBasisMode CostBasisMode // BillingCadence is the default billing cadence for subscriptions. BillingCadence datetime.ISODuration `json:"billing_cadence"` @@ -59,6 +60,16 @@ type SubscriptionRepository interface { // UpdateAnnotations updates the annotations of a subscription UpdateAnnotations(ctx context.Context, id models.NamespacedID, annotations models.Annotations) (*Subscription, error) + + CreateCostBasisPins(ctx context.Context, inputs []CreateCostBasisPinEntityInput) error +} + +type CreateCostBasisPinEntityInput struct { + Namespace string + SubscriptionID string + CustomCurrencyID string + InvoiceCurrency currencyx.Code + CostBasisID string } type CreateSubscriptionPhaseEntityInput struct { diff --git a/openmeter/subscription/service.go b/openmeter/subscription/service.go index 23958c8912..7b12d3e724 100644 --- a/openmeter/subscription/service.go +++ b/openmeter/subscription/service.go @@ -2,6 +2,7 @@ package subscription import ( "context" + "time" "github.com/openmeterio/openmeter/pkg/models" ) @@ -23,7 +24,7 @@ type CommandService interface { // Create a new subscription accotding to the given spec Create(ctx context.Context, namespace string, spec SubscriptionSpec) (Subscription, error) // Update the subscription with the given ID to the target spec - Update(ctx context.Context, subscriptionID models.NamespacedID, target SubscriptionSpec) (Subscription, error) + Update(ctx context.Context, subscriptionID models.NamespacedID, target SubscriptionSpec, options ...UpdateOption) (Subscription, error) // Delete a scheduled subscription with the given ID Delete(ctx context.Context, subscriptionID models.NamespacedID) error // Cancel a running subscription at the provided time @@ -34,6 +35,18 @@ type CommandService interface { UpdateAnnotations(ctx context.Context, subscriptionID models.NamespacedID, annotations models.Annotations) (*Subscription, error) } +type UpdateOptions struct { + CostBasisEffectiveAt time.Time +} + +type UpdateOption func(*UpdateOptions) + +func WithCostBasisEffectiveAt(at time.Time) UpdateOption { + return func(options *UpdateOptions) { + options.CostBasisEffectiveAt = at + } +} + type Service interface { QueryService CommandService diff --git a/openmeter/subscription/service/cost_basis_persistence_test.go b/openmeter/subscription/service/cost_basis_persistence_test.go new file mode 100644 index 0000000000..4854e35196 --- /dev/null +++ b/openmeter/subscription/service/cost_basis_persistence_test.go @@ -0,0 +1,255 @@ +package service_test + +import ( + "testing" + "time" + + "github.com/alpacahq/alpacadecimal" + "github.com/oklog/ulid/v2" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/openmeter/subscription" + subscriptiontestutils "github.com/openmeterio/openmeter/openmeter/subscription/testutils" + "github.com/openmeterio/openmeter/pkg/clock" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/datetime" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestSubscriptionCreationPersistsCostBasisPinsByMode(t *testing.T) { + tests := []struct { + name string + mode subscription.CostBasisMode + expectedPins int + }{ + { + name: "pinned stores one deduplicated pair", + mode: subscription.CostBasisModePinned, + expectedPins: 1, + }, + { + name: "dynamic stores no pins", + mode: subscription.CostBasisModeDynamic, + expectedPins: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // given: + // - two priced items sharing one custom-currency to invoice-fiat pair + // when: + // - a subscription is created in pinned or dynamic mode + // then: + // - only pinned mode stores the exact pair, and it stores it once + currentTime := time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC) + clock.FreezeTime(currentTime) + defer clock.UnFreeze() + + dbDeps := subscriptiontestutils.SetupDBDeps(t) + defer dbDeps.Cleanup(t) + deps := subscriptiontestutils.NewService(t, dbDeps) + + customer := deps.CustomerAdapter.CreateExampleCustomer(t) + customCurrency, err := deps.CurrencyService.CreateCurrency(t.Context(), currencies.CreateCurrencyInput{ + Namespace: subscriptiontestutils.ExampleNamespace, + Code: "CREDITS", + Name: "Credits", + Symbol: "CR", + }) + require.NoError(t, err) + + costBasisID := ulid.Make().String() + _, err = dbDeps.DBClient.CurrencyCostBasis.Create(). + SetID(costBasisID). + SetNamespace(subscriptiontestutils.ExampleNamespace). + SetCurrencyID(customCurrency.ID). + SetFiatCode(currencyx.Code("USD")). + SetRate(alpacadecimal.NewFromFloat(0.5)). + SetEffectiveFrom(currentTime.Add(-time.Hour)). + Save(t.Context()) + require.NoError(t, err) + + planInput := subscriptiontestutils.BuildTestPlanInput(t). + AddPhase(nil, newCostBasisTestRateCard(t, "fee-one", nil), newCostBasisTestRateCard(t, "fee-two", nil)). + Build() + planInput.Plan.Currency = customCurrency + plan := deps.PlanHelper.CreatePlan(t, planInput) + + spec, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ + CustomerId: customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + CostBasisMode: tt.mode, + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Cost Basis Subscription", + Annotations: models.Annotations{}, + }) + require.NoError(t, err) + + created, err := deps.SubscriptionService.Create(t.Context(), subscriptiontestutils.ExampleNamespace, spec) + require.NoError(t, err) + require.Len(t, created.CostBasisPins, tt.expectedPins) + + loaded, err := deps.SubscriptionService.Get(t.Context(), created.NamespacedID) + require.NoError(t, err) + require.Len(t, loaded.CostBasisPins, tt.expectedPins) + if tt.expectedPins == 0 { + return + } + + require.Equal(t, customCurrency.ID, loaded.CostBasisPins[0].CustomCurrencyID) + require.Equal(t, currencyx.Code("USD"), loaded.CostBasisPins[0].InvoiceCurrency) + require.Equal(t, costBasisID, loaded.CostBasisPins[0].CostBasis.ID) + }) + } +} + +func TestSubscriptionUpdatePinsNewCostBasisPairAtEffectiveTime(t *testing.T) { + // given: + // - a pinned subscription with one custom-currency pair + // - a second custom currency whose cost basis changes exactly at edit time + // when: + // - an update introduces the second pair with that explicit effective time + // then: + // - the original pin is retained and only the newly effective cost basis is appended + currentTime := time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC) + editTime := currentTime.Add(time.Hour) + clock.FreezeTime(currentTime) + defer clock.UnFreeze() + + dbDeps := subscriptiontestutils.SetupDBDeps(t) + defer dbDeps.Cleanup(t) + deps := subscriptiontestutils.NewService(t, dbDeps) + + customer := deps.CustomerAdapter.CreateExampleCustomer(t) + creditsCurrency, err := deps.CurrencyService.CreateCurrency(t.Context(), currencies.CreateCurrencyInput{ + Namespace: subscriptiontestutils.ExampleNamespace, + Code: "CREDITS", + Name: "Credits", + Symbol: "CR", + }) + require.NoError(t, err) + pointsCurrency, err := deps.CurrencyService.CreateCurrency(t.Context(), currencies.CreateCurrencyInput{ + Namespace: subscriptiontestutils.ExampleNamespace, + Code: "POINTS", + Name: "Points", + Symbol: "PT", + }) + require.NoError(t, err) + + creditsCostBasisID := ulid.Make().String() + _, err = dbDeps.DBClient.CurrencyCostBasis.Create(). + SetID(creditsCostBasisID). + SetNamespace(subscriptiontestutils.ExampleNamespace). + SetCurrencyID(creditsCurrency.ID). + SetFiatCode(currencyx.Code("USD")). + SetRate(alpacadecimal.NewFromFloat(0.5)). + SetEffectiveFrom(currentTime.Add(-time.Hour)). + Save(t.Context()) + require.NoError(t, err) + + oldPointsCostBasisID := ulid.Make().String() + _, err = dbDeps.DBClient.CurrencyCostBasis.Create(). + SetID(oldPointsCostBasisID). + SetNamespace(subscriptiontestutils.ExampleNamespace). + SetCurrencyID(pointsCurrency.ID). + SetFiatCode(currencyx.Code("USD")). + SetRate(alpacadecimal.NewFromFloat(0.25)). + SetEffectiveFrom(currentTime.Add(-time.Hour)). + SetEffectiveTo(editTime). + Save(t.Context()) + require.NoError(t, err) + + newPointsCostBasisID := ulid.Make().String() + _, err = dbDeps.DBClient.CurrencyCostBasis.Create(). + SetID(newPointsCostBasisID). + SetNamespace(subscriptiontestutils.ExampleNamespace). + SetCurrencyID(pointsCurrency.ID). + SetFiatCode(currencyx.Code("USD")). + SetRate(alpacadecimal.NewFromFloat(0.75)). + SetEffectiveFrom(editTime). + Save(t.Context()) + require.NoError(t, err) + + planInput := subscriptiontestutils.BuildTestPlanInput(t). + AddPhase(nil, newCostBasisTestRateCard(t, "credits-fee", nil)). + Build() + planInput.Plan.Currency = creditsCurrency + plan := deps.PlanHelper.CreatePlan(t, planInput) + + spec, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ + CustomerId: customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + CostBasisMode: subscription.CostBasisModePinned, + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Pinned Cost Basis Update", + Annotations: models.Annotations{}, + }) + require.NoError(t, err) + + created, err := deps.SubscriptionService.Create(t.Context(), subscriptiontestutils.ExampleNamespace, spec) + require.NoError(t, err) + require.Len(t, created.CostBasisPins, 1) + require.Equal(t, creditsCostBasisID, created.CostBasisPins[0].CostBasis.ID) + originalPinID := created.CostBasisPins[0].ID + + view, err := deps.SubscriptionService.GetView(t.Context(), created.NamespacedID) + require.NoError(t, err) + updatedSpec := view.AsSpec() + phase := updatedSpec.GetSortedPhases()[0] + phase.ItemsByKey["points-fee"] = []*subscription.SubscriptionItemSpec{ + { + CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ + PhaseKey: phase.PhaseKey, + ItemKey: "points-fee", + RateCard: newCostBasisTestRateCard(t, "points-fee", pointsCurrency), + }, + Annotations: models.Annotations{}, + }, + }, + } + + _, err = deps.SubscriptionService.Update( + t.Context(), + created.NamespacedID, + updatedSpec, + subscription.WithCostBasisEffectiveAt(editTime), + ) + require.NoError(t, err) + + loaded, err := deps.SubscriptionService.Get(t.Context(), created.NamespacedID) + require.NoError(t, err) + require.Len(t, loaded.CostBasisPins, 2) + + pinsByCustomCurrencyID := map[string]subscription.CostBasisPin{} + for _, pin := range loaded.CostBasisPins { + pinsByCustomCurrencyID[pin.CustomCurrencyID] = pin + } + require.Equal(t, originalPinID, pinsByCustomCurrencyID[creditsCurrency.ID].ID) + require.Equal(t, creditsCostBasisID, pinsByCustomCurrencyID[creditsCurrency.ID].CostBasis.ID) + require.Equal(t, newPointsCostBasisID, pinsByCustomCurrencyID[pointsCurrency.ID].CostBasis.ID) + require.NotEqual(t, oldPointsCostBasisID, pinsByCustomCurrencyID[pointsCurrency.ID].CostBasis.ID) +} + +func newCostBasisTestRateCard(t *testing.T, key string, currencyIdentity currencyx.CurrencyIdentity) productcatalog.RateCard { + t.Helper() + + billingCadence := datetime.MustParseDuration(t, "P1M") + return &productcatalog.FlatFeeRateCard{ + RateCardMeta: productcatalog.RateCardMeta{ + Key: key, + Name: key, + Currency: currencyIdentity, + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ + Amount: alpacadecimal.NewFromInt(10), + PaymentTerm: productcatalog.InAdvancePaymentTerm, + }), + }, + BillingCadence: &billingCadence, + } +} diff --git a/openmeter/subscription/service/currency.go b/openmeter/subscription/service/currency.go new file mode 100644 index 0000000000..e80289d7cd --- /dev/null +++ b/openmeter/subscription/service/currency.go @@ -0,0 +1,120 @@ +package service + +import ( + "context" + "fmt" + "slices" + "strings" + "time" + + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/openmeter/subscription" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" +) + +type costBasisPair struct { + customCurrencyID string + invoiceCurrency string +} + +func customCostBasisPairs(spec subscription.SubscriptionSpec) map[costBasisPair]struct{} { + pairs := map[costBasisPair]struct{}{} + + for _, phase := range spec.Phases { + if phase == nil { + continue + } + + for _, items := range phase.ItemsByKey { + for _, item := range items { + if item == nil || item.RateCard == nil { + continue + } + + meta := item.RateCard.AsMeta() + if meta.Price == nil || meta.Currency == nil || !meta.Currency.IsCustom() { + continue + } + + managed, ok := meta.Currency.(currencyx.ManagedCurrency) + if !ok || managed.GetID() == "" { + continue + } + + pairs[costBasisPair{ + customCurrencyID: managed.GetID(), + invoiceCurrency: spec.InvoiceCurrency.String(), + }] = struct{}{} + } + } + } + + return pairs +} + +func (s *service) resolveCostBasisRequirements( + ctx context.Context, + namespace string, + spec subscription.SubscriptionSpec, + at time.Time, + pairs map[costBasisPair]struct{}, +) ([]currencies.CostBasis, error) { + if spec.SettlementMode != productcatalog.CreditThenInvoiceSettlementMode || len(pairs) == 0 { + return nil, nil + } + + orderedPairs := make([]costBasisPair, 0, len(pairs)) + for pair := range pairs { + orderedPairs = append(orderedPairs, pair) + } + slices.SortFunc(orderedPairs, func(a, b costBasisPair) int { + if a.customCurrencyID == b.customCurrencyID { + return strings.Compare(a.invoiceCurrency, b.invoiceCurrency) + } + return strings.Compare(a.customCurrencyID, b.customCurrencyID) + }) + + resolved := make([]currencies.CostBasis, 0, len(orderedPairs)) + for _, pair := range orderedPairs { + costBasis, err := s.CostBasisService.GetCostBasisAt(ctx, currencies.GetCostBasisAtInput{ + Namespace: namespace, + CurrencyID: pair.customCurrencyID, + FiatCode: spec.InvoiceCurrency, + At: at, + }) + if err != nil { + if models.IsGenericNotFoundError(err) { + return nil, models.NewGenericValidationError(fmt.Errorf( + "%w [custom_currency_id=%s invoice_currency=%s at=%s]", + productcatalog.ErrCurrencyCostBasisNotFound, + pair.customCurrencyID, + spec.InvoiceCurrency, + at.UTC().Format(time.RFC3339), + )) + } + + return nil, fmt.Errorf("resolving subscription cost basis: %w", err) + } + + resolved = append(resolved, costBasis) + } + + return resolved, nil +} + +func costBasisPinInputs(namespace, subscriptionID string, costBases []currencies.CostBasis) []subscription.CreateCostBasisPinEntityInput { + inputs := make([]subscription.CreateCostBasisPinEntityInput, 0, len(costBases)) + for _, costBasis := range costBases { + inputs = append(inputs, subscription.CreateCostBasisPinEntityInput{ + Namespace: namespace, + SubscriptionID: subscriptionID, + CustomCurrencyID: costBasis.CurrencyID, + InvoiceCurrency: currencyx.Code(costBasis.FiatCode), + CostBasisID: costBasis.ID, + }) + } + + return inputs +} diff --git a/openmeter/subscription/service/currency_materialization_test.go b/openmeter/subscription/service/currency_materialization_test.go deleted file mode 100644 index f0d0d9a369..0000000000 --- a/openmeter/subscription/service/currency_materialization_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package service_test - -import ( - "testing" - "time" - - "github.com/stretchr/testify/require" - - "github.com/openmeterio/openmeter/openmeter/subscription" - subscriptiontestutils "github.com/openmeterio/openmeter/openmeter/subscription/testutils" - "github.com/openmeterio/openmeter/pkg/clock" - "github.com/openmeterio/openmeter/pkg/currencyx" - "github.com/openmeterio/openmeter/pkg/models" -) - -func TestUpdateMaterializesLegacySubscriptionItemCurrency(t *testing.T) { - // given: - // - a subscription whose priced item represents a pre-backfill row with no stored currency - // when: - // - the subscription goes through the normal update path - // then: - // - the recreated item stores the subscription's existing currency without changing subscription semantics - now := time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC) - clock.FreezeTime(now) - defer clock.UnFreeze() - - deps, legacyView, legacyItemID := createSubscriptionWithLegacyPricedItemCurrency(t, now) - - _, err := deps.SubscriptionService.Update(t.Context(), legacyView.Subscription.NamespacedID, legacyView.AsSpec()) - require.NoError(t, err) - - requireMaterializedSubscriptionItemCurrency(t, deps, legacyView.Subscription.NamespacedID, legacyItemID) -} - -func TestCancelMaterializesLegacySubscriptionItemCurrency(t *testing.T) { - // given: - // - a subscription whose priced item represents a pre-backfill row with no stored currency - // when: - // - cancellation reconciles the subscription directly through sync - // then: - // - the recreated item stores the subscription's existing currency - now := time.Date(2026, 7, 17, 12, 0, 0, 0, time.UTC) - clock.FreezeTime(now) - defer clock.UnFreeze() - - deps, legacyView, legacyItemID := createSubscriptionWithLegacyPricedItemCurrency(t, now) - nextBillingCycle := subscription.TimingNextBillingCycle - - _, err := deps.SubscriptionService.Cancel(t.Context(), legacyView.Subscription.NamespacedID, subscription.Timing{ - Enum: &nextBillingCycle, - }) - require.NoError(t, err) - - requireMaterializedSubscriptionItemCurrency(t, deps, legacyView.Subscription.NamespacedID, legacyItemID) -} - -func createSubscriptionWithLegacyPricedItemCurrency(t *testing.T, now time.Time) (subscriptiontestutils.SubscriptionDependencies, subscription.SubscriptionView, string) { - t.Helper() - - dbDeps := subscriptiontestutils.SetupDBDeps(t) - require.NotNil(t, dbDeps) - t.Cleanup(func() { dbDeps.Cleanup(t) }) - - deps := subscriptiontestutils.NewService(t, dbDeps) - planInput := subscriptiontestutils.BuildTestPlanInput(t). - AddPhase(nil, subscriptiontestutils.ExampleRateCard2.Clone()). - Build() - planInput.Key = "legacy-subscription-item-currency" - plan := deps.PlanHelper.CreatePlan(t, planInput) - view := subscriptiontestutils.CreateSubscriptionFromPlan(t, &deps, plan, now) - - items := view.Phases[0].ItemsByKey[subscriptiontestutils.ExampleRateCard2.Key()] - require.Len(t, items, 1) - storedCurrency := items[0].SubscriptionItem.RateCard.AsMeta().Currency - require.NotNil(t, storedCurrency) - require.Equal(t, currencyx.Code("USD"), storedCurrency.GetCode()) - - legacyItemID := items[0].SubscriptionItem.ID - _, err := dbDeps.DBClient.SubscriptionItem. - UpdateOneID(legacyItemID). - ClearFiatCurrencyCode(). - Save(t.Context()) - require.NoError(t, err) - - legacyView, err := deps.SubscriptionService.GetView(t.Context(), view.Subscription.NamespacedID) - require.NoError(t, err) - legacyItems := legacyView.Phases[0].ItemsByKey[subscriptiontestutils.ExampleRateCard2.Key()] - require.Len(t, legacyItems, 1) - require.Nil(t, legacyItems[0].SubscriptionItem.RateCard.AsMeta().Currency) - - return deps, legacyView, legacyItemID -} - -func requireMaterializedSubscriptionItemCurrency(t *testing.T, deps subscriptiontestutils.SubscriptionDependencies, subscriptionID models.NamespacedID, legacyItemID string) { - t.Helper() - - updatedView, err := deps.SubscriptionService.GetView(t.Context(), subscriptionID) - require.NoError(t, err) - updatedItems := updatedView.Phases[0].ItemsByKey[subscriptiontestutils.ExampleRateCard2.Key()] - require.Len(t, updatedItems, 1) - require.NotEqual(t, legacyItemID, updatedItems[0].SubscriptionItem.ID) - - updatedCurrency := updatedItems[0].SubscriptionItem.RateCard.AsMeta().Currency - require.NotNil(t, updatedCurrency) - require.Equal(t, currencyx.Code("USD"), updatedCurrency.GetCode()) -} diff --git a/openmeter/subscription/service/currency_test.go b/openmeter/subscription/service/currency_test.go new file mode 100644 index 0000000000..4b9a8fbaa6 --- /dev/null +++ b/openmeter/subscription/service/currency_test.go @@ -0,0 +1,154 @@ +package service + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/alpacahq/alpacadecimal" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/openmeter/subscription" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" + "github.com/openmeterio/openmeter/pkg/pagination" +) + +type costBasisServiceStub struct { + get func(context.Context, currencies.GetCostBasisAtInput) (currencies.CostBasis, error) +} + +func (costBasisServiceStub) CreateCostBasis(context.Context, currencies.CreateCostBasisInput) (currencies.CostBasis, error) { + return currencies.CostBasis{}, errors.New("not implemented") +} + +func (costBasisServiceStub) ListCostBases(context.Context, currencies.ListCostBasesInput) (pagination.Result[currencies.CostBasis], error) { + return pagination.Result[currencies.CostBasis]{}, errors.New("not implemented") +} + +func (s costBasisServiceStub) GetCostBasisAt(ctx context.Context, input currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + return s.get(ctx, input) +} + +func TestResolveCostBasisRequirements(t *testing.T) { + const ( + namespace = "default" + customCurrencyID = "01J00000000000000000000000" + costBasisID = "01J00000000000000000000001" + ) + + at := time.Date(2026, 3, 1, 12, 0, 0, 0, time.UTC) + customCurrency := currencies.Currency{ + NamespacedID: models.NamespacedID{Namespace: namespace, ID: customCurrencyID}, + Code: "CREDITS", + Name: "Credits", + } + + newSpec := func(mode productcatalog.SettlementMode) subscription.SubscriptionSpec { + newItem := func(key string) *subscription.SubscriptionItemSpec { + return &subscription.SubscriptionItemSpec{ + CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ + PhaseKey: "default", + ItemKey: key, + RateCard: &productcatalog.FlatFeeRateCard{RateCardMeta: productcatalog.RateCardMeta{ + Key: key, + Name: key, + Currency: customCurrency, + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ + Amount: alpacadecimal.NewFromInt(1), + }), + }}, + }, + }, + } + } + + return subscription.SubscriptionSpec{ + CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{InvoiceCurrency: currencyx.Code("USD")}, + CreateSubscriptionPlanInput: subscription.CreateSubscriptionPlanInput{ + SettlementMode: mode, + }, + Phases: map[string]*subscription.SubscriptionPhaseSpec{ + "default": { + CreateSubscriptionPhasePlanInput: subscription.CreateSubscriptionPhasePlanInput{PhaseKey: "default"}, + ItemsByKey: map[string][]*subscription.SubscriptionItemSpec{ + "one": {newItem("one")}, + "two": {newItem("two")}, + }, + }, + }, + } + } + + t.Run("credit only does not look up cost basis", func(t *testing.T) { + // given: + // - a credit-only subscription with custom-currency priced items + // when: + // - cost-basis requirements are resolved + // then: + // - no fiat conversion lookup is performed + calls := 0 + svc := service{ServiceConfig: ServiceConfig{CostBasisService: costBasisServiceStub{get: func(context.Context, currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + calls++ + return currencies.CostBasis{}, nil + }}}} + spec := newSpec(productcatalog.CreditOnlySettlementMode) + + resolved, err := svc.resolveCostBasisRequirements(t.Context(), namespace, spec, at, customCostBasisPairs(spec)) + require.NoError(t, err) + require.Empty(t, resolved) + require.Zero(t, calls) + }) + + t.Run("credit then invoice resolves each pair once at the requested time", func(t *testing.T) { + // given: + // - two items sharing one custom-currency to invoice-fiat pair + // when: + // - conversion eligibility is validated + // then: + // - one exact cost-basis resource is selected at the supplied effective time + calls := 0 + svc := service{ServiceConfig: ServiceConfig{CostBasisService: costBasisServiceStub{get: func(_ context.Context, input currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + calls++ + require.Equal(t, namespace, input.Namespace) + require.Equal(t, customCurrencyID, input.CurrencyID) + require.Equal(t, currencyx.Code("USD"), input.FiatCode) + require.Equal(t, at, input.At) + return currencies.CostBasis{ + NamespacedID: models.NamespacedID{Namespace: namespace, ID: costBasisID}, + CurrencyID: customCurrencyID, + FiatCode: "USD", + EffectiveFrom: at.Add(-time.Hour), + }, nil + }}}} + spec := newSpec(productcatalog.CreditThenInvoiceSettlementMode) + + resolved, err := svc.resolveCostBasisRequirements(t.Context(), namespace, spec, at, customCostBasisPairs(spec)) + require.NoError(t, err) + require.Len(t, resolved, 1) + require.Equal(t, costBasisID, resolved[0].ID) + require.Equal(t, 1, calls) + }) + + t.Run("missing cost basis is a validation error", func(t *testing.T) { + // given: + // - a credit-then-invoice subscription without an effective mapping + // when: + // - conversion eligibility is validated + // then: + // - subscription creation fails as invalid input + svc := service{ServiceConfig: ServiceConfig{CostBasisService: costBasisServiceStub{get: func(context.Context, currencies.GetCostBasisAtInput) (currencies.CostBasis, error) { + return currencies.CostBasis{}, models.NewGenericNotFoundError(errors.New("missing")) + }}}} + spec := newSpec(productcatalog.CreditThenInvoiceSettlementMode) + + _, err := svc.resolveCostBasisRequirements(t.Context(), namespace, spec, at, customCostBasisPairs(spec)) + require.Error(t, err) + require.True(t, models.IsGenericValidationError(err)) + require.ErrorIs(t, err, productcatalog.ErrCurrencyCostBasisNotFound) + }) +} diff --git a/openmeter/subscription/service/service.go b/openmeter/subscription/service/service.go index 797703a857..56b0ddb146 100644 --- a/openmeter/subscription/service/service.go +++ b/openmeter/subscription/service/service.go @@ -8,6 +8,7 @@ import ( "github.com/samber/lo" + "github.com/openmeterio/openmeter/openmeter/currencies" "github.com/openmeterio/openmeter/openmeter/customer" "github.com/openmeterio/openmeter/openmeter/productcatalog/feature" "github.com/openmeterio/openmeter/openmeter/subscription" @@ -38,12 +39,17 @@ type ServiceConfig struct { Lockr *lockr.Locker FeatureFlags ffx.Service TaxCode taxcode.Service + CostBasisService currencies.CostBasisService // Hooks Hooks []subscription.SubscriptionCommandHook } func New(conf ServiceConfig) (subscription.Service, error) { + if conf.CostBasisService == nil { + return nil, errors.New("cost basis service is required") + } + svc := &service{ ServiceConfig: conf, } @@ -102,7 +108,7 @@ func (s *service) Create(ctx context.Context, namespace string, spec subscriptio ctx = subscription.NewSubscriptionOperationContext(ctx) def := subscription.Subscription{} - if err := spec.MaterializeRateCardCurrencies(spec.Currency); err != nil { + if err := spec.MaterializeRateCardCurrencies(spec.InvoiceCurrency); err != nil { return def, fmt.Errorf("failed to materialize subscription item currencies: %w", err) } @@ -146,12 +152,23 @@ func (s *service) Create(ctx context.Context, namespace string, spec subscriptio return def, fmt.Errorf("failed to validate subscription: %w", err) } + costBases, err := s.resolveCostBasisRequirements(ctx, namespace, spec, spec.ActiveFrom, customCostBasisPairs(spec)) + if err != nil { + return def, err + } + // Create subscription entity sub, err := s.SubscriptionRepo.Create(ctx, spec.ToCreateSubscriptionEntityInput(namespace)) if err != nil { return def, fmt.Errorf("failed to create subscription: %w", err) } + if spec.CostBasisMode.IsPinned() { + if err := s.SubscriptionRepo.CreateCostBasisPins(ctx, costBasisPinInputs(namespace, sub.ID, costBases)); err != nil { + return def, fmt.Errorf("failed to create subscription cost basis pins: %w", err) + } + } + for _, phase := range spec.GetSortedPhases() { // Sanity check if phase == nil { @@ -192,15 +209,15 @@ func (s *service) Create(ctx context.Context, namespace string, spec subscriptio } // Return sub reference - return sub, nil + return view.Subscription, nil }) } -func (s *service) Update(ctx context.Context, subscriptionID models.NamespacedID, newSpec subscription.SubscriptionSpec) (subscription.Subscription, error) { +func (s *service) Update(ctx context.Context, subscriptionID models.NamespacedID, newSpec subscription.SubscriptionSpec, options ...subscription.UpdateOption) (subscription.Subscription, error) { ctx = subscription.NewSubscriptionOperationContext(ctx) var def subscription.Subscription - if err := newSpec.MaterializeRateCardCurrencies(newSpec.Currency); err != nil { + if err := newSpec.MaterializeRateCardCurrencies(newSpec.InvoiceCurrency); err != nil { return def, fmt.Errorf("failed to materialize subscription item currencies: %w", err) } @@ -214,10 +231,44 @@ func (s *service) Update(ctx context.Context, subscriptionID models.NamespacedID return def, err } + updateOptions := subscription.UpdateOptions{CostBasisEffectiveAt: clock.Now()} + for _, option := range options { + option(&updateOptions) + } + return transaction.Run(ctx, s.TransactionManager, func(ctx context.Context) (subscription.Subscription, error) { + if err := s.lockCustomer(ctx, view.Subscription.CustomerId); err != nil { + return def, err + } + s.mu.RLock() defer s.mu.RUnlock() + pairsToResolve := customCostBasisPairs(newSpec) + if newSpec.CostBasisMode.IsPinned() { + for _, pin := range view.Subscription.CostBasisPins { + delete(pairsToResolve, costBasisPair{ + customCurrencyID: pin.CustomCurrencyID, + invoiceCurrency: pin.InvoiceCurrency.String(), + }) + } + } else { + for pair := range customCostBasisPairs(view.Spec) { + delete(pairsToResolve, pair) + } + } + + costBases, err := s.resolveCostBasisRequirements(ctx, subscriptionID.Namespace, newSpec, updateOptions.CostBasisEffectiveAt, pairsToResolve) + if err != nil { + return def, err + } + + if newSpec.CostBasisMode.IsPinned() { + if err := s.SubscriptionRepo.CreateCostBasisPins(ctx, costBasisPinInputs(subscriptionID.Namespace, subscriptionID.ID, costBases)); err != nil { + return def, fmt.Errorf("failed to create subscription cost basis pins: %w", err) + } + } + err = errors.Join(lo.Map(s.Hooks, func(v subscription.SubscriptionCommandHook, _ int) error { return v.BeforeUpdate(ctx, subscriptionID, newSpec) })...) @@ -230,6 +281,10 @@ func (s *service) Update(ctx context.Context, subscriptionID models.NamespacedID return subs, err } + if err := s.updateCustomerCurrencyIfNotSet(ctx, subs, newSpec); err != nil { + return subs, err + } + // Let's fetch the view for event generation updatedView, err := s.GetView(ctx, subs.NamespacedID) if err != nil { @@ -248,7 +303,7 @@ func (s *service) Update(ctx context.Context, subscriptionID models.NamespacedID return subs, fmt.Errorf("failed to publish event: %w", err) } - return subs, nil + return updatedView.Subscription, nil }) } @@ -716,7 +771,7 @@ func (s *service) updateCustomerCurrencyIfNotSet(ctx context.Context, sub subscr return fmt.Errorf("customer is nil") } - // Let's set the customer's currency to the subscription currency for paid subscriptions (if not already set) + // Let's set the customer's currency to the subscription's invoice currency for paid subscriptions (if not already set) if cus.Currency == nil && currentSpec.HasBillables() { if _, err := s.CustomerService.UpdateCustomer(ctx, customer.UpdateCustomerInput{ CustomerID: cus.GetID(), @@ -727,7 +782,7 @@ func (s *service) updateCustomerCurrencyIfNotSet(ctx context.Context, sub subscr UsageAttribution: cus.UsageAttribution, PrimaryEmail: cus.PrimaryEmail, BillingAddress: cus.BillingAddress, - Currency: ¤tSpec.Currency, + Currency: ¤tSpec.InvoiceCurrency, Metadata: cus.Metadata, Annotation: cus.Annotation, }, diff --git a/openmeter/subscription/service/service_test.go b/openmeter/subscription/service/service_test.go index 80c653e448..81edf0f05e 100644 --- a/openmeter/subscription/service/service_test.go +++ b/openmeter/subscription/service/service_test.go @@ -43,12 +43,12 @@ func TestCreation(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) defaultSpecFromPlan, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", - Annotations: models.Annotations{}, + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", + Annotations: models.Annotations{}, }) require.Nil(t, err) @@ -58,7 +58,7 @@ func TestCreation(t *testing.T) { require.Equal(t, plan.ToCreateSubscriptionPlanInput().Plan, sub.PlanRef) require.Equal(t, subscriptiontestutils.ExampleNamespace, sub.Namespace) require.Equal(t, cust.ID, sub.CustomerId) - require.Equal(t, currencyx.Code("USD"), sub.Currency) + require.Equal(t, currencyx.Code("USD"), sub.InvoiceCurrency) require.NotNil(t, sub.Annotations) t.Run("Should find subscription by ID", func(t *testing.T) { @@ -72,7 +72,7 @@ func TestCreation(t *testing.T) { assert.Equal(t, sub.PlanRef, found.PlanRef) assert.Equal(t, sub.Namespace, found.Namespace) assert.Equal(t, sub.CustomerId, found.CustomerId) - assert.Equal(t, sub.Currency, found.Currency) + assert.Equal(t, sub.InvoiceCurrency, found.InvoiceCurrency) // Annotations should be initialized as empty map assert.NotNil(t, found.Annotations) assert.Equal(t, models.Annotations{}, found.Annotations) @@ -89,7 +89,7 @@ func TestCreation(t *testing.T) { assert.Equal(t, sub.PlanRef, foundSub.PlanRef) assert.Equal(t, sub.Namespace, foundSub.Namespace) assert.Equal(t, sub.CustomerId, foundSub.CustomerId) - assert.Equal(t, sub.Currency, foundSub.Currency) + assert.Equal(t, sub.InvoiceCurrency, foundSub.InvoiceCurrency) // Let's validate the spec & the view subscriptiontestutils.ValidateSpecAndView(t, defaultSpecFromPlan, found) @@ -114,12 +114,12 @@ func TestCreation(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) specWithAnnotations, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription with Annotations", - Annotations: models.Annotations{"test.key": "test.value", "another.key": float64(123)}, + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription with Annotations", + Annotations: models.Annotations{"test.key": "test.value", "another.key": float64(123)}, }) require.Nil(t, err) @@ -172,11 +172,11 @@ func TestCreation(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) defaultSpecFromPlan, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", }) require.Nil(t, err) @@ -217,11 +217,11 @@ func TestCreation(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) defaultSpecFromPlan, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", }) require.Nil(t, err) @@ -239,6 +239,66 @@ func TestCreation(t *testing.T) { assert.Equal(t, currencyx.Code("USD"), *c.Currency) }) + + t.Run("Should set customer currency when an update first introduces billables", func(t *testing.T) { + // given: + // - an unpriced subscription whose customer has no currency + // when: + // - an update first makes an item billable + // then: + // - the customer snapshots the subscription invoice currency + currentTime := testutils.GetRFC3339Time(t, "2021-01-01T00:00:11Z") + clock.FreezeTime(currentTime) + defer clock.UnFreeze() + + dbDeps := subscriptiontestutils.SetupDBDeps(t) + defer dbDeps.Cleanup(t) + + deps := subscriptiontestutils.NewService(t, dbDeps) + cust, err := deps.CustomerService.CreateCustomer(t.Context(), customer.CreateCustomerInput{ + Namespace: subscriptiontestutils.ExampleNamespace, + CustomerMutate: customer.CustomerMutate{ + Name: "Initially currency-less customer", + Key: lo.ToPtr("initially-currency-less-customer"), + }, + }) + require.NoError(t, err) + + plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.BuildTestPlanInput(t). + AddPhase(nil, subscriptiontestutils.ExampleAddonRateCard2.Clone()). + Build()) + spec, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ + CustomerId: cust.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Initially unpriced subscription", + }) + require.NoError(t, err) + + created, err := deps.SubscriptionService.Create(t.Context(), subscriptiontestutils.ExampleNamespace, spec) + require.NoError(t, err) + customerBefore, err := deps.CustomerService.GetCustomer(t.Context(), customer.GetCustomerInput{CustomerID: lo.ToPtr(cust.GetID())}) + require.NoError(t, err) + require.Nil(t, customerBefore.Currency) + + view, err := deps.SubscriptionService.GetView(t.Context(), created.NamespacedID) + require.NoError(t, err) + updatedSpec := view.AsSpec() + item := updatedSpec.GetSortedPhases()[0].ItemsByKey[subscriptiontestutils.ExampleAddonRateCard2.Key()][0] + item.RateCard = item.RateCard.Clone() + require.NoError(t, item.RateCard.ChangeMeta(func(meta productcatalog.RateCardMeta) (productcatalog.RateCardMeta, error) { + meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{Amount: alpacadecimal.NewFromInt(10)}) + return meta, nil + })) + + _, err = deps.SubscriptionService.Update(t.Context(), created.NamespacedID, updatedSpec) + require.NoError(t, err) + + customerAfter, err := deps.CustomerService.GetCustomer(t.Context(), customer.GetCustomerInput{CustomerID: lo.ToPtr(cust.GetID())}) + require.NoError(t, err) + require.Equal(t, currencyx.Code("USD"), *customerAfter.Currency) + }) } func TestCancellation(t *testing.T) { @@ -261,11 +321,11 @@ func TestCancellation(t *testing.T) { // First, let's create a subscription defaultSpecFromPlan, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", }) require.Nil(t, err) @@ -284,7 +344,7 @@ func TestCancellation(t *testing.T) { assert.Equal(t, sub.PlanRef, cancelledSub.PlanRef) assert.Equal(t, sub.Namespace, cancelledSub.Namespace) assert.Equal(t, sub.CustomerId, cancelledSub.CustomerId) - assert.Equal(t, sub.Currency, cancelledSub.Currency) + assert.Equal(t, sub.InvoiceCurrency, cancelledSub.InvoiceCurrency) assert.Equal(t, expectedCancelTime, *cancelledSub.ActiveTo) assert.Equal(t, subscription.SubscriptionStatusCanceled, cancelledSub.GetStatusAt(clock.Now())) @@ -378,11 +438,11 @@ func TestContinuing(t *testing.T) { // First, let's create a subscription defaultSpecFromPlan, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", }) require.Nil(t, err) @@ -465,11 +525,11 @@ func TestContinuing(t *testing.T) { // First, let's create a subscription spec1, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", }) require.Nil(t, err) @@ -487,11 +547,11 @@ func TestContinuing(t *testing.T) { // Third, let's create another subscription for later spec2, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: expectedCancelTime.AddDate(0, 0, 1), - BillingAnchor: expectedCancelTime.AddDate(0, 0, 1), - Name: "Test Subscription", + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: expectedCancelTime.AddDate(0, 0, 1), + BillingAnchor: expectedCancelTime.AddDate(0, 0, 1), + Name: "Test Subscription", }) require.Nil(t, err) @@ -574,11 +634,11 @@ func TestList(t *testing.T) { // Let's create some subscriptions: // - One active spec1, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust1.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", + CustomerId: cust1.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", }) require.Nil(t, err) @@ -587,11 +647,11 @@ func TestList(t *testing.T) { // - One canceled spec2, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust2.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", + CustomerId: cust2.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", }) require.Nil(t, err) @@ -605,11 +665,11 @@ func TestList(t *testing.T) { // - One inactive spec3, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust3.ID, - Currency: "USD", - ActiveFrom: currentTime.Add(-1 * time.Minute), - BillingAnchor: currentTime.Add(-1 * time.Minute), - Name: "Test Subscription", + CustomerId: cust3.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime.Add(-1 * time.Minute), + BillingAnchor: currentTime.Add(-1 * time.Minute), + Name: "Test Subscription", }) require.Nil(t, err) @@ -623,11 +683,11 @@ func TestList(t *testing.T) { // - One scheduled spec4, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust4.ID, - Currency: "USD", - ActiveFrom: currentTime.AddDate(0, 0, 3), - BillingAnchor: currentTime.AddDate(0, 0, 3), - Name: "Test Subscription", + CustomerId: cust4.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime.AddDate(0, 0, 3), + BillingAnchor: currentTime.AddDate(0, 0, 3), + Name: "Test Subscription", }) require.Nil(t, err) @@ -756,22 +816,22 @@ func TestList(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) spec1, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust1.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Alpha Subscription", + CustomerId: cust1.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Alpha Subscription", }) require.Nil(t, err) sub1, err := service.Create(ctx, subscriptiontestutils.ExampleNamespace, spec1) require.Nil(t, err) spec2, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust2.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Beta Subscription", + CustomerId: cust2.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Beta Subscription", }) require.Nil(t, err) sub2, err := service.Create(ctx, subscriptiontestutils.ExampleNamespace, spec2) @@ -1129,12 +1189,12 @@ func TestTaxCodeResolution(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) spec, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", - Annotations: models.Annotations{}, + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", + Annotations: models.Annotations{}, }) require.NoError(t, err) @@ -1197,12 +1257,12 @@ func TestTaxCodeResolution(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, subscriptiontestutils.GetExamplePlanInput(t)) spec, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", - Annotations: models.Annotations{}, + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", + Annotations: models.Annotations{}, }) require.NoError(t, err) @@ -1299,12 +1359,12 @@ func TestTaxCodeResolution(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, planInput) spec, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", - Annotations: models.Annotations{}, + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", + Annotations: models.Annotations{}, }) require.NoError(t, err) @@ -1367,12 +1427,12 @@ func TestTaxCodeResolution(t *testing.T) { plan := deps.PlanHelper.CreatePlan(t, planInput) spec, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cust.ID, - Currency: "USD", - ActiveFrom: currentTime, - BillingAnchor: currentTime, - Name: "Test Subscription", - Annotations: models.Annotations{}, + CustomerId: cust.ID, + InvoiceCurrency: "USD", + ActiveFrom: currentTime, + BillingAnchor: currentTime, + Name: "Test Subscription", + Annotations: models.Annotations{}, }) require.NoError(t, err) diff --git a/openmeter/subscription/service/servicevalidation.go b/openmeter/subscription/service/servicevalidation.go index bd7cefac41..0756c3ac7c 100644 --- a/openmeter/subscription/service/servicevalidation.go +++ b/openmeter/subscription/service/servicevalidation.go @@ -2,7 +2,6 @@ package service import ( "context" - "errors" "fmt" "github.com/samber/lo" @@ -10,11 +9,10 @@ import ( "github.com/openmeterio/openmeter/openmeter/customer" "github.com/openmeterio/openmeter/openmeter/subscription" "github.com/openmeterio/openmeter/pkg/clock" + "github.com/openmeterio/openmeter/pkg/currencyx" "github.com/openmeterio/openmeter/pkg/models" ) -var errCustomCurrencySubscriptionsNotSupported = errors.New("custom currencies are not yet supported on subscriptions") - func (s *service) validateCreate(ctx context.Context, cust customer.Customer, spec subscription.SubscriptionSpec) error { // Let's make sure the method was called properly if spec.CustomerId != cust.ID { @@ -28,10 +26,6 @@ func (s *service) validateCreate(ctx context.Context, cust customer.Customer, sp return fmt.Errorf("spec is invalid: %w", err) } - if err := validateSubscriptionUsesFiatOnly(spec); err != nil { - return err - } - // 2. Let's make sure Create is possible based on the transition rules if err := subscription.NewStateMachine(subscription.SubscriptionStatusInactive).CanTransitionOrErr(ctx, subscription.SubscriptionActionCreate); err != nil { return err @@ -39,8 +33,8 @@ func (s *service) validateCreate(ctx context.Context, cust customer.Customer, sp // 3. Let's make sure the currency is valid if spec.HasBillables() { - if cust.Currency != nil && (string(*cust.Currency) != string(spec.Currency)) { - return models.NewGenericValidationError(fmt.Errorf("currency mismatch: customer currency is %s, but subscription currency is %s", *cust.Currency, spec.Currency)) + if cust.Currency != nil && *cust.Currency != spec.InvoiceCurrency { + return models.NewGenericValidationError(fmt.Errorf("currency mismatch: customer currency is %s, but subscription invoice currency is %s", *cust.Currency, spec.InvoiceCurrency)) } } @@ -55,7 +49,27 @@ func (s *service) validateUpdate(ctx context.Context, currentView subscription.S return err } - if err := validateSubscriptionUsesFiatOnly(newSpec); err != nil { + if err := newSpec.Validate(); err != nil { + return fmt.Errorf("spec is invalid: %w", err) + } + + if currentView.Subscription.InvoiceCurrency != newSpec.InvoiceCurrency { + return models.NewGenericValidationError(fmt.Errorf( + "cannot change subscription invoice currency from %s to %s", + currentView.Subscription.InvoiceCurrency, + newSpec.InvoiceCurrency, + )) + } + + if currentView.Subscription.CostBasisMode.OrDefault() != newSpec.CostBasisMode.OrDefault() { + return models.NewGenericValidationError(fmt.Errorf( + "cannot change subscription cost basis mode from %s to %s", + currentView.Subscription.CostBasisMode.OrDefault(), + newSpec.CostBasisMode.OrDefault(), + )) + } + + if err := validateMaterializedItemCurrenciesUnchanged(currentView.Spec, newSpec); err != nil { return err } @@ -82,8 +96,8 @@ func (s *service) validateUpdate(ctx context.Context, currentView subscription.S if newSpec.HasBillables() { if cus.Currency != nil { - if string(*cus.Currency) != string(newSpec.Currency) { - return models.NewGenericValidationError(fmt.Errorf("currency mismatch: customer currency is %s, but subscription currency is %s", *cus.Currency, newSpec.Currency)) + if *cus.Currency != newSpec.InvoiceCurrency { + return models.NewGenericValidationError(fmt.Errorf("currency mismatch: customer currency is %s, but subscription invoice currency is %s", *cus.Currency, newSpec.InvoiceCurrency)) } } } @@ -91,42 +105,76 @@ func (s *service) validateUpdate(ctx context.Context, currentView subscription.S return nil } -// validateSubscriptionUsesFiatOnly is a temporary boundary while the product -// catalog can persist custom currencies but subscriptions cannot yet -// materialize or bill them. Checking both the subscription default and priced -// item overrides prevents plan changes and add-on updates from bypassing it. -func validateSubscriptionUsesFiatOnly(spec subscription.SubscriptionSpec) error { - if spec.Currency != "" && spec.Currency.IsCustom() { - return models.NewGenericValidationError(fmt.Errorf( - "%w: subscription currency is %q", - errCustomCurrencySubscriptionsNotSupported, - spec.Currency, - )) - } - - for phaseKey, phase := range spec.Phases { - if phase == nil { +func validateMaterializedItemCurrenciesUnchanged(currentSpec, newSpec subscription.SubscriptionSpec) error { + for phaseKey, currentPhase := range currentSpec.Phases { + newPhase, ok := newSpec.Phases[phaseKey] + if !ok || currentPhase == nil || newPhase == nil { continue } - for itemKey, items := range phase.ItemsByKey { - for _, item := range items { - if item == nil || item.RateCard == nil { + for itemKey, currentItems := range currentPhase.ItemsByKey { + newItems, ok := newPhase.ItemsByKey[itemKey] + if !ok { + continue + } + + var establishedCurrency currencyx.CurrencyIdentity + for _, currentItem := range currentItems { + if currentItem == nil || currentItem.RateCard == nil { + continue + } + + meta := currentItem.RateCard.AsMeta() + if meta.Price != nil && meta.Currency != nil { + establishedCurrency = meta.Currency + break + } + } + + for idx, currentItem := range currentItems { + if idx >= len(newItems) || currentItem == nil || newItems[idx] == nil || currentItem.RateCard == nil || newItems[idx].RateCard == nil { + continue + } + + currentCurrency := currentItem.RateCard.AsMeta().Currency + if currentCurrency == nil { continue } - meta := item.RateCard.AsMeta() - if meta.Price == nil || meta.Currency == nil || !meta.Currency.IsCustom() { + newCurrency := newItems[idx].RateCard.AsMeta().Currency + if newCurrency == nil || !currentCurrency.Equal(newCurrency) { + return models.NewGenericValidationError(fmt.Errorf( + "cannot change currency of subscription item %q[%d] in phase %q", + itemKey, + idx, + phaseKey, + )) + } + } + + for idx, newItem := range newItems { + if newItem == nil || newItem.RateCard == nil { + continue + } + + meta := newItem.RateCard.AsMeta() + if meta.Price == nil || meta.Currency == nil { continue } - return models.NewGenericValidationError(fmt.Errorf( - "%w: item %q in phase %q uses %q", - errCustomCurrencySubscriptionsNotSupported, - itemKey, - phaseKey, - meta.Currency.GetCode(), - )) + if establishedCurrency == nil { + establishedCurrency = meta.Currency + continue + } + + if !establishedCurrency.Equal(meta.Currency) { + return models.NewGenericValidationError(fmt.Errorf( + "cannot change currency of subscription item %q[%d] in phase %q", + itemKey, + idx, + phaseKey, + )) + } } } } diff --git a/openmeter/subscription/service/servicevalidation_test.go b/openmeter/subscription/service/servicevalidation_test.go index b5bf11435a..8401e4cd3a 100644 --- a/openmeter/subscription/service/servicevalidation_test.go +++ b/openmeter/subscription/service/servicevalidation_test.go @@ -6,85 +6,204 @@ import ( "github.com/alpacahq/alpacadecimal" "github.com/stretchr/testify/require" + "github.com/openmeterio/openmeter/openmeter/currencies" "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/openmeter/subscription" "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" ) -func TestValidateSubscriptionUsesFiatOnly(t *testing.T) { - customCurrency := currencyx.Code("CREDITS") +type currencyValidationItem struct { + currency currencyx.CurrencyIdentity + priced bool +} + +func newCurrencyValidationSpec(phases map[string][]currencyValidationItem) subscription.SubscriptionSpec { + spec := subscription.SubscriptionSpec{ + Phases: make(map[string]*subscription.SubscriptionPhaseSpec, len(phases)), + } - newSpec := func(subscriptionCurrency currencyx.Code, rateCardCurrency currencyx.CurrencyIdentity) subscription.SubscriptionSpec { - rateCard := &productcatalog.FlatFeeRateCard{ - RateCardMeta: productcatalog.RateCardMeta{ + for phaseKey, itemDefs := range phases { + items := make([]*subscription.SubscriptionItemSpec, 0, len(itemDefs)) + for _, itemDef := range itemDefs { + meta := productcatalog.RateCardMeta{ Key: "fee", Name: "Fee", - Currency: rateCardCurrency, - Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ + Currency: itemDef.currency, + } + if itemDef.priced { + meta.Price = productcatalog.NewPriceFrom(productcatalog.FlatPrice{ Amount: alpacadecimal.NewFromInt(1), - }), - }, - } - item := &subscription.SubscriptionItemSpec{ - CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ - CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ - PhaseKey: "default", - ItemKey: "fee", - RateCard: rateCard, - }, - }, - } + }) + } - return subscription.SubscriptionSpec{ - CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{ - Currency: subscriptionCurrency, - }, - Phases: map[string]*subscription.SubscriptionPhaseSpec{ - "default": { - ItemsByKey: map[string][]*subscription.SubscriptionItemSpec{ - "fee": {item}, + items = append(items, &subscription.SubscriptionItemSpec{ + CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ + PhaseKey: phaseKey, + ItemKey: "fee", + RateCard: &productcatalog.FlatFeeRateCard{RateCardMeta: meta}, }, }, + }) + } + + spec.Phases[phaseKey] = &subscription.SubscriptionPhaseSpec{ + ItemsByKey: map[string][]*subscription.SubscriptionItemSpec{ + "fee": items, }, } } + return spec +} + +func TestValidateMaterializedItemCurrenciesUnchanged(t *testing.T) { + customCurrency := currencies.Currency{ + NamespacedID: models.NamespacedID{Namespace: "test", ID: "01J00000000000000000000000"}, + Code: "CREDITS", + Name: "Credits", + } + + newSpec := func(identity currencyx.CurrencyIdentity) subscription.SubscriptionSpec { + return newCurrencyValidationSpec(map[string][]currencyValidationItem{ + "default": {{currency: identity, priced: true}}, + }) + } + tests := []struct { - name string - spec subscription.SubscriptionSpec - expected error + name string + current currencyx.CurrencyIdentity + updated currencyx.CurrencyIdentity + wantErr bool }{ { - name: "fiat subscription with inherited currency", - spec: newSpec(currencyx.Code("USD"), nil), + name: "same fiat currency", + current: currencyx.Code("USD"), + updated: currencyx.Code("USD"), + }, + { + name: "same managed custom currency", + current: customCurrency, + updated: customCurrency, + }, + { + name: "fiat currency changed", + current: currencyx.Code("USD"), + updated: currencyx.Code("EUR"), + wantErr: true, }, { - name: "custom subscription currency", - spec: newSpec(customCurrency, nil), - expected: errCustomCurrencySubscriptionsNotSupported, + name: "materialized currency removed", + current: customCurrency, + wantErr: true, }, { - name: "fiat subscription with custom item currency", - spec: newSpec(currencyx.Code("USD"), customCurrency), - expected: errCustomCurrencySubscriptionsNotSupported, + name: "legacy item can acquire materialized currency", + updated: currencyx.Code("USD"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + // given: + // - an existing subscription item and its updated representation + // when: + // - materialized currencies are compared across the update + // then: + // - an existing identity is immutable, while a legacy missing value can be filled + err := validateMaterializedItemCurrenciesUnchanged(newSpec(tt.current), newSpec(tt.updated)) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + }) + } +} + +func TestValidateMaterializedItemCurrenciesUnchangedForAppendedVersions(t *testing.T) { + tests := []struct { + name string + current map[string][]currencyValidationItem + updated map[string][]currencyValidationItem + wantErr bool + }{ + { + name: "appended version keeps established currency", + current: map[string][]currencyValidationItem{ + "default": {{currency: currencyx.Code("USD"), priced: true}}, + }, + updated: map[string][]currencyValidationItem{ + "default": { + {currency: currencyx.Code("USD"), priced: true}, + {currency: currencyx.Code("USD"), priced: true}, + }, + }, + }, + { + name: "appended version cannot switch established currency", + current: map[string][]currencyValidationItem{ + "default": {{currency: currencyx.Code("USD"), priced: true}}, + }, + updated: map[string][]currencyValidationItem{ + "default": { + {currency: currencyx.Code("USD"), priced: true}, + {currency: currencyx.Code("EUR"), priced: true}, + }, + }, + wantErr: true, + }, + { + name: "unpriced key can establish currency when it becomes priced", + current: map[string][]currencyValidationItem{ + "default": {{priced: false}}, + }, + updated: map[string][]currencyValidationItem{ + "default": { + {priced: false}, + {currency: currencyx.Code("EUR"), priced: true}, + }, + }, + }, + { + name: "same item key has independent currency history per phase", + current: map[string][]currencyValidationItem{ + "first": {{currency: currencyx.Code("USD"), priced: true}}, + "second": {{currency: currencyx.Code("EUR"), priced: true}}, + }, + updated: map[string][]currencyValidationItem{ + "first": { + {currency: currencyx.Code("USD"), priced: true}, + {currency: currencyx.Code("USD"), priced: true}, + }, + "second": { + {currency: currencyx.Code("EUR"), priced: true}, + {currency: currencyx.Code("EUR"), priced: true}, + }, + }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // given: - // - a subscription spec using only fiat or introducing a custom currency + // - an existing item-key timeline and an updated timeline // when: - // - the temporary subscription boundary is validated + // - later item versions are checked against the key's established currency // then: - // - custom currencies are rejected before persistence or billing sync - err := validateSubscriptionUsesFiatOnly(tt.spec) - if tt.expected == nil { - require.NoError(t, err) + // - priced versions keep that identity, while an unpriced key may establish one + err := validateMaterializedItemCurrenciesUnchanged( + newCurrencyValidationSpec(tt.current), + newCurrencyValidationSpec(tt.updated), + ) + if tt.wantErr { + require.Error(t, err) return } - require.ErrorIs(t, err, tt.expected) + require.NoError(t, err) }) } } diff --git a/openmeter/subscription/service/sync.go b/openmeter/subscription/service/sync.go index 7a9563e8cd..045312dce9 100644 --- a/openmeter/subscription/service/sync.go +++ b/openmeter/subscription/service/sync.go @@ -26,7 +26,7 @@ import ( // TODO: localize error so phase and item keys are always included (alongside subscription reference) // TODO (OM-1074): clean up this control flow func (s *service) sync(ctx context.Context, view subscription.SubscriptionView, newSpec subscription.SubscriptionSpec) (subscription.Subscription, error) { - if err := newSpec.MaterializeRateCardCurrencies(newSpec.Currency); err != nil { + if err := newSpec.MaterializeRateCardCurrencies(newSpec.InvoiceCurrency); err != nil { return subscription.Subscription{}, fmt.Errorf("failed to materialize subscription item currencies: %w", err) } diff --git a/openmeter/subscription/service/sync_test.go b/openmeter/subscription/service/sync_test.go index 8296235ff8..53c26a22e7 100644 --- a/openmeter/subscription/service/sync_test.go +++ b/openmeter/subscription/service/sync_test.go @@ -42,11 +42,11 @@ func TestEdit(t *testing.T) { defer cancel() spec, err := subscription.NewSpecFromPlan(deps.ExamplePlan, subscription.CreateSubscriptionCustomerInput{ - Name: "Test", - CustomerId: deps.Customer.ID, - Currency: currencyx.Code("USD"), - ActiveFrom: deps.CurrentTime, - BillingAnchor: deps.CurrentTime, + Name: "Test", + CustomerId: deps.Customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: deps.CurrentTime, + BillingAnchor: deps.CurrentTime, }) require.Nil(t, err) @@ -64,11 +64,11 @@ func TestEdit(t *testing.T) { defer cancel() spec, err := subscription.NewSpecFromPlan(deps.ExamplePlan, subscription.CreateSubscriptionCustomerInput{ - Name: "Test", - CustomerId: deps.Customer.ID, - Currency: currencyx.Code("USD"), - ActiveFrom: deps.CurrentTime, - BillingAnchor: deps.CurrentTime, + Name: "Test", + CustomerId: deps.Customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: deps.CurrentTime, + BillingAnchor: deps.CurrentTime, }) require.Nil(t, err) @@ -91,11 +91,11 @@ func TestEdit(t *testing.T) { defer cancel() spec, err := subscription.NewSpecFromPlan(deps.ExamplePlan, subscription.CreateSubscriptionCustomerInput{ - Name: "Test", - CustomerId: deps.Customer.ID, - Currency: currencyx.Code("USD"), - ActiveFrom: deps.CurrentTime, - BillingAnchor: deps.CurrentTime, + Name: "Test", + CustomerId: deps.Customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: deps.CurrentTime, + BillingAnchor: deps.CurrentTime, }) require.Nil(t, err) @@ -117,11 +117,11 @@ func TestEdit(t *testing.T) { defer cancel() spec, err := subscription.NewSpecFromPlan(deps.ExamplePlan, subscription.CreateSubscriptionCustomerInput{ - Name: "Test", - CustomerId: deps.Customer.ID, - Currency: currencyx.Code("USD"), - ActiveFrom: deps.CurrentTime, - BillingAnchor: deps.CurrentTime, + Name: "Test", + CustomerId: deps.Customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: deps.CurrentTime, + BillingAnchor: deps.CurrentTime, }) require.Nil(t, err) @@ -143,11 +143,11 @@ func TestEdit(t *testing.T) { defer cancel() spec, err := subscription.NewSpecFromPlan(deps.ExamplePlan, subscription.CreateSubscriptionCustomerInput{ - Name: "Test", - CustomerId: deps.Customer.ID, - Currency: currencyx.Code("USD"), - ActiveFrom: deps.CurrentTime, - BillingAnchor: deps.CurrentTime, + Name: "Test", + CustomerId: deps.Customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: deps.CurrentTime, + BillingAnchor: deps.CurrentTime, }) require.Nil(t, err) @@ -189,11 +189,11 @@ func TestEdit(t *testing.T) { defer cancel() spec, err := subscription.NewSpecFromPlan(deps.ExamplePlan, subscription.CreateSubscriptionCustomerInput{ - Name: "Test", - CustomerId: deps.Customer.ID, - Currency: currencyx.Code("USD"), - ActiveFrom: deps.CurrentTime, - BillingAnchor: deps.CurrentTime, + Name: "Test", + CustomerId: deps.Customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: deps.CurrentTime, + BillingAnchor: deps.CurrentTime, }) require.Nil(t, err) @@ -237,11 +237,11 @@ func TestEdit(t *testing.T) { defer cancel() spec, err := subscription.NewSpecFromPlan(deps.ExamplePlan, subscription.CreateSubscriptionCustomerInput{ - Name: "Test", - CustomerId: deps.Customer.ID, - Currency: currencyx.Code("USD"), - ActiveFrom: deps.CurrentTime, - BillingAnchor: deps.CurrentTime, + Name: "Test", + CustomerId: deps.Customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: deps.CurrentTime, + BillingAnchor: deps.CurrentTime, }) require.Nil(t, err) @@ -297,11 +297,11 @@ func TestEdit(t *testing.T) { defer cancel() spec, err := subscription.NewSpecFromPlan(deps.ExamplePlan, subscription.CreateSubscriptionCustomerInput{ - Name: "Test", - CustomerId: deps.Customer.ID, - Currency: currencyx.Code("USD"), - ActiveFrom: deps.CurrentTime, - BillingAnchor: deps.CurrentTime, + Name: "Test", + CustomerId: deps.Customer.ID, + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: deps.CurrentTime, + BillingAnchor: deps.CurrentTime, }) require.Nil(t, err) diff --git a/openmeter/subscription/subscription.go b/openmeter/subscription/subscription.go index 70d42d2294..1543d8974a 100644 --- a/openmeter/subscription/subscription.go +++ b/openmeter/subscription/subscription.go @@ -22,8 +22,11 @@ type Subscription struct { // References the plan (if the Subscription was created form one) PlanRef *PlanRef `json:"planRef"` - CustomerId string `json:"customerId,omitempty"` - Currency currencyx.Code `json:"currency,omitempty"` + CustomerId string `json:"customerId,omitempty"` + // Keep the legacy JSON key because v1 subscription events embed this domain type. + InvoiceCurrency currencyx.Code `json:"currency,omitempty"` + CostBasisMode CostBasisMode `json:"costBasisMode"` + CostBasisPins []CostBasisPin `json:"costBasisPins,omitempty"` BillingCadence datetime.ISODuration `json:"billing_cadence"` BillingAnchor time.Time `json:"billingAnchor"` @@ -45,7 +48,8 @@ func (s Subscription) AsEntityInput() CreateSubscriptionEntityInput { Name: s.Name, Description: s.Description, CustomerId: s.CustomerId, - Currency: s.Currency, + InvoiceCurrency: s.InvoiceCurrency, + CostBasisMode: s.CostBasisMode, BillingCadence: s.BillingCadence, BillingAnchor: s.BillingAnchor, ProRatingConfig: s.ProRatingConfig, diff --git a/openmeter/subscription/subscriptionspec.go b/openmeter/subscription/subscriptionspec.go index 984c95a5eb..791fea7b75 100644 --- a/openmeter/subscription/subscriptionspec.go +++ b/openmeter/subscription/subscriptionspec.go @@ -44,14 +44,16 @@ type CreateSubscriptionPlanInput struct { type CreateSubscriptionCustomerInput struct { models.MetadataModel `json:",inline"` - Name string `json:"name"` - Description *string `json:"description,omitempty"` - CustomerId string `json:"customerId"` - Currency currencyx.Code `json:"currency"` - ActiveFrom time.Time `json:"activeFrom,omitempty"` - ActiveTo *time.Time `json:"activeTo,omitempty"` - BillingAnchor time.Time `json:"billingAnchor,omitempty"` - Annotations models.Annotations `json:"annotations"` + Name string `json:"name"` + Description *string `json:"description,omitempty"` + CustomerId string `json:"customerId"` + // Keep the legacy JSON key because v1 subscription events embed this spec. + InvoiceCurrency currencyx.Code `json:"currency"` + CostBasisMode CostBasisMode `json:"costBasisMode"` + ActiveFrom time.Time `json:"activeFrom,omitempty"` + ActiveTo *time.Time `json:"activeTo,omitempty"` + BillingAnchor time.Time `json:"billingAnchor,omitempty"` + Annotations models.Annotations `json:"annotations"` } type SubscriptionSpec struct { @@ -95,7 +97,8 @@ func (s *SubscriptionSpec) ToCreateSubscriptionEntityInput(ns string) CreateSubs }, Plan: s.Plan, CustomerId: s.CustomerId, - Currency: s.Currency, + InvoiceCurrency: s.InvoiceCurrency, + CostBasisMode: s.CostBasisMode.OrDefault(), BillingCadence: s.BillingCadence, ProRatingConfig: s.ProRatingConfig, SettlementMode: s.SettlementMode, @@ -333,6 +336,10 @@ func (s *SubscriptionSpec) Validate() error { // All consistency checks should happen here var errs []error + if err := s.validateCurrencies(); err != nil { + errs = append(errs, err) + } + // Let's validate the billing anchor // - is present if s.BillingAnchor.IsZero() { @@ -365,6 +372,78 @@ func (s *SubscriptionSpec) Validate() error { return errors.Join(errs...) } +func (s *SubscriptionSpec) validateCurrencies() error { + var errs []error + + if err := s.InvoiceCurrency.Validate(); err != nil { + errs = append(errs, fmt.Errorf("invoiceCurrency: %w", err)) + } else if !s.InvoiceCurrency.IsFiat() { + errs = append(errs, fmt.Errorf("invoiceCurrency: currency %q must be fiat", s.InvoiceCurrency)) + } + + if err := s.CostBasisMode.Validate(); err != nil { + errs = append(errs, fmt.Errorf("costBasisMode: %w", err)) + } + if s.CostBasisMode.IsPinned() && s.SettlementMode == productcatalog.CreditOnlySettlementMode { + errs = append(errs, errors.New("costBasisMode: pinned cost basis is not supported for credit-only subscriptions")) + } + + for _, phase := range s.GetSortedPhases() { + if phase == nil { + continue + } + + for itemKey, items := range phase.ItemsByKey { + for idx, item := range items { + if item == nil || item.RateCard == nil { + continue + } + + meta := item.RateCard.AsMeta() + fieldSelector := models.NewFieldSelectorGroup( + models.NewFieldSelector("phases").WithExpression(models.NewFieldAttrValue("key", phase.PhaseKey)), + models.NewFieldSelector("itemsByKey"), + models.NewFieldSelector(itemKey).WithExpression(models.NewFieldArrIndex(idx)), + models.NewFieldSelector("rateCard"), + models.NewFieldSelector("currency"), + ) + + if meta.Price == nil { + if meta.Currency != nil { + errs = append(errs, models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrRateCardCurrencyRequiresPrice)) + } + continue + } + + if meta.Currency == nil { + errs = append(errs, models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrCurrencyInvalid)) + continue + } + if err := meta.Currency.Validate(); err != nil { + errs = append(errs, models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrCurrencyInvalid)) + continue + } + + switch { + case meta.Currency.IsFiat(): + if meta.Currency.GetCode() != s.InvoiceCurrency { + errs = append(errs, models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrPlanMultipleFiatCurrencies)) + } + case meta.Currency.IsCustom(): + managed, ok := meta.Currency.(currencyx.ManagedCurrency) + if !ok || managed.GetID() == "" { + errs = append(errs, models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrCurrencyInvalid)) + } + default: + errs = append(errs, models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrCurrencyInvalid)) + } + } + } + } + + return errors.Join(errs...) +} + func (s *SubscriptionSpec) ValidateAlignment() error { var errs []error @@ -1209,6 +1288,10 @@ func (s *SubscriptionSpec) Apply(applies AppliesToSpec, context ApplyContext) er return fmt.Errorf("apply failed: %w", err) } + if err := s.MaterializeRateCardCurrencies(s.InvoiceCurrency); err != nil { + return fmt.Errorf("failed to materialize rate card currencies: %w", err) + } + if err := s.SyncAnnotations(); err != nil { return fmt.Errorf("failed to sync annotations: %w", err) } @@ -1221,6 +1304,10 @@ func (s *SubscriptionSpec) ApplyMany(applieses []AppliesToSpec, aCtx ApplyContex return fmt.Errorf("apply failed: %w", err) } + if err := s.MaterializeRateCardCurrencies(s.InvoiceCurrency); err != nil { + return fmt.Errorf("failed to materialize rate card currencies: %w", err) + } + if err := s.Validate(); err != nil { return fmt.Errorf("final validation failed when applying patches: %w", err) } diff --git a/openmeter/subscription/subscriptionview.go b/openmeter/subscription/subscriptionview.go index 6aa3eba48a..ee8ead35f8 100644 --- a/openmeter/subscription/subscriptionview.go +++ b/openmeter/subscription/subscriptionview.go @@ -58,8 +58,25 @@ func (s *SubscriptionView) Validate(includePhases bool) error { return fmt.Errorf("subscription customer id %s does not match spec customer id %s", s.Subscription.CustomerId, spec.CustomerId) } - if spec.Currency != s.Subscription.Currency { - return fmt.Errorf("subscription currency %s does not match spec currency %s", s.Subscription.Currency, spec.Currency) + if spec.InvoiceCurrency != s.Subscription.InvoiceCurrency { + return fmt.Errorf("subscription invoice currency %s does not match spec invoice currency %s", s.Subscription.InvoiceCurrency, spec.InvoiceCurrency) + } + + if spec.CostBasisMode.OrDefault() != s.Subscription.CostBasisMode.OrDefault() { + return fmt.Errorf("subscription cost basis mode %s does not match spec cost basis mode %s", s.Subscription.CostBasisMode.OrDefault(), spec.CostBasisMode.OrDefault()) + } + + if !s.Subscription.CostBasisMode.IsPinned() && len(s.Subscription.CostBasisPins) != 0 { + return fmt.Errorf("dynamic subscription cannot have pinned cost bases") + } + + for _, pin := range s.Subscription.CostBasisPins { + if err := pin.Validate(); err != nil { + return fmt.Errorf("subscription cost basis pin is invalid: %w", err) + } + if pin.Namespace != s.Subscription.Namespace || pin.InvoiceCurrency != s.Subscription.InvoiceCurrency { + return fmt.Errorf("subscription cost basis pin does not belong to subscription invoice currency") + } } if !spec.Plan.NilEqual(s.Subscription.PlanRef) { @@ -249,14 +266,15 @@ func NewSubscriptionView( SettlementMode: sub.SettlementMode, }, CreateSubscriptionCustomerInput: CreateSubscriptionCustomerInput{ - CustomerId: sub.CustomerId, - Currency: sub.Currency, - ActiveFrom: sub.ActiveFrom, - ActiveTo: sub.ActiveTo, - MetadataModel: sub.MetadataModel, - Name: sub.Name, - Description: sub.Description, - BillingAnchor: sub.BillingAnchor, + CustomerId: sub.CustomerId, + InvoiceCurrency: sub.InvoiceCurrency, + CostBasisMode: sub.CostBasisMode, + ActiveFrom: sub.ActiveFrom, + ActiveTo: sub.ActiveTo, + MetadataModel: sub.MetadataModel, + Name: sub.Name, + Description: sub.Description, + BillingAnchor: sub.BillingAnchor, }, Phases: make(map[string]*SubscriptionPhaseSpec), } diff --git a/openmeter/subscription/testutils/builder.go b/openmeter/subscription/testutils/builder.go index 08d49b18a8..5c9357ea6b 100644 --- a/openmeter/subscription/testutils/builder.go +++ b/openmeter/subscription/testutils/builder.go @@ -134,6 +134,10 @@ func (b *testSubscriptionSpecBuilder) AddPhase(dur *datetime.ISODuration, rcs .. func (b *testSubscriptionSpecBuilder) Build() (subscription.SubscriptionSpec, error) { spec := b.s + if err := spec.MaterializeRateCardCurrencies(spec.InvoiceCurrency); err != nil { + return spec, fmt.Errorf("failed to materialize currencies: %w", err) + } + if err := spec.SyncAnnotations(); err != nil { return spec, fmt.Errorf("failed to sync annotations: %w", err) } @@ -159,12 +163,12 @@ func BuildTestSubscriptionSpec(t *testing.T) *testSubscriptionSpecBuilder { SettlementMode: productcatalog.CreditThenInvoiceSettlementMode, }, CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{ - CustomerId: "01K6JCPG631MH1EKEQB2YMDBJW", - ActiveFrom: now, - ActiveTo: nil, - Name: "test_subscription", - BillingAnchor: now, - Currency: currencyx.Code(currency.USD), + CustomerId: "01K6JCPG631MH1EKEQB2YMDBJW", + ActiveFrom: now, + ActiveTo: nil, + Name: "test_subscription", + BillingAnchor: now, + InvoiceCurrency: currencyx.Code(currency.USD), }, Phases: make(map[string]*subscription.SubscriptionPhaseSpec), }, diff --git a/openmeter/subscription/testutils/compare.go b/openmeter/subscription/testutils/compare.go index cc089f5875..17d9da5784 100644 --- a/openmeter/subscription/testutils/compare.go +++ b/openmeter/subscription/testutils/compare.go @@ -25,7 +25,7 @@ func ValidateSpecAndView(t *testing.T, expected subscription.SubscriptionSpec, f assert.Equal(t, expected.Name, found.Subscription.Name) assert.Equal(t, expected.Description, found.Subscription.Description) assert.Equal(t, expected.Plan, found.Subscription.PlanRef) - assert.Equal(t, expected.Currency, found.Subscription.Currency) + assert.Equal(t, expected.InvoiceCurrency, found.Subscription.InvoiceCurrency) assert.Equal(t, expected.CustomerId, found.Subscription.CustomerId) assert.Equal(t, expected.ActiveFrom, found.Subscription.ActiveFrom) assert.Equal(t, expected.ActiveTo, found.Subscription.ActiveTo) @@ -169,7 +169,7 @@ func SpecsEqual(t *testing.T, s1, s2 subscription.SubscriptionSpec) { assert.Equal(t, s1.Name, s2.Name) assert.Equal(t, s1.Description, s2.Description) assert.Equal(t, s1.Plan, s2.Plan) - assert.Equal(t, s1.Currency, s2.Currency) + assert.Equal(t, s1.InvoiceCurrency, s2.InvoiceCurrency) assert.Equal(t, s1.CustomerId, s2.CustomerId) assert.Equal(t, s1.ActiveFrom, s2.ActiveFrom) assert.Equal(t, s1.ActiveTo, s2.ActiveTo) @@ -226,7 +226,7 @@ func SpecsEqual(t *testing.T, s1, s2 subscription.SubscriptionSpec) { // Let's validate the item properties assert.Equal(t, i1.ItemKey, i2.ItemKey) assert.True(t, i1.RateCard.Equal(i2.RateCard), "rate card mismatch for item %s in phase %s: \nspec: %+v\n\nview: %+v", itemKey, key, i1.RateCard, i2.RateCard) - assert.Equal(t, i1.CreateSubscriptionItemPlanInput, i2.CreateSubscriptionItemPlanInput, "create subscription item plan input mismatch for item %s in phase %s", itemKey, key) + assert.Equal(t, i1.PhaseKey, i2.PhaseKey, "phase key mismatch for item %s in phase %s", itemKey, key) // We'll compare the time offsets separately i1af := i1.ActiveFromOverrideRelativeToPhaseStart diff --git a/openmeter/subscription/testutils/mock.go b/openmeter/subscription/testutils/mock.go index 97c581cc0a..e387da8275 100644 --- a/openmeter/subscription/testutils/mock.go +++ b/openmeter/subscription/testutils/mock.go @@ -41,7 +41,7 @@ func (s *MockService) Create(ctx context.Context, namespace string, spec subscri return s.CreateFn(ctx, namespace, spec) } -func (s *MockService) Update(ctx context.Context, subscriptionID models.NamespacedID, target subscription.SubscriptionSpec) (subscription.Subscription, error) { +func (s *MockService) Update(ctx context.Context, subscriptionID models.NamespacedID, target subscription.SubscriptionSpec, options ...subscription.UpdateOption) (subscription.Subscription, error) { return s.UpdateFn(ctx, subscriptionID, target) } diff --git a/openmeter/subscription/testutils/repository.go b/openmeter/subscription/testutils/repository.go index 59b7d87905..236be0f4f8 100644 --- a/openmeter/subscription/testutils/repository.go +++ b/openmeter/subscription/testutils/repository.go @@ -36,10 +36,10 @@ func (r *testSubscriptionRepo) CreateExampleSubscription(t *testing.T, customerI func getExampleCreateSubscriptionInput(customerId string, planRef subscription.PlanRef) subscription.CreateSubscriptionEntityInput { return subscription.CreateSubscriptionEntityInput{ - Plan: &planRef, - CustomerId: customerId, - Currency: "USD", - SettlementMode: productcatalog.CreditThenInvoiceSettlementMode, + Plan: &planRef, + CustomerId: customerId, + InvoiceCurrency: "USD", + SettlementMode: productcatalog.CreditThenInvoiceSettlementMode, CadencedModel: models.CadencedModel{ ActiveFrom: clock.Now(), }, diff --git a/openmeter/subscription/testutils/service.go b/openmeter/subscription/testutils/service.go index 037e911269..409b538fd4 100644 --- a/openmeter/subscription/testutils/service.go +++ b/openmeter/subscription/testutils/service.go @@ -232,6 +232,7 @@ func NewService(t *testing.T, dbDeps *DBDeps) SubscriptionDependencies { Lockr: lockr, FeatureFlags: ffService, TaxCode: taxCodeService, + CostBasisService: currencyService, }) require.NoError(t, err) @@ -288,6 +289,7 @@ func NewService(t *testing.T, dbDeps *DBDeps) SubscriptionDependencies { workflowSvc, err := subscriptionworkflowservice.NewWorkflowService(subscriptionworkflowservice.WorkflowServiceConfig{ Service: svc, CustomerService: customerService, + CurrencyResolver: currencyResolver, TransactionManager: subItemRepo, AddonService: subAddSvc, Logger: logger.With("subsystem", "subscription.workflow.service"), diff --git a/openmeter/subscription/uniqueness_test.go b/openmeter/subscription/uniqueness_test.go index e62bee3509..1df42e949c 100644 --- a/openmeter/subscription/uniqueness_test.go +++ b/openmeter/subscription/uniqueness_test.go @@ -479,12 +479,12 @@ func getSimpleSub(cad models.CadencedModel) subscription.SubscriptionSpec { BillingCadence: datetime.NewISODuration(0, 1, 0, 0, 0, 0, 0), }, CreateSubscriptionCustomerInput: subscription.CreateSubscriptionCustomerInput{ - CustomerId: "test_customer", - ActiveFrom: cad.ActiveFrom, - ActiveTo: cad.ActiveTo, - Name: "test_subscription", - BillingAnchor: cad.ActiveFrom, - Currency: currencyx.Code(currency.USD), + CustomerId: "test_customer", + ActiveFrom: cad.ActiveFrom, + ActiveTo: cad.ActiveTo, + Name: "test_subscription", + BillingAnchor: cad.ActiveFrom, + InvoiceCurrency: currencyx.Code(currency.USD), }, } diff --git a/openmeter/subscription/workflow/service.go b/openmeter/subscription/workflow/service.go index 7d888432fd..9729af47ec 100644 --- a/openmeter/subscription/workflow/service.go +++ b/openmeter/subscription/workflow/service.go @@ -32,8 +32,9 @@ type CreateSubscriptionWorkflowInput struct { type ChangeSubscriptionWorkflowInput struct { subscription.Timing models.MetadataModel - Name string - Description *string + Name string + Description *string + CostBasisMode subscription.CostBasisMode BillingAnchor *time.Time `json:"billingAnchor,omitempty"` } diff --git a/openmeter/subscription/workflow/service/addon.go b/openmeter/subscription/workflow/service/addon.go index 91f8757e32..823d1afa63 100644 --- a/openmeter/subscription/workflow/service/addon.go +++ b/openmeter/subscription/workflow/service/addon.go @@ -256,7 +256,7 @@ func (s *service) syncWithAddons( return def, fmt.Errorf("failed to calculate subscription state with addons: %w", err) } - if _, err := s.Service.Update(ctx, view.Subscription.NamespacedID, spec); err != nil { + if _, err := s.Service.Update(ctx, view.Subscription.NamespacedID, spec, subscription.WithCostBasisEffectiveAt(currentTime)); err != nil { logErrWithArgs(fmt.Errorf("failed to update subscription: %w", err)) return def, fmt.Errorf("failed to update subscription: %w", err) diff --git a/openmeter/subscription/workflow/service/addon_test.go b/openmeter/subscription/workflow/service/addon_test.go index 145affe2cc..21cd5cfa83 100644 --- a/openmeter/subscription/workflow/service/addon_test.go +++ b/openmeter/subscription/workflow/service/addon_test.go @@ -262,7 +262,7 @@ func TestAddAddon(t *testing.T) { })) expectedCurrency, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat). - WithCode(spec.Currency). + WithCode(spec.InvoiceCurrency). Build() require.NoError(t, err) require.NoError(t, spec.MaterializeRateCardCurrencies(expectedCurrency)) diff --git a/openmeter/subscription/workflow/service/currency_test.go b/openmeter/subscription/workflow/service/currency_test.go new file mode 100644 index 0000000000..a7ba87ce21 --- /dev/null +++ b/openmeter/subscription/workflow/service/currency_test.go @@ -0,0 +1,210 @@ +package service + +import ( + "context" + "testing" + + "github.com/alpacahq/alpacadecimal" + "github.com/samber/lo" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/customer" + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/openmeter/subscription" + "github.com/openmeterio/openmeter/openmeter/subscription/patch" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/models" +) + +type invoiceCurrencyPlan struct { + currency currencyx.CurrencyIdentity +} + +func (p invoiceCurrencyPlan) ToCreateSubscriptionPlanInput() subscription.CreateSubscriptionPlanInput { + return subscription.CreateSubscriptionPlanInput{} +} + +func (p invoiceCurrencyPlan) GetName() string { + return "" +} + +func (p invoiceCurrencyPlan) GetPhases() []subscription.PlanPhase { + return nil +} + +func (p invoiceCurrencyPlan) Currency() currencyx.CurrencyIdentity { + return p.currency +} + +type recordingCurrencyResolver struct { + resolve func(context.Context, string, currencyx.Code) (currencyx.CurrencyIdentity, error) +} + +func (r recordingCurrencyResolver) Resolve(ctx context.Context, namespace string, code currencyx.Code) (currencyx.CurrencyIdentity, error) { + return r.resolve(ctx, namespace, code) +} + +func (recordingCurrencyResolver) HasCostBasis(context.Context, string, currencyx.ManagedCurrency, currencyx.CurrencyIdentity) (bool, error) { + return false, nil +} + +func TestResolveSubscriptionInvoiceCurrency(t *testing.T) { + tests := []struct { + name string + planCurrency currencyx.CurrencyIdentity + customerCurrency *currencyx.Code + expected currencyx.Code + wantErr bool + }{ + { + name: "fiat plan defaults customer without currency", + planCurrency: currencyx.Code("USD"), + expected: currencyx.Code("USD"), + }, + { + name: "matching fiat customer currency", + planCurrency: currencyx.Code("USD"), + customerCurrency: lo.ToPtr(currencyx.Code("USD")), + expected: currencyx.Code("USD"), + }, + { + name: "mismatching fiat customer currency", + planCurrency: currencyx.Code("USD"), + customerCurrency: lo.ToPtr(currencyx.Code("EUR")), + wantErr: true, + }, + { + name: "custom plan requires customer currency", + planCurrency: currencyx.Code("CREDITS"), + wantErr: true, + }, + { + name: "custom plan uses customer fiat", + planCurrency: currencyx.Code("CREDITS"), + customerCurrency: lo.ToPtr(currencyx.Code("USD")), + expected: currencyx.Code("USD"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + invoiceCurrency, err := resolveSubscriptionInvoiceCurrency(customer.Customer{ + Currency: tt.customerCurrency, + }, invoiceCurrencyPlan{currency: tt.planCurrency}) + if tt.wantErr { + require.Error(t, err) + return + } + + require.NoError(t, err) + require.Equal(t, tt.expected, invoiceCurrency) + }) + } +} + +func TestResolveEditPatchCurrency(t *testing.T) { + const namespace = "default" + + managedCurrency := ¤cies.Currency{ + NamespacedID: models.NamespacedID{ + Namespace: namespace, + ID: "currency-id", + }, + Code: "CREDITS", + Name: "Credits", + } + fiatCurrency, err := currencyx.NewCurrencyBuilder(currencyx.CurrencyTypeFiat). + WithCode(currencyx.Code("USD")). + Build() + require.NoError(t, err) + + tests := []struct { + name string + currency currencyx.CurrencyIdentity + resolvedCurrency currencyx.CurrencyIdentity + expectedResolveCode *currencyx.Code + expectedCurrency currencyx.CurrencyIdentity + asPointer bool + }{ + { + name: "resolves code-only custom currency", + currency: currencyx.Code("CREDITS"), + resolvedCurrency: managedCurrency, + expectedResolveCode: lo.ToPtr(currencyx.Code("CREDITS")), + expectedCurrency: managedCurrency, + }, + { + name: "defaults omitted currency to invoice currency", + resolvedCurrency: fiatCurrency, + expectedResolveCode: lo.ToPtr(currencyx.Code("USD")), + expectedCurrency: fiatCurrency, + }, + { + name: "preserves managed custom identity", + currency: managedCurrency, + expectedCurrency: managedCurrency, + asPointer: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var resolvedCodes []currencyx.Code + resolver := recordingCurrencyResolver{ + resolve: func(_ context.Context, actualNamespace string, code currencyx.Code) (currencyx.CurrencyIdentity, error) { + require.Equal(t, namespace, actualNamespace) + resolvedCodes = append(resolvedCodes, code) + return tt.resolvedCurrency, nil + }, + } + svc := service{WorkflowServiceConfig: WorkflowServiceConfig{CurrencyResolver: resolver}} + + addItem := patch.PatchAddItem{ + PhaseKey: "phase", + ItemKey: "item", + CreateInput: subscription.SubscriptionItemSpec{ + CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ + PhaseKey: "phase", + ItemKey: "item", + RateCard: &productcatalog.FlatFeeRateCard{RateCardMeta: productcatalog.RateCardMeta{ + Key: "item", + Name: "item", + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{Amount: alpacadecimal.NewFromInt(10)}), + Currency: tt.currency, + }}, + }, + }, + }, + } + + var customization subscription.Patch = addItem + if tt.asPointer { + customization = &addItem + } + + resolvedPatch, err := svc.resolveEditPatchCurrency(t.Context(), namespace, currencyx.Code("USD"), customization) + require.NoError(t, err) + + var resolvedAddItem patch.PatchAddItem + switch actual := resolvedPatch.(type) { + case patch.PatchAddItem: + resolvedAddItem = actual + case *patch.PatchAddItem: + resolvedAddItem = *actual + default: + t.Fatalf("expected add-item patch, got %T", resolvedPatch) + } + + if tt.expectedResolveCode == nil { + require.Empty(t, resolvedCodes) + } else { + require.Equal(t, []currencyx.Code{*tt.expectedResolveCode}, resolvedCodes) + } + + actualCurrency := resolvedAddItem.CreateInput.RateCard.AsMeta().Currency + require.Same(t, tt.expectedCurrency, actualCurrency) + }) + } +} diff --git a/openmeter/subscription/workflow/service/service.go b/openmeter/subscription/workflow/service/service.go index d7fb239c79..d981ec6f1a 100644 --- a/openmeter/subscription/workflow/service/service.go +++ b/openmeter/subscription/workflow/service/service.go @@ -7,6 +7,7 @@ import ( "log/slog" "github.com/openmeterio/openmeter/openmeter/customer" + "github.com/openmeterio/openmeter/openmeter/productcatalog" "github.com/openmeterio/openmeter/openmeter/subscription" subscriptionaddon "github.com/openmeterio/openmeter/openmeter/subscription/addon" subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" @@ -19,7 +20,8 @@ type WorkflowServiceConfig struct { Service subscription.Service AddonService subscriptionaddon.Service // connectors - CustomerService customer.Service + CustomerService customer.Service + CurrencyResolver productcatalog.CurrencyResolver // framework TransactionManager transaction.Creator Logger *slog.Logger @@ -42,6 +44,10 @@ func (c WorkflowServiceConfig) Validate() error { errs = append(errs, errors.New("customer service is required")) } + if c.CurrencyResolver == nil { + errs = append(errs, errors.New("currency resolver is required")) + } + if c.TransactionManager == nil { errs = append(errs, errors.New("transaction manager is required")) } diff --git a/openmeter/subscription/workflow/service/service_test.go b/openmeter/subscription/workflow/service/service_test.go index 48861dbdbb..d14e8ac8f4 100644 --- a/openmeter/subscription/workflow/service/service_test.go +++ b/openmeter/subscription/workflow/service/service_test.go @@ -12,6 +12,7 @@ func TestNewWorkflowServiceValidatesConfig(t *testing.T) { require.ErrorContains(t, err, "subscription service is required") require.ErrorContains(t, err, "subscription add-on service is required") require.ErrorContains(t, err, "customer service is required") + require.ErrorContains(t, err, "currency resolver is required") require.ErrorContains(t, err, "transaction manager is required") require.ErrorContains(t, err, "logger is required") require.ErrorContains(t, err, "locker is required") diff --git a/openmeter/subscription/workflow/service/subscription.go b/openmeter/subscription/workflow/service/subscription.go index b2d1f8087f..506c1dd6ce 100644 --- a/openmeter/subscription/workflow/service/subscription.go +++ b/openmeter/subscription/workflow/service/subscription.go @@ -15,6 +15,7 @@ import ( "github.com/openmeterio/openmeter/openmeter/subscription/patch" subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" "github.com/openmeterio/openmeter/pkg/clock" + "github.com/openmeterio/openmeter/pkg/currencyx" "github.com/openmeterio/openmeter/pkg/featuregate" "github.com/openmeterio/openmeter/pkg/filter" "github.com/openmeterio/openmeter/pkg/framework/transaction" @@ -66,19 +67,25 @@ func (s *service) CreateFromPlan(ctx context.Context, inp subscriptionworkflow.C return def, fmt.Errorf("failed to resolve active from: %w", err) } + invoiceCurrency, err := resolveSubscriptionInvoiceCurrency(*cus, plan) + if err != nil { + return def, err + } + // Let's normalize the billing anchor to the closest iteration based on the cadence billingAnchor := lo.FromPtrOr(inp.BillingAnchor, activeFrom).UTC() // Let's create the new Spec spec, err := subscription.NewSpecFromPlan(plan, subscription.CreateSubscriptionCustomerInput{ - CustomerId: cus.ID, - Currency: plan.Currency().GetCode(), - ActiveFrom: activeFrom, - MetadataModel: inp.MetadataModel, - Name: lo.CoalesceOrEmpty(inp.Name, plan.GetName()), - Description: inp.Description, - BillingAnchor: billingAnchor, - Annotations: inp.Annotations, + CustomerId: cus.ID, + InvoiceCurrency: invoiceCurrency, + CostBasisMode: inp.CostBasisMode, + ActiveFrom: activeFrom, + MetadataModel: inp.MetadataModel, + Name: lo.CoalesceOrEmpty(inp.Name, plan.GetName()), + Description: inp.Description, + BillingAnchor: billingAnchor, + Annotations: inp.Annotations, }) if err := subscriptionworkflow.MapSubscriptionErrors(err); err != nil { @@ -99,6 +106,126 @@ func (s *service) CreateFromPlan(ctx context.Context, inp subscriptionworkflow.C }) } +func resolveSubscriptionInvoiceCurrency(cus customer.Customer, plan subscription.Plan) (currencyx.Code, error) { + planCurrency := plan.Currency() + if planCurrency == nil { + return "", models.NewGenericValidationError(errors.New("plan currency is required")) + } + if err := planCurrency.Validate(); err != nil { + return "", models.NewGenericValidationError(fmt.Errorf("invalid plan currency: %w", err)) + } + + if cus.Currency == nil { + if planCurrency.IsFiat() { + return planCurrency.GetCode(), nil + } + + return "", models.NewGenericValidationError(fmt.Errorf( + "customer currency is required when plan currency %q is custom", + planCurrency.GetCode(), + )) + } + + if !cus.Currency.IsFiat() { + return "", models.NewGenericValidationError(fmt.Errorf("customer currency %q must be fiat", *cus.Currency)) + } + + if planCurrency.IsFiat() && planCurrency.GetCode() != *cus.Currency { + return "", models.NewGenericValidationError(fmt.Errorf( + "currency mismatch: customer currency is %s, but plan currency is %s", + *cus.Currency, + planCurrency.GetCode(), + )) + } + + return *cus.Currency, nil +} + +// resolveEditPatchCurrency materializes the currency of a newly authored priced +// item before it is applied. Subscription specs require managed identities for +// custom currencies, but API patches only carry their public currency code. +func (s *service) resolveEditPatchCurrency(ctx context.Context, namespace string, invoiceCurrency currencyx.Code, customization subscription.Patch) (subscription.Patch, error) { + switch addItem := customization.(type) { + case patch.PatchAddItem: + if err := s.resolveEditAddItemCurrency(ctx, namespace, invoiceCurrency, &addItem); err != nil { + return nil, err + } + + return addItem, nil + case *patch.PatchAddItem: + if addItem == nil { + return nil, errors.New("add-item patch is required") + } + + if err := s.resolveEditAddItemCurrency(ctx, namespace, invoiceCurrency, addItem); err != nil { + return nil, err + } + + return addItem, nil + default: + return customization, nil + } +} + +func (s *service) resolveEditAddItemCurrency(ctx context.Context, namespace string, invoiceCurrency currencyx.Code, addItem *patch.PatchAddItem) error { + rateCard := addItem.CreateInput.RateCard + if rateCard == nil { + return nil + } + + meta := rateCard.AsMeta() + if meta.Price == nil { + return nil + } + + identity := meta.Currency + if identity == nil { + identity = invoiceCurrency + } + + fieldSelector := models.NewFieldSelectorGroup( + addItem.FieldDescriptor(), + models.NewFieldSelector("rateCard"), + models.NewFieldSelector("currency"), + ) + + if err := identity.Validate(); err != nil { + return models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrCurrencyInvalid) + } + + if identity.IsCustom() { + if managed, ok := identity.(currencyx.ManagedCurrency); ok && managed.GetID() != "" { + return nil + } + } else if _, ok := identity.(currencyx.Currency); ok { + return nil + } + + resolved, err := s.CurrencyResolver.Resolve(ctx, namespace, identity.GetCode()) + if err != nil { + if models.IsGenericNotFoundError(err) { + return models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrCurrencyNotFound) + } + + return fmt.Errorf("resolving add-item currency %q: %w", identity.GetCode(), err) + } + if resolved == nil { + return models.ErrorWithFieldPrefix(fieldSelector, productcatalog.ErrCurrencyInvalid) + } + + rateCard = rateCard.Clone() + if err := rateCard.ChangeMeta(func(meta productcatalog.RateCardMeta) (productcatalog.RateCardMeta, error) { + meta.Currency = resolved + return meta, nil + }); err != nil { + return fmt.Errorf("setting add-item currency: %w", err) + } + + addItem.CreateInput.RateCard = rateCard + + return nil +} + func (s *service) EditRunning(ctx context.Context, subscriptionID models.NamespacedID, customizations []subscription.Patch, timing subscription.Timing) (subscription.SubscriptionView, error) { // Finally, let's update the subscription return transaction.Run(ctx, s.TransactionManager, func(ctx context.Context) (subscription.SubscriptionView, error) { @@ -154,6 +281,18 @@ func (s *service) EditRunning(ctx context.Context, subscriptionID models.Namespa } } + // Currency resolution mutates rate card metadata through ChangeMeta, which + // validates the complete rate card. Run normal patch validation first so + // unrelated errors retain the patch's phase and item field descriptors. + for i, customization := range customizations { + customization, err = s.resolveEditPatchCurrency(ctx, subscriptionID.Namespace, curr.Spec.InvoiceCurrency, customization) + if err != nil { + return subscription.SubscriptionView{}, models.ErrorWithComponent(models.ComponentName(fmt.Sprintf("patch[%d]", i)), err) + } + + customizations[i] = customization + } + // Let's try to decode when the subscription should be patched if err := timing.ValidateForAction(subscription.SubscriptionActionUpdate, &curr); err != nil { return subscription.SubscriptionView{}, models.NewGenericValidationError(fmt.Errorf("invalid timing: %w", err)) @@ -178,7 +317,7 @@ func (s *service) EditRunning(ctx context.Context, subscriptionID models.Namespa return subscription.SubscriptionView{}, err } - sub, err := s.Service.Update(ctx, subscriptionID, spec) + sub, err := s.Service.Update(ctx, subscriptionID, spec, subscription.WithCostBasisEffectiveAt(editTime)) if err != nil { return subscription.SubscriptionView{}, fmt.Errorf("failed to update subscription: %w", err) } diff --git a/openmeter/subscription/workflow/service/subscription_test.go b/openmeter/subscription/workflow/service/subscription_test.go index 7d31ef7381..f2c9b834b4 100644 --- a/openmeter/subscription/workflow/service/subscription_test.go +++ b/openmeter/subscription/workflow/service/subscription_test.go @@ -205,6 +205,114 @@ func TestEditRunning(t *testing.T) { // assert.Equal(t, deps.SubView, subView) }, }, + { + Name: "Should materialize invoice currency on a newly priced item", + Handler: func(t *testing.T, deps testCaseDeps) { + // given: + // - a priced item authored without an explicit currency + // when: + // - it is added through the running-subscription edit workflow + // then: + // - the persisted item carries the subscription invoice currency + const ( + phaseKey = "test_phase_1" + itemKey = "edited-priced-item" + ) + + rateCard := &productcatalog.FlatFeeRateCard{ + RateCardMeta: productcatalog.RateCardMeta{ + Key: itemKey, + Name: itemKey, + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ + Amount: alpacadecimal.NewFromInt(10), + }), + }, + BillingCadence: lo.ToPtr(subscriptiontestutils.ISOMonth), + } + + subView, err := deps.WorkflowService.EditRunning(t.Context(), deps.SubView.Subscription.NamespacedID, []subscription.Patch{ + patch.PatchAddItem{ + PhaseKey: phaseKey, + ItemKey: itemKey, + CreateInput: subscription.SubscriptionItemSpec{ + CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ + PhaseKey: phaseKey, + ItemKey: itemKey, + RateCard: rateCard, + }, + }, + }, + }, + }, immediate) + require.NoError(t, err) + + phase, ok := subView.GetPhaseByKey(phaseKey) + require.True(t, ok) + require.Len(t, phase.ItemsByKey[itemKey], 1) + + itemCurrency := phase.ItemsByKey[itemKey][0].Spec.RateCard.AsMeta().Currency + require.NotNil(t, itemCurrency) + require.True(t, itemCurrency.IsFiat()) + require.Equal(t, subView.Subscription.InvoiceCurrency, itemCurrency.GetCode()) + }, + }, + { + Name: "Should preserve patch field paths when a priced item is invalid", + Handler: func(t *testing.T, deps testCaseDeps) { + // given: + // - a priced add-item patch whose entitlement template is invalid + // when: + // - the workflow validates and materializes the patch currency + // then: + // - validation still identifies the authored phase and item + const ( + phaseKey = "test_phase_1" + itemKey = "invalid-metered-item" + ) + + rateCard := &productcatalog.FlatFeeRateCard{ + RateCardMeta: productcatalog.RateCardMeta{ + Key: itemKey, + Name: itemKey, + FeatureKey: lo.ToPtr(itemKey), + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ + Amount: alpacadecimal.NewFromInt(10), + }), + EntitlementTemplate: productcatalog.NewEntitlementTemplateFrom(productcatalog.MeteredEntitlementTemplate{ + IssueAfterResetPriority: lo.ToPtr(uint8(10)), + UsagePeriod: subscriptiontestutils.ISOMonth, + }), + }, + BillingCadence: lo.ToPtr(subscriptiontestutils.ISOMonth), + } + + _, err := deps.WorkflowService.EditRunning(t.Context(), deps.SubView.Subscription.NamespacedID, []subscription.Patch{ + patch.PatchAddItem{ + PhaseKey: phaseKey, + ItemKey: itemKey, + CreateInput: subscription.SubscriptionItemSpec{ + CreateSubscriptionItemInput: subscription.CreateSubscriptionItemInput{ + CreateSubscriptionItemPlanInput: subscription.CreateSubscriptionItemPlanInput{ + PhaseKey: phaseKey, + ItemKey: itemKey, + RateCard: rateCard, + }, + }, + }, + }, + }, immediate) + require.Error(t, err) + + issues, err := models.AsValidationIssues(err) + require.NoError(t, err) + require.Len(t, issues, 2) + + for _, issue := range issues { + require.Equal(t, "phases.test_phase_1.items.invalid-metered-item.entitlementTemplate.issueAfterReset", issue.Field().String()) + } + }, + }, { Name: "Should validate the provided patches", Handler: func(t *testing.T, deps testCaseDeps) { @@ -321,6 +429,7 @@ func TestEditRunning(t *testing.T) { workflowService, err := workflowservice.NewWorkflowService(workflowservice.WorkflowServiceConfig{ Service: &mSvc, CustomerService: tuDeps.CustomerService, + CurrencyResolver: tuDeps.CurrencyResolver, TransactionManager: tuDeps.CustomerAdapter, AddonService: tuDeps.SubscriptionAddonService, Logger: slog.Default(), @@ -1001,14 +1110,14 @@ func TestChangeToPlan(t *testing.T) { // Let's check that the new plan looks as we expect targetSpec, err := subscription.NewSpecFromPlan(deps.Plan2, subscription.CreateSubscriptionCustomerInput{ - Name: changeInput.Name, - Description: changeInput.Description, - MetadataModel: changeInput.MetadataModel, - CustomerId: curr.CustomerId, - Currency: deps.Plan2.Currency().GetCode(), - ActiveFrom: testutils.GetRFC3339Time(t, "2021-02-01T00:00:00Z"), - BillingAnchor: sub.Subscription.BillingAnchor, - ActiveTo: nil, + Name: changeInput.Name, + Description: changeInput.Description, + MetadataModel: changeInput.MetadataModel, + CustomerId: curr.CustomerId, + InvoiceCurrency: deps.Plan2.Currency().GetCode(), + ActiveFrom: testutils.GetRFC3339Time(t, "2021-02-01T00:00:00Z"), + BillingAnchor: sub.Subscription.BillingAnchor, + ActiveTo: nil, }) require.Nil(t, err) diff --git a/test/billing/subscription_suite.go b/test/billing/subscription_suite.go index adb81bb2c4..310199c192 100644 --- a/test/billing/subscription_suite.go +++ b/test/billing/subscription_suite.go @@ -178,6 +178,7 @@ func (s *SubscriptionMixin) SetupSuite(t *testing.T, deps SubscriptionMixInDepen TransactionManager: subsRepo, Lockr: lockr, FeatureFlags: ffService, + CostBasisService: currencyService, // events Publisher: publisher, }) @@ -239,6 +240,7 @@ func (s *SubscriptionMixin) SetupSuite(t *testing.T, deps SubscriptionMixInDepen Service: s.SubscriptionService, AddonService: s.SubscriptionAddonService, CustomerService: deps.CustomerService, + CurrencyResolver: currencyResolver, TransactionManager: subsRepo, Logger: slog.Default(), Lockr: lockr, diff --git a/test/customer/customer.go b/test/customer/customer.go index 3234fc8d2b..916ee4190f 100644 --- a/test/customer/customer.go +++ b/test/customer/customer.go @@ -412,11 +412,11 @@ func (s *CustomerHandlerTestSuite) TestUpdateWithSubscriptionPresent(ctx context now := clock.Now() spec, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: originalCustomer.ID, - Name: "Test Subscription", - Currency: currencyx.Code("USD"), - ActiveFrom: now, - BillingAnchor: now, + CustomerId: originalCustomer.ID, + Name: "Test Subscription", + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: now, + BillingAnchor: now, }) require.Nil(t, err) @@ -1080,11 +1080,11 @@ func (s *CustomerHandlerTestSuite) TestDelete(ctx context.Context, t *testing.T) now := clock.Now() spec, err := subscription.NewSpecFromPlan(p, subscription.CreateSubscriptionCustomerInput{ - CustomerId: originalCustomer.ID, - Name: "Test Subscription", - Currency: currencyx.Code("USD"), - ActiveFrom: now, - BillingAnchor: now, + CustomerId: originalCustomer.ID, + Name: "Test Subscription", + InvoiceCurrency: currencyx.Code("USD"), + ActiveFrom: now, + BillingAnchor: now, }) require.Nil(t, err) diff --git a/test/customer/testenv.go b/test/customer/testenv.go index 6f1e7e5918..ef4c3589da 100644 --- a/test/customer/testenv.go +++ b/test/customer/testenv.go @@ -350,6 +350,7 @@ func NewTestEnv(t *testing.T, ctx context.Context) (TestEnv, error) { Publisher: publisher, Lockr: locker, FeatureFlags: ffService, + CostBasisService: currencyService, }) require.NoError(t, err) @@ -406,6 +407,7 @@ func NewTestEnv(t *testing.T, ctx context.Context) (TestEnv, error) { workflowSvc, err := subscriptionworkflowservice.NewWorkflowService(subscriptionworkflowservice.WorkflowServiceConfig{ Service: svc, CustomerService: customerService, + CurrencyResolver: currencyResolver, TransactionManager: subItemRepo, AddonService: subAddSvc, Logger: logger.With("subsystem", "subscription.workflow.service"), diff --git a/test/subscription/custom_currency_test.go b/test/subscription/custom_currency_test.go new file mode 100644 index 0000000000..974df55bbb --- /dev/null +++ b/test/subscription/custom_currency_test.go @@ -0,0 +1,267 @@ +package subscription_test + +import ( + "testing" + "time" + + decimal "github.com/alpacahq/alpacadecimal" + "github.com/samber/lo" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/billing/worker/subscriptionsync" + "github.com/openmeterio/openmeter/openmeter/currencies" + "github.com/openmeterio/openmeter/openmeter/customer" + "github.com/openmeterio/openmeter/openmeter/productcatalog" + "github.com/openmeterio/openmeter/openmeter/productcatalog/plan" + pcsubscription "github.com/openmeterio/openmeter/openmeter/productcatalog/subscription" + "github.com/openmeterio/openmeter/openmeter/subscription" + subscriptionworkflow "github.com/openmeterio/openmeter/openmeter/subscription/workflow" + "github.com/openmeterio/openmeter/pkg/clock" + "github.com/openmeterio/openmeter/pkg/currencyx" + "github.com/openmeterio/openmeter/pkg/datetime" + "github.com/openmeterio/openmeter/pkg/models" +) + +func TestCustomCurrencySubscriptionLifecycle(t *testing.T) { + // given: + // - a managed custom currency and a published plan priced entirely in it + // - customers whose billing currency is USD + // when: + // - subscriptions are started in each supported settlement and cost-basis mode + // then: + // - the persisted subscription keeps USD for invoicing and the managed currency on its item + // - conversion eligibility and billing's temporary safety boundary are enforced end to end + const namespace = "test-namespace" + + now := time.Now().UTC().Truncate(time.Second) + clock.FreezeTime(now) + defer clock.UnFreeze() + + deps := setup(t, setupConfig{}) + defer deps.cleanup(t) + + customCode := currencyx.Code("CREDITS") + customCurrency, err := deps.CurrencyService.CreateCurrency(t.Context(), currencies.CreateCurrencyInput{ + Namespace: namespace, + Code: customCode.String(), + Name: "Credits", + Symbol: "CR", + }) + require.NoError(t, err) + + month := datetime.MustParseDuration(t, "P1M") + customPlan, err := deps.PlanService.CreatePlan(t.Context(), plan.CreatePlanInput{ + NamespacedModel: models.NamespacedModel{Namespace: namespace}, + Plan: productcatalog.Plan{ + PlanMeta: productcatalog.PlanMeta{ + Key: "custom-currency-subscriptions", + Name: "Custom currency subscriptions", + Currency: customCode, + BillingCadence: month, + ProRatingConfig: productcatalog.ProRatingConfig{ + Enabled: true, + Mode: productcatalog.ProRatingModeProratePrices, + }, + SettlementMode: productcatalog.CreditThenInvoiceSettlementMode, + }, + Phases: []productcatalog.Phase{ + { + PhaseMeta: productcatalog.PhaseMeta{Key: "default", Name: "Default"}, + RateCards: productcatalog.RateCards{ + &productcatalog.FlatFeeRateCard{ + RateCardMeta: productcatalog.RateCardMeta{ + Key: "credits", + Name: "Credits", + Price: productcatalog.NewPriceFrom(productcatalog.FlatPrice{ + Amount: decimal.NewFromInt(25), + PaymentTerm: productcatalog.InAdvancePaymentTerm, + }), + }, + BillingCadence: &month, + }, + }, + }, + }, + }, + }) + require.NoError(t, err) + + customPlan, err = deps.PlanService.PublishPlan(t.Context(), plan.PublishPlanInput{ + NamespacedID: customPlan.NamespacedID, + EffectivePeriod: productcatalog.EffectivePeriod{ + EffectiveFrom: lo.ToPtr(now.Add(-time.Second)), + }, + }) + require.NoError(t, err) + + startsAt := now.Add(time.Hour) + + t.Run("credit only materializes currency without a cost basis", func(t *testing.T) { + // given: + // - no USD cost basis exists for the plan's custom currency + // when: + // - a credit-only subscription is started for a USD customer + // then: + // - it persists with USD as invoice currency and the managed custom identity on its item + cus := createUSDSubscriptionCustomer(t, deps, namespace, "credit-only") + sub, err := createCustomCurrencySubscription( + t, + deps, + *customPlan, + cus.ID, + startsAt, + productcatalog.CreditOnlySettlementMode, + subscription.CostBasisModeDynamic, + ) + require.NoError(t, err) + + view, err := deps.subscriptionService.GetView(t.Context(), sub.NamespacedID) + require.NoError(t, err) + require.NoError(t, view.Validate(true)) + require.Equal(t, currencyx.Code("USD"), view.Subscription.InvoiceCurrency) + require.Empty(t, view.Subscription.CostBasisPins) + + phase, ok := view.GetPhaseByKey("default") + require.True(t, ok) + require.Len(t, phase.ItemsByKey["credits"], 1) + + itemCurrency := phase.ItemsByKey["credits"][0].SubscriptionItem.RateCard.AsMeta().Currency + managedCurrency, ok := itemCurrency.(currencyx.ManagedCurrency) + require.True(t, ok, "persisted custom item currency must reload as a managed resource") + require.Equal(t, customCurrency.ID, managedCurrency.GetID()) + + // Billing conversion is deliberately outside this PR. Explicit callers see the + // unsupported boundary; automatic reconciliation skips the whole subscription. + err = deps.subscriptionSyncService.SyncByView(t.Context(), view, startsAt) + require.ErrorIs(t, err, subscriptionsync.ErrCustomCurrencyBillingNotSupported) + require.True(t, models.IsGenericConflictError(err)) + + err = deps.subscriptionSyncService.SyncByView( + t.Context(), + view, + startsAt, + subscriptionsync.SkipCustomCurrencySubscriptions(), + ) + require.NoError(t, err) + }) + + t.Run("credit then invoice resolves dynamic and pinned cost bases", func(t *testing.T) { + // given: + // - a USD customer and no effective custom-currency to USD cost basis + // when: + // - a credit-then-invoice subscription is started + // then: + // - creation is rejected until a cost basis exists + dynamicCustomer := createUSDSubscriptionCustomer(t, deps, namespace, "dynamic-cost-basis") + _, err := createCustomCurrencySubscription( + t, + deps, + *customPlan, + dynamicCustomer.ID, + startsAt, + productcatalog.CreditThenInvoiceSettlementMode, + subscription.CostBasisModeDynamic, + ) + require.Error(t, err) + require.True(t, models.IsGenericValidationError(err)) + require.ErrorIs(t, err, productcatalog.ErrCurrencyCostBasisNotFound) + + costBasis, err := deps.CurrencyService.CreateCostBasis(t.Context(), currencies.CreateCostBasisInput{ + Namespace: namespace, + CurrencyID: customCurrency.ID, + FiatCode: "USD", + Rate: decimal.NewFromInt(2), + }) + require.NoError(t, err) + + // Dynamic mode validates the pair at creation time but does not persist a pin. + dynamicSub, err := createCustomCurrencySubscription( + t, + deps, + *customPlan, + dynamicCustomer.ID, + startsAt, + productcatalog.CreditThenInvoiceSettlementMode, + subscription.CostBasisModeDynamic, + ) + require.NoError(t, err) + + dynamicView, err := deps.subscriptionService.GetView(t.Context(), dynamicSub.NamespacedID) + require.NoError(t, err) + require.Equal(t, subscription.CostBasisModeDynamic, dynamicView.Subscription.CostBasisMode) + require.Empty(t, dynamicView.Subscription.CostBasisPins) + + // Pinned mode reloads the exact managed cost-basis resource selected at start. + pinnedCustomer := createUSDSubscriptionCustomer(t, deps, namespace, "pinned-cost-basis") + pinnedSub, err := createCustomCurrencySubscription( + t, + deps, + *customPlan, + pinnedCustomer.ID, + startsAt, + productcatalog.CreditThenInvoiceSettlementMode, + subscription.CostBasisModePinned, + ) + require.NoError(t, err) + + pinnedView, err := deps.subscriptionService.GetView(t.Context(), pinnedSub.NamespacedID) + require.NoError(t, err) + require.NoError(t, pinnedView.Validate(true)) + require.Equal(t, subscription.CostBasisModePinned, pinnedView.Subscription.CostBasisMode) + require.Len(t, pinnedView.Subscription.CostBasisPins, 1) + + pin := pinnedView.Subscription.CostBasisPins[0] + require.Equal(t, customCurrency.ID, pin.CustomCurrencyID) + require.Equal(t, currencyx.Code("USD"), pin.InvoiceCurrency) + require.Equal(t, costBasis.ID, pin.CostBasis.ID) + require.Equal(t, float64(2), pin.CostBasis.Rate.InexactFloat64()) + }) +} + +func createUSDSubscriptionCustomer(t *testing.T, deps testDeps, namespace, key string) *customer.Customer { + t.Helper() + + cus, err := deps.CustomerService.CreateCustomer(t.Context(), customer.CreateCustomerInput{ + Namespace: namespace, + CustomerMutate: customer.CustomerMutate{ + Key: lo.ToPtr(key), + Name: key, + Currency: lo.ToPtr(currencyx.Code("USD")), + }, + }) + require.NoError(t, err) + + return cus +} + +func createCustomCurrencySubscription( + t *testing.T, + deps testDeps, + customPlan plan.Plan, + customerID string, + startsAt time.Time, + settlementMode productcatalog.SettlementMode, + costBasisMode subscription.CostBasisMode, +) (subscription.Subscription, error) { + t.Helper() + + planInput := &pcsubscription.PlanInput{} + planInput.FromRef(&pcsubscription.PlanRefInput{ + Key: customPlan.Key, + Version: &customPlan.Version, + }) + + return deps.pcSubscriptionService.Create(t.Context(), pcsubscription.CreateSubscriptionRequest{ + WorkflowInput: subscriptionworkflow.CreateSubscriptionWorkflowInput{ + Namespace: customPlan.Namespace, + CustomerID: customerID, + ChangeSubscriptionWorkflowInput: subscriptionworkflow.ChangeSubscriptionWorkflowInput{ + Timing: subscription.Timing{Custom: &startsAt}, + Name: customerID, + CostBasisMode: costBasisMode, + }, + }, + PlanInput: *planInput, + SettlementMode: &settlementMode, + }) +} diff --git a/test/subscription/framework_test.go b/test/subscription/framework_test.go index c701d37f55..34b55e8e17 100644 --- a/test/subscription/framework_test.go +++ b/test/subscription/framework_test.go @@ -62,13 +62,15 @@ func setup(t *testing.T, _ setupConfig) testDeps { deps := subscriptiontestutils.NewService(t, dbDeps) - pcSubsService := pcsubscriptionservice.New(pcsubscriptionservice.Config{ + pcSubsService, err := pcsubscriptionservice.New(pcsubscriptionservice.Config{ WorkflowService: deps.WorkflowService, SubscriptionService: deps.SubscriptionService, PlanService: deps.PlanService, + CurrencyResolver: deps.CurrencyResolver, Logger: testutils.NewLogger(t), CustomerService: deps.CustomerService, }) + require.NoError(t, err) // App appAdapter, err := appadapter.New(appadapter.Config{ diff --git a/tools/migrate/migrations/20260717195001_custom_currency_subscription_semantics.down.sql b/tools/migrate/migrations/20260717195001_custom_currency_subscription_semantics.down.sql new file mode 100644 index 0000000000..f1f26e8235 --- /dev/null +++ b/tools/migrate/migrations/20260717195001_custom_currency_subscription_semantics.down.sql @@ -0,0 +1,18 @@ +-- reverse: create index "subscriptioncostbasispin_subscription_id" to table: "subscription_cost_basis_pins" +DROP INDEX "subscriptioncostbasispin_subscription_id"; +-- reverse: create index "subscriptioncostbasispin_namespace_subscription_id_custom_curre" to table: "subscription_cost_basis_pins" +DROP INDEX "subscriptioncostbasispin_namespace_subscription_id_custom_curre"; +-- reverse: create index "subscriptioncostbasispin_namespace" to table: "subscription_cost_basis_pins" +DROP INDEX "subscriptioncostbasispin_namespace"; +-- reverse: create index "subscriptioncostbasispin_id" to table: "subscription_cost_basis_pins" +DROP INDEX "subscriptioncostbasispin_id"; +-- reverse: create index "subscriptioncostbasispin_custom_currency_id" to table: "subscription_cost_basis_pins" +DROP INDEX "subscriptioncostbasispin_custom_currency_id"; +-- reverse: create index "subscriptioncostbasispin_cost_basis_id" to table: "subscription_cost_basis_pins" +DROP INDEX "subscriptioncostbasispin_cost_basis_id"; +-- reverse: create "subscription_cost_basis_pins" table +DROP TABLE "subscription_cost_basis_pins"; +-- reverse: modify "subscriptions" table +ALTER TABLE "subscriptions" DROP COLUMN "cost_basis_mode"; +-- reverse: modify "subscription_items" table +ALTER TABLE "subscription_items" DROP CONSTRAINT "subscription_item_currency_has_price", ADD CONSTRAINT "subscription_item_currency_has_price" CHECK ((price IS NOT NULL) OR ((currency IS NULL) AND (custom_currency_id IS NULL))); diff --git a/tools/migrate/migrations/20260717195001_custom_currency_subscription_semantics.up.sql b/tools/migrate/migrations/20260717195001_custom_currency_subscription_semantics.up.sql new file mode 100644 index 0000000000..72550b2fe5 --- /dev/null +++ b/tools/migrate/migrations/20260717195001_custom_currency_subscription_semantics.up.sql @@ -0,0 +1,32 @@ +-- modify "subscription_items" table +ALTER TABLE "subscription_items" DROP CONSTRAINT "subscription_item_currency_has_price", ADD CONSTRAINT "subscription_item_currency_has_price" CHECK (((price IS NULL) AND (currency IS NULL) AND (custom_currency_id IS NULL)) OR ((price IS NOT NULL) AND ((currency IS NOT NULL) OR (custom_currency_id IS NOT NULL)))); +-- modify "subscriptions" table +ALTER TABLE "subscriptions" ADD COLUMN "cost_basis_mode" character varying NOT NULL DEFAULT 'dynamic'; +-- create "subscription_cost_basis_pins" table +CREATE TABLE "subscription_cost_basis_pins" ( + "id" character(26) NOT NULL, + "namespace" character varying NOT NULL, + "created_at" timestamptz NOT NULL, + "updated_at" timestamptz NOT NULL, + "deleted_at" timestamptz NULL, + "invoice_currency" character varying NOT NULL, + "cost_basis_id" character(26) NOT NULL, + "custom_currency_id" character(26) NOT NULL, + "subscription_id" character(26) NOT NULL, + PRIMARY KEY ("id"), + CONSTRAINT "subscription_cost_basis_pins_currency_cost_bases_subscription_p" FOREIGN KEY ("cost_basis_id") REFERENCES "currency_cost_bases" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT, + CONSTRAINT "subscription_cost_basis_pins_custom_currencies_subscription_cos" FOREIGN KEY ("custom_currency_id") REFERENCES "custom_currencies" ("id") ON UPDATE NO ACTION ON DELETE RESTRICT, + CONSTRAINT "subscription_cost_basis_pins_subscriptions_cost_basis_pins" FOREIGN KEY ("subscription_id") REFERENCES "subscriptions" ("id") ON UPDATE NO ACTION ON DELETE CASCADE +); +-- create index "subscriptioncostbasispin_cost_basis_id" to table: "subscription_cost_basis_pins" +CREATE INDEX "subscriptioncostbasispin_cost_basis_id" ON "subscription_cost_basis_pins" ("cost_basis_id"); +-- create index "subscriptioncostbasispin_custom_currency_id" to table: "subscription_cost_basis_pins" +CREATE INDEX "subscriptioncostbasispin_custom_currency_id" ON "subscription_cost_basis_pins" ("custom_currency_id"); +-- create index "subscriptioncostbasispin_id" to table: "subscription_cost_basis_pins" +CREATE UNIQUE INDEX "subscriptioncostbasispin_id" ON "subscription_cost_basis_pins" ("id"); +-- create index "subscriptioncostbasispin_namespace" to table: "subscription_cost_basis_pins" +CREATE INDEX "subscriptioncostbasispin_namespace" ON "subscription_cost_basis_pins" ("namespace"); +-- create index "subscriptioncostbasispin_namespace_subscription_id_custom_curre" to table: "subscription_cost_basis_pins" +CREATE UNIQUE INDEX "subscriptioncostbasispin_namespace_subscription_id_custom_curre" ON "subscription_cost_basis_pins" ("namespace", "subscription_id", "custom_currency_id", "invoice_currency"); +-- create index "subscriptioncostbasispin_subscription_id" to table: "subscription_cost_basis_pins" +CREATE INDEX "subscriptioncostbasispin_subscription_id" ON "subscription_cost_basis_pins" ("subscription_id"); diff --git a/tools/migrate/migrations/atlas.sum b/tools/migrate/migrations/atlas.sum index 13a99d4106..207f7e904a 100644 --- a/tools/migrate/migrations/atlas.sum +++ b/tools/migrate/migrations/atlas.sum @@ -1,4 +1,4 @@ -h1:6f4Rg+mwo9xLMzrRCjFv5hqfqLVxPBu5GVNq0/1SdNU= +h1:Px4SaMIIOHAVQVopR6skS3VRwFaf4LnnKmWvM5/jb18= 20240826120919_init.up.sql h1:tc1V91/smlmaeJGQ8h+MzTEeFjjnrrFDbDAjOYJK91o= 20240903155435_entitlement-expired-index.up.sql h1:Hp8u5uckmLXc1cRvWU0AtVnnK8ShlpzZNp8pbiJLhac= 20240917172257_billing-entities.up.sql h1:Q1dAMo0Vjiit76OybClNfYPGC5nmvov2/M2W1ioi4Kw= @@ -241,3 +241,4 @@ h1:6f4Rg+mwo9xLMzrRCjFv5hqfqLVxPBu5GVNq0/1SdNU= 20260717143312_add_rate_card_currency.up.sql h1:J6EGrDkaqRfqdTRohzJcXAld+JE3aPv5mJaJ3fEmDsE= 20260717143818_add_product_catalog_currency_references.up.sql h1:aAzAwYGErOfRAMnDXDd6kuCd451MUBKTTAQ4GUYnPeg= 20260717150347_add_subscription_item_currency_references.up.sql h1:XHGUpkjJtitf1Hha5oO4usxmtIYQtv1wpYJ5obSgCWU= +20260717195001_custom_currency_subscription_semantics.up.sql h1:P6+qgIDBC5KjXRmtBZPAVPlRzZAg1kDbkw3OzVvC3rs= diff --git a/tools/migrate/subscription_custom_currency_semantics_test.go b/tools/migrate/subscription_custom_currency_semantics_test.go new file mode 100644 index 0000000000..79bbb70811 --- /dev/null +++ b/tools/migrate/subscription_custom_currency_semantics_test.go @@ -0,0 +1,167 @@ +package migrate_test + +import ( + "errors" + "testing" + + "github.com/oklog/ulid/v2" + "github.com/stretchr/testify/require" + + "github.com/openmeterio/openmeter/openmeter/testutils" + "github.com/openmeterio/openmeter/tools/migrate" +) + +func TestSubscriptionCustomCurrencySemanticsMigrationRequiresBackfill(t *testing.T) { + const ( + namespace = "default" + previousVersion = uint(20260717150347) + targetVersion = uint(20260717195001) + ) + + // given: + // - an expand-and-write schema containing one legacy priced item without currency + // when: + // - the strict semantic migration is attempted before and after the documented backfill + // then: + // - it first fails safely, then installs strict item and pinned-cost-basis invariants + testDB := testutils.InitPostgresDB(t, testutils.PostgresDBStateEmpty) + t.Cleanup(func() { testDB.Close(t) }) + + migrator, err := migrate.New(migrate.MigrateOptions{ + ConnectionString: testDB.URL, + Migrations: migrate.OMMigrationsConfig, + Logger: testutils.NewLogger(t), + }) + require.NoError(t, err) + t.Cleanup(func() { + sourceErr, databaseErr := migrator.Close() + require.NoError(t, errors.Join(sourceErr, databaseErr)) + }) + require.NoError(t, migrator.Migrate(previousVersion)) + + db := testDB.PGDriver.DB() + customerID := ulid.Make().String() + subscriptionID := ulid.Make().String() + phaseID := ulid.Make().String() + itemID := ulid.Make().String() + + _, err = db.Exec(` + INSERT INTO customers (id, namespace, created_at, updated_at, key, name, currency) + VALUES ($1, $2, NOW(), NOW(), 'legacy-customer', 'Legacy Customer', 'USD') + `, customerID, namespace) + require.NoError(t, err) + + _, err = db.Exec(` + INSERT INTO subscriptions ( + id, namespace, created_at, updated_at, active_from, customer_id, currency, + billing_anchor, billing_cadence, pro_rating_config + ) + VALUES ( + $1, $2, NOW(), NOW(), '2024-01-01', $3, 'USD', '2024-01-01', 'P1M', + '{"enabled":true,"mode":"prorate_prices"}'::jsonb + ) + `, subscriptionID, namespace, customerID) + require.NoError(t, err) + + _, err = db.Exec(` + INSERT INTO subscription_phases ( + id, namespace, created_at, updated_at, key, name, subscription_id, active_from + ) + VALUES ($1, $2, NOW(), NOW(), 'default', 'Default', $3, '2024-01-01') + `, phaseID, namespace, subscriptionID) + require.NoError(t, err) + + _, err = db.Exec(` + INSERT INTO subscription_items ( + id, namespace, created_at, updated_at, active_from, key, name, phase_id, price + ) + VALUES ( + $1, $2, NOW(), NOW(), '2024-01-01', 'legacy', 'Legacy', $3, + '{"type":"flat","amount":"10","paymentTerm":"in_advance"}'::jsonb + ) + `, itemID, namespace, phaseID) + require.NoError(t, err) + + err = migrator.Migrate(targetVersion) + require.Error(t, err, "strict migration must not silently strand a legacy priced item") + require.NoError(t, migrator.Force(previousVersion), "clear the failed migration marker after the transactional DDL rollback") + + _, err = db.Exec(` + UPDATE subscription_items AS item + SET currency = subscription.currency, + updated_at = NOW() + FROM subscription_phases AS phase + JOIN subscriptions AS subscription + ON subscription.id = phase.subscription_id + AND subscription.namespace = phase.namespace + WHERE item.phase_id = phase.id + AND item.namespace = phase.namespace + AND item.price IS NOT NULL + AND item.currency IS NULL + AND item.custom_currency_id IS NULL + `) + require.NoError(t, err) + require.NoError(t, migrator.Migrate(targetVersion)) + + var costBasisMode string + err = db.QueryRow(`SELECT cost_basis_mode FROM subscriptions WHERE id = $1`, subscriptionID).Scan(&costBasisMode) + require.NoError(t, err) + require.Equal(t, "dynamic", costBasisMode) + + var subscriptionLookupIndex *string + err = db.QueryRow(`SELECT to_regclass('subscriptioncostbasispin_subscription_id')::text`).Scan(&subscriptionLookupIndex) + require.NoError(t, err) + require.NotNil(t, subscriptionLookupIndex, "pin eager-loading must have a subscription ID index") + + _, err = db.Exec(`UPDATE subscription_items SET currency = NULL WHERE id = $1`, itemID) + require.Error(t, err, "priced items must carry exactly one currency reference") + + _, err = db.Exec(`UPDATE subscription_items SET price = NULL WHERE id = $1`, itemID) + require.Error(t, err, "unpriced items must not carry a currency reference") + + customCurrencyID := ulid.Make().String() + costBasisID := ulid.Make().String() + pinID := ulid.Make().String() + _, err = db.Exec(` + INSERT INTO custom_currencies (id, namespace, created_at, updated_at, code, name, symbol) + VALUES ($1, $2, NOW(), NOW(), 'CREDITS', 'Credits', 'CR') + `, customCurrencyID, namespace) + require.NoError(t, err) + + _, err = db.Exec(` + INSERT INTO currency_cost_bases ( + id, namespace, created_at, updated_at, currency_id, fiat_code, rate, effective_from + ) + VALUES ($1, $2, NOW(), NOW(), $3, 'USD', 0.5, '2024-01-01') + `, costBasisID, namespace, customCurrencyID) + require.NoError(t, err) + + _, err = db.Exec(` + INSERT INTO subscription_cost_basis_pins ( + id, namespace, created_at, updated_at, subscription_id, custom_currency_id, + invoice_currency, cost_basis_id + ) + VALUES ($1, $2, NOW(), NOW(), $3, $4, 'USD', $5) + `, pinID, namespace, subscriptionID, customCurrencyID, costBasisID) + require.NoError(t, err) + + _, err = db.Exec(` + INSERT INTO subscription_cost_basis_pins ( + id, namespace, created_at, updated_at, subscription_id, custom_currency_id, + invoice_currency, cost_basis_id + ) + VALUES ($1, $2, NOW(), NOW(), $3, $4, 'USD', $5) + `, ulid.Make().String(), namespace, subscriptionID, customCurrencyID, costBasisID) + require.Error(t, err, "a subscription can pin a custom/fiat pair only once") + + var pinCount int + err = db.QueryRow(`SELECT COUNT(*) FROM subscription_cost_basis_pins WHERE id = $1`, pinID).Scan(&pinCount) + require.NoError(t, err) + require.Equal(t, 1, pinCount) + + require.NoError(t, migrator.Migrate(previousVersion)) + var pinTable *string + err = db.QueryRow(`SELECT to_regclass('subscription_cost_basis_pins')::text`).Scan(&pinTable) + require.NoError(t, err) + require.Nil(t, pinTable) +}