diff --git a/pos/blueprints/service_order_bp.py b/pos/blueprints/service_order_bp.py
index 5ffb82d..224acf3 100644
--- a/pos/blueprints/service_order_bp.py
+++ b/pos/blueprints/service_order_bp.py
@@ -3,6 +3,8 @@
Prefix: /pos/api/service-orders
"""
+import json
+
from flask import Blueprint, g, jsonify, request
from middleware import require_auth
from services.service_order_engine import (
@@ -374,6 +376,40 @@ def assign_mechanic_endpoint(so_id):
conn.close()
+@service_order_bp.route('/customers', methods=['POST'])
+@require_auth('workshop.edit')
+def create_customer_for_workshop():
+ """Create a customer directly from the workshop flow."""
+ data = request.get_json() or {}
+ if not data.get('name'):
+ return jsonify({'error': 'El nombre es obligatorio'}), 400
+
+ conn = get_tenant_conn(g.tenant_id)
+ cur = conn.cursor()
+ try:
+ cur.execute("""
+ INSERT INTO customers
+ (branch_id, name, rfc, razon_social, regimen_fiscal, uso_cfdi,
+ cp, email, phone, address, price_tier, credit_limit, max_discount_pct, vehicle_info)
+ VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
+ RETURNING id
+ """, (
+ data.get('branch_id', g.branch_id), data['name'], data.get('rfc'),
+ data.get('razon_social'), data.get('regimen_fiscal'), data.get('uso_cfdi', 'G03'),
+ data.get('cp'), data.get('email'), data.get('phone'), data.get('address'),
+ data.get('price_tier', 1), data.get('credit_limit', 0), data.get('max_discount_pct', 0),
+ json.dumps(data['vehicle_info']) if data.get('vehicle_info') else None
+ ))
+ customer_id = cur.fetchone()[0]
+ conn.commit()
+ cur.close(); conn.close()
+ return jsonify({'id': customer_id, 'message': 'Cliente creado'}), 201
+ except Exception as e:
+ conn.rollback()
+ cur.close(); conn.close()
+ return jsonify({'error': str(e)}), 500
+
+
@service_order_bp.route('/vehicles', methods=['POST'])
@require_auth('workshop.edit')
def create_vehicle_for_workshop():
diff --git a/pos/static/js/workshop.js b/pos/static/js/workshop.js
index 61ed494..4162cd0 100644
--- a/pos/static/js/workshop.js
+++ b/pos/static/js/workshop.js
@@ -23,6 +23,7 @@ var Workshop = (function() {
var vehicleSearchTimer = null;
var selectedEditVehicle = null;
var newVehicleContext = null;
+ var newCustomerContext = null;
var currentView = 'list';
var currentPage = 1;
var perPage = 25;
@@ -768,6 +769,65 @@ var Workshop = (function() {
}
}
+ function openNewCustomerModal(context) {
+ newCustomerContext = context || 'new';
+ document.getElementById('newCustomerModal').classList.add('is-open');
+ document.getElementById('ncName').value = '';
+ document.getElementById('ncPhone').value = '';
+ document.getElementById('ncEmail').value = '';
+ document.getElementById('ncRfc').value = '';
+ document.getElementById('ncPriceTier').value = '1';
+ document.getElementById('ncAddress').value = '';
+ }
+
+ function openNewCustomerModalFromNewOrder() {
+ openNewCustomerModal('new');
+ }
+
+ function closeNewCustomerModal() {
+ document.getElementById('newCustomerModal').classList.remove('is-open');
+ }
+
+ async function saveNewCustomer() {
+ var name = document.getElementById('ncName').value.trim();
+ if (!name) {
+ alert('El nombre es obligatorio');
+ return;
+ }
+ var payload = {
+ name: name,
+ phone: document.getElementById('ncPhone').value.trim() || null,
+ email: document.getElementById('ncEmail').value.trim() || null,
+ rfc: document.getElementById('ncRfc').value.trim() || null,
+ price_tier: parseInt(document.getElementById('ncPriceTier').value, 10) || 1,
+ address: document.getElementById('ncAddress').value.trim() || null,
+ branch_id: (currentOrder ? currentOrder.branch_id : null) || (window.POS_USER ? window.POS_USER.branch_id : null)
+ };
+ try {
+ var res = await api('POST', '/customers', payload);
+ var label = name + (payload.phone ? ' (' + payload.phone + ')' : '');
+ if (newCustomerContext === 'new') {
+ var sel = document.getElementById('noCustomer');
+ var opt = document.createElement('option');
+ opt.value = res.id;
+ opt.textContent = label.trim();
+ sel.appendChild(opt);
+ sel.value = res.id;
+ if (typeof customers !== 'undefined') {
+ customers.push({id: res.id, name: name, phone: payload.phone, rfc: payload.rfc});
+ }
+ } else {
+ selectedEditCustomer = {id: res.id, name: name};
+ document.getElementById('eoCustomerSearch').value = name;
+ document.getElementById('eoCustomerId').value = res.id;
+ }
+ toast('Cliente creado');
+ closeNewCustomerModal();
+ } catch (e) {
+ alert('Error: ' + e.message);
+ }
+ }
+
// ─── Actions ───
function changeStatus() {
@@ -1181,6 +1241,10 @@ var Workshop = (function() {
openNewVehicleModalFromNewOrder: openNewVehicleModalFromNewOrder,
closeNewVehicleModal: closeNewVehicleModal,
saveNewVehicle: saveNewVehicle,
+ openNewCustomerModal: openNewCustomerModal,
+ openNewCustomerModalFromNewOrder: openNewCustomerModalFromNewOrder,
+ closeNewCustomerModal: closeNewCustomerModal,
+ saveNewCustomer: saveNewCustomer,
};
})();
diff --git a/pos/templates/workshop.html b/pos/templates/workshop.html
index b682f6a..b36c508 100644
--- a/pos/templates/workshop.html
+++ b/pos/templates/workshop.html
@@ -15,7 +15,7 @@
-
+