refactor: third iteration of update subscription cleanup
This commit is contained in:
@@ -51,8 +51,8 @@ export const computeStripeInvoiceAction = ({
|
||||
description: detail.stripeInvoiceItemDescription,
|
||||
amount: Math.round(detail.calculatedProrationAmountDollars * 100),
|
||||
period: {
|
||||
start: msToSeconds(detail.subscriptionPeriodStartEpochMs),
|
||||
end: msToSeconds(detail.subscriptionPeriodEndEpochMs),
|
||||
start: msToSeconds(detail.billingPeriod.start),
|
||||
end: msToSeconds(detail.billingPeriod.end),
|
||||
},
|
||||
}));
|
||||
|
||||
|
||||
@@ -1,18 +1,21 @@
|
||||
import {
|
||||
cusProductToProduct,
|
||||
extractBillingPeriod,
|
||||
type FeatureOptions,
|
||||
findFeatureByInternalId,
|
||||
findFeatureOptionsByFeature,
|
||||
InternalError,
|
||||
type LineItemContext,
|
||||
orgToCurrency,
|
||||
secondsToMs,
|
||||
} from "@autumn/shared";
|
||||
import { usagePriceToLineDescription } from "@autumn/shared/utils/billingUtils/invoicingUtils/descriptionUtils/usagePriceToLineDescription";
|
||||
import { getLineItemBillingPeriod } from "@shared/utils/billingUtils/cycleUtils/getLineItemBillingPeriod";
|
||||
import type Stripe from "stripe";
|
||||
import { findStripeItemForPrice } from "@/external/stripe/stripeSubUtils/stripeSubItemUtils";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
||||
import type { QuantityUpdateDetails } from "@/internal/billing/v2/typesOld";
|
||||
import type { UpdateSubscriptionContext } from "../fetch/updateSubscriptionContextSchema";
|
||||
import { calculateEntitlementChange } from "./quantityUpdateUtils/calculateEntitlementChange";
|
||||
import { calculateCustomerEntitlementChange } from "./quantityUpdateUtils/calculateCustomerEntitlementChange";
|
||||
import { calculateProrationAmount } from "./quantityUpdateUtils/calculateProrationAmount";
|
||||
import { calculateQuantityDifferences } from "./quantityUpdateUtils/calculateQuantityDifferences";
|
||||
import { resolvePriceForQuantityUpdate } from "./quantityUpdateUtils/resolvePriceForQuantityUpdate";
|
||||
@@ -32,18 +35,16 @@ import { resolvePriceForQuantityUpdate } from "./quantityUpdateUtils/resolvePric
|
||||
*/
|
||||
export const computeQuantityUpdateDetails = ({
|
||||
ctx,
|
||||
previousOptions,
|
||||
updatedOptions,
|
||||
updateSubscriptionContext,
|
||||
}: {
|
||||
ctx: AutumnContext;
|
||||
previousOptions: FeatureOptions;
|
||||
updatedOptions: FeatureOptions;
|
||||
updateSubscriptionContext: UpdateSubscriptionContext;
|
||||
}): QuantityUpdateDetails => {
|
||||
const { customerProduct, stripeSubscription, currentEpochMs } =
|
||||
updateSubscriptionContext;
|
||||
const { features } = ctx;
|
||||
const { features, org } = ctx;
|
||||
|
||||
const internalFeatureId = updatedOptions.internal_feature_id;
|
||||
const featureId = updatedOptions.feature_id;
|
||||
@@ -71,6 +72,11 @@ export const computeQuantityUpdateDetails = ({
|
||||
});
|
||||
}
|
||||
|
||||
const previousOptions = findFeatureOptionsByFeature({
|
||||
featureOptions: customerProduct.options,
|
||||
feature,
|
||||
});
|
||||
|
||||
const quantityDifferences = calculateQuantityDifferences({
|
||||
previousOptions,
|
||||
updatedOptions,
|
||||
@@ -82,13 +88,28 @@ export const computeQuantityUpdateDetails = ({
|
||||
isUpgrade: quantityDifferences.isUpgrade,
|
||||
});
|
||||
|
||||
const billingPeriod = extractBillingPeriod({
|
||||
stripeSubscription,
|
||||
interval: priceConfiguration.priceConfig.interval,
|
||||
intervalCount: priceConfiguration.priceConfig.interval_count,
|
||||
currentEpochMs,
|
||||
const billingCycleAnchorMs = secondsToMs(
|
||||
stripeSubscription.billing_cycle_anchor,
|
||||
);
|
||||
|
||||
if (!billingCycleAnchorMs) {
|
||||
throw new InternalError({
|
||||
message: `[Quantity Update] Invalid billing_cycle_anchor: ${stripeSubscription.billing_cycle_anchor}`,
|
||||
});
|
||||
}
|
||||
|
||||
const billingPeriod = getLineItemBillingPeriod({
|
||||
anchor: billingCycleAnchorMs,
|
||||
price: priceConfiguration.price,
|
||||
now: currentEpochMs,
|
||||
});
|
||||
|
||||
if (!billingPeriod) {
|
||||
throw new InternalError({
|
||||
message: `[Quantity Update] Billing period not found for price: ${priceConfiguration.price.id}`,
|
||||
});
|
||||
}
|
||||
|
||||
const calculatedProrationAmountDollars = calculateProrationAmount({
|
||||
updateSubscriptionContext,
|
||||
previousOptions,
|
||||
@@ -100,11 +121,13 @@ export const computeQuantityUpdateDetails = ({
|
||||
|
||||
const product = cusProductToProduct({ cusProduct: customerProduct });
|
||||
|
||||
const currency = orgToCurrency({ org });
|
||||
|
||||
const lineItemContext: LineItemContext = {
|
||||
price: priceConfiguration.price,
|
||||
product,
|
||||
feature,
|
||||
currency: "usd",
|
||||
currency,
|
||||
direction: "charge",
|
||||
now: currentEpochMs,
|
||||
billingTiming: "in_advance",
|
||||
@@ -120,7 +143,7 @@ export const computeQuantityUpdateDetails = ({
|
||||
stripeItems: stripeSubscription.items.data,
|
||||
}) as Stripe.SubscriptionItem | undefined;
|
||||
|
||||
const entitlementChange = calculateEntitlementChange({
|
||||
const entitlementChange = calculateCustomerEntitlementChange({
|
||||
quantityDifferenceForEntitlements:
|
||||
quantityDifferences.quantityDifferenceForEntitlements,
|
||||
billingUnitsPerQuantity: priceConfiguration.billingUnitsPerQuantity,
|
||||
@@ -137,6 +160,7 @@ export const computeQuantityUpdateDetails = ({
|
||||
return {
|
||||
featureId,
|
||||
internalFeatureId,
|
||||
billingPeriod,
|
||||
previousFeatureQuantity: previousOptions.quantity,
|
||||
updatedFeatureQuantity: updatedOptions.quantity,
|
||||
quantityDifferenceForEntitlements:
|
||||
@@ -148,9 +172,6 @@ export const computeQuantityUpdateDetails = ({
|
||||
priceConfiguration.shouldFinalizeInvoiceImmediately,
|
||||
billingUnitsPerQuantity: priceConfiguration.billingUnitsPerQuantity,
|
||||
calculatedProrationAmountDollars,
|
||||
subscriptionPeriodStartEpochMs:
|
||||
billingPeriod.subscriptionPeriodStartEpochMs,
|
||||
subscriptionPeriodEndEpochMs: billingPeriod.subscriptionPeriodEndEpochMs,
|
||||
stripeInvoiceItemDescription,
|
||||
customerPrice: priceConfiguration.customerPrice,
|
||||
stripePriceId: priceConfiguration.price.config.stripe_price_id,
|
||||
|
||||
@@ -1,8 +1,4 @@
|
||||
import {
|
||||
findFeatureOptionsByFeature,
|
||||
InternalError,
|
||||
type SubscriptionUpdateV0Params,
|
||||
} from "@autumn/shared";
|
||||
import { InternalError, type SubscriptionUpdateV0Params } from "@autumn/shared";
|
||||
import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
||||
import type { BillingPlan } from "../../billingPlan";
|
||||
import { buildStripeSubscriptionAction } from "../../providers/stripe/actionBuilders/buildStripeSubscriptionAction";
|
||||
@@ -30,19 +26,13 @@ export const computeSubscriptionUpdateQuantityPlan = ({
|
||||
|
||||
const newOptions = params.options || [];
|
||||
|
||||
const quantityUpdateDetails = newOptions.map((updatedOption) => {
|
||||
const previousOption = findFeatureOptionsByFeature({
|
||||
featureOptions: customerProduct.options,
|
||||
featureId: updatedOption.feature_id,
|
||||
});
|
||||
|
||||
return computeQuantityUpdateDetails({
|
||||
const quantityUpdateDetails = newOptions.map((updatedOptions) =>
|
||||
computeQuantityUpdateDetails({
|
||||
ctx,
|
||||
previousOptions: previousOption,
|
||||
updatedOptions: updatedOption,
|
||||
updatedOptions,
|
||||
updateSubscriptionContext,
|
||||
});
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
const customerProductWithNewOptions = {
|
||||
...customerProduct,
|
||||
|
||||
@@ -17,7 +17,7 @@ import { getRelatedCusEnt } from "@/internal/customers/cusProducts/cusPrices/cus
|
||||
* @param customerEntitlements - Array of all entitlements for this customer product
|
||||
* @returns Entitlement ID and balance change to apply
|
||||
*/
|
||||
export const calculateEntitlementChange = ({
|
||||
export const calculateCustomerEntitlementChange = ({
|
||||
quantityDifferenceForEntitlements,
|
||||
billingUnitsPerQuantity,
|
||||
customerPrice,
|
||||
@@ -1,6 +1,6 @@
|
||||
import {
|
||||
applyProration,
|
||||
type extractBillingPeriod,
|
||||
type BillingPeriod,
|
||||
type FeatureOptions,
|
||||
priceToLineAmount,
|
||||
} from "@autumn/shared";
|
||||
@@ -36,11 +36,10 @@ export const calculateProrationAmount = ({
|
||||
updatedOptions: FeatureOptions;
|
||||
priceConfiguration: ReturnType<typeof resolvePriceForQuantityUpdate>;
|
||||
quantityDifferences: ReturnType<typeof calculateQuantityDifferences>;
|
||||
billingPeriod: ReturnType<typeof extractBillingPeriod>;
|
||||
billingPeriod: BillingPeriod;
|
||||
}): number | undefined => {
|
||||
const { stripeSubscription, currentEpochMs } = updateSubscriptionContext;
|
||||
const { price, billingUnitsPerQuantity, shouldApplyProration } =
|
||||
priceConfiguration;
|
||||
const { price, billingUnitsPerQuantity } = priceConfiguration;
|
||||
const { isUpgrade } = quantityDifferences;
|
||||
|
||||
if (!stripeSubscription) {
|
||||
@@ -49,7 +48,7 @@ export const calculateProrationAmount = ({
|
||||
|
||||
const isTrialing = stripeSubscription.status === "trialing";
|
||||
|
||||
if (!shouldApplyProration || isTrialing) {
|
||||
if (isTrialing) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -76,10 +75,7 @@ export const calculateProrationAmount = ({
|
||||
|
||||
const proratedAmountDollars = applyProration({
|
||||
now: currentEpochMs,
|
||||
billingPeriod: {
|
||||
start: billingPeriod.subscriptionPeriodStartEpochMs,
|
||||
end: billingPeriod.subscriptionPeriodEndEpochMs,
|
||||
},
|
||||
billingPeriod,
|
||||
amount: amountDifferenceDollars.toNumber(),
|
||||
});
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@ import type { AutumnContext } from "@/honoUtils/HonoEnv";
|
||||
import { fetchStripeCustomerForBilling } from "@/internal/billing/v2/providers/stripe/fetch/fetchStripeCustomerForBilling";
|
||||
import { fetchStripeSubscriptionForBilling } from "@/internal/billing/v2/providers/stripe/fetch/fetchStripeSubscriptionForBilling";
|
||||
import { fetchStripeSubscriptionScheduleForBilling } from "@/internal/billing/v2/providers/stripe/fetch/fetchStripeSubscriptionScheduleForBilling";
|
||||
import { mapOptionsList } from "@/internal/customers/attach/attachUtils/mapOptionsList";
|
||||
import { CusService } from "../../../../customers/CusService";
|
||||
import { parseFeatureQuantitiesParams } from "../../utils/parseFeatureQuantitiesParams";
|
||||
import { fetchTargetCusProductForUpdate } from "./fetchTargetCusProductForUpdate";
|
||||
import type { UpdateSubscriptionContext } from "./updateSubscriptionContextSchema";
|
||||
|
||||
@@ -74,11 +74,11 @@ export const fetchApiSubscriptionUpdateContext = async ({
|
||||
});
|
||||
|
||||
if (params.options) {
|
||||
params.options = mapOptionsList({
|
||||
params.options = parseFeatureQuantitiesParams({
|
||||
optionsInput: params.options,
|
||||
features,
|
||||
prices: targetCustomerProduct.customer_prices.map((cp) => cp.price),
|
||||
curCusProduct: targetCustomerProduct,
|
||||
currentCustomerProduct: targetCustomerProduct,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
type AttachBodyV1,
|
||||
BillingPeriodSchema,
|
||||
type FeatureOptions,
|
||||
type FreeTrial,
|
||||
type FullCusProduct,
|
||||
@@ -96,8 +97,7 @@ export const QuantityUpdateDetailsSchema = z.object({
|
||||
billingUnitsPerQuantity: z.number(),
|
||||
|
||||
calculatedProrationAmountDollars: z.number().optional(),
|
||||
subscriptionPeriodStartEpochMs: z.number(),
|
||||
subscriptionPeriodEndEpochMs: z.number(),
|
||||
billingPeriod: BillingPeriodSchema,
|
||||
|
||||
stripeInvoiceItemDescription: z.string(),
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
import {
|
||||
ErrCode,
|
||||
type Feature,
|
||||
type FeatureOptions,
|
||||
type FullCusProduct,
|
||||
nullish,
|
||||
type Price,
|
||||
type UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { findPrepaidPrice } from "@/internal/products/prices/priceUtils/findPriceUtils.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
|
||||
/**
|
||||
* Parses and normalizes feature quantity options for billing.
|
||||
*
|
||||
* Converts raw quantity input to billing units and merges with existing options
|
||||
* from the current customer product (for recurring products only).
|
||||
*
|
||||
* @param optionsInput - Raw feature options with quantities
|
||||
* @param features - Available features to validate against
|
||||
* @param prices - Product prices to find prepaid price config
|
||||
* @param currentCustomerProduct - Existing customer product for merging options
|
||||
* @returns Normalized feature options with internal_feature_id and adjusted quantities
|
||||
*/
|
||||
export const parseFeatureQuantitiesParams = ({
|
||||
optionsInput,
|
||||
features,
|
||||
prices,
|
||||
currentCustomerProduct,
|
||||
}: {
|
||||
optionsInput?: FeatureOptions[];
|
||||
features: Feature[];
|
||||
prices: Price[];
|
||||
currentCustomerProduct?: FullCusProduct;
|
||||
}): FeatureOptions[] => {
|
||||
const parsedOptions = parseAndNormalizeOptions({
|
||||
optionsInput,
|
||||
features,
|
||||
prices,
|
||||
});
|
||||
|
||||
if (isOneOff(prices) || isFreeProduct(prices)) {
|
||||
return parsedOptions;
|
||||
}
|
||||
|
||||
return mergeWithExistingOptions({
|
||||
parsedOptions,
|
||||
currentCustomerProduct,
|
||||
prices,
|
||||
});
|
||||
};
|
||||
|
||||
const parseAndNormalizeOptions = ({
|
||||
optionsInput,
|
||||
features,
|
||||
prices,
|
||||
}: {
|
||||
optionsInput?: FeatureOptions[];
|
||||
features: Feature[];
|
||||
prices: Price[];
|
||||
}): FeatureOptions[] => {
|
||||
const result: FeatureOptions[] = [];
|
||||
|
||||
for (const options of optionsInput || []) {
|
||||
const feature = features.find(
|
||||
(feature) => feature.id === options.feature_id,
|
||||
);
|
||||
|
||||
if (!feature) {
|
||||
throw new RecaseError({
|
||||
message: `Feature ${options.feature_id} passed into options but not found`,
|
||||
code: ErrCode.FeatureNotFound,
|
||||
});
|
||||
}
|
||||
|
||||
const prepaidPrice = findPrepaidPrice({
|
||||
prices,
|
||||
internalFeatureId: feature.internal_id,
|
||||
});
|
||||
|
||||
if (!prepaidPrice) {
|
||||
throw new RecaseError({
|
||||
message: `No prepaid price found for feature ${feature.id}`,
|
||||
code: ErrCode.PriceNotFound,
|
||||
});
|
||||
}
|
||||
|
||||
const config = prepaidPrice.config as UsagePriceConfig;
|
||||
const billingUnits = config.billing_units || 1;
|
||||
|
||||
if (nullish(options.quantity)) {
|
||||
throw new RecaseError({
|
||||
message: `Quantity is required for feature ${feature.id}`,
|
||||
code: ErrCode.InvalidOptions,
|
||||
});
|
||||
}
|
||||
|
||||
const normalizedQuantity = new Decimal(options.quantity)
|
||||
.div(billingUnits)
|
||||
.ceil()
|
||||
.toNumber();
|
||||
|
||||
result.push({
|
||||
...options,
|
||||
internal_feature_id: feature.internal_id,
|
||||
quantity: normalizedQuantity,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const mergeWithExistingOptions = ({
|
||||
parsedOptions,
|
||||
currentCustomerProduct,
|
||||
prices,
|
||||
}: {
|
||||
parsedOptions: FeatureOptions[];
|
||||
currentCustomerProduct?: FullCusProduct;
|
||||
prices: Price[];
|
||||
}): FeatureOptions[] => {
|
||||
const existingOptions = currentCustomerProduct?.options || [];
|
||||
const mergedOptions = [...parsedOptions];
|
||||
|
||||
for (const existingOption of existingOptions) {
|
||||
const alreadyIncluded = parsedOptions.some(
|
||||
(option) => option.feature_id === existingOption.feature_id,
|
||||
);
|
||||
|
||||
if (alreadyIncluded) continue;
|
||||
|
||||
const hasPrepaidPrice = findPrepaidPrice({
|
||||
prices,
|
||||
internalFeatureId: existingOption.internal_feature_id!,
|
||||
});
|
||||
|
||||
if (hasPrepaidPrice) {
|
||||
mergedOptions.push(existingOption);
|
||||
}
|
||||
}
|
||||
|
||||
return mergedOptions;
|
||||
};
|
||||
@@ -7,10 +7,14 @@ import {
|
||||
type UsagePriceConfig,
|
||||
} from "@autumn/shared";
|
||||
import { Decimal } from "decimal.js";
|
||||
import { parseFeatureQuantitiesParams } from "@/internal/billing/v2/utils/parseFeatureQuantitiesParams.js";
|
||||
import { findPrepaidPrice } from "@/internal/products/prices/priceUtils/findPriceUtils.js";
|
||||
import { isFreeProduct, isOneOff } from "@/internal/products/productUtils.js";
|
||||
import RecaseError from "@/utils/errorUtils.js";
|
||||
|
||||
/**
|
||||
* @deprecated Can now use {@link parseFeatureQuantitiesParams} instead
|
||||
*/
|
||||
export const mapOptionsList = ({
|
||||
optionsInput,
|
||||
features,
|
||||
|
||||
@@ -8,7 +8,6 @@ export * from "./intervalUtils/intervalArithmetic";
|
||||
// Invoicing utils
|
||||
export * from "./invoicingUtils/cusProductToArrearLineItems";
|
||||
export * from "./invoicingUtils/cusProductToLineItems";
|
||||
export * from "./invoicingUtils/extractBillingPeriod";
|
||||
export * from "./invoicingUtils/lineItemBuilders/fixedPriceToLineItem";
|
||||
export * from "./invoicingUtils/lineItemBuilders/usagePriceToLineItem";
|
||||
export * from "./invoicingUtils/lineItemUtils/priceToLineAmount";
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import { InternalError } from "@api/errors/base/InternalError";
|
||||
import type { BillingInterval } from "@models/productModels/intervals/billingInterval";
|
||||
import { secondsToMs } from "@utils/common/unixUtils";
|
||||
import type Stripe from "stripe";
|
||||
import { getCycleEnd } from "../cycleUtils/getCycleEnd";
|
||||
import { getCycleStart } from "../cycleUtils/getCycleStart";
|
||||
|
||||
/**
|
||||
* Calculates the current billing period for a subscription.
|
||||
*
|
||||
* Uses the subscription's billing_cycle_anchor and the price's interval configuration
|
||||
* to calculate the billing period in-house, rather than relying on Stripe subscription
|
||||
* item periods.
|
||||
*
|
||||
* @param stripeSubscription - Stripe subscription object (for billing_cycle_anchor)
|
||||
* @param interval - The billing interval from the price config
|
||||
* @param intervalCount - Number of intervals per cycle (default: 1)
|
||||
* @param currentEpochMs - Current timestamp in milliseconds
|
||||
* @returns Start and end timestamps in milliseconds
|
||||
*/
|
||||
export const extractBillingPeriod = ({
|
||||
stripeSubscription,
|
||||
interval,
|
||||
intervalCount = 1,
|
||||
currentEpochMs,
|
||||
}: {
|
||||
stripeSubscription: Stripe.Subscription;
|
||||
interval: BillingInterval;
|
||||
intervalCount?: number;
|
||||
currentEpochMs: number;
|
||||
}): {
|
||||
subscriptionPeriodStartEpochMs: number;
|
||||
subscriptionPeriodEndEpochMs: number;
|
||||
} => {
|
||||
const billingCycleAnchorMs = secondsToMs(
|
||||
stripeSubscription.billing_cycle_anchor,
|
||||
);
|
||||
|
||||
if (!billingCycleAnchorMs) {
|
||||
throw new InternalError({
|
||||
message: `[Billing] Invalid billing_cycle_anchor: ${stripeSubscription.billing_cycle_anchor}`,
|
||||
});
|
||||
}
|
||||
|
||||
const subscriptionPeriodStartEpochMs = getCycleStart({
|
||||
anchor: billingCycleAnchorMs,
|
||||
interval,
|
||||
intervalCount,
|
||||
now: currentEpochMs,
|
||||
});
|
||||
|
||||
const subscriptionPeriodEndEpochMs = getCycleEnd({
|
||||
anchor: billingCycleAnchorMs,
|
||||
interval,
|
||||
intervalCount,
|
||||
now: currentEpochMs,
|
||||
});
|
||||
|
||||
return {
|
||||
subscriptionPeriodStartEpochMs,
|
||||
subscriptionPeriodEndEpochMs,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { InternalError } from "@api/errors";
|
||||
import type { FeatureOptions } from "@models/cusProductModels/cusProductModels";
|
||||
import type { Feature } from "@models/featureModels/featureModels";
|
||||
|
||||
/**
|
||||
* Find the feature options for a feature
|
||||
* @param featureOptions - The feature options to search through
|
||||
* @param feature - The feature to find the options for
|
||||
* @returns The feature options, or undefined if not found
|
||||
*/
|
||||
export const findFeatureOptionsByFeature = ({
|
||||
featureOptions,
|
||||
feature,
|
||||
}: {
|
||||
featureOptions: FeatureOptions[];
|
||||
feature: Feature;
|
||||
}) => {
|
||||
const options = featureOptions.find(
|
||||
(oldOption) =>
|
||||
oldOption.internal_feature_id === feature.internal_id ||
|
||||
oldOption.feature_id === feature.id,
|
||||
);
|
||||
|
||||
if (!options) {
|
||||
throw new InternalError({
|
||||
message: `[Find Feature Options By Feature] Cannot find feature options for feature: ${feature.id}.`,
|
||||
});
|
||||
}
|
||||
return options;
|
||||
};
|
||||
@@ -1,21 +0,0 @@
|
||||
import { InternalError } from "@api/errors";
|
||||
import type { FeatureOptions } from "@models/cusProductModels/cusProductModels";
|
||||
|
||||
export const findFeatureOptionsByFeature = ({
|
||||
featureOptions,
|
||||
featureId,
|
||||
}: {
|
||||
featureOptions: FeatureOptions[];
|
||||
featureId: string;
|
||||
}) => {
|
||||
const previousOption = featureOptions.find(
|
||||
(oldOption) => oldOption.feature_id === featureId,
|
||||
);
|
||||
|
||||
if (!previousOption) {
|
||||
throw new InternalError({
|
||||
message: `[Find Feature Options By Feature] Cannot find feature options for feature: ${featureId}.`,
|
||||
});
|
||||
}
|
||||
return previousOption;
|
||||
};
|
||||
@@ -40,7 +40,7 @@ export * from "./cusProductUtils/cusProductConstants.js";
|
||||
export * from "./cusProductUtils/cusProductUtils.js";
|
||||
export * from "./cusProductUtils/filterCusProductUtils.js";
|
||||
export * from "./cusProductUtils/filterCusProductUtils.js";
|
||||
export * from "./cusProductUtils/findFeatureOptionsByFeature.js";
|
||||
export * from "./cusProductUtils/featureOptionUtils/findFeatureOptions.js";
|
||||
export * from "./cusProductUtils/getCusProductFromCustomer.js";
|
||||
export * from "./cusProductUtils/productIdToCusProduct.js";
|
||||
// Cus utils
|
||||
|
||||
Reference in New Issue
Block a user