chore: add business logic
This commit is contained in:
@@ -1,14 +1,19 @@
|
||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||
import { InjectQueue } from "@nestjs/bullmq";
|
||||
import { BadRequestException, ForbiddenException, Injectable } from "@nestjs/common";
|
||||
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 { 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 { SearchUserSubsQueryDto } from "../DTO/search-user-subs-query.dto";
|
||||
import { ServiceSubsQueryDto } from "../DTO/service-subs-query.dto";
|
||||
@@ -23,6 +28,7 @@ import { UserSubscriptionsRepository } from "../repositories/user-subscriptions.
|
||||
@Injectable()
|
||||
export class SubscriptionsService {
|
||||
constructor(
|
||||
@InjectQueue(SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME) private readonly provisioningQueue: Queue,
|
||||
private readonly subscriptionsPlanRepository: SubscriptionsPlanRepository,
|
||||
private readonly userSubscriptionsRepository: UserSubscriptionsRepository,
|
||||
private readonly invoicesService: InvoicesService,
|
||||
@@ -196,7 +202,6 @@ export class SubscriptionsService {
|
||||
|
||||
async subscribeToPlan(serviceId: string, subscribeDto: SubscribeServiceDto, userId: string) {
|
||||
const queryRunner = this.dataSource.createQueryRunner();
|
||||
|
||||
try {
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
@@ -210,18 +215,35 @@ export class SubscriptionsService {
|
||||
directDiscount: true,
|
||||
},
|
||||
});
|
||||
|
||||
if (!plan) throw new BadRequestException(SubscriptionMessage.NOT_FOUND);
|
||||
|
||||
const startDate = dayjs().toDate();
|
||||
const endDate = dayjs().add(plan.duration, "day").toDate();
|
||||
|
||||
//
|
||||
const userSubscription = queryRunner.manager.create(UserSubscription, { ...subscribeDto, user, plan, startDate, endDate });
|
||||
let slug = subscribeDto.slug;
|
||||
if (!slug) {
|
||||
slug = slugify(subscribeDto.businessName, { lower: true, strict: true });
|
||||
}
|
||||
|
||||
const existingSlug = await queryRunner.manager.findOne(UserSubscription, { where: { slug } });
|
||||
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);
|
||||
@@ -229,6 +251,8 @@ export class SubscriptionsService {
|
||||
|
||||
await queryRunner.manager.save(UserSubscription, userSubscription);
|
||||
|
||||
await this.addProvisioningJob(userSubscription, plan, user);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
return {
|
||||
message: SubscriptionMessage.SUBSCRIBED,
|
||||
@@ -293,134 +317,43 @@ export class SubscriptionsService {
|
||||
}
|
||||
|
||||
//************************************ */
|
||||
|
||||
async getPlanRevenueAverage() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const startOfYear = new Date(currentYear, 0, 1);
|
||||
const endOfYear = new Date(currentYear, 11, 31);
|
||||
|
||||
const result = await this.userSubscriptionsRepository
|
||||
.createQueryBuilder("userSubscription")
|
||||
.leftJoin("userSubscription.plan", "plan")
|
||||
.select("AVG(plan.price)", "averageRevenue")
|
||||
.where("userSubscription.startDate >= :startDate", { startDate: startOfYear })
|
||||
.andWhere("userSubscription.startDate <= :endDate", { endDate: endOfYear })
|
||||
.andWhere("userSubscription.status = :status", { status: SubscriptionStatus.ACTIVE })
|
||||
.getRawOne();
|
||||
|
||||
return Number(result.averageRevenue) || 0;
|
||||
}
|
||||
|
||||
async getPlansSalesCount() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const startOfYear = new Date(currentYear, 0, 1);
|
||||
const endOfYear = new Date(currentYear, 11, 31);
|
||||
|
||||
return await this.userSubscriptionsRepository
|
||||
.createQueryBuilder("userSubscription")
|
||||
.where("userSubscription.startDate >= :startDate", { startDate: startOfYear })
|
||||
.andWhere("userSubscription.startDate <= :endDate", { endDate: endOfYear })
|
||||
.andWhere("userSubscription.status = :status", { status: SubscriptionStatus.ACTIVE })
|
||||
.getCount();
|
||||
}
|
||||
|
||||
async getPlansSalesCountByMonthsInYear() {
|
||||
const now = new Date();
|
||||
const startOfYear = new Date(now.getFullYear(), 0, 1);
|
||||
const endOfYear = new Date(now.getFullYear(), 11, 31);
|
||||
|
||||
const sales = await this.userSubscriptionsRepository
|
||||
.createQueryBuilder("userSubscription")
|
||||
.select("EXTRACT(MONTH FROM userSubscription.startDate)", "month")
|
||||
.addSelect("COUNT(*)", "count")
|
||||
.where("userSubscription.startDate >= :startDate", { startDate: startOfYear })
|
||||
.andWhere("userSubscription.startDate <= :endDate", { endDate: endOfYear })
|
||||
.andWhere("userSubscription.status = :status", { status: SubscriptionStatus.ACTIVE })
|
||||
.groupBy("EXTRACT(MONTH FROM userSubscription.startDate)")
|
||||
.orderBy("month", "ASC")
|
||||
.getRawMany();
|
||||
|
||||
const salesByMonth = Array(12).fill(0);
|
||||
|
||||
sales.forEach((sale) => {
|
||||
const month = parseInt(sale.month) - 1;
|
||||
salesByMonth[month] = parseInt(sale.count);
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
return salesByMonth;
|
||||
if (!userSubscriptions.length) throw new ForbiddenException(SubscriptionMessage.USER_DOES_NOT_HAVE_ACCESS);
|
||||
|
||||
return { workspaces: userSubscriptions };
|
||||
}
|
||||
|
||||
async getPlansSalesCountByDaysInMonth() {
|
||||
const now = new Date();
|
||||
const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1);
|
||||
const endOfMonth = new Date(now.getFullYear(), now.getMonth() + 1, 0);
|
||||
|
||||
const sales = await this.userSubscriptionsRepository
|
||||
.createQueryBuilder("userSubscription")
|
||||
.select("DATE(userSubscription.startDate)", "date")
|
||||
.addSelect("COUNT(*)", "count")
|
||||
.where("userSubscription.startDate >= :startDate", { startDate: startOfMonth })
|
||||
.andWhere("userSubscription.startDate <= :endDate", { endDate: endOfMonth })
|
||||
.andWhere("userSubscription.status = :status", { status: SubscriptionStatus.ACTIVE })
|
||||
.groupBy("DATE(userSubscription.startDate)")
|
||||
.orderBy("date", "ASC")
|
||||
.getRawMany();
|
||||
|
||||
const daysInMonth = endOfMonth.getDate();
|
||||
const salesByDay = Array(daysInMonth).fill(0);
|
||||
|
||||
sales.forEach((sale) => {
|
||||
const day = new Date(sale.date).getDate() - 1;
|
||||
salesByDay[day] = parseInt(sale.count);
|
||||
});
|
||||
|
||||
return salesByDay;
|
||||
//************************************ */
|
||||
async addProvisioningJob(userSubscription: UserSubscription, plan: SubscriptionPlan, user: User) {
|
||||
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,
|
||||
},
|
||||
{
|
||||
priority: SUBSCRIPTIONS.PROVISIONING_JOB_PRIORITY,
|
||||
delay: SUBSCRIPTIONS.PROVISIONING_JOB_DELAY,
|
||||
attempts: SUBSCRIPTIONS.PROVISIONING_JOB_ATTEMPTS,
|
||||
backoff: { type: "exponential", delay: SUBSCRIPTIONS.PROVISIONING_JOB_BACKOFF },
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
async getPlansSalesCountByDaysInWeek() {
|
||||
const startOfWeek = dayjs().startOf("week").toDate();
|
||||
const endOfWeek = dayjs().endOf("week").toDate();
|
||||
|
||||
const sales = await this.userSubscriptionsRepository
|
||||
.createQueryBuilder("userSubscription")
|
||||
.select("DATE(userSubscription.startDate)", "date")
|
||||
.addSelect("COUNT(*)", "count")
|
||||
.where("userSubscription.startDate >= :startDate", { startDate: startOfWeek })
|
||||
.andWhere("userSubscription.startDate <= :endDate", { endDate: endOfWeek })
|
||||
.andWhere("userSubscription.status = :status", { status: SubscriptionStatus.ACTIVE })
|
||||
.groupBy("DATE(userSubscription.startDate)")
|
||||
.orderBy("date", "ASC")
|
||||
.getRawMany();
|
||||
|
||||
const salesByDay = Array(7).fill(0);
|
||||
|
||||
sales.forEach((sale) => {
|
||||
const day = (dayjs(sale.date).day() + 1) % 7;
|
||||
salesByDay[day] = parseInt(sale.count);
|
||||
});
|
||||
|
||||
return salesByDay;
|
||||
}
|
||||
|
||||
async getSubscriptionGrowthRate() {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const lastYear = currentYear - 1;
|
||||
|
||||
const currentYearCount = await this.userSubscriptionsRepository
|
||||
.createQueryBuilder("userSubscription")
|
||||
.where("EXTRACT(YEAR FROM userSubscription.startDate) = :year", { year: currentYear })
|
||||
.andWhere("userSubscription.status = :status", { status: SubscriptionStatus.ACTIVE })
|
||||
.getCount();
|
||||
|
||||
const lastYearCount = await this.userSubscriptionsRepository
|
||||
.createQueryBuilder("userSubscription")
|
||||
.where("EXTRACT(YEAR FROM userSubscription.startDate) = :year", { year: lastYear })
|
||||
.andWhere("userSubscription.status = :status", { status: SubscriptionStatus.ACTIVE })
|
||||
.getCount();
|
||||
|
||||
if (lastYearCount === 0) return 100;
|
||||
|
||||
const growthRate = ((currentYearCount - lastYearCount) / lastYearCount) * 100;
|
||||
return Math.round(growthRate * 100) / 100;
|
||||
}
|
||||
//************************************ */
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user