feat: add due date reminders functionality with cron jobs and test scripts

This commit is contained in:
2025-12-19 09:54:27 +01:00
parent 8b11dc5083
commit 2b27583c28
7 changed files with 241 additions and 1 deletions

View File

@@ -40,6 +40,9 @@ RUN npm run build
COPY docker-entrypoint.sh /docker-entrypoint.sh
RUN chmod +x /docker-entrypoint.sh
# Make scripts executable
RUN chmod +x backup-db.mjs send-due-date-reminders.mjs
# Expose the default Next.js port
EXPOSE 3000

44
check-project-dates.mjs Normal file
View File

@@ -0,0 +1,44 @@
#!/usr/bin/env node
import db from "./src/lib/db.js";
import { parseISO, isAfter, startOfDay, addDays } from "date-fns";
const today = startOfDay(new Date());
const threeDaysFromNow = addDays(today, 3);
const oneDayFromNow = addDays(today, 1);
console.log(`Today: ${today.toISOString().split('T')[0]}`);
console.log(`3 days from now: ${threeDaysFromNow.toISOString().split('T')[0]}`);
console.log(`1 day from now: ${oneDayFromNow.toISOString().split('T')[0]}`);
const projects = db.prepare(`
SELECT project_name, finish_date, project_status
FROM projects
WHERE finish_date IS NOT NULL
AND project_status != 'fulfilled'
AND project_status != 'cancelled'
ORDER BY finish_date ASC
`).all();
console.log(`\nFound ${projects.length} active projects with due dates:`);
projects.forEach(project => {
try {
const finishDate = parseISO(project.finish_date);
const finishDateStart = startOfDay(finishDate);
const isDueIn3Days = finishDateStart.getTime() === threeDaysFromNow.getTime();
const isDueIn1Day = finishDateStart.getTime() === oneDayFromNow.getTime();
const isOverdue = isAfter(today, finishDateStart);
let status = '';
if (isDueIn3Days) status = '⚠️ DUE IN 3 DAYS';
else if (isDueIn1Day) status = '🚨 DUE IN 1 DAY';
else if (isOverdue) status = '❌ OVERDUE';
else status = '📅 Future';
console.log(`${status} - ${project.project_name}: ${project.finish_date.split('T')[0]} (${project.project_status})`);
} catch (error) {
console.log(`❌ Error parsing date for ${project.project_name}: ${project.finish_date}`);
}
});

View File

@@ -25,6 +25,13 @@ echo "⏰ Setting up daily backup cron job..."
echo "0 2 * * * cd /app && node backup-db.mjs >> /app/data/backup.log 2>&1" > /etc/cron.d/backup-cron
chmod 0644 /etc/cron.d/backup-cron
crontab /etc/cron.d/backup-cron
# Set up daily due date reminders cron job (runs at 3 AM daily)
echo "⏰ Setting up daily due date reminders cron job..."
echo "0 3 * * * cd /app && node send-due-date-reminders.mjs >> /app/data/reminders.log 2>&1" > /etc/cron.d/reminders-cron
chmod 0644 /etc/cron.d/reminders-cron
crontab -l | cat - /etc/cron.d/reminders-cron > /tmp/crontab.tmp && crontab /tmp/crontab.tmp
service cron start
# Start the development server

View File

@@ -29,7 +29,12 @@ echo "⏰ Setting up daily backup cron job..."
echo "0 2 * * * cd /app && /usr/local/bin/node backup-db.mjs >> /app/data/backup.log 2>&1" > /etc/cron.d/backup-cron
chmod 0644 /etc/cron.d/backup-cron
crontab /etc/cron.d/backup-cron
service cron start
# Set up daily due date reminders cron job (runs at 3 AM daily)
echo "⏰ Setting up daily due date reminders cron job..."
echo "0 3 * * * cd /app && /usr/local/bin/node send-due-date-reminders.mjs >> /app/data/reminders.log 2>&1" > /etc/cron.d/reminders-cron
chmod 0644 /etc/cron.d/reminders-cron
crontab -l | cat - /etc/cron.d/reminders-cron > /tmp/crontab.tmp && crontab /tmp/crontab.tmp
# Start the application
echo "✅ Starting production server..."

View File

@@ -10,6 +10,8 @@
"lint": "next lint",
"create-admin": "node scripts/create-admin.js",
"export-projects": "node export-projects-to-excel.mjs",
"send-due-date-reminders": "node send-due-date-reminders.mjs",
"test-due-date-reminders": "node test-due-date-reminders.mjs",
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",

107
send-due-date-reminders.mjs Normal file
View File

@@ -0,0 +1,107 @@
#!/usr/bin/env node
/**
* Daily script to send due date reminders for projects
* Runs nightly to check for projects due in 3 days and 1 day
*/
import db from "./src/lib/db.js";
import { createNotification, NOTIFICATION_TYPES } from "./src/lib/notifications.js";
import { addDays, isBefore, parseISO, startOfDay } from "date-fns";
async function sendDueDateReminders() {
try {
console.log("🔍 Checking for projects with upcoming due dates...");
const today = startOfDay(new Date());
const threeDaysFromNow = addDays(today, 3);
const oneDayFromNow = addDays(today, 1);
// Get projects that are not fulfilled and have finish dates
const projects = db.prepare(`
SELECT
p.project_id,
p.project_name,
p.finish_date,
p.address,
p.project_status,
c.customer
FROM projects p
LEFT JOIN contracts c ON p.contract_id = c.contract_id
WHERE
p.finish_date IS NOT NULL
AND p.project_status != 'fulfilled'
AND p.project_status != 'cancelled'
`).all();
console.log(`📋 Found ${projects.length} active projects with due dates`);
let remindersSent = 0;
for (const project of projects) {
try {
const finishDate = parseISO(project.finish_date);
const finishDateStart = startOfDay(finishDate);
// Check if due in 3 days
if (finishDateStart.getTime() === threeDaysFromNow.getTime()) {
await sendReminder(project, 3);
remindersSent++;
}
// Check if due in 1 day
else if (finishDateStart.getTime() === oneDayFromNow.getTime()) {
await sendReminder(project, 1);
remindersSent++;
}
} catch (error) {
console.error(`❌ Error processing project ${project.project_id}:`, error);
}
}
console.log(`✅ Sent ${remindersSent} due date reminders`);
} catch (error) {
console.error("❌ Error in due date reminder script:", error);
}
}
async function sendReminder(project, daysUntilDue) {
try {
// Get users who should receive notifications (admins and team leads)
const recipients = db.prepare(`
SELECT id, name, role
FROM users
WHERE role IN ('admin', 'team_lead') AND is_active = 1
`).all();
if (recipients.length === 0) {
console.log("⚠️ No active admin or team lead users found to notify");
return;
}
const dayText = daysUntilDue === 1 ? "dzień" : "dni";
const title = `Projekt kończy się za ${daysUntilDue} ${dayText}`;
const message = `Projekt "${project.project_name}" (${project.customer || 'Brak klienta'}) kończy się ${new Date(project.finish_date).toLocaleDateString('pl-PL')}. Adres: ${project.address || 'Brak adresu'}.`;
for (const user of recipients) {
await createNotification({
userId: user.id,
type: NOTIFICATION_TYPES.DUE_DATE_REMINDER,
title,
message,
resourceType: "project",
resourceId: project.project_id.toString(),
actionUrl: `/projects/${project.project_id}`,
priority: daysUntilDue === 1 ? "urgent" : "high"
});
console.log(`📢 Reminder sent to ${user.name} (${user.role}) for project: ${project.project_name}`);
}
} catch (error) {
console.error(`❌ Failed to send reminder for project ${project.project_id}:`, error);
}
}
// Run the script
sendDueDateReminders();

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env node
/**
* Test script to simulate due date reminders
* Creates a test project due in 3 days and runs the reminder script
*/
import db from "./src/lib/db.js";
import { addDays, format } from "date-fns";
async function createTestProject() {
try {
console.log("🧪 Creating test project due in 3 days...");
// Create a test contract first
const contractResult = db.prepare(`
INSERT INTO contracts (contract_number, contract_name, customer, date_signed)
VALUES (?, ?, ?, ?)
`).run('TEST-001', 'Test Contract', 'Test Customer', new Date().toISOString());
const contractId = contractResult.lastInsertRowid;
// Create a test project due in 3 days
const dueDate = addDays(new Date(), 3);
const projectResult = db.prepare(`
INSERT INTO projects (
contract_id, project_name, project_number, address,
finish_date, project_status
) VALUES (?, ?, ?, ?, ?, ?)
`).run(
contractId,
'Test Project - Due Soon',
'1/TEST-001',
'Test Address 123',
dueDate.toISOString(),
'in_progress_design'
);
console.log(`✅ Created test project due on ${format(dueDate, 'yyyy-MM-dd')}`);
console.log("🔄 Running due date reminders script...");
// Run the reminders script
const { execSync } = await import('child_process');
execSync('node send-due-date-reminders.mjs', { stdio: 'inherit' });
// Check if notifications were created
const notifications = db.prepare(`
SELECT * FROM notifications
WHERE type = 'due_date_reminder'
ORDER BY created_at DESC
LIMIT 5
`).all();
console.log(`📢 Found ${notifications.length} due date reminder notifications:`);
notifications.forEach(notif => {
console.log(` - ${notif.title}: ${notif.message.substring(0, 100)}...`);
});
// Clean up test data
console.log("🧹 Cleaning up test data...");
db.prepare('DELETE FROM projects WHERE project_name = ?').run('Test Project - Due Soon');
db.prepare('DELETE FROM contracts WHERE contract_number = ?').run('TEST-001');
db.prepare('DELETE FROM notifications WHERE type = ? AND title LIKE ?').run('due_date_reminder', 'Projekt kończy się za%');
console.log("✅ Test completed successfully!");
} catch (error) {
console.error("❌ Test failed:", error);
}
}
createTestProject();