- Add import_batch_contexts table plus ImportBatchContext model so
per-batch data (e.g. Items CSV attribute definitions) is stored
once and referenced by context_id, instead of duplicated into
every queued row's job payload.
- Extend queue_jobs, queue_jobs_failed, and new queue_paused_jobs
tables with batch_id/context_id columns and FKs (ON DELETE
RESTRICT) back to import_batch_contexts, so context rows cannot
be purged while any job still references them.
- Introduce ImportBatchService.pushRow() to centralize queuing a
batch row with batch/context stamping and priority; update
Customers and Items controllers to use it instead of calling
importBatch->create() and queue->push() separately.
- Add queue_paused_jobs table and Jobs::postPauseJobs()/pauseQueues()
to move pending jobs out of queue_jobs into a paused table per
queue (all core queues or a selected subset), leaving in-flight
jobs untouched (issue #3833 pause/resume design).
- Change Jobs::getEditJob()/postSaveJob() routes to accept separate
source/id path segments instead of a combined uid string.
- Add test coverage for ImportBatchContext, ImportBatchService, and
ImportBatch (including purge-retains-while-referenced guards).
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Adds the queue_jobs, queue_jobs_failed, and job_throttles tables and wires
up BoundedQueueWorker (deadline-bounded queue draining, shared by the
JobRunner filter for web/auto mode and the manual "Process Jobs" UI) gated
by JobThrottleGate. Adds retry limit, retention, and auto-purge settings
to the Job Queue settings tab, and a manual per-queue processing UI on the
Utilities tab. Also adds a second-granularity throttle period and
BaseOsposJob as the shared base for core queue jobs.
This is issue #3833 Phase 2 — no real job types are queued yet.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduce a soft, log-only per-task time budget (jobs_task_max_seconds)
separate from the existing web request budget (jobs_web_max_seconds),
since PHP cannot preempt a running task mid-execution.
- Config: add taskMaxSeconds default (30s) to app/Config/Jobs.php
- Controller: validate and persist task_max_seconds in
Jobs::postSaveSettings
- BoundedTaskRunner: accept taskMaxSeconds, measure task runtime via
microtime, log a warning when a task exceeds the threshold
- JobRunner: read jobs_task_max_seconds from config and pass it to
BoundedTaskRunner
- Migration: add AddJobsTaskMaxSecondsConfigKey to seed default
app_config value
- Language: add en strings for label, validation message, and
enabled/disabled tooltips
- View: add task_max_seconds form field to settings_config.php,
wired to same web-mode enable/disable toggle as web_max_seconds
- Tests: update JobsControllerTest to cover new field in save/
validation scenarios
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Bug: saveThrottles validated and saved throttles in same loop.
Invalid entry mid-loop stopped processing but left already-saved
throttles committed, and omitted throttles could still get deleted
via notToDelete tracking — partial/inconsistent state on failure.
- app/Controllers/Jobs.php: split into two passes — validate all
throttleData entries first; on any invalid entry, roll back
transaction and return failure immediately before touching DB.
Only save/delete once full payload validated. Drop now-redundant
$success flag folded into invalid-entry early return.
- tests/Controllers/JobsControllerTest.php: add test asserting
existing throttles stay unchanged in DB when payload contains
invalid throttle data.
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
Add PHPUnit tests for Jobs feature to lock in current
behavior before further changes.
- JobsControllerTest: covers /jobs index render, saveSettings
validation (invalid mode, non-natural max_seconds) and
success path, saveThrottles save/soft-delete-missing/invalid
period handling, throttles partial render, and stub
processAllJobs/processSelectedJobs endpoints
- JobThrottleModelTest: covers exists(), saveValue() insert vs
update, getAll() excluding soft-deleted rows, and delete()
soft-delete behavior
Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>