From 1dd2813a01cfcf99203e5d1e490583b3c1775f86 Mon Sep 17 00:00:00 2001 From: hamid zarghami Date: Wed, 8 Jul 2026 22:23:14 +0330 Subject: [PATCH] get subscription in admin --- src/auth/auth.service.ts | 8 ++--- src/auth/dto/request-otp-response.dto.ts | 3 +- .../dto/request-salon-otp-response.dto.ts | 3 +- src/auth/dto/verify-salon-otp.dto.ts | 8 +---- src/authorization/policies/reserve.policy.ts | 5 ++- src/authorization/policies/stylist.policy.ts | 4 +-- src/common/filters/api-exception.filter.ts | 7 ++-- .../transform-response.interceptor.ts | 17 +++++----- src/customers/customers.service.ts | 3 +- src/dashboard/dashboard.controller.ts | 6 +--- src/dashboard/dashboard.service.ts | 15 +++++++-- .../migrations/1719600000000-InitialSchema.ts | 7 +++- .../migrations/1719700000000-AddStylists.ts | 14 ++++++-- .../migrations/1719800000000-AddServices.ts | 14 ++++++-- .../1720100000000-AddSmsTemplates.ts | 7 +--- ...00000000-ExtendPaymentsForSubscriptions.ts | 11 ++++--- ...900000000-MoveSuggestionsToServiceSkill.ts | 19 +++++++---- src/payments/payments.service.ts | 10 ++---- src/reserves/reserves.controller.ts | 5 +-- src/reserves/reserves.service.ts | 13 ++++---- src/services/dto/create-service.dto.ts | 8 ++++- src/services/entities/service-skill.entity.ts | 4 ++- src/services/services.service.ts | 4 ++- src/sms-templates/sms-templates.service.ts | 5 +-- .../utils/render-sms-template.util.ts | 5 +-- src/stylists/dto/salon-stylist.dto.ts | 5 ++- src/stylists/stylists.controller.ts | 4 ++- src/stylists/stylists.service.ts | 8 ++--- .../salon-subscriptions.controller.ts | 9 ++++++ .../salon-subscriptions.service.ts | 32 +++++++++++++++++-- src/subscriptions/sms-packages.service.ts | 5 +-- .../subscription-plans.service.ts | 8 ++--- .../create-bulk-service-suggestions.dto.ts | 7 +--- src/suggestions/suggestions.service.ts | 24 ++++++++++---- 34 files changed, 188 insertions(+), 119 deletions(-) diff --git a/src/auth/auth.service.ts b/src/auth/auth.service.ts index 61e96cc..88637d9 100644 --- a/src/auth/auth.service.ts +++ b/src/auth/auth.service.ts @@ -102,7 +102,9 @@ export class AuthService { this.configService.get('JWT_REFRESH_EXPIRES_IN') ?? '7d'; } - async requestOtp(mobile: string): Promise<{ message: string; code?: string }> { + async requestOtp( + mobile: string, + ): Promise<{ message: string; code?: string }> { const code = await this.sendOtp(mobile, this.otpExpiresInMinutes); return this.buildOtpRequestResponse(code); } @@ -114,9 +116,7 @@ export class AuthService { return this.requestOtp(mobile); } - async checkSalonMobile( - mobile: string, - ): Promise<{ isRegistered: boolean }> { + async checkSalonMobile(mobile: string): Promise<{ isRegistered: boolean }> { await this.ensureSalonMobileCanProceed(mobile); const isRegistered = await this.isSalonOwnerRegistered(mobile); return { isRegistered }; diff --git a/src/auth/dto/request-otp-response.dto.ts b/src/auth/dto/request-otp-response.dto.ts index e554c22..0aecc5f 100644 --- a/src/auth/dto/request-otp-response.dto.ts +++ b/src/auth/dto/request-otp-response.dto.ts @@ -5,7 +5,8 @@ export class RequestOtpResponseDto { message: string; @ApiPropertyOptional({ - description: 'Only returned in non-production environments when SMS is unavailable', + description: + 'Only returned in non-production environments when SMS is unavailable', example: '12345', }) code?: string; diff --git a/src/auth/dto/request-salon-otp-response.dto.ts b/src/auth/dto/request-salon-otp-response.dto.ts index f7e87cc..cab9533 100644 --- a/src/auth/dto/request-salon-otp-response.dto.ts +++ b/src/auth/dto/request-salon-otp-response.dto.ts @@ -16,7 +16,8 @@ export class RequestSalonOtpResponseDto { expiresInSeconds: number; @ApiPropertyOptional({ - description: 'Only returned in non-production environments when SMS is unavailable', + description: + 'Only returned in non-production environments when SMS is unavailable', example: '12345', }) code?: string; diff --git a/src/auth/dto/verify-salon-otp.dto.ts b/src/auth/dto/verify-salon-otp.dto.ts index 65101d4..f7d0080 100644 --- a/src/auth/dto/verify-salon-otp.dto.ts +++ b/src/auth/dto/verify-salon-otp.dto.ts @@ -1,10 +1,4 @@ -import { - IsOptional, - IsString, - IsUUID, - Length, - Matches, -} from 'class-validator'; +import { IsOptional, IsString, IsUUID, Length, Matches } from 'class-validator'; export class VerifySalonOtpDto { @Matches(/^09\d{9}$/) diff --git a/src/authorization/policies/reserve.policy.ts b/src/authorization/policies/reserve.policy.ts index 9b8ec12..07595db 100644 --- a/src/authorization/policies/reserve.policy.ts +++ b/src/authorization/policies/reserve.policy.ts @@ -10,7 +10,10 @@ import { AuthorizationService } from '../authorization.service'; export class ReservePolicy { constructor(private readonly authorizationService: AuthorizationService) {} - canCreate(user: AuthUser, reserve: Pick): boolean { + canCreate( + user: AuthUser, + reserve: Pick, + ): boolean { if (this.authorizationService.isAdmin(user)) { return true; } diff --git a/src/authorization/policies/stylist.policy.ts b/src/authorization/policies/stylist.policy.ts index af63a11..e000382 100644 --- a/src/authorization/policies/stylist.policy.ts +++ b/src/authorization/policies/stylist.policy.ts @@ -20,9 +20,7 @@ export class StylistPolicy { if (this.authorizationService.isAdmin(user)) { return true; } - return ( - user.role === UserRole.SALON && stylist.salon.ownerId === user.id - ); + return user.role === UserRole.SALON && stylist.salon.ownerId === user.id; } canDelete(user: AuthUser, stylist: Stylist): boolean { diff --git a/src/common/filters/api-exception.filter.ts b/src/common/filters/api-exception.filter.ts index 875f38e..ecb79cc 100644 --- a/src/common/filters/api-exception.filter.ts +++ b/src/common/filters/api-exception.filter.ts @@ -20,7 +20,9 @@ export class ApiExceptionFilter implements ExceptionFilter { const response = ctx.getResponse(); const request = ctx.getRequest(); - if (SWAGGER_PATH_PREFIXES.some((prefix) => request.url.startsWith(prefix))) { + if ( + SWAGGER_PATH_PREFIXES.some((prefix) => request.url.startsWith(prefix)) + ) { if (exception instanceof HttpException) { response.status(exception.getStatus()).json(exception.getResponse()); return; @@ -67,8 +69,7 @@ export class ApiExceptionFilter implements ExceptionFilter { errors = responseMessage.filter( (item): item is string => typeof item === 'string', ); - message = - errors.length > 0 ? 'Validation failed' : 'Bad Request'; + message = errors.length > 0 ? 'Validation failed' : 'Bad Request'; } else if (typeof responseMessage === 'string') { message = responseMessage; } else if (typeof responseBody.error === 'string') { diff --git a/src/common/interceptors/transform-response.interceptor.ts b/src/common/interceptors/transform-response.interceptor.ts index 602865d..9d26aa8 100644 --- a/src/common/interceptors/transform-response.interceptor.ts +++ b/src/common/interceptors/transform-response.interceptor.ts @@ -33,29 +33,30 @@ function isPaginatedResult(value: unknown): value is PaginatedResult { ); } -function isMessageOnlyResponse( - value: unknown, -): value is { message: string } { +function isMessageOnlyResponse(value: unknown): value is { message: string } { return ( typeof value === 'object' && value !== null && 'message' in value && - typeof (value as { message: unknown }).message === 'string' && + typeof value.message === 'string' && Object.keys(value).length === 1 ); } @Injectable() -export class TransformResponseInterceptor - implements NestInterceptor> -{ +export class TransformResponseInterceptor implements NestInterceptor< + T, + ApiSuccessResponse +> { intercept( context: ExecutionContext, next: CallHandler, ): Observable> { const request = context.switchToHttp().getRequest(); - if (SWAGGER_PATH_PREFIXES.some((prefix) => request.url.startsWith(prefix))) { + if ( + SWAGGER_PATH_PREFIXES.some((prefix) => request.url.startsWith(prefix)) + ) { return next.handle() as Observable>; } diff --git a/src/customers/customers.service.ts b/src/customers/customers.service.ts index 33a992d..aba4998 100644 --- a/src/customers/customers.service.ts +++ b/src/customers/customers.service.ts @@ -349,8 +349,7 @@ export class CustomersService { !search || fullName.includes(search) || normalizedMobile.includes(search); - const matchesStatus = - !query.status || customer.status === query.status; + const matchesStatus = !query.status || customer.status === query.status; return matchesSearch && matchesStatus; }); diff --git a/src/dashboard/dashboard.controller.ts b/src/dashboard/dashboard.controller.ts index d4a795a..0e65340 100644 --- a/src/dashboard/dashboard.controller.ts +++ b/src/dashboard/dashboard.controller.ts @@ -1,8 +1,4 @@ -import { - Controller, - Get, - UseGuards, -} from '@nestjs/common'; +import { Controller, Get, UseGuards } from '@nestjs/common'; import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; import { CurrentUser } from '../auth/decorators/current-user.decorator'; import { Permissions } from '../auth/decorators/permissions.decorator'; diff --git a/src/dashboard/dashboard.service.ts b/src/dashboard/dashboard.service.ts index c815370..a9cc14f 100644 --- a/src/dashboard/dashboard.service.ts +++ b/src/dashboard/dashboard.service.ts @@ -1,4 +1,8 @@ -import { ForbiddenException, Injectable, NotFoundException } from '@nestjs/common'; +import { + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { AuthUser } from '../auth/interfaces/auth-user.interface'; @@ -272,11 +276,16 @@ export class DashboardService { }, 0); } - private buildRevenueChart(reserves: Reserve[], referenceDate: Date): number[] { + private buildRevenueChart( + reserves: Reserve[], + referenceDate: Date, + ): number[] { const chart: number[] = []; for (let offset = 11; offset >= 0; offset -= 1) { - const monthDate = this.startOfMonth(this.addMonths(referenceDate, -offset)); + const monthDate = this.startOfMonth( + this.addMonths(referenceDate, -offset), + ); const nextMonth = this.startOfNextMonth(monthDate); chart.push(this.sumIncomeInRange(reserves, monthDate, nextMonth)); } diff --git a/src/database/migrations/1719600000000-InitialSchema.ts b/src/database/migrations/1719600000000-InitialSchema.ts index 331796b..d4ca823 100644 --- a/src/database/migrations/1719600000000-InitialSchema.ts +++ b/src/database/migrations/1719600000000-InitialSchema.ts @@ -1,4 +1,9 @@ -import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'; +import { + MigrationInterface, + QueryRunner, + Table, + TableForeignKey, +} from 'typeorm'; export class InitialSchema1719600000000 implements MigrationInterface { name = 'InitialSchema1719600000000'; diff --git a/src/database/migrations/1719700000000-AddStylists.ts b/src/database/migrations/1719700000000-AddStylists.ts index 8f691ca..320ef17 100644 --- a/src/database/migrations/1719700000000-AddStylists.ts +++ b/src/database/migrations/1719700000000-AddStylists.ts @@ -1,4 +1,9 @@ -import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'; +import { + MigrationInterface, + QueryRunner, + Table, + TableForeignKey, +} from 'typeorm'; export class AddStylists1719700000000 implements MigrationInterface { name = 'AddStylists1719700000000'; @@ -17,7 +22,12 @@ export class AddStylists1719700000000 implements MigrationInterface { }, { name: 'salonId', type: 'uuid' }, { name: 'userId', type: 'uuid', isUnique: true }, - { name: 'avatarUrl', type: 'varchar', length: '500', isNullable: true }, + { + name: 'avatarUrl', + type: 'varchar', + length: '500', + isNullable: true, + }, { name: 'experienceYears', type: 'int', default: 0 }, { name: 'createdAt', type: 'timestamp', default: 'now()' }, { name: 'updatedAt', type: 'timestamp', default: 'now()' }, diff --git a/src/database/migrations/1719800000000-AddServices.ts b/src/database/migrations/1719800000000-AddServices.ts index 4fcabe3..344fd36 100644 --- a/src/database/migrations/1719800000000-AddServices.ts +++ b/src/database/migrations/1719800000000-AddServices.ts @@ -1,4 +1,9 @@ -import { MigrationInterface, QueryRunner, Table, TableForeignKey } from 'typeorm'; +import { + MigrationInterface, + QueryRunner, + Table, + TableForeignKey, +} from 'typeorm'; export class AddServices1719800000000 implements MigrationInterface { name = 'AddServices1719800000000'; @@ -17,7 +22,12 @@ export class AddServices1719800000000 implements MigrationInterface { }, { name: 'title', type: 'varchar', length: '200' }, { name: 'iconUrl', type: 'varchar', length: '500', isNullable: true }, - { name: 'coverUrl', type: 'varchar', length: '500', isNullable: true }, + { + name: 'coverUrl', + type: 'varchar', + length: '500', + isNullable: true, + }, { name: 'createdAt', type: 'timestamp', default: 'now()' }, { name: 'updatedAt', type: 'timestamp', default: 'now()' }, ], diff --git a/src/database/migrations/1720100000000-AddSmsTemplates.ts b/src/database/migrations/1720100000000-AddSmsTemplates.ts index f9324aa..b37905f 100644 --- a/src/database/migrations/1720100000000-AddSmsTemplates.ts +++ b/src/database/migrations/1720100000000-AddSmsTemplates.ts @@ -1,9 +1,4 @@ -import { - MigrationInterface, - QueryRunner, - Table, - TableIndex, -} from 'typeorm'; +import { MigrationInterface, QueryRunner, Table, TableIndex } from 'typeorm'; export class AddSmsTemplates1720100000000 implements MigrationInterface { name = 'AddSmsTemplates1720100000000'; diff --git a/src/database/migrations/1720700000000-ExtendPaymentsForSubscriptions.ts b/src/database/migrations/1720700000000-ExtendPaymentsForSubscriptions.ts index 6498d91..0705ec9 100644 --- a/src/database/migrations/1720700000000-ExtendPaymentsForSubscriptions.ts +++ b/src/database/migrations/1720700000000-ExtendPaymentsForSubscriptions.ts @@ -1,8 +1,11 @@ -import { MigrationInterface, QueryRunner, TableForeignKey, TableIndex } from 'typeorm'; +import { + MigrationInterface, + QueryRunner, + TableForeignKey, + TableIndex, +} from 'typeorm'; -export class ExtendPaymentsForSubscriptions1720700000000 - implements MigrationInterface -{ +export class ExtendPaymentsForSubscriptions1720700000000 implements MigrationInterface { name = 'ExtendPaymentsForSubscriptions1720700000000'; public async up(queryRunner: QueryRunner): Promise { diff --git a/src/database/migrations/1720900000000-MoveSuggestionsToServiceSkill.ts b/src/database/migrations/1720900000000-MoveSuggestionsToServiceSkill.ts index b81c289..5330cbc 100644 --- a/src/database/migrations/1720900000000-MoveSuggestionsToServiceSkill.ts +++ b/src/database/migrations/1720900000000-MoveSuggestionsToServiceSkill.ts @@ -7,9 +7,7 @@ import { TableUnique, } from 'typeorm'; -export class MoveSuggestionsToServiceSkill1720900000000 - implements MigrationInterface -{ +export class MoveSuggestionsToServiceSkill1720900000000 implements MigrationInterface { name = 'MoveSuggestionsToServiceSkill1720900000000'; public async up(queryRunner: QueryRunner): Promise { @@ -26,7 +24,10 @@ export class MoveSuggestionsToServiceSkill1720900000000 fk.columnNames.includes('suggestedServiceId'), ); if (suggestedServiceFk) { - await queryRunner.dropForeignKey('service_suggestions', suggestedServiceFk); + await queryRunner.dropForeignKey( + 'service_suggestions', + suggestedServiceFk, + ); } const uniqueConstraints = table?.uniques ?? []; @@ -36,7 +37,10 @@ export class MoveSuggestionsToServiceSkill1720900000000 const indexes = table?.indices ?? []; for (const index of indexes) { - if (index.columnNames.includes('serviceId') || index.columnNames.includes('serviceSkillId')) { + if ( + index.columnNames.includes('serviceId') || + index.columnNames.includes('serviceSkillId') + ) { await queryRunner.dropIndex('service_suggestions', index); } } @@ -128,7 +132,10 @@ export class MoveSuggestionsToServiceSkill1720900000000 'IDX_service_suggestions_serviceSkillId_isActive_displayPriority', ); await queryRunner.dropColumn('service_suggestions', 'serviceSkillId'); - await queryRunner.dropColumn('service_suggestions', 'suggestedServiceSkillId'); + await queryRunner.dropColumn( + 'service_suggestions', + 'suggestedServiceSkillId', + ); await queryRunner.addColumn( 'service_suggestions', diff --git a/src/payments/payments.service.ts b/src/payments/payments.service.ts index 40b1694..6524699 100644 --- a/src/payments/payments.service.ts +++ b/src/payments/payments.service.ts @@ -202,10 +202,7 @@ export class PaymentsService { }); } - async findByReserveId( - reserveId: string, - user: AuthUser, - ): Promise { + async findByReserveId(reserveId: string, user: AuthUser): Promise { await this.reservesService.findOne(reserveId, user); return this.paymentsRepository.find({ @@ -313,10 +310,7 @@ export class PaymentsService { throw new ForbiddenException(); } - private buildSuccessReturnUrl( - returnUrl: string, - payment: Payment, - ): string { + private buildSuccessReturnUrl(returnUrl: string, payment: Payment): string { return this.buildReturnUrl(returnUrl, { success: 'true', type: payment.type, diff --git a/src/reserves/reserves.controller.ts b/src/reserves/reserves.controller.ts index 23878bf..e71cd08 100644 --- a/src/reserves/reserves.controller.ts +++ b/src/reserves/reserves.controller.ts @@ -53,10 +53,7 @@ export class ReservesController { @Permissions(PermissionCode.RESERVES_READ) @ApiStandardOkResponse(Reserve, { isArray: true }) @Get() - findAll( - @Query() query: FindReservesQueryDto, - @CurrentUser() user: AuthUser, - ) { + findAll(@Query() query: FindReservesQueryDto, @CurrentUser() user: AuthUser) { return this.reservesService.findAll(user, query); } diff --git a/src/reserves/reserves.service.ts b/src/reserves/reserves.service.ts index 2d24381..ece07e6 100644 --- a/src/reserves/reserves.service.ts +++ b/src/reserves/reserves.service.ts @@ -65,7 +65,10 @@ export class ReservesService { throw new ForbiddenException(); } - this.validateTimeRange(createReserveDto.startTime, createReserveDto.endTime); + this.validateTimeRange( + createReserveDto.startTime, + createReserveDto.endTime, + ); this.validateAmounts( createReserveDto.totalAmount, createReserveDto.depositAmount, @@ -319,9 +322,7 @@ export class ReservesService { private validateAmounts(totalAmount: number, depositAmount: number): void { if (depositAmount > totalAmount) { - throw new BadRequestException( - 'depositAmount cannot exceed totalAmount', - ); + throw new BadRequestException('depositAmount cannot exceed totalAmount'); } } @@ -329,9 +330,7 @@ export class ReservesService { return time.length === 5 ? `${time}:00` : time; } - private async validateItems( - items: CreateReserveDto['items'], - ): Promise { + private async validateItems(items: CreateReserveDto['items']): Promise { for (const item of items) { const serviceExists = await this.servicesRepository.existsBy({ id: item.serviceId, diff --git a/src/services/dto/create-service.dto.ts b/src/services/dto/create-service.dto.ts index c87c4be..2f18089 100644 --- a/src/services/dto/create-service.dto.ts +++ b/src/services/dto/create-service.dto.ts @@ -1,4 +1,10 @@ -import { IsNotEmpty, IsOptional, IsString, IsUrl, MaxLength } from 'class-validator'; +import { + IsNotEmpty, + IsOptional, + IsString, + IsUrl, + MaxLength, +} from 'class-validator'; export class CreateServiceDto { @IsString() diff --git a/src/services/entities/service-skill.entity.ts b/src/services/entities/service-skill.entity.ts index 1f6b5c2..4e1e5b2 100644 --- a/src/services/entities/service-skill.entity.ts +++ b/src/services/entities/service-skill.entity.ts @@ -17,7 +17,9 @@ export class ServiceSkill { @Column() serviceId: string; - @ManyToOne(() => Service, (service) => service.skills, { onDelete: 'CASCADE' }) + @ManyToOne(() => Service, (service) => service.skills, { + onDelete: 'CASCADE', + }) @JoinColumn({ name: 'serviceId' }) service: Service; diff --git a/src/services/services.service.ts b/src/services/services.service.ts index 5ee26ff..b347acf 100644 --- a/src/services/services.service.ts +++ b/src/services/services.service.ts @@ -27,7 +27,9 @@ export class ServicesService { create(createServiceDto: CreateServiceDto, user: AuthUser): Promise { this.ensureCanManage(user); const service = this.servicesRepository.create(createServiceDto); - return this.servicesRepository.save(service).then((saved) => this.findOne(saved.id)); + return this.servicesRepository + .save(service) + .then((saved) => this.findOne(saved.id)); } findAll(): Promise { diff --git a/src/sms-templates/sms-templates.service.ts b/src/sms-templates/sms-templates.service.ts index d64ef67..1a81e39 100644 --- a/src/sms-templates/sms-templates.service.ts +++ b/src/sms-templates/sms-templates.service.ts @@ -69,10 +69,7 @@ export class SmsTemplatesService { return extractPlaceholders(template.body); } - render( - template: SmsTemplate, - variables: Record, - ): string { + render(template: SmsTemplate, variables: Record): string { return renderSmsTemplate(template.body, variables); } diff --git a/src/sms-templates/utils/render-sms-template.util.ts b/src/sms-templates/utils/render-sms-template.util.ts index 8eb7300..5818818 100644 --- a/src/sms-templates/utils/render-sms-template.util.ts +++ b/src/sms-templates/utils/render-sms-template.util.ts @@ -15,7 +15,8 @@ export function renderSmsTemplate( body: string, variables: Record, ): string { - return body.replace(PLACEHOLDER_PATTERN, (_, key: string) => - variables[key] ?? `{${key}}`, + return body.replace( + PLACEHOLDER_PATTERN, + (_, key: string) => variables[key] ?? `{${key}}`, ); } diff --git a/src/stylists/dto/salon-stylist.dto.ts b/src/stylists/dto/salon-stylist.dto.ts index cf96c5e..11d85c1 100644 --- a/src/stylists/dto/salon-stylist.dto.ts +++ b/src/stylists/dto/salon-stylist.dto.ts @@ -65,7 +65,10 @@ export class UpdateSalonStylistDto { @Min(0) experienceYears?: number; - @ApiPropertyOptional({ example: 'https://example.com/avatar.jpg', nullable: true }) + @ApiPropertyOptional({ + example: 'https://example.com/avatar.jpg', + nullable: true, + }) @IsOptional() @ValidateIf((_, value) => value !== null) @IsString() diff --git a/src/stylists/stylists.controller.ts b/src/stylists/stylists.controller.ts index 77f05a2..d3084b9 100644 --- a/src/stylists/stylists.controller.ts +++ b/src/stylists/stylists.controller.ts @@ -117,7 +117,9 @@ export class StylistsController { @ApiQuery({ name: 'salonId', required: false, type: String, format: 'uuid' }) @ApiStandardOkResponse(Stylist, { isArray: true }) @Get() - findAll(@Query('salonId', new ParseUUIDPipe({ optional: true })) salonId?: string) { + findAll( + @Query('salonId', new ParseUUIDPipe({ optional: true })) salonId?: string, + ) { return this.stylistsService.findAll(salonId); } diff --git a/src/stylists/stylists.service.ts b/src/stylists/stylists.service.ts index ccbad1f..eae41ea 100644 --- a/src/stylists/stylists.service.ts +++ b/src/stylists/stylists.service.ts @@ -114,9 +114,7 @@ export class StylistsService { return stylists.filter((stylist) => { const fullName = `${stylist.user.firstName} ${stylist.user.lastName}`.toLowerCase(); - return ( - fullName.includes(search) || stylist.user.mobile.includes(search) - ); + return fullName.includes(search) || stylist.user.mobile.includes(search); }); } @@ -214,9 +212,7 @@ export class StylistsService { } if (existingUser.role === UserRole.ADMIN) { - throw new ConflictException( - 'Admins cannot be registered as stylists', - ); + throw new ConflictException('Admins cannot be registered as stylists'); } const existingStylist = await this.stylistsRepository.findOne({ diff --git a/src/subscriptions/salon-subscriptions.controller.ts b/src/subscriptions/salon-subscriptions.controller.ts index be730dc..148f0d9 100644 --- a/src/subscriptions/salon-subscriptions.controller.ts +++ b/src/subscriptions/salon-subscriptions.controller.ts @@ -65,6 +65,15 @@ export class SalonSubscriptionsController { return this.salonSubscriptionsService.purchaseSmsPackage(dto, user); } + @Roles(UserRole.ADMIN, UserRole.SALON) + @Permissions(PermissionCode.SUBSCRIPTIONS_READ) + @AllowWithoutSubscription() + @ApiStandardOkResponse(SalonSubscription, { isArray: true }) + @Get() + findAll(@CurrentUser() user: AuthUser) { + return this.salonSubscriptionsService.findAll(user); + } + @Roles(UserRole.ADMIN, UserRole.SALON) @Permissions(PermissionCode.SUBSCRIPTIONS_READ) @AllowWithoutSubscription() diff --git a/src/subscriptions/salon-subscriptions.service.ts b/src/subscriptions/salon-subscriptions.service.ts index 417a14d..03959d0 100644 --- a/src/subscriptions/salon-subscriptions.service.ts +++ b/src/subscriptions/salon-subscriptions.service.ts @@ -9,6 +9,7 @@ import { Repository } from 'typeorm'; import { AuthUser } from '../auth/interfaces/auth-user.interface'; import { SubscriptionPolicy } from '../authorization/policies/subscription.policy'; import { SalonsService } from '../salons/salons.service'; +import { UserRole } from '../users/enums/user-role.enum'; import { SMS_ROLLOVER_GRACE_DAYS } from './constants/subscription.constants'; import { PurchaseSmsPackageDto } from './dto/purchase-sms-package.dto'; import { SalonSmsPurchase } from './entities/salon-sms-purchase.entity'; @@ -126,6 +127,23 @@ export class SalonSubscriptionsService { }); } + async findAll(user: AuthUser): Promise { + if (user.role === UserRole.SALON) { + return this.subscriptionsRepository + .createQueryBuilder('subscription') + .innerJoinAndSelect('subscription.salon', 'salon') + .innerJoinAndSelect('subscription.plan', 'plan') + .where('salon.ownerId = :ownerId', { ownerId: user.id }) + .orderBy('subscription.createdAt', 'DESC') + .getMany(); + } + + return this.subscriptionsRepository.find({ + relations: { plan: true, salon: true }, + order: { createdAt: 'DESC' }, + }); + } + async findActiveForSalon( salonId: string, user: AuthUser, @@ -138,7 +156,10 @@ export class SalonSubscriptionsService { return this.findActiveSubscription(salonId); } - async getSmsBalance(salonId: string, user: AuthUser): Promise { + async getSmsBalance( + salonId: string, + user: AuthUser, + ): Promise { const salon = await this.salonsService.findOne(salonId); if (!this.subscriptionPolicy.canRead(user, salon)) { throw new ForbiddenException(); @@ -206,7 +227,9 @@ export class SalonSubscriptionsService { const hasActive = await this.hasActiveSubscription(salonId); if (!hasActive) { - throw new BadRequestException('Salon does not have an active subscription'); + throw new BadRequestException( + 'Salon does not have an active subscription', + ); } let remaining = count; @@ -303,7 +326,10 @@ export class SalonSubscriptionsService { order: { endDate: 'DESC' }, }); - if (!lastSubscription || lastSubscription.status === SalonSubscriptionStatus.ACTIVE) { + if ( + !lastSubscription || + lastSubscription.status === SalonSubscriptionStatus.ACTIVE + ) { return 0; } diff --git a/src/subscriptions/sms-packages.service.ts b/src/subscriptions/sms-packages.service.ts index e722ced..13c5567 100644 --- a/src/subscriptions/sms-packages.service.ts +++ b/src/subscriptions/sms-packages.service.ts @@ -1,7 +1,4 @@ -import { - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateSmsPackageDto } from './dto/create-sms-package.dto'; diff --git a/src/subscriptions/subscription-plans.service.ts b/src/subscriptions/subscription-plans.service.ts index d193067..fec26b0 100644 --- a/src/subscriptions/subscription-plans.service.ts +++ b/src/subscriptions/subscription-plans.service.ts @@ -1,7 +1,4 @@ -import { - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CreateSubscriptionPlanDto } from './dto/create-subscription-plan.dto'; @@ -56,7 +53,8 @@ export class SubscriptionPlansService { } if (dto.name !== undefined) plan.name = dto.name; if (dto.code !== undefined) plan.code = dto.code; - if (dto.durationMonths !== undefined) plan.durationMonths = dto.durationMonths; + if (dto.durationMonths !== undefined) + plan.durationMonths = dto.durationMonths; if (dto.freeSmsCount !== undefined) plan.freeSmsCount = dto.freeSmsCount; if (dto.isActive !== undefined) plan.isActive = dto.isActive; return this.plansRepository.save(plan); diff --git a/src/suggestions/dto/create-bulk-service-suggestions.dto.ts b/src/suggestions/dto/create-bulk-service-suggestions.dto.ts index 1ae7550..bfaf617 100644 --- a/src/suggestions/dto/create-bulk-service-suggestions.dto.ts +++ b/src/suggestions/dto/create-bulk-service-suggestions.dto.ts @@ -1,10 +1,5 @@ import { Type } from 'class-transformer'; -import { - ArrayMinSize, - IsArray, - IsUUID, - ValidateNested, -} from 'class-validator'; +import { ArrayMinSize, IsArray, IsUUID, ValidateNested } from 'class-validator'; import { CreateBulkServiceSuggestionItemDto } from './create-bulk-service-suggestion-item.dto'; export class CreateBulkServiceSuggestionsDto { diff --git a/src/suggestions/suggestions.service.ts b/src/suggestions/suggestions.service.ts index 6c34e32..ea08b88 100644 --- a/src/suggestions/suggestions.service.ts +++ b/src/suggestions/suggestions.service.ts @@ -124,11 +124,15 @@ export class SuggestionsService { ): Promise { this.ensureCanManage(user); const suggestion = await this.findOne(id); - const serviceSkillId = updateDto.serviceSkillId ?? suggestion.serviceSkillId; + const serviceSkillId = + updateDto.serviceSkillId ?? suggestion.serviceSkillId; const suggestedServiceSkillId = updateDto.suggestedServiceSkillId ?? suggestion.suggestedServiceSkillId; - await this.validateSuggestionTargets(serviceSkillId, suggestedServiceSkillId); + await this.validateSuggestionTargets( + serviceSkillId, + suggestedServiceSkillId, + ); Object.assign(suggestion, updateDto); await this.suggestionsRepository.save(suggestion); return this.findOne(id); @@ -175,14 +179,20 @@ export class SuggestionsService { ]); } - private async validateBaseServiceSkill(serviceSkillId: string): Promise { - const exists = await this.serviceSkillsRepository.existsBy({ id: serviceSkillId }); + private async validateBaseServiceSkill( + serviceSkillId: string, + ): Promise { + const exists = await this.serviceSkillsRepository.existsBy({ + id: serviceSkillId, + }); if (!exists) { throw new NotFoundException(`Service skill #${serviceSkillId} not found`); } } - private ensureUniqueSuggestedSkills(suggestedServiceSkillIds: string[]): void { + private ensureUniqueSuggestedSkills( + suggestedServiceSkillIds: string[], + ): void { const uniqueIds = new Set(suggestedServiceSkillIds); if (uniqueIds.size !== suggestedServiceSkillIds.length) { throw new BadRequestException('Duplicate suggested skills in batch'); @@ -204,7 +214,9 @@ export class SuggestionsService { if (suggestedSkills.length !== suggestedServiceSkillIds.length) { const foundIds = new Set(suggestedSkills.map((skill) => skill.id)); - const missingId = suggestedServiceSkillIds.find((id) => !foundIds.has(id)); + const missingId = suggestedServiceSkillIds.find( + (id) => !foundIds.has(id), + ); throw new NotFoundException( `Suggested service skill #${missingId} not found`, );