78 lines
2.7 KiB
TypeScript
78 lines
2.7 KiB
TypeScript
import type { ApiCustomerV5 } from "../../api/customers/apiCustomerV5.js";
|
|
import type { ApiEntityV2 } from "../../api/entities/apiEntityV2.js";
|
|
|
|
/**
|
|
* Build a new entity apiSubject whose billing_controls inherit the customer's
|
|
* spend_limits, usage_limits and overage_allowed entries per feature_id.
|
|
* Entity's own entry always wins per feature; customer's entries fill gaps.
|
|
*
|
|
* Used at check time so `apiSubjectToSpendLimit` / `apiSubjectToUsageLimitHeadroom`
|
|
* / `apiSubjectToOverageAllowedControl` (which read from `subject.billing_controls`)
|
|
* see the inherited controls without needing to know about the customer separately.
|
|
*
|
|
* Pure — does not mutate inputs.
|
|
*/
|
|
export const mergeCustomerBillingControlsForCheck = ({
|
|
entityApiSubject,
|
|
customerApiSubject,
|
|
}: {
|
|
entityApiSubject: ApiEntityV2;
|
|
customerApiSubject: ApiCustomerV5;
|
|
}): ApiEntityV2 => {
|
|
const entitySpendLimits =
|
|
entityApiSubject.billing_controls?.spend_limits ?? [];
|
|
const entityUsageLimits =
|
|
entityApiSubject.billing_controls?.usage_limits ?? [];
|
|
const entityOverageAllowed =
|
|
entityApiSubject.billing_controls?.overage_allowed ?? [];
|
|
const customerSpendLimits =
|
|
customerApiSubject.billing_controls?.spend_limits ?? [];
|
|
const customerUsageLimits =
|
|
customerApiSubject.billing_controls?.usage_limits ?? [];
|
|
const customerOverageAllowed =
|
|
customerApiSubject.billing_controls?.overage_allowed ?? [];
|
|
|
|
const entitySpendLimitFeatureIds = new Set(
|
|
entitySpendLimits
|
|
.map((entry) => entry.feature_id)
|
|
.filter((id): id is string => !!id),
|
|
);
|
|
const entityUsageLimitFeatureIds = new Set(
|
|
entityUsageLimits.map((entry) => entry.feature_id),
|
|
);
|
|
const entityOverageAllowedFeatureIds = new Set(
|
|
entityOverageAllowed.map((entry) => entry.feature_id),
|
|
);
|
|
|
|
const inheritedSpendLimits = customerSpendLimits.filter(
|
|
(entry) =>
|
|
!!entry.feature_id && !entitySpendLimitFeatureIds.has(entry.feature_id),
|
|
);
|
|
// Inherited entries keep the CUSTOMER-window `usage`: the gap-filling cap is
|
|
// the shared aggregate window, not a per-entity copy.
|
|
const inheritedUsageLimits = customerUsageLimits.filter(
|
|
(entry) => !entityUsageLimitFeatureIds.has(entry.feature_id),
|
|
);
|
|
const inheritedOverageAllowed = customerOverageAllowed.filter(
|
|
(entry) => !entityOverageAllowedFeatureIds.has(entry.feature_id),
|
|
);
|
|
|
|
if (
|
|
inheritedSpendLimits.length === 0 &&
|
|
inheritedUsageLimits.length === 0 &&
|
|
inheritedOverageAllowed.length === 0
|
|
) {
|
|
return entityApiSubject;
|
|
}
|
|
|
|
return {
|
|
...entityApiSubject,
|
|
billing_controls: {
|
|
...entityApiSubject.billing_controls,
|
|
spend_limits: [...entitySpendLimits, ...inheritedSpendLimits],
|
|
usage_limits: [...entityUsageLimits, ...inheritedUsageLimits],
|
|
overage_allowed: [...entityOverageAllowed, ...inheritedOverageAllowed],
|
|
},
|
|
};
|
|
};
|