diff --git a/pos/blueprints/fleet_bp.py b/pos/blueprints/fleet_bp.py
index 116f412..faad711 100644
--- a/pos/blueprints/fleet_bp.py
+++ b/pos/blueprints/fleet_bp.py
@@ -269,11 +269,13 @@ def list_all_history():
def create_vehicle():
"""Create a fleet vehicle.
- Body: {plate, vin, make, model, year, current_mileage, fuel_type, color, owner_name, notes}
+ Body: {customer_id, plate, vin, make, model, year, current_mileage, fuel_type, color, owner_name, notes}
"""
data = request.get_json() or {}
if not data.get('plate') and not data.get('vin'):
return jsonify({'error': 'plate or vin is required'}), 400
+ if not data.get('customer_id'):
+ return jsonify({'error': 'El vehiculo debe estar asignado a un cliente'}), 400
branch_id = data.get('branch_id', g.branch_id)
@@ -281,17 +283,23 @@ def create_vehicle():
cur = conn.cursor()
try:
+ cur.execute("SELECT name FROM customers WHERE id = %s", (data['customer_id'],))
+ cust = cur.fetchone()
+ if not cust:
+ cur.close(); conn.close()
+ return jsonify({'error': 'Cliente no encontrado'}), 404
+
cur.execute("""
INSERT INTO fleet_vehicles
- (branch_id, plate, vin, make, model, year,
+ (branch_id, customer_id, plate, vin, make, model, year,
current_mileage, fuel_type, color, owner_name, notes)
- VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
RETURNING id
""", (
- branch_id, data.get('plate'), data.get('vin'),
+ branch_id, data['customer_id'], data.get('plate'), data.get('vin'),
data.get('make'), data.get('model'), data.get('year'),
data.get('current_mileage', 0), data.get('fuel_type', 'gasolina'),
- data.get('color'), data.get('owner_name'), data.get('notes'),
+ data.get('color'), data.get('owner_name') or cust[0], data.get('notes'),
))
vehicle_id = cur.fetchone()[0]
diff --git a/pos/blueprints/service_order_bp.py b/pos/blueprints/service_order_bp.py
index 224acf3..ff468ee 100644
--- a/pos/blueprints/service_order_bp.py
+++ b/pos/blueprints/service_order_bp.py
@@ -413,25 +413,36 @@ def create_customer_for_workshop():
@service_order_bp.route('/vehicles', methods=['POST'])
@require_auth('workshop.edit')
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.
+ """
data = request.get_json() or {}
if not data.get('plate') and not data.get('vin'):
return jsonify({'error': 'Se requiere placa o VIN'}), 400
+ if not data.get('customer_id'):
+ return jsonify({'error': 'El vehiculo debe estar asignado a un cliente'}), 400
conn = get_tenant_conn(g.tenant_id)
cur = conn.cursor()
try:
+ cur.execute("SELECT name FROM customers WHERE id = %s", (data['customer_id'],))
+ cust = cur.fetchone()
+ if not cust:
+ cur.close(); conn.close()
+ return jsonify({'error': 'Cliente no encontrado'}), 404
+
cur.execute("""
INSERT INTO fleet_vehicles
- (branch_id, plate, vin, make, model, year,
+ (branch_id, customer_id, plate, vin, make, model, year,
current_mileage, fuel_type, color, owner_name, notes)
- VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
RETURNING id
""", (
- data.get('branch_id', g.branch_id), data.get('plate'), data.get('vin'),
+ data.get('branch_id', g.branch_id), data['customer_id'], data.get('plate'), data.get('vin'),
data.get('make'), data.get('model'), data.get('year'),
data.get('current_mileage', 0), data.get('fuel_type', 'gasolina'),
- data.get('color'), data.get('owner_name'), data.get('notes'),
+ data.get('color'), cust[0], data.get('notes'),
))
vehicle_id = cur.fetchone()[0]
conn.commit()
diff --git a/pos/migrations/v4.13_fleet_vehicle_customer.sql b/pos/migrations/v4.13_fleet_vehicle_customer.sql
new file mode 100644
index 0000000..8a57b76
--- /dev/null
+++ b/pos/migrations/v4.13_fleet_vehicle_customer.sql
@@ -0,0 +1,20 @@
+-- v4.13_fleet_vehicle_customer.sql
+-- Link fleet vehicles to a customer.
+
+ALTER TABLE fleet_vehicles
+ ADD COLUMN IF NOT EXISTS customer_id INTEGER;
+
+DO $$
+BEGIN
+ IF NOT EXISTS (
+ SELECT 1 FROM pg_constraint
+ WHERE conname = 'fleet_vehicles_customer_id_fkey'
+ ) THEN
+ ALTER TABLE fleet_vehicles
+ ADD CONSTRAINT fleet_vehicles_customer_id_fkey
+ FOREIGN KEY (customer_id) REFERENCES customers(id);
+ END IF;
+END $$;
+
+CREATE INDEX IF NOT EXISTS idx_fleet_vehicles_customer_id
+ ON fleet_vehicles (customer_id);
diff --git a/pos/static/js/workshop.js b/pos/static/js/workshop.js
index 4162cd0..856b747 100644
--- a/pos/static/js/workshop.js
+++ b/pos/static/js/workshop.js
@@ -708,6 +708,22 @@ var Workshop = (function() {
function openNewVehicleModal(context) {
newVehicleContext = context || 'edit';
+ var customerId = null;
+ var customerName = '';
+ if (newVehicleContext === 'new') {
+ var sel = document.getElementById('noCustomer');
+ customerId = sel ? parseInt(sel.value, 10) || null : null;
+ customerName = sel && sel.selectedIndex >= 0 ? sel.options[sel.selectedIndex].text : '';
+ } else {
+ customerId = currentOrder ? currentOrder.customer_id : null;
+ customerName = currentOrder ? (currentOrder.customer_name || '') : '';
+ }
+ if (!customerId) {
+ alert('Primero selecciona un cliente para poder crear el vehículo.');
+ return;
+ }
+ document.getElementById('nvCustomerId').value = customerId;
+ document.getElementById('nvCustomerName').value = customerName;
document.getElementById('newVehicleModal').classList.add('is-open');
document.getElementById('nvPlate').value = '';
document.getElementById('nvVIN').value = '';
@@ -715,7 +731,6 @@ var Workshop = (function() {
document.getElementById('nvModel').value = '';
document.getElementById('nvYear').value = '';
document.getElementById('nvColor').value = '';
- document.getElementById('nvOwner').value = '';
}
function openNewVehicleModalFromNewOrder() {
@@ -729,18 +744,23 @@ var Workshop = (function() {
async function saveNewVehicle() {
var plate = document.getElementById('nvPlate').value.trim();
var vin = document.getElementById('nvVIN').value.trim();
+ var customerId = parseInt(document.getElementById('nvCustomerId').value, 10) || null;
if (!plate && !vin) {
alert('Se requiere al menos placa o VIN');
return;
}
+ if (!customerId) {
+ alert('El vehículo debe estar asignado a un cliente');
+ return;
+ }
var payload = {
plate: plate,
vin: vin,
+ customer_id: customerId,
make: document.getElementById('nvMake').value.trim() || null,
model: document.getElementById('nvModel').value.trim() || null,
year: document.getElementById('nvYear').value ? parseInt(document.getElementById('nvYear').value, 10) : null,
color: document.getElementById('nvColor').value.trim() || null,
- owner_name: document.getElementById('nvOwner').value.trim() || null,
branch_id: (currentOrder ? currentOrder.branch_id : null) || (window.POS_USER ? window.POS_USER.branch_id : null)
};
try {
diff --git a/pos/templates/workshop.html b/pos/templates/workshop.html
index 03fc1f5..4086004 100644
--- a/pos/templates/workshop.html
+++ b/pos/templates/workshop.html
@@ -15,7 +15,7 @@
-
+