277 lines
11 KiB
TypeScript
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();
|
|
}
|
|
}
|
|
}
|