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:
108
pos/blueprints/savings_bp.py
Normal file
108
pos/blueprints/savings_bp.py
Normal file
@@ -0,0 +1,108 @@
|
||||
"""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,
|
||||
})
|
||||
Reference in New Issue
Block a user