Files
Autoparts-DB/pos/blueprints/savings_bp.py
Nexus Dev 9ff3dc4c8b 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
2026-04-27 05:23:30 +00:00

109 lines
3.4 KiB
Python

"""Savings Blueprint: retail price management and savings reports.
Prefix: /pos/api/savings
"""
from flask import Blueprint, request, jsonify, g
from middleware import require_auth
from tenant_db import get_tenant_conn
from services.savings_engine import (
get_customer_savings_report, get_global_savings_stats, calculate_item_savings,
)
savings_bp = Blueprint('savings', __name__, url_prefix='/pos/api/savings')
@savings_bp.route('/inventory/<int:item_id>/retail-price', methods=['PUT'])
@require_auth()
def set_retail_price(item_id):
data = request.get_json() or {}
retail_price = data.get('retail_price')
if retail_price is None:
return jsonify({'error': 'retail_price is required'}), 400
conn = get_tenant_conn(g.tenant_id)
try:
cur = conn.cursor()
cur.execute("""
UPDATE inventory SET retail_price = %s WHERE id = %s
""", (retail_price, item_id))
conn.commit()
cur.close()
return jsonify({'message': 'Retail price updated'})
finally:
conn.close()
@savings_bp.route('/inventory/bulk-retail-price', methods=['POST'])
@require_auth()
def bulk_set_retail_price():
"""Bulk update retail prices from CSV/JSON.
Body: {"items": [{"item_id": 1, "retail_price": 100.00}, ...]}
"""
data = request.get_json() or {}
items = data.get('items', [])
if not items:
return jsonify({'error': 'items array is required'}), 400
conn = get_tenant_conn(g.tenant_id)
try:
cur = conn.cursor()
updated = 0
for item in items:
item_id = item.get('item_id')
retail_price = item.get('retail_price')
if item_id and retail_price is not None:
cur.execute("""
UPDATE inventory SET retail_price = %s WHERE id = %s
""", (retail_price, item_id))
updated += cur.rowcount
conn.commit()
cur.close()
return jsonify({'updated': updated, 'message': f'{updated} prices updated'})
finally:
conn.close()
@savings_bp.route('/customers/<int:customer_id>', methods=['GET'])
@require_auth()
def customer_savings(customer_id):
months = int(request.args.get('months', 12))
conn = get_tenant_conn(g.tenant_id)
try:
report = get_customer_savings_report(conn, customer_id, months=months)
return jsonify(report)
finally:
conn.close()
@savings_bp.route('/stats', methods=['GET'])
@require_auth()
def global_savings():
from_date = request.args.get('from_date')
to_date = request.args.get('to_date')
conn = get_tenant_conn(g.tenant_id)
try:
stats = get_global_savings_stats(conn, g.tenant_id, from_date=from_date, to_date=to_date)
return jsonify(stats)
finally:
conn.close()
@savings_bp.route('/calculate', methods=['POST'])
@require_auth()
def calculate_savings():
"""Calculate savings for a given price vs retail."""
data = request.get_json() or {}
unit_price = float(data.get('unit_price', 0))
retail_price = float(data.get('retail_price', 0))
quantity = int(data.get('quantity', 1))
savings, pct = calculate_item_savings(unit_price, retail_price, quantity)
return jsonify({
'unit_price': unit_price,
'retail_price': retail_price,
'quantity': quantity,
'savings_amount': savings,
'savings_percentage': pct,
})