mirror of
https://github.com/Studio-Saelix/sencho.git
synced 2026-08-07 17:34:23 +00:00
fix: fire a one-time schedule on the exact chosen date and time (#1497)
Simple "Once" schedules compiled to a 5-field cron, which has no year field, so the scheduler computed the next run as the next annual occurrence. A date chosen for a later year ran a year early, and a time already elapsed today ran a year late, contradicting the UI promise that the task fires on the chosen date. One-time schedules now send the chosen absolute timestamp (run_at) and the backend pins next_run_at to it instead of the cron-derived next run, so the run fires on the exact selected instant including the year. The Simple-mode validation now compares the full chosen instant against the current time, so a time earlier today is rejected as past rather than silently deferred a year. run_at is validated as a finite, future epoch-millisecond timestamp on create and update. The enable/disable toggle preserves a one-shot's pinned next_run_at (its yearless cron cannot reconstruct the chosen year), so re-enabling restores the exact instant. Recurring shapes and Advanced mode are unchanged (cron stays authoritative).
This commit is contained in:
@@ -22,6 +22,7 @@ import {
|
||||
buildCron,
|
||||
parseCron,
|
||||
getSimpleScheduleError,
|
||||
getOnceRunAt,
|
||||
type SimpleSchedule,
|
||||
} from '@/lib/scheduling';
|
||||
import { ScheduleSimplePanel } from './ScheduleSimplePanel';
|
||||
@@ -277,6 +278,11 @@ export default function ScheduledOperationsView({ filterNodeId, onClearFilter, p
|
||||
// backend stores; Advanced mode sends the raw expression as-is.
|
||||
const cronExpression = scheduleMode === 'simple' ? buildCron(simpleSchedule) : formCron;
|
||||
|
||||
// A one-time ('once') Simple schedule pins its exact run instant (including
|
||||
// year) via run_at, because the 5-field cron cannot encode a year. null for
|
||||
// every recurring shape and for Advanced mode, where the cron is authoritative.
|
||||
const runAt = scheduleMode === 'simple' ? getOnceRunAt(simpleSchedule) : null;
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
name: formName,
|
||||
target_type: actionDef.targetType,
|
||||
@@ -284,6 +290,7 @@ export default function ScheduledOperationsView({ filterNodeId, onClearFilter, p
|
||||
cron_expression: cronExpression,
|
||||
enabled: formEnabled,
|
||||
delete_after_run: formDeleteAfterRun,
|
||||
run_at: runAt,
|
||||
target_id: actionDef.requiresStack ? formTargetId : null,
|
||||
node_id: actionDef.requiresNode && formNodeId ? parseInt(formNodeId, 10) : null,
|
||||
prune_targets: formAction === 'prune' && formPruneTargets.length > 0 ? formPruneTargets : null,
|
||||
|
||||
Reference in New Issue
Block a user