<?php
/**
* Products API
* GET /api/products.php — lista alla produkter
* GET /api/products.php?id=SOL01 — hämta en produkt
* POST /api/products.php — skapa/uppdatera produkt (JSON)
* POST /api/products.php?upload=1 — ladda upp produktbild (multipart)
* POST /api/products.php?upload=1&gallery=1 — ladda upp galleribild
* POST /api/products.php?upload=1&document=1 — ladda upp dokument
* POST /api/products.php?remove_gallery=1 — ta bort galleribild
* POST /api/products.php?remove_document=1 — ta bort dokument
* DELETE /api/products.php?id=SOL01 — ta bort produkt
*/
require_once __DIR__ . '/config.php';
function normalizeLinkNumber($value): ?float {
if ($value === '' || $value === null) return null;
if (!is_numeric($value)) return null;
return (float)$value;
}
function saveProductLinks(PDO $db, string $productId, array $links): void {
$db->prepare('DELETE FROM product_services WHERE product_id = ?')->execute([$productId]);
$ins = $db->prepare('INSERT INTO product_services (product_id, service_id, default_on, hidden, mandatory, default_variant, default_qty, min_qty, max_qty, sort_order) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)');
foreach ($links as $idx => $link) {
if (is_string($link)) {
$link = ['service_id' => $link];
}
$serviceId = $link['service_id'] ?? $link['id'] ?? '';
if ($serviceId === '') continue;
$ins->execute([
$productId,
$serviceId,
!empty($link['default_on']) ? 1 : 0,
!empty($link['hidden']) ? 1 : 0,
!empty($link['mandatory']) ? 1 : 0,
($link['default_variant'] ?? '') !== '' ? ($link['default_variant'] ?? null) : null,
normalizeLinkNumber($link['default_qty'] ?? null),
normalizeLinkNumber($link['min_qty'] ?? null),
normalizeLinkNumber($link['max_qty'] ?? null),
$idx + 1,
]);
}
}
header('Content-Type: application/json; charset=utf-8');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET, POST, DELETE, OPTIONS');
header('Access-Control-Allow-Headers: Content-Type');
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') { http_response_code(200); exit; }
try {
$db = getDB();
// Ensure table exists
$db->exec("CREATE TABLE IF NOT EXISTS products (
id VARCHAR(20) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
cat VARCHAR(50) NOT NULL DEFAULT '',
cat_label VARCHAR(100) NOT NULL DEFAULT '',
description TEXT,
price DECIMAL(12,2) NOT NULL DEFAULT 0,
stock VARCHAR(50) DEFAULT 'I lager',
stock_class VARCHAR(20) DEFAULT 'green',
img VARCHAR(500) DEFAULT '',
watt INT DEFAULT NULL,
kwh_capacity DECIMAL(10,2) DEFAULT NULL,
specs JSON DEFAULT NULL,
sort_order INT DEFAULT 0,
active TINYINT(1) DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
$db->exec("CREATE TABLE IF NOT EXISTS product_services (
id INT AUTO_INCREMENT PRIMARY KEY,
product_id VARCHAR(20) NOT NULL,
service_id VARCHAR(20) NOT NULL,
default_on TINYINT(1) NOT NULL DEFAULT 0,
hidden TINYINT(1) NOT NULL DEFAULT 0,
mandatory TINYINT(1) NOT NULL DEFAULT 0,
default_variant VARCHAR(100) DEFAULT NULL,
default_qty DECIMAL(10,2) DEFAULT NULL,
min_qty DECIMAL(10,2) DEFAULT NULL,
max_qty DECIMAL(10,2) DEFAULT NULL,
sort_order INT DEFAULT 0,
UNIQUE KEY uniq_product_service (product_id, service_id),
KEY idx_service_id (service_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4");
foreach ([
"ALTER TABLE product_services ADD COLUMN default_qty DECIMAL(10,2) DEFAULT NULL AFTER default_variant",
"ALTER TABLE product_services ADD COLUMN min_qty DECIMAL(10,2) DEFAULT NULL AFTER default_qty",
"ALTER TABLE product_services ADD COLUMN max_qty DECIMAL(10,2) DEFAULT NULL AFTER min_qty",
] as $sql) {
try { $db->exec($sql); } catch (Throwable $e) { /* already exists */ }
}
if ($_SERVER['REQUEST_METHOD'] === 'GET') {
// Hämta leverantörer
if (isset($_GET['suppliers'])) {
$stmt = $db->query('SELECT id, name FROM suppliers ORDER BY name');
echo json_encode(['success' => true, 'suppliers' => $stmt->fetchAll()]);
exit;
}
// Hämta kopplade tjänster för en produkt
$servicesFor = $_GET['services_for'] ?? '';
if ($servicesFor) {
$stmt = $db->prepare('SELECT ps.service_id, ps.default_on, ps.hidden, ps.mandatory, ps.default_variant, ps.default_qty, ps.min_qty, ps.max_qty, ps.sort_order FROM product_services ps WHERE ps.product_id = ? ORDER BY ps.sort_order');
$stmt->execute([$servicesFor]);
echo json_encode(['success' => true, 'services' => $stmt->fetchAll()]);
exit;
}
// Hämta vilka produkter denna produkt är kopplad till (reverse lookup)
$usedBy = $_GET["used_by"] ?? "";
if ($usedBy) {
$stmt = $db->prepare("SELECT p.id, p.name, p.cat_label, p.price, ps.default_on, ps.hidden, ps.mandatory, ps.default_variant, ps.default_qty, ps.min_qty, ps.max_qty FROM product_services ps JOIN products p ON p.id = ps.product_id WHERE ps.service_id = ? ORDER BY p.name");
$stmt->execute([$usedBy]);
echo json_encode(["success" => true, "products" => $stmt->fetchAll()]);
exit;
}
$id = $_GET['id'] ?? '';
if ($id) {
$stmt = $db->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$id]);
$product = $stmt->fetch();
if (!$product) { http_response_code(404); echo json_encode(['error' => 'Produkt ej hittad']); exit; }
echo json_encode(['success' => true, 'product' => $product]);
} else {
$cat = $_GET['cat'] ?? '';
$subcat = $_GET['subcat'] ?? null;
$active = $_GET['active'] ?? '1';
$sql = 'SELECT * FROM products WHERE active = ?';
$params = [$active];
if ($cat) { $sql .= ' AND cat = ?'; $params[] = $cat; }
if ($subcat !== null) { $sql .= ' AND subcat = ?'; $params[] = $subcat; }
$sql .= ' ORDER BY subcat, sort_order, cat, name';
$stmt = $db->prepare($sql);
$stmt->execute($params);
$products = $stmt->fetchAll();
echo json_encode(['success' => true, 'products' => $products]);
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'POST') {
// === UPDATE LINK FLAGS (per-koppling) ===
if (isset($_GET["update_link"])) {
$body = json_decode(file_get_contents("php://input"), true);
$productId = $body["product_id"] ?? "";
$serviceId = $body["service_id"] ?? "";
$field = $body["field"] ?? "";
if (!in_array($field, ["mandatory", "default_on", "hidden", "default_variant", "default_qty", "min_qty", "max_qty"], true)) {
echo json_encode(["success" => false, "error" => "Invalid field"]);
exit;
}
if (in_array($field, ["default_variant"], true)) {
$value = ($body["value"] ?? '') !== '' ? ($body["value"] ?? null) : null;
} elseif (in_array($field, ["default_qty", "min_qty", "max_qty"], true)) {
$value = normalizeLinkNumber($body["value"] ?? null);
} else {
$value = intval($body["value"] ?? 0);
}
$stmt = $db->prepare("UPDATE product_services SET $field = ? WHERE product_id = ? AND service_id = ?");
$stmt->execute([$value, $productId, $serviceId]);
echo json_encode(["success" => true]);
exit;
}
// === REMOVE GALLERY IMAGE ===
if (isset($_GET['remove_gallery'])) {
$body = json_decode(file_get_contents('php://input'), true);
$productId = $body['id'] ?? '';
$imgPath = $body['img_path'] ?? '';
if (!$productId || !$imgPath) throw new Exception('id och img_path krävs');
$stmt = $db->prepare('SELECT gallery FROM products WHERE id = ?');
$stmt->execute([$productId]);
$row = $stmt->fetch();
$gallery = $row && $row['gallery'] ? json_decode($row['gallery'], true) : [];
$gallery = array_values(array_filter($gallery, fn($g) => $g !== $imgPath));
$stmt = $db->prepare('UPDATE products SET gallery = ? WHERE id = ?');
$stmt->execute([json_encode($gallery), $productId]);
// Delete file
$filePath = __DIR__ . '/../' . $imgPath;
if (file_exists($filePath)) unlink($filePath);
echo json_encode(['success' => true, 'gallery' => $gallery]);
exit;
}
// === REMOVE DOCUMENT ===
if (isset($_GET['remove_document'])) {
$body = json_decode(file_get_contents('php://input'), true);
$productId = $body['id'] ?? '';
$docFile = $body['doc_file'] ?? '';
if (!$productId || !$docFile) throw new Exception('id och doc_file krävs');
$stmt = $db->prepare('SELECT documents FROM products WHERE id = ?');
$stmt->execute([$productId]);
$row = $stmt->fetch();
$docs = $row && $row['documents'] ? json_decode($row['documents'], true) : [];
$docs = array_values(array_filter($docs, fn($d) => $d['file'] !== $docFile));
$stmt = $db->prepare('UPDATE products SET documents = ? WHERE id = ?');
$stmt->execute([json_encode($docs), $productId]);
$filePath = __DIR__ . '/../' . $docFile;
if (file_exists($filePath)) unlink($filePath);
echo json_encode(['success' => true, 'documents' => $docs]);
exit;
}
// === FILE UPLOADS ===
if (isset($_GET['upload'])) {
// --- DOCUMENT UPLOAD ---
if (isset($_GET['document']) && !empty($_FILES['document'])) {
$productId = $_POST['id'] ?? '';
if (!$productId) throw new Exception('id krävs');
$file = $_FILES['document'];
if ($file['error'] !== UPLOAD_ERR_OK) throw new Exception('Uppladdningsfel: ' . $file['error']);
$maxSize = 20 * 1024 * 1024; // 20MB
if ($file['size'] > $maxSize) throw new Exception('Filen är för stor (max 20MB)');
$allowedMime = ['application/pdf','application/msword','application/vnd.openxmlformats-officedocument.wordprocessingml.document'];
$mime = mime_content_type($file['tmp_name']);
if (!in_array($mime, $allowedMime)) throw new Exception('Otillåten filtyp: ' . $mime);
$uploadDir = __DIR__ . '/../Docs/';
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
$ext = pathinfo($file['name'], PATHINFO_EXTENSION) ?: 'pdf';
$safeName = preg_replace('/[^a-z0-9_-]/i', '_', pathinfo($file['name'], PATHINFO_FILENAME));
$filename = strtolower($productId) . '_' . $safeName . '_' . time() . '.' . $ext;
$dest = $uploadDir . $filename;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
throw new Exception('Kunde inte spara dokumentet');
}
$docPath = 'Docs/' . $filename;
$docName = $_POST['doc_name'] ?? pathinfo($file['name'], PATHINFO_FILENAME);
$docType = $_POST['doc_type'] ?? 'manual';
// Append to documents JSON
$stmt = $db->prepare('SELECT documents FROM products WHERE id = ?');
$stmt->execute([$productId]);
$row = $stmt->fetch();
$docs = $row && $row['documents'] ? json_decode($row['documents'], true) : [];
$docs[] = ['name' => $docName, 'file' => $docPath, 'type' => $docType, 'size' => $file['size']];
$stmt = $db->prepare('UPDATE products SET documents = ? WHERE id = ?');
$stmt->execute([json_encode($docs), $productId]);
echo json_encode(['success' => true, 'documents' => $docs]);
exit;
}
// --- IMAGE UPLOAD (main or gallery) ---
if (!empty($_FILES['image'])) {
$productId = $_POST['id'] ?? '';
if (!$productId) throw new Exception('id krävs för bilduppladdning');
$file = $_FILES['image'];
if ($file['error'] !== UPLOAD_ERR_OK) throw new Exception('Uppladdningsfel: ' . $file['error']);
$maxSize = 10 * 1024 * 1024; // 10MB
if ($file['size'] > $maxSize) throw new Exception('Bilden är för stor (max 10MB)');
$allowed = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
$mime = mime_content_type($file['tmp_name']);
if (!in_array($mime, $allowed)) throw new Exception('Otillåten filtyp: ' . $mime);
$uploadDir = __DIR__ . '/../Photo/';
if (!is_dir($uploadDir)) mkdir($uploadDir, 0755, true);
$ext = pathinfo($file['name'], PATHINFO_EXTENSION) ?: 'webp';
$suffix = isset($_GET['gallery']) ? '_g' . time() : '_' . time();
$filename = strtolower($productId) . $suffix . '.' . $ext;
$dest = $uploadDir . $filename;
if (!move_uploaded_file($file['tmp_name'], $dest)) {
throw new Exception('Kunde inte spara bilden');
}
$imgPath = 'Photo/' . $filename;
if (isset($_GET['gallery'])) {
// Append to gallery JSON
$stmt = $db->prepare('SELECT gallery FROM products WHERE id = ?');
$stmt->execute([$productId]);
$row = $stmt->fetch();
$gallery = $row && $row['gallery'] ? json_decode($row['gallery'], true) : [];
$gallery[] = $imgPath;
$stmt = $db->prepare('UPDATE products SET gallery = ? WHERE id = ?');
$stmt->execute([json_encode($gallery), $productId]);
echo json_encode(['success' => true, 'img' => $imgPath, 'gallery' => $gallery]);
} else {
// Update main image
$stmt = $db->prepare('UPDATE products SET img = ? WHERE id = ?');
$stmt->execute([$imgPath, $productId]);
echo json_encode(['success' => true, 'img' => $imgPath]);
}
exit;
}
throw new Exception('Ingen fil bifogad');
}
// === CREATE/UPDATE PRODUCT ===
$body = json_decode(file_get_contents('php://input'), true);
if (!$body) throw new Exception('JSON body krävs');
$id = $body['id'] ?? '';
if (!$id) throw new Exception('id krävs');
$stmt = $db->prepare('SELECT id FROM products WHERE id = ?');
$stmt->execute([$id]);
$exists = $stmt->fetch();
if ($exists) {
// Update
$fields = [];
$params = [];
foreach (['name','cat','cat_label','subcat','description','price','cost_price','cost_currency','stock','stock_class','img','watt','kwh_capacity','sort_order','active','green_tech_eligible','rot_eligible','supplier_id','unit','markup_type','markup_value','tillval_obligatorisk','tillval_hidden'] as $f) {
if (array_key_exists($f, $body)) {
$fields[] = "$f = ?";
$params[] = $body[$f];
}
}
if (!empty($body['specs'])) {
$fields[] = 'specs = ?';
$params[] = json_encode($body['specs']);
}
if (array_key_exists('gallery', $body)) {
$fields[] = 'gallery = ?';
$params[] = is_array($body['gallery']) ? json_encode($body['gallery']) : $body['gallery'];
}
if (array_key_exists('documents', $body)) {
$fields[] = 'documents = ?';
$params[] = is_array($body['documents']) ? json_encode($body['documents']) : $body['documents'];
}
if (empty($fields)) throw new Exception('Inget att uppdatera');
$params[] = $id;
$stmt = $db->prepare('UPDATE products SET ' . implode(', ', $fields) . ' WHERE id = ?');
$stmt->execute($params);
// Uppdatera tjänst-kopplingar om skickade
if (isset($body['_services']) && is_array($body['_services'])) {
saveProductLinks($db, $id, $body['_services']);
}
echo json_encode(['success' => true, 'action' => 'updated']);
} else {
// Insert
$stmt = $db->prepare('INSERT INTO products (id, name, cat, cat_label, subcat, description, price, cost_price, cost_currency, stock, stock_class, img, watt, kwh_capacity, specs, sort_order, active, green_tech_eligible, rot_eligible, supplier_id, unit, markup_type, markup_value, tillval_obligatorisk, tillval_hidden) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
$stmt->execute([
$id,
$body['name'] ?? '',
$body['cat'] ?? '',
$body['cat_label'] ?? $body['catLabel'] ?? '',
$body['subcat'] ?? '',
$body['description'] ?? $body['desc'] ?? '',
$body['price'] ?? 0,
$body['cost_price'] ?? null,
$body['cost_currency'] ?? 'SEK',
$body['stock'] ?? 'I lager',
$body['stock_class'] ?? $body['stockClass'] ?? 'green',
$body['img'] ?? '',
$body['watt'] ?? null,
$body['kwh_capacity'] ?? null,
!empty($body['specs']) ? json_encode($body['specs']) : null,
$body['sort_order'] ?? 0,
$body['active'] ?? 1,
$body['green_tech_eligible'] ?? 0,
$body['rot_eligible'] ?? 0,
$body['supplier_id'] ?? null,
$body['unit'] ?? 'st',
$body['markup_type'] ?? 'percent',
$body['markup_value'] ?? 0
]);
if (isset($body['_services']) && is_array($body['_services'])) {
saveProductLinks($db, $id, $body['_services']);
}
echo json_encode(['success' => true, 'action' => 'created']);
}
} elseif ($_SERVER['REQUEST_METHOD'] === 'DELETE') {
$id = $_GET['id'] ?? '';
if (!$id) throw new Exception('id krävs');
$stmt = $db->prepare('DELETE FROM products WHERE id = ?');
$stmt->execute([$id]);
echo json_encode(['success' => true, 'action' => 'deleted']);
}
} catch (Exception $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
}