refactor: odstraneni PSR-1 SideEffects warningu
- Handler funkce extrahovany z API souboru do api/admin/handlers/ - config.php rozdeleny na helpers.php (funkce) a constants.php (konstanty) - require_once odstranen z class souboru (AuditLog, JWTAuth, LeaveNotification) - vendor/autoload.php presunuto do config.php bootstrap - totp-handlers.php: pridany use deklarace pro TwoFactorAuth - phpstan.neon: bootstrapFiles, scanDirectories, dynamicConstantNames - Opraveny chybejici routing bloky v totp.php a session.php Vysledek: phpcs 0 errors 0 warnings, PHPStan 0 errors, ESLint 0 errors Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
588
api/admin/handlers/offers-handlers.php
Normal file
588
api/admin/handlers/offers-handlers.php
Normal file
@@ -0,0 +1,588 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
function handleGetList(PDO $pdo): void
|
||||
{
|
||||
$search = trim($_GET['search'] ?? '');
|
||||
$sort = $_GET['sort'] ?? 'created_at';
|
||||
$order = strtoupper($_GET['order'] ?? 'DESC') === 'ASC' ? 'ASC' : 'DESC';
|
||||
$page = max(1, (int) ($_GET['page'] ?? 1));
|
||||
$perPage = min(500, max(1, (int) ($_GET['per_page'] ?? 500)));
|
||||
|
||||
$sortMap = [
|
||||
'Date' => 'q.created_at',
|
||||
'CreatedAt' => 'q.created_at',
|
||||
'created_at' => 'q.created_at',
|
||||
'QuotationNumber' => 'q.quotation_number',
|
||||
'quotation_number' => 'q.quotation_number',
|
||||
'ProjectCode' => 'q.project_code',
|
||||
'project_code' => 'q.project_code',
|
||||
'ValidUntil' => 'q.valid_until',
|
||||
'valid_until' => 'q.valid_until',
|
||||
'Currency' => 'q.currency',
|
||||
'currency' => 'q.currency',
|
||||
];
|
||||
if (!isset($sortMap[$sort])) {
|
||||
errorResponse('Neplatný parametr řazení', 400);
|
||||
}
|
||||
$sortCol = $sortMap[$sort];
|
||||
|
||||
$where = 'WHERE 1=1';
|
||||
$params = [];
|
||||
|
||||
if ($search) {
|
||||
$search = mb_substr($search, 0, 100);
|
||||
$where .= ' AND (q.quotation_number LIKE ? OR q.project_code LIKE ? OR c.name LIKE ?)';
|
||||
$searchParam = "%{$search}%";
|
||||
$params = [$searchParam, $searchParam, $searchParam];
|
||||
}
|
||||
|
||||
// Celkovy pocet pro pagination
|
||||
$countSql = "
|
||||
SELECT COUNT(*)
|
||||
FROM quotations q
|
||||
LEFT JOIN customers c ON q.customer_id = c.id
|
||||
$where
|
||||
";
|
||||
$stmt = $pdo->prepare($countSql);
|
||||
$stmt->execute($params);
|
||||
$total = (int) $stmt->fetchColumn();
|
||||
|
||||
$offset = ($page - 1) * $perPage;
|
||||
|
||||
$sql = "
|
||||
SELECT q.id, q.quotation_number, q.project_code, q.created_at, q.valid_until,
|
||||
q.currency, q.language, q.apply_vat, q.vat_rate, q.exchange_rate,
|
||||
q.customer_id, q.order_id, q.status,
|
||||
c.name as customer_name,
|
||||
(SELECT COALESCE(SUM(CASE WHEN qi.is_included_in_total THEN qi.quantity * qi.unit_price ELSE 0 END), 0)
|
||||
FROM quotation_items qi WHERE qi.quotation_id = q.id) as total
|
||||
FROM quotations q
|
||||
LEFT JOIN customers c ON q.customer_id = c.id
|
||||
$where
|
||||
ORDER BY $sortCol $order
|
||||
LIMIT $perPage OFFSET $offset
|
||||
";
|
||||
|
||||
$stmt = $pdo->prepare($sql);
|
||||
$stmt->execute($params);
|
||||
$quotations = $stmt->fetchAll();
|
||||
|
||||
successResponse([
|
||||
'quotations' => $quotations,
|
||||
'total' => $total,
|
||||
'page' => $page,
|
||||
'per_page' => $perPage,
|
||||
]);
|
||||
}
|
||||
|
||||
function handleGetDetail(PDO $pdo, int $id): void
|
||||
{
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT q.*, c.name as customer_name
|
||||
FROM quotations q
|
||||
LEFT JOIN customers c ON q.customer_id = c.id
|
||||
WHERE q.id = ?
|
||||
');
|
||||
$stmt->execute([$id]);
|
||||
$quotation = $stmt->fetch();
|
||||
|
||||
if (!$quotation) {
|
||||
errorResponse('Nabídka nebyla nalezena', 404);
|
||||
}
|
||||
|
||||
// Get items
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT * FROM quotation_items
|
||||
WHERE quotation_id = ?
|
||||
ORDER BY position
|
||||
');
|
||||
$stmt->execute([$id]);
|
||||
$quotation['items'] = $stmt->fetchAll();
|
||||
|
||||
// Get scope sections
|
||||
$stmt = $pdo->prepare('
|
||||
SELECT * FROM scope_sections
|
||||
WHERE quotation_id = ?
|
||||
ORDER BY position
|
||||
');
|
||||
$stmt->execute([$id]);
|
||||
$quotation['sections'] = $stmt->fetchAll();
|
||||
|
||||
// Get customer
|
||||
if ($quotation['customer_id']) {
|
||||
$stmt = $pdo->prepare(
|
||||
'SELECT id, name, company_id, vat_id, street, city, postal_code, country, custom_fields
|
||||
FROM customers WHERE id = ?'
|
||||
);
|
||||
$stmt->execute([$quotation['customer_id']]);
|
||||
$quotation['customer'] = $stmt->fetch();
|
||||
}
|
||||
|
||||
// Get linked order info
|
||||
if ($quotation['order_id']) {
|
||||
$stmt = $pdo->prepare('SELECT id, order_number, status FROM orders WHERE id = ?');
|
||||
$stmt->execute([$quotation['order_id']]);
|
||||
$quotation['order'] = $stmt->fetch() ?: null;
|
||||
} else {
|
||||
$quotation['order'] = null;
|
||||
}
|
||||
|
||||
successResponse($quotation);
|
||||
}
|
||||
|
||||
function handleGetNextNumber(PDO $pdo): void
|
||||
{
|
||||
$settings = $pdo->query('SELECT quotation_prefix FROM company_settings LIMIT 1')->fetch();
|
||||
if (!$settings) {
|
||||
errorResponse('Nastavení firmy nenalezeno');
|
||||
}
|
||||
|
||||
$year = date('Y');
|
||||
$prefix = $settings['quotation_prefix'] ?: 'N';
|
||||
$number = getMaxQuotationNumber($pdo, $year, $prefix) + 1;
|
||||
|
||||
$formatted = sprintf('%s/%s/%03d', $year, $prefix, $number);
|
||||
|
||||
successResponse([
|
||||
'number' => $formatted,
|
||||
'raw_number' => $number,
|
||||
'prefix' => $prefix,
|
||||
'year' => $year,
|
||||
]);
|
||||
}
|
||||
|
||||
function getMaxQuotationNumber(PDO $pdo, string $year, string $prefix): int
|
||||
{
|
||||
$likePattern = "{$year}/{$prefix}/%";
|
||||
$stmt = $pdo->prepare("
|
||||
SELECT COALESCE(MAX(CAST(SUBSTRING_INDEX(quotation_number, '/', -1) AS UNSIGNED)), 0)
|
||||
FROM quotations
|
||||
WHERE quotation_number LIKE ?
|
||||
");
|
||||
$stmt->execute([$likePattern]);
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
function generateNextNumber(PDO $pdo): string
|
||||
{
|
||||
$settings = $pdo->query('SELECT quotation_prefix FROM company_settings LIMIT 1')->fetch();
|
||||
|
||||
$year = date('Y');
|
||||
$prefix = $settings['quotation_prefix'] ?: 'N';
|
||||
$number = getMaxQuotationNumber($pdo, $year, $prefix) + 1;
|
||||
|
||||
return sprintf('%s/%s/%03d', $year, $prefix, $number);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $q */
|
||||
function validateQuotationInput(array $q): void
|
||||
{
|
||||
if (empty($q['customer_id'])) {
|
||||
errorResponse('Vyberte zákazníka');
|
||||
}
|
||||
if (empty($q['created_at'])) {
|
||||
errorResponse('Zadejte datum vytvoření');
|
||||
}
|
||||
if (empty($q['valid_until'])) {
|
||||
errorResponse('Zadejte datum platnosti');
|
||||
}
|
||||
if (!empty($q['created_at']) && !empty($q['valid_until']) && $q['valid_until'] < $q['created_at']) {
|
||||
errorResponse('Datum platnosti nesmí být před datem vytvoření');
|
||||
}
|
||||
if (empty($q['currency'])) {
|
||||
errorResponse('Vyberte měnu');
|
||||
}
|
||||
|
||||
// Validace formatu dat
|
||||
foreach (['created_at', 'valid_until'] as $dateField) {
|
||||
if (!empty($q[$dateField]) && !preg_match('/^\d{4}-\d{2}-\d{2}$/', $q[$dateField])) {
|
||||
errorResponse("Neplatný formát data: $dateField");
|
||||
}
|
||||
}
|
||||
// Validace meny a jazyka
|
||||
if (!in_array($q['currency'] ?? '', ['EUR', 'USD', 'CZK', 'GBP'])) {
|
||||
errorResponse('Neplatná měna');
|
||||
}
|
||||
if (!empty($q['language']) && !in_array($q['language'], ['EN', 'CZ'])) {
|
||||
errorResponse('Neplatný jazyk');
|
||||
}
|
||||
// Validace DPH
|
||||
if (isset($q['vat_rate'])) {
|
||||
$rate = floatval($q['vat_rate']);
|
||||
if ($rate < 0 || $rate > 100) {
|
||||
errorResponse('Sazba DPH musí být mezi 0 a 100');
|
||||
}
|
||||
}
|
||||
// Delkove limity
|
||||
if (!empty($q['project_code']) && mb_strlen($q['project_code']) > 100) {
|
||||
errorResponse('Kód projektu je příliš dlouhý (max 100 znaků)');
|
||||
}
|
||||
}
|
||||
|
||||
function handleCreateOffer(PDO $pdo): void
|
||||
{
|
||||
$input = getJsonInput();
|
||||
$quotation = $input['quotation'] ?? $input;
|
||||
$items = $input['items'] ?? [];
|
||||
$sections = $input['sections'] ?? [];
|
||||
|
||||
validateQuotationInput($quotation);
|
||||
|
||||
// Serialize number generation across concurrent requests
|
||||
$locked = $pdo->query("SELECT GET_LOCK('boha_quotation_number', 5)")->fetchColumn();
|
||||
if (!$locked) {
|
||||
errorResponse('Nepodařilo se získat zámek pro číslo nabídky, zkuste to znovu', 503);
|
||||
}
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$quotationNumber = generateNextNumber($pdo);
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO quotations (
|
||||
quotation_number, project_code, customer_id, created_at, valid_until,
|
||||
currency, language, vat_rate, apply_vat, exchange_rate,
|
||||
scope_title, scope_description, modified_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
$quotationNumber,
|
||||
$quotation['project_code'] ?? '',
|
||||
$quotation['customer_id'] ? (int)$quotation['customer_id'] : null,
|
||||
$quotation['created_at'] ?? date('Y-m-d H:i:s'),
|
||||
$quotation['valid_until'] ?? date('Y-m-d H:i:s', strtotime('+30 days')),
|
||||
$quotation['currency'] ?? 'EUR',
|
||||
$quotation['language'] ?? 'EN',
|
||||
$quotation['vat_rate'] ?? 21,
|
||||
isset($quotation['apply_vat']) ? ($quotation['apply_vat'] ? 1 : 0) : 0,
|
||||
$quotation['exchange_rate'] ?? null,
|
||||
$quotation['scope_title'] ?? '',
|
||||
$quotation['scope_description'] ?? '',
|
||||
]);
|
||||
|
||||
$quotationId = (int)$pdo->lastInsertId();
|
||||
|
||||
saveItems($pdo, $quotationId, $items);
|
||||
saveSections($pdo, $quotationId, $sections);
|
||||
|
||||
|
||||
$pdo->commit();
|
||||
$pdo->query("SELECT RELEASE_LOCK('boha_quotation_number')");
|
||||
|
||||
AuditLog::logCreate('offers_quotation', $quotationId, [
|
||||
'quotation_number' => $quotationNumber,
|
||||
'project_code' => $quotation['project_code'] ?? '',
|
||||
], "Vytvořena nabídka '$quotationNumber'");
|
||||
|
||||
successResponse([
|
||||
'id' => $quotationId,
|
||||
'number' => $quotationNumber,
|
||||
], 'Nabídka byla vytvořena');
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$pdo->query("SELECT RELEASE_LOCK('boha_quotation_number')");
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function handleUpdateOffer(PDO $pdo, int $id): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT * FROM quotations WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$existing = $stmt->fetch();
|
||||
|
||||
if (!$existing) {
|
||||
errorResponse('Nabídka nebyla nalezena', 404);
|
||||
}
|
||||
|
||||
if ($existing['status'] === 'invalidated') {
|
||||
errorResponse('Zneplatněnou nabídku nelze upravovat', 403);
|
||||
}
|
||||
|
||||
$input = getJsonInput();
|
||||
$quotation = $input['quotation'] ?? $input;
|
||||
$items = $input['items'] ?? [];
|
||||
$sections = $input['sections'] ?? [];
|
||||
|
||||
validateQuotationInput($quotation);
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$stmt = $pdo->prepare('
|
||||
UPDATE quotations SET
|
||||
project_code = ?,
|
||||
customer_id = ?,
|
||||
created_at = ?,
|
||||
valid_until = ?,
|
||||
currency = ?,
|
||||
language = ?,
|
||||
vat_rate = ?,
|
||||
apply_vat = ?,
|
||||
exchange_rate = ?,
|
||||
scope_title = ?,
|
||||
scope_description = ?,
|
||||
modified_at = NOW()
|
||||
WHERE id = ?
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
$quotation['project_code'] ?? $existing['project_code'],
|
||||
isset($quotation['customer_id'])
|
||||
? ($quotation['customer_id'] ? (int)$quotation['customer_id'] : null)
|
||||
: $existing['customer_id'],
|
||||
$quotation['created_at'] ?? $existing['created_at'],
|
||||
$quotation['valid_until'] ?? $existing['valid_until'],
|
||||
$quotation['currency'] ?? $existing['currency'],
|
||||
$quotation['language'] ?? $existing['language'],
|
||||
$quotation['vat_rate'] ?? $existing['vat_rate'],
|
||||
isset($quotation['apply_vat']) ? ($quotation['apply_vat'] ? 1 : 0) : $existing['apply_vat'],
|
||||
array_key_exists('exchange_rate', $quotation) ? $quotation['exchange_rate'] : $existing['exchange_rate'],
|
||||
$quotation['scope_title'] ?? $existing['scope_title'],
|
||||
$quotation['scope_description'] ?? $existing['scope_description'],
|
||||
$id,
|
||||
]);
|
||||
|
||||
// Replace items
|
||||
$stmt = $pdo->prepare('DELETE FROM quotation_items WHERE quotation_id = ?');
|
||||
$stmt->execute([$id]);
|
||||
saveItems($pdo, $id, $items);
|
||||
|
||||
// Replace sections
|
||||
$stmt = $pdo->prepare('DELETE FROM scope_sections WHERE quotation_id = ?');
|
||||
$stmt->execute([$id]);
|
||||
saveSections($pdo, $id, $sections);
|
||||
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
AuditLog::logUpdate(
|
||||
'offers_quotation',
|
||||
$id,
|
||||
['quotation_number' => $existing['quotation_number']],
|
||||
['project_code' => $quotation['project_code'] ?? $existing['project_code']],
|
||||
"Upravena nabídka '{$existing['quotation_number']}'"
|
||||
);
|
||||
|
||||
successResponse(null, 'Nabídka byla aktualizována');
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function handleDuplicate(PDO $pdo, int $sourceId): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT * FROM quotations WHERE id = ?');
|
||||
$stmt->execute([$sourceId]);
|
||||
$source = $stmt->fetch();
|
||||
|
||||
if (!$source) {
|
||||
errorResponse('Zdrojová nabídka nebyla nalezena', 404);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('SELECT * FROM quotation_items WHERE quotation_id = ? ORDER BY position');
|
||||
$stmt->execute([$sourceId]);
|
||||
$sourceItems = $stmt->fetchAll();
|
||||
|
||||
$stmt = $pdo->prepare('SELECT * FROM scope_sections WHERE quotation_id = ? ORDER BY position');
|
||||
$stmt->execute([$sourceId]);
|
||||
$sourceSections = $stmt->fetchAll();
|
||||
|
||||
$locked = $pdo->query("SELECT GET_LOCK('boha_quotation_number', 5)")->fetchColumn();
|
||||
if (!$locked) {
|
||||
errorResponse('Nepodařilo se získat zámek pro číslo nabídky, zkuste to znovu', 503);
|
||||
}
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$newNumber = generateNextNumber($pdo);
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO quotations (
|
||||
quotation_number, project_code, customer_id, created_at, valid_until,
|
||||
currency, language, vat_rate, apply_vat, exchange_rate,
|
||||
scope_title, scope_description, modified_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
||||
');
|
||||
|
||||
$stmt->execute([
|
||||
$newNumber,
|
||||
$source['project_code'],
|
||||
$source['customer_id'],
|
||||
date('Y-m-d H:i:s'),
|
||||
date('Y-m-d H:i:s', strtotime('+30 days')),
|
||||
$source['currency'],
|
||||
$source['language'],
|
||||
$source['vat_rate'],
|
||||
$source['apply_vat'],
|
||||
$source['exchange_rate'],
|
||||
$source['scope_title'],
|
||||
$source['scope_description'],
|
||||
]);
|
||||
|
||||
$newId = (int)$pdo->lastInsertId();
|
||||
|
||||
$items = array_map(function ($item) {
|
||||
return [
|
||||
'description' => $item['description'],
|
||||
'item_description' => $item['item_description'],
|
||||
'quantity' => $item['quantity'],
|
||||
'unit_price' => $item['unit_price'],
|
||||
'is_included_in_total' => $item['is_included_in_total'],
|
||||
'position' => $item['position'],
|
||||
];
|
||||
}, $sourceItems);
|
||||
saveItems($pdo, $newId, $items);
|
||||
|
||||
$sections = array_map(function ($section) {
|
||||
return [
|
||||
'title' => $section['title'],
|
||||
'title_cz' => $section['title_cz'],
|
||||
'content' => $section['content'],
|
||||
'position' => $section['position'],
|
||||
];
|
||||
}, $sourceSections);
|
||||
saveSections($pdo, $newId, $sections);
|
||||
|
||||
$pdo->commit();
|
||||
$pdo->query("SELECT RELEASE_LOCK('boha_quotation_number')");
|
||||
|
||||
AuditLog::logCreate('offers_quotation', $newId, [
|
||||
'quotation_number' => $newNumber,
|
||||
'duplicated_from' => $source['quotation_number'],
|
||||
], "Duplikována nabídka '{$source['quotation_number']}' jako '$newNumber'");
|
||||
|
||||
successResponse([
|
||||
'id' => $newId,
|
||||
'number' => $newNumber,
|
||||
], 'Nabídka byla duplikována');
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
$pdo->query("SELECT RELEASE_LOCK('boha_quotation_number')");
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
function handleInvalidateOffer(PDO $pdo, int $id): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT quotation_number, status, order_id FROM quotations WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$quotation = $stmt->fetch();
|
||||
|
||||
if (!$quotation) {
|
||||
errorResponse('Nabídka nebyla nalezena', 404);
|
||||
}
|
||||
|
||||
if ($quotation['status'] === 'invalidated') {
|
||||
errorResponse('Nabídka je již zneplatněna', 400);
|
||||
}
|
||||
|
||||
if ($quotation['order_id']) {
|
||||
errorResponse('Nabídku s objednávkou nelze zneplatnit', 400);
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('UPDATE quotations SET status = ?, modified_at = NOW() WHERE id = ?');
|
||||
$stmt->execute(['invalidated', $id]);
|
||||
|
||||
AuditLog::logUpdate(
|
||||
'offers_quotation',
|
||||
$id,
|
||||
['status' => 'active'],
|
||||
['status' => 'invalidated'],
|
||||
"Zneplatněna nabídka '{$quotation['quotation_number']}'"
|
||||
);
|
||||
|
||||
successResponse(null, 'Nabídka byla zneplatněna');
|
||||
}
|
||||
|
||||
function handleDeleteQuotation(PDO $pdo, int $id): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT quotation_number FROM quotations WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
$quotation = $stmt->fetch();
|
||||
|
||||
|
||||
if (!$quotation) {
|
||||
errorResponse('Nabídka nebyla nalezena', 404);
|
||||
}
|
||||
|
||||
$pdo->beginTransaction();
|
||||
try {
|
||||
$stmt = $pdo->prepare('DELETE FROM quotation_items WHERE quotation_id = ?');
|
||||
$stmt->execute([$id]);
|
||||
|
||||
$stmt = $pdo->prepare('DELETE FROM scope_sections WHERE quotation_id = ?');
|
||||
$stmt->execute([$id]);
|
||||
|
||||
$stmt = $pdo->prepare('DELETE FROM quotations WHERE id = ?');
|
||||
$stmt->execute([$id]);
|
||||
|
||||
$pdo->commit();
|
||||
|
||||
AuditLog::logDelete('offers_quotation', $id, [
|
||||
'quotation_number' => $quotation['quotation_number'],
|
||||
], "Smazána nabídka '{$quotation['quotation_number']}'");
|
||||
|
||||
successResponse(null, 'Nabídka byla smazána');
|
||||
} catch (PDOException $e) {
|
||||
$pdo->rollBack();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
/** @param list<array<string, mixed>> $items */
|
||||
function saveItems(PDO $pdo, int $quotationId, array $items): void
|
||||
{
|
||||
if (empty($items)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO quotation_items (
|
||||
quotation_id, description, item_description, quantity, unit,
|
||||
unit_price, is_included_in_total, position, modified_at
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
||||
');
|
||||
|
||||
foreach ($items as $i => $item) {
|
||||
$stmt->execute([
|
||||
$quotationId,
|
||||
$item['description'] ?? '',
|
||||
$item['item_description'] ?? '',
|
||||
$item['quantity'] ?? 1,
|
||||
$item['unit'] ?? '',
|
||||
$item['unit_price'] ?? 0,
|
||||
isset($item['is_included_in_total']) ? ($item['is_included_in_total'] ? 1 : 0) : 1,
|
||||
$item['position'] ?? ($i + 1),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param list<array<string, mixed>> $sections */
|
||||
function saveSections(PDO $pdo, int $quotationId, array $sections): void
|
||||
{
|
||||
if (empty($sections)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$stmt = $pdo->prepare('
|
||||
INSERT INTO scope_sections (
|
||||
quotation_id, title, title_cz, content, position, modified_at
|
||||
) VALUES (?, ?, ?, ?, ?, NOW())
|
||||
');
|
||||
|
||||
foreach ($sections as $i => $section) {
|
||||
$stmt->execute([
|
||||
$quotationId,
|
||||
$section['title'] ?? '',
|
||||
$section['title_cz'] ?? '',
|
||||
$section['content'] ?? '',
|
||||
$section['position'] ?? ($i + 1),
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user