chore: add user to the invoice

This commit is contained in:
Mahyargdz
2025-05-08 10:24:01 +03:30
parent 9faa75b049
commit 3d915930a7
18 changed files with 27 additions and 80 deletions
-59
View File
@@ -16,64 +16,5 @@ services:
POSTGRES_USER: ${DB_USER} POSTGRES_USER: ${DB_USER}
POSTGRES_DB: ${DB_NAME} POSTGRES_DB: ${DB_NAME}
nestjs:
build: .
ports:
- "3000:3000"
depends_on:
- pg_db
environment:
- NODE_ENV=development
volumes:
- ./src:/app/src
- ./dist:/app/dist
prometheus:
image: prom/prometheus:latest
container_name: prometheus
ports:
- "9090:9090"
volumes:
- ./prometheus:/etc/prometheus
- prometheus_data:/prometheus
command:
- "--config.file=/etc/prometheus/prometheus.yml"
- "--storage.tsdb.path=/prometheus"
- "--web.console.libraries=/usr/share/prometheus/console_libraries"
- "--web.console.templates=/usr/share/prometheus/consoles"
restart: always
grafana:
image: grafana/grafana:latest
container_name: grafana
ports:
- "3001:3000"
volumes:
- grafana_data:/var/lib/grafana
environment:
- GF_SECURITY_ADMIN_USER=admin
- GF_SECURITY_ADMIN_PASSWORD=admin
- GF_USERS_ALLOW_SIGN_UP=false
depends_on:
- prometheus
restart: always
# pgadmin:
# image: dpage/pgadmin4
# restart: always
# container_name: pgadmin
# ports:
# - 5050:80
# environment:
# PGADMIN_DEFAULT_EMAIL: ${PGADMIN_EMAIL}
# PGADMIN_DEFAULT_PASSWORD: ${PGADMIN_PASSWORD}
# depends_on:
# - pg_db
volumes: volumes:
postgres_data: postgres_data:
prometheus_data:
grafana_data:
# networks:
# app_network:
# driver: bridge
+2
View File
@@ -106,6 +106,7 @@ export const enum UserMessage {
INVALID_ADMIN_ID = "شناسه ادمین نامعتبر است", INVALID_ADMIN_ID = "شناسه ادمین نامعتبر است",
ADMIN_ID_SHOULD_BE_UUID = "شناسه ادمین باید یک UUID باشد", ADMIN_ID_SHOULD_BE_UUID = "شناسه ادمین باید یک UUID باشد",
ADMIN_NOT_FOUND = "ادمین یافت نشد", ADMIN_NOT_FOUND = "ادمین یافت نشد",
PHONE_MUST_BE_A_STRING = "شماره تلفن باید یک رشته باشد",
} }
export const enum CommonMessage { export const enum CommonMessage {
@@ -523,6 +524,7 @@ export const enum InvoiceMessage {
COUNT_MUST_BE_POSITIVE = "تعداد باید مثبت باشد", COUNT_MUST_BE_POSITIVE = "تعداد باید مثبت باشد",
DISCOUNT_MUST_BE_BETWEEN_0_AND_100 = "تخفیف باید بین ۰ تا ۱۰۰ باشد", DISCOUNT_MUST_BE_BETWEEN_0_AND_100 = "تخفیف باید بین ۰ تا ۱۰۰ باشد",
TOTAL_PRICE_MUST_BE_POSITIVE = "قیمت کل باید مثبت باشد", TOTAL_PRICE_MUST_BE_POSITIVE = "قیمت کل باید مثبت باشد",
DISCOUNT_NOT_FOR_THIS_USER = "این تخفیف برای شما معتبر نیست",
} }
export const enum LearningMessage { export const enum LearningMessage {
@@ -13,7 +13,7 @@ import {
ValidateIf, ValidateIf,
} from "class-validator"; } from "class-validator";
import { DiscountMessage } from "../../../common/enums/message.enum"; import { DiscountMessage, UserMessage } from "../../../common/enums/message.enum";
import { DiscountType } from "../enums/discount-type.enum"; import { DiscountType } from "../enums/discount-type.enum";
export class CreateDiscountDto { export class CreateDiscountDto {
@@ -74,12 +74,12 @@ export class CreateDiscountDto {
@IsOptional() @IsOptional()
@ValidateIf((o) => !o.direct) @ValidateIf((o) => !o.direct)
@IsUUID("4", { message: DiscountMessage.USER_ID_MUST_BE_A_UUID }) @IsString({ message: UserMessage.PHONE_MUST_BE_A_STRING })
@ApiPropertyOptional({ @ApiPropertyOptional({
description: "The ID of the user this discount is specific to. If not provided, the discount will be available to all users", description: "The phone number of the user this discount is specific to. If not provided, the discount will be available to all users",
example: "123e4567-e89b-12d3-a456-426614174000", example: "09123456789",
}) })
userId?: string; userPhone?: string;
// @IsString() // @IsString()
// @IsOptional() // @IsOptional()
@@ -8,9 +8,9 @@ import { DiscountsService } from "./providers/discounts.service";
import { AdminRoute } from "../../common/decorators/admin.decorator"; import { AdminRoute } from "../../common/decorators/admin.decorator";
import { AuthGuards } from "../../common/decorators/auth-guard.decorator"; import { AuthGuards } from "../../common/decorators/auth-guard.decorator";
import { PermissionsDec } from "../../common/decorators/permission.decorator"; import { PermissionsDec } from "../../common/decorators/permission.decorator";
import { UserDec } from "../../common/decorators/user.decorator";
import { ParamDto } from "../../common/DTO/param.dto"; import { ParamDto } from "../../common/DTO/param.dto";
import { PermissionEnum } from "../users/enums/permission.enum"; import { PermissionEnum } from "../users/enums/permission.enum";
@ApiTags("Discounts") @ApiTags("Discounts")
@Controller("discounts") @Controller("discounts")
@AuthGuards() @AuthGuards()
@@ -70,7 +70,7 @@ export class DiscountsController {
@ApiOperation({ summary: "Get discount list for user" }) @ApiOperation({ summary: "Get discount list for user" })
@Get("user-discounts") @Get("user-discounts")
getDiscountListForUser(@Query() queryDto: SearchDiscountQueryDto) { getDiscountListForUser(@Query() queryDto: SearchDiscountQueryDto, @UserDec("id") userId: string) {
return this.discountsService.getDiscountListForUser(queryDto); return this.discountsService.getDiscountListForUser(queryDto, userId);
} }
} }
@@ -7,10 +7,10 @@ import dayjs from "dayjs";
import { Decimal } from "decimal.js"; import { Decimal } from "decimal.js";
import { DataSource, FindOptionsWhere, QueryRunner } from "typeorm"; import { DataSource, FindOptionsWhere, QueryRunner } from "typeorm";
import { CommonMessage, DiscountMessage } from "../../../common/enums/message.enum"; import { CommonMessage, DiscountMessage, UserMessage } from "../../../common/enums/message.enum";
import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity"; import { SubscriptionPlan } from "../../subscriptions/entities/subscription.entity";
import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service"; import { SubscriptionsService } from "../../subscriptions/providers/subscriptions.service";
import { UsersService } from "../../users/providers/users.service"; import { User } from "../../users/entities/user.entity";
import { DISCOUNT } from "../constants"; import { DISCOUNT } from "../constants";
import { CreateDiscountDto } from "../DTO/create-discount.dto"; import { CreateDiscountDto } from "../DTO/create-discount.dto";
import { SearchDiscountQueryDto } from "../DTO/search-discount-query.dto"; import { SearchDiscountQueryDto } from "../DTO/search-discount-query.dto";
@@ -28,7 +28,6 @@ export class DiscountsService {
@InjectQueue(DISCOUNT.DISCOUNT_QUEUE_NAME) private readonly discountQueue: Queue, @InjectQueue(DISCOUNT.DISCOUNT_QUEUE_NAME) private readonly discountQueue: Queue,
private readonly discountRepository: DiscountRepository, private readonly discountRepository: DiscountRepository,
private readonly subscriptionService: SubscriptionsService, private readonly subscriptionService: SubscriptionsService,
private readonly usersService: UsersService,
private readonly dataSource: DataSource, private readonly dataSource: DataSource,
) {} ) {}
@@ -159,8 +158,8 @@ export class DiscountsService {
} }
} }
//************************************************************ */ //************************************************************ */
async getDiscountListForUser(queryDto: SearchDiscountQueryDto) { async getDiscountListForUser(queryDto: SearchDiscountQueryDto, userId: string) {
const [discounts, count] = await this.discountRepository.findDiscountForUser(queryDto); const [discounts, count] = await this.discountRepository.findDiscountForUser(queryDto, userId);
return { return {
discounts, discounts,
@@ -237,15 +236,19 @@ export class DiscountsService {
//************************************************************ */ //************************************************************ */
private async createDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) { private async createDiscount(createDiscountDto: CreateDiscountDto, queryRunner: QueryRunner) {
const { userId } = createDiscountDto; const { userPhone } = createDiscountDto;
if (userId) { let user: null | User = null;
await this.usersService.findOneByIdWithQueryRunner(userId, queryRunner);
//
if (userPhone) {
user = await queryRunner.manager.findOne(User, { where: { phone: userPhone } });
if (!user) throw new BadRequestException(UserMessage.USER_NOT_FOUND);
} }
const discount = queryRunner.manager.create(this.discountRepository.target, { const discount = queryRunner.manager.create(this.discountRepository.target, {
...createDiscountDto, ...createDiscountDto,
applicationType: createDiscountDto.direct ? DiscountApplicationType.DIRECT : DiscountApplicationType.CODE_BASED, applicationType: createDiscountDto.direct ? DiscountApplicationType.DIRECT : DiscountApplicationType.CODE_BASED,
user: !createDiscountDto.direct && userId ? { id: userId } : null, user: !createDiscountDto.direct && user ? { id: user.id } : null,
}); });
if (!createDiscountDto.direct) { if (!createDiscountDto.direct) {
@@ -93,7 +93,7 @@ export class DiscountRepository extends Repository<Discount> {
return this.softDelete(id); return this.softDelete(id);
} }
//************************************************************ */ //************************************************************ */
async findDiscountForUser(queryDto: SearchDiscountQueryDto, userId?: string) { async findDiscountForUser(queryDto: SearchDiscountQueryDto, userId: string) {
const { limit, skip } = PaginationUtils(queryDto); const { limit, skip } = PaginationUtils(queryDto);
const queryBuilder = this.createQueryBuilder("discount") const queryBuilder = this.createQueryBuilder("discount")
.where("discount.deletedAt IS NULL") .where("discount.deletedAt IS NULL")
@@ -118,9 +118,7 @@ export class DiscountRepository extends Repository<Discount> {
"user.email", "user.email",
]); ]);
if (userId) { queryBuilder.andWhere("(discount.userId = :userId OR discount.userId IS NULL)", { userId });
queryBuilder.andWhere("(discount.userId = :userId OR discount.userId IS NULL)", { userId });
}
if (queryDto.q) { if (queryDto.q) {
queryBuilder.andWhere("discount.title ILIKE :query", { query: `%${queryDto.q}%` }); queryBuilder.andWhere("discount.title ILIKE :query", { query: `%${queryDto.q}%` });
@@ -686,8 +686,11 @@ export class InvoicesService {
const discount = await queryRunner.manager.findOne(Discount, { const discount = await queryRunner.manager.findOne(Discount, {
where: { code: discountCode, isActive: true }, where: { code: discountCode, isActive: true },
relations: { user: true },
}); });
if (discount?.user?.id && discount?.user?.id !== userId) throw new BadRequestException(InvoiceMessage.DISCOUNT_NOT_FOR_THIS_USER);
if (!discount) throw new BadRequestException(InvoiceMessage.INVALID_DISCOUNT_CODE); if (!discount) throw new BadRequestException(InvoiceMessage.INVALID_DISCOUNT_CODE);
if (discount.endDate && dayjs(discount.endDate).isBefore(dayjs())) throw new BadRequestException(InvoiceMessage.DISCOUNT_EXPIRED); if (discount.endDate && dayjs(discount.endDate).isBefore(dayjs())) throw new BadRequestException(InvoiceMessage.DISCOUNT_EXPIRED);