apps/recallassess/recallassess-api/src/jobs/subscription-expiry-schedule.service.ts
Runs subscription expiry synchronization on a fixed interval while API is up.
Properties |
|
Methods |
|
constructor(prisma: BNestPrismaService)
|
||||||
|
Parameters :
|
| Private Async expireSubscriptions |
expireSubscriptions()
|
|
Returns :
Promise<void>
|
| Async runExpireSubscriptionsCron |
runExpireSubscriptionsCron()
|
Decorators :
@Cron(CronExpression.EVERY_MINUTE)
|
|
Returns :
Promise<void>
|
| Private Async syncCompanySubscriptionExpiryToggleWithSubscriptionState |
syncCompanySubscriptionExpiryToggleWithSubscriptionState()
|
|
Returns :
Promise<void>
|
| Private Readonly logger |
Type : unknown
|
Default value : new Logger(SubscriptionExpiryScheduleService.name)
|
import { BNestPrismaService } from "@bish-nest/core/services";
import { Injectable, Logger } from "@nestjs/common";
import { Cron, CronExpression } from "@nestjs/schedule";
import { SubscriptionStatus } from "@prisma/client";
/**
* Runs subscription expiry synchronization on a fixed interval while API is up.
*/
@Injectable()
export class SubscriptionExpiryScheduleService {
private readonly logger = new Logger(SubscriptionExpiryScheduleService.name);
constructor(private readonly prisma: BNestPrismaService) {}
@Cron(CronExpression.EVERY_MINUTE)
async runExpireSubscriptionsCron(): Promise<void> {
try {
await this.expireSubscriptions();
} catch (err) {
this.logger.error("Scheduled expireSubscriptions failed", err instanceof Error ? err.stack : String(err));
}
}
private async expireSubscriptions(): Promise<void> {
this.logger.log("Running subscription expiry cron...");
const now = new Date();
const subscriptions = await this.prisma.client.subscription.findMany({
where: {
is_current: true,
status: SubscriptionStatus.ACTIVE,
next_billing_date: { lt: now },
},
select: { id: true, company_id: true },
});
if (subscriptions.length === 0) {
this.logger.debug("No subscriptions eligible for past-due ACTIVE expiry.");
return;
}
let expiredCount = 0;
for (const subscription of subscriptions) {
try {
await this.prisma.client.$transaction(async (tx) => {
await tx.subscription.update({
where: { id: subscription.id },
data: {
status: SubscriptionStatus.EXPIRED,
is_current: false,
},
});
await tx.company.update({
where: { id: subscription.company_id },
data: { is_subscription_expiry: true },
});
});
expiredCount += 1;
} catch (error) {
this.logger.error(
`Failed to expire subscription ${subscription.id} for company ${subscription.company_id}`,
error instanceof Error ? error.stack : String(error),
);
}
}
await this.syncCompanySubscriptionExpiryToggleWithSubscriptionState();
this.logger.log(`Subscription expiry cron completed. Expired: ${expiredCount}`);
}
private async syncCompanySubscriptionExpiryToggleWithSubscriptionState(): Promise<void> {
const [companies, activeByCompany] = await Promise.all([
this.prisma.client.company.findMany({
select: { id: true, is_subscription_expiry: true },
}),
this.prisma.client.subscription.findMany({
where: { is_current: true, status: SubscriptionStatus.ACTIVE },
select: { company_id: true },
distinct: ["company_id"],
}),
]);
const hasCurrentActiveSub = new Set(activeByCompany.map((r) => r.company_id));
for (const company of companies) {
const desiredExpiry = !hasCurrentActiveSub.has(company.id);
if (desiredExpiry !== company.is_subscription_expiry) {
await this.prisma.client.company.update({
where: { id: company.id },
data: { is_subscription_expiry: desiredExpiry },
});
}
}
}
}