Files
dsc-api/src/modules/support-plans/providers/support-plans.service.ts
T

277 lines
11 KiB
TypeScript

import { BadRequestException, Injectable } from "@nestjs/common";
import dayjs from "dayjs";
import { Decimal } from "decimal.js";
import { DataSource } from "typeorm";
import { SupportPlanMessage } from "../../../common/enums/message.enum";
import { INVOICE } from "../../invoices/constants";
import { InvoicesService } from "../../invoices/providers/invoices.service";
import { UsersService } from "../../users/providers/users.service";
import { CreateSupportPlanDto } from "../DTO/create-support-plan.dto";
import { GetSupportPlanListQueryDto } from "../DTO/get-support-plan-list-query.dto";
import { SearchSupportPlanUsersQueryDto } from "../DTO/search-support-plan-users-query.dto";
import { UpdateSupportPlanDto } from "../DTO/update-support-plan-feature.dto";
import { UserSupportPlanStatus } from "../enums/user-support-plan-status.enum";
import { SupportPlanFeatureRepository } from "../repositories/support-plan-feature.repository";
import { SupportPlanRepository } from "../repositories/support-plan.repository";
import { UserSupportPlanRepository } from "../repositories/user-support-plan.repository";
@Injectable()
export class SupportPlansService {
constructor(
private readonly supportPlanRepo: SupportPlanRepository,
private readonly supportPlanFeatureRepo: SupportPlanFeatureRepository,
private readonly userSupportPlanRepo: UserSupportPlanRepository,
private readonly usersService: UsersService,
private readonly invoicesService: InvoicesService,
private readonly dataSource: DataSource,
) {}
async createSupportPlan(createSupportPlanDto: CreateSupportPlanDto) {
const supportPlan = this.supportPlanRepo.create(createSupportPlanDto);
await this.supportPlanRepo.save({ ...supportPlan, isFree: createSupportPlanDto.price === 0 });
return {
message: SupportPlanMessage.SUPPORT_PLAN_CREATED,
supportPlan,
};
}
//***************************************** */
async getSupportPlansList(queryDto: GetSupportPlanListQueryDto) {
const [supportPlans, count] = await this.supportPlanRepo.getSupportPlansListAdmin(queryDto);
return {
supportPlans,
count,
paginate: true,
};
}
//***************************************** */
async getSupportPlansListUser() {
const supportPlans = await this.supportPlanRepo.getSupportPlansListUser();
return {
supportPlans,
};
}
//***************************************** */
async getSupportPlanById(id: string) {
const supportPlan = await this.supportPlanRepo.getSupportPlanById(id);
if (!supportPlan) throw new BadRequestException(SupportPlanMessage.SUPPORT_PLAN_NOT_FOUND);
return {
supportPlan,
};
}
//***************************************** */
async deleteSupportPlanById(id: string) {
const supportPlan = await this.supportPlanRepo.getSupportPlanById(id);
if (!supportPlan) throw new BadRequestException(SupportPlanMessage.SUPPORT_PLAN_NOT_FOUND);
await this.supportPlanRepo.softDelete(id);
return {
message: SupportPlanMessage.SUPPORT_PLAN_DELETED,
};
}
//***************************************** */
async updateSupportPlanById(id: string, updateSupportPlanDto: UpdateSupportPlanDto) {
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
const supportPlan = await queryRunner.manager.findOne(this.supportPlanRepo.target, { where: { id }, relations: { features: true } });
if (!supportPlan) throw new BadRequestException(SupportPlanMessage.SUPPORT_PLAN_NOT_FOUND);
// If updating features:
if (updateSupportPlanDto.features) {
// Remove old features (optional, if replacing)
await queryRunner.manager.delete(this.supportPlanFeatureRepo.target, { supportPlan: { id } });
// Add new features, making sure to set the relation
const features = updateSupportPlanDto.features.map((f) => ({
...f,
supportPlan, // or supportPlan: { id }
}));
await queryRunner.manager.save(this.supportPlanFeatureRepo.target, features);
}
// Update the plan itself
await queryRunner.manager.save(this.supportPlanRepo.target, { ...supportPlan, ...updateSupportPlanDto, features: undefined });
await queryRunner.commitTransaction();
return {
message: SupportPlanMessage.SUPPORT_PLAN_UPDATED,
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
//***************************************** */
async subscribeToSupportPlan(id: string, userId: string) {
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
const supportPlan = await queryRunner.manager.findOne(this.supportPlanRepo.target, { where: { id } });
if (!supportPlan) throw new BadRequestException(SupportPlanMessage.SUPPORT_PLAN_NOT_FOUND);
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
const existingUserSupportPlan = await queryRunner.manager.findOne(this.userSupportPlanRepo.target, {
where: { user: { id: userId }, supportPlan: { id } },
});
if (existingUserSupportPlan) throw new BadRequestException(SupportPlanMessage.SUPPORT_PLAN_ALREADY_SUBSCRIBED);
const existingUserSupport = await queryRunner.manager.findOne(this.userSupportPlanRepo.target, {
where: { user: { id: userId } },
});
if (existingUserSupport) throw new BadRequestException(SupportPlanMessage.SUPPORT_PLAN_ALREADY_SUBSCRIBED_OR_INVOICE_EXIST);
const startDate = dayjs().toDate();
const endDate = dayjs().add(supportPlan.duration, "day").toDate();
const userSupportPlan = queryRunner.manager.create(this.userSupportPlanRepo.target, { user, supportPlan, startDate, endDate });
await queryRunner.manager.save(this.userSupportPlanRepo.target, userSupportPlan);
const invoiceDueDate = dayjs(userSupportPlan.startDate).add(INVOICE.DUEDATE, "day").toDate();
const invoice = await this.invoicesService.createInvoiceForSupportPlan(
user,
supportPlan,
userSupportPlan,
invoiceDueDate,
queryRunner,
);
userSupportPlan.status = UserSupportPlanStatus.INACTIVE;
await queryRunner.manager.save(this.userSupportPlanRepo.target, userSupportPlan);
if (supportPlan.isFree) {
userSupportPlan.status = UserSupportPlanStatus.ACTIVE;
await queryRunner.manager.save(this.userSupportPlanRepo.target, userSupportPlan);
await this.invoicesService.payInvoice(invoice.id, user.id, queryRunner);
}
await queryRunner.commitTransaction();
return {
message: SupportPlanMessage.SUPPORT_PLAN_SUBSCRIBED,
invoice,
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
//***************************************** */
async getUserSupportPlanOfUser(userId: string) {
const userSupportPlan = await this.userSupportPlanRepo.findOne({
where: { user: { id: userId }, status: UserSupportPlanStatus.ACTIVE },
relations: { supportPlan: { features: true } },
});
return { userSupportPlan };
}
//***************************************** */
async getSupportPlanUsers(supportPlanId: string, queryDto: SearchSupportPlanUsersQueryDto) {
const [users, count] = await this.userSupportPlanRepo.getSupportPlanUsers(supportPlanId, queryDto);
return { users, count, paginate: true };
}
//***************************************** */
async upgradeSupportPlan(newPlanId: string, userId: string) {
const queryRunner = this.dataSource.createQueryRunner();
try {
await queryRunner.connect();
await queryRunner.startTransaction();
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
const currentUserSupportPlan = await queryRunner.manager.findOne(this.userSupportPlanRepo.target, {
where: { user: { id: userId } },
relations: { supportPlan: true },
});
if (!currentUserSupportPlan) throw new BadRequestException(SupportPlanMessage.USER_HAS_NO_SUPPORT_PLAN);
const newSupportPlan = await queryRunner.manager.findOne(this.supportPlanRepo.target, {
where: { id: newPlanId },
});
if (!newSupportPlan) throw new BadRequestException(SupportPlanMessage.SUPPORT_PLAN_NOT_FOUND);
const newSupportPlanPrice = new Decimal(newSupportPlan.price);
const currentUserSupportPlanPrice = new Decimal(currentUserSupportPlan.supportPlan.price);
// check if new plan is actually an upgrade
if (newSupportPlanPrice.lessThanOrEqualTo(currentUserSupportPlanPrice)) {
throw new BadRequestException(SupportPlanMessage.DOWN_GRADE_NOT_ALLOWED);
}
// calculate remaining days and price
const remainingDays = dayjs(currentUserSupportPlan.endDate).diff(dayjs(), "day");
const dailyPrice = currentUserSupportPlanPrice.div(currentUserSupportPlan.supportPlan.duration);
const remainingPrice = dailyPrice.mul(remainingDays);
// calculate upgrade price
const upgradePrice = newSupportPlanPrice.sub(remainingPrice).round();
// create new user support plan
const startDate = dayjs().toDate();
const endDate = dayjs().add(newSupportPlan.duration, "day").toDate();
const newUserSupportPlan = queryRunner.manager.create(this.userSupportPlanRepo.target, {
user,
supportPlan: newSupportPlan,
startDate,
endDate,
status: UserSupportPlanStatus.INACTIVE,
});
await queryRunner.manager.save(this.userSupportPlanRepo.target, newUserSupportPlan);
// create invoice for upgrade
const invoiceDueDate = dayjs(startDate).add(INVOICE.DUEDATE, "day").toDate();
const invoice = await this.invoicesService.createInvoiceForSupportPlan(
user,
newSupportPlan,
newUserSupportPlan,
invoiceDueDate,
queryRunner,
upgradePrice.toNumber(),
);
// inactivate old support plan
// currentUserSupportPlan.status = UserSupportPlanStatus.INACTIVE;
await queryRunner.manager.save(this.userSupportPlanRepo.target, currentUserSupportPlan);
if (newSupportPlan.isFree) {
currentUserSupportPlan.status = UserSupportPlanStatus.ACTIVE;
await queryRunner.manager.save(this.userSupportPlanRepo.target, currentUserSupportPlan);
await this.invoicesService.payInvoice(invoice.id, user.id, queryRunner);
}
await queryRunner.commitTransaction();
return {
message: SupportPlanMessage.SUPPORT_PLAN_UPGRADE_INITIATED_SUCCESSFULLY,
invoice,
upgradePrice: upgradePrice.toNumber(),
};
} catch (error) {
await queryRunner.rollbackTransaction();
throw error;
} finally {
await queryRunner.release();
}
}
}