fix: company save 500 error — catch DB exceptions, show error to user
- CompanyRepository::save() now catches PDOException and throws a RuntimeException with a human-readable message (duplicate name, etc.) - Also fixes unchecked checkboxes (requires_approval, is_active) not being saved as 0 on UPDATE - index.php save handler catches RuntimeException and redirects back to the form with an ?error= param instead of crashing with 500 - companyEdit() renders the error banner when ?error= is present Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
68317da748
commit
b25126e52b
@@ -1017,7 +1017,12 @@ HTML;
|
|||||||
$botOptions .= "<option value=\"{$val}\" {$sel}>{$lbl}</option>\n";
|
$botOptions .= "<option value=\"{$val}\" {$sel}>{$lbl}</option>\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
$pageTitle = $isEdit ? 'Editar Empresa' : 'Nueva Empresa';
|
$pageTitle = $isEdit ? 'Editar Empresa' : 'Nueva Empresa';
|
||||||
|
$errorBanner = '';
|
||||||
|
if (!empty($_GET['error'])) {
|
||||||
|
$errText = self::h(urldecode($_GET['error']));
|
||||||
|
$errorBanner = "<div style='background:#fef2f2;border:1px solid #fca5a5;color:#991b1b;padding:12px 16px;border-radius:8px;margin-bottom:16px;font-size:14px'>⚠ {$errText}</div>";
|
||||||
|
}
|
||||||
echo Layout::open($pageTitle, 'companies', $user['name'] ?? 'Admin');
|
echo Layout::open($pageTitle, 'companies', $user['name'] ?? 'Admin');
|
||||||
|
|
||||||
// ── Tab: Números WhatsApp ─────────────────────────────────────────────
|
// ── Tab: Números WhatsApp ─────────────────────────────────────────────
|
||||||
@@ -1251,6 +1256,7 @@ HTML : '';
|
|||||||
<h1 style="font-size:18px;font-weight:700;color:#111827">{$pageTitle}</h1>
|
<h1 style="font-size:18px;font-weight:700;color:#111827">{$pageTitle}</h1>
|
||||||
<a href="/admin/companies" class="btn-secondary" style="font-size:12px">← Empresas</a>
|
<a href="/admin/companies" class="btn-secondary" style="font-size:12px">← Empresas</a>
|
||||||
</div>
|
</div>
|
||||||
|
{$errorBanner}
|
||||||
{$tabsHtml}
|
{$tabsHtml}
|
||||||
|
|
||||||
<!-- Tab General -->
|
<!-- Tab General -->
|
||||||
|
|||||||
+11
-6
@@ -241,12 +241,17 @@ $routes = [
|
|||||||
// ─── Guardar empresa (crear/actualizar) ─────────────────────────────────
|
// ─── Guardar empresa (crear/actualizar) ─────────────────────────────────
|
||||||
['POST', '/admin/company/save', fn() => (function () {
|
['POST', '/admin/company/save', fn() => (function () {
|
||||||
SessionAuth::require();
|
SessionAuth::require();
|
||||||
$data = $_POST;
|
$data = $_POST;
|
||||||
$id = CompanyRepository::save($data);
|
$isNew = empty($data['id']) || (int)$data['id'] === 0;
|
||||||
if ($id > 0) {
|
try {
|
||||||
header('Location: /admin/companies?msg=' . ($data['id'] ?? 0 > 0 ? 'updated' : 'created'));
|
CompanyRepository::save($data);
|
||||||
} else {
|
header('Location: /admin/companies?msg=' . ($isNew ? 'created' : 'updated'));
|
||||||
header('Location: /admin/companies?msg=error');
|
} catch (\RuntimeException $e) {
|
||||||
|
$errMsg = urlencode($e->getMessage());
|
||||||
|
$redirect = $isNew
|
||||||
|
? '/admin/companies/new?error=' . $errMsg
|
||||||
|
: '/admin/company/edit?id=' . (int)$data['id'] . '&error=' . $errMsg;
|
||||||
|
header('Location: ' . $redirect);
|
||||||
}
|
}
|
||||||
exit;
|
exit;
|
||||||
})()],
|
})()],
|
||||||
|
|||||||
@@ -33,12 +33,23 @@ class CompanyRepository
|
|||||||
return db()->query("SELECT * FROM companies WHERE {$where} ORDER BY name")->fetchAll();
|
return db()->query("SELECT * FROM companies WHERE {$where} ORDER BY name")->fetchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws \RuntimeException with a human-readable message on DB error
|
||||||
|
*/
|
||||||
public static function save(array $data): int
|
public static function save(array $data): int
|
||||||
{
|
{
|
||||||
$db = db();
|
$db = db();
|
||||||
$id = (int)($data['id'] ?? 0);
|
$id = (int)($data['id'] ?? 0);
|
||||||
|
|
||||||
$fields = ['name', 'display_name', 'phone_number_id', 'display_phone', 'api_base_url', 'api_key', 'bot_type', 'requires_approval', 'is_active', 'config_json', 'erp_active_users'];
|
$fields = ['name', 'display_name', 'phone_number_id', 'display_phone', 'api_base_url', 'api_key', 'bot_type', 'requires_approval', 'is_active', 'config_json', 'erp_active_users'];
|
||||||
|
|
||||||
|
// Checkboxes: absent when unchecked — explicitly set to 0 so UPDATE clears them
|
||||||
|
foreach (['requires_approval', 'is_active'] as $cb) {
|
||||||
|
if (!array_key_exists($cb, $data)) {
|
||||||
|
$data[$cb] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$params = [];
|
$params = [];
|
||||||
$sets = [];
|
$sets = [];
|
||||||
foreach ($fields as $f) {
|
foreach ($fields as $f) {
|
||||||
@@ -48,18 +59,19 @@ class CompanyRepository
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($id > 0) {
|
try {
|
||||||
$params[] = $id;
|
if ($id > 0) {
|
||||||
$stmt = $db->prepare("UPDATE companies SET " . implode(', ', $sets) . " WHERE id = ?");
|
$params[] = $id;
|
||||||
$stmt->execute($params);
|
$db->prepare("UPDATE companies SET " . implode(', ', $sets) . " WHERE id = ?")
|
||||||
return $id;
|
->execute($params);
|
||||||
} else {
|
return $id;
|
||||||
// Ensure required fields
|
|
||||||
$required = ['name', 'api_base_url'];
|
|
||||||
$insertData = [];
|
|
||||||
foreach ($required as $r) {
|
|
||||||
$insertData[$r] = $data[$r] ?? '';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// INSERT — require name
|
||||||
|
if (trim($data['name'] ?? '') === '') {
|
||||||
|
throw new \RuntimeException('El nombre de la empresa es requerido.');
|
||||||
|
}
|
||||||
|
$insertData = [];
|
||||||
foreach ($fields as $f) {
|
foreach ($fields as $f) {
|
||||||
if (array_key_exists($f, $data)) {
|
if (array_key_exists($f, $data)) {
|
||||||
$insertData[$f] = $data[$f];
|
$insertData[$f] = $data[$f];
|
||||||
@@ -67,9 +79,17 @@ class CompanyRepository
|
|||||||
}
|
}
|
||||||
$cols = implode(', ', array_keys($insertData));
|
$cols = implode(', ', array_keys($insertData));
|
||||||
$vals = implode(', ', array_fill(0, count($insertData), '?'));
|
$vals = implode(', ', array_fill(0, count($insertData), '?'));
|
||||||
$stmt = $db->prepare("INSERT INTO companies ({$cols}) VALUES ({$vals})");
|
$db->prepare("INSERT INTO companies ({$cols}) VALUES ({$vals})")
|
||||||
$stmt->execute(array_values($insertData));
|
->execute(array_values($insertData));
|
||||||
return (int)$db->lastInsertId();
|
return (int)$db->lastInsertId();
|
||||||
|
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
$msg = $e->getMessage();
|
||||||
|
// Duplicate entry on name unique key
|
||||||
|
if (str_contains($msg, '1062') || str_contains($msg, 'Duplicate entry')) {
|
||||||
|
throw new \RuntimeException('Ya existe una empresa con ese nombre.');
|
||||||
|
}
|
||||||
|
throw new \RuntimeException('Error al guardar: ' . $msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user