fix: track entity product

This commit is contained in:
John Yeo
2025-11-04 23:28:42 +00:00
parent 9ba51d5055
commit d76d9d2a5e
48 changed files with 3075 additions and 283 deletions

View File

@@ -18,14 +18,12 @@ export const getSummedEntityBalances = ({
}
return {
balance: Object.values(cusEnt.entities).reduce(
(acc, curr) => acc + curr.balance,
0,
),
adjustment: Object.values(cusEnt.entities).reduce(
(acc, curr) => acc + curr.adjustment,
0,
),
balance: Object.values(cusEnt.entities)
.reduce((acc, curr) => acc.add(curr.balance), new Decimal(0))
.toNumber(),
adjustment: Object.values(cusEnt.entities)
.reduce((acc, curr) => acc.add(curr.adjustment), new Decimal(0))
.toNumber(),
unused: 0,
count: Object.values(cusEnt.entities).length,
};

View File

@@ -95,11 +95,14 @@ export const cusEntToIncludedUsage = ({
export const cusEntToUsageLimit = ({
cusEnt,
entityId,
}: {
cusEnt: FullCusEntWithFullCusProduct;
entityId?: string;
}) => {
const startingBalance = cusEntToIncludedUsage({
cusEnt,
entityId,
});
if (cusEnt.entitlement.usage_limit) return cusEnt.entitlement.usage_limit;

View File

@@ -2,11 +2,8 @@ import type {
EntityBalance,
FullCustomerEntitlement,
} from "@models/cusProductModels/cusEntModels/cusEntModels.js";
import type { Entity } from "../../models/cusModels/entityModels/entityModels.js";
import type { FullCustomer } from "../../models/cusModels/fullCusModel.js";
import type { FullCusEntWithFullCusProduct } from "../../models/cusProductModels/cusEntModels/cusEntWithProduct.js";
import type { Feature } from "../../models/featureModels/featureModels.js";
import { notNullish } from "../utils.js";
export const formatCusEnt = ({
cusEnt,
@@ -16,6 +13,17 @@ export const formatCusEnt = ({
return `${cusEnt.entitlement.feature_id} (${cusEnt.entitlement.interval}) (${cusEnt.balance})`;
};
export const isEntityCusEnt = ({
cusEnt,
}: {
cusEnt: FullCusEntWithFullCusProduct;
}): boolean => {
return !!(
cusEnt.entitlement.entity_feature_id ||
cusEnt.customer_product?.internal_entity_id
);
};
export const updateCusEntInFullCus = ({
fullCus,
cusEntId,
@@ -47,33 +55,3 @@ export const updateCusEntInFullCus = ({
}
}
};
export const cusEntMatchesEntity = ({
cusEnt,
entity,
features,
}: {
cusEnt: FullCusEntWithFullCusProduct;
entity?: Entity;
features?: Feature[];
}) => {
if (!entity) return true;
let cusProductMatch = true;
if (notNullish(cusEnt.customer_product?.internal_entity_id)) {
cusProductMatch =
cusEnt.customer_product.internal_entity_id === entity.internal_id;
}
let entityFeatureIdMatch = true;
// let feature = features?.find(
// (f) => f.id == cusEnt.entitlement.entity_feature_id,
// );
if (notNullish(cusEnt.entitlement.entity_feature_id)) {
entityFeatureIdMatch =
cusEnt.entitlement.entity_feature_id === entity.feature_id;
}
return cusProductMatch && entityFeatureIdMatch;
};

View File

@@ -0,0 +1,64 @@
import type { Entity } from "../../models/cusModels/entityModels/entityModels.js";
import type { FullCusEntWithFullCusProduct } from "../../models/cusProductModels/cusEntModels/cusEntWithProduct.js";
import type { Feature } from "../../models/featureModels/featureModels.js";
import { notNullish, nullish } from "../utils.js";
export const cusEntMatchesEntity = ({
cusEnt,
entity,
features,
}: {
cusEnt: FullCusEntWithFullCusProduct;
entity?: Entity;
features?: Feature[];
}) => {
if (!entity) return true;
let cusProductMatch = true;
if (notNullish(cusEnt.customer_product?.internal_entity_id)) {
cusProductMatch =
cusEnt.customer_product.internal_entity_id === entity.internal_id;
}
let entityFeatureIdMatch = true;
// let feature = features?.find(
// (f) => f.id == cusEnt.entitlement.entity_feature_id,
// );
if (notNullish(cusEnt.entitlement.entity_feature_id)) {
entityFeatureIdMatch =
cusEnt.entitlement.entity_feature_id === entity.feature_id;
}
return cusProductMatch && entityFeatureIdMatch;
};
export const filterOutEntityCusEnts = ({
cusEnts,
}: {
cusEnts: FullCusEntWithFullCusProduct[];
}) => {
return cusEnts.filter(
(ce) =>
nullish(ce.entitlement.entity_feature_id) &&
nullish(ce.customer_product?.internal_entity_id),
);
};
export const filterPerEntityCusEnts = ({
cusEnts,
}: {
cusEnts: FullCusEntWithFullCusProduct[];
}) => {
return cusEnts.filter((ce) => notNullish(ce.entitlement.entity_feature_id));
};
export const filterEntityProductCusEnts = ({
cusEnts,
}: {
cusEnts: FullCusEntWithFullCusProduct[];
}) => {
return cusEnts.filter((ce) =>
notNullish(ce.customer_product?.internal_entity_id),
);
};

View File

@@ -2,15 +2,33 @@ import type { FullCusEntWithFullCusProduct } from "../../models/cusProductModels
import { FeatureType } from "../../models/featureModels/featureEnums.js";
import { AllowanceType } from "../../models/productModels/entModels/entModels.js";
import { entIntervalToValue } from "../intervalUtils.js";
import { isEntityCusEnt } from "./cusEntUtils.js";
export const sortCusEntsForDeduction = (
cusEnts: FullCusEntWithFullCusProduct[],
reverseOrder: boolean = false,
entityId?: string,
) => {
cusEnts.sort((a, b) => {
const aEnt = a.entitlement;
const bEnt = b.entitlement;
// 0. Sort customer-level vs entity-level based on tracking context
// If entityId is provided: entity-level goes first (deduct entity's own resources first)
// If entityId is null: customer-level goes first (deduct customer resources first)
const aIsEntity = isEntityCusEnt({ cusEnt: a });
const bIsEntity = isEntityCusEnt({ cusEnt: b });
if (aIsEntity !== bIsEntity) {
if (entityId) {
// Entity-level tracking: entity entitlements go first
return aIsEntity ? -1 : 1;
} else {
// Customer-level tracking: customer entitlements go first
return aIsEntity ? 1 : -1;
}
}
// 1. If boolean, go first
if (aEnt.feature.type === FeatureType.Boolean) {
return -1;
@@ -85,6 +103,19 @@ export const sortCusEntsForDeduction = (
}
}
// 0a. If both are entity products (attached to entities), sort by entity_id for consistent ordering
const aIsProductEntity = !!a.customer_product?.internal_entity_id;
const bIsProductEntity = !!b.customer_product?.internal_entity_id;
if (aIsProductEntity && bIsProductEntity) {
const aEntityId = a.customer_product?.entity_id;
const bEntityId = b.customer_product?.entity_id;
if (aEntityId && bEntityId && aEntityId !== bEntityId) {
return aEntityId.localeCompare(bEntityId);
}
}
// Check if a is main product
const aIsAddOn = a.customer_product?.product?.is_add_on;
const bIsAddOn = b.customer_product?.product?.is_add_on;