Files
Autoparts-DB/pos/blueprints/supplier_portal_bp.py
consultoria-as 2cfe4b3913 feat(api): add BNPL, ERP, WhatsApp Cloud, Supplier Portal stubs
- bnpl_bp.py: APLAZO/Kueski/Clip application workflow (mock)
- erp_bp.py: Aspel/CONTPAQi/SAP/Odoo sync jobs (mock)
- whatsapp_cloud_bp.py: Meta Cloud API webhook, messages, templates
- supplier_portal_bp.py: demand by zone/branch and top-parts analytics
- app.py: register all new blueprints
2026-04-29 06:31:03 +00:00

106 lines
3.5 KiB
Python

"""Supplier Portal Blueprint — Demand insights for vendors.
Allows suppliers to view demand by zone, part type, and branch.
"""
from flask import Blueprint, request, jsonify, g
from functools import wraps
from datetime import datetime, timedelta
from decimal import Decimal
import json
supplier_portal_bp = Blueprint('supplier_portal', __name__, url_prefix='/pos/api/supplier-portal')
from middleware import require_auth
class DecimalEncoder(json.JSONEncoder):
def default(self, o):
if isinstance(o, Decimal):
return float(o)
return super().default(o)
@supplier_portal_bp.route('/demand', methods=['GET'])
@require_auth()
def get_demand():
"""Aggregated demand by zone, part group, and time range."""
days = request.args.get('days', 30, type=int)
group_id = request.args.get('group_id', type=int)
branch_id = request.args.get('branch_id', type=int)
from tenant_db import get_tenant_db
db = get_tenant_db()
since = datetime.utcnow() - timedelta(days=days)
params = [since]
filters = "s.created_at >= %s"
if group_id:
filters += " AND p.group_id = %s"
params.append(group_id)
if branch_id:
filters += " AND s.branch_id = %s"
params.append(branch_id)
rows = db.execute(
f"""SELECT g.name as group_name, b.name as branch_name,
COUNT(DISTINCT s.id_sale) as orders,
SUM(si.quantity) as qty_requested,
COALESCE(SUM(si.total), 0) as revenue
FROM sale_items si
JOIN sales s ON si.sale_id = s.id_sale
JOIN parts p ON si.part_id = p.id_part
JOIN part_groups g ON p.group_id = g.id_group
LEFT JOIN branches b ON s.branch_id = b.id_branch
WHERE {filters}
GROUP BY g.name, b.name
ORDER BY revenue DESC
LIMIT 100""", tuple(params)
).fetchall()
return jsonify({
'since': since.isoformat(),
'days': days,
'demand': [
{'group': row['group_name'], 'branch': row['branch_name'],
'orders': row['orders'], 'quantity': row['qty_requested'],
'revenue': row['revenue']}
for row in rows
]
}, cls=DecimalEncoder)
@supplier_portal_bp.route('/top-parts', methods=['GET'])
@require_auth()
def get_top_parts():
"""Top moving parts for suppliers to restock."""
days = request.args.get('days', 30, type=int)
from tenant_db import get_tenant_db
db = get_tenant_db()
since = datetime.utcnow() - timedelta(days=days)
rows = db.execute(
"""SELECT p.oem_part_number, p.name, g.name as group_name,
SUM(si.quantity) as sold, COALESCE(SUM(si.total), 0) as revenue,
COALESCE(SUM(wi.stock_quantity), 0) as current_stock
FROM sale_items si
JOIN sales s ON si.sale_id = s.id_sale
JOIN parts p ON si.part_id = p.id_part
JOIN part_groups g ON p.group_id = g.id_group
LEFT JOIN warehouse_inventory wi ON p.id_part = wi.part_id
WHERE s.created_at >= %s
GROUP BY p.oem_part_number, p.name, g.name
ORDER BY sold DESC
LIMIT 50""", (since,)
).fetchall()
return jsonify({
'since': since.isoformat(),
'parts': [
{'oem': row['oem_part_number'], 'name': row['name'],
'group': row['group_name'], 'sold': row['sold'],
'revenue': row['revenue'], 'stock': row['current_stock']}
for row in rows
]
}, cls=DecimalEncoder)