feat(cronjob): implement CronJobProcessor and CronJobRepository for job scheduling and processing

- Added CronJobProcessor class to handle job creation and queue processing.
- Implemented CronJobRepository for database interactions related to cron jobs.
- Introduced CronJobType class to define job types, priorities, and statuses.
- Created ApiloLogger for logging actions related to job processing.
- Initialized apilo-sync-queue.json for job queue management.
This commit is contained in:
2026-02-27 14:51:30 +01:00
parent fc45bbf20e
commit 4cf7039759
34 changed files with 3099 additions and 741 deletions

View File

@@ -48,7 +48,7 @@ class AttributeRepository
FROM pp_shop_attributes AS sa
WHERE {$whereSql}
";
$stmtCount = $this->db->query($sqlCount, $params);
$stmtCount = $this->db->query($sqlCount, $whereData['params']);
$countRows = $stmtCount ? $stmtCount->fetchAll() : [];
$total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0;
@@ -534,6 +534,216 @@ class AttributeRepository
return $attributes;
}
/**
* Zwraca aktywne atrybuty z wartościami i wielojęzycznymi nazwami dla REST API.
*
* @return array<int, array<string, mixed>>
*/
public function listForApi(): array
{
// 1. Get all active attribute IDs (1 query)
$rows = $this->db->select('pp_shop_attributes', ['id', 'type', 'status'], [
'status' => 1,
'ORDER' => ['o' => 'ASC'],
]);
if (!is_array($rows) || empty($rows)) {
return [];
}
$attrIds = [];
foreach ($rows as $row) {
$id = (int)($row['id'] ?? 0);
if ($id > 0) {
$attrIds[] = $id;
}
}
if (empty($attrIds)) {
return [];
}
// 2. Batch load ALL attribute translations (1 query)
$allAttrTranslations = $this->db->select(
'pp_shop_attributes_langs',
['attribute_id', 'lang_id', 'name'],
['attribute_id' => $attrIds]
);
$attrNamesMap = [];
if (is_array($allAttrTranslations)) {
foreach ($allAttrTranslations as $t) {
$aId = (int)($t['attribute_id'] ?? 0);
$langId = (string)($t['lang_id'] ?? '');
if ($aId > 0 && $langId !== '') {
$attrNamesMap[$aId][$langId] = (string)($t['name'] ?? '');
}
}
}
// 3. Batch load ALL values for those attribute IDs (1 query)
$allValueRows = $this->db->select(
'pp_shop_attributes_values',
['id', 'attribute_id', 'is_default', 'impact_on_the_price'],
[
'attribute_id' => $attrIds,
'ORDER' => ['id' => 'ASC'],
]
);
$valuesByAttr = [];
$allValueIds = [];
if (is_array($allValueRows)) {
foreach ($allValueRows as $vRow) {
$valueId = (int)($vRow['id'] ?? 0);
$attrId = (int)($vRow['attribute_id'] ?? 0);
if ($valueId > 0 && $attrId > 0) {
$valuesByAttr[$attrId][] = $vRow;
$allValueIds[] = $valueId;
}
}
}
// 4. Batch load ALL value translations (1 query)
$valueNamesMap = [];
if (!empty($allValueIds)) {
$allValueTranslations = $this->db->select(
'pp_shop_attributes_values_langs',
['value_id', 'lang_id', 'name'],
['value_id' => $allValueIds]
);
if (is_array($allValueTranslations)) {
foreach ($allValueTranslations as $vt) {
$vId = (int)($vt['value_id'] ?? 0);
$langId = (string)($vt['lang_id'] ?? '');
if ($vId > 0 && $langId !== '') {
$valueNamesMap[$vId][$langId] = (string)($vt['name'] ?? '');
}
}
}
}
// 5. Assemble result in-memory
$result = [];
foreach ($rows as $row) {
$attributeId = (int)($row['id'] ?? 0);
if ($attributeId <= 0) {
continue;
}
$names = isset($attrNamesMap[$attributeId]) ? $attrNamesMap[$attributeId] : [];
$values = [];
if (isset($valuesByAttr[$attributeId])) {
foreach ($valuesByAttr[$attributeId] as $vRow) {
$valueId = (int)$vRow['id'];
$impact = $vRow['impact_on_the_price'];
$values[] = [
'id' => $valueId,
'names' => isset($valueNamesMap[$valueId]) ? $valueNamesMap[$valueId] : [],
'is_default' => (int)($vRow['is_default'] ?? 0),
'impact_on_the_price' => ($impact !== null && $impact !== '') ? (float)$impact : null,
];
}
}
$result[] = [
'id' => $attributeId,
'type' => (int)($row['type'] ?? 0),
'status' => (int)($row['status'] ?? 0),
'names' => $names,
'values' => $values,
];
}
return $result;
}
/**
* Find existing attribute by name/type or create a new one for API integration.
*
* @return array{id:int,created:bool}|null
*/
public function ensureAttributeForApi(string $name, int $type = 0, string $langId = 'pl'): ?array
{
$normalizedName = trim($name);
$normalizedLangId = trim($langId) !== '' ? trim($langId) : 'pl';
$normalizedType = $this->toTypeValue($type);
if ($normalizedName === '') {
return null;
}
$existingId = $this->findAttributeIdByNameAndType($normalizedName, $normalizedType);
if ($existingId > 0) {
return ['id' => $existingId, 'created' => false];
}
$this->db->insert('pp_shop_attributes', [
'status' => 1,
'type' => $normalizedType,
'o' => $this->nextOrder(),
]);
$attributeId = (int) $this->db->id();
if ($attributeId <= 0) {
return null;
}
$this->db->insert('pp_shop_attributes_langs', [
'attribute_id' => $attributeId,
'lang_id' => $normalizedLangId,
'name' => $normalizedName,
]);
$this->clearTempAndCache();
$this->clearFrontCache($attributeId, 'frontAttributeDetails');
return ['id' => $attributeId, 'created' => true];
}
/**
* Find existing value by name within attribute or create a new one for API integration.
*
* @return array{id:int,created:bool}|null
*/
public function ensureAttributeValueForApi(int $attributeId, string $name, string $langId = 'pl'): ?array
{
$normalizedName = trim($name);
$normalizedLangId = trim($langId) !== '' ? trim($langId) : 'pl';
$attributeId = max(0, $attributeId);
if ($attributeId <= 0 || $normalizedName === '') {
return null;
}
$attributeExists = (int) $this->db->count('pp_shop_attributes', ['id' => $attributeId]) > 0;
if (!$attributeExists) {
return null;
}
$existingId = $this->findAttributeValueIdByName($attributeId, $normalizedName);
if ($existingId > 0) {
return ['id' => $existingId, 'created' => false];
}
$this->db->insert('pp_shop_attributes_values', [
'attribute_id' => $attributeId,
'impact_on_the_price' => null,
'is_default' => 0,
]);
$valueId = (int) $this->db->id();
if ($valueId <= 0) {
return null;
}
$this->db->insert('pp_shop_attributes_values_langs', [
'value_id' => $valueId,
'lang_id' => $normalizedLangId,
'name' => $normalizedName,
'value' => null,
]);
$this->clearTempAndCache();
$this->clearFrontCache($valueId, 'frontValueDetails');
return ['id' => $valueId, 'created' => true];
}
/**
* @return array{sql: string, params: array<string, mixed>}
*/
@@ -851,6 +1061,52 @@ class AttributeRepository
return $this->defaultLangId;
}
private function findAttributeIdByNameAndType(string $name, int $type): int
{
$statement = $this->db->query(
'SELECT sa.id
FROM pp_shop_attributes sa
INNER JOIN pp_shop_attributes_langs sal ON sal.attribute_id = sa.id
WHERE sa.type = :type
AND LOWER(TRIM(sal.name)) = LOWER(TRIM(:name))
ORDER BY sa.id ASC
LIMIT 1',
[
':type' => $type,
':name' => $name,
]
);
if (!$statement) {
return 0;
}
$id = $statement->fetchColumn();
return $id === false ? 0 : (int) $id;
}
private function findAttributeValueIdByName(int $attributeId, string $name): int
{
$statement = $this->db->query(
'SELECT sav.id
FROM pp_shop_attributes_values sav
INNER JOIN pp_shop_attributes_values_langs savl ON savl.value_id = sav.id
WHERE sav.attribute_id = :attribute_id
AND LOWER(TRIM(savl.name)) = LOWER(TRIM(:name))
ORDER BY sav.id ASC
LIMIT 1',
[
':attribute_id' => $attributeId,
':name' => $name,
]
);
if (!$statement) {
return 0;
}
$id = $statement->fetchColumn();
return $id === false ? 0 : (int) $id;
}
// ── Frontend methods ──────────────────────────────────────────
public function frontAttributeDetails(int $attributeId, string $langId): array