fix(audit): corrige errores criticos y mayores, mejora UX/accesibilidad y optimiza rendimiento
- Arregla @require_auth, permisos, race conditions, locks de caja/stock - Elimina N+1 en layaway, flotilla, dashboard y global_invoice - Asegura folios atomicos para CFDI, ordenes de servicio y polizas - Protege client_secret de MercadoLibre en backend - Conecta botones/filtros de config, customers, accounting e invoicing - Mejora accesibilidad (labels/aria-label) y estados de carga/vacio - Limpia accounting.js obsoleto y consolida accounting.v9.js - Actualiza cache busting a v32 y Service Worker a v32 - Documenta todo en docs/AUDIT_Y_MEJORAS_2026-06-15.md Tests: 35 passed
This commit is contained in:
@@ -33,12 +33,15 @@ service_order_bp = Blueprint('service_orders', __name__, url_prefix='/pos/api/se
|
||||
|
||||
|
||||
@service_order_bp.route('', methods=['GET'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.view')
|
||||
def list_orders():
|
||||
status = request.args.get('status')
|
||||
priority = request.args.get('priority')
|
||||
customer_id = request.args.get('customer_id', type=int)
|
||||
employee_id = request.args.get('employee_id', type=int)
|
||||
delivery_method = request.args.get('delivery_method')
|
||||
is_direct = request.args.get('is_direct', type=lambda v: v.lower() == 'true') if 'is_direct' in request.args else None
|
||||
q = request.args.get('q')
|
||||
page = int(request.args.get('page', 1))
|
||||
per_page = min(int(request.args.get('per_page', 50)), 200)
|
||||
|
||||
@@ -47,15 +50,53 @@ def list_orders():
|
||||
result = list_service_orders(
|
||||
conn, status=status, branch_id=g.branch_id,
|
||||
customer_id=customer_id, priority=priority,
|
||||
employee_id=employee_id, page=page, per_page=per_page
|
||||
employee_id=employee_id, delivery_method=delivery_method,
|
||||
is_direct=is_direct, q=q, page=page, per_page=per_page
|
||||
)
|
||||
return jsonify(result)
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@service_order_bp.route('/from-pos', methods=['POST'])
|
||||
@require_auth('pos.sell')
|
||||
def create_order_from_pos():
|
||||
"""Create a service order directly from the POS cart (quotation)."""
|
||||
data = request.get_json() or {}
|
||||
items = data.get('items', [])
|
||||
estimated_cost = sum(
|
||||
(it.get('unit_price') or 0) * (it.get('quantity') or 1)
|
||||
for it in items
|
||||
)
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
result = create_service_order(conn, {
|
||||
'tenant_id': g.tenant_id,
|
||||
'branch_id': data.get('branch_id', g.branch_id),
|
||||
'customer_id': data.get('customer_id'),
|
||||
'vehicle_id': data.get('vehicle_id'),
|
||||
'priority': data.get('priority', 'normal'),
|
||||
'reception_notes': data.get('reception_notes'),
|
||||
'estimated_cost': estimated_cost or None,
|
||||
'estimated_completion': data.get('estimated_completion'),
|
||||
'employee_id': data.get('employee_id'),
|
||||
'mileage_in': data.get('mileage_in'),
|
||||
'created_by': getattr(g, 'employee_id', None),
|
||||
'delivery_method': data.get('delivery_method'),
|
||||
'courier_id': data.get('courier_id'),
|
||||
'is_direct': data.get('is_direct', False),
|
||||
})
|
||||
so_id = result['service_order_id']
|
||||
for item in items:
|
||||
add_item(conn, so_id, item)
|
||||
order = get_service_order(conn, so_id)
|
||||
return jsonify(order), 201
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@service_order_bp.route('', methods=['POST'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def create_order():
|
||||
data = request.get_json() or {}
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
@@ -73,6 +114,9 @@ def create_order():
|
||||
'mileage_in': data.get('mileage_in'),
|
||||
'fuel_level': data.get('fuel_level'),
|
||||
'created_by': getattr(g, 'employee_id', None),
|
||||
'delivery_method': data.get('delivery_method'),
|
||||
'courier_id': data.get('courier_id'),
|
||||
'is_direct': data.get('is_direct', False),
|
||||
})
|
||||
return jsonify(result), 201
|
||||
finally:
|
||||
@@ -80,7 +124,7 @@ def create_order():
|
||||
|
||||
|
||||
@service_order_bp.route('/<int:so_id>', methods=['GET'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.view')
|
||||
def get_order(so_id):
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
@@ -93,7 +137,7 @@ def get_order(so_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/<int:so_id>', methods=['PUT'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def update_order(so_id):
|
||||
data = request.get_json() or {}
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
@@ -107,7 +151,7 @@ def update_order(so_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/<int:so_id>/status', methods=['PUT'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def change_status(so_id):
|
||||
data = request.get_json() or {}
|
||||
new_status = data.get('status')
|
||||
@@ -130,7 +174,7 @@ def change_status(so_id):
|
||||
# ─── Items (Parts) ─────────────────────────────
|
||||
|
||||
@service_order_bp.route('/<int:so_id>/items', methods=['POST'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def add_order_item(so_id):
|
||||
data = request.get_json() or {}
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
@@ -142,7 +186,7 @@ def add_order_item(so_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/items/<int:item_id>', methods=['PUT'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def update_order_item(item_id):
|
||||
data = request.get_json() or {}
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
@@ -156,7 +200,7 @@ def update_order_item(item_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/items/<int:item_id>', methods=['DELETE'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def delete_order_item(item_id):
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
@@ -169,7 +213,7 @@ def delete_order_item(item_id):
|
||||
# ─── Labor ─────────────────────────────
|
||||
|
||||
@service_order_bp.route('/<int:so_id>/labor', methods=['POST'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def add_order_labor(so_id):
|
||||
data = request.get_json() or {}
|
||||
if not data.get('description'):
|
||||
@@ -183,7 +227,7 @@ def add_order_labor(so_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/labor/<int:labor_id>', methods=['PUT'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def update_order_labor(labor_id):
|
||||
data = request.get_json() or {}
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
@@ -197,7 +241,7 @@ def update_order_labor(labor_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/labor/<int:labor_id>', methods=['DELETE'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def delete_order_labor(labor_id):
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
@@ -210,7 +254,7 @@ def delete_order_labor(labor_id):
|
||||
# ─── Kanban Summary ─────────────────────────────
|
||||
|
||||
@service_order_bp.route('/kanban/summary', methods=['GET'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.view')
|
||||
def kanban_summary():
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
@@ -224,7 +268,7 @@ def kanban_summary():
|
||||
|
||||
|
||||
@service_order_bp.route('/<int:so_id>/items/<int:item_id>/reserve', methods=['POST'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def reserve_order_item(so_id, item_id):
|
||||
"""Reserve inventory for a service order item."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
@@ -238,7 +282,7 @@ def reserve_order_item(so_id, item_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/<int:so_id>/items/<int:item_id>/release', methods=['POST'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def release_order_item(so_id, item_id):
|
||||
"""Release a previous inventory reservation."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
@@ -294,7 +338,7 @@ def convert_order_to_sale(so_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/<int:so_id>/assign-mechanic', methods=['PUT'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def assign_mechanic_endpoint(so_id):
|
||||
"""Assign a mechanic/technician to a service order."""
|
||||
data = request.get_json() or {}
|
||||
@@ -316,7 +360,7 @@ def assign_mechanic_endpoint(so_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/service-catalog', methods=['GET'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.view')
|
||||
def list_catalog():
|
||||
"""List reusable labor/service concepts."""
|
||||
active_only = request.args.get('active_only', 'true').lower() != 'false'
|
||||
@@ -329,7 +373,7 @@ def list_catalog():
|
||||
|
||||
|
||||
@service_order_bp.route('/service-catalog', methods=['POST'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def create_catalog_item():
|
||||
"""Create a reusable labor concept."""
|
||||
data = request.get_json() or {}
|
||||
@@ -345,7 +389,7 @@ def create_catalog_item():
|
||||
|
||||
|
||||
@service_order_bp.route('/service-catalog/<int:item_id>', methods=['PUT'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def update_catalog_item(item_id):
|
||||
"""Update a reusable labor concept."""
|
||||
data = request.get_json() or {}
|
||||
@@ -360,7 +404,7 @@ def update_catalog_item(item_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/service-catalog/<int:item_id>', methods=['DELETE'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.edit')
|
||||
def delete_catalog_item(item_id):
|
||||
"""Soft-delete a reusable labor concept."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
@@ -375,7 +419,7 @@ def delete_catalog_item(item_id):
|
||||
|
||||
|
||||
@service_order_bp.route('/<int:so_id>/print', methods=['POST'])
|
||||
@require_auth()
|
||||
@require_auth('workshop.view')
|
||||
def print_service_order_ticket(so_id):
|
||||
"""Generate a printable ticket for a service order.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user