Skip to content

Commit

Permalink
chore: update API create review/ add review in order items of customer
Browse files Browse the repository at this point in the history
  • Loading branch information
MinhhTien committed Jun 19, 2024
1 parent a40e131 commit f255110
Show file tree
Hide file tree
Showing 7 changed files with 115 additions and 36 deletions.
7 changes: 6 additions & 1 deletion src/common/contracts/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ export const Errors: Record<string, ErrorResponse> = {
message: 'Đơn hàng không hợp lệ.',
httpStatus: HttpStatus.BAD_REQUEST
},
ORDER_ITEM_NOT_FOUND: {
error: 'ORDER_ITEM_NOT_FOUND',
message: 'Sản phẩm không tồn tại trong đơn hàng.',
httpStatus: HttpStatus.BAD_REQUEST
},
STAFF_NOT_FOUND: {
error: 'STAFF_NOT_FOUND',
message: 'Không tìm thấy nhân viên.',
Expand Down Expand Up @@ -133,7 +138,7 @@ export const Errors: Record<string, ErrorResponse> = {
},
REVIEW_ALREADY_EXIST: {
error: 'REVIEW_ALREADY_EXIST',
message: 'Bạn đã review sản phẩm này.',
message: 'Bạn đã review sản phẩm trong đơn hàng này.',
httpStatus: HttpStatus.BAD_REQUEST
},
}
4 changes: 4 additions & 0 deletions src/order/schemas/order.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { IsEmail, IsNotEmpty, IsPhoneNumber, MaxLength, ValidateNested } from 'c
import { Product } from '@product/schemas/product.schema'
import { CreateOrderItemDto } from '@order/dto/order.dto'
import { Payment } from '@payment/schemas/payment.schema'
import { Review } from '@review/schemas/review.schema'

export class CustomerOrderDto {
_id?: string
Expand Down Expand Up @@ -68,6 +69,9 @@ export class OrderItemDto extends CreateOrderItemDto {

@ApiProperty()
quantity: number

@ApiProperty()
review?: Review | string
}

export type OrderDocument = HydratedDocument<Order>
Expand Down
9 changes: 8 additions & 1 deletion src/order/services/order.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { CreateMomoPaymentResponse, QueryMomoPaymentDto } from '@payment/dto/mom
import { ConfigService } from '@nestjs/config'
import { MailerService } from '@nestjs-modules/mailer'
import { CheckoutRequestType, CheckoutResponseDataType, PaymentLinkDataType } from '@payos/node/lib/type'
import { Review } from '@review/schemas/review.schema'

@Injectable()
export class OrderService {
Expand Down Expand Up @@ -63,7 +64,13 @@ export class OrderService {
$ne: OrderStatus.DELETED
}
},
projection: '+items'
projection: '+items',
populates: [
{
path: 'items.review',
model: Review.name
}
]
})
if (!order) throw new AppException(Errors.ORDER_NOT_FOUND)

Expand Down
5 changes: 5 additions & 0 deletions src/review/dtos/review.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ export class CreateReviewDto {
@IsMongoId()
productId: string

@ApiProperty()
@IsNotEmpty()
@IsMongoId()
orderId: string

@ApiProperty()
@IsNotEmpty()
@Max(5)
Expand Down
4 changes: 3 additions & 1 deletion src/review/review.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import { ReviewController } from './controllers/customer.review.controller'
import { ReviewService } from './services/review.service'
import { ReviewRepository } from './repositories/review.repository'
import { ProductModule } from '@product/product.module'
import { OrderModule } from '@order/order.module'

@Global()
@Module({
imports: [
MongooseModule.forFeature([{ name: Review.name, schema: ReviewSchema }]),
HttpModule,
CustomerModule,
ProductModule
ProductModule,
OrderModule
],
controllers: [ReviewController],
providers: [ReviewService, ReviewRepository],
Expand Down
4 changes: 4 additions & 0 deletions src/review/schemas/review.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { ApiProperty } from '@nestjs/swagger'
import { Customer } from '@customer/schemas/customer.schema'
import { Product } from '@product/schemas/product.schema'
import { ReviewStatus } from '@common/contracts/constant'
import { Order } from '@order/schemas/order.schema'

export type ReviewDocument = HydratedDocument<Review>

Expand Down Expand Up @@ -34,6 +35,9 @@ export class Review {
@Prop({ type: Types.ObjectId, ref: Product.name })
product: Product;

@Prop({ type: Types.ObjectId, ref: Order.name, select: false })
order: Types.ObjectId;

@ApiProperty()
@Prop({ type: Number })
rate: number
Expand Down
118 changes: 85 additions & 33 deletions src/review/services/review.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,23 @@ import { Review } from '@review/schemas/review.schema'
import { PaginationParams } from '@common/decorators/pagination.decorator'
import { CreateReviewDto } from '@review/dtos/review.dto'
import { ProductRepository } from '@product/repositories/product.repository'
import { ProductStatus } from '@common/contracts/constant'
import { OrderStatus, ProductStatus, TransactionStatus } from '@common/contracts/constant'
import { AppException } from '@common/exceptions/app.exception'
import { Errors } from '@common/contracts/error'
import { SuccessResponse } from '@common/contracts/dto'
import { pick } from 'lodash'
import { OrderRepository } from '@order/repositories/order.repository'
import { Connection } from 'mongoose'
import { InjectConnection } from '@nestjs/mongoose'

@Injectable()
export class ReviewService {
private readonly logger = new Logger(ReviewService.name)
constructor(
@InjectConnection() readonly connection: Connection,
private readonly reviewRepository: ReviewRepository,
private readonly productRepository: ProductRepository
private readonly productRepository: ProductRepository,
private readonly orderRepository: OrderRepository
) {}

public async getReviewList(filter: FilterQuery<Review>, paginationParams: PaginationParams) {
Expand Down Expand Up @@ -53,6 +58,18 @@ export class ReviewService {

public async createReview(createReviewDto: CreateReviewDto) {
// 1. Check if customer has completed order product
const order = await this.orderRepository.findOne({
conditions: {
_id: createReviewDto.orderId,
'customer._id': createReviewDto.customerId
},
projection: '+items'
})
if (!order) throw new AppException(Errors.ORDER_NOT_FOUND)
// if (order.orderStatus !== OrderStatus.COMPLETED || order.transactionStatus !== TransactionStatus.CAPTURED)
// throw new AppException(Errors.ORDER_STATUS_INVALID)
const orderItemIndex = order.items.findIndex((item) => item.productId.toString() === createReviewDto.productId)
if (orderItemIndex === -1) throw new AppException(Errors.ORDER_ITEM_NOT_FOUND)

// 2. Save review
// 2.1 Check valid product
Expand All @@ -67,47 +84,82 @@ export class ReviewService {
if (!product) throw new AppException(Errors.PRODUCT_NOT_FOUND)

// 2.2 Check already review
const review = await this.reviewRepository.findOne({
const existedReview = await this.reviewRepository.findOne({
conditions: {
customer: createReviewDto.customerId,
product: createReviewDto.productId
product: createReviewDto.productId,
order: createReviewDto.orderId
}
})
if (review) throw new AppException(Errors.REVIEW_ALREADY_EXIST)
if (existedReview) throw new AppException(Errors.REVIEW_ALREADY_EXIST)

await this.reviewRepository.create({
...createReviewDto,
customer: createReviewDto.customerId,
product: createReviewDto.productId
})

// 3. Update rating product
const rateSummary = await this.reviewRepository.model.aggregate([
{
$group: {
_id: '$product',
avgRating: { $avg: '$rate' },
1: { $sum: { $cond: [{ $eq: ['$rate', 1] }, 1, 0] } },
2: { $sum: { $cond: [{ $eq: ['$rate', 2] }, 1, 0] } },
3: { $sum: { $cond: [{ $eq: ['$rate', 3] }, 1, 0] } },
4: { $sum: { $cond: [{ $eq: ['$rate', 4] }, 1, 0] } },
5: { $sum: { $cond: [{ $eq: ['$rate', 5] }, 1, 0] } }
// Execute in transaction
const session = await this.connection.startSession()
session.startTransaction()
try {
// 2.3 Save review
const review = await this.reviewRepository.create(
{
...createReviewDto,
customer: createReviewDto.customerId,
product: createReviewDto.productId,
order: createReviewDto.orderId
},
{
session
}
},
{ $project: { roundedAvgRating: { $round: ['$avgRating', 1] }, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1 } }
])
)

// 3. Update rating product
const rateSummary = await this.reviewRepository.model.aggregate([
{
$group: {
_id: '$product',
avgRating: { $avg: '$rate' },
1: { $sum: { $cond: [{ $eq: ['$rate', 1] }, 1, 0] } },
2: { $sum: { $cond: [{ $eq: ['$rate', 2] }, 1, 0] } },
3: { $sum: { $cond: [{ $eq: ['$rate', 3] }, 1, 0] } },
4: { $sum: { $cond: [{ $eq: ['$rate', 4] }, 1, 0] } },
5: { $sum: { $cond: [{ $eq: ['$rate', 5] }, 1, 0] } }
}
},
{ $project: { roundedAvgRating: { $round: ['$avgRating', 1] }, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1 } }
])
if (!!rateSummary[0]?.roundedAvgRating) {
await this.productRepository.findOneAndUpdate(
{ _id: createReviewDto.productId },
{
rate: rateSummary[0]?.roundedAvgRating,
ratingCount: pick(rateSummary[0], ['1', '2', '3', '4', '5'])
},
{
session
}
)
}

this.logger.debug('ReviewService.createReview: ', rateSummary[0])
if (!!rateSummary[0]?.roundedAvgRating) {
await this.productRepository.findOneAndUpdate(
{ _id: createReviewDto.productId },
// 4. Save reviewId to orderItem
order.items[orderItemIndex].review = review._id.toString()
await this.orderRepository.findOneAndUpdate(
{
rate: rateSummary[0]?.roundedAvgRating,
ratingCount: pick(rateSummary[0], ['1', '2', '3', '4', '5'])
_id: createReviewDto.orderId
},
{
$set: {
items: order.items
}
},
{
session
}
)
}

return new SuccessResponse(true)
await session.commitTransaction()
return new SuccessResponse(true)
} catch (error) {
await session.abortTransaction()
console.error(error)
throw error
}
}
}

0 comments on commit f255110

Please sign in to comment.