342 lines
11 KiB
TypeScript
342 lines
11 KiB
TypeScript
import { VALIDATION, AUTH_LEVELS, USER_STATUS, TRANSMISSION_TYPES, FUEL_TYPES, USE_TYPES, PAYMENT_STATUS } from './constants';
|
|
|
|
// User validation
|
|
export function validateUser(data: {
|
|
name?: string;
|
|
username?: string;
|
|
email?: string;
|
|
password?: string;
|
|
authLevel?: number;
|
|
status?: string;
|
|
}) {
|
|
const errors: Record<string, string> = {};
|
|
|
|
if (data.name !== undefined) {
|
|
if (!data.name || data.name.trim().length === 0) {
|
|
errors.name = 'الاسم مطلوب';
|
|
} else if (data.name.length > VALIDATION.MAX_NAME_LENGTH) {
|
|
errors.name = `الاسم يجب أن يكون أقل من ${VALIDATION.MAX_NAME_LENGTH} حرف`;
|
|
}
|
|
}
|
|
|
|
if (data.username !== undefined) {
|
|
if (!data.username || data.username.trim().length === 0) {
|
|
errors.username = 'اسم المستخدم مطلوب';
|
|
} else if (data.username.length < 3) {
|
|
errors.username = 'اسم المستخدم يجب أن يكون على الأقل 3 أحرف';
|
|
} else if (!/^[a-zA-Z0-9_]+$/.test(data.username)) {
|
|
errors.username = 'اسم المستخدم يجب أن يحتوي على أحرف وأرقام فقط';
|
|
}
|
|
}
|
|
|
|
if (data.email !== undefined) {
|
|
if (!data.email || data.email.trim().length === 0) {
|
|
errors.email = 'البريد الإلكتروني مطلوب';
|
|
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
|
|
errors.email = 'البريد الإلكتروني غير صحيح';
|
|
}
|
|
}
|
|
|
|
if (data.password !== undefined) {
|
|
if (!data.password || data.password.length === 0) {
|
|
errors.password = 'كلمة المرور مطلوبة';
|
|
} else if (data.password.length < VALIDATION.MIN_PASSWORD_LENGTH) {
|
|
errors.password = `كلمة المرور يجب أن تكون على الأقل ${VALIDATION.MIN_PASSWORD_LENGTH} أحرف`;
|
|
}
|
|
}
|
|
|
|
if (data.authLevel !== undefined) {
|
|
if (!Object.values(AUTH_LEVELS).includes(data.authLevel as any)) {
|
|
errors.authLevel = 'مستوى الصلاحية غير صحيح';
|
|
}
|
|
}
|
|
|
|
if (data.status !== undefined) {
|
|
if (!Object.values(USER_STATUS).includes(data.status as any)) {
|
|
errors.status = 'حالة المستخدم غير صحيحة';
|
|
}
|
|
}
|
|
|
|
return {
|
|
isValid: Object.keys(errors).length === 0,
|
|
errors,
|
|
};
|
|
}
|
|
|
|
// Customer validation
|
|
export function validateCustomer(data: {
|
|
name?: string;
|
|
phone?: string;
|
|
email?: string;
|
|
address?: string;
|
|
}) {
|
|
const errors: Record<string, string> = {};
|
|
|
|
if (data.name !== undefined) {
|
|
if (!data.name || data.name.trim().length === 0) {
|
|
errors.name = 'اسم العميل مطلوب';
|
|
} else if (data.name.length > VALIDATION.MAX_NAME_LENGTH) {
|
|
errors.name = `الاسم يجب أن يكون أقل من ${VALIDATION.MAX_NAME_LENGTH} حرف`;
|
|
}
|
|
}
|
|
|
|
if (data.phone && data.phone.trim().length > 0) {
|
|
if (!/^[\+]?[0-9\s\-\(\)]+$/.test(data.phone)) {
|
|
errors.phone = 'رقم الهاتف غير صحيح';
|
|
}
|
|
}
|
|
|
|
if (data.email && data.email.trim().length > 0) {
|
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
|
|
errors.email = 'البريد الإلكتروني غير صحيح';
|
|
}
|
|
}
|
|
|
|
return {
|
|
isValid: Object.keys(errors).length === 0,
|
|
errors,
|
|
};
|
|
}
|
|
|
|
// Vehicle validation
|
|
export function validateVehicle(data: {
|
|
plateNumber?: string;
|
|
bodyType?: string;
|
|
manufacturer?: string;
|
|
model?: string;
|
|
year?: number;
|
|
transmission?: string;
|
|
fuel?: string;
|
|
cylinders?: number;
|
|
engineDisplacement?: number;
|
|
useType?: string;
|
|
ownerId?: number;
|
|
}) {
|
|
const errors: Record<string, string> = {};
|
|
|
|
if (data.plateNumber !== undefined) {
|
|
if (!data.plateNumber || data.plateNumber.trim().length === 0) {
|
|
errors.plateNumber = 'رقم اللوحة مطلوب';
|
|
}
|
|
}
|
|
|
|
if (data.bodyType !== undefined) {
|
|
if (!data.bodyType || data.bodyType.trim().length === 0) {
|
|
errors.bodyType = 'نوع الهيكل مطلوب';
|
|
}
|
|
}
|
|
|
|
if (data.manufacturer !== undefined) {
|
|
if (!data.manufacturer || data.manufacturer.trim().length === 0) {
|
|
errors.manufacturer = 'الشركة المصنعة مطلوبة';
|
|
}
|
|
}
|
|
|
|
if (data.model !== undefined) {
|
|
if (!data.model || data.model.trim().length === 0) {
|
|
errors.model = 'الموديل مطلوب';
|
|
}
|
|
}
|
|
|
|
if (data.year !== undefined) {
|
|
if (!data.year || data.year < VALIDATION.MIN_YEAR || data.year > VALIDATION.MAX_YEAR) {
|
|
errors.year = `السنة يجب أن تكون بين ${VALIDATION.MIN_YEAR} و ${VALIDATION.MAX_YEAR}`;
|
|
}
|
|
}
|
|
|
|
if (data.transmission !== undefined) {
|
|
const validTransmissions = TRANSMISSION_TYPES.map(t => t.value);
|
|
if (!validTransmissions.includes(data.transmission as any)) {
|
|
errors.transmission = 'نوع ناقل الحركة غير صحيح';
|
|
}
|
|
}
|
|
|
|
if (data.fuel !== undefined) {
|
|
const validFuels = FUEL_TYPES.map(f => f.value);
|
|
if (!validFuels.includes(data.fuel as any)) {
|
|
errors.fuel = 'نوع الوقود غير صحيح';
|
|
}
|
|
}
|
|
|
|
if (data.cylinders !== undefined && data.cylinders !== null) {
|
|
if (data.cylinders < 1 || data.cylinders > VALIDATION.MAX_CYLINDERS) {
|
|
errors.cylinders = `عدد الأسطوانات يجب أن يكون بين 1 و ${VALIDATION.MAX_CYLINDERS}`;
|
|
}
|
|
}
|
|
|
|
if (data.engineDisplacement !== undefined && data.engineDisplacement !== null) {
|
|
if (data.engineDisplacement <= 0 || data.engineDisplacement > VALIDATION.MAX_ENGINE_DISPLACEMENT) {
|
|
errors.engineDisplacement = `سعة المحرك يجب أن تكون بين 0.1 و ${VALIDATION.MAX_ENGINE_DISPLACEMENT}`;
|
|
}
|
|
}
|
|
|
|
if (data.useType !== undefined) {
|
|
const validUseTypes = USE_TYPES.map(u => u.value);
|
|
if (!validUseTypes.includes(data.useType as any)) {
|
|
errors.useType = 'نوع الاستخدام غير صحيح';
|
|
}
|
|
}
|
|
|
|
if (data.ownerId !== undefined) {
|
|
if (!data.ownerId || data.ownerId <= 0) {
|
|
errors.ownerId = 'مالك المركبة مطلوب';
|
|
}
|
|
}
|
|
|
|
return {
|
|
isValid: Object.keys(errors).length === 0,
|
|
errors,
|
|
};
|
|
}
|
|
|
|
// Maintenance visit validation
|
|
export function validateMaintenanceVisit(data: {
|
|
vehicleId?: number;
|
|
customerId?: number;
|
|
maintenanceJobs?: Array<{typeId: number; job: string; notes?: string}>;
|
|
description?: string;
|
|
cost?: number;
|
|
paymentStatus?: string;
|
|
kilometers?: number;
|
|
nextVisitDelay?: number;
|
|
}) {
|
|
const errors: Record<string, string> = {};
|
|
|
|
if (data.vehicleId !== undefined) {
|
|
if (!data.vehicleId || data.vehicleId <= 0) {
|
|
errors.vehicleId = 'المركبة مطلوبة';
|
|
}
|
|
}
|
|
|
|
if (data.customerId !== undefined) {
|
|
if (!data.customerId || data.customerId <= 0) {
|
|
errors.customerId = 'العميل مطلوب';
|
|
}
|
|
}
|
|
|
|
if (data.maintenanceJobs !== undefined) {
|
|
if (!data.maintenanceJobs || data.maintenanceJobs.length === 0) {
|
|
errors.maintenanceJobs = 'يجب إضافة عمل صيانة واحد على الأقل';
|
|
} else {
|
|
// Validate each maintenance job
|
|
const invalidJobs = data.maintenanceJobs.filter(job =>
|
|
!job.typeId || job.typeId <= 0 || !job.job || job.job.trim().length === 0
|
|
);
|
|
if (invalidJobs.length > 0) {
|
|
errors.maintenanceJobs = 'جميع أعمال الصيانة يجب أن تحتوي على نوع ووصف صحيح';
|
|
}
|
|
}
|
|
}
|
|
|
|
if (data.description !== undefined) {
|
|
if (!data.description || data.description.trim().length === 0) {
|
|
errors.description = 'وصف الصيانة مطلوب';
|
|
} else if (data.description.length > VALIDATION.MAX_DESCRIPTION_LENGTH) {
|
|
errors.description = `الوصف يجب أن يكون أقل من ${VALIDATION.MAX_DESCRIPTION_LENGTH} حرف`;
|
|
}
|
|
}
|
|
|
|
if (data.cost !== undefined) {
|
|
if (data.cost === null || data.cost < VALIDATION.MIN_COST || data.cost > VALIDATION.MAX_COST) {
|
|
errors.cost = `التكلفة يجب أن تكون بين ${VALIDATION.MIN_COST} و ${VALIDATION.MAX_COST}`;
|
|
}
|
|
}
|
|
|
|
if (data.paymentStatus !== undefined) {
|
|
if (!Object.values(PAYMENT_STATUS).includes(data.paymentStatus as any)) {
|
|
errors.paymentStatus = 'حالة الدفع غير صحيحة';
|
|
}
|
|
}
|
|
|
|
if (data.kilometers !== undefined) {
|
|
if (data.kilometers === null || data.kilometers < 0) {
|
|
errors.kilometers = 'عدد الكيلومترات يجب أن يكون رقم موجب';
|
|
}
|
|
}
|
|
|
|
if (data.nextVisitDelay !== undefined) {
|
|
if (!data.nextVisitDelay || ![1, 2, 3, 4].includes(data.nextVisitDelay)) {
|
|
errors.nextVisitDelay = 'فترة الزيارة التالية يجب أن تكون 1، 2، 3، أو 4 أشهر';
|
|
}
|
|
}
|
|
|
|
return {
|
|
isValid: Object.keys(errors).length === 0,
|
|
errors,
|
|
};
|
|
}
|
|
|
|
// Expense validation
|
|
export function validateExpense(data: {
|
|
description?: string;
|
|
category?: string;
|
|
amount?: number;
|
|
}) {
|
|
const errors: Record<string, string> = {};
|
|
|
|
if (data.description !== undefined) {
|
|
if (!data.description || data.description.trim().length === 0) {
|
|
errors.description = 'وصف المصروف مطلوب';
|
|
} else if (data.description.length > VALIDATION.MAX_DESCRIPTION_LENGTH) {
|
|
errors.description = `الوصف يجب أن يكون أقل من ${VALIDATION.MAX_DESCRIPTION_LENGTH} حرف`;
|
|
}
|
|
}
|
|
|
|
if (data.category !== undefined) {
|
|
if (!data.category || data.category.trim().length === 0) {
|
|
errors.category = 'فئة المصروف مطلوبة';
|
|
}
|
|
}
|
|
|
|
if (data.amount !== undefined) {
|
|
if (data.amount === null || data.amount <= VALIDATION.MIN_COST || data.amount > VALIDATION.MAX_COST) {
|
|
errors.amount = `المبلغ يجب أن يكون بين ${VALIDATION.MIN_COST + 0.01} و ${VALIDATION.MAX_COST}`;
|
|
}
|
|
}
|
|
|
|
return {
|
|
isValid: Object.keys(errors).length === 0,
|
|
errors,
|
|
};
|
|
}
|
|
|
|
// Generic validation helpers
|
|
export function isValidDate(date: any): date is Date {
|
|
return date instanceof Date && !isNaN(date.getTime());
|
|
}
|
|
|
|
export function isValidNumber(value: any): value is number {
|
|
return typeof value === 'number' && !isNaN(value) && isFinite(value);
|
|
}
|
|
|
|
export function isValidString(value: any, minLength = 1): value is string {
|
|
return typeof value === 'string' && value.trim().length >= minLength;
|
|
}
|
|
|
|
export function sanitizeString(value: string): string {
|
|
return value.trim().replace(/\s+/g, ' ');
|
|
}
|
|
|
|
export function formatCurrency(amount: number): string {
|
|
return new Intl.NumberFormat('en-US', {
|
|
style: 'currency',
|
|
currency: 'JOD',
|
|
}).format(amount);
|
|
}
|
|
|
|
export function formatDate(date: Date, format: string = 'dd/MM/yyyy'): string {
|
|
const day = date.getDate().toString().padStart(2, '0');
|
|
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
const year = date.getFullYear();
|
|
const hours = date.getHours().toString().padStart(2, '0');
|
|
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
|
|
switch (format) {
|
|
case 'dd/MM/yyyy':
|
|
return `${day}/${month}/${year}`;
|
|
case 'dd/MM/yyyy HH:mm':
|
|
return `${day}/${month}/${year} ${hours}:${minutes}`;
|
|
default:
|
|
return date.toLocaleDateString('ar-SA');
|
|
}
|
|
} |