feat(workshop): make brand/model required, plate optional, remove VIN
Some checks failed
CI / lint-and-test (3.11) (push) Has been cancelled
CI / lint-and-test (3.13) (push) Has been cancelled

- create_vehicle endpoints now require make and model; plate is optional; VIN is no longer stored.

- Updated new-vehicle modal: removed VIN field, plate is optional, brand/model marked required.
This commit is contained in:
2026-06-30 20:08:56 +00:00
parent 73553a3771
commit 7dc34f68ab
4 changed files with 27 additions and 30 deletions

View File

@@ -269,11 +269,12 @@ def list_all_history():
def create_vehicle(): def create_vehicle():
"""Create a fleet vehicle. """Create a fleet vehicle.
Body: {customer_id, plate, vin, make, model, year, current_mileage, fuel_type, color, owner_name, notes} Body: {customer_id, plate, make, model, year, current_mileage, fuel_type, color, owner_name, notes}
Brand and model are required; plate is optional; VIN is not used.
""" """
data = request.get_json() or {} data = request.get_json() or {}
if not data.get('plate') and not data.get('vin'): if not data.get('make') or not data.get('model'):
return jsonify({'error': 'plate or vin is required'}), 400 return jsonify({'error': 'Marca y modelo son obligatorios'}), 400
if not data.get('customer_id'): if not data.get('customer_id'):
return jsonify({'error': 'El vehiculo debe estar asignado a un cliente'}), 400 return jsonify({'error': 'El vehiculo debe estar asignado a un cliente'}), 400
@@ -291,12 +292,12 @@ def create_vehicle():
cur.execute(""" cur.execute("""
INSERT INTO fleet_vehicles INSERT INTO fleet_vehicles
(branch_id, customer_id, plate, vin, make, model, year, (branch_id, customer_id, plate, make, model, year,
current_mileage, fuel_type, color, owner_name, notes) current_mileage, fuel_type, color, owner_name, notes)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
RETURNING id RETURNING id
""", ( """, (
branch_id, data['customer_id'], data.get('plate'), data.get('vin'), branch_id, data['customer_id'], data.get('plate'),
data.get('make'), data.get('model'), data.get('year'), data.get('make'), data.get('model'), data.get('year'),
data.get('current_mileage', 0), data.get('fuel_type', 'gasolina'), data.get('current_mileage', 0), data.get('fuel_type', 'gasolina'),
data.get('color'), data.get('owner_name') or cust[0], data.get('notes'), data.get('color'), data.get('owner_name') or cust[0], data.get('notes'),

View File

@@ -415,11 +415,12 @@ def create_customer_for_workshop():
def create_vehicle_for_workshop(): def create_vehicle_for_workshop():
"""Create a fleet vehicle directly from the workshop flow. """Create a fleet vehicle directly from the workshop flow.
Vehicles must be assigned to a customer. Vehicles must be assigned to a customer. Brand and model are required;
plate is optional and VIN is not used.
""" """
data = request.get_json() or {} data = request.get_json() or {}
if not data.get('plate') and not data.get('vin'): if not data.get('make') or not data.get('model'):
return jsonify({'error': 'Se requiere placa o VIN'}), 400 return jsonify({'error': 'Marca y modelo son obligatorios'}), 400
if not data.get('customer_id'): if not data.get('customer_id'):
return jsonify({'error': 'El vehiculo debe estar asignado a un cliente'}), 400 return jsonify({'error': 'El vehiculo debe estar asignado a un cliente'}), 400
@@ -434,12 +435,12 @@ def create_vehicle_for_workshop():
cur.execute(""" cur.execute("""
INSERT INTO fleet_vehicles INSERT INTO fleet_vehicles
(branch_id, customer_id, plate, vin, make, model, year, (branch_id, customer_id, plate, make, model, year,
current_mileage, fuel_type, color, owner_name, notes) current_mileage, fuel_type, color, owner_name, notes)
VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
RETURNING id RETURNING id
""", ( """, (
data.get('branch_id', g.branch_id), data['customer_id'], data.get('plate'), data.get('vin'), data.get('branch_id', g.branch_id), data['customer_id'], data.get('plate'),
data.get('make'), data.get('model'), data.get('year'), data.get('make'), data.get('model'), data.get('year'),
data.get('current_mileage', 0), data.get('fuel_type', 'gasolina'), data.get('current_mileage', 0), data.get('fuel_type', 'gasolina'),
data.get('color'), cust[0], data.get('notes'), data.get('color'), cust[0], data.get('notes'),

View File

@@ -726,7 +726,6 @@ var Workshop = (function() {
document.getElementById('nvCustomerName').value = customerName; document.getElementById('nvCustomerName').value = customerName;
document.getElementById('newVehicleModal').classList.add('is-open'); document.getElementById('newVehicleModal').classList.add('is-open');
document.getElementById('nvPlate').value = ''; document.getElementById('nvPlate').value = '';
document.getElementById('nvVIN').value = '';
document.getElementById('nvMake').value = ''; document.getElementById('nvMake').value = '';
document.getElementById('nvModel').value = ''; document.getElementById('nvModel').value = '';
document.getElementById('nvYear').value = ''; document.getElementById('nvYear').value = '';
@@ -743,10 +742,11 @@ var Workshop = (function() {
async function saveNewVehicle() { async function saveNewVehicle() {
var plate = document.getElementById('nvPlate').value.trim(); var plate = document.getElementById('nvPlate').value.trim();
var vin = document.getElementById('nvVIN').value.trim(); var make = document.getElementById('nvMake').value.trim();
var model = document.getElementById('nvModel').value.trim();
var customerId = parseInt(document.getElementById('nvCustomerId').value, 10) || null; var customerId = parseInt(document.getElementById('nvCustomerId').value, 10) || null;
if (!plate && !vin) { if (!make || !model) {
alert('Se requiere al menos placa o VIN'); alert('Marca y modelo son obligatorios');
return; return;
} }
if (!customerId) { if (!customerId) {
@@ -755,17 +755,16 @@ var Workshop = (function() {
} }
var payload = { var payload = {
plate: plate, plate: plate,
vin: vin,
customer_id: customerId, customer_id: customerId,
make: document.getElementById('nvMake').value.trim() || null, make: make,
model: document.getElementById('nvModel').value.trim() || null, model: model,
year: document.getElementById('nvYear').value ? parseInt(document.getElementById('nvYear').value, 10) : null, year: document.getElementById('nvYear').value ? parseInt(document.getElementById('nvYear').value, 10) : null,
color: document.getElementById('nvColor').value.trim() || null, color: document.getElementById('nvColor').value.trim() || null,
branch_id: (currentOrder ? currentOrder.branch_id : null) || (window.POS_USER ? window.POS_USER.branch_id : null) branch_id: (currentOrder ? currentOrder.branch_id : null) || (window.POS_USER ? window.POS_USER.branch_id : null)
}; };
try { try {
var res = await api('POST', '/vehicles', payload); var res = await api('POST', '/vehicles', payload);
var label = plate + (payload.make ? ' · ' + payload.make : '') + (payload.model ? ' ' + payload.model : ''); var label = [payload.make, payload.model, payload.year, plate].filter(Boolean).join(' · ');
if (newVehicleContext === 'new') { if (newVehicleContext === 'new') {
var sel = document.getElementById('noVehicle'); var sel = document.getElementById('noVehicle');
var opt = document.createElement('option'); var opt = document.createElement('option');

View File

@@ -15,7 +15,7 @@
<meta name="theme-color" content="#F5A623" /> <meta name="theme-color" content="#F5A623" />
<link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" /> <link rel="shortcut icon" type="image/png" href="/pos/static/pwa/icon-192.png" />
<link rel="stylesheet" href="/pos/static/css/workshop.css?v=41"> <link rel="stylesheet" href="/pos/static/css/workshop.css?v=42">
<style> <style>
.so-notes-grid { display: grid; grid-template-columns: 120px 1fr; gap: var(--space-2); align-items: start; } .so-notes-grid { display: grid; grid-template-columns: 120px 1fr; gap: var(--space-2); align-items: start; }
.so-notes-grid .form-label { margin: 0; padding-top: var(--space-2); } .so-notes-grid .form-label { margin: 0; padding-top: var(--space-2); }
@@ -348,19 +348,15 @@
<div class="modal__body"> <div class="modal__body">
<form class="form-grid" id="newVehicleForm" onsubmit="return false;"> <form class="form-grid" id="newVehicleForm" onsubmit="return false;">
<div class="form-field"> <div class="form-field">
<label class="form-label" for="nvPlate">Placa *</label> <label class="form-label" for="nvPlate">Placa</label>
<input class="form-input" id="nvPlate" placeholder="Ej. ABC-123" /> <input class="form-input" id="nvPlate" placeholder="Ej. ABC-123 (opcional)" />
</div> </div>
<div class="form-field"> <div class="form-field">
<label class="form-label" for="nvVIN">VIN</label> <label class="form-label" for="nvMake">Marca *</label>
<input class="form-input" id="nvVIN" placeholder="N&uacute;mero de serie" />
</div>
<div class="form-field">
<label class="form-label" for="nvMake">Marca</label>
<input class="form-input" id="nvMake" placeholder="Ej. Nissan" /> <input class="form-input" id="nvMake" placeholder="Ej. Nissan" />
</div> </div>
<div class="form-field"> <div class="form-field">
<label class="form-label" for="nvModel">Modelo</label> <label class="form-label" for="nvModel">Modelo *</label>
<input class="form-input" id="nvModel" placeholder="Ej. Versa" /> <input class="form-input" id="nvModel" placeholder="Ej. Versa" />
</div> </div>
<div class="form-field"> <div class="form-field">
@@ -487,7 +483,7 @@
<script src="/pos/static/js/pos-utils.js?v=33" defer></script> <script src="/pos/static/js/pos-utils.js?v=33" defer></script>
<script src="/pos/static/js/sidebar.js?v=36" defer></script> <script src="/pos/static/js/sidebar.js?v=36" defer></script>
<script src="/pos/static/js/offline-banner.js" defer></script> <script src="/pos/static/js/offline-banner.js" defer></script>
<script src="/pos/static/js/workshop.js?v=41" defer></script> <script src="/pos/static/js/workshop.js?v=42" defer></script>
<script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script> <script>if('serviceWorker' in navigator){navigator.serviceWorker.register('/pos/sw.js',{scope:'/pos/'});}</script>
<script src="/pos/static/js/pwa-install.js" defer></script> <script src="/pos/static/js/pwa-install.js" defer></script>
<script src="/pos/static/js/chat.js" defer></script> <script src="/pos/static/js/chat.js" defer></script>