update: add review count and also verification from admin panel
This commit is contained in:
@@ -386,6 +386,7 @@ export const enum SubscriptionMessage {
|
|||||||
BUSINESS_DESCRIPTION_LENGTH = "توضیحات کسب و کار باید بین ۱۰ تا ۵۰۰ کاراکتر باشد",
|
BUSINESS_DESCRIPTION_LENGTH = "توضیحات کسب و کار باید بین ۱۰ تا ۵۰۰ کاراکتر باشد",
|
||||||
BUSINESS_DESCRIPTION_STRING = "توضیحات کسب و کار باید یک رشته باشد",
|
BUSINESS_DESCRIPTION_STRING = "توضیحات کسب و کار باید یک رشته باشد",
|
||||||
IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت اشتراک باید یکی از مقادیر ۰ و ۱ باشد",
|
IS_ACTIVE_SHOULD_BE_1_0 = "وضعیت اشتراک باید یکی از مقادیر ۰ و ۱ باشد",
|
||||||
|
NOT_PURCHASED_CANNOT_REVIEW = "شما این اشتراک را خریداری نکردهاید و نمیتوانید نظر بدهید",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum InvoiceMessage {
|
export const enum InvoiceMessage {
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
import { PaginationDto } from "../../../common/DTO/pagination.dto";
|
||||||
|
|
||||||
|
export class DanakServiceReviewQueryDto extends PaginationDto {}
|
||||||
@@ -5,6 +5,7 @@ import { AddReviewDto } from "./DTO/add-review.dto";
|
|||||||
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "./DTO/category-search-query.dto";
|
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "./DTO/category-search-query.dto";
|
||||||
import { CreateCategoryDto } from "./DTO/create-category.dto";
|
import { CreateCategoryDto } from "./DTO/create-category.dto";
|
||||||
import { CreateServiceDto } from "./DTO/create-service.dto";
|
import { CreateServiceDto } from "./DTO/create-service.dto";
|
||||||
|
import { DanakServiceReviewQueryDto } from "./DTO/danak-service-review-query.dto";
|
||||||
import { DanakServicesSearchQueryDto } from "./DTO/danak-services-search-query.dto";
|
import { DanakServicesSearchQueryDto } from "./DTO/danak-services-search-query.dto";
|
||||||
import { DanakServicesService } from "./providers/danak-services.service";
|
import { DanakServicesService } from "./providers/danak-services.service";
|
||||||
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
|
||||||
@@ -103,8 +104,8 @@ export class DanakServicesController {
|
|||||||
@ApiOperation({ summary: "get danak service by id" })
|
@ApiOperation({ summary: "get danak service by id" })
|
||||||
@AuthGuards()
|
@AuthGuards()
|
||||||
@Get(":id")
|
@Get(":id")
|
||||||
getDanakServiceById(@Param() paramDto: ParamDto, @UserDec("isAdmin") isAdmin: boolean) {
|
getDanakServiceById(@Param() paramDto: ParamDto, @UserDec("isAdmin") isAdmin: boolean, @UserDec("id") userId: string) {
|
||||||
return this.danakServicesService.getDanakServiceByIdWithSubs(paramDto.id, isAdmin);
|
return this.danakServicesService.getDanakServiceByIdWithSubs(paramDto.id, isAdmin, userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiOperation({ summary: "add review to danak service ==> user route" })
|
@ApiOperation({ summary: "add review to danak service ==> user route" })
|
||||||
@@ -114,6 +115,15 @@ export class DanakServicesController {
|
|||||||
return this.danakServicesService.addReview(userId, paramDto.id, addReviewDto);
|
return this.danakServicesService.addReview(userId, paramDto.id, addReviewDto);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiOperation({ summary: "get danak service reviews ==> admin route" })
|
||||||
|
@AuthGuards()
|
||||||
|
@Pagination()
|
||||||
|
@PermissionsDec(PermissionEnum.SERVICES)
|
||||||
|
@Get("reviews")
|
||||||
|
getDanakServiceReviews(@Query() queryDto: DanakServiceReviewQueryDto) {
|
||||||
|
return this.danakServicesService.getDanakServiceReviews(queryDto);
|
||||||
|
}
|
||||||
|
|
||||||
// @AuthGuards()
|
// @AuthGuards()
|
||||||
//
|
//
|
||||||
// @ApiOperation({ summary: "get all user purchased danak services" })
|
// @ApiOperation({ summary: "get all user purchased danak services" })
|
||||||
|
|||||||
@@ -51,6 +51,12 @@ export class DanakService extends BaseEntity {
|
|||||||
@Column({ type: "varchar", length: 255, nullable: false })
|
@Column({ type: "varchar", length: 255, nullable: false })
|
||||||
icon: string;
|
icon: string;
|
||||||
|
|
||||||
|
// @Column({ type: "int", nullable: false })
|
||||||
|
// count: number;
|
||||||
|
|
||||||
|
// @Column({ type: "float", nullable: false })
|
||||||
|
// averageRating: number;
|
||||||
|
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
@ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakServices, {
|
@ManyToOne(() => DanakServiceCategory, (danakServiceCategory) => danakServiceCategory.danakServices, {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
@@ -75,10 +81,4 @@ export class DanakService extends BaseEntity {
|
|||||||
|
|
||||||
@OneToMany(() => DanakServiceReview, (danakServiceReview) => danakServiceReview.service)
|
@OneToMany(() => DanakServiceReview, (danakServiceReview) => danakServiceReview.service)
|
||||||
reviews: DanakServiceReview[];
|
reviews: DanakServiceReview[];
|
||||||
|
|
||||||
// @ManyToMany(() => Discount, (discount) => discount.services, { nullable: true })
|
|
||||||
// discounts?: Discount[];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// @ManyToMany(() => User, (user) => user.danakServices)
|
|
||||||
// users: User[];
|
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
import Decimal from "decimal.js";
|
|
||||||
import { FindOptionsWhere, In, IsNull } from "typeorm";
|
import { FindOptionsWhere, In, IsNull } from "typeorm";
|
||||||
|
|
||||||
import { ParamDto } from "../../../common/DTO/param.dto";
|
import { ParamDto } from "../../../common/DTO/param.dto";
|
||||||
import { CategoryMessage, CommonMessage, ServiceMessage } from "../../../common/enums/message.enum";
|
import { CategoryMessage, CommonMessage, ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum";
|
||||||
import { DiscountCalculationType } from "../../discounts/enums/discount-type.enum";
|
import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum";
|
||||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
import { AddReviewDto } from "../DTO/add-review.dto";
|
import { AddReviewDto } from "../DTO/add-review.dto";
|
||||||
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto";
|
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto";
|
||||||
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
import { CreateCategoryDto } from "../DTO/create-category.dto";
|
||||||
import { CreateServiceDto } from "../DTO/create-service.dto";
|
import { CreateServiceDto } from "../DTO/create-service.dto";
|
||||||
|
import { DanakServiceReviewQueryDto } from "../DTO/danak-service-review-query.dto";
|
||||||
import { DanakServicesSearchQueryDto } from "../DTO/danak-services-search-query.dto";
|
import { DanakServicesSearchQueryDto } from "../DTO/danak-services-search-query.dto";
|
||||||
import { DanakServiceCategory } from "../entities/danak-service-category.entity";
|
import { DanakServiceCategory } from "../entities/danak-service-category.entity";
|
||||||
import { ReviewStatus } from "../enums/review-status.enum";
|
import { ReviewStatus } from "../enums/review-status.enum";
|
||||||
@@ -178,58 +178,97 @@ export class DanakServicesService {
|
|||||||
}
|
}
|
||||||
/******************************************** */
|
/******************************************** */
|
||||||
|
|
||||||
async getDanakServiceByIdWithSubs(serviceId: string, isAdmin: boolean) {
|
async getDanakServiceByIdWithSubs(serviceId: string, isAdmin: boolean, userId: string) {
|
||||||
const danakService = await this.danakServicesRepository.findOne({
|
const serviceQueryBuilder = this.danakServicesRepository
|
||||||
where: { id: serviceId, ...(isAdmin && { isActive: true, subscriptionPlans: { isActive: true } }) },
|
.createQueryBuilder("service")
|
||||||
relations: { images: true, subscriptionPlans: { discounts: true } },
|
.leftJoinAndSelect("service.images", "images")
|
||||||
});
|
.leftJoinAndSelect("service.subscriptionPlans", "subscriptionPlans")
|
||||||
|
.leftJoin("service.reviews", "reviews", "reviews.status = :reviewStatus", { reviewStatus: ReviewStatus.APPROVED })
|
||||||
|
.leftJoin("reviews.user", "user")
|
||||||
|
.addSelect([
|
||||||
|
"reviews.id",
|
||||||
|
"reviews.comment",
|
||||||
|
"reviews.rating",
|
||||||
|
"reviews.title",
|
||||||
|
"reviews.createdAt",
|
||||||
|
"reviews.status",
|
||||||
|
"user.id",
|
||||||
|
"user.firstName",
|
||||||
|
"user.lastName",
|
||||||
|
"user.profilePic",
|
||||||
|
])
|
||||||
|
// .loadRelationCountAndMap("service.reviewCount", "service.reviews")
|
||||||
|
// .addSelect(
|
||||||
|
// (subQuery) =>
|
||||||
|
// subQuery
|
||||||
|
// .select("CAST(AVG(rating) AS DECIMAL(10,2))", "averageRating")
|
||||||
|
// .from(DanakServiceReview, "r")
|
||||||
|
// .where("r.serviceId = service.id")
|
||||||
|
// .andWhere("r.status = :reviewStatus", { reviewStatus: ReviewStatus.APPROVED }),
|
||||||
|
// "averageRating",
|
||||||
|
// )
|
||||||
|
.where("service.id = :serviceId", { serviceId })
|
||||||
|
.groupBy("service.id, images.id, subscriptionPlans.id, reviews.id, user.id");
|
||||||
|
|
||||||
|
if (!isAdmin) {
|
||||||
|
serviceQueryBuilder
|
||||||
|
.andWhere("service.isActive = :isActive", { isActive: true })
|
||||||
|
.andWhere("subscriptionPlans.isActive = :planActive", { planActive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const danakService = await serviceQueryBuilder.getOne();
|
||||||
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
|
if (!danakService) throw new BadRequestException(ServiceMessage.SERVICE_NOT_FOUND_BY_ID);
|
||||||
|
|
||||||
const transformedSubscriptions = danakService.subscriptionPlans.map((subscription) => {
|
const averageRatingResult = await this.danakServiceReviewRepository
|
||||||
let price = new Decimal(subscription.price);
|
.createQueryBuilder("r")
|
||||||
|
.select("CAST(AVG(r.rating) AS DECIMAL(10,2))", "averageRating")
|
||||||
|
.where("r.serviceId = :serviceId", { serviceId })
|
||||||
|
.andWhere("r.status = :reviewStatus", { reviewStatus: ReviewStatus.APPROVED })
|
||||||
|
.getRawOne();
|
||||||
|
|
||||||
if (subscription.discounts && subscription.discounts.length > 0) {
|
const averageRating = parseFloat(averageRatingResult?.averageRating) || 0;
|
||||||
subscription.discounts.forEach((discount) => {
|
|
||||||
if (discount.isActive && new Date() >= discount.startDate && new Date() <= discount.endDate) {
|
|
||||||
if (discount.calculationType === DiscountCalculationType.PERCENTAGE) {
|
|
||||||
const discountAmount = price.mul(discount.amount).div(100);
|
|
||||||
price = price.sub(discountAmount);
|
|
||||||
} else if (discount.calculationType === DiscountCalculationType.FIXED) {
|
|
||||||
price = price.sub(discount.amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
const hasSubscription = await this.checkUserPurchasedService(userId, serviceId);
|
||||||
...subscription,
|
|
||||||
finalPrice: price.toNumber(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
danakService.subscriptionPlans = transformedSubscriptions;
|
|
||||||
|
|
||||||
const reviews = await this.danakServiceReviewRepository.find({
|
|
||||||
where: { service: { id: serviceId }, status: ReviewStatus.APPROVED },
|
|
||||||
relations: { user: true },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
comment: true,
|
|
||||||
rating: true,
|
|
||||||
title: true,
|
|
||||||
createdAt: true,
|
|
||||||
status: true,
|
|
||||||
user: {
|
|
||||||
id: true,
|
|
||||||
firstName: true,
|
|
||||||
lastName: true,
|
|
||||||
profilePic: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return {
|
return {
|
||||||
danakService,
|
danakService,
|
||||||
|
averageRating,
|
||||||
|
purchased: !!hasSubscription,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
/******************************************** */
|
||||||
|
|
||||||
|
async getDanakServiceReviews(queryDto: DanakServiceReviewQueryDto) {
|
||||||
|
const { limit, skip } = PaginationUtils(queryDto);
|
||||||
|
|
||||||
|
const reviewsQueryBuilder = this.danakServiceReviewRepository
|
||||||
|
.createQueryBuilder("review")
|
||||||
|
.leftJoinAndSelect("review.user", "user")
|
||||||
|
.leftJoinAndSelect("review.service", "service")
|
||||||
|
.select([
|
||||||
|
"review.id",
|
||||||
|
"review.title",
|
||||||
|
"review.comment",
|
||||||
|
"review.rating",
|
||||||
|
"review.status",
|
||||||
|
"review.createdAt",
|
||||||
|
"user.id",
|
||||||
|
"user.firstName",
|
||||||
|
"user.lastName",
|
||||||
|
"user.profilePic",
|
||||||
|
"service.id",
|
||||||
|
"service.name",
|
||||||
|
])
|
||||||
|
.orderBy("review.createdAt", "DESC")
|
||||||
|
.skip(skip)
|
||||||
|
.take(limit);
|
||||||
|
|
||||||
|
const [reviews, count] = await reviewsQueryBuilder.getManyAndCount();
|
||||||
|
|
||||||
|
return {
|
||||||
reviews,
|
reviews,
|
||||||
|
count,
|
||||||
|
paginate: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -261,6 +300,18 @@ export class DanakServicesService {
|
|||||||
async addReview(userId: string, serviceId: string, addReviewDto: AddReviewDto) {
|
async addReview(userId: string, serviceId: string, addReviewDto: AddReviewDto) {
|
||||||
const service = await this.findServiceById(serviceId);
|
const service = await this.findServiceById(serviceId);
|
||||||
|
|
||||||
|
const hasSubscription = await this.checkUserPurchasedService(userId, serviceId);
|
||||||
|
if (!hasSubscription) throw new BadRequestException(SubscriptionMessage.NOT_PURCHASED_CANNOT_REVIEW);
|
||||||
|
|
||||||
|
// // Check if user already reviewed this service
|
||||||
|
// const existingReview = await this.danakServiceReviewRepository.findOne({
|
||||||
|
// where: { user: { id: userId }, service: { id: serviceId } },
|
||||||
|
// });
|
||||||
|
|
||||||
|
// if (existingReview) {
|
||||||
|
// throw new BadRequestException(ServiceMessage.ALREADY_REVIEWED);
|
||||||
|
// }
|
||||||
|
|
||||||
const review = this.danakServiceReviewRepository.create({ user: { id: userId }, ...addReviewDto, service });
|
const review = this.danakServiceReviewRepository.create({ user: { id: userId }, ...addReviewDto, service });
|
||||||
await this.danakServiceReviewRepository.save(review);
|
await this.danakServiceReviewRepository.save(review);
|
||||||
return {
|
return {
|
||||||
@@ -311,6 +362,22 @@ export class DanakServicesService {
|
|||||||
|
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/******************************************** */
|
/******************************************** */
|
||||||
|
|
||||||
|
private async checkUserPurchasedService(userId: string, serviceId: string) {
|
||||||
|
const subscription = await this.danakServicesRepository
|
||||||
|
.createQueryBuilder()
|
||||||
|
.select("1")
|
||||||
|
.from("user_subscription", "us")
|
||||||
|
.innerJoin("subscription_plan", "sp", "us.planId = sp.id")
|
||||||
|
.where("us.userId = :userId", { userId })
|
||||||
|
.andWhere("sp.serviceId = :serviceId", { serviceId })
|
||||||
|
.andWhere(`(us.status = '${SubscriptionStatus.ACTIVE}')`)
|
||||||
|
.andWhere("us.endDate > NOW()")
|
||||||
|
.andWhere("us.startDate < NOW()")
|
||||||
|
.limit(1)
|
||||||
|
.getRawOne();
|
||||||
|
|
||||||
|
return !!subscription;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export class Discount extends BaseEntity {
|
|||||||
@Column({ type: "timestamptz" })
|
@Column({ type: "timestamptz" })
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
|
|
||||||
@ManyToMany(() => SubscriptionPlan, (subscriptionPlan) => subscriptionPlan.discounts, { nullable: true })
|
@ManyToMany(() => SubscriptionPlan, { nullable: true })
|
||||||
@JoinTable()
|
@JoinTable()
|
||||||
subscriptionPlans: SubscriptionPlan[];
|
subscriptionPlans: SubscriptionPlan[];
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { NotificationsService } from "../../notifications/providers/notification
|
|||||||
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
|
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
|
||||||
import { UserSubscription } from "../../subscriptions/entities/user-subscription.entity";
|
import { UserSubscription } from "../../subscriptions/entities/user-subscription.entity";
|
||||||
import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum";
|
import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum";
|
||||||
|
import { User } from "../../users/entities/user.entity";
|
||||||
import { UsersService } from "../../users/providers/users.service";
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
import { OTPService } from "../../utils/providers/otp.service";
|
import { OTPService } from "../../utils/providers/otp.service";
|
||||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
@@ -82,6 +83,7 @@ export class InvoicesService {
|
|||||||
{
|
{
|
||||||
invoiceId: invoice.numericId.toString(),
|
invoiceId: invoice.numericId.toString(),
|
||||||
dueDate: invoice.dueDate,
|
dueDate: invoice.dueDate,
|
||||||
|
createDate: invoice.createdAt,
|
||||||
price: new Decimal(invoice.totalPrice).toNumber(),
|
price: new Decimal(invoice.totalPrice).toNumber(),
|
||||||
userPhone: user.phone,
|
userPhone: user.phone,
|
||||||
userEmail: user.email,
|
userEmail: user.email,
|
||||||
@@ -173,7 +175,7 @@ export class InvoicesService {
|
|||||||
///********************************** */
|
///********************************** */
|
||||||
|
|
||||||
async createInvoiceForSubscription(
|
async createInvoiceForSubscription(
|
||||||
userId: string,
|
user: User,
|
||||||
plan: SubscriptionPlan,
|
plan: SubscriptionPlan,
|
||||||
userSub: UserSubscription,
|
userSub: UserSubscription,
|
||||||
dueDate: Date,
|
dueDate: Date,
|
||||||
@@ -191,12 +193,26 @@ export class InvoicesService {
|
|||||||
];
|
];
|
||||||
const invoice = queryRunner.manager.create(Invoice, {
|
const invoice = queryRunner.manager.create(Invoice, {
|
||||||
totalPrice: plan.price,
|
totalPrice: plan.price,
|
||||||
user: { id: userId },
|
user,
|
||||||
status: InvoiceStatus.PENDING,
|
status: InvoiceStatus.WAIT_PAYMENT,
|
||||||
paidAt: new Date(),
|
paidAt: new Date(),
|
||||||
dueDate,
|
dueDate,
|
||||||
items: invoiceItems,
|
items: invoiceItems,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await this.notificationsService.createInvoiceCreationNotification(
|
||||||
|
user.id,
|
||||||
|
{
|
||||||
|
invoiceId: invoice.numericId.toString(),
|
||||||
|
dueDate: invoice.dueDate,
|
||||||
|
createDate: invoice.createdAt,
|
||||||
|
price: new Decimal(invoice.totalPrice).toNumber(),
|
||||||
|
userPhone: user.phone,
|
||||||
|
userEmail: user.email,
|
||||||
|
items: invoiceItems.map((item) => item.name).join(", "),
|
||||||
|
},
|
||||||
|
queryRunner,
|
||||||
|
);
|
||||||
//
|
//
|
||||||
await queryRunner.manager.save(Invoice, invoice);
|
await queryRunner.manager.save(Invoice, invoice);
|
||||||
return invoice;
|
return invoice;
|
||||||
@@ -285,7 +301,6 @@ export class InvoicesService {
|
|||||||
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
|
const user = await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
|
||||||
|
|
||||||
const invoice = await this.getPendingInvoiceByIdWithQueryRunner(invoiceId, user.id, queryRunner);
|
const invoice = await this.getPendingInvoiceByIdWithQueryRunner(invoiceId, user.id, queryRunner);
|
||||||
console.log(invoice);
|
|
||||||
|
|
||||||
if (invoice.status !== InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_PAID);
|
if (invoice.status !== InvoiceStatus.WAIT_PAYMENT) throw new BadRequestException(InvoiceMessage.INVOICE_CAN_NOT_PAID);
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export interface IInvoiceNotificationData extends IBaseNotificationData {
|
|||||||
price: number;
|
price: number;
|
||||||
items: string;
|
items: string;
|
||||||
dueDate: Date;
|
dueDate: Date;
|
||||||
|
createDate: Date;
|
||||||
invoiceId: string;
|
invoiceId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -137,7 +137,14 @@ export class NotificationsService {
|
|||||||
queryRunner,
|
queryRunner,
|
||||||
);
|
);
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
await this.smsService.sendInvoiceCreationSms(data.userPhone, data.invoiceId, data.price, data.items, dateFormat(data.dueDate));
|
await this.smsService.sendInvoiceCreationSms(
|
||||||
|
data.userPhone,
|
||||||
|
data.invoiceId,
|
||||||
|
data.price,
|
||||||
|
data.items,
|
||||||
|
dateFormat(data.createDate),
|
||||||
|
dateFormat(data.dueDate),
|
||||||
|
);
|
||||||
//send email too
|
//send email too
|
||||||
}
|
}
|
||||||
return this.createNotification({ title: NotificationMessage.INVOICE_CREATION, message, recipientId }, queryRunner);
|
return this.createNotification({ title: NotificationMessage.INVOICE_CREATION, message, recipientId }, queryRunner);
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// eslint-disable-next-line import/no-named-as-default
|
// eslint-disable-next-line import/no-named-as-default
|
||||||
import Decimal from "decimal.js";
|
import Decimal from "decimal.js";
|
||||||
import { Column, Entity, Index, ManyToMany, ManyToOne } from "typeorm";
|
import { Column, Entity, Index, ManyToOne } from "typeorm";
|
||||||
|
|
||||||
import { BaseEntity } from "../../../common/entities/base.entity";
|
import { BaseEntity } from "../../../common/entities/base.entity";
|
||||||
import { DecimalTransformer } from "../../../common/transformers/decimal.transformer";
|
import { DecimalTransformer } from "../../../common/transformers/decimal.transformer";
|
||||||
import { DanakService } from "../../danak-services/entities/danak-service.entity";
|
import { DanakService } from "../../danak-services/entities/danak-service.entity";
|
||||||
import { Discount } from "../../discounts/entities/discount.entity";
|
// import { Discount } from "../../discounts/entities/discount.entity";
|
||||||
|
|
||||||
@Entity()
|
@Entity()
|
||||||
@Index(["service", "name"], { unique: true })
|
@Index(["service", "name"], { unique: true })
|
||||||
@@ -25,6 +25,6 @@ export class SubscriptionPlan extends BaseEntity {
|
|||||||
@ManyToOne(() => DanakService, (danakService) => danakService.subscriptionPlans, { nullable: false, onDelete: "CASCADE" })
|
@ManyToOne(() => DanakService, (danakService) => danakService.subscriptionPlans, { nullable: false, onDelete: "CASCADE" })
|
||||||
service: DanakService;
|
service: DanakService;
|
||||||
|
|
||||||
@ManyToMany(() => Discount, (discount) => discount.subscriptionPlans)
|
// @ManyToMany(() => Discount, (discount) => discount.subscriptionPlans)
|
||||||
discounts: Discount[];
|
// discounts: Discount[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +1,12 @@
|
|||||||
import { BadRequestException, Injectable } from "@nestjs/common";
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
// eslint-disable-next-line import/no-named-as-default
|
|
||||||
import Decimal from "decimal.js";
|
|
||||||
import { DataSource, In } from "typeorm";
|
import { DataSource, In } from "typeorm";
|
||||||
|
|
||||||
import { ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum";
|
import { ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum";
|
||||||
import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
|
import { DanakServicesService } from "../../danak-services/providers/danak-services.service";
|
||||||
import { DiscountCalculationType } from "../../discounts/enums/discount-type.enum";
|
|
||||||
import { InvoicesService } from "../../invoices/providers/invoices.service";
|
import { InvoicesService } from "../../invoices/providers/invoices.service";
|
||||||
import { UsersService } from "../../users/providers/users.service";
|
import { UsersService } from "../../users/providers/users.service";
|
||||||
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
||||||
// import { Wallet } from "../../wallets/entities/wallet.entity";
|
|
||||||
// import { WalletsService } from "../../wallets/providers/wallets.service";
|
|
||||||
import { AddSubscriptionsToServiceDto } from "../DTO/create-subscription.dto";
|
import { AddSubscriptionsToServiceDto } from "../DTO/create-subscription.dto";
|
||||||
import { SearchUserSubsQueryDto } from "../DTO/search-user-subs-query.dto";
|
import { SearchUserSubsQueryDto } from "../DTO/search-user-subs-query.dto";
|
||||||
import { ServiceSubsQueryDto } from "../DTO/service-subs-query.dto";
|
import { ServiceSubsQueryDto } from "../DTO/service-subs-query.dto";
|
||||||
@@ -30,7 +25,6 @@ export class SubscriptionsService {
|
|||||||
private readonly userSubscriptionsRepository: UserSubscriptionsRepository,
|
private readonly userSubscriptionsRepository: UserSubscriptionsRepository,
|
||||||
private readonly invoicesService: InvoicesService,
|
private readonly invoicesService: InvoicesService,
|
||||||
private readonly usersService: UsersService,
|
private readonly usersService: UsersService,
|
||||||
// private readonly walletsService: WalletsService,
|
|
||||||
private readonly danakServices: DanakServicesService,
|
private readonly danakServices: DanakServicesService,
|
||||||
private readonly dataSource: DataSource,
|
private readonly dataSource: DataSource,
|
||||||
) {}
|
) {}
|
||||||
@@ -162,31 +156,9 @@ export class SubscriptionsService {
|
|||||||
|
|
||||||
const [subscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
|
const [subscriptions, count] = await queryBuilder.skip(skip).take(limit).getManyAndCount();
|
||||||
|
|
||||||
const transformedSubscriptions = subscriptions.map((subscription) => {
|
|
||||||
let price = new Decimal(subscription.price);
|
|
||||||
|
|
||||||
if (subscription.discounts && subscription.discounts.length > 0) {
|
|
||||||
subscription.discounts.forEach((discount) => {
|
|
||||||
if (discount.isActive && new Date() >= discount.startDate && new Date() <= discount.endDate) {
|
|
||||||
if (discount.calculationType === DiscountCalculationType.PERCENTAGE) {
|
|
||||||
const discountAmount = price.mul(discount.amount).div(100);
|
|
||||||
price = price.sub(discountAmount);
|
|
||||||
} else if (discount.calculationType === DiscountCalculationType.FIXED) {
|
|
||||||
price = price.sub(discount.amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
...subscription,
|
|
||||||
finalPrice: price.toNumber(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
service,
|
service,
|
||||||
subscriptions: transformedSubscriptions,
|
subscriptions,
|
||||||
count,
|
count,
|
||||||
pagination: true,
|
pagination: true,
|
||||||
};
|
};
|
||||||
@@ -219,7 +191,6 @@ export class SubscriptionsService {
|
|||||||
where: { id: subscribeDto.planId, service: { id: serviceId } },
|
where: { id: subscribeDto.planId, service: { id: serviceId } },
|
||||||
relations: {
|
relations: {
|
||||||
service: true,
|
service: true,
|
||||||
discounts: true,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -237,7 +208,7 @@ export class SubscriptionsService {
|
|||||||
//TODO: need queue handling for notification
|
//TODO: need queue handling for notification
|
||||||
const invoiceDueDate = dayjs(userSubscription.startDate).add(5, "day").toDate();
|
const invoiceDueDate = dayjs(userSubscription.startDate).add(5, "day").toDate();
|
||||||
|
|
||||||
const invoice = await this.invoicesService.createInvoiceForSubscription(userId, plan, userSubscription, invoiceDueDate, queryRunner);
|
const invoice = await this.invoicesService.createInvoiceForSubscription(user, plan, userSubscription, invoiceDueDate, queryRunner);
|
||||||
userSubscription.status = SubscriptionStatus.INACTIVE;
|
userSubscription.status = SubscriptionStatus.INACTIVE;
|
||||||
|
|
||||||
await queryRunner.manager.save(UserSubscription, userSubscription);
|
await queryRunner.manager.save(UserSubscription, userSubscription);
|
||||||
|
|||||||
@@ -39,5 +39,6 @@ export type TemplateParams =
|
|||||||
| "amount"
|
| "amount"
|
||||||
| "newBalance"
|
| "newBalance"
|
||||||
| "reason"
|
| "reason"
|
||||||
|
| "dueDate"
|
||||||
| "subject"
|
| "subject"
|
||||||
| "message";
|
| "message";
|
||||||
|
|||||||
@@ -114,13 +114,14 @@ export class SmsService {
|
|||||||
}
|
}
|
||||||
//************************************************* */
|
//************************************************* */
|
||||||
|
|
||||||
async sendInvoiceCreationSms(mobile: string, invoiceId: string, price: number, items: string, invoiceDate: string) {
|
async sendInvoiceCreationSms(mobile: string, invoiceId: string, price: number, items: string, invoiceDate: string, dueDate: string) {
|
||||||
const smsData: ISmsVerifyBody = {
|
const smsData: ISmsVerifyBody = {
|
||||||
Parameters: [
|
Parameters: [
|
||||||
{ name: "invoiceId", value: invoiceId },
|
{ name: "invoiceId", value: invoiceId },
|
||||||
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
|
{ name: "price", value: Intl.NumberFormat("fa-IR").format(price) },
|
||||||
{ name: "items", value: items },
|
{ name: "items", value: items },
|
||||||
{ name: "invoiceDate", value: invoiceDate },
|
{ name: "invoiceDate", value: invoiceDate },
|
||||||
|
{ name: "dueDate", value: dueDate },
|
||||||
],
|
],
|
||||||
Mobile: mobile,
|
Mobile: mobile,
|
||||||
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_CREATED,
|
TemplateId: this.smsConfigs.SMS_PATTERN_INVOICE_CREATED,
|
||||||
|
|||||||
Reference in New Issue
Block a user