import { z } from 'zod' import type { CarplaceHomepageContent, CarplaceHomepageHowItWorksStep, CarplaceHomepageMetric, CarplaceHomepagePillar, CarplaceHomepageSectionType, CarplaceHomepageStep, CarplaceHomepageTestimonial, } from '@rentaldrivego/types' export const loginSchema = z.object({ email: z.string().email().max(255).trim().toLowerCase(), password: z.string().max(128), totpCode: z.string().length(6).optional(), recoveryCode: z.string().min(8).max(32).optional(), }) export const forgotPasswordSchema = z.object({ email: z.string().email().max(255).trim().toLowerCase(), }) export const resetPasswordSchema = z.object({ token: z.string().min(1), password: z.string().min(8).max(128), }) export const totpVerifySchema = z.object({ code: z.string().length(6), }) export const companiesQuerySchema = z.object({ q: z.string().optional(), status: z.string().optional(), plan: z.string().optional(), page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(20), }) export const rentersQuerySchema = z.object({ q: z.string().optional(), blocked: z.string().optional(), page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(20), }) export const auditLogQuerySchema = z.object({ adminId: z.string().optional(), action: z.string().optional(), companyId: z.string().optional(), entityId: z.string().optional(), page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(50), }) const notificationChannelSchema = z.enum(['EMAIL', 'SMS', 'WHATSAPP', 'IN_APP', 'PUSH']) const notificationStatusSchema = z.enum(['PENDING', 'QUEUED', 'SENT', 'DELIVERED', 'FAILED', 'SKIPPED', 'DEAD_LETTER', 'READ']) export const notificationsQuerySchema = z.object({ channel: notificationChannelSchema.optional(), status: notificationStatusSchema.optional(), companyId: z.string().optional(), page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(200).default(50), }) export const billingQuerySchema = z.object({ q: z.string().optional(), status: z.string().optional(), plan: z.string().optional(), page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(20), }) export const invoicesQuerySchema = z.object({ page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(20), }) export const permissionSchema = z.object({ resource: z.enum(['COMPANIES', 'COMPANY_EMPLOYEES', 'SUBSCRIPTIONS', 'PAYMENTS', 'OFFERS', 'RENTERS', 'ADMIN_USERS', 'AUDIT_LOGS', 'PLATFORM_METRICS']), actions: z.array(z.enum(['READ', 'CREATE', 'UPDATE', 'DELETE', 'SUSPEND', 'IMPERSONATE'])).min(1), }) export const createAdminSchema = z.object({ email: z.string().email().trim().toLowerCase(), firstName: z.string().min(1).max(100).trim(), lastName: z.string().min(1).max(100).trim(), role: z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']), password: z.string().min(8), permissions: z.array(permissionSchema).optional(), }) export const updateAdminSchema = z.object({ email: z.string().email().trim().toLowerCase().optional(), firstName: z.string().min(1).max(100).trim().optional(), lastName: z.string().min(1).max(100).trim().optional(), role: z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']).optional(), password: z.string().min(8).optional(), isActive: z.boolean().optional(), }) export const adminRoleSchema = z.object({ role: z.enum(['SUPER_ADMIN', 'ADMIN', 'SUPPORT', 'FINANCE', 'VIEWER']), }) export const adminPermissionsSchema = z.object({ permissions: z.array(permissionSchema), }) export const companyStatusSchema = z.object({ status: z.enum(['ACTIVE', 'SUSPENDED', 'CANCELLED']), reason: z.string().optional(), }) const nullableString = z.union([z.string(), z.null()]).optional() const nullableEmail = z.union([z.string().email(), z.null()]).optional() const nullableUrl = z.union([z.string().url(), z.null()]).optional() const nullableDate = z.union([z.string().datetime(), z.string().regex(/^\d{4}-\d{2}-\d{2}$/), z.null()]).optional() export const adminCompanyUpdateSchema = z.object({ company: z.object({ name: z.string().min(1).optional(), slug: z.string().min(1).optional(), email: z.string().email().optional(), phone: nullableString, status: z.enum(['PENDING', 'TRIALING', 'ACTIVE', 'PAST_DUE', 'SUSPENDED', 'CANCELLED']).optional(), subscriptionPaymentRef: nullableString, address: z.object({ streetAddress: nullableString, city: nullableString, country: nullableString, zipCode: nullableString, legalName: nullableString, legalForm: nullableString, companyEmail: nullableEmail, managerName: nullableString, iceNumber: nullableString, operatingLicenseNumber: nullableString, operatingLicenseIssuedAt: nullableString, operatingLicenseIssuedBy: nullableString, fax: nullableString, yearsActive: nullableString, representativeName: nullableString, representativeTitle: nullableString, responsibleName: nullableString, responsibleRole: nullableString, responsibleIdentityNumber: nullableString, responsibleQualification: nullableString, responsiblePhone: nullableString, responsibleEmail: nullableEmail, }).optional(), }).optional(), subscription: z.object({ plan: z.enum(['STARTER', 'GROWTH', 'PRO']).optional(), billingPeriod: z.enum(['MONTHLY', 'ANNUAL']).optional(), status: z.enum(['TRIALING', 'ACTIVE', 'PAST_DUE', 'CANCELLED', 'UNPAID']).optional(), currency: z.literal('MAD').optional(), trialStartAt: nullableDate, trialEndAt: nullableDate, currentPeriodStart: nullableDate, currentPeriodEnd: nullableDate, cancelledAt: nullableDate, cancelAtPeriodEnd: z.boolean().optional(), }).optional(), brand: z.object({ displayName: z.string().min(1).optional(), tagline: nullableString, subdomain: z.string().min(1).optional(), customDomain: nullableString, publicEmail: nullableEmail, publicPhone: nullableString, publicAddress: nullableString, publicCity: nullableString, publicCountry: nullableString, websiteUrl: nullableUrl, whatsappNumber: nullableString, defaultLocale: z.string().min(2).optional(), defaultCurrency: z.literal('MAD').optional(), isListedOnCarplace: z.boolean().optional(), homePageConfig: z.any().optional(), menuConfig: z.any().optional(), }).optional(), contractSettings: z.object({ legalName: nullableString, registrationNumber: nullableString, taxId: nullableString, terms: z.string().optional(), fuelPolicyType: z.enum(['FULL_TO_FULL', 'FULL_TO_EMPTY', 'SAME_TO_SAME', 'PREPAID', 'FREE']).optional(), lateFeePerHour: z.number().int().nullable().optional(), taxRate: z.number().nullable().optional(), signatureRequired: z.boolean().optional(), showTax: z.boolean().optional(), }).optional(), accountingSettings: z.object({ reportingPeriod: z.enum(['WEEKLY', 'MONTHLY', 'QUARTERLY', 'ANNUAL']).optional(), fiscalYearStart: z.number().int().min(1).max(12).optional(), currency: z.literal('MAD').optional(), accountantEmail: nullableEmail, accountantName: nullableString, autoSendReport: z.boolean().optional(), reportFormat: z.enum(['PDF', 'CSV', 'BOTH']).optional(), }).optional(), }) const carplaceMetricSchema: z.ZodType = z.object({ value: z.string().min(1), label: z.string().min(1) }) const carplacePillarSchema: z.ZodType = z.object({ title: z.string().min(1), body: z.string().min(1) }) const carplaceStepSchema: z.ZodType = z.object({ step: z.string().min(1), title: z.string().min(1), body: z.string().min(1) }) const carplaceHowItWorksStepSchema: z.ZodType = z.object({ number: z.string().min(1), title: z.string().min(1), description: z.string().min(1) }) const carplaceTestimonialSchema: z.ZodType = z.object({ quote: z.string().min(1), author: z.string().min(1), role: z.string().min(1), company: z.string().optional() }) const carplaceSectionSchema: z.ZodType = z.enum(['hero', 'surface', 'pillars', 'audiences', 'features', 'howitworks', 'steps', 'testimonials', 'closing']) const carplaceHomepageContentSchema: z.ZodType = z.object({ sections: z.array(carplaceSectionSchema).min(1), heroKicker: z.string().min(1), heroTitle: z.string().min(1), heroBody: z.string().min(1), startTrial: z.string().min(1), exploreVehicles: z.string().min(1), surfaceLabel: z.string().min(1), surfaceTitle: z.string().min(1), surfaceBody: z.string().min(1), liveLabel: z.string().min(1), trustedFleets: z.string().min(1), brandedFlows: z.string().min(1), multiTenant: z.string().min(1), companyKicker: z.string().min(1), companyTitle: z.string().min(1), companyBody: z.string().min(1), renterKicker: z.string().min(1), renterTitle: z.string().min(1), renterBody: z.string().min(1), pillars: z.array(carplacePillarSchema).length(3), metrics: z.array(carplaceMetricSchema).length(3), featureLabel: z.string().min(1), features: z.array(z.string().min(1)).min(1), howitworksKicker: z.string().min(1), howitworksTitle: z.string().min(1), howitworksSteps: z.array(carplaceHowItWorksStepSchema).min(1), stepsTitle: z.string().min(1), steps: z.array(carplaceStepSchema).length(3), stepLabel: z.string().min(1), testimonialsKicker: z.string().min(1), testimonialsTitle: z.string().min(1), testimonials: z.array(carplaceTestimonialSchema).min(1), readyKicker: z.string().min(1), readyTitle: z.string().min(1), readyBody: z.string().min(1), viewPricing: z.string().min(1), createWorkspace: z.string().min(1), }) export const carplaceHomepageConfigSchema = z.object({ en: carplaceHomepageContentSchema, fr: carplaceHomepageContentSchema, ar: carplaceHomepageContentSchema, }) export const idParamSchema = z.object({ id: z.string().min(1), }) export const companyIdParamSchema = z.object({ companyId: z.string().min(1), }) export const billingAccountIdParamSchema = z.object({ billingAccountId: z.string().min(1), }) export const invoiceIdParamSchema = z.object({ invoiceId: z.string().min(1), }) export const billingAccountUpdateSchema = z.object({ legalName: z.string().min(1).max(255).optional(), billingEmail: z.string().email().optional(), billingAddress: z.any().optional(), taxId: z.union([z.string().max(120), z.null()]).optional(), taxExempt: z.boolean().optional(), invoiceTerms: z.enum(['DUE_ON_RECEIPT', 'NET_7', 'NET_15', 'NET_30', 'NET_45', 'NET_60']).optional(), netTermsDays: z.number().int().min(0).max(365).optional(), }) export const billingLineItemInputSchema = z.object({ type: z.enum([ 'SUBSCRIPTION_FEE', 'SEAT_FEE', 'USAGE_FEE', 'SETUP_FEE', 'DISCOUNT', 'TAX', 'CREDIT', 'PRORATION', 'REFUND_ADJUSTMENT', 'MANUAL_ADJUSTMENT', 'PROFESSIONAL_SERVICES', 'CANCELLATION_FEE', ]), description: z.string().min(1).max(255), quantity: z.number().int().positive().max(100000), unitAmount: z.number().int(), periodStart: nullableDate, periodEnd: nullableDate, }) export const createBillingInvoiceSchema = z.object({ subscriptionId: z.union([z.string(), z.null()]).optional(), invoiceType: z.enum([ 'SUBSCRIPTION_INITIAL', 'SUBSCRIPTION_RENEWAL', 'TRIAL_CONVERSION', 'SUBSCRIPTION_UPGRADE', 'SUBSCRIPTION_DOWNGRADE_CREDIT', 'USAGE_BASED', 'ONE_TIME', 'MANUAL', 'CORRECTION', 'CANCELLATION_FEE', ]), currency: z.string().min(3).max(8).optional(), dueAt: nullableDate, isSubscriptionBlocking: z.boolean().optional(), adminReason: z.union([z.string().max(500), z.null()]).optional(), lineItems: z.array(billingLineItemInputSchema).min(1), }) export const payBillingInvoiceSchema = z.object({ amount: z.number().int().positive().optional(), paymentMethodId: z.union([z.string(), z.null()]).optional(), providerPaymentId: z.union([z.string(), z.null()]).optional(), }) export const retryBillingInvoiceSchema = z.object({ paymentMethodId: z.union([z.string(), z.null()]).optional(), }) export const billingReasonSchema = z.object({ reason: z.string().min(1).max(500), }) export const billingCreditNoteSchema = z.object({ amount: z.number().int().positive(), reason: z.string().min(1).max(500), }) export const billingRefundSchema = z.object({ amount: z.number().int().positive(), reason: z.string().min(1).max(500), }) export const homepageUpdateSchema = z.object({ homepage: carplaceHomepageConfigSchema, }) export const pricingUpdateSchema = z.object({ entries: z.array(z.object({ plan: z.enum(['STARTER', 'GROWTH', 'PRO']), billingPeriod: z.enum(['MONTHLY', 'ANNUAL']), amount: z.number().int().positive(), })).min(1), }) const planFeatureSchema = z.object({ plan: z.enum(['STARTER', 'GROWTH', 'PRO']), label: z.string().min(1).max(120), sortOrder: z.number().int().min(0).default(0), }) export const planFeatureCreateSchema = planFeatureSchema export const planFeatureUpdateSchema = planFeatureSchema.partial() export const promotionCreateSchema = z.object({ code: z.string().min(2).max(30).regex(/^[A-Z0-9_-]+$/, 'Code must be uppercase A-Z, 0-9, _ or -'), name: z.string().min(1).max(100), description: z.string().max(500).optional(), discountType: z.enum(['PERCENTAGE', 'FIXED']), discountValue: z.number().int().positive(), plans: z.array(z.enum(['STARTER', 'GROWTH', 'PRO'])), periods: z.array(z.enum(['MONTHLY', 'ANNUAL'])), maxUses: z.number().int().positive().nullable().optional(), validFrom: z.string().datetime(), validUntil: z.string().datetime().nullable().optional(), isActive: z.boolean().default(true), }) export const promotionUpdateSchema = promotionCreateSchema.partial() export const planFeatureIdParamSchema = z.object({ featureId: z.string().min(1) }) export const promotionIdParamSchema = z.object({ promotionId: z.string().min(1) }) const employeeRoleSchema = z.enum(['OWNER', 'MANAGER', 'AGENT']) const planSchema = z.enum(['STARTER', 'GROWTH', 'PRO']) const menuItemTypeSchema = z.enum(['INTERNAL_PAGE', 'EXTERNAL_LINK', 'PARENT_MENU', 'SECTION_LABEL', 'DIVIDER']) export const menuItemSchema = z.object({ systemKey: z.string().trim().min(1).max(120).optional().nullable(), label: z.string().trim().min(1).max(120), itemType: menuItemTypeSchema, routeOrUrl: z.string().trim().max(400).optional().nullable(), icon: z.string().trim().max(120).optional().nullable(), parentId: z.string().trim().min(1).optional().nullable(), displayOrder: z.number().int().min(0).default(0), openInNewTab: z.boolean().default(false), isRequired: z.boolean().default(false), isActive: z.boolean().default(true), roles: z.array(employeeRoleSchema).default([]), subscriptionPlans: z.array(z.object({ plan: planSchema, displayOrder: z.number().int().min(0).optional(), isActive: z.boolean().optional(), })).default([]), companyAssignments: z.array(z.object({ companyId: z.string().trim().min(1), displayOrder: z.number().int().min(0).optional(), isActive: z.boolean().optional(), })).default([]), }) export const menuItemStatusSchema = z.object({ isActive: z.boolean(), }) export const menuPlanAssignmentsSchema = z.object({ assignments: z.array(z.object({ plan: planSchema, displayOrder: z.number().int().min(0).optional(), isActive: z.boolean().optional(), })).min(1), }) export const menuCompanyAssignmentsSchema = z.object({ assignments: z.array(z.object({ companyId: z.string().trim().min(1), displayOrder: z.number().int().min(0).optional(), isActive: z.boolean().optional(), })).min(1), }) export const menuPreviewSchema = z.object({ companyId: z.string().trim().min(1), role: employeeRoleSchema, }) export const menuAuditLogQuerySchema = z.object({ page: z.coerce.number().int().min(1).default(1), pageSize: z.coerce.number().int().min(1).max(100).default(20), }) export const menuItemIdParamSchema = z.object({ id: z.string().min(1), }) export const menuPlanParamSchema = z.object({ id: z.string().min(1), plan: planSchema, }) export const menuCompanyParamSchema = z.object({ id: z.string().min(1), companyId: z.string().min(1), })