Actualiser api/index.php
CI / PHP Syntax Check (push) Has been cancelled
CI / JavaScript Lint (push) Has been cancelled
CI / Docker Build Test (push) Has been cancelled
CI / Validate Translation Files (push) Has been cancelled
CI / Auto-merge develop → main (push) Has been cancelled
CI / Create GitHub Release (push) Has been cancelled
Security Scan (Trivy) / Trivy — Docker image scan (push) Has been cancelled
Security Scan (Trivy) / Trivy — Filesystem scan (push) Has been cancelled

This commit is contained in:
2026-06-18 08:38:06 +00:00
parent 0c8eee404e
commit 8ae455d82c
+132 -10
View File
@@ -21,6 +21,28 @@ function isValidLocation(PDO $db, string $location): bool {
return isset($cache[$location]);
}
function isValidSubcategory(PDO $db, string $category, string $subcategory): bool {
static $cache = [];
if (!isset($cache[$category])) {
$stmt = $db->prepare("SELECT key FROM subcategories WHERE category = ?");
$stmt->execute([$category]);
$cache[$category] = array_flip($stmt->fetchAll(PDO::FETCH_COLUMN));
}
return isset($cache[$category][$subcategory]);
}
function getRequiredSubcategoryCategories(PDO $db): array {
static $cache = null;
if ($cache === null) {
$stmt = $db->prepare("SELECT value FROM app_settings WHERE key = 'subcategory_required_categories'");
$stmt->execute();
$row = $stmt->fetch();
$decoded = $row ? json_decode($row['value'], true) : null;
$cache = is_array($decoded) ? $decoded : ['bevande'];
}
return $cache;
}
const RECIPE_PANTRY_MIN_MATCH_SCORE = 80;
const RECENTLY_EXHAUSTED_DAYS = 30;
/** How long to suppress auto-re-add after user bought an item (ms, synced with client blocklist). */
@@ -942,6 +964,18 @@ try {
case 'locations_update':
locationsUpdate($db);
break;
case 'subcategories_list':
subcategoriesList($db);
break;
case 'subcategories_add':
subcategoriesAdd($db);
break;
case 'subcategories_remove':
subcategoriesRemove($db);
break;
case 'subcategories_update':
subcategoriesUpdate($db);
break;
case 'recipes_list':
recipesList($db);
break;
@@ -2703,19 +2737,18 @@ function saveProduct(PDO $db): void {
? $input['shopping_name']
: computeShoppingName($input['name'], $input['category'] ?? '', $input['brand'] ?? '');
// Sous-catégorie obligatoire uniquement pour les boissons
// Sous-catégorie : validée et rendue obligatoire dynamiquement selon la config en base
$category = $input['category'] ?? '';
$subcategory = trim($input['subcategory'] ?? '');
$validSubcategories = ['vin', 'biere', 'spiritueux', 'soda', 'jus', 'eau', 'autre'];
if ($category === 'bevande') {
if ($subcategory === '' || !in_array($subcategory, $validSubcategories, true)) {
http_response_code(400);
echo json_encode(['error' => 'subcategory_required', 'message' => 'Sous-catégorie requise pour les boissons']);
return;
}
} else {
$subcategory = null;
if ($subcategory !== '' && !isValidSubcategory($db, $category, $subcategory)) {
$subcategory = ''; // invalide pour cette catégorie -> ignorée plutôt que de planter
}
if (in_array($category, getRequiredSubcategoryCategories($db), true) && $subcategory === '') {
http_response_code(400);
echo json_encode(['error' => 'subcategory_required', 'message' => 'Sous-catégorie requise pour cette catégorie']);
return;
}
$subcategory = $subcategory !== '' ? $subcategory : null;
$barcode = normalizeProductBarcode($input['barcode'] ?? null);
@@ -12082,6 +12115,95 @@ function locationsUpdate(PDO $db): void {
echo json_encode(['success' => true]);
}
function subcategoriesList(PDO $db): void {
$rows = $db->query("SELECT id, category, key, label, sort_order FROM subcategories ORDER BY category ASC, sort_order ASC, id ASC")->fetchAll();
echo json_encode(['success' => true, 'subcategories' => $rows]);
}
function subcategoriesAdd(PDO $db): void {
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$category = trim($input['category'] ?? '');
$label = trim($input['label'] ?? '');
if ($category === '' || $label === '') {
echo json_encode(['success' => false, 'error' => 'category and label required']);
return;
}
$key = mb_strtolower(trim($label));
$key = preg_replace('/[^a-z0-9]+/u', '_', $key);
$key = trim($key, '_');
if ($key === '') {
echo json_encode(['success' => false, 'error' => 'invalid label']);
return;
}
$stmt = $db->prepare("SELECT id FROM subcategories WHERE category = ? AND key = ?");
$stmt->execute([$category, $key]);
if ($stmt->fetch()) {
echo json_encode(['success' => false, 'error' => 'subcategory already exists for this category']);
return;
}
$stmt = $db->prepare("SELECT COALESCE(MAX(sort_order), 0) FROM subcategories WHERE category = ?");
$stmt->execute([$category]);
$maxOrder = (int)$stmt->fetchColumn();
$stmt = $db->prepare("INSERT INTO subcategories (category, key, label, sort_order) VALUES (?, ?, ?, ?)");
$stmt->execute([$category, $key, $label, $maxOrder + 1]);
echo json_encode(['success' => true, 'id' => (int)$db->lastInsertId(), 'key' => $key]);
}
function subcategoriesRemove(PDO $db): void {
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$id = (int)($input['id'] ?? 0);
if ($id <= 0) {
echo json_encode(['success' => false, 'error' => 'id required']);
return;
}
$stmt = $db->prepare("SELECT category, key FROM subcategories WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
if (!$row) {
echo json_encode(['success' => false, 'error' => 'subcategory not found']);
return;
}
$stmt = $db->prepare("SELECT COUNT(*) FROM products WHERE category = ? AND subcategory = ?");
$stmt->execute([$row['category'], $row['key']]);
if ((int)$stmt->fetchColumn() > 0) {
echo json_encode(['success' => false, 'error' => 'subcategory still used by products']);
return;
}
$db->prepare("DELETE FROM subcategories WHERE id = ?")->execute([$id]);
echo json_encode(['success' => true]);
}
function subcategoriesUpdate(PDO $db): void {
$input = json_decode(file_get_contents('php://input'), true) ?? [];
$id = (int)($input['id'] ?? 0);
$label = trim($input['label'] ?? '');
if ($id <= 0 || $label === '') {
echo json_encode(['success' => false, 'error' => 'id and label required']);
return;
}
$stmt = $db->prepare("SELECT id FROM subcategories WHERE id = ?");
$stmt->execute([$id]);
if (!$stmt->fetch()) {
echo json_encode(['success' => false, 'error' => 'subcategory not found']);
return;
}
$db->prepare("UPDATE subcategories SET label = ? WHERE id = ?")->execute([$label, $id]);
echo json_encode(['success' => true]);
}
// ===== SHARED APP DATA FUNCTIONS =====
function appSettingsGet(PDO $db): void {