feat: AI Kurzy module, project/service documents, services SQL import
- Add AI Kurzy module with courses, participants, and registrations management - Add project documents and service documents features - Add service folders for document organization - Add SQL import queries for services from firmy.slovensko.ai - Update todo notifications and group messaging - Various API improvements and bug fixes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { db } from '../config/database.js';
|
||||
import { todos, todoUsers, notes, projects, companies, users } from '../db/schema.js';
|
||||
import { eq, desc, ilike, or, and, inArray } from 'drizzle-orm';
|
||||
import { eq, desc, ilike, or, and, inArray, lt, ne, sql } from 'drizzle-orm';
|
||||
import { NotFoundError } from '../utils/errors.js';
|
||||
import { getAccessibleResourceIds } from '../middlewares/auth/resourceAccessMiddleware.js';
|
||||
import { sendPushNotificationToUsers } from './push.service.js';
|
||||
@@ -471,3 +471,90 @@ export const getTodosByUserId = async (userId) => {
|
||||
.where(inArray(todos.id, todoIds))
|
||||
.orderBy(desc(todos.createdAt));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get count of overdue todos for a user
|
||||
* Overdue = dueDate < now AND status !== 'completed'
|
||||
* For members: only counts todos they are assigned to
|
||||
*/
|
||||
export const getOverdueCount = async (userId, userRole) => {
|
||||
const now = new Date();
|
||||
|
||||
// Get accessible todo IDs for non-admin users
|
||||
let accessibleTodoIds = null;
|
||||
if (userRole && userRole !== 'admin') {
|
||||
accessibleTodoIds = await getAccessibleResourceIds('todo', userId);
|
||||
if (accessibleTodoIds.length === 0) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const conditions = [
|
||||
lt(todos.dueDate, now),
|
||||
ne(todos.status, 'completed'),
|
||||
];
|
||||
|
||||
if (accessibleTodoIds !== null) {
|
||||
conditions.push(inArray(todos.id, accessibleTodoIds));
|
||||
}
|
||||
|
||||
const result = await db
|
||||
.select({ count: sql`count(*)::int` })
|
||||
.from(todos)
|
||||
.where(and(...conditions));
|
||||
|
||||
return result[0]?.count || 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get count of todos created by user that were completed but not yet notified
|
||||
* Returns todos where createdBy = userId AND status = 'completed' AND completedNotifiedAt IS NULL
|
||||
*/
|
||||
export const getCompletedByMeCount = async (userId) => {
|
||||
const result = await db
|
||||
.select({ count: sql`count(*)::int` })
|
||||
.from(todos)
|
||||
.where(
|
||||
and(
|
||||
eq(todos.createdBy, userId),
|
||||
eq(todos.status, 'completed'),
|
||||
sql`${todos.completedNotifiedAt} IS NULL`
|
||||
)
|
||||
);
|
||||
|
||||
return result[0]?.count || 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Mark all completed todos created by user as notified
|
||||
* Called when user opens the Todos page
|
||||
*/
|
||||
export const markCompletedAsNotified = async (userId) => {
|
||||
await db
|
||||
.update(todos)
|
||||
.set({ completedNotifiedAt: new Date() })
|
||||
.where(
|
||||
and(
|
||||
eq(todos.createdBy, userId),
|
||||
eq(todos.status, 'completed'),
|
||||
sql`${todos.completedNotifiedAt} IS NULL`
|
||||
)
|
||||
);
|
||||
|
||||
return { success: true };
|
||||
};
|
||||
|
||||
/**
|
||||
* Get combined todo counts for sidebar badges
|
||||
*/
|
||||
export const getTodoCounts = async (userId, userRole) => {
|
||||
const [overdueCount, completedByMeCount] = await Promise.all([
|
||||
getOverdueCount(userId, userRole),
|
||||
getCompletedByMeCount(userId),
|
||||
]);
|
||||
|
||||
return {
|
||||
overdueCount,
|
||||
completedByMeCount,
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user