Files
starr-philproperties/cron/jobs/task_overdue.cron.js
T
kennethobsequio 1d12f04967 new commits
Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
2026-07-03 16:20:58 +08:00

74 lines
3.5 KiB
JavaScript

/***********************************************************************************************************************************************************************
* File Name : task_overdue.cron.js
* Type : Cron Job
* Description : Flips Task.status to 'overdue' once its deadline has passed,
* provided it isn't already 'completed' or 'overdue'. Purely
* an admin-facing lifecycle label — does NOT touch
* TaskCompletion/TaskLinkVisit/TaskProgress, does NOT affect
* per-user completion signals or client-side Ongoing/Done/
* Overdue bucketing, and does NOT block late submissions.
*
* Safety pattern:
* - Task.update is the primary operation and must always succeed.
* - AdminNotification.create is secondary — wrapped in its own
* try/catch so a notification failure never rolls back or
* suppresses the status flip. If it fails once, the next
* hourly run will insert its own summary for that batch.
*
* Schedule : Every hour, on the hour ("0 * * * *"). Registered by
* cron/admin.cron.js, not scheduled here directly.
*
* Author: Kenneth Obsequio (@lash0000)
* Date Created: Jun. 17, 2026
* Modified : Jun. 19, 2026
***********************************************************************************************************************************************************************/
const { Op } = require('sequelize');
const { Task } = require('../../models/task/task.mdl');
const AdminNotification = require('../../models/notifications/admin_notification.mdl');
const CronNotificationSetting = require('../../models/notifications/cron_notification_setting.mdl');
const { NOTIFICATION_REGISTRY } = require('../../data/notifications.data');
// ─── The actual sweep ────────────────────────────────────────────────────────
async function run() {
// ── 1. Primary: flip task statuses ───────────────────────────────────────
let affectedCount = 0;
try {
[affectedCount] = await Task.update(
{ status: 'overdue' },
{
where: {
deadline: { [Op.lt]: new Date() },
status: { [Op.notIn]: ['completed', 'overdue'] },
},
}
);
} catch (err) {
console.error('[CRON][TASK OVERDUE] Failed to update task statuses:', err);
return;
}
if (affectedCount === 0) return;
console.log(`[CRON][TASK OVERDUE] Marked ${affectedCount} task(s) as overdue.`);
// ── 2. Secondary: admin notification — isolated, never blocks step 1 ─────
// Skippable via /admin/notifications/settings — the status flip above always happens either way.
try {
const settings = await CronNotificationSetting.findOne({ where: { job_name: 'taskOverdue' } });
if (settings && !settings.enabled) return;
await AdminNotification.create(
NOTIFICATION_REGISTRY.task_overdue.build({ count: affectedCount })
);
} catch (err) {
console.error('[CRON][TASK OVERDUE] Failed to insert admin notification:', err);
}
}
module.exports = {
name: 'taskOverdue',
schedule: '0 * * * *',
run,
};