add cancellationReason

This commit is contained in:
2026-06-13 13:42:52 +03:30
parent 1d685b3338
commit 9ec1a476a0
5 changed files with 51 additions and 2 deletions
@@ -0,0 +1,21 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class AddBookingCancellationReason1789046800000
implements MigrationInterface
{
name = 'AddBookingCancellationReason1789046800000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "bookings"
ADD COLUMN IF NOT EXISTS "cancellationReason" text
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
ALTER TABLE "bookings"
DROP COLUMN IF EXISTS "cancellationReason"
`);
}
}
+3
View File
@@ -66,6 +66,9 @@ export class BookingEntity {
@Column({ type: 'text', nullable: true }) @Column({ type: 'text', nullable: true })
note!: string | null; note!: string | null;
@Column({ type: 'text', nullable: true })
cancellationReason!: string | null;
@CreateDateColumn() @CreateDateColumn()
createdAt!: Date; createdAt!: Date;
+3 -1
View File
@@ -20,6 +20,7 @@ import { RolesGuard } from '../auth/guards/roles.guard';
import { BookingsService } from './bookings.service'; import { BookingsService } from './bookings.service';
import { CreateBookingDto } from './dto/create-booking.dto'; import { CreateBookingDto } from './dto/create-booking.dto';
import { CreateBookingBatchDto } from './dto/create-booking-batch.dto'; import { CreateBookingBatchDto } from './dto/create-booking-batch.dto';
import { CancelBookingDto } from './dto/cancel-booking.dto';
import { UpdateBookingDto } from './dto/update-booking.dto'; import { UpdateBookingDto } from './dto/update-booking.dto';
@Controller('bookings') @Controller('bookings')
@@ -128,9 +129,10 @@ export class BookingsController {
@Roles(UserRole.ADMIN, UserRole.BUSINESS_OWNER, UserRole.CUSTOMER) @Roles(UserRole.ADMIN, UserRole.BUSINESS_OWNER, UserRole.CUSTOMER)
cancel( cancel(
@Param('id') id: string, @Param('id') id: string,
@Body() payload: CancelBookingDto,
@CurrentUser() user: AuthUser, @CurrentUser() user: AuthUser,
): Promise<BookingEntity> { ): Promise<BookingEntity> {
return this.service.cancel(id, user); return this.service.cancel(id, user, payload);
} }
@Delete(':id') @Delete(':id')
+14 -1
View File
@@ -324,9 +324,22 @@ export class BookingsService {
return this.repo.save(row); return this.repo.save(row);
} }
async cancel(id: string, actor?: AuthUser): Promise<BookingEntity> { async cancel(
id: string,
actor?: AuthUser,
payload?: { cancellationReason?: string },
): Promise<BookingEntity> {
const row = await this.findOne(id); const row = await this.findOne(id);
this.assertCanCancelBooking(row, actor); this.assertCanCancelBooking(row, actor);
const reason = payload?.cancellationReason?.trim() ?? '';
if (actor?.role === UserRole.BUSINESS_OWNER && !reason) {
throw new BadRequestException('Cancellation reason is required');
}
if (reason) {
row.cancellationReason = reason;
}
this.applyStatusTransition(row, BookingStatus.CANCELLED, actor); this.applyStatusTransition(row, BookingStatus.CANCELLED, actor);
return this.repo.save(row); return this.repo.save(row);
} }
@@ -0,0 +1,10 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsOptional, IsString, MaxLength } from 'class-validator';
export class CancelBookingDto {
@ApiPropertyOptional({ example: 'ظرفیت این بازه تکمیل شده است.' })
@IsOptional()
@IsString()
@MaxLength(500)
cancellationReason?: string;
}