feat(crm): costo estimado por servicio adicional, folios visibles y sidebar por flujo

- Solicitud: al marcar un servicio adicional se habilita su costo estimado
  (columna JSON additional_service_costs). Al cotizar, cada servicio marcado se
  siembra como concepto de la cotización con ese costo de partida (costo=venta).
- Folios visibles: se muestran en la tarjeta de Oportunidad del kanban y se aclara
  en el formulario que el folio se asigna al guardar (las listas ya lo mostraban).
- Sidebar CRM reordenado por flujo comercial (captación → embudo → solicitud →
  cotización → catálogos de apoyo).
- Migración c2d3e4f5a6b7 aditiva y reversible. Suite backend en verde (102).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Ernesto Herrera
2026-08-04 07:00:42 -06:00
parent 915bdd19fe
commit 36e98ee976
11 changed files with 115 additions and 13 deletions

View File

@@ -6,6 +6,7 @@ from sqlalchemy import func
from sqlalchemy.orm import Session
from ..accounts.models import Account
from ..catalogs.models import CatalogItem
from ..common.folios import next_folio
from ..service_requests.models import RateRequest, ServiceRequest
from ..suppliers.models import Supplier
@@ -150,6 +151,14 @@ def create_quotes_from_service_request(
)
.all()
)
# Etiquetas legibles de los servicios adicionales (global + tenant) para los conceptos
service_labels = {
code: label
for code, label in db.query(CatalogItem.code, CatalogItem.label).filter(
CatalogItem.catalog == "servicio_adicional"
)
}
service_costs = sr.additional_service_costs or {}
created: list[Quote] = []
for variant in variants:
@@ -178,6 +187,15 @@ def create_quotes_from_service_request(
unit_cost=amount, unit_sale=amount, currency=rr.currency,
tenant_id=tenant_id, company_id=company_id,
))
# Servicios adicionales marcados en la solicitud → conceptos con su costo estimado
for code in (sr.additional_services or []):
amount = Decimal(str(service_costs.get(code) or 0))
db.add(QuoteItem(
quote_id=quote.id, concept=code[:60],
description=service_labels.get(code, "Servicio adicional"),
quantity=Decimal(1), unit_cost=amount, unit_sale=amount,
currency=sr.currency, tenant_id=tenant_id, company_id=company_id,
))
db.flush()
_recompute_totals(db, quote)
created.append(quote)