import { db } from '../config/database.js'; import { companies, companyReminders } from '../db/schema.js'; import { eq, desc, sql, and, lte, gte, isNull, or, inArray } from 'drizzle-orm'; import { NotFoundError, BadRequestError } from '../utils/errors.js'; import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js'; import { logCompanyReminderCreated, logCompanyReminderUpdated, logCompanyReminderDeleted } from './audit.service.js'; const ensureCompanyExists = async (companyId) => { const [company] = await db .select({ id: companies.id, name: companies.name }) .from(companies) .where(eq(companies.id, companyId)) .limit(1); if (!company) { throw new NotFoundError('Firma nenájdená'); } return company; }; export const getReminderById = async (reminderId) => { const [reminder] = await db .select() .from(companyReminders) .where(eq(companyReminders.id, reminderId)) .limit(1); if (!reminder) { throw new NotFoundError('Reminder nenájdený'); } return reminder; }; export const getRemindersByCompanyId = async (companyId) => { await ensureCompanyExists(companyId); const reminders = await db .select() .from(companyReminders) .where(eq(companyReminders.companyId, companyId)) .orderBy(companyReminders.dueDate, desc(companyReminders.createdAt)); return reminders; }; export const createReminder = async (companyId, data, auditContext = null) => { await ensureCompanyExists(companyId); const description = data.description?.trim(); if (!description) { throw new BadRequestError('Popis pripomienky je povinný'); } const [reminder] = await db .insert(companyReminders) .values({ companyId, description, dueDate: data.dueDate ? new Date(data.dueDate) : null, isChecked: data.isChecked ?? false, }) .returning(); if (auditContext) { await logCompanyReminderCreated(auditContext.userId, reminder.id, companyId, data.dueDate, auditContext.ipAddress, auditContext.userAgent); } return reminder; }; export const updateReminder = async (companyId, reminderId, data, auditContext = null) => { const reminder = await getReminderById(reminderId); if (reminder.companyId !== companyId) { throw new NotFoundError('Reminder nenájdený'); } const trimmedDescription = data.description !== undefined ? data.description.trim() : reminder.description; if (data.description !== undefined && !trimmedDescription) { throw new BadRequestError('Popis pripomienky je povinný'); } const [updatedReminder] = await db .update(companyReminders) .set({ description: trimmedDescription, dueDate: data.dueDate !== undefined ? (data.dueDate ? new Date(data.dueDate) : null) : reminder.dueDate, isChecked: data.isChecked !== undefined ? data.isChecked : reminder.isChecked, updatedAt: new Date(), }) .where(eq(companyReminders.id, reminderId)) .returning(); if (auditContext) { await logCompanyReminderUpdated(auditContext.userId, reminderId, companyId, reminder.dueDate, data.dueDate, auditContext.ipAddress, auditContext.userAgent); } return updatedReminder; }; export const deleteReminder = async (companyId, reminderId, auditContext = null) => { const reminder = await getReminderById(reminderId); if (reminder.companyId !== companyId) { throw new NotFoundError('Reminder nenájdený'); } await db.delete(companyReminders).where(eq(companyReminders.id, reminderId)); if (auditContext) { await logCompanyReminderDeleted(auditContext.userId, reminderId, companyId, reminder.dueDate, auditContext.ipAddress, auditContext.userAgent); } return { success: true, message: 'Reminder bol odstránený' }; }; export const getReminderSummary = async (userId = null, userRole = null) => { // Pre membera filtruj len pristupne firmy let accessibleCompanyIds = null; if (userRole && userRole !== 'admin' && userId) { accessibleCompanyIds = await getAccessibleResourceIds('company', userId); if (accessibleCompanyIds.length === 0) { return { total: 0, active: 0, completed: 0 }; } } let query = db .select({ total: sql`COUNT(*)::int`, active: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = false)::int`, completed: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = true)::int`, }) .from(companyReminders); if (accessibleCompanyIds !== null) { query = query.where(inArray(companyReminders.companyId, accessibleCompanyIds)); } const [row] = await query; return { total: row?.total ?? 0, active: row?.active ?? 0, completed: row?.completed ?? 0, }; }; export const getReminderCountsByCompany = async (userId = null, userRole = null) => { // Pre membera filtruj len pristupne firmy let accessibleCompanyIds = null; if (userRole && userRole !== 'admin' && userId) { accessibleCompanyIds = await getAccessibleResourceIds('company', userId); if (accessibleCompanyIds.length === 0) { return []; } } let query = db .select({ companyId: companyReminders.companyId, total: sql`COUNT(*)::int`, active: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = false)::int`, completed: sql`COUNT(*) FILTER (WHERE ${companyReminders.isChecked} = true)::int`, }) .from(companyReminders); if (accessibleCompanyIds !== null) { query = query.where(inArray(companyReminders.companyId, accessibleCompanyIds)); } const rows = await query .groupBy(companyReminders.companyId) .orderBy(desc(companyReminders.companyId)); return rows; }; /** * Get upcoming reminders for dashboard * Returns reminders due within the next 5 days that are not checked * Includes company name for display * For members: returns only reminders from companies they are assigned to */ export const getUpcomingReminders = async (userId = null, userRole = null) => { // Pre membera filtruj len pristupne firmy let accessibleCompanyIds = null; if (userRole && userRole !== 'admin' && userId) { accessibleCompanyIds = await getAccessibleResourceIds('company', userId); if (accessibleCompanyIds.length === 0) { return []; } } const now = new Date(); const fiveDaysFromNow = new Date(); fiveDaysFromNow.setDate(fiveDaysFromNow.getDate() + 5); const conditions = [ eq(companyReminders.isChecked, false), lte(companyReminders.dueDate, fiveDaysFromNow), gte(companyReminders.dueDate, now) ]; if (accessibleCompanyIds !== null) { conditions.push(inArray(companyReminders.companyId, accessibleCompanyIds)); } const reminders = await db .select({ id: companyReminders.id, description: companyReminders.description, dueDate: companyReminders.dueDate, isChecked: companyReminders.isChecked, companyId: companyReminders.companyId, companyName: companies.name, createdAt: companyReminders.createdAt, }) .from(companyReminders) .leftJoin(companies, eq(companyReminders.companyId, companies.id)) .where(and(...conditions)) .orderBy(companyReminders.dueDate); return reminders; };