feat(customers): hard-delete customers
DELETE /customers/<id> now removes the row. - Deletes layaways and dependent items/payments. - Nullifies customer_id in sales, service_orders, fleet_vehicles, plate_vehicles, quotations, returns, warranties, bnpl_transactions, customer_payments. - Cascade-delete CRM tables are handled by FK constraints.
This commit is contained in:
@@ -321,7 +321,12 @@ def update_customer(customer_id):
|
|||||||
@customers_bp.route('/<int:customer_id>', methods=['DELETE'])
|
@customers_bp.route('/<int:customer_id>', methods=['DELETE'])
|
||||||
@require_auth('customers.delete')
|
@require_auth('customers.delete')
|
||||||
def delete_customer(customer_id):
|
def delete_customer(customer_id):
|
||||||
"""Soft-delete (deactivate) a customer."""
|
"""Hard-delete a customer.
|
||||||
|
|
||||||
|
Related records (sales, service orders, vehicles, etc.) keep their data but
|
||||||
|
lose the customer reference. Layaways for this customer are removed because
|
||||||
|
they require a customer_id.
|
||||||
|
"""
|
||||||
conn = get_tenant_conn(g.tenant_id)
|
conn = get_tenant_conn(g.tenant_id)
|
||||||
cur = conn.cursor()
|
cur = conn.cursor()
|
||||||
|
|
||||||
@@ -330,8 +335,29 @@ def delete_customer(customer_id):
|
|||||||
cur.close(); conn.close()
|
cur.close(); conn.close()
|
||||||
return jsonify({'error': 'Customer not found'}), 404
|
return jsonify({'error': 'Customer not found'}), 404
|
||||||
|
|
||||||
cur.execute("UPDATE customers SET is_active = false WHERE id = %s", (customer_id,))
|
# Remove layaway dependencies first, then the layaways themselves
|
||||||
log_action(conn, 'CUSTOMER_DEACTIVATE', 'customer', customer_id)
|
cur.execute("DELETE FROM layaway_items WHERE layaway_id IN (SELECT id FROM layaways WHERE customer_id = %s)", (customer_id,))
|
||||||
|
cur.execute("DELETE FROM layaway_payments WHERE layaway_id IN (SELECT id FROM layaways WHERE customer_id = %s)", (customer_id,))
|
||||||
|
cur.execute("DELETE FROM layaways WHERE customer_id = %s", (customer_id,))
|
||||||
|
|
||||||
|
# Nullify every other FK reference back to customers.
|
||||||
|
cur.execute("""
|
||||||
|
SELECT c.relname::text AS tbl, a.attname::text AS col
|
||||||
|
FROM pg_constraint con
|
||||||
|
JOIN pg_class c ON c.oid = con.conrelid
|
||||||
|
JOIN pg_attribute a ON a.attrelid = con.conrelid AND a.attnum = ANY(con.conkey)
|
||||||
|
WHERE con.confrelid = 'public.customers'::regclass
|
||||||
|
AND con.contype = 'f'
|
||||||
|
""")
|
||||||
|
refs = cur.fetchall()
|
||||||
|
for tbl, col in refs:
|
||||||
|
# Cascade-delete tables and already-handled layaways are skipped.
|
||||||
|
if tbl in ('customer_activities', 'customer_tag_assignments', 'loyalty_points', 'loyalty_redemptions', 'layaways'):
|
||||||
|
continue
|
||||||
|
cur.execute(f'UPDATE "{tbl}" SET "{col}" = NULL WHERE "{col}" = %s', (customer_id,))
|
||||||
|
|
||||||
|
cur.execute("DELETE FROM customers WHERE id = %s", (customer_id,))
|
||||||
|
log_action(conn, 'CUSTOMER_DELETE', 'customer', customer_id)
|
||||||
conn.commit()
|
conn.commit()
|
||||||
cur.close()
|
cur.close()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|||||||
@@ -442,7 +442,7 @@ const Customers = (() => {
|
|||||||
alert('No tienes permiso para eliminar clientes');
|
alert('No tienes permiso para eliminar clientes');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!confirm(`¿Eliminar al cliente "${currentCustomer.name}"? Se marcará como inactivo.`)) return;
|
if (!confirm(`¿Eliminar al cliente "${currentCustomer.name}"? Se borrará completamente. Las ventas, órdenes y vehículos conservarán sus datos pero quedarán sin cliente asignado.`)) return;
|
||||||
try {
|
try {
|
||||||
await api(`/pos/api/customers/${currentCustomer.id}`, { method: 'DELETE' });
|
await api(`/pos/api/customers/${currentCustomer.id}`, { method: 'DELETE' });
|
||||||
alert('Cliente eliminado');
|
alert('Cliente eliminado');
|
||||||
|
|||||||
Reference in New Issue
Block a user