diff --git a/.gitignore b/.gitignore index 0d22414a..2ad13b91 100644 --- a/.gitignore +++ b/.gitignore @@ -48,4 +48,8 @@ lerna-debug.log* /profile # qr code -/qrcode \ No newline at end of file +/qrcode + + +# task +todo.md \ No newline at end of file diff --git a/apps/e-commerce/src/admin/product-comment/constants.ts b/apps/e-commerce/src/admin/product-comment/constants.ts new file mode 100644 index 00000000..b0d29c9c --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/constants.ts @@ -0,0 +1,2 @@ +export const SCORE_COMMENT_QUEUE = 'SCORE_COMMENT_QUEUE'; +export const SCORE_COMMENT_JOB = 'SCORE_COMMENT_JOB'; diff --git a/apps/e-commerce/src/admin/product-comment/dto/comment-status.dto.ts b/apps/e-commerce/src/admin/product-comment/dto/comment-status.dto.ts new file mode 100644 index 00000000..2bff1f78 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/dto/comment-status.dto.ts @@ -0,0 +1,19 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { I18nTranslations } from 'apps/main/src/generated/i18n.generated'; +import { Type } from 'class-transformer'; +import { IsInt, IsNumber, IsOptional, IsString } from 'class-validator'; +import { i18nValidationMessage } from 'nestjs-i18n'; + +export class CommentStatusDto { + @ApiProperty({ + required: false, + type: IsNumber, + description: 'commentStatusId', + }) + @IsOptional() + @IsInt({ + message: i18nValidationMessage('validation.NUMBER'), + }) + @Type(() => Number) + commentStatusId?: number; +} diff --git a/apps/e-commerce/src/admin/product-comment/dto/confirm-comment.dto.ts b/apps/e-commerce/src/admin/product-comment/dto/confirm-comment.dto.ts new file mode 100644 index 00000000..3a56c311 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/dto/confirm-comment.dto.ts @@ -0,0 +1,7 @@ +import { IsOptional, IsString } from 'class-validator'; + +export class ConfirmCommentDto { + @IsString() + @IsOptional() + description?: string; +} diff --git a/apps/e-commerce/src/admin/product-comment/dto/get-product-comment.dto.ts b/apps/e-commerce/src/admin/product-comment/dto/get-product-comment.dto.ts new file mode 100644 index 00000000..5cf55d80 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/dto/get-product-comment.dto.ts @@ -0,0 +1,8 @@ +import { IntersectionType } from '@nestjs/swagger'; +import { ListFilter } from '@rahino/query-filter'; +import { CommentStatusDto } from './comment-status.dto'; + +export class GetProductCommentDto extends IntersectionType( + ListFilter, + CommentStatusDto, +) {} diff --git a/apps/e-commerce/src/admin/product-comment/dto/index.ts b/apps/e-commerce/src/admin/product-comment/dto/index.ts new file mode 100644 index 00000000..3cc5e482 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/dto/index.ts @@ -0,0 +1,2 @@ +export * from './confirm-comment.dto'; +export * from './get-product-comment.dto'; diff --git a/apps/e-commerce/src/admin/product-comment/processor/index.ts b/apps/e-commerce/src/admin/product-comment/processor/index.ts new file mode 100644 index 00000000..549c2588 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/processor/index.ts @@ -0,0 +1 @@ +export * from './score-comment.processor'; diff --git a/apps/e-commerce/src/admin/product-comment/processor/score-comment.processor.ts b/apps/e-commerce/src/admin/product-comment/processor/score-comment.processor.ts new file mode 100644 index 00000000..34a5b895 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/processor/score-comment.processor.ts @@ -0,0 +1,29 @@ +import { Processor, WorkerHost } from '@nestjs/bullmq'; +import { Job } from 'bullmq'; +import { SCORE_COMMENT_QUEUE } from '../constants'; +import { DBLogger } from '@rahino/logger'; +import { CalculateCommentScoreService } from '../services'; + +@Processor(SCORE_COMMENT_QUEUE) +export class ScoreCommentProcessor extends WorkerHost { + constructor( + private readonly logger: DBLogger, + private readonly calculateCommentScoreService: CalculateCommentScoreService, + ) { + super(); + } + + async process(job: Job, token?: string): Promise { + if (!job.data.commentId) { + return Promise.reject('commentId not provided!'); + } + + const commentId = job.data.commentId; + try { + await this.calculateCommentScoreService.calculateCommentScore(commentId); + } catch (error) { + Promise.reject(error.message); + } + return Promise.resolve(commentId); + } +} diff --git a/apps/e-commerce/src/admin/product-comment/product-comment.controller.ts b/apps/e-commerce/src/admin/product-comment/product-comment.controller.ts new file mode 100644 index 00000000..62e36985 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/product-comment.controller.ts @@ -0,0 +1,92 @@ +import { + ApiBearerAuth, + ApiOperation, + ApiQuery, + ApiTags, +} from '@nestjs/swagger'; +import { + Body, + Controller, + Get, + HttpCode, + HttpStatus, + Param, + Patch, + Query, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { JwtGuard } from '@rahino/auth/guard'; +import { JsonResponseTransformInterceptor } from '@rahino/response/interceptor'; +import { GetUser } from '@rahino/auth/decorator'; +import { User } from '@rahino/database/models/core/user.entity'; +import { PermissionGuard } from '@rahino/permission-checker/guard'; +import { CheckPermission } from '@rahino/permission-checker/decorator'; +import { ConfirmCommentDto, GetProductCommentDto } from './dto'; +import { ProductCommentService } from './product-comment.service'; + +@ApiTags('Product-Comment') +@UseGuards(JwtGuard, PermissionGuard) +@ApiBearerAuth() +@UseInterceptors(JsonResponseTransformInterceptor) +@Controller({ + path: '/api/ecommerce/admin/productComments', + version: ['1'], +}) +export class ProductCommentController { + constructor(private readonly service: ProductCommentService) {} + + @ApiOperation({ description: 'show all product comments' }) + @CheckPermission({ + permissionSymbol: 'ecommerce.admin.productcomments.getall', + }) + @Get('/') + @ApiQuery({ + name: 'filter', + type: GetProductCommentDto, + style: 'deepObject', + explode: true, + }) + @HttpCode(HttpStatus.OK) + async findAll(@GetUser() user: User, @Query() filter: GetProductCommentDto) { + return await this.service.findAll(user, filter); + } + + @ApiOperation({ description: 'show postage orders by given id' }) + @CheckPermission({ + permissionSymbol: 'ecommerce.admin.productcomments.getone', + }) + @Get('/:id') + @HttpCode(HttpStatus.OK) + async findById(@Param('id') entityId: bigint, @GetUser() user: User) { + return await this.service.findById(entityId, user); + } + + @ApiOperation({ description: 'confirm comment by id' }) + @CheckPermission({ + permissionSymbol: 'ecommerce.admin.productcomments.condfirmcomment', + }) + @Patch('/confirmComment/:id') + @HttpCode(HttpStatus.OK) + async confirmComment( + @Param('id') commentId: bigint, + @GetUser() user: User, + @Body() dto: ConfirmCommentDto, + ) { + return await this.service.confirmComment(commentId, user, dto); + } + + @ApiOperation({ description: 'reject comment by id' }) + @CheckPermission({ + permissionSymbol: 'ecommerce.admin.productcomments.rejectcomment', + }) + @Patch('/rejectComment/:id') + @HttpCode(HttpStatus.OK) + async rejectComment( + @Param('id') commentId: bigint, + @GetUser() user: User, + @Body() dto: ConfirmCommentDto, + ) { + return await this.service.rejectComment(commentId, user, dto); + } +} diff --git a/apps/e-commerce/src/admin/product-comment/product-comment.module.ts b/apps/e-commerce/src/admin/product-comment/product-comment.module.ts new file mode 100644 index 00000000..f5df4ce9 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/product-comment.module.ts @@ -0,0 +1,46 @@ +import { Module } from '@nestjs/common'; +import { SequelizeModule } from '@nestjs/sequelize'; +import { User } from '@rahino/database/models/core/user.entity'; +import { Permission } from '@rahino/database/models/core/permission.entity'; +import { ECProductComment } from '@rahino/database/models/ecommerce-eav/ec-product-comment.entity'; +import { ProductCommentController } from './product-comment.controller'; +import { ProductCommentService } from './product-comment.service'; +import { BullModule } from '@nestjs/bullmq'; +import { SCORE_COMMENT_QUEUE } from './constants'; +import { ConfigService } from '@nestjs/config'; +import { CalculateCommentScoreService } from './services'; +import { ScoreCommentProcessor } from './processor'; +import { ECProductCommentFactor } from '@rahino/database/models/ecommerce-eav/ec-product-comment-factor.entity'; +import { ECProduct } from '@rahino/database/models/ecommerce-eav/ec-product.entity'; +import { DBLoggerModule } from '@rahino/logger'; + +@Module({ + imports: [ + BullModule.registerQueueAsync({ + name: SCORE_COMMENT_QUEUE, + inject: [ConfigService], + useFactory: (config: ConfigService) => ({ + connection: { + host: config.get('REDIS_ADDRESS'), + port: config.get('REDIS_PORT'), + password: config.get('REDIS_PASSWORD'), + }, + }), + }), + SequelizeModule.forFeature([ + User, + Permission, + ECProductComment, + ECProductCommentFactor, + ECProduct, + ]), + DBLoggerModule, + ], + controllers: [ProductCommentController], + providers: [ + ProductCommentService, + CalculateCommentScoreService, + ScoreCommentProcessor, + ], +}) +export class AdminProductCommentModule {} diff --git a/apps/e-commerce/src/admin/product-comment/product-comment.service.ts b/apps/e-commerce/src/admin/product-comment/product-comment.service.ts new file mode 100644 index 00000000..08f95784 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/product-comment.service.ts @@ -0,0 +1,262 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { InjectModel } from '@nestjs/sequelize'; +import { User } from '@rahino/database/models/core/user.entity'; +import { ConfirmCommentDto, GetProductCommentDto } from './dto'; +import { QueryOptionsBuilder } from '@rahino/query-filter/sequelize-query-builder'; +import { Sequelize } from 'sequelize'; +import { Op } from 'sequelize'; +import { I18nContext, I18nService } from 'nestjs-i18n'; +import { I18nTranslations } from 'apps/main/src/generated/i18n.generated'; +import { ECProductComment } from '@rahino/database/models/ecommerce-eav/ec-product-comment.entity'; +import { ECProductCommentStatus } from '@rahino/database/models/ecommerce-eav/ec-comment-status.entity'; +import { ECProductCommentFactor } from '@rahino/database/models/ecommerce-eav/ec-product-comment-factor.entity'; +import { ECEntityTypeFactor } from '@rahino/database/models/ecommerce-eav/ec-entitytype-factor.entity'; +import { ProductCommentStatusEnum } from '@rahino/ecommerce/util/enum/product-comment-status.enum'; +import { SCORE_COMMENT_JOB, SCORE_COMMENT_QUEUE } from './constants'; +import { InjectQueue } from '@nestjs/bullmq'; +import { Queue } from 'bullmq'; + +@Injectable() +export class ProductCommentService { + constructor( + @InjectModel(ECProductComment) + private readonly repository: typeof ECProductComment, + private readonly i18n: I18nService, + + @InjectQueue(SCORE_COMMENT_QUEUE) + private scoreCommentQueue: Queue, + ) {} + + async findAll(user: User, filter: GetProductCommentDto) { + let builder = new QueryOptionsBuilder().filter( + Sequelize.where( + Sequelize.fn('isnull', Sequelize.col('ECProductComment.isDeleted'), 0), + { + [Op.eq]: 0, + }, + ), + ); + if (filter.commentStatusId) { + builder = builder.filter({ statusId: filter.commentStatusId }); + } + + const count = await this.repository.count(builder.build()); + + builder = builder + .attributes([ + 'id', + 'description', + 'statusId', + 'userId', + 'replyId', + 'createdAt', + 'updatedAt', + ]) + .include([ + { + attributes: ['id', 'title'], + model: ECProductCommentStatus, + as: 'status', + required: false, + }, + { + attributes: ['id', 'firstname', 'lastname'], + model: User, + as: 'user', + required: false, + }, + { + attributes: ['id', 'description', 'entityId', 'statusId'], + model: ECProductComment, + as: 'reply', + required: false, + }, + { + attributes: ['id', 'commentId', 'entityId', 'factorId', 'score'], + model: ECProductCommentFactor, + as: 'commentFactors', + required: false, + include: [ + { + attributes: ['id', 'name'], + model: ECEntityTypeFactor, + as: 'factor', + required: false, + }, + ], + }, + ]) + .offset(filter.offset) + .limit(filter.limit) + .order({ orderBy: filter.orderBy, sortOrder: filter.sortOrder }); + + const result = await this.repository.findAll(builder.build()); + return { + result: result, + total: count, + }; + } + + async findById(id: bigint, user: User) { + let builder = new QueryOptionsBuilder() + .filter( + Sequelize.where( + Sequelize.fn( + 'isnull', + Sequelize.col('ECProductComment.isDeleted'), + 0, + ), + { + [Op.eq]: 0, + }, + ), + ) + .filter({ id: id }) + .attributes([ + 'id', + 'description', + 'statusId', + 'userId', + 'replyId', + 'createdAt', + 'updatedAt', + ]) + .include([ + { + attributes: ['id', 'title'], + model: ECProductCommentStatus, + as: 'status', + required: false, + }, + { + attributes: ['id', 'firstname', 'lastname'], + model: User, + as: 'user', + required: false, + }, + { + attributes: ['id', 'description', 'entityId', 'statusId'], + model: ECProductComment, + as: 'reply', + required: false, + }, + { + attributes: ['id', 'commentId', 'entityId', 'factorId', 'score'], + model: ECProductCommentFactor, + as: 'commentFactors', + required: false, + include: [ + { + attributes: ['id', 'name'], + model: ECEntityTypeFactor, + as: 'factor', + required: false, + }, + ], + }, + ]); + + const result = await this.repository.findOne(builder.build()); + + if (!result) { + throw new NotFoundException( + this.i18n.t('core.not_found_id', { + lang: I18nContext.current().lang, + }), + ); + } + + return { + result: result, + }; + } + + async rejectComment(commentId: bigint, user: User, dto: ConfirmCommentDto) { + let comment = await this.repository.findOne( + new QueryOptionsBuilder() + .filter({ id: commentId }) + .filter( + Sequelize.where( + Sequelize.fn( + 'isnull', + Sequelize.col('ECProductComment.isDeleted'), + 0, + ), + { + [Op.eq]: 0, + }, + ), + ) + .build(), + ); + if (!comment) { + throw new NotFoundException( + this.i18n.t('core.not_found_id', { + lang: I18nContext.current().lang, + }), + ); + } + + comment.isDeleted = true; + comment = await comment.save(); + + return { + result: comment, + }; + } + + async confirmComment(commentId: bigint, user: User, dto: ConfirmCommentDto) { + let comment = await this.repository.findOne( + new QueryOptionsBuilder() + .filter({ id: commentId }) + .filter( + Sequelize.where( + Sequelize.fn( + 'isnull', + Sequelize.col('ECProductComment.isDeleted'), + 0, + ), + { + [Op.eq]: 0, + }, + ), + ) + .build(), + ); + if (!comment) { + throw new NotFoundException( + this.i18n.t('core.not_found_id', { + lang: I18nContext.current().lang, + }), + ); + } + + if (dto.description) { + // create admin reply comment + await this.repository.create({ + replyId: comment.id, + description: dto.description, + userId: user.id, + statusId: ProductCommentStatusEnum.adminReply, + }); + } + + // TODO confirm date + + comment.statusId = ProductCommentStatusEnum.confirm; + comment = await comment.save(); + + // calculate score comment + await this.scoreCommentQueue.add( + SCORE_COMMENT_JOB, + { + commentId: comment.id, + }, + { removeOnComplete: true }, + ); + + return { + result: comment, + }; + } +} diff --git a/apps/e-commerce/src/admin/product-comment/services/calculate-comment-score.service.ts b/apps/e-commerce/src/admin/product-comment/services/calculate-comment-score.service.ts new file mode 100644 index 00000000..5ad9a29c --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/services/calculate-comment-score.service.ts @@ -0,0 +1,104 @@ +import { Injectable, InternalServerErrorException } from '@nestjs/common'; +import { InjectModel } from '@nestjs/sequelize'; +import { ECProductCommentFactor } from '@rahino/database/models/ecommerce-eav/ec-product-comment-factor.entity'; +import { ECProductComment } from '@rahino/database/models/ecommerce-eav/ec-product-comment.entity'; +import { ECProduct } from '@rahino/database/models/ecommerce-eav/ec-product.entity'; +import { ProductCommentStatusEnum } from '@rahino/ecommerce/util/enum'; +import { QueryOptionsBuilder } from '@rahino/query-filter/sequelize-query-builder'; +import { Op } from 'sequelize'; +import { Sequelize } from 'sequelize'; + +@Injectable() +export class CalculateCommentScoreService { + constructor( + @InjectModel(ECProductComment) + private readonly repository: typeof ECProductComment, + @InjectModel(ECProductCommentFactor) + private readonly factorRepository: typeof ECProductCommentFactor, + @InjectModel(ECProduct) + private readonly productRepository: typeof ECProduct, + ) {} + + async calculateCommentScore(commentId: bigint) { + let comment = await this.repository.findOne( + new QueryOptionsBuilder() + .filter({ id: commentId }) + .filter({ statusId: ProductCommentStatusEnum.confirm }) + .filter( + Sequelize.where( + Sequelize.fn( + 'isnull', + Sequelize.col('ECProductComment.isDeleted'), + 0, + ), + { + [Op.eq]: 0, + }, + ), + ) + .build(), + ); + if (!comment) { + throw new InternalServerErrorException( + 'the comment with this given id not founded!', + ); + } + // calculate score of factor, otherwise set 5 as default + const result = await this.factorRepository.findOne( + new QueryOptionsBuilder() + .attributes([ + [ + Sequelize.fn( + 'isnull', + Sequelize.fn( + 'avg', + Sequelize.col('ECProductCommentFactor.score'), + ), + 5, + ), + 'score', + ], + [Sequelize.fn('count', '*'), 'cntFactor'], + ]) + .filter({ commentId: comment.id }) + .raw(true) + .build(), + ); + comment.score = result['score']; + comment.cntFactor = result['cntFactor']; + comment = await comment.save(); + return await this.calcualteProductCommentScore(comment.entityId); + } + + async calcualteProductCommentScore(productId: bigint) { + let product = await this.productRepository.findOne( + new QueryOptionsBuilder().filter({ id: productId }).build(), + ); + if (!product) { + throw new InternalServerErrorException( + 'the product with this given id not founded!', + ); + } + const comment = await this.repository.findOne( + new QueryOptionsBuilder() + .attributes([ + [ + Sequelize.fn( + 'isnull', + Sequelize.fn('avg', Sequelize.col('ECProductComment.score')), + 5, + ), + 'score', + ], + [Sequelize.fn('count', '*'), 'cntComment'], + ]) + .filter({ entityId: product.id }) + .filter({ statusId: ProductCommentStatusEnum.confirm }) + .raw(true) + .build(), + ); + product.score = comment['score']; + product.cntComment = comment['cntComment']; + return await product.save(); + } +} diff --git a/apps/e-commerce/src/admin/product-comment/services/index.ts b/apps/e-commerce/src/admin/product-comment/services/index.ts new file mode 100644 index 00000000..fb18aa51 --- /dev/null +++ b/apps/e-commerce/src/admin/product-comment/services/index.ts @@ -0,0 +1 @@ +export * from './calculate-comment-score.service'; diff --git a/apps/e-commerce/src/e-commerce.module.ts b/apps/e-commerce/src/e-commerce.module.ts index 1940441e..746b2b54 100644 --- a/apps/e-commerce/src/e-commerce.module.ts +++ b/apps/e-commerce/src/e-commerce.module.ts @@ -67,6 +67,7 @@ import { InventoryReportModule } from './report/inventory/inventory-report.modul import { InventoryStatusModule } from './admin/inventory-status/inventory-status.module'; import { EntityTypeFactorModule } from './admin/entity-type-factor/entity-type-factor.module'; import { ProductCommentModule } from './product-comment/product-comment.module'; +import { AdminProductCommentModule } from './admin/product-comment/product-comment.module'; @Module({ imports: [ @@ -136,6 +137,7 @@ import { ProductCommentModule } from './product-comment/product-comment.module'; InventoryStatusModule, EntityTypeFactorModule, ProductCommentModule, + AdminProductCommentModule, ], providers: [ { diff --git a/apps/e-commerce/src/product/product.module.ts b/apps/e-commerce/src/product/product.module.ts index a3c023b1..d1a7bbc8 100644 --- a/apps/e-commerce/src/product/product.module.ts +++ b/apps/e-commerce/src/product/product.module.ts @@ -44,6 +44,7 @@ import { EAVEntityType } from '@rahino/database/models/eav/eav-entity-type.entit ECInventoryStatus, EAVEntityType, ]), + SequelizeModule, ], controllers: [ProductController], providers: [ diff --git a/apps/e-commerce/src/util/enum/product-comment-status.enum.ts b/apps/e-commerce/src/util/enum/product-comment-status.enum.ts index e3b48588..cf1739b6 100644 --- a/apps/e-commerce/src/util/enum/product-comment-status.enum.ts +++ b/apps/e-commerce/src/util/enum/product-comment-status.enum.ts @@ -1,4 +1,5 @@ export enum ProductCommentStatusEnum { confirm = 1, waiting = 2, + adminReply = 3, } diff --git a/apps/main/src/sql/core-v1.sql b/apps/main/src/sql/core-v1.sql index 0d6c2dde..1c1b7ef1 100644 --- a/apps/main/src/sql/core-v1.sql +++ b/apps/main/src/sql/core-v1.sql @@ -3659,6 +3659,27 @@ END GO +-- ec-productcomments-v1 +IF NOT EXISTS (SELECT 1 FROM Migrations WHERE version = 'ec-productcomments-v2' + ) + AND EXISTS ( + SELECT 1 FROM Settings + WHERE ([key] = 'SITE_NAME' AND [value] IN ('ecommerce')) + ) +BEGIN + + ALTER TABLE ECProductComments + ADD score float(53) NULL, + cntFactor int null + + + INSERT INTO Migrations(version, createdAt, updatedAt) + SELECT 'ec-productcomments-v2', GETDATE(), GETDATE() +END + +GO + + -- ec-productcommentfactors-v1 IF NOT EXISTS (SELECT 1 FROM Migrations WHERE version = 'ec-productcommentfactors-v1' ) diff --git a/apps/main/src/sql/table-to-model.sql b/apps/main/src/sql/table-to-model.sql index 20da71d3..feaf8875 100644 --- a/apps/main/src/sql/table-to-model.sql +++ b/apps/main/src/sql/table-to-model.sql @@ -1,5 +1,5 @@ declare @Result nvarchar(max) = '' -declare @TableName nvarchar(max) = 'ECPaymentGatewayCommissions' +declare @TableName nvarchar(max) = 'ECProductCommentFactors' SELECT @Result = @Result + '@Column({' diff --git a/libs/database/src/models/ecommerce-eav/ec-product-comment.entity.ts b/libs/database/src/models/ecommerce-eav/ec-product-comment.entity.ts index 46a475c2..e50e6d7f 100644 --- a/libs/database/src/models/ecommerce-eav/ec-product-comment.entity.ts +++ b/libs/database/src/models/ecommerce-eav/ec-product-comment.entity.ts @@ -80,4 +80,15 @@ export class ECProductComment extends Model { foreignKey: 'commentId', }) commentFactors?: ECProductCommentFactor[]; + + @Column({ + type: DataType.FLOAT, + allowNull: true, + }) + score?: number; + @Column({ + type: DataType.INTEGER, + allowNull: true, + }) + cntFactor?: number; } diff --git a/libs/database/src/models/ecommerce-eav/ec-product.entity.ts b/libs/database/src/models/ecommerce-eav/ec-product.entity.ts index 6d849834..f43ec2c8 100644 --- a/libs/database/src/models/ecommerce-eav/ec-product.entity.ts +++ b/libs/database/src/models/ecommerce-eav/ec-product.entity.ts @@ -194,4 +194,15 @@ export class ECProduct extends Model { allowNull: true, }) weight?: number; + + @Column({ + type: DataType.FLOAT, + allowNull: true, + }) + score?: number; + @Column({ + type: DataType.INTEGER, + allowNull: true, + }) + cntComment?: number; }