Mejoras de sesion: estabilizacion, IA, Nexus Yonke y marketplace B2B
- F0 blindaje: Redis/Celery, pg_hba sin trust LAN, secretos a env 600, backups, webhook auth, jobs muertos - F1 caja: bugs POS, reembolsos al corte, apartados, CFDI, topes de descuento en cotizacion/apartado - F2/F7 IA de catalogo: autocategorizacion, dedup de SKUs con embeddings, OCR de facturas, descripciones, claves SAT, limpieza de fitment - F3 IA en el flujo: vision Yonke, preguntas MeLi, venta cruzada, anomalias, sinonimos, busqueda semantica (rerank/embeddings) - F4/F8 escala y cartera: /metrics+Prometheus+Grafana, pool, score de credito, churn, metricas multi-tenant - Nexus Yonke (M1-M6): modelo de datos 1-de-1, alta por donador, vitrina publica, MeLi usado, consignacion, garantia por dias, reportes, etiquetas - Marketplace B2B entre yonkes (catalogo + pedidos cross-tenant) - Seguridad: auth en /api/admin/* (35 endpoints), alertas por tenant - Migraciones v4.27/v4.28; timers expire-demos y rollup-metrics
This commit is contained in:
@@ -63,6 +63,28 @@ def _format_rate(val):
|
||||
return str(_to_dec(val).quantize(SIX, ROUND_HALF_UP))
|
||||
|
||||
|
||||
def _item_sat_keys(item):
|
||||
"""Return (clave_prod_serv, clave_unidad), falling back to SAT rules.
|
||||
|
||||
Precedence: the value carried by the sale item, then the rule engine over
|
||||
the item name/brand/category (``services.sat_keys``), then the historic
|
||||
hard-coded default. Never raises, so a CFDI can always be built.
|
||||
"""
|
||||
cps = item.get('clave_prod_serv')
|
||||
cu = item.get('clave_unidad')
|
||||
if cps and cu:
|
||||
return cps, cu
|
||||
try:
|
||||
from services import sat_keys
|
||||
k, u, _src = sat_keys.resolve_key(
|
||||
name=item.get('name'), brand=item.get('brand'),
|
||||
category=item.get('category'), part_number=item.get('part_number'),
|
||||
)
|
||||
except Exception:
|
||||
k, u = None, None
|
||||
return (cps or k or '25174800'), (cu or u or 'H87')
|
||||
|
||||
|
||||
def _make_element(parent, tag, attribs=None, ns=CFDI_NS):
|
||||
"""Create a subelement with the given namespace."""
|
||||
elem = etree.SubElement(parent, f'{{{ns}}}{tag}')
|
||||
@@ -83,6 +105,9 @@ def build_ingreso_xml(sale, tenant_config, customer=None):
|
||||
items: [{part_number, name, quantity, unit_price, discount_amount,
|
||||
tax_rate, tax_amount, subtotal,
|
||||
clave_prod_serv, clave_unidad}]
|
||||
(clave_prod_serv / clave_unidad are optional: when absent they are
|
||||
resolved by services.sat_keys rules over name/brand/category,
|
||||
falling back to 25174800 / H87)
|
||||
tenant_config: dict with keys:
|
||||
rfc, razon_social, regimen_fiscal, cp (codigo postal),
|
||||
serie (optional), nombre_comercial (optional)
|
||||
@@ -175,10 +200,10 @@ def build_ingreso_xml(sale, tenant_config, customer=None):
|
||||
base = (importe - discount_amount).quantize(TWO, ROUND_HALF_UP)
|
||||
|
||||
concepto = _make_element(conceptos, 'Concepto')
|
||||
concepto.set('ClaveProdServ', item.get('clave_prod_serv') or '25174800')
|
||||
concepto.set('ClaveProdServ', _item_sat_keys(item)[0])
|
||||
concepto.set('NoIdentificacion', item.get('part_number') or '')
|
||||
concepto.set('Cantidad', str(qty))
|
||||
concepto.set('ClaveUnidad', item.get('clave_unidad') or 'H87')
|
||||
concepto.set('ClaveUnidad', _item_sat_keys(item)[1])
|
||||
concepto.set('Unidad', 'PZA')
|
||||
concepto.set('Descripcion', item.get('name') or 'Autoparte')
|
||||
concepto.set('ValorUnitario', _format_amount(unit_price))
|
||||
@@ -301,10 +326,10 @@ def build_egreso_xml(sale, tenant_config, customer, original_uuid):
|
||||
base = (importe - discount_amount).quantize(TWO, ROUND_HALF_UP)
|
||||
|
||||
concepto = _make_element(conceptos, 'Concepto')
|
||||
concepto.set('ClaveProdServ', item.get('clave_prod_serv') or '25174800')
|
||||
concepto.set('ClaveProdServ', _item_sat_keys(item)[0])
|
||||
concepto.set('NoIdentificacion', item.get('part_number') or '')
|
||||
concepto.set('Cantidad', str(qty))
|
||||
concepto.set('ClaveUnidad', item.get('clave_unidad') or 'H87')
|
||||
concepto.set('ClaveUnidad', _item_sat_keys(item)[1])
|
||||
concepto.set('Unidad', 'PZA')
|
||||
concepto.set('Descripcion', item.get('name') or 'Autoparte')
|
||||
concepto.set('ValorUnitario', _format_amount(unit_price))
|
||||
@@ -556,10 +581,10 @@ def build_global_invoice_xml(sales, tenant_config, year, month):
|
||||
base = (importe - discount_amount).quantize(TWO, ROUND_HALF_UP)
|
||||
|
||||
concepto = _make_element(conceptos, 'Concepto')
|
||||
concepto.set('ClaveProdServ', item.get('clave_prod_serv') or '25174800')
|
||||
concepto.set('ClaveProdServ', _item_sat_keys(item)[0])
|
||||
concepto.set('NoIdentificacion', item.get('part_number') or str(sale['id']))
|
||||
concepto.set('Cantidad', str(qty))
|
||||
concepto.set('ClaveUnidad', item.get('clave_unidad') or 'H87')
|
||||
concepto.set('ClaveUnidad', _item_sat_keys(item)[1])
|
||||
concepto.set('Unidad', 'PZA')
|
||||
concepto.set('Descripcion', item.get('name') or 'Autoparte')
|
||||
concepto.set('ValorUnitario', _format_amount(unit_price))
|
||||
|
||||
Reference in New Issue
Block a user