717 lines
27 KiB
TypeScript
Executable File
717 lines
27 KiB
TypeScript
Executable File
import { InjectQueue } from "@nestjs/bullmq";
|
||
import { BadRequestException, ForbiddenException, Injectable, Logger } from "@nestjs/common";
|
||
import { ModuleRef } from "@nestjs/core";
|
||
import { Queue } from "bullmq";
|
||
import dayjs from "dayjs";
|
||
import Decimal from "decimal.js";
|
||
import slugify from "slugify";
|
||
import { DataSource, In, Not, QueryRunner } from "typeorm";
|
||
|
||
import { ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum";
|
||
import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
|
||
import { RestaurantService } from "../../dmenu/providers/restaurant.service";
|
||
import { INVOICE } from "../../invoices/constants";
|
||
import { InvoicesService } from "../../invoices/providers/invoices.service";
|
||
import { User } from "../../users/entities/user.entity";
|
||
import { UsersService } from "../../users/providers/users.service";
|
||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||
import { SUBSCRIPTIONS } from "../constants";
|
||
import { AddSubscriptionsToServiceDto } from "../DTO/create-subscription.dto";
|
||
import { AdminUserSubscriptionsQueryDto } from "../DTO/admin-user-subscriptions-query.dto";
|
||
import { SearchUserSubsQueryDto } from "../DTO/search-user-subs-query.dto";
|
||
import { ServiceSubsQueryDto } from "../DTO/service-subs-query.dto";
|
||
import { ExtendUSerSubscribeServiceDto, SubscribeServiceDto } from "../DTO/subscribe-service.dto";
|
||
import { SubscriptionAccessDto } from "../DTO/subscription-access-dto";
|
||
import { UpdateSubscriptionPlanDto } from "../DTO/update-subscription.dto";
|
||
import { SubscriptionPlan } from "../entities/subscription.entity";
|
||
import { UserSubscription } from "../entities/user-subscription.entity";
|
||
import { SubscriptionStatus } from "../enums/subscription-status.enum";
|
||
import { IBusinessProvisioningJob } from "../interfaces/IProvisioning-job-data";
|
||
import { SubscriptionsPlanRepository } from "../repositories/subscriptions.repository";
|
||
import { UserSubscriptionsRepository } from "../repositories/user-subscriptions.repository";
|
||
import { InvoicePurpose } from "../../invoices/interfaces/external-invoice.interface";
|
||
|
||
@Injectable()
|
||
export class SubscriptionsService {
|
||
private readonly logger: Logger = new Logger(SubscriptionsService.name);
|
||
|
||
constructor(
|
||
@InjectQueue(SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME) private readonly provisioningQueue: Queue,
|
||
private readonly subscriptionsPlanRepository: SubscriptionsPlanRepository,
|
||
private readonly userSubscriptionsRepository: UserSubscriptionsRepository,
|
||
private readonly moduleRef: ModuleRef,
|
||
private readonly usersService: UsersService,
|
||
private readonly danakServices: DanakServicesService,
|
||
private readonly dataSource: DataSource,
|
||
) { }
|
||
|
||
private get invoicesService(): InvoicesService {
|
||
return this.moduleRef.get(InvoicesService, { strict: false });
|
||
}
|
||
|
||
private get restaurantService(): RestaurantService {
|
||
return this.moduleRef.get(RestaurantService, { strict: false });
|
||
}
|
||
|
||
//************************************ */
|
||
async createSubscriptionsPlan(createDto: AddSubscriptionsToServiceDto) {
|
||
const danakService = await this.danakServices.findServiceById(createDto.serviceId);
|
||
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
|
||
|
||
const subscriptions = createDto.subs.map((sub) => {
|
||
const adjustedPrice = sub.price < 100_000 ? new Decimal(0) : new Decimal(sub.price);
|
||
const isFree = sub.price < 100_000;
|
||
if (isFree) {
|
||
console.log(`🎁 Plan "${sub.name}" marked as FREE (price ${sub.price} < 100,000 IRR)`);
|
||
} else {
|
||
console.log(`💰 Plan "${sub.name}" price: ${adjustedPrice.toNumber()} IRR`);
|
||
}
|
||
return {
|
||
...sub,
|
||
service: { id: createDto.serviceId },
|
||
price: adjustedPrice,
|
||
originalPrice: adjustedPrice,
|
||
isFree,
|
||
};
|
||
});
|
||
|
||
const subscriptionNames = createDto.subs.map((sub) => sub.name);
|
||
const existingSubscriptions = await this.subscriptionsPlanRepository.find({
|
||
where: {
|
||
name: In(subscriptionNames),
|
||
service: { id: createDto.serviceId },
|
||
},
|
||
});
|
||
|
||
if (existingSubscriptions.length > 0)
|
||
throw new BadRequestException({ message: [SubscriptionMessage.NAME_EXIST, existingSubscriptions] });
|
||
|
||
const createdSubscriptions = this.subscriptionsPlanRepository.create(subscriptions);
|
||
await this.subscriptionsPlanRepository.save(createdSubscriptions);
|
||
|
||
return {
|
||
message: SubscriptionMessage.CREATED,
|
||
subscriptions: createdSubscriptions,
|
||
};
|
||
}
|
||
|
||
//************************************ */
|
||
async getUserSubscriptions(userId: string, queryDto: SearchUserSubsQueryDto) {
|
||
const queryBuilder = this.userSubscriptionsRepository
|
||
.createQueryBuilder("userSubscription")
|
||
.leftJoinAndSelect("userSubscription.plan", "plan")
|
||
.leftJoinAndSelect("plan.service", "service")
|
||
.where("userSubscription.user.id = :userId", { userId });
|
||
|
||
if (queryDto.q) {
|
||
queryBuilder.andWhere("(service.name ILIKE :query OR service.title ILIKE :query OR service.description ILIKE :query)", {
|
||
query: `%${queryDto.q}%`,
|
||
});
|
||
}
|
||
|
||
const subscriptions = await queryBuilder.getMany();
|
||
|
||
return {
|
||
subscriptions,
|
||
};
|
||
}
|
||
//************************************ */
|
||
|
||
async getUserSubscriptionById(userSubId: string, userId: string) {
|
||
const userSubscription = await this.userSubscriptionsRepository.findOne({
|
||
where: { id: userSubId, user: { id: userId } },
|
||
relations: {
|
||
plan: {
|
||
service: {
|
||
images: true,
|
||
subscriptionPlans: true,
|
||
},
|
||
},
|
||
},
|
||
});
|
||
if (!userSubscription) throw new BadRequestException(SubscriptionMessage.USER_SUBS_NOT_FOUND);
|
||
|
||
return {
|
||
userSubscription,
|
||
};
|
||
}
|
||
//************************************ */
|
||
async updateSubscriptionPlan(id: string, updateDto: UpdateSubscriptionPlanDto) {
|
||
const subscription = await this.subscriptionsPlanRepository.findOne({ where: { id }, relations: { service: true } });
|
||
if (!subscription) throw new BadRequestException(SubscriptionMessage.NOT_FOUND);
|
||
|
||
if (updateDto.serviceId) {
|
||
const danakService = await this.danakServices.findServiceById(updateDto.serviceId);
|
||
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
|
||
subscription.service = danakService;
|
||
}
|
||
|
||
if (updateDto.name) {
|
||
const existSubscription = await this.subscriptionsPlanRepository.findOneBy({
|
||
name: updateDto.name,
|
||
id: Not(id),
|
||
service: { id: subscription.service.id },
|
||
});
|
||
if (existSubscription) throw new BadRequestException(SubscriptionMessage.NAME_EXIST);
|
||
subscription.name = updateDto.name;
|
||
}
|
||
|
||
if (updateDto.duration) subscription.duration = updateDto.duration;
|
||
if (updateDto.price) {
|
||
const adjustedPrice = updateDto.price < 100_000 ? new Decimal(0) : new Decimal(updateDto.price);
|
||
const isFree = updateDto.price < 100_000;
|
||
if (isFree) {
|
||
console.log(`🎁 Plan "${subscription.name}" updated to FREE (price ${updateDto.price} < 100,000 IRR)`);
|
||
} else {
|
||
console.log(`💰 Plan "${subscription.name}" price updated to: ${adjustedPrice.toNumber()} IRR`);
|
||
}
|
||
subscription.price = adjustedPrice;
|
||
subscription.originalPrice = adjustedPrice;
|
||
}
|
||
|
||
await this.subscriptionsPlanRepository.save({
|
||
...subscription,
|
||
isFree: updateDto.price ? updateDto.price < 100_000 : false,
|
||
});
|
||
|
||
return {
|
||
message: SubscriptionMessage.UPDATED,
|
||
subscription,
|
||
};
|
||
}
|
||
//************************************ */
|
||
|
||
async getSubscriptionPlanById(subscriptionId: string) {
|
||
const subscription = await this.subscriptionsPlanRepository.findOneBy({ id: subscriptionId });
|
||
if (!subscription) throw new BadRequestException(SubscriptionMessage.NOT_FOUND);
|
||
return {
|
||
subscription,
|
||
};
|
||
}
|
||
//************************************ */
|
||
|
||
async All(serviceId: string, queryDto: ServiceSubsQueryDto) {
|
||
const service = await this.danakServices.findServiceById(serviceId);
|
||
|
||
const { limit, skip } = PaginationUtils(queryDto);
|
||
|
||
const queryBuilder = this.subscriptionsPlanRepository
|
||
.createQueryBuilder("subscription")
|
||
.leftJoin("subscription.service", "service")
|
||
.addSelect(["service.id"])
|
||
// .leftJoin("subscription.discounts", "discount")
|
||
// .addSelect(["discount.calculationType", "discount.amount", "discount.startDate", "discount.endDate", "discount.isActive"])
|
||
.where("service.id = :serviceId", { serviceId });
|
||
|
||
if (queryDto.q) {
|
||
queryBuilder.andWhere("subscription.name ILIKE :query", { query: `%${queryDto.q}%` });
|
||
}
|
||
|
||
if (queryDto.isActive !== undefined) {
|
||
queryBuilder.andWhere("subscription.isActive = :isActive", { isActive: queryDto.isActive === 1 });
|
||
}
|
||
|
||
const [subscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
|
||
|
||
return {
|
||
service,
|
||
subscriptions,
|
||
count,
|
||
pagination: true,
|
||
};
|
||
}
|
||
|
||
//************************************ */
|
||
|
||
async toggleSubStatus(subId: string) {
|
||
const subscription = await this.subscriptionsPlanRepository.findOneBy({ id: subId });
|
||
if (!subscription) throw new BadRequestException(SubscriptionMessage.NOT_FOUND);
|
||
|
||
subscription.isActive = !subscription.isActive;
|
||
|
||
await this.subscriptionsPlanRepository.save(subscription);
|
||
return {
|
||
message: SubscriptionMessage.STATUS_UPDATED,
|
||
isActive: subscription.isActive,
|
||
};
|
||
}
|
||
//************************************ */
|
||
|
||
async subscribeToPlan(serviceId: string, subscribeDto: SubscribeServiceDto, userId: string) {
|
||
const queryRunner = this.dataSource.createQueryRunner();
|
||
|
||
let isFree = false;
|
||
try {
|
||
await queryRunner.connect();
|
||
await queryRunner.startTransaction();
|
||
|
||
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
|
||
|
||
const plan = await queryRunner.manager.findOne(SubscriptionPlan, {
|
||
where: { id: subscribeDto.planId, service: { id: serviceId } },
|
||
relations: {
|
||
service: true,
|
||
directDiscount: true,
|
||
},
|
||
});
|
||
if (!plan) throw new BadRequestException(SubscriptionMessage.NOT_FOUND);
|
||
|
||
if (plan.isFree) {
|
||
isFree = true;
|
||
// const oneMonthAgo = dayjs().subtract(1, "month").toDate();
|
||
const existingFreePlanSubscription = await queryRunner.manager.findOne(UserSubscription, {
|
||
where: {
|
||
user: { id: userId },
|
||
plan: {
|
||
isFree: true,
|
||
service: {
|
||
id: serviceId,
|
||
},
|
||
},
|
||
// startDate: MoreThan(oneMonthAgo),
|
||
},
|
||
relations: {
|
||
plan: true,
|
||
},
|
||
});
|
||
|
||
if (existingFreePlanSubscription) {
|
||
throw new BadRequestException(SubscriptionMessage.FREE_PLAN_LIMIT_EXCEEDED);
|
||
}
|
||
}
|
||
|
||
const startDate = dayjs().toDate();
|
||
const endDate = dayjs().add(plan.duration, "day").toDate();
|
||
|
||
let slug = subscribeDto.slug;
|
||
if (!slug) {
|
||
slug = slugify(subscribeDto.businessName, { lower: true, strict: true });
|
||
}
|
||
|
||
const existingSlug = await queryRunner.manager.findOne(UserSubscription, { where: { slug, deletedAt: undefined } });
|
||
if (existingSlug) throw new BadRequestException(SubscriptionMessage.SLUG_EXISTS);
|
||
|
||
let staff: User[] = [];
|
||
if (subscribeDto.staff && subscribeDto.staff.length > 0) {
|
||
staff = await this.usersService.findUsersByIds(subscribeDto.staff);
|
||
}
|
||
|
||
const userSubscription = queryRunner.manager.create(UserSubscription, {
|
||
...subscribeDto,
|
||
user,
|
||
plan,
|
||
startDate,
|
||
endDate,
|
||
slug,
|
||
staff,
|
||
});
|
||
|
||
await queryRunner.manager.save(UserSubscription, userSubscription);
|
||
const invoiceDueDate = dayjs(userSubscription.startDate).add(INVOICE.DUEDATE, "day").toDate();
|
||
|
||
const invoice = await this.invoicesService.createInvoiceForSubscription(user, plan, userSubscription, invoiceDueDate, queryRunner, InvoicePurpose.NEW);
|
||
userSubscription.status = SubscriptionStatus.INACTIVE;
|
||
|
||
await queryRunner.manager.save(UserSubscription, userSubscription);
|
||
|
||
await this.addProvisioningJob(userSubscription, plan, user);
|
||
|
||
if (isFree) {
|
||
await this.invoicesService.payInvoice(invoice.id, user.id, undefined, queryRunner);
|
||
}
|
||
|
||
await queryRunner.commitTransaction();
|
||
return {
|
||
message: SubscriptionMessage.SUBSCRIBED,
|
||
userSubscription,
|
||
invoice,
|
||
};
|
||
} catch (error) {
|
||
await queryRunner.rollbackTransaction();
|
||
throw error;
|
||
} finally {
|
||
await queryRunner.release();
|
||
}
|
||
}
|
||
//************************************ */
|
||
|
||
async renewUserSubscription(userSubscriptionId: string, dto: ExtendUSerSubscribeServiceDto, userId: string) {
|
||
const queryRunner = this.dataSource.createQueryRunner();
|
||
|
||
try {
|
||
await queryRunner.connect();
|
||
await queryRunner.startTransaction();
|
||
|
||
const userSubscription = await queryRunner.manager.findOne(UserSubscription, {
|
||
where: { id: userSubscriptionId },
|
||
relations: {
|
||
user: true,
|
||
plan: true,
|
||
},
|
||
});
|
||
|
||
if (!userSubscription) throw new BadRequestException(SubscriptionMessage.NOT_FOUND);
|
||
|
||
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
|
||
|
||
const plan = await queryRunner.manager.findOne(SubscriptionPlan, {
|
||
where: { id: dto.planId, service: { id: dto.serviceId } },
|
||
relations: { service: true, directDiscount: true },
|
||
});
|
||
|
||
if (!plan) throw new BadRequestException(SubscriptionMessage.PLAN_NOT_FOUND);
|
||
|
||
if (plan.isFree) {
|
||
throw new BadRequestException(SubscriptionMessage.FREE_PLAN_NOT_ALLOWED);
|
||
}
|
||
|
||
const invoiceDueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate();
|
||
|
||
const invoice = await this.invoicesService.createInvoiceForSubscription(user, plan, userSubscription, invoiceDueDate, queryRunner, InvoicePurpose.RENEW);
|
||
|
||
// userSubscription.plan = plan;
|
||
|
||
// await queryRunner.manager.save(userSubscription);
|
||
|
||
await this.addProvisioningJob(userSubscription, plan, user);
|
||
|
||
await queryRunner.commitTransaction();
|
||
return {
|
||
message: SubscriptionMessage.INVOICE_CREATED_SUCCESSFULLY,
|
||
userSubscription,
|
||
invoice,
|
||
};
|
||
} catch (error) {
|
||
await queryRunner.rollbackTransaction();
|
||
throw error;
|
||
} finally {
|
||
await queryRunner.release();
|
||
}
|
||
}
|
||
|
||
//************************************ */
|
||
async upgradeSubscription(userSubscriptionId: string, planId: string, userId: string) {
|
||
const queryRunner = this.dataSource.createQueryRunner();
|
||
|
||
try {
|
||
await queryRunner.connect();
|
||
await queryRunner.startTransaction();
|
||
|
||
const userSubscription = await queryRunner.manager.findOne(UserSubscription, {
|
||
where: { id: userSubscriptionId, user: { id: userId } },
|
||
relations: { plan: { service: true }, user: true }
|
||
});
|
||
if (!userSubscription) throw new BadRequestException(SubscriptionMessage.USER_SUBS_NOT_FOUND);
|
||
|
||
// Check if restaurant exists for dmenu service
|
||
if (userSubscription.plan.service.name.includes('دی منو')) {
|
||
try {
|
||
const rest = await this.restaurantService.getRestaurantSubscription(userSubscriptionId);
|
||
console.log('rest', rest)
|
||
} catch (error) {
|
||
throw new BadRequestException('Restaurant* not found for this subscription');
|
||
}
|
||
}
|
||
|
||
const newPlan = await this.subscriptionsPlanRepository.findById(planId);
|
||
if (!newPlan) throw new BadRequestException(SubscriptionMessage.PLAN_NOT_FOUND);
|
||
|
||
|
||
if (userSubscription.plan.service.id !== newPlan.service.id) {
|
||
throw new BadRequestException(SubscriptionMessage.SERVICE_MISMATCH);
|
||
}
|
||
|
||
// Validate that new plan is more expensive than current plan
|
||
if (new Decimal(newPlan.price).lessThan(userSubscription.plan.price)) {
|
||
throw new BadRequestException(SubscriptionMessage.UPGRADE_TO_LOWER_OR_EQUAL_PLAN_NOT_ALLOWED);
|
||
}
|
||
|
||
// Calculate remaining days in current subscription
|
||
const today = dayjs();
|
||
const endDate = dayjs(userSubscription.endDate);
|
||
const currentPlanRemainingDays = Math.max(0, endDate.diff(today, 'day'));
|
||
console.log('🔢 CALCULATION LOGS:');
|
||
console.log(`📅 Remaining days: ${currentPlanRemainingDays} (endDate: ${endDate.format('YYYY-MM-DD')}, today: ${today.format('YYYY-MM-DD')})`);
|
||
|
||
if (currentPlanRemainingDays <= 0) {
|
||
throw new BadRequestException(SubscriptionMessage.SUBSCRIPTION_EXPIRED_CANNOT_UPGRADE);
|
||
}
|
||
|
||
const remainingDays = newPlan.duration - currentPlanRemainingDays
|
||
// Calculate daily rates
|
||
const currentPlanDuration = userSubscription.plan.duration;
|
||
const newPlanDuration = newPlan.duration;
|
||
|
||
const currentDailyRate = new Decimal(userSubscription.plan.price).div(currentPlanDuration);
|
||
const newDailyRate = new Decimal(newPlan.price).div(newPlanDuration);
|
||
console.log(`💰 Current plan: ${userSubscription.plan.price} IRR for ${currentPlanDuration} days = ${currentDailyRate.toNumber()} IRR/day`);
|
||
console.log(`💰 New plan: ${newPlan.price} IRR for ${newPlanDuration} days = ${newDailyRate.toNumber()} IRR/day`);
|
||
|
||
// Calculate price difference per day
|
||
const dailyPriceDifference = newDailyRate.sub(currentDailyRate);
|
||
console.log(`📈 Daily price difference: ${dailyPriceDifference.toNumber()} IRR/day`);
|
||
|
||
// Calculate prorated upgrade cost for remaining days
|
||
const proratedUpgradeCost = dailyPriceDifference.mul(currentPlanRemainingDays).add(newDailyRate.mul(remainingDays))
|
||
console.log(`🧮 Prorated upgrade cost: ${dailyPriceDifference.toNumber()} IRR/day × ${currentPlanRemainingDays} days = ${proratedUpgradeCost.toNumber()} IRR`);
|
||
|
||
// Ensure minimum charge amount
|
||
const minimumCharge = new Decimal(1000); // 1000 IRR minimum
|
||
const finalUpgradeCost = + Decimal.max(proratedUpgradeCost, minimumCharge).toNumber().toFixed(0)
|
||
console.log(`✅ Final upgrade cost: max(${proratedUpgradeCost.toNumber()} IRR, ${minimumCharge.toNumber()} IRR minimum) = ${finalUpgradeCost} IRR`);
|
||
|
||
const user = await this.usersService.findOneByIdWithQueryRunner(userSubscription.user.id, queryRunner);
|
||
|
||
const invoiceDueDate = dayjs().add(INVOICE.DUEDATE, "day").toDate();
|
||
|
||
const invoice = await this.invoicesService.createInvoiceForSubscriptionUpgrade(
|
||
user,
|
||
newPlan,
|
||
userSubscription,
|
||
invoiceDueDate,
|
||
queryRunner,
|
||
finalUpgradeCost
|
||
);
|
||
|
||
// Update the subscription to the new plan
|
||
// userSubscription.plan = newPlan;
|
||
// await queryRunner.manager.save(UserSubscription, userSubscription);
|
||
|
||
// Add provisioning job for the new plan
|
||
await this.addProvisioningJob(userSubscription, newPlan, user);
|
||
|
||
await queryRunner.commitTransaction();
|
||
return {
|
||
message: SubscriptionMessage.INVOICE_CREATED_SUCCESSFULLY,
|
||
userSubscription,
|
||
invoice,
|
||
upgradeDetails: {
|
||
remainingDays: currentPlanRemainingDays,
|
||
currentPlanPrice: userSubscription.plan.price,
|
||
newPlanPrice: newPlan.price,
|
||
proratedUpgradeCost: finalUpgradeCost,
|
||
},
|
||
};
|
||
} catch (error) {
|
||
await queryRunner.rollbackTransaction();
|
||
throw error;
|
||
} finally {
|
||
await queryRunner.release();
|
||
}
|
||
}
|
||
|
||
//************************************ */
|
||
async getServiceSubscriptions(serviceId: string, queryDto: ServiceSubsQueryDto) {
|
||
const service = await this.danakServices.findServiceById(serviceId);
|
||
|
||
const { limit, skip } = PaginationUtils(queryDto);
|
||
|
||
const queryBuilder = this.subscriptionsPlanRepository
|
||
.createQueryBuilder("subscription")
|
||
.leftJoin("subscription.service", "service")
|
||
.addSelect(["service.id"])
|
||
// .leftJoin("subscription.discounts", "discount")
|
||
// .addSelect(["discount.calculationType", "discount.amount", "discount.startDate", "discount.endDate", "discount.isActive"])
|
||
.where("service.id = :serviceId", { serviceId });
|
||
|
||
if (queryDto.q) {
|
||
queryBuilder.andWhere("subscription.name ILIKE :query", { query: `%${queryDto.q}%` });
|
||
}
|
||
|
||
if (queryDto.isActive !== undefined) {
|
||
queryBuilder.andWhere("subscription.isActive = :isActive", { isActive: queryDto.isActive === 1 });
|
||
}
|
||
|
||
const [subscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
|
||
|
||
return {
|
||
service,
|
||
subscriptions,
|
||
count,
|
||
pagination: true,
|
||
};
|
||
}
|
||
//************************************ */
|
||
|
||
async getAllServiceSubscriptions(serviceIds: string[], queryRunner: QueryRunner) {
|
||
const subscriptions = await queryRunner.manager.find(this.subscriptionsPlanRepository.target, {
|
||
where: { service: { id: In(serviceIds) }, isActive: true },
|
||
relations: { directDiscount: true, service: true },
|
||
});
|
||
return subscriptions;
|
||
}
|
||
//************************************ */
|
||
|
||
async countUserSubscriptions(userId: string) {
|
||
const subscriptionCount = await this.userSubscriptionsRepository.count({
|
||
where: { user: { id: userId }, status: SubscriptionStatus.ACTIVE },
|
||
});
|
||
|
||
return subscriptionCount;
|
||
}
|
||
|
||
//************************************ */
|
||
async getUserSubscriptionsForService(userId: string, serviceId: string) {
|
||
const userSubscriptions = await this.userSubscriptionsRepository.find({
|
||
where: [
|
||
{ user: { id: userId }, plan: { service: { id: serviceId } }, status: SubscriptionStatus.ACTIVE },
|
||
{ staff: { id: userId }, plan: { service: { id: serviceId } }, status: SubscriptionStatus.ACTIVE },
|
||
],
|
||
relations: {
|
||
plan: { service: true },
|
||
staff: true,
|
||
},
|
||
order: { endDate: "DESC" },
|
||
select: {
|
||
id: true,
|
||
businessName: true,
|
||
businessPhone: true,
|
||
description: true,
|
||
slug: true,
|
||
status: true,
|
||
endDate: true,
|
||
startDate: true,
|
||
plan: { id: true, name: true, duration: true, service: { id: true, name: true, slug: true, title: true } },
|
||
staff: { id: true, firstName: true, lastName: true, email: true, phone: true },
|
||
},
|
||
});
|
||
|
||
if (!userSubscriptions.length) throw new ForbiddenException(SubscriptionMessage.USER_DOES_NOT_HAVE_ACCESS);
|
||
|
||
return { workspaces: userSubscriptions };
|
||
}
|
||
|
||
//************************************ */
|
||
async checkUserAccessToService(serviceId: string, accessDto: SubscriptionAccessDto) {
|
||
const userSubscriptions = await this.userSubscriptionsRepository.findOne({
|
||
where: [
|
||
{
|
||
id: accessDto.danakSubscriptionId,
|
||
user: { id: accessDto.userId },
|
||
plan: { service: { id: serviceId } },
|
||
status: SubscriptionStatus.ACTIVE,
|
||
},
|
||
{
|
||
id: accessDto.danakSubscriptionId,
|
||
staff: { id: accessDto.userId },
|
||
plan: { service: { id: serviceId } },
|
||
status: SubscriptionStatus.ACTIVE,
|
||
},
|
||
],
|
||
relations: {
|
||
plan: { service: true },
|
||
staff: true,
|
||
},
|
||
});
|
||
return { hasAccess: !!userSubscriptions && userSubscriptions.status === SubscriptionStatus.ACTIVE };
|
||
}
|
||
|
||
//************************************ */
|
||
async addProvisioningJob(userSubscription: UserSubscription, plan: SubscriptionPlan, user: User) {
|
||
this.logger.debug(`Adding provisioning job for user ${user.id} and subscription ${userSubscription.id}`);
|
||
await this.provisioningQueue.add(
|
||
SUBSCRIPTIONS.PROVISIONING_JOB_NAME,
|
||
{
|
||
subscriptionId: userSubscription.id,
|
||
serviceId: plan.service.id,
|
||
serviceName: plan.service.name,
|
||
businessName: userSubscription.businessName,
|
||
userId: user.id,
|
||
slug: userSubscription.slug,
|
||
phone: user.phone,
|
||
email: user.email,
|
||
fullName: `${user.firstName} ${user.lastName}`,
|
||
} as IBusinessProvisioningJob,
|
||
{
|
||
priority: SUBSCRIPTIONS.PROVISIONING_JOB_PRIORITY,
|
||
delay: SUBSCRIPTIONS.PROVISIONING_JOB_DELAY,
|
||
attempts: SUBSCRIPTIONS.PROVISIONING_JOB_ATTEMPTS,
|
||
backoff: { type: "exponential", delay: SUBSCRIPTIONS.PROVISIONING_JOB_BACKOFF },
|
||
},
|
||
);
|
||
}
|
||
|
||
//************************************ */
|
||
async getAllUserSubscriptions(queryDto: AdminUserSubscriptionsQueryDto) {
|
||
const { limit, skip } = PaginationUtils(queryDto);
|
||
|
||
const queryBuilder = this.userSubscriptionsRepository
|
||
.createQueryBuilder("userSubscription")
|
||
.leftJoinAndSelect("userSubscription.user", "user")
|
||
.leftJoinAndSelect("userSubscription.plan", "plan")
|
||
.leftJoin("plan.service", "service")
|
||
.addSelect(["service.name", "service.title"]);
|
||
|
||
if (queryDto.q) {
|
||
queryBuilder.andWhere(
|
||
"(userSubscription.businessName ILIKE :query OR userSubscription.description ILIKE :query OR user.email ILIKE :query OR user.phone ILIKE :query OR user.firstName ILIKE :query OR user.lastName ILIKE :query OR service.name ILIKE :query OR service.title ILIKE :query)",
|
||
{ query: `%${queryDto.q}%` }
|
||
);
|
||
}
|
||
|
||
if (queryDto.userId) {
|
||
queryBuilder.andWhere("user.id = :userId", { userId: queryDto.userId });
|
||
}
|
||
|
||
if (queryDto.serviceId) {
|
||
queryBuilder.andWhere("service.id = :serviceId", { serviceId: queryDto.serviceId });
|
||
}
|
||
|
||
if (queryDto.status) {
|
||
queryBuilder.andWhere("userSubscription.status = :status", { status: queryDto.status });
|
||
}
|
||
|
||
if (queryDto.startDateFrom) {
|
||
queryBuilder.andWhere("userSubscription.startDate >= :startDateFrom", { startDateFrom: queryDto.startDateFrom });
|
||
}
|
||
|
||
if (queryDto.startDateTo) {
|
||
queryBuilder.andWhere("userSubscription.startDate <= :startDateTo", { startDateTo: queryDto.startDateTo });
|
||
}
|
||
|
||
if (queryDto.endDateFrom) {
|
||
queryBuilder.andWhere("userSubscription.endDate >= :endDateFrom", { endDateFrom: queryDto.endDateFrom });
|
||
}
|
||
|
||
if (queryDto.endDateTo) {
|
||
queryBuilder.andWhere("userSubscription.endDate <= :endDateTo", { endDateTo: queryDto.endDateTo });
|
||
}
|
||
|
||
queryBuilder.orderBy("userSubscription.createdAt", "DESC");
|
||
|
||
const [userSubscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
|
||
|
||
return {
|
||
userSubscriptions,
|
||
count,
|
||
pagination: true,
|
||
};
|
||
}
|
||
|
||
//************************************ */
|
||
async getDpagePlans() {
|
||
const plans = await this.subscriptionsPlanRepository.find({ where: { service:{slug:'Dpage'} } });
|
||
return plans;
|
||
}
|
||
//************************************ */
|
||
|
||
async softDeleteUSerSubscription(userSubscriptionId: string) {
|
||
const usersubscription = await this.userSubscriptionsRepository.findOne({ where: { id: userSubscriptionId } })
|
||
if (!usersubscription) {
|
||
throw new BadRequestException(SubscriptionMessage.NOT_FOUND)
|
||
}
|
||
await this.userSubscriptionsRepository.softDelete(userSubscriptionId)
|
||
return {
|
||
message: SubscriptionMessage.SUBSCRIPTION_DELETED
|
||
}
|
||
}
|
||
|
||
// ******************************** //
|
||
async findOneOrFail(userSubscriptionId: string){
|
||
const usersubscription = await this.userSubscriptionsRepository.findOne({ where: { id: userSubscriptionId },relations:['user'] })
|
||
if (!usersubscription) {
|
||
throw new BadRequestException(SubscriptionMessage.NOT_FOUND)
|
||
}
|
||
return usersubscription
|
||
}
|
||
} |