FASE 4-5-6: Infraestructura, CRM, Service Orders, Notificaciones, Ahorro, Logistica, API Publica
FASE 4: - Redis cache de stock con fallback graceful - Multi-moneda (MXN/USD) con contabilidad en MXN - Proveedores y ordenes de compra completo - Meilisearch 1.5M+ partes indexadas - Metabase KPIs con dashboard auto-generado FASE 5: - CRM mejorado: activities, tags, loyalty program, analytics - Imagenes de partes: upload, resize, thumbnails WebP - Ordenes de servicio Kanban: received->diagnosis->repair->ready->delivered - Garantias/RMA, alertas de reorden, multi-sucursal - Stubs BNPL (APLAZO) y ERP Sync (Aspel/Contpaqi) FASE 6: - Notificaciones automaticas: push/WhatsApp/email/in-app - Reportes de ahorro vs retail_price - Logistica + tracking: DHL, FedEx, Estafeta, 99min, Uber - API Publica: API keys, rate limiting, catalog search Migraciones: v1.9-v3.0 Tests: 93/93 pasando Backup: nexus_backup_20260427_045859.tar.gz
This commit is contained in:
208
pos/blueprints/warranty_bp.py
Normal file
208
pos/blueprints/warranty_bp.py
Normal file
@@ -0,0 +1,208 @@
|
||||
"""Warranty / RMA blueprint.
|
||||
|
||||
Endpoints (all under /pos/api):
|
||||
GET/POST /warranties
|
||||
GET /warranties/<id>
|
||||
GET /customers/<id>/warranties
|
||||
POST /warranty-claims
|
||||
GET /warranty-claims
|
||||
GET /warranty-claims/<id>
|
||||
PUT /warranty-claims/<id>/resolve
|
||||
PUT /warranty-claims/<id>/close
|
||||
"""
|
||||
|
||||
from flask import Blueprint, request, jsonify, g
|
||||
from middleware import require_auth
|
||||
from tenant_db import get_tenant_conn
|
||||
from services.warranty_engine import (
|
||||
register_warranty, create_claim, resolve_claim, close_claim,
|
||||
get_warranty, list_warranties, get_claim, list_claims, expire_warranties,
|
||||
)
|
||||
|
||||
warranty_bp = Blueprint('warranty', __name__, url_prefix='/pos/api')
|
||||
|
||||
|
||||
# ── WARRANTIES ─────────────────────────────────────────────────────────────
|
||||
|
||||
@warranty_bp.route('/warranties', methods=['GET'])
|
||||
@require_auth('inventory.view')
|
||||
def get_warranties():
|
||||
"""List warranties."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
status = request.args.get('status')
|
||||
customer_id = request.args.get('customer_id', type=int)
|
||||
limit = request.args.get('limit', 50, type=int)
|
||||
offset = request.args.get('offset', 0, type=int)
|
||||
data = list_warranties(conn, customer_id=customer_id, status=status, limit=limit, offset=offset)
|
||||
conn.close()
|
||||
return jsonify({'data': data})
|
||||
|
||||
|
||||
@warranty_bp.route('/warranties', methods=['POST'])
|
||||
@require_auth('pos.sell')
|
||||
def post_warranty():
|
||||
"""Register a warranty (usually called at sale time)."""
|
||||
data = request.get_json() or {}
|
||||
required = ['sale_id', 'sale_item_id', 'inventory_id', 'customer_id', 'warranty_months']
|
||||
missing = [f for f in required if f not in data]
|
||||
if missing:
|
||||
return jsonify({'error': f'Missing fields: {missing}'}), 400
|
||||
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
w_id = register_warranty(
|
||||
conn,
|
||||
sale_id=data['sale_id'],
|
||||
sale_item_id=data['sale_item_id'],
|
||||
inventory_id=data['inventory_id'],
|
||||
customer_id=data['customer_id'],
|
||||
warranty_months=int(data['warranty_months']),
|
||||
supplier_id=data.get('supplier_id'),
|
||||
part_number=data.get('part_number'),
|
||||
name=data.get('name'),
|
||||
notes=data.get('notes'),
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'id': w_id, 'message': 'Warranty registered'}), 201
|
||||
except ValueError as e:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 400
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@warranty_bp.route('/warranties/<int:warranty_id>', methods=['GET'])
|
||||
@require_auth('inventory.view')
|
||||
def get_warranty_detail(warranty_id):
|
||||
"""Get warranty by ID."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
w = get_warranty(conn, warranty_id)
|
||||
conn.close()
|
||||
if not w:
|
||||
return jsonify({'error': 'Warranty not found'}), 404
|
||||
return jsonify(w)
|
||||
|
||||
|
||||
@warranty_bp.route('/customers/<int:customer_id>/warranties', methods=['GET'])
|
||||
@require_auth('inventory.view')
|
||||
def get_customer_warranties(customer_id):
|
||||
"""List warranties for a customer."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
data = list_warranties(conn, customer_id=customer_id)
|
||||
conn.close()
|
||||
return jsonify({'data': data})
|
||||
|
||||
|
||||
# ── WARRANTY CLAIMS ────────────────────────────────────────────────────────
|
||||
|
||||
@warranty_bp.route('/warranty-claims', methods=['POST'])
|
||||
@require_auth('inventory.edit')
|
||||
def post_claim():
|
||||
"""File a warranty claim."""
|
||||
data = request.get_json() or {}
|
||||
if not data.get('warranty_id') or not data.get('reason'):
|
||||
return jsonify({'error': 'warranty_id and reason are required'}), 400
|
||||
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
claim_id = create_claim(
|
||||
conn,
|
||||
warranty_id=data['warranty_id'],
|
||||
reason=data['reason'],
|
||||
employee_id=g.employee_id,
|
||||
notes=data.get('notes')
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
return jsonify({'id': claim_id, 'message': 'Claim filed'}), 201
|
||||
except ValueError as e:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 400
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@warranty_bp.route('/warranty-claims', methods=['GET'])
|
||||
@require_auth('inventory.view')
|
||||
def get_claims():
|
||||
"""List warranty claims."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
status = request.args.get('status')
|
||||
warranty_id = request.args.get('warranty_id', type=int)
|
||||
limit = request.args.get('limit', 50, type=int)
|
||||
offset = request.args.get('offset', 0, type=int)
|
||||
data = list_claims(conn, status=status, warranty_id=warranty_id, limit=limit, offset=offset)
|
||||
conn.close()
|
||||
return jsonify({'data': data})
|
||||
|
||||
|
||||
@warranty_bp.route('/warranty-claims/<int:claim_id>', methods=['GET'])
|
||||
@require_auth('inventory.view')
|
||||
def get_claim_detail(claim_id):
|
||||
"""Get claim by ID."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
c = get_claim(conn, claim_id)
|
||||
conn.close()
|
||||
if not c:
|
||||
return jsonify({'error': 'Claim not found'}), 404
|
||||
return jsonify(c)
|
||||
|
||||
|
||||
@warranty_bp.route('/warranty-claims/<int:claim_id>/resolve', methods=['PUT'])
|
||||
@require_auth('inventory.edit')
|
||||
def put_resolve_claim(claim_id):
|
||||
"""Resolve a claim."""
|
||||
data = request.get_json() or {}
|
||||
resolution = data.get('resolution')
|
||||
if not resolution:
|
||||
return jsonify({'error': 'resolution is required'}), 400
|
||||
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
ok = resolve_claim(
|
||||
conn, claim_id, resolution,
|
||||
diagnosis=data.get('diagnosis'),
|
||||
replacement_inventory_id=data.get('replacement_inventory_id'),
|
||||
refund_amount=data.get('refund_amount'),
|
||||
labor_cost=data.get('labor_cost'),
|
||||
supplier_rma_number=data.get('supplier_rma_number'),
|
||||
notes=data.get('notes')
|
||||
)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
if not ok:
|
||||
return jsonify({'error': 'Claim not found or already closed'}), 400
|
||||
return jsonify({'message': 'Claim resolved'})
|
||||
except ValueError as e:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 400
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
|
||||
|
||||
@warranty_bp.route('/warranty-claims/<int:claim_id>/close', methods=['PUT'])
|
||||
@require_auth('inventory.edit')
|
||||
def put_close_claim(claim_id):
|
||||
"""Close a resolved claim."""
|
||||
conn = get_tenant_conn(g.tenant_id)
|
||||
try:
|
||||
ok = close_claim(conn, claim_id)
|
||||
conn.commit()
|
||||
conn.close()
|
||||
if not ok:
|
||||
return jsonify({'error': 'Claim not found or not resolved'}), 400
|
||||
return jsonify({'message': 'Claim closed'})
|
||||
except Exception as e:
|
||||
conn.rollback()
|
||||
conn.close()
|
||||
return jsonify({'error': str(e)}), 500
|
||||
Reference in New Issue
Block a user