|
|
|
@@ -1,15 +1,15 @@
|
|
|
|
|
import { BadRequestException, Injectable } from "@nestjs/common";
|
|
|
|
|
import Decimal from "decimal.js";
|
|
|
|
|
import { FindOptionsWhere, In, IsNull } from "typeorm";
|
|
|
|
|
|
|
|
|
|
import { ParamDto } from "../../../common/DTO/param.dto";
|
|
|
|
|
import { CategoryMessage, CommonMessage, ServiceMessage } from "../../../common/enums/message.enum";
|
|
|
|
|
import { DiscountCalculationType } from "../../discounts/enums/discount-type.enum";
|
|
|
|
|
import { CategoryMessage, CommonMessage, ServiceMessage, SubscriptionMessage } from "../../../common/enums/message.enum";
|
|
|
|
|
import { SubscriptionStatus } from "../../subscriptions/enums/subscription-status.enum";
|
|
|
|
|
import { PaginationUtils } from "../../utils/providers/pagination.utils";
|
|
|
|
|
import { AddReviewDto } from "../DTO/add-review.dto";
|
|
|
|
|
import { CategoryListSearchQueryDto, CategorySearchQueryDto } from "../DTO/category-search-query.dto";
|
|
|
|
|
import { CreateCategoryDto } from "../DTO/create-category.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 { DanakServiceCategory } from "../entities/danak-service-category.entity";
|
|
|
|
|
import { ReviewStatus } from "../enums/review-status.enum";
|
|
|
|
@@ -178,58 +178,97 @@ export class DanakServicesService {
|
|
|
|
|
}
|
|
|
|
|
/******************************************** */
|
|
|
|
|
|
|
|
|
|
async getDanakServiceByIdWithSubs(serviceId: string, isAdmin: boolean) {
|
|
|
|
|
const danakService = await this.danakServicesRepository.findOne({
|
|
|
|
|
where: { id: serviceId, ...(isAdmin && { isActive: true, subscriptionPlans: { isActive: true } }) },
|
|
|
|
|
relations: { images: true, subscriptionPlans: { discounts: true } },
|
|
|
|
|
});
|
|
|
|
|
async getDanakServiceByIdWithSubs(serviceId: string, isAdmin: boolean, userId: string) {
|
|
|
|
|
const serviceQueryBuilder = this.danakServicesRepository
|
|
|
|
|
.createQueryBuilder("service")
|
|
|
|
|
.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);
|
|
|
|
|
|
|
|
|
|
const transformedSubscriptions = danakService.subscriptionPlans.map((subscription) => {
|
|
|
|
|
let price = new Decimal(subscription.price);
|
|
|
|
|
const averageRatingResult = await this.danakServiceReviewRepository
|
|
|
|
|
.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) {
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
const averageRating = parseFloat(averageRatingResult?.averageRating) || 0;
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
...subscription,
|
|
|
|
|
finalPrice: price.toNumber(),
|
|
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
const hasSubscription = await this.checkUserPurchasedService(userId, serviceId);
|
|
|
|
|
|
|
|
|
|
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 {
|
|
|
|
|
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,
|
|
|
|
|
count,
|
|
|
|
|
paginate: true,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -261,6 +300,18 @@ export class DanakServicesService {
|
|
|
|
|
async addReview(userId: string, serviceId: string, addReviewDto: AddReviewDto) {
|
|
|
|
|
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 });
|
|
|
|
|
await this.danakServiceReviewRepository.save(review);
|
|
|
|
|
return {
|
|
|
|
@@ -311,6 +362,22 @@ export class DanakServicesService {
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|