Files
cfw-autumn/shared/utils/productV2Utils/compareProductUtils.ts/compareItemUtils.ts
2025-09-26 16:33:19 +01:00

303 lines
6.9 KiB
TypeScript

/** biome-ignore-all lint/suspicious/noDoubleEquals: need to compare null / undefined for different fields */
import {
entIntervalsSame,
type Feature,
type FeatureItem,
type FeaturePriceItem,
FeatureUsageType,
intervalsSame,
itemToBillingInterval,
itemToEntInterval,
type PriceItem,
type ProductItem,
type UsageTier,
} from "@autumn/shared";
import { itemToFeature } from "../productItemUtils/convertItemUtils.js";
import {
isFeatureItem,
isFeaturePriceItem,
isPriceItem,
} from "../productItemUtils/getItemType.js";
export const findSimilarItem = ({
item,
items,
}: {
item: ProductItem;
items: ProductItem[];
}) => {
if (isFeatureItem(item)) {
return items.find(
(i) =>
i.feature_id === item.feature_id &&
entIntervalsSame({
intervalA: {
interval: itemToEntInterval({ item: i }),
intervalCount: i.interval_count,
},
intervalB: {
interval: itemToEntInterval({ item }),
intervalCount: item.interval_count,
},
}),
);
}
if (isFeaturePriceItem(item)) {
return items.find(
(i) =>
i.feature_id === item.feature_id &&
intervalsSame({
intervalA: {
interval: itemToBillingInterval({ item: i }),
intervalCount: i.interval_count,
},
intervalB: {
interval: itemToBillingInterval({ item }),
intervalCount: item.interval_count,
},
}) &&
item.usage_model == i.usage_model,
);
}
// 2. If price item
if (isPriceItem(item)) {
return items.find((i) => {
return (
isPriceItem(i) &&
i.price == item.price &&
i.interval == item.interval &&
(i.interval_count || 1) == (item.interval_count || 1)
);
});
}
return null;
};
const tiersAreSame = (
tiers1: UsageTier[] | null,
tiers2: UsageTier[] | null,
) => {
if (!tiers1 && !tiers2) {
return true;
}
if (!tiers1 || !tiers2) {
return false;
}
if (tiers1.length !== tiers2.length) {
return false;
}
return tiers1.every(
(tier: UsageTier, index: number) =>
tier.amount === tiers2[index].amount && tier.to === tiers2[index].to,
);
};
export const featureItemsAreSame = ({
item1,
item2,
}: {
item1: FeatureItem;
item2: FeatureItem;
}) => {
// Compare config objects (including rollover)
const configsAreSame =
JSON.stringify(item1.config) === JSON.stringify(item2.config);
const same =
item1.feature_id === item2.feature_id &&
item1.included_usage == item2.included_usage &&
item1.interval == item2.interval &&
(item1.interval_count || 1) == (item2.interval_count || 1) &&
item1.entity_feature_id == item2.entity_feature_id &&
item1.reset_usage_when_enabled == item2.reset_usage_when_enabled &&
configsAreSame;
return same;
};
export const priceItemsAreSame = ({
item1,
item2,
}: {
item1: PriceItem;
item2: PriceItem;
}) => {
const same =
item1.price === item2.price &&
item1.interval == item2.interval &&
(item1.interval_count || 1) == (item2.interval_count || 1);
if (!same) {
console.log(`Price items different: ${item1.price}`);
}
return same;
};
export const featurePriceItemsAreSame = ({
item1,
item2,
}: {
item1: FeaturePriceItem;
item2: FeaturePriceItem;
}) => {
const entsSame = {
included_usage: {
condition: item1.included_usage == item2.included_usage,
message: `Included usage different: ${item1.included_usage} != ${item2.included_usage}`,
},
usage_limit: {
condition: item1.usage_limit == item2.usage_limit,
message: `Usage limit different: ${item1.usage_limit} !== ${item2.usage_limit}`,
},
reset_usage_when_enabled: {
condition:
item1.reset_usage_when_enabled == item2.reset_usage_when_enabled,
message: `Reset usage when enabled different: ${item1.reset_usage_when_enabled} !== ${item2.reset_usage_when_enabled}`,
},
config: {
condition: JSON.stringify(item1.config) === JSON.stringify(item2.config),
message: `Config different: ${JSON.stringify(item1.config)} !== ${JSON.stringify(item2.config)}`,
},
};
const pricesSame = {
feature_id: {
condition: item1.feature_id === item2.feature_id,
message: `Feature ID different: ${item1.feature_id} != ${item2.feature_id}`,
},
interval: {
condition: item1.interval == item2.interval,
message: `Interval different: ${item1.interval} != ${item2.interval}`,
},
interval_count: {
condition: (item1.interval_count || 1) == (item2.interval_count || 1),
message: `Interval count different: ${item1.interval_count} != ${item2.interval_count}`,
},
usage_model: {
condition: item1.usage_model === item2.usage_model,
message: `Usage model different: ${item1.usage_model} != ${item2.usage_model}`,
},
price: {
condition: item1.price == item2.price,
message: `Price different: ${item1.price} != ${item2.price}`,
},
tiers: {
condition: tiersAreSame(item1.tiers || null, item2.tiers || null),
message: `Tiers different`,
},
billing_units: {
condition: item1.billing_units == item2.billing_units,
message: `Billing units different: ${item1.billing_units} !== ${item2.billing_units}`,
},
reset_usage_when_enabled: {
condition:
item1.reset_usage_when_enabled == item2.reset_usage_when_enabled,
message: `Reset usage when enabled different: ${item1.reset_usage_when_enabled} !== ${item2.reset_usage_when_enabled}`,
},
};
const same =
Object.values(pricesSame).every((d) => d.condition) &&
Object.values(entsSame).every((d) => d.condition);
const pricesChanged = Object.values(pricesSame).some((d) => !d.condition);
if (!same) {
console.log(
"Feature price items different:",
Object.values(entsSame)
.filter((d) => !d.condition)
.map((d) => d.message),
Object.values(pricesSame)
.filter((d) => !d.condition)
.map((d) => d.message),
);
}
return {
same,
pricesChanged,
};
};
export const itemsAreSame = ({
item1,
item2,
features,
}: {
item1: ProductItem;
item2: ProductItem;
features?: Feature[];
}) => {
// 1. If feature item
let same = false;
let pricesChanged = false;
if (isFeatureItem(item1)) {
if (!isFeatureItem(item2)) {
return {
same: false,
pricesChanged: true,
};
}
same = featureItemsAreSame({
item1: item1 as FeatureItem,
item2: item2 as FeatureItem,
});
pricesChanged = false;
}
if (isFeaturePriceItem(item1)) {
if (!isFeaturePriceItem(item2)) {
return {
same: false,
pricesChanged: true,
};
}
const { same: same_, pricesChanged: pricesChanged_ } =
featurePriceItemsAreSame({
item1: item1 as FeaturePriceItem,
item2: item2 as FeaturePriceItem,
});
same = same_;
const feature = itemToFeature({
item: item1,
features: features || [],
});
if (feature?.config?.usage_type === FeatureUsageType.Continuous) {
pricesChanged = true;
} else {
pricesChanged = pricesChanged_;
}
}
// 2. If price item
if (isPriceItem(item1)) {
same = priceItemsAreSame({
item1: item1 as PriceItem,
item2: item2 as PriceItem,
});
if (!same) {
pricesChanged = true;
}
}
return {
same,
pricesChanged: !same && pricesChanged,
};
};