feat(customers): show vehicles and delete customer
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- get_customer now returns fleet_vehicles assigned to the customer.

- Added DELETE /pos/api/customers/<id> soft-delete (is_active=false) with customers.delete permission.

- Added Vehicles section in customer detail panel.

- Added Eliminar button in quick actions (owner/admin/customers.delete).
This commit is contained in:
2026-06-30 19:58:45 +00:00
parent 66b8fe3d69
commit 073c97406b
4 changed files with 114 additions and 3 deletions

View File

@@ -11,6 +11,11 @@ const Customers = (() => {
let currentCustomer = null;
let searchTimeout = null;
const user = window.POS_USER || {};
const userRole = (user.role || '').toLowerCase();
const userPerms = user.permissions || [];
const canDeleteCustomer = userRole === 'owner' || userRole === 'admin' || userPerms.includes('customers.delete');
const fmt = (n) => '$' + parseFloat(n || 0).toLocaleString('es-MX', {
minimumFractionDigits: 2, maximumFractionDigits: 2
});
@@ -295,6 +300,28 @@ const Customers = (() => {
const discountEl = document.getElementById('detailMaxDiscount');
if (discountEl) discountEl.textContent = (c.max_discount_pct || 0) + '%';
// Vehicles
const vehiclesEl = document.getElementById('detailVehicles');
if (vehiclesEl) {
const vehicles = c.fleet_vehicles || [];
if (vehicles.length === 0) {
vehiclesEl.innerHTML = '<span style="color:var(--color-text-muted);">Sin veh&iacute;culos registrados</span>';
} else {
vehiclesEl.innerHTML = vehicles.map(v => {
const title = [v.year, v.make, v.model].filter(Boolean).join(' ');
const subtitle = [v.plate, v.vin, v.color].filter(Boolean).join(' · ');
return `<div class="vehicle-row" style="padding:var(--space-2);border:1px solid var(--color-border);border-radius:var(--radius-md);">
<div style="font-weight:600;">${title || 'Veh&iacute;culo'}</div>
<div style="font-size:var(--text-caption);color:var(--color-text-muted);">${subtitle}</div>
</div>`;
}).join('');
}
}
// Delete button visibility
const btnDelete = document.getElementById('btnDeleteCustomer');
if (btnDelete) btnDelete.style.display = canDeleteCustomer ? 'inline-flex' : 'none';
// Re-wire action buttons after detail panel is visible
wireActionButtons();
@@ -396,7 +423,7 @@ const Customers = (() => {
// Wire action buttons in detail panel
function wireActionButtons() {
const btns = document.querySelectorAll('.quick-actions .action-btn');
// Order: Nueva Venta, Editar, Estado de Cuenta, Historial
// Order: Nueva Venta, Editar, Estado de Cuenta, Historial, Eliminar
if (btns.length >= 1) btns[0].onclick = () => {
if (currentCustomer) window.location.href = '/pos/sale?customer=' + currentCustomer.id;
};
@@ -405,8 +432,29 @@ const Customers = (() => {
if (btns.length >= 4) btns[3].onclick = () => {
if (currentCustomer) showCustomerHistory(currentCustomer.id);
};
const btnDelete = document.getElementById('btnDeleteCustomer');
if (btnDelete) btnDelete.onclick = () => deleteCustomer();
}
async function deleteCustomer() {
if (!currentCustomer) return;
if (!canDeleteCustomer) {
alert('No tienes permiso para eliminar clientes');
return;
}
if (!confirm(`¿Eliminar al cliente "${currentCustomer.name}"? Se marcará como inactivo.`)) return;
try {
await api(`/pos/api/customers/${currentCustomer.id}`, { method: 'DELETE' });
alert('Cliente eliminado');
currentCustomer = null;
closeDetail();
loadCustomers(currentPage);
} catch (e) {
alert('Error: ' + e.message);
}
}
window.deleteCustomer = deleteCustomer;
async function showCustomerHistory(customerId) {
try {
const res = await api(`/pos/api/customers/${customerId}/purchases`);
@@ -906,6 +954,7 @@ const Customers = (() => {
showStatement, closeStatement,
showPaymentModal, closePayment, recordPayment,
showCustomerHistory, closeCustomerHistoryModal,
deleteCustomer,
};
// Register Cmd+K items