Add unit and integration tests for economic_transfer_queue and related endpoints, replacing synchronous fallback methods with queue-based processing.
This commit is contained in:
@@ -36,9 +36,22 @@ class economic_transfer_queue
|
||||
{
|
||||
global $db;
|
||||
|
||||
$transfer_type = $this->validateTransferType($transfer_type);
|
||||
$max_attempts = max(1, min(10, $max_attempts));
|
||||
$created_by = max(0, $created_by);
|
||||
$max_attempts = max(1, min(10, $max_attempts));
|
||||
$transfer_type = $this->validateTransferType($transfer_type);
|
||||
$payload = $this->normalizePayloadForTransferType($transfer_type, $payload, $created_by);
|
||||
|
||||
$active_job = $this->findActiveJobByTarget($transfer_type, $payload);
|
||||
if ($active_job !== null) {
|
||||
$target_label = $this->buildTargetLabel($transfer_type, $payload);
|
||||
$this->logQueueEvent(
|
||||
1,
|
||||
$created_by,
|
||||
'ECONOMIC_TRANSFER_JOB_DEDUPED',
|
||||
'Transfer job deduped for active target (' . $target_label . '), returning existing job #' . (int)($active_job['id'] ?? 0)
|
||||
);
|
||||
return $active_job;
|
||||
}
|
||||
|
||||
$payload_json = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
if ($payload_json === false) {
|
||||
@@ -62,9 +75,7 @@ class economic_transfer_queue
|
||||
$job_id = (int)$db->insert_id();
|
||||
$stmt->close();
|
||||
|
||||
(new logs_o())->add(
|
||||
'economic_transfer_queue',
|
||||
'global',
|
||||
$this->logQueueEvent(
|
||||
1,
|
||||
$created_by,
|
||||
'ECONOMIC_TRANSFER_JOB_ENQUEUED',
|
||||
@@ -372,9 +383,7 @@ class economic_transfer_queue
|
||||
WHERE id = $job_id";
|
||||
$db->query($sql);
|
||||
|
||||
(new logs_o())->add(
|
||||
'economic_transfer_queue',
|
||||
'global',
|
||||
$this->logQueueEvent(
|
||||
1,
|
||||
0,
|
||||
'ECONOMIC_TRANSFER_JOB_COMPLETED',
|
||||
@@ -402,9 +411,7 @@ class economic_transfer_queue
|
||||
WHERE id = $job_id";
|
||||
$db->query($sql);
|
||||
|
||||
(new logs_o())->add(
|
||||
'economic_transfer_queue',
|
||||
'global',
|
||||
$this->logQueueEvent(
|
||||
3,
|
||||
0,
|
||||
'ECONOMIC_TRANSFER_JOB_FAILED',
|
||||
@@ -462,6 +469,172 @@ class economic_transfer_queue
|
||||
$db->query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function normalizePayloadForTransferType(string $transfer_type, array $payload, int $created_by): array
|
||||
{
|
||||
$normalized_payload = $payload;
|
||||
if (isset($normalized_payload['requested_by'])) {
|
||||
$normalized_payload['requested_by'] = max(0, (int)$normalized_payload['requested_by']);
|
||||
}
|
||||
|
||||
return match ($transfer_type) {
|
||||
self::TYPE_ORDER_DRAFT_EXPORT, self::TYPE_ORDER_INVOICE_EXPORT => $this->normalizeOrderPayload($normalized_payload, $created_by),
|
||||
self::TYPE_COLLECTED_INVOICE_EXPORT => $this->normalizeCollectedInvoicePayload($normalized_payload, $created_by),
|
||||
default => $this->rejectPayload($created_by, 'Unsupported transfer type payload: ' . $transfer_type),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function normalizeOrderPayload(array $payload, int $created_by): array
|
||||
{
|
||||
$order_id = $payload['order_id'] ?? null;
|
||||
if ($order_id === null || !is_numeric($order_id) || (int)$order_id < 1) {
|
||||
return $this->rejectPayload($created_by, 'order_id is required and must be a positive number');
|
||||
}
|
||||
$payload['order_id'] = (int)$order_id;
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function normalizeCollectedInvoicePayload(array $payload, int $created_by): array
|
||||
{
|
||||
$collected_invoice_id = $payload['collected_invoice_id'] ?? null;
|
||||
if ($collected_invoice_id === null || !is_numeric($collected_invoice_id) || (int)$collected_invoice_id < 1) {
|
||||
return $this->rejectPayload($created_by, 'collected_invoice_id is required and must be a positive number');
|
||||
}
|
||||
|
||||
$payload['collected_invoice_id'] = (int)$collected_invoice_id;
|
||||
$payload['send_as_is'] = $this->normalizeBooleanPayloadValue($payload['send_as_is'] ?? false, 'send_as_is', $created_by);
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function normalizeBooleanPayloadValue(mixed $value, string $field_name, int $created_by): bool
|
||||
{
|
||||
if (is_bool($value)) {
|
||||
return $value;
|
||||
}
|
||||
if (is_int($value) || is_float($value) || (is_string($value) && is_numeric($value))) {
|
||||
$numeric = (int)$value;
|
||||
if ($numeric === 0 || $numeric === 1) {
|
||||
return $numeric === 1;
|
||||
}
|
||||
return $this->rejectPayload($created_by, $field_name . ' must be a boolean');
|
||||
}
|
||||
if (is_string($value)) {
|
||||
$normalized = strtolower(trim($value));
|
||||
if (in_array($normalized, ['true', 'false', '1', '0'], true)) {
|
||||
return in_array($normalized, ['true', '1'], true);
|
||||
}
|
||||
return $this->rejectPayload($created_by, $field_name . ' must be a boolean');
|
||||
}
|
||||
|
||||
return $this->rejectPayload($created_by, $field_name . ' must be a boolean');
|
||||
}
|
||||
|
||||
private function findActiveJobByTarget(string $transfer_type, array $payload): ?array
|
||||
{
|
||||
return match ($transfer_type) {
|
||||
self::TYPE_ORDER_DRAFT_EXPORT, self::TYPE_ORDER_INVOICE_EXPORT => $this->findActiveJobByJsonNumericTarget(
|
||||
$transfer_type,
|
||||
'$.order_id',
|
||||
(int)($payload['order_id'] ?? 0)
|
||||
),
|
||||
self::TYPE_COLLECTED_INVOICE_EXPORT => $this->findActiveJobByJsonNumericTarget(
|
||||
$transfer_type,
|
||||
'$.collected_invoice_id',
|
||||
(int)($payload['collected_invoice_id'] ?? 0)
|
||||
),
|
||||
default => null,
|
||||
};
|
||||
}
|
||||
|
||||
private function findActiveJobByJsonNumericTarget(string $transfer_type, string $json_path, int $target_value): ?array
|
||||
{
|
||||
global $db;
|
||||
|
||||
if ($target_value < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$stmt = $db->prepare(
|
||||
"SELECT id
|
||||
FROM economic_transfer_queue_jobs
|
||||
WHERE transfer_type = ?
|
||||
AND status IN (?, ?)
|
||||
AND CAST(JSON_UNQUOTE(JSON_EXTRACT(payload_json, '$json_path')) AS UNSIGNED) = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT 1"
|
||||
);
|
||||
if (!$stmt) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$queued = self::STATUS_QUEUED;
|
||||
$processing = self::STATUS_PROCESSING;
|
||||
$stmt->bind_param('sssi', $transfer_type, $queued, $processing, $target_value);
|
||||
if (!$stmt->execute()) {
|
||||
$stmt->close();
|
||||
return null;
|
||||
}
|
||||
|
||||
$result = $stmt->get_result();
|
||||
$row = $result instanceof mysqli_result ? $result->fetch_assoc() : null;
|
||||
$stmt->close();
|
||||
if (!$row || !isset($row['id'])) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->getJobById((int)$row['id']);
|
||||
}
|
||||
|
||||
private function buildTargetLabel(string $transfer_type, array $payload): string
|
||||
{
|
||||
return match ($transfer_type) {
|
||||
self::TYPE_ORDER_DRAFT_EXPORT, self::TYPE_ORDER_INVOICE_EXPORT => 'order_id=' . (int)($payload['order_id'] ?? 0),
|
||||
self::TYPE_COLLECTED_INVOICE_EXPORT => 'collected_invoice_id=' . (int)($payload['collected_invoice_id'] ?? 0),
|
||||
default => 'unknown',
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
private function rejectPayload(int $created_by, string $message): never
|
||||
{
|
||||
$this->logQueueEvent(
|
||||
3,
|
||||
max(0, $created_by),
|
||||
'ECONOMIC_TRANSFER_JOB_VALIDATION_REJECTED',
|
||||
'Transfer job enqueue rejected: ' . $message
|
||||
);
|
||||
throw new Exception($message);
|
||||
}
|
||||
|
||||
private function logQueueEvent(int $status_code, int $user_id, string $event, string $message): void
|
||||
{
|
||||
try {
|
||||
(new logs_o())->add(
|
||||
'economic_transfer_queue',
|
||||
'global',
|
||||
$status_code,
|
||||
$user_id,
|
||||
$event,
|
||||
$message
|
||||
);
|
||||
} catch (\Throwable) {
|
||||
// Logging is best effort for queue operations.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Exception
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user