Opción C: Vinculación híbrida de inventario local con vehículos
- Nueva tabla inventory_vehicle_compat (v3.1) - Motor inventory_vehicle_compat.py: auto-match + gestión manual - catalog_service.get_parts_local() ahora incluye piezas locales vinculadas - inventory_bp: auto-match en create/update + endpoints REST /vehicles - Frontend catalog.js: badge 'Stock Local' para piezas nativas del tenant - Frontend inventory.js: panel de vehículos compatibles con auto-match - Tests: test_compatibility.py (9/9 pasan) Piezas locales aparecen en navegación por vehículo aunque no estén en TecDoc. Auto-match busca part_number en parts/aftermarket_parts y copia MYEs compatibles.
This commit is contained in:
@@ -15,6 +15,11 @@ from services.inventory_engine import (
|
||||
)
|
||||
from services.barcode_generator import generate_barcode
|
||||
from services.audit import log_action
|
||||
from tenant_db import get_master_conn
|
||||
from services.inventory_vehicle_compat import (
|
||||
auto_match_vehicle_compatibility, add_compatibility, remove_compatibility,
|
||||
remove_all_compatibility, get_compatibility, search_mye,
|
||||
)
|
||||
|
||||
inventory_bp = Blueprint('inventory', __name__, url_prefix='/pos/api/inventory')
|
||||
|
||||
@@ -276,7 +281,18 @@ def create_item():
|
||||
new_value={'part_number': data['part_number'], 'name': data['name'], 'initial_stock': initial_stock})
|
||||
|
||||
conn.commit()
|
||||
cur.close(); conn.close()
|
||||
cur.close()
|
||||
|
||||
# Auto-match vehicle compatibility via TecDoc
|
||||
try:
|
||||
master = get_master_conn()
|
||||
auto_match_vehicle_compatibility(master, conn, item_id, data['part_number'],
|
||||
brand=data.get('brand'), name=data.get('name'))
|
||||
master.close()
|
||||
except Exception as am_err:
|
||||
print(f"[auto_match] Error for item {item_id}: {am_err}")
|
||||
|
||||
conn.close()
|
||||
return jsonify({'id': item_id, 'barcode': barcode, 'message': 'Item created'}), 201
|
||||
|
||||
except Exception as e:
|
||||
@@ -338,7 +354,21 @@ def update_item(item_id):
|
||||
new_value={k: data[k] for k in changing_prices})
|
||||
|
||||
conn.commit()
|
||||
cur.close(); conn.close()
|
||||
cur.close()
|
||||
|
||||
# Re-run auto-match if part_number changed
|
||||
if 'part_number' in data and data['part_number'] != old_dict.get('part_number'):
|
||||
try:
|
||||
master = get_master_conn()
|
||||
# Clear old compatibilities and re-match
|
||||
remove_all_compatibility(conn, item_id)
|
||||
auto_match_vehicle_compatibility(master, conn, item_id, data['part_number'],
|
||||
brand=data.get('brand'), name=data.get('name'))
|
||||
master.close()
|
||||
except Exception as am_err:
|
||||
print(f"[auto_match] Re-match error for item {item_id}: {am_err}")
|
||||
|
||||
conn.close()
|
||||
return jsonify({'message': 'Item updated'})
|
||||
|
||||
|
||||
@@ -1244,3 +1274,88 @@ def api_reorder_suggest_po():
|
||||
suggestion = suggest_po_from_alerts(conn, supplier_id=supplier_id, branch_id=branch_id)
|
||||
conn.close()
|
||||
return jsonify(suggestion)
|
||||
|
||||
|
||||
# ─── Vehicle Compatibility ───────────────────────────
|
||||
|
||||
@inventory_bp.route('/items/<int:item_id>/vehicles', methods=['GET'])
|
||||
@require_auth('inventory.view')
|
||||
def get_item_vehicles(item_id):
|
||||
"""Get all vehicle compatibilities for an inventory item."""
|
||||
tenant = get_tenant_conn(g.tenant_id)
|
||||
master = get_master_conn()
|
||||
try:
|
||||
vehicles = get_compatibility(tenant, master, item_id)
|
||||
return jsonify({'vehicles': vehicles})
|
||||
finally:
|
||||
tenant.close()
|
||||
master.close()
|
||||
|
||||
|
||||
@inventory_bp.route('/items/<int:item_id>/vehicles', methods=['POST'])
|
||||
@require_auth('inventory.edit')
|
||||
def add_item_vehicle(item_id):
|
||||
"""Manually add a vehicle compatibility."""
|
||||
data = request.get_json() or {}
|
||||
mye_id = data.get('model_year_engine_id')
|
||||
if not mye_id:
|
||||
return jsonify({'error': 'model_year_engine_id required'}), 400
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
cid = add_compatibility(conn, item_id, mye_id, source='manual')
|
||||
return jsonify({'id': cid, 'message': 'Compatibility added'}), 201
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@inventory_bp.route('/items/<int:item_id>/vehicles/<int:mye_id>', methods=['DELETE'])
|
||||
@require_auth('inventory.edit')
|
||||
def delete_item_vehicle(item_id, mye_id):
|
||||
"""Remove a vehicle compatibility."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
deleted = remove_compatibility(conn, item_id, mye_id)
|
||||
return jsonify({'message': 'Compatibility removed', 'deleted': deleted})
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
@inventory_bp.route('/items/<int:item_id>/vehicles/auto-match', methods=['POST'])
|
||||
@require_auth('inventory.edit')
|
||||
def auto_match_item_vehicles(item_id):
|
||||
"""Run auto-match for an existing inventory item."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT part_number, brand, name FROM inventory WHERE id = %s", (item_id,))
|
||||
row = cur.fetchone()
|
||||
cur.close()
|
||||
if not row:
|
||||
conn.close()
|
||||
return jsonify({'error': 'Item not found'}), 404
|
||||
|
||||
part_number, brand, name = row
|
||||
master = get_master_conn()
|
||||
try:
|
||||
result = auto_match_vehicle_compatibility(master, conn, item_id, part_number,
|
||||
brand=brand, name=name)
|
||||
return jsonify(result)
|
||||
finally:
|
||||
master.close()
|
||||
conn.close()
|
||||
|
||||
|
||||
@inventory_bp.route('/mye/search', methods=['GET'])
|
||||
@require_auth()
|
||||
def search_mye_endpoint():
|
||||
"""Search model_year_engine records for manual compatibility assignment."""
|
||||
brand_id = request.args.get('brand_id', type=int)
|
||||
model_id = request.args.get('model_id', type=int)
|
||||
year_id = request.args.get('year_id', type=int)
|
||||
engine_id = request.args.get('engine_id', type=int)
|
||||
master = get_master_conn()
|
||||
try:
|
||||
results = search_mye(master, brand_id=brand_id, model_id=model_id,
|
||||
year_id=year_id, engine_id=engine_id)
|
||||
return jsonify({'data': results})
|
||||
finally:
|
||||
master.close()
|
||||
|
||||
Reference in New Issue
Block a user