diff --git a/pos/services/service_order_engine.py b/pos/services/service_order_engine.py index 0a36e34..c5841c3 100644 --- a/pos/services/service_order_engine.py +++ b/pos/services/service_order_engine.py @@ -12,8 +12,7 @@ VALID_TRANSITIONS = { 'received': ['diagnosis', 'cancelled'], 'diagnosis': ['waiting_parts', 'repair', 'cancelled'], 'waiting_parts': ['repair', 'cancelled'], - 'repair': ['quality_check', 'cancelled'], - 'quality_check': ['ready', 'cancelled'], + 'repair': ['ready', 'cancelled'], 'ready': ['delivered', 'cancelled'], 'delivered': [], 'cancelled': [], @@ -455,9 +454,10 @@ def remove_labor(conn, labor_id): def update_service_order(conn, so_id, data): """Update general service order fields.""" cur = conn.cursor() - allowed = ['priority', 'reception_notes', 'diagnosis_notes', 'repair_notes', + allowed = ['customer_id', 'vehicle_id', 'branch_id', 'priority', + 'reception_notes', 'diagnosis_notes', 'repair_notes', 'delivery_notes', 'estimated_cost', 'estimated_completion', - 'employee_id', 'mileage_out', 'fuel_level', 'final_cost', + 'employee_id', 'mileage_in', 'mileage_out', 'fuel_level', 'final_cost', 'delivery_method', 'courier_id', 'is_direct'] sets = [] vals = [] diff --git a/pos/static/js/workshop.js b/pos/static/js/workshop.js index 15eeb1c..9ce9e10 100644 --- a/pos/static/js/workshop.js +++ b/pos/static/js/workshop.js @@ -18,6 +18,10 @@ var Workshop = (function() { var currentOrder = null; var selectedInventoryItem = null; var itemSearchTimer = null; + var customerSearchTimer = null; + var selectedEditCustomer = null; + var vehicleSearchTimer = null; + var selectedEditVehicle = null; var currentView = 'list'; var currentPage = 1; var perPage = 25; @@ -35,7 +39,6 @@ var Workshop = (function() { {key: 'diagnosis', label: 'Diagnóstico'}, {key: 'waiting_parts', label: 'Espera refacciones'}, {key: 'repair', label: 'En reparación'}, - {key: 'quality_check', label: 'Control calidad'}, {key: 'ready', label: 'Listo'}, {key: 'delivered', label: 'Entregado'}, ]; @@ -45,7 +48,6 @@ var Workshop = (function() { diagnosis: 'Diagnóstico', waiting_parts: 'Espera refacciones', repair: 'En reparación', - quality_check: 'Control calidad', ready: 'Listo', delivered: 'Entregado', cancelled: 'Cancelado' @@ -55,8 +57,7 @@ var Workshop = (function() { received: ['diagnosis', 'cancelled'], diagnosis: ['waiting_parts', 'repair', 'cancelled'], waiting_parts: ['repair', 'cancelled'], - repair: ['quality_check', 'cancelled'], - quality_check: ['ready', 'cancelled'], + repair: ['ready', 'cancelled'], ready: ['delivered', 'cancelled'], delivered: [], cancelled: [] @@ -100,6 +101,11 @@ var Workshop = (function() { return el.innerHTML; } + function escJs(s) { + if (s == null) return ''; + return String(s).replace(/\\/g, '\\\\').replace(/'/g, "\\'").replace(/"/g, '\\"'); + } + function api(method, url, body) { var opts = {method: method, headers: headers()}; if (body) opts.body = JSON.stringify(body); @@ -414,6 +420,7 @@ var Workshop = (function() { '' : ''; footer.innerHTML = statusHtml + 'Cerrar' + + (canEdit ? 'Editar orden' : '') + (canDelete ? 'Eliminar orden' : '') + '' + '' + @@ -528,6 +535,175 @@ var Workshop = (function() { currentOrder = null; } + function openEditOrderModal() { + if (!currentOrder) return; + populateEditOrderModal(); + document.getElementById('editOrderModal').classList.add('is-open'); + } + + function closeEditOrderModal() { + document.getElementById('editOrderModal').classList.remove('is-open'); + selectedEditCustomer = null; + selectedEditVehicle = null; + } + + function toDatetimeLocal(d) { + if (!d) return ''; + var date = new Date(d); + date.setMinutes(date.getMinutes() - date.getTimezoneOffset()); + return date.toISOString().slice(0, 16); + } + + async function populateEditOrderModal() { + var o = currentOrder; + selectedEditCustomer = o.customer_id ? {id: o.customer_id, name: o.customer_name || ''} : null; + selectedEditVehicle = o.vehicle_id ? {id: o.vehicle_id, label: (o.vehicle_plate || '') + ' ' + (o.vehicle_make || '') + ' ' + (o.vehicle_model || '')} : null; + + document.getElementById('eoCustomerSearch').value = selectedEditCustomer ? selectedEditCustomer.name : ''; + document.getElementById('eoCustomerId').value = selectedEditCustomer ? selectedEditCustomer.id : ''; + document.getElementById('eoVehicleSearch').value = selectedEditVehicle ? selectedEditVehicle.label.trim() : ''; + document.getElementById('eoVehicleId').value = selectedEditVehicle ? selectedEditVehicle.id : ''; + + // Branches + var branchSel = document.getElementById('eoBranch'); + branchSel.innerHTML = branches.map(function(b) { + return '' + esc(b.name) + ''; + }).join(''); + + // Mechanics/employees + var mechSel = document.getElementById('eoMechanic'); + mechSel.innerHTML = '— Ninguno —'; + try { + var res = await fetch('/pos/api/config/employees', {headers: headers()}); + var json = await res.json(); + (json.data || []).forEach(function(e) { + if (!e.is_active) return; + mechSel.innerHTML += '' + esc(e.name) + ''; + }); + } catch (e) {} + + document.getElementById('eoPriority').value = o.priority || 'normal'; + document.getElementById('eoFuelLevel').value = o.fuel_level || ''; + document.getElementById('eoMileageIn').value = o.mileage_in != null ? o.mileage_in : ''; + document.getElementById('eoMileageOut').value = o.mileage_out != null ? o.mileage_out : ''; + document.getElementById('eoEstimatedCompletion').value = toDatetimeLocal(o.estimated_completion); + document.getElementById('eoEstimatedCost').value = o.estimated_cost != null ? o.estimated_cost : ''; + document.getElementById('eoNotes').value = o.reception_notes || ''; + } + + function hideEditSearchResults(type) { + var box = document.getElementById(type === 'customer' ? 'eoCustomerResults' : 'eoVehicleResults'); + if (box) box.style.display = 'none'; + } + + function searchCustomersForSO() { + var input = document.getElementById('eoCustomerSearch'); + var box = document.getElementById('eoCustomerResults'); + var q = input.value.trim(); + selectedEditCustomer = null; + document.getElementById('eoCustomerId').value = ''; + if (!q || q.length < 2) { + box.style.display = 'none'; box.innerHTML = ''; + return; + } + clearTimeout(customerSearchTimer); + customerSearchTimer = setTimeout(function() { + fetch('/pos/api/customers?q=' + encodeURIComponent(q) + '&per_page=20', {headers: headers()}) + .then(function(r) { return r.json(); }) + .then(function(d) { + var items = d.data || []; + if (!items.length) { + box.innerHTML = 'Sin resultados'; + box.style.display = 'block'; return; + } + box.innerHTML = items.map(function(c) { + return '' + + '' + esc(c.name) + '' + + '' + esc(c.phone || '') + ' · ' + esc(c.rfc || '') + '' + + ''; + }).join(''); + box.style.display = 'block'; + }) + .catch(function() { box.style.display = 'none'; }); + }, 250); + } + + function selectEditCustomer(id, name) { + selectedEditCustomer = {id: id, name: name}; + document.getElementById('eoCustomerSearch').value = name; + document.getElementById('eoCustomerId').value = id; + hideEditSearchResults('customer'); + } + + function searchVehiclesForSO() { + var input = document.getElementById('eoVehicleSearch'); + var box = document.getElementById('eoVehicleResults'); + var q = input.value.trim(); + selectedEditVehicle = null; + document.getElementById('eoVehicleId').value = ''; + if (!q || q.length < 2) { + box.style.display = 'none'; box.innerHTML = ''; + return; + } + clearTimeout(vehicleSearchTimer); + vehicleSearchTimer = setTimeout(function() { + fetch('/pos/api/fleet/vehicles?q=' + encodeURIComponent(q) + '&per_page=20&active_only=true', {headers: headers()}) + .then(function(r) { return r.json(); }) + .then(function(d) { + var items = d.data || []; + if (!items.length) { + box.innerHTML = 'Sin resultados'; + box.style.display = 'block'; return; + } + box.innerHTML = items.map(function(v) { + var label = (v.plate || '') + ' · ' + (v.make || '') + ' ' + (v.model || ''); + return '' + + '' + esc(v.plate || '') + '' + + '' + esc(v.make || '') + ' ' + esc(v.model || '') + ' · ' + esc(v.owner_name || '') + '' + + ''; + }).join(''); + box.style.display = 'block'; + }) + .catch(function() { box.style.display = 'none'; }); + }, 250); + } + + function selectEditVehicle(id, label) { + selectedEditVehicle = {id: id, label: label}; + document.getElementById('eoVehicleSearch').value = label; + document.getElementById('eoVehicleId').value = id; + hideEditSearchResults('vehicle'); + } + + async function saveOrderChanges() { + if (!currentOrderId) return; + var customerId = selectedEditCustomer ? selectedEditCustomer.id : parseInt(document.getElementById('eoCustomerId').value, 10) || null; + var vehicleId = selectedEditVehicle ? selectedEditVehicle.id : parseInt(document.getElementById('eoVehicleId').value, 10) || null; + var payload = { + customer_id: customerId, + vehicle_id: vehicleId, + branch_id: parseInt(document.getElementById('eoBranch').value, 10) || null, + employee_id: parseInt(document.getElementById('eoMechanic').value, 10) || null, + priority: document.getElementById('eoPriority').value, + fuel_level: document.getElementById('eoFuelLevel').value || null, + mileage_in: document.getElementById('eoMileageIn').value ? parseInt(document.getElementById('eoMileageIn').value, 10) : null, + mileage_out: document.getElementById('eoMileageOut').value ? parseInt(document.getElementById('eoMileageOut').value, 10) : null, + estimated_completion: document.getElementById('eoEstimatedCompletion').value || null, + estimated_cost: document.getElementById('eoEstimatedCost').value ? parseFloat(document.getElementById('eoEstimatedCost').value) : null, + reception_notes: document.getElementById('eoNotes').value + }; + try { + await api('PUT', '/' + currentOrderId, payload); + toast('Orden actualizada'); + closeEditOrderModal(); + closeDetailModal(); + loadSummary(); + loadOrders(); + } catch (e) { + alert('Error: ' + e.message); + } + } + // ─── Actions ─── function changeStatus() { @@ -930,6 +1106,13 @@ var Workshop = (function() { closeCatalogModal: closeCatalogModal, addCatalogItem: addCatalogItem, deleteCatalogItem: deleteCatalogItem, + openEditOrderModal: openEditOrderModal, + closeEditOrderModal: closeEditOrderModal, + saveOrderChanges: saveOrderChanges, + selectEditCustomer: selectEditCustomer, + selectEditVehicle: selectEditVehicle, + searchCustomersForSO: searchCustomersForSO, + searchVehiclesForSO: searchVehiclesForSO, }; })(); diff --git a/pos/templates/workshop.html b/pos/templates/workshop.html index 8f6318f..d430c0e 100644 --- a/pos/templates/workshop.html +++ b/pos/templates/workshop.html @@ -15,7 +15,7 @@ - +