Files
crm-server/src/validators/crm.validators.js
richardtekula 70fa080455 feat: Add user management APIs, status enum, enhanced notifications
- Add updateUser and resetUserPassword admin endpoints
- Change company status from boolean to enum (registered, lead, customer, inactive)
- Add 'important' event type to calendar validators and email templates
- Add 1-hour-before event notifications cron job
- Add 18:00 evening notifications for next-day events
- Add contact description field support
- Fix count() function usage in admin service
- Add SQL migrations for schema changes

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-15 09:41:29 +01:00

194 lines
8.0 KiB
JavaScript

import { z } from 'zod';
// Company validators
export const createCompanySchema = z.object({
name: z
.string({
required_error: 'Názov firmy je povinný',
})
.min(1, 'Názov firmy nemôže byť prázdny')
.max(255, 'Názov firmy môže mať maximálne 255 znakov'),
description: z.string().max(1000).optional(),
address: z.string().max(255).optional(),
city: z.string().max(100).optional(),
country: z.string().max(100).optional(),
phone: z.string().max(50).optional(),
email: z.string().email('Neplatný formát emailu').max(255).optional().or(z.literal('')),
website: z.string().url('Neplatný formát URL').max(255).optional().or(z.literal('')),
status: z.enum(['registered', 'lead', 'customer', 'inactive']).optional(),
});
export const updateCompanySchema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(1000).optional(),
address: z.string().max(255).optional(),
city: z.string().max(100).optional(),
country: z.string().max(100).optional(),
phone: z.string().max(50).optional(),
email: z.string().email('Neplatný formát emailu').max(255).optional().or(z.literal('')),
website: z.string().url('Neplatný formát URL').max(255).optional().or(z.literal('')),
status: z.enum(['registered', 'lead', 'customer', 'inactive']).optional(),
});
// Project validators
export const createProjectSchema = z.object({
name: z
.string({
required_error: 'Názov projektu je povinný',
})
.min(1, 'Názov projektu nemôže byť prázdny')
.max(255, 'Názov projektu môže mať maximálne 255 znakov'),
description: z.string().max(1000).optional(),
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('')),
status: z.enum(['active', 'completed', 'on_hold', 'cancelled']).optional(),
startDate: z.string().optional().or(z.literal('')),
endDate: z.string().optional().or(z.literal('')),
});
export const updateProjectSchema = z.object({
name: z.string().min(1).max(255).optional(),
description: z.string().max(1000).optional(),
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('').or(z.null())),
status: z.enum(['active', 'completed', 'on_hold', 'cancelled']).optional(),
startDate: z.string().optional().or(z.literal('').or(z.null())),
endDate: z.string().optional().or(z.literal('').or(z.null())),
});
// Todo validators
export const createTodoSchema = z.object({
title: z
.string({
required_error: 'Názov todo je povinný',
})
.min(1, 'Názov todo nemôže byť prázdny')
.max(255, 'Názov todo môže mať maximálne 255 znakov'),
description: z.string().max(1000).optional(),
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('')),
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('')),
assignedUserIds: z.array(z.string().uuid('Neplatný formát user ID')).optional(),
status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).optional(),
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
dueDate: z.string().optional().or(z.literal('')),
});
export const updateTodoSchema = z.object({
title: z.string().min(1).max(255).optional(),
description: z.string().max(1000).optional(),
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('').or(z.null())),
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('').or(z.null())),
assignedUserIds: z.array(z.string().uuid('Neplatný formát user ID')).optional(),
status: z.enum(['pending', 'in_progress', 'completed', 'cancelled']).optional(),
priority: z.enum(['low', 'medium', 'high', 'urgent']).optional(),
dueDate: z.string().optional().or(z.literal('').or(z.null())),
});
// Note validators (s voliteľným dueDate pre dátum a čas)
export const createNoteSchema = z.object({
title: z.string().max(255).optional(),
content: z
.string({
required_error: 'Obsah poznámky je povinný',
})
.min(1, 'Obsah poznámky nemôže byť prázdny')
.max(5000, 'Obsah poznámky môže mať maximálne 5000 znakov'),
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('')),
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('')),
todoId: z.string().uuid('Neplatný formát todo ID').optional().or(z.literal('')),
contactId: z.string().uuid('Neplatný formát contact ID').optional().or(z.literal('')),
dueDate: z.string().optional().or(z.literal('')), // ISO string s dátumom a časom (24h formát)
});
export const updateNoteSchema = z.object({
title: z.string().max(255).optional().or(z.literal('').or(z.null())),
content: z.string().min(1).max(5000).optional(),
companyId: z.string().uuid('Neplatný formát company ID').optional().or(z.literal('').or(z.null())),
projectId: z.string().uuid('Neplatný formát project ID').optional().or(z.literal('').or(z.null())),
todoId: z.string().uuid('Neplatný formát todo ID').optional().or(z.literal('').or(z.null())),
contactId: z.string().uuid('Neplatný formát contact ID').optional().or(z.literal('').or(z.null())),
dueDate: z.string().optional().or(z.literal('').or(z.null())), // ISO string s dátumom a časom (24h formát)
});
// Company reminder validators (with dueDate)
export const createCompanyReminderSchema = z.object({
description: z.string().min(1).max(1000),
dueDate: z.string().optional().or(z.literal('')),
isChecked: z.boolean().optional(),
});
export const updateCompanyReminderSchema = z.object({
description: z.string().min(1).max(1000).optional(),
dueDate: z.string().optional().or(z.literal('').or(z.null())),
isChecked: z.boolean().optional(),
}).refine(
(data) => data.description !== undefined || data.isChecked !== undefined || data.dueDate !== undefined,
{ message: 'Je potrebné zadať description, dueDate alebo isChecked' }
);
// Time Tracking validators
const optionalUuid = (message) =>
z
.preprocess(
(val) => {
if (val === undefined) return undefined;
if (val === null || val === '') return null;
return val;
},
z.string().uuid(message).nullable()
)
.optional();
const optionalDescription = z
.preprocess(
(val) => {
if (val === undefined) return undefined;
if (val === null) return null;
if (typeof val !== 'string') return val;
const trimmed = val.trim();
return trimmed === '' ? null : trimmed;
},
z.string().max(1000).nullable()
)
.optional();
export const startTimeEntrySchema = z.object({
projectId: optionalUuid('Neplatný formát project ID'),
todoId: optionalUuid('Neplatný formát todo ID'),
companyId: optionalUuid('Neplatný formát company ID'),
description: optionalDescription,
});
export const stopTimeEntrySchema = z.object({
projectId: optionalUuid('Neplatný formát project ID'),
todoId: optionalUuid('Neplatný formát todo ID'),
companyId: optionalUuid('Neplatný formát company ID'),
description: optionalDescription,
});
export const updateTimeEntrySchema = z.object({
startTime: z.string().optional(),
endTime: z.string().optional(),
projectId: optionalUuid('Neplatný formát project ID'),
todoId: optionalUuid('Neplatný formát todo ID'),
companyId: optionalUuid('Neplatný formát company ID'),
description: optionalDescription,
});
// Event validators
export const createEventSchema = z.object({
title: z.string().min(1, 'Názov je povinný'),
description: z.string().optional(),
type: z.enum(['meeting', 'event', 'important']).default('meeting'),
start: z.string().min(1, 'Začiatok je povinný'),
end: z.string().min(1, 'Koniec je povinný'),
assignedUserIds: z.array(z.string().uuid('Neplatný formát user ID')).optional(),
});
export const updateEventSchema = z.object({
title: z.string().min(1).optional(),
description: z.string().optional(),
type: z.enum(['meeting', 'event', 'important']).optional(),
start: z.string().optional(),
end: z.string().optional(),
assignedUserIds: z.array(z.string().uuid('Neplatný formát user ID')).optional(),
});