Files
cfw-autumn/shared/utils/usageWindowUtils/convertUsageWindow/usageLimitToUsageWindowLimit.ts
2026-06-11 15:28:59 +01:00

95 lines
3.3 KiB
TypeScript

import type { DbUsageLimit } from "../../../models/cusModels/billingControls/usageLimit.js";
import type { FullSubject } from "../../../models/cusModels/fullSubject/fullSubjectModel.js";
import type { UsageWindowLimit } from "../../../models/cusProductModels/cusEntModels/usageWindowModels.js";
import type { CusProductStatus } from "../../../models/cusProductModels/cusProductEnums.js";
import type { Feature } from "../../../models/featureModels/featureModels.js";
import { resetIntvToEntIntv } from "../../productV2Utils/productItemUtils/convertProductItem/planItemIntervals.js";
import { buildUsageWindowKey } from "../buildUsageWindowKey.js";
import { findUsageWindowAnchor } from "../findUsageWindowAnchor/findUsageWindowAnchor.js";
import { getUsageWindowAnchorTimestamp } from "../getUsageWindowAnchorTimestamp.js";
import { getUsageWindowBounds } from "../getUsageWindowBounds.js";
import { getUsageWindowDimension } from "./getUsageWindowDimension.js";
/** Non-null = the cap is the entity's own: its counter is entity-scoped. */
export type UsageWindowEntityScope = {
entityId: string | null;
internalEntityId: string;
};
/**
* Resolves one stored usage-limit entry into the enforceable UsageWindowLimit
* handed to the deduction script. The entry's ResetInterval converts to the
* internal EntInterval here -- the single edge between the API/storage
* vocabulary and the window internals. Window bounds align to the customer's
* billing cycle via the anchor entitlement when one exists, else UTC calendar.
*/
export const usageLimitToUsageWindowLimit = ({
fullSubject,
usageLimit,
feature,
features,
now,
inStatuses,
entityScope = null,
}: {
fullSubject: FullSubject;
usageLimit: DbUsageLimit;
feature: Feature;
features: Feature[];
now: number;
inStatuses?: CusProductStatus[];
entityScope?: UsageWindowEntityScope | null;
}): UsageWindowLimit | null => {
// No catalog internal_id => unstorable counter row (NOT NULL FK); the cap
// is unenforceable, so skip it.
if (feature.internal_id == null) return null;
const interval = resetIntvToEntIntv({ resetIntv: usageLimit.interval });
if (interval == null) return null;
const { dimensionType, dimensionFeatureId } = getUsageWindowDimension({
feature,
});
const scopeType = entityScope ? "entity" : "customer";
const { anchorCustomerEntitlementId, anchorCustomerEntitlement } =
findUsageWindowAnchor({
fullSubject,
featureId: feature.id,
features,
isCreditSystem: dimensionType === "balance",
inStatuses,
scopeType,
});
const { windowStartAt, windowEndAt } = getUsageWindowBounds({
interval,
now,
anchor: getUsageWindowAnchorTimestamp({ anchorCustomerEntitlement }),
});
return {
feature_id: feature.id,
internal_feature_id: feature.internal_id,
internal_customer_id: fullSubject.internalCustomerId,
key: buildUsageWindowKey({
scopeType,
internalEntityId: entityScope?.internalEntityId ?? null,
dimensionType,
dimensionFeatureId,
interval,
windowStartAt,
}),
dimension_type: dimensionType,
dimension_feature_id: dimensionFeatureId,
scope_type: scopeType,
entity_id: entityScope?.entityId ?? null,
internal_entity_id: entityScope?.internalEntityId ?? null,
interval,
window_start_at: windowStartAt,
window_end_at: windowEndAt,
limit: usageLimit.limit,
anchor_customer_entitlement_id: anchorCustomerEntitlementId,
};
};