This repository has been archived on 2026-03-21. You can view files and clone it, but cannot push or open issues or pull requests.
payfrit-biz/api/admin/scheduledTasks/runDue.cfm
John Mizerek 1210249f54 Normalize database column and table names across entire codebase
Update all SQL queries, query result references, and ColdFusion code to match
the renamed database schema. Tables use plural CamelCase, PKs are all `ID`,
column prefixes stripped (e.g. BusinessName→Name, UserFirstName→FirstName).

Key changes:
- Strip table-name prefixes from all column references (Businesses, Users,
  Addresses, Hours, Menus, Categories, Items, Stations, Orders,
  OrderLineItems, Tasks, TaskCategories, TaskRatings, QuickTaskTemplates,
  ScheduledTaskDefinitions, ChatMessages, Beacons, ServicePoints, Employees,
  VisitorTrackings, ApiPerfLogs, tt_States, tt_Days, tt_AddressTypes,
  tt_OrderTypes, tt_TaskTypes)
- Rename PK references from {TableName}ID to ID in all queries
- Rewrite 7 admin beacon files to use ServicePoints.BeaconID instead of
  dropped lt_Beacon_Businesses_ServicePoints link table
- Rewrite beacon assignment files (list, save, delete) for new schema
- Fix FK references incorrectly changed to ID (OrderLineItems.OrderID,
  Categories.MenuID, Tasks.CategoryID, ServicePoints.BeaconID)
- Update Addresses: AddressLat→Latitude, AddressLng→Longitude
- Update Users: UserPassword→Password, UserIsEmailVerified→IsEmailVerified,
  UserIsActive→IsActive, UserBalance→Balance, etc.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 15:39:12 -08:00

151 lines
5.7 KiB
Text

<cfsetting showdebugoutput="false" requesttimeout="60">
<cfsetting enablecfoutputonly="true">
<cfcontent type="application/json; charset=utf-8" reset="true">
<cfscript>
// Process all due scheduled tasks
// Called by cron every minute (or manually for testing)
// Creates Tasks entries for any ScheduledTaskDefinitions that are due
// Public endpoint - no auth required (should be restricted by IP in production)
function apiAbort(required struct payload) {
writeOutput(serializeJSON(payload));
abort;
}
// Calculate next run time from cron expression
function calculateNextRun(required string cronExpression) {
var parts = listToArray(cronExpression, " ");
if (arrayLen(parts) != 5) {
return dateAdd("d", 1, now());
}
var cronMinute = parts[1];
var cronHour = parts[2];
var cronDay = parts[3];
var cronMonth = parts[4];
var cronWeekday = parts[5];
// Start from current time + 1 minute
var checkDate = dateAdd("n", 1, now());
checkDate = createDateTime(year(checkDate), month(checkDate), day(checkDate), hour(checkDate), minute(checkDate), 0);
var maxIterations = 400 * 24 * 60; // 400 days in minutes
var iterations = 0;
while (iterations < maxIterations) {
var matchMinute = (cronMinute == "*" || (isNumeric(cronMinute) && minute(checkDate) == int(cronMinute)));
var matchHour = (cronHour == "*" || (isNumeric(cronHour) && hour(checkDate) == int(cronHour)));
var matchDay = (cronDay == "*" || (isNumeric(cronDay) && day(checkDate) == int(cronDay)));
var matchMonth = (cronMonth == "*" || (isNumeric(cronMonth) && month(checkDate) == int(cronMonth)));
var dow = dayOfWeek(checkDate) - 1; // Convert to 0-based (0=Sunday)
var matchWeekday = (cronWeekday == "*");
if (!matchWeekday) {
if (find("-", cronWeekday)) {
var range = listToArray(cronWeekday, "-");
if (arrayLen(range) == 2 && isNumeric(range[1]) && isNumeric(range[2])) {
matchWeekday = (dow >= int(range[1]) && dow <= int(range[2]));
}
} else if (isNumeric(cronWeekday)) {
matchWeekday = (dow == int(cronWeekday));
}
}
if (matchMinute && matchHour && matchDay && matchMonth && matchWeekday) {
return checkDate;
}
checkDate = dateAdd("n", 1, checkDate);
iterations++;
}
return dateAdd("d", 1, now());
}
try {
// Find all active scheduled tasks that are due
dueTasks = queryExecute("
SELECT
ScheduledTaskID,
BusinessID as BusinessID,
TaskCategoryID as CategoryID,
Title as Title,
Details as Details,
CronExpression as CronExpression,
COALESCE(ScheduleType, 'cron') as ScheduleType,
IntervalMinutes as IntervalMinutes
FROM ScheduledTaskDefinitions
WHERE IsActive = 1
AND NextRunOn <= NOW()
", {}, { datasource: "payfrit" });
createdTasks = [];
for (task in dueTasks) {
// Create the actual task (ClaimedByUserID=0 means unclaimed/pending)
queryExecute("
INSERT INTO Tasks (
BusinessID, CategoryID, TaskTypeID,
Title, Details, CreatedOn, ClaimedByUserID
) VALUES (
:businessID, :categoryID, :typeID,
:title, :details, NOW(), 0
)
", {
businessID: { value: task.BusinessID, cfsqltype: "cf_sql_integer" },
categoryID: { value: task.CategoryID, cfsqltype: "cf_sql_integer", null: isNull(task.CategoryID) },
typeID: { value: 0, cfsqltype: "cf_sql_integer" },
title: { value: task.Title, cfsqltype: "cf_sql_varchar" },
details: { value: task.Details, cfsqltype: "cf_sql_longvarchar", null: isNull(task.Details) }
}, { datasource: "payfrit" });
qNew = queryExecute("SELECT LAST_INSERT_ID() as newID", [], { datasource: "payfrit" });
// Calculate next run based on schedule type
if (task.ScheduleType == "interval_after_completion" && !isNull(task.IntervalMinutes) && task.IntervalMinutes > 0) {
// After-completion interval: don't schedule next run until task is completed
// Set to far future (effectively paused until task completion triggers recalculation)
nextRun = javaCast("null", "");
} else if (task.ScheduleType == "interval" && !isNull(task.IntervalMinutes) && task.IntervalMinutes > 0) {
// Fixed interval: next run = NOW + interval minutes
nextRun = dateAdd("n", task.IntervalMinutes, now());
} else {
// Cron-based: use cron parser
nextRun = calculateNextRun(task.CronExpression);
}
queryExecute("
UPDATE ScheduledTaskDefinitions SET
LastRunOn = NOW(),
NextRunOn = :nextRun
WHERE ID = :id
", {
nextRun: { value: nextRun, cfsqltype: "cf_sql_timestamp", null: isNull(nextRun) },
id: { value: task.ID, cfsqltype: "cf_sql_integer" }
}, { datasource: "payfrit" });
arrayAppend(createdTasks, {
"ScheduledTaskID": task.ID,
"TaskID": qNew.newID,
"BusinessID": task.BusinessID,
"Title": task.Title
});
}
apiAbort({
"OK": true,
"MESSAGE": "Processed #arrayLen(createdTasks)# scheduled task(s)",
"CREATED_TASKS": createdTasks,
"CHECKED_COUNT": dueTasks.recordCount,
"RAN_AT": dateTimeFormat(now(), "yyyy-mm-dd HH:nn:ss")
});
} catch (any e) {
apiAbort({
"OK": false,
"ERROR": "server_error",
"MESSAGE": e.message
});
}
</cfscript>