edit time rezerv

This commit is contained in:
2026-05-25 20:06:10 +03:30
parent 8263198d1e
commit a8c255a3a4
3 changed files with 82 additions and 2 deletions
+45 -1
View File
@@ -12,6 +12,49 @@ const TEHRAN_WEEKDAY: Record<string, number> = {
};
const TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
const PERSIAN_DIGITS = '۰۱۲۳۴۵۶۷۸۹';
const ARABIC_DIGITS = '٠١٢٣٤٥٦٧٨٩';
function toAsciiDigits(value: string): string {
return value.replace(/./g, (ch) => {
const pi = PERSIAN_DIGITS.indexOf(ch);
if (pi >= 0) return String(pi);
const ai = ARABIC_DIGITS.indexOf(ch);
if (ai >= 0) return String(ai);
return ch;
});
}
/** Accepts browser variants (9:00, 09:00:00, Persian digits) → HH:mm */
export function normalizeHmTimeInput(value: unknown): string | null {
if (value == null || value === '') return null;
let raw = toAsciiDigits(String(value).trim()).normalize('NFKC');
raw = raw.replace(/[\u200e\u200f\u202a\u202c]/g, '');
const ampm = /^(\d{1,2}):(\d{1,2})(?::\d{1,2})?\s*([ap])(?:\.)?m?\.?$/i.exec(
raw,
);
if (ampm) {
let h = Number.parseInt(ampm[1], 10);
const m = Number.parseInt(ampm[2], 10);
const isPm = ampm[3].toLowerCase() === 'p';
if (h === 12) h = isPm ? 12 : 0;
else if (isPm) h += 12;
if (h < 0 || h > 23 || m < 0 || m > 59) return null;
return minuteToHm(h * 60 + m);
}
const match = /^(\d{1,2}):(\d{1,2})(?::\d{1,2})?$/.exec(raw);
if (!match) return null;
let h = Number.parseInt(match[1], 10);
const m = Number.parseInt(match[2], 10);
if (h === 24 && m === 0) {
h = 23;
return '23:59';
}
if (h < 0 || h > 23 || m < 0 || m > 59) return null;
return minuteToHm(h * 60 + m);
}
export type AvailabilityWindow = {
dayOfWeek: number;
@@ -25,7 +68,8 @@ export type BusyInterval = {
};
export function parseHmToMinute(value: string): number {
const match = TIME_RE.exec(String(value).trim());
const normalized = normalizeHmTimeInput(value);
const match = TIME_RE.exec(normalized ?? '');
if (!match) {
throw new BadRequestException('Time must be HH:mm (24h)');
}
@@ -0,0 +1,32 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class CreateBusinessAvailabilitySlots1789043000000
implements MigrationInterface
{
name = 'CreateBusinessAvailabilitySlots1789043000000';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`
CREATE TABLE IF NOT EXISTS "business_availability_slots" (
"id" uuid NOT NULL DEFAULT gen_random_uuid(),
"businessId" uuid NOT NULL,
"dayOfWeek" smallint NOT NULL,
"startMinute" integer NOT NULL,
"endMinute" integer NOT NULL,
"createdAt" TIMESTAMP NOT NULL DEFAULT now(),
"updatedAt" TIMESTAMP NOT NULL DEFAULT now(),
CONSTRAINT "PK_business_availability_slots" PRIMARY KEY ("id"),
CONSTRAINT "FK_availability_business" FOREIGN KEY ("businessId")
REFERENCES "businesses"("id") ON DELETE CASCADE
)
`);
await queryRunner.query(`
CREATE INDEX IF NOT EXISTS "IDX_availability_business_day"
ON "business_availability_slots" ("businessId", "dayOfWeek")
`);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`DROP TABLE IF EXISTS "business_availability_slots"`);
}
}
@@ -1,5 +1,5 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { Transform, Type } from 'class-transformer';
import {
ArrayMaxSize,
IsArray,
@@ -10,20 +10,24 @@ import {
Min,
ValidateNested,
} from 'class-validator';
import { normalizeHmTimeInput } from '../../../common/utils/booking-schedule.util';
export class AvailabilitySlotDto {
@ApiProperty({ description: '0=شنبه … 6=جمعه', minimum: 0, maximum: 6 })
@Type(() => Number)
@IsInt()
@Min(0)
@Max(6)
dayOfWeek!: number;
@ApiProperty({ example: '09:00' })
@Transform(({ value }) => normalizeHmTimeInput(value) ?? value)
@IsString()
@Matches(/^([01]\d|2[0-3]):[0-5]\d]$/)
startTime!: string;
@ApiProperty({ example: '17:00' })
@Transform(({ value }) => normalizeHmTimeInput(value) ?? value)
@IsString()
@Matches(/^([01]\d|2[0-3]):[0-5]\d]$/)
endTime!: string;