149 lines
4.4 KiB
TypeScript
149 lines
4.4 KiB
TypeScript
import type { ApiPlanFeature } from "@api/products/planFeature/apiPlanFeature.js";
|
|
import {
|
|
type ProductItem,
|
|
type ProductItemConfig,
|
|
ProductItemSchema,
|
|
ProductItemType,
|
|
type RolloverConfig,
|
|
} from "@models/productV2Models/productItemModels/productItemModels.js";
|
|
import { type ApiFeatureV0, FeatureNotFoundError } from "../../api/models.js";
|
|
import type { UpdatePlanFeatureParams } from "../../api/products/planFeature/planFeatureOpModels.js";
|
|
import { ApiVersion } from "../../api/versionUtils/ApiVersion.js";
|
|
import { ApiVersionClass } from "../../api/versionUtils/ApiVersionClass.js";
|
|
import type { Feature } from "../../models/featureModels/featureModels.js";
|
|
import type { ProrationConfig } from "../../models/productModels/priceModels/priceModels.js";
|
|
import { Infinite } from "../../models/productModels/productEnums.js";
|
|
import {
|
|
OnDecrease,
|
|
OnIncrease,
|
|
} from "../../models/productV2Models/productItemModels/productItemEnums.js";
|
|
import { dbToApiFeatureV1 } from "../featureUtils/apiFeatureToDbFeature.js";
|
|
import { featureToItemFeatureType } from "../featureUtils/convertFeatureUtils.js";
|
|
import { billingToItemInterval } from "../productV2Utils/productItemUtils/itemIntervalUtils.js";
|
|
import { hasPrice, hasResetInterval } from "./classifyPlanFeature.js";
|
|
import { resetIntvToItemIntv } from "./planFeatureIntervals.js";
|
|
|
|
const planFeatureToItemInterval = ({
|
|
planFeature,
|
|
}: {
|
|
planFeature: ApiPlanFeature | UpdatePlanFeatureParams;
|
|
}) => {
|
|
// 1. If feature has reset interval, use it
|
|
if (hasResetInterval(planFeature)) {
|
|
return resetIntvToItemIntv(planFeature.reset.interval);
|
|
}
|
|
|
|
// 2. If feature has price interval, use it
|
|
if (hasPrice(planFeature)) {
|
|
return billingToItemInterval({
|
|
billingInterval: planFeature.price.interval,
|
|
});
|
|
}
|
|
|
|
return null;
|
|
};
|
|
|
|
const planFeatureToItemConfig = ({
|
|
planFeature,
|
|
}: {
|
|
planFeature: ApiPlanFeature | UpdatePlanFeatureParams;
|
|
}) => {
|
|
const toItemRollover = () => {
|
|
if (planFeature.rollover) {
|
|
return {
|
|
max: planFeature.rollover.max,
|
|
duration: planFeature.rollover.expiry_duration_type,
|
|
length: planFeature.rollover.expiry_duration_length ?? 1,
|
|
} satisfies RolloverConfig;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const toItemProration = () => {
|
|
if (planFeature.proration) {
|
|
return {
|
|
on_increase:
|
|
planFeature.proration.on_increase ?? OnIncrease.ProrateImmediately,
|
|
on_decrease: planFeature.proration.on_decrease ?? OnDecrease.Prorate,
|
|
} satisfies ProrationConfig;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
const rollover = toItemRollover();
|
|
const proration = toItemProration();
|
|
|
|
if (rollover || proration) {
|
|
return {
|
|
rollover,
|
|
on_increase: proration?.on_increase,
|
|
on_decrease: proration?.on_decrease,
|
|
} satisfies ProductItemConfig;
|
|
}
|
|
return undefined;
|
|
};
|
|
|
|
export const planFeaturesToItems = ({
|
|
planFeatures,
|
|
features,
|
|
}: {
|
|
planFeatures: (ApiPlanFeature | UpdatePlanFeatureParams)[];
|
|
features: Feature[];
|
|
}): ProductItem[] => {
|
|
if (!planFeatures) return [];
|
|
|
|
return planFeatures.map((planFeature) => {
|
|
const feature = features.find((f) => f.id === planFeature.feature_id);
|
|
if (!feature) {
|
|
throw new FeatureNotFoundError({ featureId: planFeature.feature_id });
|
|
}
|
|
|
|
// Get interval
|
|
const interval = planFeatureToItemInterval({ planFeature });
|
|
const config = planFeatureToItemConfig({ planFeature });
|
|
|
|
const type = planFeature.price
|
|
? ProductItemType.FeaturePrice
|
|
: ProductItemType.Feature;
|
|
|
|
return ProductItemSchema.parse({
|
|
type,
|
|
|
|
feature_id: planFeature.feature_id,
|
|
feature_type: featureToItemFeatureType({ feature }),
|
|
feature: dbToApiFeatureV1({
|
|
dbFeature: feature,
|
|
targetVersion: new ApiVersionClass(ApiVersion.V1_2),
|
|
}) as unknown as ApiFeatureV0,
|
|
|
|
included_usage: planFeature.unlimited
|
|
? Infinite
|
|
: planFeature.granted_balance,
|
|
|
|
interval,
|
|
interval_count: planFeature.reset?.interval_count,
|
|
|
|
entity_feature_id: null,
|
|
|
|
price: planFeature.price?.amount,
|
|
|
|
tiers: planFeature.price?.tiers?.map((tier) => ({
|
|
amount: tier.amount,
|
|
to: tier.to,
|
|
})),
|
|
|
|
usage_model: planFeature.price?.usage_model,
|
|
billing_units: planFeature.price?.billing_units,
|
|
usage_limit: planFeature.price?.max_purchase
|
|
? planFeature.price.max_purchase + (planFeature.granted_balance ?? 0)
|
|
: undefined,
|
|
|
|
reset_usage_when_enabled: planFeature.reset?.reset_when_enabled,
|
|
|
|
config,
|
|
|
|
display: "display" in planFeature ? planFeature.display : undefined,
|
|
} satisfies ProductItem);
|
|
});
|
|
};
|