edit time rezerv
This commit is contained in:
@@ -12,6 +12,49 @@ const TEHRAN_WEEKDAY: Record<string, number> = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const TIME_RE = /^([01]\d|2[0-3]):([0-5]\d)$/;
|
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 = {
|
export type AvailabilityWindow = {
|
||||||
dayOfWeek: number;
|
dayOfWeek: number;
|
||||||
@@ -25,7 +68,8 @@ export type BusyInterval = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export function parseHmToMinute(value: string): number {
|
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) {
|
if (!match) {
|
||||||
throw new BadRequestException('Time must be HH:mm (24h)');
|
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 { ApiProperty } from '@nestjs/swagger';
|
||||||
import { Type } from 'class-transformer';
|
import { Transform, Type } from 'class-transformer';
|
||||||
import {
|
import {
|
||||||
ArrayMaxSize,
|
ArrayMaxSize,
|
||||||
IsArray,
|
IsArray,
|
||||||
@@ -10,20 +10,24 @@ import {
|
|||||||
Min,
|
Min,
|
||||||
ValidateNested,
|
ValidateNested,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
import { normalizeHmTimeInput } from '../../../common/utils/booking-schedule.util';
|
||||||
|
|
||||||
export class AvailabilitySlotDto {
|
export class AvailabilitySlotDto {
|
||||||
@ApiProperty({ description: '0=شنبه … 6=جمعه', minimum: 0, maximum: 6 })
|
@ApiProperty({ description: '0=شنبه … 6=جمعه', minimum: 0, maximum: 6 })
|
||||||
|
@Type(() => Number)
|
||||||
@IsInt()
|
@IsInt()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@Max(6)
|
@Max(6)
|
||||||
dayOfWeek!: number;
|
dayOfWeek!: number;
|
||||||
|
|
||||||
@ApiProperty({ example: '09:00' })
|
@ApiProperty({ example: '09:00' })
|
||||||
|
@Transform(({ value }) => normalizeHmTimeInput(value) ?? value)
|
||||||
@IsString()
|
@IsString()
|
||||||
@Matches(/^([01]\d|2[0-3]):[0-5]\d]$/)
|
@Matches(/^([01]\d|2[0-3]):[0-5]\d]$/)
|
||||||
startTime!: string;
|
startTime!: string;
|
||||||
|
|
||||||
@ApiProperty({ example: '17:00' })
|
@ApiProperty({ example: '17:00' })
|
||||||
|
@Transform(({ value }) => normalizeHmTimeInput(value) ?? value)
|
||||||
@IsString()
|
@IsString()
|
||||||
@Matches(/^([01]\d|2[0-3]):[0-5]\d]$/)
|
@Matches(/^([01]\d|2[0-3]):[0-5]\d]$/)
|
||||||
endTime!: string;
|
endTime!: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user