feat: add recurring bookings feature with related handlers and services

- Introduced recurring booking groups with the ability to create and cancel them.
- Added database migrations for recurring bookings, including new tables and relationships.
- Implemented handlers for creating and canceling recurring bookings in the admin booking module.
- Enhanced existing booking services to support recurring bookings logic.
- Updated API contract to include new schemas for recurring bookings.
- Refactored existing code for improved readability and maintainability.
This commit is contained in:
Jose Selesan
2026-06-16 11:58:06 -03:00
parent 1b5eb253f2
commit 1318e3bf57
38 changed files with 2025 additions and 265 deletions

View File

@@ -1,6 +1,6 @@
import { z } from 'zod'
import { z } from 'zod';
export const adminPaymentStatusSchema = z.enum(['active', 'no_plan', 'expired'])
export const adminPaymentStatusSchema = z.enum(['active', 'no_plan', 'expired']);
export const adminComplexListItemSchema = z.object({
id: z.string(),
@@ -13,7 +13,7 @@ export const adminComplexListItemSchema = z.object({
courtCount: z.number().int().nonnegative(),
avgBookingsPerDay: z.number().nonnegative(),
paymentStatus: adminPaymentStatusSchema,
})
});
export const adminComplexStatsSchema = z.object({
id: z.string(),
@@ -34,20 +34,20 @@ export const adminComplexStatsSchema = z.object({
noshow: z.number().int().nonnegative(),
}),
paymentStatus: adminPaymentStatusSchema,
})
});
export const adminCreatePlanSchema = z.object({
code: z.string().trim().min(1).max(10),
name: z.string().trim().min(1).max(30),
price: z.number().nonnegative(),
rules: z.any(),
})
});
export const adminUpdatePlanSchema = z.object({
name: z.string().trim().min(1).max(30).optional(),
price: z.number().nonnegative().optional(),
rules: z.any().optional(),
})
});
export const adminUserSchema = z.object({
id: z.string(),
@@ -60,18 +60,18 @@ export const adminUserSchema = z.object({
createdAt: z.string().datetime(),
complexCount: z.number().int().nonnegative(),
activeSessions: z.number().int().nonnegative(),
})
});
export const adminBlockUserSchema = z.object({
banReason: z.string().max(500).optional(),
})
});
export const adminGlobalStatsSchema = z.object({
totalComplexes: z.number().int().nonnegative(),
totalUsers: z.number().int().nonnegative(),
totalCourts: z.number().int().nonnegative(),
totalBookings: z.number().int().nonnegative(),
})
});
export const adminUserSessionSchema = z.object({
id: z.string(),
@@ -82,35 +82,35 @@ export const adminUserSessionSchema = z.object({
city: z.string().nullable(),
country: z.string().nullable(),
countryCode: z.string().nullable(),
})
});
export type AdminComplexListItem = z.infer<typeof adminComplexListItemSchema>
export type AdminComplexStats = z.infer<typeof adminComplexStatsSchema>
export type AdminCreatePlanInput = z.infer<typeof adminCreatePlanSchema>
export type AdminUpdatePlanInput = z.infer<typeof adminUpdatePlanSchema>
export type AdminUser = z.infer<typeof adminUserSchema>
export type AdminBlockUserInput = z.infer<typeof adminBlockUserSchema>
export type AdminGlobalStats = z.infer<typeof adminGlobalStatsSchema>
export type AdminPaymentStatus = z.infer<typeof adminPaymentStatusSchema>
export type AdminUserSession = z.infer<typeof adminUserSessionSchema>
export type AdminComplexListItem = z.infer<typeof adminComplexListItemSchema>;
export type AdminComplexStats = z.infer<typeof adminComplexStatsSchema>;
export type AdminCreatePlanInput = z.infer<typeof adminCreatePlanSchema>;
export type AdminUpdatePlanInput = z.infer<typeof adminUpdatePlanSchema>;
export type AdminUser = z.infer<typeof adminUserSchema>;
export type AdminBlockUserInput = z.infer<typeof adminBlockUserSchema>;
export type AdminGlobalStats = z.infer<typeof adminGlobalStatsSchema>;
export type AdminPaymentStatus = z.infer<typeof adminPaymentStatusSchema>;
export type AdminUserSession = z.infer<typeof adminUserSessionSchema>;
export const adminGeoCitySchema = z.object({
city: z.string(),
userCount: z.number().int().nonnegative(),
})
});
export const adminGeoCountrySchema = z.object({
country: z.string(),
countryCode: z.string(),
totalUsers: z.number().int().nonnegative(),
cities: z.array(adminGeoCitySchema),
})
});
export const adminGeoStatsSchema = z.object({
countries: z.array(adminGeoCountrySchema),
totalUniqueCountries: z.number().int().nonnegative(),
})
});
export type AdminGeoCity = z.infer<typeof adminGeoCitySchema>
export type AdminGeoCountry = z.infer<typeof adminGeoCountrySchema>
export type AdminGeoStats = z.infer<typeof adminGeoStatsSchema>
export type AdminGeoCity = z.infer<typeof adminGeoCitySchema>;
export type AdminGeoCountry = z.infer<typeof adminGeoCountrySchema>;
export type AdminGeoStats = z.infer<typeof adminGeoStatsSchema>;