diff --git a/server/src/internal/balances/utils/deduction/prepareFeatureDeduction.ts b/server/src/internal/balances/utils/deduction/prepareFeatureDeduction.ts index e051c5103..ce8f833fb 100644 --- a/server/src/internal/balances/utils/deduction/prepareFeatureDeduction.ts +++ b/server/src/internal/balances/utils/deduction/prepareFeatureDeduction.ts @@ -63,7 +63,7 @@ export const prepareFeatureDeduction = ({ customerEntitlementFilters, }); - if (fullCustomer.entity?.id && fullCustomer.config?.block_shared_pool) { + if (fullCustomer.entity?.id && fullCustomer.config?.disable_pooled_balance) { cusEnts = cusEnts.filter((ce) => isEntityCusEnt({ cusEnt: ce })); } diff --git a/server/src/internal/customers/actions/update/updateCustomer.ts b/server/src/internal/customers/actions/update/updateCustomer.ts index 6a78af4d1..9f45579ad 100644 --- a/server/src/internal/customers/actions/update/updateCustomer.ts +++ b/server/src/internal/customers/actions/update/updateCustomer.ts @@ -152,8 +152,8 @@ export const updateCustomer = async ({ ? { config: { ...(originalCustomer.config ?? {}), - ...(config.block_shared_pool !== undefined && { - block_shared_pool: config.block_shared_pool, + ...(config.disable_pooled_balance !== undefined && { + disable_pooled_balance: config.disable_pooled_balance, }), }, } diff --git a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts index b5e4ee597..d765efaad 100644 --- a/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts +++ b/server/src/internal/customers/cusUtils/apiCusUtils/getApiCustomerBase.ts @@ -86,7 +86,7 @@ export const getApiCustomerBase = async ({ overage_allowed: fullCus.overage_allowed ?? undefined, }, config: { - block_shared_pool: fullCus.config?.block_shared_pool, + disable_pooled_balance: fullCus.config?.disable_pooled_balance, }, invoices: diff --git a/server/tests/balances/track/entity-balances/track-entity-balances7.test.ts b/server/tests/balances/track/entity-balances/track-entity-balances7.test.ts index af4786049..9991061d4 100644 --- a/server/tests/balances/track/entity-balances/track-entity-balances7.test.ts +++ b/server/tests/balances/track/entity-balances/track-entity-balances7.test.ts @@ -9,9 +9,9 @@ import chalk from "chalk"; import { setCustomerOverageAllowed } from "../../../integration/balances/utils/overage-allowed-utils/customerOverageAllowedUtils.js"; /** - * track-entity-balances7: block_shared_pool + * track-entity-balances7: disable_pooled_balance * - * When customer.config.block_shared_pool is true and a deduction is scoped + * When customer.config.disable_pooled_balance is true and a deduction is scoped * to an entity, the entity must NOT fall back into the shared customer pool. * * Three deterministic branches: @@ -54,7 +54,7 @@ test.concurrent(`${chalk.yellowBright("track-entity-balances7-reject: entity ove s.deleteCustomer({ customerId }), s.customer({ testClock: false, - data: { config: { block_shared_pool: true } }, + data: { config: { disable_pooled_balance: true } }, }), s.products({ list: [freeProd] }), s.entities({ count: 1, featureId: TestFeature.Users }), @@ -111,7 +111,7 @@ test.concurrent(`${chalk.yellowBright("track-entity-balances7-cap: entity caps a s.deleteCustomer({ customerId }), s.customer({ testClock: false, - data: { config: { block_shared_pool: true } }, + data: { config: { disable_pooled_balance: true } }, }), s.products({ list: [freeProd] }), s.entities({ count: 1, featureId: TestFeature.Users }), @@ -169,7 +169,7 @@ test.concurrent(`${chalk.yellowBright("track-entity-balances7-overage: entity go s.deleteCustomer({ customerId }), s.customer({ testClock: false, - data: { config: { block_shared_pool: true } }, + data: { config: { disable_pooled_balance: true } }, }), s.products({ list: [freeProd] }), s.entities({ count: 1, featureId: TestFeature.Users }), diff --git a/server/tests/integration/crud/customers/customer-config.test.ts b/server/tests/integration/crud/customers/customer-config.test.ts index a0d5ca5fc..566dddc1e 100644 --- a/server/tests/integration/crud/customers/customer-config.test.ts +++ b/server/tests/integration/crud/customers/customer-config.test.ts @@ -5,256 +5,232 @@ import chalk from "chalk"; import { CusService } from "@/internal/customers/CusService.js"; // ═══════════════════════════════════════════════════════════════════════════════ -// CUSTOMER CONFIG — block_shared_pool +// CUSTOMER CONFIG — disable_pooled_balance // Mirrors the billing_controls test style: cached API read, uncached API read, // and a direct DB read to prove the field round-trips through every layer. // ═══════════════════════════════════════════════════════════════════════════════ -test.concurrent( - `${chalk.yellowBright("customer config: create customer without config leaves block_shared_pool undefined")}`, - async () => { - const customerId = "customer-config-default"; - const { autumnV2_1, ctx } = await initScenario({ - setup: [s.deleteCustomer({ customerId })], - actions: [], - }); +test.concurrent(`${chalk.yellowBright("customer config: create customer without config leaves disable_pooled_balance undefined")}`, async () => { + const customerId = "customer-config-default"; + const { autumnV2_1, ctx } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); - await autumnV2_1.customers.create({ - id: customerId, - name: "Config Default", - email: `${customerId}@example.com`, - }); + await autumnV2_1.customers.create({ + id: customerId, + name: "Config Default", + email: `${customerId}@example.com`, + }); - const cached = await autumnV2_1.customers.get(customerId); - expect(cached.config).toBeDefined(); - expect(cached.config?.block_shared_pool).toBeUndefined(); + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.config).toBeDefined(); + expect(cached.config?.disable_pooled_balance).toBeUndefined(); - const uncached = await autumnV2_1.customers.get(customerId, { - skip_cache: "true", - }); - expect(uncached.config?.block_shared_pool).toBeUndefined(); + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.config?.disable_pooled_balance).toBeUndefined(); - const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId }); - // DB row should be null/undefined when no config was provided. - expect(fromDb.config?.block_shared_pool).toBeUndefined(); - }, -); + const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId }); + // DB row should be null/undefined when no config was provided. + expect(fromDb.config?.disable_pooled_balance).toBeUndefined(); +}); -test.concurrent( - `${chalk.yellowBright("customer config: create customer with block_shared_pool=true")}`, - async () => { - const customerId = "customer-config-create-true"; - const { autumnV2_1, ctx } = await initScenario({ - setup: [s.deleteCustomer({ customerId })], - actions: [], - }); +test.concurrent(`${chalk.yellowBright("customer config: create customer with disable_pooled_balance=true")}`, async () => { + const customerId = "customer-config-create-true"; + const { autumnV2_1, ctx } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); - await autumnV2_1.customers.create({ - id: customerId, - name: "Config Create True", - email: `${customerId}@example.com`, - config: { block_shared_pool: true }, - }); + await autumnV2_1.customers.create({ + id: customerId, + name: "Config Create True", + email: `${customerId}@example.com`, + config: { disable_pooled_balance: true }, + }); - const cached = await autumnV2_1.customers.get(customerId); - expect(cached.config?.block_shared_pool).toBe(true); + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.config?.disable_pooled_balance).toBe(true); - const uncached = await autumnV2_1.customers.get(customerId, { - skip_cache: "true", - }); - expect(uncached.config?.block_shared_pool).toBe(true); + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.config?.disable_pooled_balance).toBe(true); - const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId }); - expect(fromDb.config?.block_shared_pool).toBe(true); - }, -); + const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId }); + expect(fromDb.config?.disable_pooled_balance).toBe(true); +}); -test.concurrent( - `${chalk.yellowBright("customer config: create customer with block_shared_pool=false")}`, - async () => { - const customerId = "customer-config-create-false"; - const { autumnV2_1, ctx } = await initScenario({ - setup: [s.deleteCustomer({ customerId })], - actions: [], - }); +test.concurrent(`${chalk.yellowBright("customer config: create customer with disable_pooled_balance=false")}`, async () => { + const customerId = "customer-config-create-false"; + const { autumnV2_1, ctx } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); - await autumnV2_1.customers.create({ - id: customerId, - name: "Config Create False", - email: `${customerId}@example.com`, - config: { block_shared_pool: false }, - }); + await autumnV2_1.customers.create({ + id: customerId, + name: "Config Create False", + email: `${customerId}@example.com`, + config: { disable_pooled_balance: false }, + }); - const cached = await autumnV2_1.customers.get(customerId); - expect(cached.config?.block_shared_pool).toBe(false); + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.config?.disable_pooled_balance).toBe(false); - const uncached = await autumnV2_1.customers.get(customerId, { - skip_cache: "true", - }); - expect(uncached.config?.block_shared_pool).toBe(false); + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.config?.disable_pooled_balance).toBe(false); - const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId }); - expect(fromDb.config?.block_shared_pool).toBe(false); - }, -); + const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId }); + expect(fromDb.config?.disable_pooled_balance).toBe(false); +}); -test.concurrent( - `${chalk.yellowBright("customer config: update block_shared_pool from unset to true")}`, - async () => { - const customerId = "customer-config-update-true"; - const { autumnV2_1, ctx } = await initScenario({ - customerId, - setup: [s.customer({})], - actions: [], - }); +test.concurrent(`${chalk.yellowBright("customer config: update disable_pooled_balance from unset to true")}`, async () => { + const customerId = "customer-config-update-true"; + const { autumnV2_1, ctx } = await initScenario({ + customerId, + setup: [s.customer({})], + actions: [], + }); - const before = await autumnV2_1.customers.get(customerId); - expect(before.config?.block_shared_pool).toBeUndefined(); + const before = await autumnV2_1.customers.get(customerId); + expect(before.config?.disable_pooled_balance).toBeUndefined(); - await autumnV2_1.customers.update(customerId, { - config: { block_shared_pool: true }, - }); + await autumnV2_1.customers.update(customerId, { + config: { disable_pooled_balance: true }, + }); - const cached = await autumnV2_1.customers.get(customerId); - expect(cached.config?.block_shared_pool).toBe(true); + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.config?.disable_pooled_balance).toBe(true); - const uncached = await autumnV2_1.customers.get(customerId, { - skip_cache: "true", - }); - expect(uncached.config?.block_shared_pool).toBe(true); + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.config?.disable_pooled_balance).toBe(true); - const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId }); - expect(fromDb.config?.block_shared_pool).toBe(true); - }, -); + const fromDb = await CusService.getFull({ ctx, idOrInternalId: customerId }); + expect(fromDb.config?.disable_pooled_balance).toBe(true); +}); -test.concurrent( - `${chalk.yellowBright("customer config: update block_shared_pool from true to false")}`, - async () => { - const customerId = "customer-config-update-false"; - const { autumnV2_1 } = await initScenario({ - setup: [s.deleteCustomer({ customerId })], - actions: [], - }); +test.concurrent(`${chalk.yellowBright("customer config: update disable_pooled_balance from true to false")}`, async () => { + const customerId = "customer-config-update-false"; + const { autumnV2_1 } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); - await autumnV2_1.customers.create({ - id: customerId, - name: "Config Flip Off", - email: `${customerId}@example.com`, - config: { block_shared_pool: true }, - }); + await autumnV2_1.customers.create({ + id: customerId, + name: "Config Flip Off", + email: `${customerId}@example.com`, + config: { disable_pooled_balance: true }, + }); - const before = await autumnV2_1.customers.get(customerId); - expect(before.config?.block_shared_pool).toBe(true); + const before = await autumnV2_1.customers.get(customerId); + expect(before.config?.disable_pooled_balance).toBe(true); - await autumnV2_1.customers.update(customerId, { - config: { block_shared_pool: false }, - }); + await autumnV2_1.customers.update(customerId, { + config: { disable_pooled_balance: false }, + }); - const cached = await autumnV2_1.customers.get(customerId); - expect(cached.config?.block_shared_pool).toBe(false); + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.config?.disable_pooled_balance).toBe(false); - const uncached = await autumnV2_1.customers.get(customerId, { - skip_cache: "true", - }); - expect(uncached.config?.block_shared_pool).toBe(false); - }, -); + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.config?.disable_pooled_balance).toBe(false); +}); -test.concurrent( - `${chalk.yellowBright("customer config: partial update leaves other fields untouched")}`, - async () => { - // Regression guard: mirrors the billing_controls pattern — updating config - // shouldn't clobber name/email/send_email_receipts. - const customerId = "customer-config-partial"; - const { autumnV2_1 } = await initScenario({ - setup: [s.deleteCustomer({ customerId })], - actions: [], - }); +test.concurrent(`${chalk.yellowBright("customer config: partial update leaves other fields untouched")}`, async () => { + // Regression guard: mirrors the billing_controls pattern — updating config + // shouldn't clobber name/email/send_email_receipts. + const customerId = "customer-config-partial"; + const { autumnV2_1 } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); - await autumnV2_1.customers.create({ - id: customerId, - name: "Original Name", - email: `${customerId}@example.com`, - send_email_receipts: true, - }); + await autumnV2_1.customers.create({ + id: customerId, + name: "Original Name", + email: `${customerId}@example.com`, + send_email_receipts: true, + }); - await autumnV2_1.customers.update(customerId, { - config: { block_shared_pool: true }, - }); + await autumnV2_1.customers.update(customerId, { + config: { disable_pooled_balance: true }, + }); - const cached = await autumnV2_1.customers.get(customerId); - expect(cached.name).toBe("Original Name"); - expect(cached.email).toBe(`${customerId}@example.com`); - expect(cached.send_email_receipts).toBe(true); - expect(cached.config?.block_shared_pool).toBe(true); - }, -); + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.name).toBe("Original Name"); + expect(cached.email).toBe(`${customerId}@example.com`); + expect(cached.send_email_receipts).toBe(true); + expect(cached.config?.disable_pooled_balance).toBe(true); +}); -test.concurrent( - `${chalk.yellowBright("customer config: omitting config in update does not reset it")}`, - async () => { - const customerId = "customer-config-omit"; - const { autumnV2_1 } = await initScenario({ - setup: [s.deleteCustomer({ customerId })], - actions: [], - }); +test.concurrent(`${chalk.yellowBright("customer config: omitting config in update does not reset it")}`, async () => { + const customerId = "customer-config-omit"; + const { autumnV2_1 } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); - await autumnV2_1.customers.create({ - id: customerId, - name: "Config Persist", - email: `${customerId}@example.com`, - config: { block_shared_pool: true }, - }); + await autumnV2_1.customers.create({ + id: customerId, + name: "Config Persist", + email: `${customerId}@example.com`, + config: { disable_pooled_balance: true }, + }); - // Update something unrelated — config should survive. - await autumnV2_1.customers.update(customerId, { - name: "Config Persist Renamed", - }); + // Update something unrelated — config should survive. + await autumnV2_1.customers.update(customerId, { + name: "Config Persist Renamed", + }); - const cached = await autumnV2_1.customers.get(customerId); - expect(cached.name).toBe("Config Persist Renamed"); - expect(cached.config?.block_shared_pool).toBe(true); + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.name).toBe("Config Persist Renamed"); + expect(cached.config?.disable_pooled_balance).toBe(true); - const uncached = await autumnV2_1.customers.get(customerId, { - skip_cache: "true", - }); - expect(uncached.config?.block_shared_pool).toBe(true); - }, -); + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.config?.disable_pooled_balance).toBe(true); +}); -test.concurrent( - `${chalk.yellowBright("customer config: updating billing_controls alongside config does not clobber either")}`, - async () => { - // Cross-field regression: config and billing_controls are both partial - // updates on the same row — updating one must not reset the other. - const customerId = "customer-config-mixed"; - const { autumnV2_1 } = await initScenario({ - setup: [s.deleteCustomer({ customerId })], - actions: [], - }); +test.concurrent(`${chalk.yellowBright("customer config: updating billing_controls alongside config does not clobber either")}`, async () => { + // Cross-field regression: config and billing_controls are both partial + // updates on the same row — updating one must not reset the other. + const customerId = "customer-config-mixed"; + const { autumnV2_1 } = await initScenario({ + setup: [s.deleteCustomer({ customerId })], + actions: [], + }); - await autumnV2_1.customers.create({ - id: customerId, - name: "Mixed", - email: `${customerId}@example.com`, - config: { block_shared_pool: true }, - }); + await autumnV2_1.customers.create({ + id: customerId, + name: "Mixed", + email: `${customerId}@example.com`, + config: { disable_pooled_balance: true }, + }); - await autumnV2_1.customers.update(customerId, { - billing_controls: { - spend_limits: [], - }, - }); + await autumnV2_1.customers.update(customerId, { + billing_controls: { + spend_limits: [], + }, + }); - const cached = await autumnV2_1.customers.get(customerId); - expect(cached.config?.block_shared_pool).toBe(true); - expect(cached.billing_controls?.spend_limits).toEqual([]); + const cached = await autumnV2_1.customers.get(customerId); + expect(cached.config?.disable_pooled_balance).toBe(true); + expect(cached.billing_controls?.spend_limits).toEqual([]); - const uncached = await autumnV2_1.customers.get(customerId, { - skip_cache: "true", - }); - expect(uncached.config?.block_shared_pool).toBe(true); - expect(uncached.billing_controls?.spend_limits).toEqual([]); - }, -); + const uncached = await autumnV2_1.customers.get(customerId, { + skip_cache: "true", + }); + expect(uncached.config?.disable_pooled_balance).toBe(true); + expect(uncached.billing_controls?.spend_limits).toEqual([]); +}); diff --git a/shared/api/common/customerData.ts b/shared/api/common/customerData.ts index 2bf81181c..3ec57eec0 100644 --- a/shared/api/common/customerData.ts +++ b/shared/api/common/customerData.ts @@ -56,7 +56,7 @@ export const CustomerDataSchema = z config: z .object({ - block_shared_pool: z.boolean().optional().meta({ + disable_pooled_balance: z.boolean().optional().meta({ description: "Whether to block this customer from the shared feature pool.", }), diff --git a/shared/api/customers/apiCustomerV5.ts b/shared/api/customers/apiCustomerV5.ts index 81bea74d9..366529090 100644 --- a/shared/api/customers/apiCustomerV5.ts +++ b/shared/api/customers/apiCustomerV5.ts @@ -76,7 +76,7 @@ export const API_CUSTOMER_V5_EXAMPLE = { }, }, config: { - block_shared_pool: false, + disable_pooled_balance: false, }, }; @@ -99,12 +99,9 @@ export const BaseApiCustomerV5Schema = BaseApiCustomerSchema.extend({ }), config: z .object({ - block_shared_pool: z - .boolean() - .optional() - .meta({ - description: "Whether to block the shared pool for the customer.", - }), + disable_pooled_balance: z.boolean().optional().meta({ + description: "Whether to block the shared pool for the customer.", + }), }) .optional() .meta({ diff --git a/shared/api/customers/cusFeatures/utils/getApiBalances.ts b/shared/api/customers/cusFeatures/utils/getApiBalances.ts index d421a6f03..cb7881c5f 100644 --- a/shared/api/customers/cusFeatures/utils/getApiBalances.ts +++ b/shared/api/customers/cusFeatures/utils/getApiBalances.ts @@ -29,11 +29,11 @@ export const getApiBalances = async ({ entity: fullCus.entity, }); - // When block_shared_pool is enabled and we're scoped to an entity, drop + // When disable_pooled_balance is enabled and we're scoped to an entity, drop // customer-level (shared pool) cusEnts so the returned balances reflect // only the entity's own pool. Matches the filter in prepareFeatureDeduction // so the deduction path and reporting stay consistent. - if (fullCus.entity?.id && fullCus.config?.block_shared_pool) { + if (fullCus.entity?.id && fullCus.config?.disable_pooled_balance) { allCusEnts = allCusEnts.filter((ce) => isEntityCusEnt({ cusEnt: ce })); } diff --git a/shared/models/cusModels/cusModels.ts b/shared/models/cusModels/cusModels.ts index df9d57cd8..9412359ef 100644 --- a/shared/models/cusModels/cusModels.ts +++ b/shared/models/cusModels/cusModels.ts @@ -29,7 +29,7 @@ export const CustomerSchema = z.object({ overage_allowed: z.array(DbOverageAllowedSchema).nullish(), config: z .object({ - block_shared_pool: z.boolean().optional(), + disable_pooled_balance: z.boolean().optional(), }) .nullish(), }); diff --git a/shared/models/cusModels/cusTable.ts b/shared/models/cusModels/cusTable.ts index 23105d4c8..01def5f1d 100644 --- a/shared/models/cusModels/cusTable.ts +++ b/shared/models/cusModels/cusTable.ts @@ -21,7 +21,7 @@ import type { } from "./billingControls/customerBillingControls.js"; export type CustomerConfig = { - block_shared_pool?: boolean; + disable_pooled_balance?: boolean; }; export type CustomerProcessor = {