Files
crm-server/src/services/company-reminder.service.js
richardtekula 3aba6c2955 refactor: Move audit logging from controllers into services
Add auditContext parameter to service mutating functions. Services now
call audit log functions internally when auditContext is provided.
Controllers pass { userId, ipAddress, userAgent } and no longer import
audit service or fetch extra data for audit purposes.

Files modified:
- 10 service files: added audit imports and auditContext parameter
- 9 controller files: removed audit imports and calls

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 07:39:41 +01:00

230 lines
7.1 KiB
JavaScript

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;
};