mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-09-22 10:45:03 -04:00
feat(jobs): add background job queue with web/auto/manual scheduling
Introduce CodeIgniter Tasks-based job scheduling to support long-running background work (e.g. large CSV imports) without blocking requests. - composer.json/composer.lock: add codeigniter4/tasks (and its dependencies codeigniter4/queue, codeigniter4/settings) to power the task scheduler and settings-backed configuration. - app/Models/JobThrottle.php: new model for job_throttles table, providing exists/save/getAll/delete helpers to rate-limit or throttle job execution per throttle_id. - INSTALL.md: document the three trigger modes (Web default via request-hook processing, Auto via cron/Task Scheduler, Manual via admin UI button), including step-by-step Linux/Mac cron and Windows Task Scheduler setup, and Docker considerations (worker container, supervisor, or cron sidecar) since cron does not run inside a single-process container by default. Signed-off-by: objecttothis <17935339+objecttothis@users.noreply.github.com>
This commit is contained in:
1 parent
b4db867ca1
commit
84abb9a4af
4 files changed
+322
-2
No files matched your search
+62
@@ -77,6 +77,68 @@ Start the containers using the following command
|
||||
docker-compose up
|
||||
```
|
||||
|
||||
## Background Job Scheduling
|
||||
|
||||
OSPOS includes a background job queue (Office → Job Queue) for long-running tasks such as large CSV imports. It supports three trigger modes, configured on the Job Queue → Settings tab:
|
||||
|
||||
- **Web** (default) — no setup required. Jobs are processed via a request hook after page loads, using `fastcgi_finish_request()` where available. Works out of the box on shared hosting, VPS, and Docker.
|
||||
- **Auto** — a cron entry (Linux/Mac) or Task Scheduler task (Windows) triggers processing on a fixed interval. Recommended for VPS/dedicated servers with cron access.
|
||||
- **Manual** — an admin clicks a "Process All Jobs" button on the Job Queue → Utilities tab. Always available regardless of mode, useful for debugging.
|
||||
|
||||
### `auto` mode: Linux/Mac cron
|
||||
|
||||
Add the following entry to your crontab (`crontab -e`), adjusting the path to your OSPOS install:
|
||||
|
||||
```
|
||||
* * * * * cd /path/to/ospos && php spark tasks:run >> /dev/null 2>&1
|
||||
```
|
||||
|
||||
### `auto` mode: Windows Task Scheduler
|
||||
|
||||
1. Open Task Scheduler and create a new task.
|
||||
2. Trigger: `Daily`, check `Repeat task every: 5 minutes` (the fastest interval the GUI allows), `for a duration of: Indefinitely`, no expiration.
|
||||
- Don't use a `One time` trigger with a fixed repeat duration (e.g. `1 day`) — it stops repeating once that duration elapses instead of running forever.
|
||||
3. Action: start a program.
|
||||
- Program/script: `php.exe` (full path, e.g. `C:\php\php.exe`)
|
||||
- Arguments: `spark tasks:run`
|
||||
- Start in: **must be the OSPOS project root — the directory containing the `spark` file** (e.g. `C:\laragon\www\opensourcepos`, or `C:\wamp64\www\opensourcepos\public\..`). This is NOT your PHP installation directory. If `Start in` is wrong or blank, `spark` fails immediately with `Could not open input file: spark` and the window closes before you can read it.
|
||||
- Alternatively, avoid relying on `Start in` altogether by giving the full path to `spark` directly in Arguments: `Arguments: C:\laragon\www\opensourcepos\spark tasks:run`.
|
||||
4. On the **General** tab, select **"Run whether user is logged on or not"**. Without this, the task runs in your interactive session and briefly flashes a console window every time it fires.
|
||||
- You'll be prompted for your account password to save the task. If it's rejected (common with Microsoft accounts using Windows Hello — fingerprint/PIN/face login won't work here), check **"Do not store password"** instead. This limits the task to local computer resources only, which is sufficient for `spark tasks:run`.
|
||||
5. Save the task. It will invoke the scheduler every 5 minutes — less frequent than the once-per-minute cron example above, so jobs are processed in 5-minute batches instead.
|
||||
6. Before trusting the scheduled task, verify it manually: open `cmd.exe`, `cd` to the same `Start in` directory, and run the same `Program/script` + `Arguments`. You should see `Running Tasks...` then `Completed Running Tasks`, and a new heartbeat line in `writable/logs/`.
|
||||
|
||||
### `manual` mode
|
||||
|
||||
No setup is required. Set Mode to Manual on the Job Queue → Settings tab, then use the "Process All Jobs" button on the Utilities tab whenever you need jobs processed.
|
||||
|
||||
### Docker considerations
|
||||
|
||||
Docker containers are single-process by default, so cron does not run inside the OSPOS container out of the box — this is why **Web mode is the default** and works with zero extra Docker configuration.
|
||||
|
||||
If you want `auto` mode under Docker, pick one of the following:
|
||||
|
||||
**Option 1: separate worker container (recommended)**
|
||||
|
||||
Add a second service to your `docker-compose.yml` pointing at the same database:
|
||||
|
||||
```yaml
|
||||
worker:
|
||||
image: opensourcepos
|
||||
command: php spark tasks:run
|
||||
depends_on:
|
||||
- db
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
**Option 2: supervisor inside the container**
|
||||
|
||||
Add supervisor to the image and configure it to run both the web server and the scheduler. Works, but goes against the one-process-per-container convention — acceptable for simple single-container setups.
|
||||
|
||||
**Option 3: cron sidecar container**
|
||||
|
||||
Run a minimal sidecar container that fires `php spark tasks:run` every minute via cron, sharing the same network and database as the main container.
|
||||
|
||||
## Nginx install using Docker
|
||||
|
||||
Since OSPOS version `3.3.0` the Docker installation offers a reverse proxy based on Nginx with a Let's Encrypt TLS certificate termination (aka HTTPS connection).
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use CodeIgniter\Database\ResultInterface;
|
||||
use CodeIgniter\Model;
|
||||
|
||||
/**
|
||||
* JobThrottle class
|
||||
*/
|
||||
class JobThrottle extends Model
|
||||
{
|
||||
protected $table = 'job_throttles';
|
||||
protected $primaryKey = 'throttle_id';
|
||||
protected $useAutoIncrement = true;
|
||||
protected $useSoftDeletes = false;
|
||||
protected $allowedFields = [
|
||||
'max_count',
|
||||
'period',
|
||||
'deleted'
|
||||
];
|
||||
|
||||
/**
|
||||
* @param int $throttleId
|
||||
* @return bool
|
||||
*/
|
||||
public function exists(int $throttleId): bool
|
||||
{
|
||||
$builder = $this->db->table('job_throttles');
|
||||
$builder->where('throttle_id', $throttleId);
|
||||
|
||||
return ($builder->get()->getNumRows() >= 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $throttleData
|
||||
* @param int $throttleId
|
||||
* @return bool
|
||||
*/
|
||||
public function saveValue(array $throttleData, int $throttleId): bool
|
||||
{
|
||||
$throttleDataToSave = [
|
||||
'max_count' => $throttleData['max_count'],
|
||||
'period' => $throttleData['period'],
|
||||
'deleted' => 0
|
||||
];
|
||||
|
||||
if (!$this->exists($throttleId)) {
|
||||
$builder = $this->db->table('job_throttles');
|
||||
return $builder->insert($throttleDataToSave);
|
||||
}
|
||||
|
||||
$builder = $this->db->table('job_throttles');
|
||||
$builder->where('throttle_id', $throttleId);
|
||||
|
||||
return $builder->update($throttleDataToSave);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ResultInterface
|
||||
*/
|
||||
public function getAll(): ResultInterface
|
||||
{
|
||||
$builder = $this->db->table('job_throttles');
|
||||
$builder->where('deleted', 0);
|
||||
|
||||
return $builder->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes one throttle
|
||||
*/
|
||||
public function delete($throttleId = null, bool $purge = false): bool
|
||||
{
|
||||
$builder = $this->db->table('job_throttles');
|
||||
$builder->where('throttle_id', $throttleId);
|
||||
|
||||
return $builder->update(['deleted' => 1]);
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@
|
||||
"php": "^8.2",
|
||||
"ext-intl": "*",
|
||||
"codeigniter4/framework": "4.7.4",
|
||||
"codeigniter4/tasks": "^1.0",
|
||||
"dompdf/dompdf": "^3.1.6",
|
||||
"ezyang/htmlpurifier": "^4.17",
|
||||
"laminas/laminas-escaper": "2.18.0",
|
||||
|
||||
Generated
+179
-2
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "caa6ba9519694ccf54b8a487640e87fd",
|
||||
"content-hash": "7f9d30d341a97be2979b62b5732ee285",
|
||||
"packages": [
|
||||
{
|
||||
"name": "codeigniter4/framework",
|
||||
@@ -83,6 +83,183 @@
|
||||
},
|
||||
"time": "2026-07-07T09:23:35+00:00"
|
||||
},
|
||||
{
|
||||
"name": "codeigniter4/queue",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/codeigniter4/queue.git",
|
||||
"reference": "87f736a69a0d64bce0c40bcb1a3f966250f70f03"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/codeigniter4/queue/zipball/87f736a69a0d64bce0c40bcb1a3f966250f70f03",
|
||||
"reference": "87f736a69a0d64bce0c40bcb1a3f966250f70f03",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeigniter4/devkit": "^1.3",
|
||||
"codeigniter4/framework": "^4.3",
|
||||
"php-amqplib/php-amqplib": "^3.7",
|
||||
"phpstan/phpstan-strict-rules": "^2.0",
|
||||
"predis/predis": "^2.0 || ^3.0"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-redis": "If you want to use RedisHandler",
|
||||
"php-amqplib/php-amqplib": "If you want to use RabbitMQHandler",
|
||||
"predis/predis": "If you want to use PredisHandler"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"CodeIgniter\\Queue\\": "src"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"**/Database/Migrations/**"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "michalsn",
|
||||
"homepage": "https://github.com/michalsn",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Queues for CodeIgniter 4 framework",
|
||||
"homepage": "https://github.com/codeigniter4/queue",
|
||||
"keywords": [
|
||||
"codeigniter",
|
||||
"codeigniter4",
|
||||
"database",
|
||||
"predis",
|
||||
"queue",
|
||||
"redis"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/codeigniter4/queue/issues",
|
||||
"source": "https://github.com/codeigniter4/queue/tree/v1.0.1"
|
||||
},
|
||||
"time": "2026-07-23T17:03:38+00:00"
|
||||
},
|
||||
{
|
||||
"name": "codeigniter4/settings",
|
||||
"version": "v2.4.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/codeigniter4/settings.git",
|
||||
"reference": "7cbd2e2146e32211cc8194d9c35632631500adad"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/codeigniter4/settings/zipball/7cbd2e2146e32211cc8194d9c35632631500adad",
|
||||
"reference": "7cbd2e2146e32211cc8194d9c35632631500adad",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeigniter4/devkit": "^1.3",
|
||||
"codeigniter4/framework": "^4.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"CodeIgniter\\Settings\\": "src"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"**/Database/Migrations/**"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Lonnie Ezell",
|
||||
"email": "lonnieje@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Settings library for CodeIgniter 4",
|
||||
"homepage": "https://github.com/codeigniter4/settings",
|
||||
"keywords": [
|
||||
"Settings",
|
||||
"codeigniter",
|
||||
"codeigniter4"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/codeigniter4/settings/issues",
|
||||
"source": "https://github.com/codeigniter4/settings/tree/v2.4.0"
|
||||
},
|
||||
"time": "2026-07-30T16:03:46+00:00"
|
||||
},
|
||||
{
|
||||
"name": "codeigniter4/tasks",
|
||||
"version": "v1.0.1",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/codeigniter4/tasks.git",
|
||||
"reference": "30a329a040f86e49d723b7c2eaa64836a703eb55"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/codeigniter4/tasks/zipball/30a329a040f86e49d723b7c2eaa64836a703eb55",
|
||||
"reference": "30a329a040f86e49d723b7c2eaa64836a703eb55",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"codeigniter4/queue": "^1.0",
|
||||
"codeigniter4/settings": "^2.0",
|
||||
"ext-json": "*",
|
||||
"php": "^8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"codeigniter4/devkit": "^1.3",
|
||||
"codeigniter4/framework": "^4.3"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"CodeIgniter\\Tasks\\": "src"
|
||||
},
|
||||
"exclude-from-classmap": [
|
||||
"**/Database/Migrations/**"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Lonnie Ezell",
|
||||
"email": "lonnieje@gmail.com",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "Task Scheduler for CodeIgniter 4",
|
||||
"homepage": "https://github.com/codeigniter4/tasks",
|
||||
"keywords": [
|
||||
"codeigniter",
|
||||
"codeigniter4",
|
||||
"cron",
|
||||
"task scheduling"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/codeigniter4/tasks/issues",
|
||||
"source": "https://github.com/codeigniter4/tasks/tree/v1.0.1"
|
||||
},
|
||||
"time": "2026-03-30T05:38:19+00:00"
|
||||
},
|
||||
{
|
||||
"name": "dompdf/dompdf",
|
||||
"version": "v3.1.6",
|
||||
@@ -5546,5 +5723,5 @@
|
||||
"ext-intl": "*"
|
||||
},
|
||||
"platform-dev": {},
|
||||
"plugin-api-version": "2.9.0"
|
||||
"plugin-api-version": "2.6.0"
|
||||
}
|
||||
Reference in new issue
Block a user