mirror of
https://github.com/opensourcepos/opensourcepos.git
synced 2026-11-08 23:43:32 -05:00
- 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>
114 lines
3.6 KiB
PHP
114 lines
3.6 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use CodeIgniter\Model;
|
|
|
|
/**
|
|
* Tracks progress of a queued CSV import (issue #3833 Phase 3). Each row of
|
|
* an import is pushed as an independent job, so no single job knows it is
|
|
* the last one — increment() resolves this with an atomic counter, and only
|
|
* the job that pushes the batch to totals returns true from isComplete() so
|
|
* 'import_completed' fires exactly once.
|
|
*/
|
|
class ImportBatch extends Model
|
|
{
|
|
protected $table = 'import_batches';
|
|
protected $primaryKey = 'id';
|
|
protected $useAutoIncrement = false;
|
|
protected $useTimestamps = true;
|
|
protected $allowedFields = [
|
|
'id',
|
|
'type',
|
|
'total',
|
|
'completed',
|
|
'failed',
|
|
'status',
|
|
];
|
|
|
|
public function create(string $batchId, string $type, int $total): void
|
|
{
|
|
$this->insert([
|
|
'id' => $batchId,
|
|
'type' => $type,
|
|
'total' => $total,
|
|
'status' => $total > 0 ? 'processing' : 'completed',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Atomically increments completed or failed for the batch and reports
|
|
* whether this call finished it. Locks the row for the duration of the
|
|
* transaction so concurrent workers incrementing the same batch can't
|
|
* both observe completion.
|
|
*
|
|
* @return bool True if this call completed the batch (completed + failed = total).
|
|
*/
|
|
public function increment(string $batchId, bool $rowFailed): bool
|
|
{
|
|
$this->db->transStart();
|
|
|
|
$sql = $this->db->table($this->table)
|
|
->where('id', $batchId)
|
|
->getCompiledSelect();
|
|
|
|
$batch = $this->db->query($sql . ' FOR UPDATE')->getRowArray();
|
|
|
|
$column = $rowFailed ? 'failed' : 'completed';
|
|
|
|
$this->db->table($this->table)
|
|
->where('id', $batchId)
|
|
->set($column, $column . ' + 1', false)
|
|
->update();
|
|
|
|
$completed = ($batch['completed'] ?? 0) + ($rowFailed ? 0 : 1);
|
|
$failed = ($batch['failed'] ?? 0) + ($rowFailed ? 1 : 0);
|
|
$total = $batch['total'] ?? 0;
|
|
$isComplete = ($completed + $failed) >= $total;
|
|
|
|
if ($isComplete) {
|
|
$this->db->table($this->table)
|
|
->where('id', $batchId)
|
|
->update(['status' => $failed > 0 ? 'partial' : 'completed']);
|
|
}
|
|
|
|
$this->db->transComplete();
|
|
|
|
return $isComplete;
|
|
}
|
|
|
|
/**
|
|
* Deletes batches finished more than $retentionDays ago, but only once no
|
|
* row referencing their batch_id remains in queue_jobs or
|
|
* queue_jobs_failed (e.g. a failed row kept alive by a longer failed-job
|
|
* retention window). 0 means keep until manual purge.
|
|
*/
|
|
public function purgeFinished(int $retentionDays): void
|
|
{
|
|
if ($retentionDays <= 0) {
|
|
return;
|
|
}
|
|
|
|
$cutoff = date('Y-m-d H:i:s', strtotime("-{$retentionDays} days"));
|
|
|
|
$candidates = $this->db->table($this->table)
|
|
->select('id')
|
|
->whereIn('status', ['completed', 'partial'])
|
|
->where('updated_at <=', $cutoff)
|
|
->get()
|
|
->getResultArray();
|
|
|
|
foreach ($candidates as $batch) {
|
|
$batchId = $batch['id'];
|
|
|
|
$stillReferenced = $this->db->table('queue_jobs')->where('batch_id', $batchId)->countAllResults() > 0
|
|
|| $this->db->table('queue_jobs_failed')->where('batch_id', $batchId)->countAllResults() > 0;
|
|
|
|
if (!$stillReferenced) {
|
|
$this->db->table($this->table)->where('id', $batchId)->delete();
|
|
model(ImportBatchContext::class)->deleteForBatch($batchId);
|
|
}
|
|
}
|
|
}
|
|
}
|