feat: Complete AI Chat Agent, Admin Settings (MCP/AI), and Translations
continuous-integration/drone/push Build is passing
continuous-integration/drone/push Build is passing
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { IStorage } from "../storage";
|
||||
import { Task, InsertTask } from "@shared/schema";
|
||||
import { addDays, addWeeks, addMonths, addYears } from "date-fns";
|
||||
|
||||
export class RecurrenceService {
|
||||
private storage: IStorage;
|
||||
|
||||
constructor(storage: IStorage) {
|
||||
this.storage = storage;
|
||||
}
|
||||
|
||||
async handleTaskCompletion(task: Task): Promise<Task | undefined> {
|
||||
if (!task.isRecurring || !task.recurrenceInterval) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Determine the next due date
|
||||
const nextDate = this.calculateNextDate(task);
|
||||
if (!nextDate) return;
|
||||
|
||||
// Check if we passed the end date
|
||||
if (task.recurrenceEnd && nextDate > task.recurrenceEnd) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the next task
|
||||
// Note: startDate is nullable in schema but required in InsertTask type if strict.
|
||||
// We cast to any to avoid strict type issues with recent schema changes if types aren't perfectly synced.
|
||||
const newTask: any = {
|
||||
title: task.title,
|
||||
description: task.description,
|
||||
priority: task.priority,
|
||||
status: "todo",
|
||||
dueDate: nextDate,
|
||||
estimatedDuration: task.estimatedDuration,
|
||||
labelId: task.labelId,
|
||||
projectId: task.projectId,
|
||||
energyLevel: task.energyLevel,
|
||||
userId: task.userId,
|
||||
|
||||
// Copy recurrence settings
|
||||
isRecurring: true,
|
||||
recurrenceInterval: task.recurrenceInterval,
|
||||
recurrenceIntervalValue: task.recurrenceIntervalValue,
|
||||
recurrenceDays: task.recurrenceDays,
|
||||
recurrenceEnd: task.recurrenceEnd,
|
||||
|
||||
startDate: null,
|
||||
};
|
||||
|
||||
const created = await this.storage.createTask(newTask);
|
||||
|
||||
// Log it
|
||||
await this.storage.createAuditLog({
|
||||
userId: task.userId!,
|
||||
action: "CREATE",
|
||||
entityType: "TASK",
|
||||
entityId: created.id,
|
||||
details: { message: `Recurring task created from ${task.id}`, recurrence: task.recurrenceInterval },
|
||||
source: "SYSTEM"
|
||||
});
|
||||
|
||||
return created;
|
||||
}
|
||||
|
||||
private calculateNextDate(task: Task): Date | null {
|
||||
// Base calculation on the original due date or today if missing (though recurring tasks should have due dates)
|
||||
const baseDate = task.dueDate ? new Date(task.dueDate) : new Date();
|
||||
const intervalValue = task.recurrenceIntervalValue || 1;
|
||||
|
||||
let nextDate: Date = new Date(); // Default init to satisfy typescript, overwritten below
|
||||
|
||||
switch (task.recurrenceInterval) {
|
||||
case 'daily':
|
||||
nextDate = addDays(baseDate, intervalValue);
|
||||
break;
|
||||
case 'weekly':
|
||||
if (task.recurrenceDays && task.recurrenceDays.length > 0) {
|
||||
// Complex logic for specific days (e.g., Mon, Wed)
|
||||
// Simplified approach for MVP: Outlook style often just means "same day next week" if no days specified.
|
||||
// If days ARE specified, find the next matching day.
|
||||
|
||||
let potentialDate = addDays(baseDate, 1);
|
||||
// Search for next 14 days maximum to avoid infinite loops
|
||||
let found = false;
|
||||
for (let i = 0; i < 14; i++) {
|
||||
const dayOfWeek = potentialDate.getDay(); // 0=Sun, 1=Mon
|
||||
if (task.recurrenceDays.includes(dayOfWeek)) {
|
||||
nextDate = potentialDate;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
potentialDate = addDays(potentialDate, 1);
|
||||
}
|
||||
if (!found) nextDate = addWeeks(baseDate, intervalValue); // Fallback
|
||||
} else {
|
||||
nextDate = addWeeks(baseDate, intervalValue);
|
||||
}
|
||||
break;
|
||||
case 'monthly':
|
||||
nextDate = addMonths(baseDate, intervalValue);
|
||||
break;
|
||||
case 'yearly':
|
||||
nextDate = addYears(baseDate, intervalValue);
|
||||
break;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return nextDate;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user