Files
app/api/admin/handlers/customers-handlers.php
Simon 758be819c3 feat: P4 backend kvalita - SELECT * fix, overdue konsolidace, Validator
- SELECT * nahrazen explicitnimi sloupci ve 22 PHP souborech (69+ vyskytu)
- users-handlers.php: password_hash explicitne vyloucen z dotazu
- Overdue detekce presunuta do invoices.php routeru (1x pred dispatch misto 3x v handlerech)
- Validator.php: validacni helper s pravidly required, string, int, email, in, numeric
- PaginationHelper: PHPStan typy opraveny

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:42:42 +01:00

288 lines
9.0 KiB
PHP

<?php
declare(strict_types=1);
/** @param array<string, mixed> $customer */
function parseCustomerCustomFields(array &$customer): void
{
/** @var array<mixed>|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<string, mixed> $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<mixed>|null $currentRaw */
$currentRaw = !empty($existingJson) ? json_decode($existingJson, true) : null;
if (is_array($currentRaw) && !isset($currentRaw['fields'])) {
/** @var array<string, mixed> $stored */
$stored = ['fields' => $currentRaw, 'field_order' => null];
} elseif (is_array($currentRaw) && isset($currentRaw['fields'])) {
/** @var array<string, mixed> $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');
}