$customer */ function parseCustomerCustomFields(array &$customer): void { /** @var array|null $cfRaw */ $cfRaw = !empty($customer['custom_fields']) ? json_decode($customer['custom_fields'], true) : null; if (is_array($cfRaw) && !isset($cfRaw['fields'])) { $customer['custom_fields'] = $cfRaw; $customer['customer_field_order'] = null; } elseif (is_array($cfRaw) && isset($cfRaw['fields'])) { $customer['custom_fields'] = $cfRaw['fields']; $customer['customer_field_order'] = $cfRaw['field_order'] ?? $cfRaw['fieldOrder'] ?? null; } else { $customer['custom_fields'] = []; $customer['customer_field_order'] = null; } } /** @param array $input */ function encodeCustomerCustomFields(array $input, ?string $existingJson): ?string { if (!array_key_exists('custom_fields', $input) && !array_key_exists('customer_field_order', $input)) { return $existingJson; } /** @var array|null $currentRaw */ $currentRaw = !empty($existingJson) ? json_decode($existingJson, true) : null; if (is_array($currentRaw) && !isset($currentRaw['fields'])) { /** @var array $stored */ $stored = ['fields' => $currentRaw, 'field_order' => null]; } elseif (is_array($currentRaw) && isset($currentRaw['fields'])) { /** @var array $stored */ $stored = $currentRaw; } else { $stored = ['fields' => [], 'field_order' => null]; } if (array_key_exists('custom_fields', $input) && is_array($input['custom_fields'])) { $stored['fields'] = $input['custom_fields']; } if (array_key_exists('customer_field_order', $input)) { $stored['field_order'] = is_array($input['customer_field_order']) ? $input['customer_field_order'] : null; } unset($stored['fieldOrder']); return json_encode($stored, JSON_UNESCAPED_UNICODE); } function handleGetAll(PDO $pdo): void { $stmt = $pdo->query(' SELECT c.id, c.name, c.street, c.city, c.postal_code, c.country, c.company_id, c.vat_id, c.custom_fields, c.created_at, COUNT(q.id) as quotation_count FROM customers c LEFT JOIN quotations q ON q.customer_id = c.id GROUP BY c.id ORDER BY c.name ASC '); $customers = $stmt->fetchAll(); foreach ($customers as &$c) { parseCustomerCustomFields($c); } unset($c); successResponse(['customers' => $customers]); } function handleGetOne(PDO $pdo, int $id): void { $stmt = $pdo->prepare( 'SELECT id, name, street, city, postal_code, country, company_id, vat_id, custom_fields, created_at FROM customers WHERE id = ?' ); $stmt->execute([$id]); $customer = $stmt->fetch(); if (!$customer) { errorResponse('Zákazník nebyl nalezen', 404); } parseCustomerCustomFields($customer); successResponse($customer); } function handleSearch(PDO $pdo): void { $q = trim($_GET['q'] ?? ''); if (strlen($q) < 1 || mb_strlen($q) > 100) { successResponse(['customers' => []]); return; } $stmt = $pdo->prepare(' SELECT id, name, street, city, postal_code, country, company_id, vat_id, custom_fields FROM customers WHERE name LIKE ? OR company_id LIKE ? OR city LIKE ? ORDER BY name ASC LIMIT 20 '); $search = "%{$q}%"; $stmt->execute([$search, $search, $search]); $results = $stmt->fetchAll(); foreach ($results as &$c) { parseCustomerCustomFields($c); } unset($c); successResponse(['customers' => $results]); } function handleCreateCustomer(PDO $pdo): void { $input = getJsonInput(); if (empty($input['name'])) { errorResponse('Název zákazníka je povinný'); } if (mb_strlen($input['name']) > 255) { errorResponse('Název zákazníka je příliš dlouhý (max 255 znaků)'); } foreach (['street', 'city', 'country'] as $f) { if (isset($input[$f]) && mb_strlen($input[$f]) > 255) { errorResponse("Pole $f je příliš dlouhé (max 255 znaků)"); } } if (isset($input['postal_code']) && mb_strlen($input['postal_code']) > 20) { errorResponse('PSČ je příliš dlouhé (max 20 znaků)'); } if (isset($input['company_id']) && mb_strlen($input['company_id']) > 50) { errorResponse('IČO je příliš dlouhé (max 50 znaků)'); } if (isset($input['vat_id']) && mb_strlen($input['vat_id']) > 50) { errorResponse('DIČ je příliš dlouhé (max 50 znaků)'); } $uuid = sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0x0fff) | 0x4000, random_int(0, 0x3fff) | 0x8000, random_int(0, 0xffff), random_int(0, 0xffff), random_int(0, 0xffff) ); $customFieldsJson = encodeCustomerCustomFields($input, null); $stmt = $pdo->prepare(' INSERT INTO customers (name, street, city, postal_code, country, company_id, vat_id, custom_fields, created_at, uuid, modified_at, sync_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW(), ?, NOW(), 1) '); $stmt->execute([ $input['name'], $input['street'] ?? '', $input['city'] ?? '', $input['postal_code'] ?? '', $input['country'] ?? '', $input['company_id'] ?? '', $input['vat_id'] ?? '', $customFieldsJson, $uuid, ]); $newId = (int)$pdo->lastInsertId(); AuditLog::logCreate('customer', (int)$newId, [ 'name' => $input['name'], ], "Vytvořen zákazník '{$input['name']}'"); successResponse(['id' => $newId], 'Zákazník byl vytvořen'); } function handleUpdateCustomer(PDO $pdo, int $id): void { $stmt = $pdo->prepare( 'SELECT id, name, street, city, postal_code, country, company_id, vat_id, custom_fields FROM customers WHERE id = ?' ); $stmt->execute([$id]); $existing = $stmt->fetch(); if (!$existing) { errorResponse('Zákazník nebyl nalezen', 404); } $input = getJsonInput(); // Delkove limity if (isset($input['name']) && mb_strlen($input['name']) > 255) { errorResponse('Název je příliš dlouhý (max 255 znaků)'); } foreach (['street', 'city', 'country'] as $f) { if (isset($input[$f]) && mb_strlen($input[$f]) > 255) { errorResponse("Pole $f je příliš dlouhé (max 255 znaků)"); } } if (isset($input['postal_code']) && mb_strlen($input['postal_code']) > 20) { errorResponse('PSČ je příliš dlouhé (max 20 znaků)'); } if (isset($input['company_id']) && mb_strlen($input['company_id']) > 50) { errorResponse('IČO je příliš dlouhé (max 50 znaků)'); } if (isset($input['vat_id']) && mb_strlen($input['vat_id']) > 50) { errorResponse('DIČ je příliš dlouhé (max 50 znaků)'); } $customFieldsJson = encodeCustomerCustomFields($input, $existing['custom_fields'] ?? null); $stmt = $pdo->prepare(' UPDATE customers SET name = ?, street = ?, city = ?, postal_code = ?, country = ?, company_id = ?, vat_id = ?, custom_fields = ?, modified_at = NOW(), sync_version = sync_version + 1 WHERE id = ? '); $stmt->execute([ $input['name'] ?? $existing['name'], $input['street'] ?? $existing['street'], $input['city'] ?? $existing['city'], $input['postal_code'] ?? $existing['postal_code'], $input['country'] ?? $existing['country'], $input['company_id'] ?? $existing['company_id'], $input['vat_id'] ?? $existing['vat_id'], $customFieldsJson, $id, ]); AuditLog::logUpdate( 'customer', $id, ['name' => $existing['name']], ['name' => $input['name'] ?? $existing['name']], "Upraven zákazník #$id" ); successResponse(null, 'Zákazník byl aktualizován'); } function handleDeleteCustomer(PDO $pdo, int $id): void { $stmt = $pdo->prepare('SELECT id, name FROM customers WHERE id = ?'); $stmt->execute([$id]); $customer = $stmt->fetch(); if (!$customer) { errorResponse('Zákazník nebyl nalezen', 404); } // Check if customer has quotations $stmt = $pdo->prepare('SELECT COUNT(*) FROM quotations WHERE customer_id = ?'); $stmt->execute([$id]); $count = (int)$stmt->fetchColumn(); if ($count > 0) { errorResponse("Zákazníka nelze smazat, má $count nabídek"); } $stmt = $pdo->prepare('DELETE FROM customers WHERE id = ?'); $stmt->execute([$id]); AuditLog::logDelete('customer', $id, ['name' => $customer['name']], "Smazán zákazník '{$customer['name']}'"); successResponse(null, 'Zákazník byl smazán'); }