feat(config): agrega opcion para eliminar empleados
- Backend: nuevo endpoint DELETE /employees/<id> que desactiva (soft-delete). No permite eliminar cuentas de dueno. - Frontend: boton Eliminar en la tabla de empleados con confirmacion. - Exponer deleteEmployee en el modulo Config. Tests: 35 passed
This commit is contained in:
@@ -312,6 +312,35 @@ def update_employee(emp_id):
|
|||||||
return jsonify({'ok': True, 'message': 'Employee updated'})
|
return jsonify({'ok': True, 'message': 'Employee updated'})
|
||||||
|
|
||||||
|
|
||||||
|
@config_bp.route('/employees/<int:emp_id>', methods=['DELETE'])
|
||||||
|
@require_auth('config.edit')
|
||||||
|
def delete_employee(emp_id):
|
||||||
|
"""Soft-delete (deactivate) an employee. Owners cannot be deleted via UI
|
||||||
|
to prevent locking out the tenant; use direct DB access if really needed."""
|
||||||
|
conn = get_tenant_conn(g.tenant_id)
|
||||||
|
cur = conn.cursor()
|
||||||
|
|
||||||
|
cur.execute("SELECT role FROM employees WHERE id = %s", (emp_id,))
|
||||||
|
row = cur.fetchone()
|
||||||
|
if not row:
|
||||||
|
cur.close(); conn.close()
|
||||||
|
return jsonify({'error': 'Employee not found'}), 404
|
||||||
|
|
||||||
|
if row[0] == 'owner':
|
||||||
|
cur.close(); conn.close()
|
||||||
|
return jsonify({'error': 'No se puede eliminar una cuenta de dueno'}), 403
|
||||||
|
|
||||||
|
cur.execute("UPDATE employees SET is_active = false WHERE id = %s", (emp_id,))
|
||||||
|
|
||||||
|
from services.audit import log_action
|
||||||
|
log_action(conn, 'EMPLOYEE_DEACTIVATE', 'employee', emp_id)
|
||||||
|
|
||||||
|
conn.commit()
|
||||||
|
cur.close()
|
||||||
|
conn.close()
|
||||||
|
return jsonify({'ok': True, 'message': 'Employee deactivated'})
|
||||||
|
|
||||||
|
|
||||||
@config_bp.route('/currency', methods=['GET'])
|
@config_bp.route('/currency', methods=['GET'])
|
||||||
@require_auth()
|
@require_auth()
|
||||||
def get_currency():
|
def get_currency():
|
||||||
|
|||||||
@@ -298,7 +298,10 @@ const Config = (() => {
|
|||||||
+ '<td>' + escHtml(emp.branch_name || 'Todas') + '</td>'
|
+ '<td>' + escHtml(emp.branch_name || 'Todas') + '</td>'
|
||||||
+ '<td>' + statusBadge + '</td>'
|
+ '<td>' + statusBadge + '</td>'
|
||||||
+ '<td>' + (emp.max_discount_pct || 0) + '%</td>'
|
+ '<td>' + (emp.max_discount_pct || 0) + '%</td>'
|
||||||
+ '<td><button class="btn btn--ghost btn--sm" onclick="Config.editEmployee(' + emp.id + ')">Editar</button></td>'
|
+ '<td>'
|
||||||
|
+ '<button class="btn btn--ghost btn--sm" onclick="Config.editEmployee(' + emp.id + ')">Editar</button>'
|
||||||
|
+ (emp.role !== 'owner' ? ' <button class="btn btn--danger btn--sm" onclick="Config.deleteEmployee(' + emp.id + ', \'' + escHtml(emp.name).replace(/\\/g, '\\\\').replace(/'/g, "\\'") + '\')">Eliminar</button>' : '')
|
||||||
|
+ '</td>'
|
||||||
+ '</tr>';
|
+ '</tr>';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -362,6 +365,21 @@ const Config = (() => {
|
|||||||
return el ? el.value.trim() : '';
|
return el ? el.value.trim() : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteEmployee(empId, name) {
|
||||||
|
if (!confirm('¿Eliminar al empleado "' + name + '"? Esta accion lo desactiva.')) return;
|
||||||
|
try {
|
||||||
|
var res = await fetch(API + '/employees/' + empId, { method: 'DELETE', headers: headers() });
|
||||||
|
if (!res.ok) {
|
||||||
|
var err = await res.json().catch(function() { return { error: 'Error ' + res.status }; });
|
||||||
|
throw new Error(err.error || 'Error al eliminar');
|
||||||
|
}
|
||||||
|
toast('Empleado eliminado', 'ok');
|
||||||
|
loadEmployees();
|
||||||
|
} catch (e) {
|
||||||
|
toast(e.message, 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function editEmployee(empId) {
|
async function editEmployee(empId) {
|
||||||
if (!checkAuth()) return;
|
if (!checkAuth()) return;
|
||||||
// Find the employee in the loaded data by re-fetching
|
// Find the employee in the loaded data by re-fetching
|
||||||
@@ -1028,7 +1046,7 @@ const Config = (() => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
init, setTheme, selectThemeOption, loadAllowedBrands, saveAllowedBrands,
|
init, setTheme, selectThemeOption, loadAllowedBrands, saveAllowedBrands,
|
||||||
loadBranches, loadEmployees, saveBranch, saveEmployee, editEmployee,
|
loadBranches, loadEmployees, saveBranch, saveEmployee, editEmployee, deleteEmployee,
|
||||||
loadBusiness, saveBusiness, saveTaxParams, saveAll,
|
loadBusiness, saveBusiness, saveTaxParams, saveAll,
|
||||||
loadCurrency, saveCurrency,
|
loadCurrency, saveCurrency,
|
||||||
loadVehicleCompatSource, saveVehicleCompatSource,
|
loadVehicleCompatSource, saveVehicleCompatSource,
|
||||||
|
|||||||
Reference in New Issue
Block a user