Files
cfw-autumn/server/src/internal/api/entities/handlers/handleDeleteEntity.ts
2025-10-17 13:38:35 +01:00

179 lines
4.5 KiB
TypeScript

import { CusProductStatus, ErrCode } from "@autumn/shared";
import { StatusCodes } from "http-status-codes";
import { handleCustomerRaceCondition } from "@/external/redis/redisUtils.js";
import { CusService } from "@/internal/customers/CusService.js";
import { CusEntService } from "@/internal/customers/cusProducts/cusEnts/CusEntitlementService.js";
import {
findLinkedCusEnts,
findMainCusEntForFeature,
} from "@/internal/customers/cusProducts/cusEnts/cusEntUtils/findCusEntUtils.js";
import {
deleteEntityFromCusEnt,
replaceEntityInCusEnt,
} from "@/internal/customers/cusProducts/cusEnts/cusEntUtils/linkedCusEntUtils.js";
import { RepService } from "@/internal/customers/cusProducts/cusEnts/RepService.js";
import { cancelSubsForEntity } from "@/internal/entities/handlers/handleDeleteEntity/cancelSubsForEntity.js";
import { adjustAllowance } from "@/trigger/adjustAllowance.js";
import RecaseError, { handleRequestError } from "@/utils/errorUtils.js";
import { EntityService } from "../EntityService.js";
export const handleDeleteEntity = async (req: any, res: any) => {
try {
const { org, env, db, logger, features } = req;
const { customer_id, entity_id } = req.params;
await handleCustomerRaceCondition({
action: "entity",
customerId: customer_id,
orgId: org.id,
env,
res,
logger,
});
const customer = await CusService.getFull({
db,
idOrInternalId: customer_id,
orgId: req.orgId,
env: req.env,
withEntities: true,
inStatuses: [
CusProductStatus.Active,
CusProductStatus.PastDue,
CusProductStatus.Scheduled,
],
});
if (!customer) {
throw new RecaseError({
message: `Customer ${customer_id} not found`,
code: ErrCode.CustomerNotFound,
statusCode: StatusCodes.NOT_FOUND,
});
}
const existingEntities = customer.entities;
const cusProducts = customer.customer_products;
const entity = existingEntities.find((e: any) => e.id === entity_id);
if (!entity) {
throw new RecaseError({
message: `Entity ${entity_id} not found`,
code: ErrCode.EntityNotFound,
statusCode: StatusCodes.NOT_FOUND,
});
} else if (entity.deleted) {
throw new RecaseError({
message: `Entity ${entity_id} already deleted`,
code: ErrCode.EntityAlreadyDeleted,
statusCode: StatusCodes.BAD_REQUEST,
});
}
const feature = features.find((f: any) => f.id === entity?.feature_id);
for (const cusProduct of cusProducts) {
const cusEnts = cusProduct.customer_entitlements;
const mainCusEnt = findMainCusEntForFeature({
cusEnts,
feature,
});
if (!mainCusEnt) {
continue;
}
const { newReplaceables } = await adjustAllowance({
db,
env,
org,
cusPrices: cusProducts.flatMap((p: any) => p.customer_prices),
customer,
affectedFeature: mainCusEnt.entitlement.feature,
cusEnt: { ...mainCusEnt, customer_product: cusProduct },
originalBalance: mainCusEnt.balance!,
newBalance: mainCusEnt.balance! + 1,
logger,
});
const linkedCusEnts = findLinkedCusEnts({
cusEnts: cusProduct.customer_entitlements,
feature: mainCusEnt.entitlement.feature,
});
const replaceable =
newReplaceables && newReplaceables.length > 0
? newReplaceables[0]
: null;
if (replaceable) {
await RepService.update({
db,
id: replaceable.id,
data: {
from_entity_id: entity.id,
},
});
}
// Update linked cus ents with replaceables...
for (const linkedCusEnt of linkedCusEnts) {
let newEntities;
if (replaceable) {
const { newEntities: newEntities_ } = replaceEntityInCusEnt({
cusEnt: linkedCusEnt,
entityId: entity.id,
replaceable,
});
newEntities = newEntities_;
} else {
const { newEntities: newEntities_ } = deleteEntityFromCusEnt({
cusEnt: linkedCusEnt,
entityId: entity.id,
});
newEntities = newEntities_;
}
await CusEntService.update({
db,
id: linkedCusEnt.id,
updates: {
entities: newEntities,
},
});
}
if (!replaceable) {
await CusEntService.increment({
db,
id: mainCusEnt.id,
amount: 1,
});
}
}
// Cancel any subs
await cancelSubsForEntity({
req,
cusProducts,
entity,
});
await EntityService.deleteInInternalIds({
db,
internalIds: [entity.internal_id],
orgId: req.orgId,
env: req.env,
});
logger.info(` ✅ Finished deleting entity ${entity_id}`);
return res.status(200).json({
success: true,
});
} catch (error) {
handleRequestError({ error, req, res, action: "delete entity" });
}
};