Files
cfw-autumn/shared/utils/planFeatureUtils/planFeaturesToItems.ts
2025-11-17 16:24:07 +00:00

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);
});
};