apps/recallassess/recallassess-api/src/api/admin/company/services/company.service.ts
BNestBaseModuleService
Methods |
|
constructor(prisma: BNestPrismaService, systemLogService: SystemLogService, companyTimezoneService: CompanyTimezoneService, clSubscriptionService: CLSubscriptionService, stripeService: StripeService, stripePaymentService: StripePaymentService)
|
|||||||||||||||||||||
|
Parameters :
|
| Async add | ||||||
add(data: any)
|
||||||
|
Override add method to log creation
Parameters :
Returns :
Promise<any>
|
| Async delete | ||||||
delete(id: number)
|
||||||
|
Override delete method to log deletion
Parameters :
Returns :
Promise<void>
|
| Async getDetail | ||||||
getDetail(id: number)
|
||||||
|
Parameters :
Returns :
Promise<DetailResponseDataInterface<any>>
|
| Async getList | ||||||
getList(paginationOptions: any)
|
||||||
|
Parameters :
Returns :
Promise<ListResponseDataInterface<any>>
|
| getStripeConnectionDiagnostics |
getStripeConnectionDiagnostics()
|
|
Whether the API process has Stripe env vars (UAT ECS / local docker). Does not call Stripe.
Returns :
CompanyStripeConnectionDto
|
| Async getStripeSubscriptionSnapshot | ||||||
getStripeSubscriptionSnapshot(companyId: number)
|
||||||
|
Live Stripe subscription snapshot for admin UI (not from local
Parameters :
Returns :
Promise<CompanyStripeSubscriptionDto>
|
| Private Async hydrateCurrentSubscriptionFieldsForListRows | ||||||
hydrateCurrentSubscriptionFieldsForListRows(rows: any[])
|
||||||
|
Parameters :
Returns :
Promise<any[]>
|
| Async previewAdminReactivateStripeSubscription |
previewAdminReactivateStripeSubscription(companyId: number, fromDate: string)
|
|
Returns :
Promise<ImmediateRecoveryQuote>
|
| Async runAdminCreateStripeSubscription | ||||||
runAdminCreateStripeSubscription(companyId: number)
|
||||||
|
Admin: one-shot create/recreate Stripe subscription for this company. This cancels/overwrites existing billable subscriptions in Stripe (as required by ops).
Parameters :
Returns :
Promise<literal type>
|
| Async runAdminReactivateStripeSubscription |
runAdminReactivateStripeSubscription(companyId: number, fromDate: string)
|
|
Returns :
Promise<literal type>
|
| Async save |
save(id: number, data: any)
|
|
Override save method to prevent updating email, plan, and name fields
Returns :
Promise<any>
|
| Async syncStripeInvoicesAndRefunds | ||||||
syncStripeInvoicesAndRefunds(companyId: number)
|
||||||
|
Parameters :
Returns :
Promise<literal type>
|
| Async updateAdminStripeNextBillingDate |
updateAdminStripeNextBillingDate(companyId: number, nextBillingDate: string)
|
|
Returns :
Promise<literal type>
|
import { CompanyStripeConnectionDto } from "@api/admin/company/dto/company-stripe-connection.dto";
import { CompanyStripeSubscriptionDto } from "@api/admin/company/dto/company-stripe-subscription.dto";
import { CLSubscriptionService } from "@api/client/subscription/subscription.service";
import { SystemLogService } from "@api/shared/services";
import { StripeService } from "@api/shared/stripe/services/stripe.service";
import {
ImmediateRecoveryQuote,
StripePaymentService,
} from "@api/shared/stripe/services/stripe-payment.service";
import { CompanyTimezoneService } from "@api/shared/timezone/company-timezone.service";
import { buildListResponseData } from "@bish-nest/core";
import { BNestBaseModuleService } from "@bish-nest/core/data/module-service/base-module.service";
import { DetailResponseDataInterface } from "@bish-nest/core/interfaces/detail-response-data.interface";
import { ListResponseDataInterface } from "@bish-nest/core/interfaces/list-response-data.interface";
import { BNestPrismaService } from "@bish-nest/core/services";
import { Injectable, NotFoundException, UnprocessableEntityException } from "@nestjs/common";
import { SystemLogEntityType } from "@prisma/client";
import { plainToInstance } from "class-transformer";
import Stripe from "stripe";
/** End of current billing period (Stripe API 2025+ exposes this on subscription items, not the parent). */
function stripeSubscriptionPeriodEndUnix(sub: Stripe.Subscription): number {
const ends = sub.items?.data?.map((item) => item.current_period_end) ?? [];
return ends.length > 0 ? Math.max(...ends) : 0;
}
/** Never leak secrets (Stripe keys, webhook secrets) in API responses. */
function sanitizeExternalErrorMessage(input: unknown): string {
const raw = input instanceof Error ? input.message : typeof input === "string" ? input : String(input);
// Stripe sometimes echoes the provided key (e.g. "Expired API Key provided: sk_test_...").
// Mask any recognizable secret-like tokens.
return raw
.replace(/\bsk_(test|live)_[A-Za-z0-9]+\b/g, "sk_$1_[REDACTED]")
.replace(/\brk_(test|live)_[A-Za-z0-9]+\b/g, "rk_$1_[REDACTED]")
.replace(/\bwhsec_[A-Za-z0-9]+\b/g, "whsec_[REDACTED]");
}
@Injectable()
export class CompanyService extends BNestBaseModuleService {
constructor(
protected prisma: BNestPrismaService,
private readonly systemLogService: SystemLogService,
private readonly companyTimezoneService: CompanyTimezoneService,
private readonly clSubscriptionService: CLSubscriptionService,
private readonly stripeService: StripeService,
private readonly stripePaymentService: StripePaymentService,
) {
super();
}
override async getList(paginationOptions: any): Promise<ListResponseDataInterface<any>> {
const { orderBy, sortOrder } = paginationOptions ?? {};
let normalizedOrderBy = typeof orderBy === "string" ? orderBy.trim() : "";
let normalizedSortOrder = typeof sortOrder === "string" ? sortOrder.trim().toLowerCase() : "";
// Defensive normalization for mixed query formats.
// Accept:
// - orderBy=expired_date&sortOrder=asc
// - sortOrder=expired_date:asc
// - orderBy=expired_date:asc&sortOrder=asc
if (normalizedOrderBy.includes(":")) {
const [field, direction] = normalizedOrderBy.split(":");
normalizedOrderBy = field?.trim() || normalizedOrderBy;
if (direction?.trim()) {
normalizedSortOrder = direction.trim().toLowerCase();
}
}
if (normalizedSortOrder.includes(":")) {
const [field, direction] = normalizedSortOrder.split(":");
if (field?.trim()) {
normalizedOrderBy = field.trim();
}
normalizedSortOrder = direction?.trim().toLowerCase() || normalizedSortOrder;
}
// Some list columns are derived from latest subscription, so Prisma cannot orderBy them directly.
const needsDerivedSort =
normalizedOrderBy === "next_billing_date" ||
normalizedOrderBy === "expired_date" ||
normalizedOrderBy === "stripe_cancel_at_period_end" ||
normalizedOrderBy === "current_plan";
if (!needsDerivedSort) {
const baseList = await super.getList(paginationOptions);
baseList.data = await this.hydrateCurrentSubscriptionFieldsForListRows(baseList.data ?? []);
return baseList;
}
const page = Number(paginationOptions?.page) || 1;
const limit = Number(paginationOptions?.limit) || 10;
const direction = normalizedSortOrder === "asc" ? "asc" : "desc";
const moduleCurrentCfg = this.gVars.moduleCurrentCfg;
const whereCondition = this.moduleMethods.getWhereCondition(paginationOptions);
const rawRows = await this.prisma.client.company.findMany({
where: whereCondition,
include: moduleCurrentCfg.relationObjToIncludeForList,
});
const hydratedRows = await this.hydrateCurrentSubscriptionFieldsForListRows(rawRows as any[]);
const rowsForSort =
normalizedOrderBy === "expired_date"
? hydratedRows.filter((row: any) => row.is_subscription_expiry === true && !!row.next_billing_date)
: hydratedRows;
const sortedRows = [...rowsForSort].sort((a: any, b: any) => {
if (normalizedOrderBy === "next_billing_date" || normalizedOrderBy === "expired_date") {
const isExpiredSort = normalizedOrderBy === "expired_date";
const aDate =
isExpiredSort && a.is_subscription_expiry !== true
? null
: a.next_billing_date
? new Date(a.next_billing_date).getTime()
: null;
const bDate =
isExpiredSort && b.is_subscription_expiry !== true
? null
: b.next_billing_date
? new Date(b.next_billing_date).getTime()
: null;
// Keep empty dates at the end for both directions.
if (aDate === null && bDate === null) return 0;
if (aDate === null) return 1;
if (bDate === null) return -1;
return direction === "asc" ? aDate - bDate : bDate - aDate;
}
if (normalizedOrderBy === "current_plan") {
const aPlan = (a.current_plan ?? null) as string | null;
const bPlan = (b.current_plan ?? null) as string | null;
// Keep empty values at the end for both directions.
if (aPlan === null && bPlan === null) return 0;
if (aPlan === null) return 1;
if (bPlan === null) return -1;
const cmp = aPlan.localeCompare(bPlan, undefined, { sensitivity: "base" });
if (cmp !== 0) return direction === "asc" ? cmp : -cmp;
// Stable-ish tie-breaker.
return direction === "asc" ? (a.id ?? 0) - (b.id ?? 0) : (b.id ?? 0) - (a.id ?? 0);
}
// normalizedOrderBy === "stripe_cancel_at_period_end"
// Auto-renew (display): Yes when cancel_at_period_end=false, No otherwise.
// This intentionally does not depend on company.stripe_subscription_id.
const aCancel = a.stripe_cancel_at_period_end ?? null;
const bCancel = b.stripe_cancel_at_period_end ?? null;
const aAutoRenew = aCancel === false ? 1 : 0; // 1=Yes, 0=No
const bAutoRenew = bCancel === false ? 1 : 0;
if (aAutoRenew !== bAutoRenew) {
return direction === "asc" ? aAutoRenew - bAutoRenew : bAutoRenew - aAutoRenew;
}
// Stable-ish tie-breaker.
return direction === "asc" ? (a.id ?? 0) - (b.id ?? 0) : (b.id ?? 0) - (a.id ?? 0);
});
const start = (page - 1) * limit;
const pagedRows = sortedRows.slice(start, start + limit);
let data: any[] = pagedRows;
if (paginationOptions?.vl) {
if (moduleCurrentCfg.simpleDto) {
data = pagedRows.map((row: any) =>
plainToInstance(moduleCurrentCfg.simpleDto, row, { excludeExtraneousValues: true }),
);
} else if (moduleCurrentCfg.listDto) {
// Company module has no simpleDto; keep transformed list fields (current_plan, auto-renew flag) for vl requests.
data = pagedRows.map((row: any) =>
plainToInstance(moduleCurrentCfg.listDto, row, { excludeExtraneousValues: true }),
);
}
} else if (moduleCurrentCfg.listDto) {
data = pagedRows.map((row: any) =>
plainToInstance(moduleCurrentCfg.listDto, row, { excludeExtraneousValues: true }),
);
}
return buildListResponseData(data, page, limit, sortedRows.length);
}
private async hydrateCurrentSubscriptionFieldsForListRows(rows: any[]): Promise<any[]> {
if (!Array.isArray(rows) || rows.length === 0) {
return rows;
}
const companyIds = rows
.map((row) => Number(row?.id))
.filter((id) => Number.isInteger(id) && id > 0);
if (companyIds.length === 0) {
return rows;
}
const currentSubs = await this.prisma.client.subscription.findMany({
where: {
company_id: { in: companyIds },
is_current: true,
},
orderBy: { id: "desc" },
select: {
company_id: true,
next_billing_date: true,
end_date: true,
stripe_cancel_at_period_end: true,
package: { select: { name: true } },
},
});
const byCompanyId = new Map<
number,
{
current_plan: string | null;
next_billing_date: Date | null;
stripe_cancel_at_period_end: boolean | null;
}
>();
for (const sub of currentSubs) {
if (!byCompanyId.has(sub.company_id)) {
byCompanyId.set(sub.company_id, {
current_plan: sub.package?.name ?? null,
next_billing_date: sub.next_billing_date ?? sub.end_date ?? null,
stripe_cancel_at_period_end: sub.stripe_cancel_at_period_end ?? null,
});
}
}
// After expiry jobs, a company may have no `is_current=true` row.
// Fallback to the latest subscription row so list/detail still show plan + renewal context.
const missingCompanyIds = companyIds.filter((id) => !byCompanyId.has(id));
if (missingCompanyIds.length > 0) {
const fallbackSubs = await this.prisma.client.subscription.findMany({
where: {
company_id: { in: missingCompanyIds },
},
orderBy: [{ company_id: "asc" }, { id: "desc" }],
select: {
company_id: true,
next_billing_date: true,
end_date: true,
stripe_cancel_at_period_end: true,
package: { select: { name: true } },
},
});
for (const sub of fallbackSubs) {
if (!byCompanyId.has(sub.company_id)) {
byCompanyId.set(sub.company_id, {
current_plan: sub.package?.name ?? null,
next_billing_date: sub.next_billing_date ?? sub.end_date ?? null,
stripe_cancel_at_period_end: sub.stripe_cancel_at_period_end ?? null,
});
}
}
}
return rows.map((row) => {
const id = Number(row?.id);
const sub = byCompanyId.get(id);
if (!sub) {
return row;
}
const renewalOrExpiryDate = sub.next_billing_date;
const isExpired = row["is_subscription_expiry"] === true;
return {
...row,
current_plan: sub.current_plan,
next_billing_date: renewalOrExpiryDate,
expired_date: isExpired && renewalOrExpiryDate ? renewalOrExpiryDate : null,
stripe_cancel_at_period_end: sub.stripe_cancel_at_period_end,
};
});
}
override async getDetail(id: number): Promise<DetailResponseDataInterface<any>> {
const result = await super.getDetail(id);
const company = result.data[0] as Record<string, any>;
company["timezone"] = this.companyTimezoneService.resolve({
country: company["country"],
preferred_timezone: company["preferred_timezone"],
});
const currentSub = await this.prisma.client.subscription.findFirst({
where: { company_id: id, is_current: true },
orderBy: { id: "desc" },
select: {
next_billing_date: true,
end_date: true,
stripe_cancel_at_period_end: true,
package: { select: { name: true } },
},
});
const subForDisplay = currentSub
?? (await this.prisma.client.subscription.findFirst({
where: { company_id: id },
orderBy: { id: "desc" },
select: {
next_billing_date: true,
end_date: true,
stripe_cancel_at_period_end: true,
package: { select: { name: true } },
},
}));
company["current_plan"] = subForDisplay?.package?.name ?? null;
const renewalOrExpiryDate = subForDisplay?.next_billing_date ?? subForDisplay?.end_date ?? null;
company["next_billing_date"] = renewalOrExpiryDate;
company["stripe_cancel_at_period_end"] = subForDisplay?.stripe_cancel_at_period_end ?? null;
// Virtual list/detail field: mirrors next_billing_date when subscription is expired.
company["expired_date"] =
company["is_subscription_expiry"] === true && renewalOrExpiryDate ? renewalOrExpiryDate : null;
return result;
}
/**
* Override add method to log creation
*/
async add(data: any): Promise<any> {
// Set only by subscription expiry job / system — not editable via admin API
const { is_subscription_expiry: _ignoredSubscriptionExpiry, ...addData } = data;
const addResponse = await super.add(addData);
const company = addResponse.data;
// Log the creation
await this.systemLogService.logInsert(
SystemLogEntityType.COMPANY,
company.id,
company as Record<string, unknown>,
);
return addResponse;
}
/**
* Override save method to prevent updating email, plan, and name fields
*/
async save(id: number, data: any): Promise<any> {
// Get the existing company record (full record for logging)
const oldCompany = await this.prisma.client.company.findUnique({
where: { id },
});
if (!oldCompany) {
throw new NotFoundException(`Company with ID ${id} not found`);
}
// Get specific fields for validation
const existingCompany = {
name: oldCompany.name,
email: oldCompany.email,
plan: oldCompany.plan,
};
// Check if name is being changed
if (data.name !== undefined && data.name !== existingCompany.name) {
throw new UnprocessableEntityException({
message: [
{
colName: "name",
errorMessage: "Company name cannot be changed after creation.",
},
],
code: "FORM_VALIDATION_ERROR",
});
}
// Check if email is being changed
if (data.email !== undefined && data.email !== existingCompany.email) {
throw new UnprocessableEntityException({
message: [
{
colName: "email",
errorMessage: "Company email cannot be changed after creation.",
},
],
code: "FORM_VALIDATION_ERROR",
});
}
// Check if plan is being changed
if (data.plan !== undefined && data.plan !== existingCompany.plan) {
throw new UnprocessableEntityException({
message: [
{
colName: "plan",
errorMessage: "Company plan cannot be changed after creation.",
},
],
code: "FORM_VALIDATION_ERROR",
});
}
const {
name,
email,
plan,
timezone,
is_subscription_expiry: _ignoredSubscriptionExpiry,
...updateData
} = data;
// Call parent save method with cleaned data (timezone is derived in getDetail, not a company column;
// is_subscription_expiry is driven by the subscription expiry job and cleared when upgrade/checkout/auto-payment leaves a current ACTIVE sub with renewal ahead.
const saveResponse = await super.save(id, updateData);
const returnPayload = saveResponse;
const updatedCompany = returnPayload.data[0] as Record<string, unknown>;
// Calculate changed fields and log the update (use latest company row for logging)
const changedFields = SystemLogService.calculateChangedFields(
oldCompany as Record<string, unknown>,
updatedCompany,
);
await this.systemLogService.logUpdate(
SystemLogEntityType.COMPANY,
id,
oldCompany as Record<string, unknown>,
updatedCompany,
changedFields,
);
return returnPayload;
}
/**
* Whether the API process has Stripe env vars (UAT ECS / local docker). Does not call Stripe.
*/
getStripeConnectionDiagnostics(): CompanyStripeConnectionDto {
const secretConfigured = this.stripeService.isConfigured();
return plainToInstance(CompanyStripeConnectionDto, {
secret_key_configured: secretConfigured,
key_mode: secretConfigured ? this.stripeService.getDashboardAccountMode() : null,
publishable_key_configured: !!this.stripeService.getPublishableKey(),
});
}
/**
* Live Stripe subscription snapshot for admin UI (not from local `subscription` table).
*/
async getStripeSubscriptionSnapshot(companyId: number): Promise<CompanyStripeSubscriptionDto> {
const company = await this.prisma.client.company.findUnique({
where: { id: companyId },
select: {
stripe_customer_id: true,
stripe_subscription_id: true,
},
});
if (!company) {
throw new NotFoundException(`Company with ID ${companyId} not found`);
}
if (!this.stripeService.isConfigured()) {
return plainToInstance(CompanyStripeSubscriptionDto, {
loaded: false,
stripe_subscription_id: null,
status: null,
current_period_end: null,
cancel_at_period_end: null,
collection_method: null,
currency: null,
quantity: null,
stripe_api_mode: null,
card_brand: null,
card_last4: null,
reason: "STRIPE_NOT_CONFIGURED",
message: "Stripe is not configured",
});
}
const stripeApiMode = this.stripeService.getDashboardAccountMode();
let cardBrand: string | null = null;
let cardLast4: string | null = null;
if (company.stripe_customer_id) {
try {
const pm = await this.stripeService.getCustomerDefaultPaymentMethod(company.stripe_customer_id);
cardBrand = pm.brand;
cardLast4 = pm.last4;
} catch {
// Card summary is optional; subscription snapshot still useful
}
}
const base = (): Record<string, unknown> => ({
loaded: false,
stripe_subscription_id: null,
status: null,
current_period_end: null,
cancel_at_period_end: null,
collection_method: null,
currency: null,
quantity: null,
recurring_total_amount: null,
stripe_api_mode: stripeApiMode,
card_brand: cardBrand,
card_last4: cardLast4,
});
let subscriptionId = company.stripe_subscription_id;
if (!subscriptionId && company.stripe_customer_id) {
try {
const activeLike = await this.stripeService.listSubscriptionsForCustomer(company.stripe_customer_id, [
"active",
"trialing",
"past_due",
"unpaid",
]);
const sortedActive = [...activeLike].sort(
(a, b) => stripeSubscriptionPeriodEndUnix(b) - stripeSubscriptionPeriodEndUnix(a),
);
subscriptionId = sortedActive[0]?.id ?? null;
// If nothing billable is open, still show the most recent ended subscription (e.g. canceled)
// so admin sees why there is "no charge" and can open Stripe or ask the customer to resubscribe.
if (!subscriptionId) {
const endedLike = await this.stripeService.listSubscriptionsForCustomer(company.stripe_customer_id, [
"canceled",
"incomplete_expired",
"incomplete",
]);
const sortedEnded = [...endedLike].sort(
(a, b) => stripeSubscriptionPeriodEndUnix(b) - stripeSubscriptionPeriodEndUnix(a),
);
subscriptionId = sortedEnded[0]?.id ?? null;
}
} catch (e) {
return plainToInstance(CompanyStripeSubscriptionDto, {
...base(),
reason: "STRIPE_ERROR",
message: sanitizeExternalErrorMessage(e),
});
}
}
if (!subscriptionId) {
return plainToInstance(CompanyStripeSubscriptionDto, {
...base(),
reason: "NO_STRIPE_SUBSCRIPTION",
message: "No Stripe subscription found for this customer",
});
}
try {
const sub = await this.stripeService.retrieveSubscription(subscriptionId);
const quantity = sub.items?.data?.reduce((sum, item) => sum + (item.quantity ?? 0), 0) ?? null;
const upcoming = await this.stripeService.retrieveUpcomingInvoiceAmountForSubscription(sub.id);
const periodEndUnix = stripeSubscriptionPeriodEndUnix(sub);
const periodEnd = periodEndUnix ? new Date(periodEndUnix * 1000).toISOString() : null;
return plainToInstance(CompanyStripeSubscriptionDto, {
loaded: true,
stripe_subscription_id: sub.id,
status: sub.status,
current_period_end: periodEnd,
cancel_at_period_end: sub.cancel_at_period_end ?? null,
collection_method: sub.collection_method ?? null,
currency: sub.currency ?? null,
quantity,
recurring_total_amount: upcoming.amount,
stripe_api_mode: stripeApiMode,
card_brand: cardBrand,
card_last4: cardLast4,
});
} catch (e) {
return plainToInstance(CompanyStripeSubscriptionDto, {
...base(),
reason: "STRIPE_ERROR",
message: sanitizeExternalErrorMessage(e),
stripe_subscription_id: subscriptionId,
});
}
}
/**
* Admin: one-shot create/recreate Stripe subscription for this company.
* This cancels/overwrites existing billable subscriptions in Stripe (as required by ops).
*/
async runAdminCreateStripeSubscription(companyId: number): Promise<{
success: boolean;
message: string;
next_period_end_iso?: string | null;
}> {
return this.stripePaymentService.runAdminCreateStripeSubscription(companyId);
}
async runAdminReactivateStripeSubscription(
companyId: number,
fromDate: string,
): Promise<{ success: boolean; message: string }> {
const oldCompany = await this.prisma.client.company.findUnique({ where: { id: companyId } });
const result = await this.stripePaymentService.runImmediateExpiredRecoveryCharge(companyId, {
forceRecreateStripeSubscription: true,
recoveryFromDate: fromDate,
});
const newCompany = await this.prisma.client.company.findUnique({ where: { id: companyId } });
await this.systemLogService.logUpdate(
SystemLogEntityType.COMPANY,
companyId,
(oldCompany as unknown as Record<string, unknown>) ?? { id: companyId },
(newCompany as unknown as Record<string, unknown>) ?? { id: companyId },
{
action: "admin_reactivate_subscription_request",
requested_from_date: fromDate,
success: result.success,
response_message: result.message,
},
);
return {
success: result.success,
message: result.message,
};
}
async previewAdminReactivateStripeSubscription(
companyId: number,
fromDate: string,
): Promise<ImmediateRecoveryQuote> {
return this.stripePaymentService.previewImmediateExpiredRecoveryCharge(companyId, fromDate);
}
async updateAdminStripeNextBillingDate(
companyId: number,
nextBillingDate: string,
): Promise<{ success: boolean; message: string; next_billing_date: string }> {
return this.stripePaymentService.updateAdminStripeNextBillingDate(companyId, nextBillingDate);
}
async syncStripeInvoicesAndRefunds(companyId: number): Promise<{
success: boolean;
message: string;
invoices_created: number;
refunds_created: number;
}> {
return this.stripePaymentService.syncCompanyInvoicesAndRefundsFromStripe(companyId);
}
/**
* Override delete method to log deletion
*/
async delete(id: number): Promise<void> {
// Get company data before deletion
const company = await this.prisma.client.company.findUnique({
where: { id },
});
if (!company) {
throw new NotFoundException(`Company with ID ${id} not found`);
}
// Call parent delete method
await super.delete(id);
// Log the deletion
await this.systemLogService.logDelete(SystemLogEntityType.COMPANY, id, company);
}
}