Al-HUWAITI Shell
Al-huwaiti


Server : Apache
System : Linux dedi-14684855.grupobig.com 5.14.0-611.49.1.el9_7.x86_64 #1 SMP PREEMPT_DYNAMIC Tue Apr 21 16:39:08 EDT 2026 x86_64
User : grupo692 ( 1004)
PHP Version : 8.2.31
Disable Function : NONE
Directory :  /opt/dash_backend_new/app/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Current File : //opt/dash_backend_new/app/queries.py
from datetime import date
from typing import List, Optional, Any, Dict, Tuple
import math

import pandas as pd
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel

from .db import get_db_conn
from .auth import get_current_user, UserTokenData
from .tenant_oracle_db import get_tenant_context, TenantContext
from .sandbox_data import is_sandbox_empresa, vendas_por_unidade_fake, vendas_por_grupo_fake

router = APIRouter(prefix="/vendas", tags=["vendas"])


# -----------------------------------------------------------------------------
# HELPERS
# -----------------------------------------------------------------------------
def _sanitize_for_json(value: Any) -> Any:
    """
    Garante que não existam NaN/Infinity no payload final.
    JSON não aceita NaN/Inf.
    """
    if value is None:
        return None

    if isinstance(value, (float, int)):
        if isinstance(value, float) and (math.isnan(value) or math.isinf(value)):
            return None
        return value

    try:
        if hasattr(value, "item"):
            value = value.item()
            if isinstance(value, float) and (math.isnan(value) or math.isinf(value)):
                return None
            return value
    except Exception:
        pass

    return value


def _sanitize_records(records: List[Dict]) -> List[Dict]:
    out: List[Dict] = []
    for row in records:
        out.append({k: _sanitize_for_json(v) for k, v in row.items()})
    return out


def _bind_in_params(prefix: str, values: List[int]) -> Tuple[str, Dict[str, int]]:
    """
    Oracle não aceita bind de lista direto no IN.
    Então criamos :u0,:u1,:u2... e devolvemos também o dict dos params.
    """
    params: Dict[str, int] = {}
    keys: List[str] = []
    for i, v in enumerate(values):
        k = f"{prefix}{i}"
        keys.append(f":{k}")
        params[k] = int(v)
    return ", ".join(keys), params


def _unidades_cte(prefix: str, unidades: List[int]) -> Tuple[str, Dict[str, int]]:
    """
    Pequena CTE para garantir que todas as unidades apareçam, mesmo com 0 vendas.
    """
    params: Dict[str, int] = {}
    parts: List[str] = []
    for i, u in enumerate(unidades):
        k = f"{prefix}{i}"
        params[k] = int(u)
        if i == 0:
            parts.append(f"SELECT :{k} AS cod_und FROM dual")
        else:
            parts.append(f"UNION ALL SELECT :{k} FROM dual")
    return "\n    ".join(parts), params


# -----------------------------------------------------------------------------
# META MODE (compatibilidade com app publicado)
# -----------------------------------------------------------------------------
def _is_month_to_date_current_month(start_date: date, end_date: date) -> bool:
    today = date.today()
    return (
        start_date.year == today.year
        and start_date.month == today.month
        and start_date.day == 1
        and end_date == today
    )


def _resolve_meta_mode(meta_mode: Optional[str], start_date: date, end_date: date) -> str:
    """
    Retorna "full_month" ou "period".

    - Se meta_mode vier explícito, respeita.
    - Se NÃO vier, preserva comportamento do app publicado:
        * quando o range for (1º do mês atual -> hoje), usa meta do mês inteiro.
    - Caso contrário, usa meta do período (correto para Dia/Semana/Quinzena/Livre).
    """
    if meta_mode:
        mm = meta_mode.strip().lower()
        if mm in ("full_month", "month", "mensal", "mes", "mês"):
            return "full_month"
        if mm in ("period", "range", "periodo", "período", "periodo"):
            return "period"

    return "full_month" if _is_month_to_date_current_month(start_date, end_date) else "period"


# -----------------------------------------------------------------------------
# MODELS
# -----------------------------------------------------------------------------
class VendasUnidadeItem(BaseModel):
    cod_und: str
    unidade: str
    qntd_pedidos: int
    valor_total: float
    mta_meta: float
    mta_big_meta: float
    tkt_medio: float
    mta_meta_tkt_medio: float

    valor_total_ano_anterior: Optional[float] = None
    crescimento_pct: Optional[float] = None


class VendasGrupoItem(BaseModel):
    grupo: str
    valor: float


# -----------------------------------------------------------------------------
# ENDPOINTS
# -----------------------------------------------------------------------------
@router.get("/unidades", response_model=List[VendasUnidadeItem])
def vendas_por_unidade(
    start_date: date,
    end_date: date,
    meta_mode: Optional[str] = None,
    current_user: UserTokenData = Depends(get_current_user),
    ctx: TenantContext = Depends(get_tenant_context),
    conn=Depends(get_db_conn),
):
    if is_sandbox_empresa(ctx.empresa) or getattr(ctx, "sandbox", False):
        return vendas_por_unidade_fake(start_date, end_date, ctx.unidades)

    unidades = ctx.unidades or []
    if not unidades:
        return []

    in_clause, in_params = _bind_in_params("u", unidades)
    unidades_cte_sql, unidades_cte_params = _unidades_cte("c", unidades)

    mode = _resolve_meta_mode(meta_mode, start_date, end_date)

    # Observação importante:
    # - full_month: meta mensal inteira (BIG_METAS)
    # - period: meta do período selecionado (BIG_METAS_PESO * BIG_METAS)
    #
    # Isso permite corrigir Quinzena/Livre sem quebrar o comportamento legado do "Mês"
    # para o app publicado (quando meta_mode não é enviado).
    if mode == "full_month":
        meta_cte = f"""
meta_calc AS (
    SELECT
        m.MTA_UNIDADE_FK_PK AS cod_und,
        NVL(m.MTA_META, 0) AS MTA_META,
        NVL(m.MTA_BIG_META, 0) AS MTA_BIG_META,
        NVL(m.MTA_META_TKT_MEDIO, 0) AS MTA_META_TKT_MEDIO
    FROM DBAUSER.BIG_METAS m
    WHERE TRUNC(m.MTA_MES_ANO, 'MM') = TRUNC(:start_date, 'MM')
      AND m.MTA_UNIDADE_FK_PK IN ({in_clause})
),
"""
    else:
        meta_cte = f"""
meta_calc AS (
    SELECT
        p.MTA_UNIDADE_FK_PK AS cod_und,
        NVL(SUM(p.MTA_PESO * m.MTA_META), 0) AS MTA_META,
        NVL(SUM(p.MTA_PESO * m.MTA_BIG_META), 0) AS MTA_BIG_META,
        NVL(SUM(p.MTA_PESO * m.MTA_META_TKT_MEDIO), 0) AS MTA_META_TKT_MEDIO
    FROM DBAUSER.BIG_METAS_PESO p
    JOIN DBAUSER.BIG_METAS m
      ON m.MTA_UNIDADE_FK_PK = p.MTA_UNIDADE_FK_PK
     AND TRUNC(m.MTA_MES_ANO, 'MM') = TRUNC(p.MTA_DATA, 'MM')
    WHERE p.MTA_DATA >= :start_date
      AND p.MTA_DATA < (:end_date + 1)
      AND p.MTA_UNIDADE_FK_PK IN ({in_clause})
    GROUP BY p.MTA_UNIDADE_FK_PK
),
"""

    query = f"""
WITH
unidades AS (
    {unidades_cte_sql}
),
{meta_cte}
pedidos_atual AS (
    SELECT
        TPED_UNIDADE_FK_PK AS cod_und,
        COUNT(*) AS qntd_pedidos,
        SUM(TPED_VALOR_TOTAL_PEDIDO) AS valor_pedidos
    FROM TPED_PEDIDO_VENDA
    WHERE TPED_UNIDADE_FK_PK IN ({in_clause})
      AND TPED_DATA_EMISSAO >= :start_date
      AND TPED_DATA_EMISSAO < (:end_date + 1)
      AND TPED_STATUS_PEDIDO = 'MA'
      AND TPED_NATUREZA_MOVIMENTACAO = 'VM'
    GROUP BY TPED_UNIDADE_FK_PK

    UNION ALL

    SELECT
        TPED_UNIDADE_FK_PK AS cod_und,
        COUNT(*) AS qntd_pedidos,
        SUM(TPED_VALOR_TOTAL_PEDIDO) AS valor_pedidos
    FROM TPED_HISTORICO_VENDA
    WHERE TPED_UNIDADE_FK_PK IN ({in_clause})
      AND TPED_DATA_EMISSAO >= :start_date
      AND TPED_DATA_EMISSAO < (:end_date + 1)
      AND TPED_STATUS_PEDIDO = 'MA'
      AND TPED_NATUREZA_MOVIMENTACAO = 'VM'
    GROUP BY TPED_UNIDADE_FK_PK
),

atual_agg AS (
    SELECT
        cod_und,
        SUM(qntd_pedidos) AS qntd_pedidos,
        SUM(valor_pedidos) AS valor_pedidos
    FROM pedidos_atual
    GROUP BY cod_und
),

descontos_atual AS (
    SELECT
        TMOV_UNIDADE_FK AS cod_und,
        SUM(TMOV_VALOR_TOTAL) AS valor_descontos
    FROM TMOV_EXTRA
    WHERE TMOV_UNIDADE_FK IN ({in_clause})
      AND TMOV_NATUREZA_MOVIMENTACAO = 'DC'
      AND TMOV_ESTADO_MOVIMENTACAO = 'CL'
      AND TMOV_APENAS_IMPRESSAO = 'N'
      AND TMOV_DATA_DOCUMENTO >= :start_date
      AND TMOV_DATA_DOCUMENTO < (:end_date + 1)
    GROUP BY TMOV_UNIDADE_FK
),

vendas_atual AS (
    SELECT
        u.cod_und,
        NVL(a.qntd_pedidos, 0) AS qntd_pedidos,
        NVL(a.valor_pedidos, 0) - NVL(d.valor_descontos, 0) AS valor_total
    FROM unidades u
    LEFT JOIN atual_agg a ON a.cod_und = u.cod_und
    LEFT JOIN descontos_atual d ON d.cod_und = u.cod_und
),

pedidos_aa AS (
    SELECT
        TPED_UNIDADE_FK_PK AS cod_und,
        SUM(TPED_VALOR_TOTAL_PEDIDO) AS valor_pedidos
    FROM TPED_PEDIDO_VENDA
    WHERE TPED_UNIDADE_FK_PK IN ({in_clause})
      AND TPED_DATA_EMISSAO >= ADD_MONTHS(:start_date, -12)
      AND TPED_DATA_EMISSAO < (ADD_MONTHS(:end_date, -12) + 1)
      AND TPED_STATUS_PEDIDO = 'MA'
      AND TPED_NATUREZA_MOVIMENTACAO = 'VM'
    GROUP BY TPED_UNIDADE_FK_PK

    UNION ALL

    SELECT
        TPED_UNIDADE_FK_PK AS cod_und,
        SUM(TPED_VALOR_TOTAL_PEDIDO) AS valor_pedidos
    FROM TPED_HISTORICO_VENDA
    WHERE TPED_UNIDADE_FK_PK IN ({in_clause})
      AND TPED_DATA_EMISSAO >= ADD_MONTHS(:start_date, -12)
      AND TPED_DATA_EMISSAO < (ADD_MONTHS(:end_date, -12) + 1)
      AND TPED_STATUS_PEDIDO = 'MA'
      AND TPED_NATUREZA_MOVIMENTACAO = 'VM'
    GROUP BY TPED_UNIDADE_FK_PK
),

aa_agg AS (
    SELECT
        cod_und,
        SUM(valor_pedidos) AS valor_pedidos
    FROM pedidos_aa
    GROUP BY cod_und
),

descontos_aa AS (
    SELECT
        TMOV_UNIDADE_FK AS cod_und,
        SUM(TMOV_VALOR_TOTAL) AS valor_descontos
    FROM TMOV_EXTRA
    WHERE TMOV_UNIDADE_FK IN ({in_clause})
      AND TMOV_NATUREZA_MOVIMENTACAO = 'DC'
      AND TMOV_ESTADO_MOVIMENTACAO = 'CL'
      AND TMOV_APENAS_IMPRESSAO = 'N'
      AND TMOV_DATA_DOCUMENTO >= ADD_MONTHS(:start_date, -12)
      AND TMOV_DATA_DOCUMENTO < (ADD_MONTHS(:end_date, -12) + 1)
    GROUP BY TMOV_UNIDADE_FK
),

vendas_aa AS (
    SELECT
        u.cod_und,
        NVL(a.valor_pedidos, 0) - NVL(d.valor_descontos, 0) AS valor_total_ano_anterior
    FROM unidades u
    LEFT JOIN aa_agg a ON a.cod_und = u.cod_und
    LEFT JOIN descontos_aa d ON d.cod_und = u.cod_und
)

SELECT
    u.cod_und AS COD_UND,
    tu.TUND_FANTASIA AS UNIDADE,
    va.qntd_pedidos AS QNTD_PEDIDOS,
    va.valor_total AS VALOR_TOTAL,

    NVL(mc.MTA_META, 0) AS MTA_META,
    NVL(mc.MTA_BIG_META, 0) AS MTA_BIG_META,
    NVL(mc.MTA_META_TKT_MEDIO, 0) AS MTA_META_TKT_MEDIO,

    NVL(ROUND(va.valor_total / NULLIF(va.qntd_pedidos, 0), 2), 0) AS TKT_MEDIO,

    vaa.valor_total_ano_anterior AS VALOR_TOTAL_ANO_ANTERIOR,
    CASE
        WHEN vaa.valor_total_ano_anterior IS NULL OR vaa.valor_total_ano_anterior = 0 THEN NULL
        ELSE ROUND(((va.valor_total - vaa.valor_total_ano_anterior) / vaa.valor_total_ano_anterior) * 100, 2)
    END AS CRESCIMENTO_PCT
FROM unidades u
LEFT JOIN vendas_atual va ON va.cod_und = u.cod_und
LEFT JOIN vendas_aa vaa ON vaa.cod_und = u.cod_und
LEFT JOIN TUND_UNIDADE tu ON tu.TUND_UNIDADE_PK = u.cod_und
LEFT JOIN meta_calc mc ON mc.cod_und = u.cod_und
ORDER BY VALOR_TOTAL DESC
"""

    params: Dict[str, Any] = {}
    params.update(in_params)
    params.update(unidades_cte_params)
    params.update({"start_date": start_date, "end_date": end_date})

    try:
        df = pd.read_sql(query, con=conn, params=params)
    except Exception as exc:
        raise HTTPException(status_code=500, detail=f"Erro ao executar vendas/unidades: {exc}")

    if "COD_UND" in df.columns:
        df["COD_UND"] = df["COD_UND"].astype(str)

    df = df.rename(
        columns={
            "COD_UND": "cod_und",
            "UNIDADE": "unidade",
            "QNTD_PEDIDOS": "qntd_pedidos",
            "VALOR_TOTAL": "valor_total",
            "MTA_META": "mta_meta",
            "MTA_BIG_META": "mta_big_meta",
            "TKT_MEDIO": "tkt_medio",
            "MTA_META_TKT_MEDIO": "mta_meta_tkt_medio",
            "VALOR_TOTAL_ANO_ANTERIOR": "valor_total_ano_anterior",
            "CRESCIMENTO_PCT": "crescimento_pct",
        }
    )

    return _sanitize_records(df.to_dict(orient="records"))


@router.get("/grupos", response_model=List[VendasGrupoItem])
def vendas_por_grupo(
    start_date: date,
    end_date: date,
    current_user: UserTokenData = Depends(get_current_user),
    ctx: TenantContext = Depends(get_tenant_context),
    conn=Depends(get_db_conn),
):
    if is_sandbox_empresa(ctx.empresa) or getattr(ctx, "sandbox", False):
        return vendas_por_grupo_fake(start_date, end_date)

    unidades = ctx.unidades or []
    if not unidades:
        return []

    in_clause, in_params = _bind_in_params("u", unidades)

    query = f"""
SELECT *
FROM (
    SELECT
        COD || ' - ' || GRUPO AS GRUPO,
        SUM(VALOR) AS VALOR
    FROM (
        SELECT
            SUM(TPED_PEDIDO_VENDA_ITEM.TPED_VALOR_LIQUIDO_ITEM) AS VALOR,
            TMER_GRUPO_MERCADORIA.TMER_GRUPO_MERCADORIA_PK AS COD,
            TMER_GRUPO_MERCADORIA.TMER_DESCRICAO AS GRUPO
        FROM
            TPED_PEDIDO_VENDA_ITEM
            LEFT JOIN TPED_PEDIDO_VENDA
                ON TPED_PEDIDO_VENDA.TPED_NUMERO_PEDIDO_PK = TPED_PEDIDO_VENDA_ITEM.TPED_NUMERO_PEDIDO_FK_PK
            LEFT JOIN TMER_ESTOQUE
                ON TMER_ESTOQUE.TMER_CODIGO_PRI_FK_PK = TPED_PEDIDO_VENDA_ITEM.TPED_CODIGO_PRI_FK_PK
            LEFT JOIN TMER_MERCADORIA
                ON TMER_MERCADORIA.TMER_CODIGO_PRI_PK = TMER_ESTOQUE.TMER_CODIGO_PRI_FK_PK
            LEFT JOIN TMER_GRUPO_MERCADORIA
                ON TMER_GRUPO_MERCADORIA.TMER_GRUPO_MERCADORIA_PK = TMER_MERCADORIA.TMER_GRUPO_MERCADORIA_FK
        WHERE
            TPED_PEDIDO_VENDA_ITEM.TPED_UNIDADE_FK_PK IN ({in_clause})
            AND TMER_ESTOQUE.TMER_UNIDADE_FK_PK IN ({in_clause})
            AND TPED_PEDIDO_VENDA.TPED_UNIDADE_FK_PK IN ({in_clause})
            AND TPED_PEDIDO_VENDA.TPED_DATA_EMISSAO >= :start_date
            AND TPED_PEDIDO_VENDA.TPED_DATA_EMISSAO < (:end_date + 1)
            AND TPED_STATUS_PEDIDO = 'MA'
            AND TPED_PEDIDO_VENDA.TPED_NATUREZA_MOVIMENTACAO = 'VM'
            AND TPED_PEDIDO_VENDA_ITEM.TPED_UNIDADE_FK_PK = TMER_ESTOQUE.TMER_UNIDADE_FK_PK
            AND TPED_PEDIDO_VENDA_ITEM.TPED_UNIDADE_FK_PK = TPED_PEDIDO_VENDA.TPED_UNIDADE_FK_PK
            AND TMER_ESTOQUE.TMER_UNIDADE_FK_PK = TPED_PEDIDO_VENDA.TPED_UNIDADE_FK_PK
        GROUP BY
            TMER_GRUPO_MERCADORIA.TMER_DESCRICAO,
            TMER_GRUPO_MERCADORIA.TMER_GRUPO_MERCADORIA_PK

        UNION ALL

        SELECT
            SUM(TPED_HISTORICO_VENDA_ITEM.TPED_VALOR_LIQUIDO_ITEM) AS VALOR,
            TMER_GRUPO_MERCADORIA.TMER_GRUPO_MERCADORIA_PK AS COD,
            TMER_GRUPO_MERCADORIA.TMER_DESCRICAO AS GRUPO
        FROM
            TPED_HISTORICO_VENDA_ITEM
            LEFT JOIN TPED_HISTORICO_VENDA
                ON TPED_HISTORICO_VENDA.TPED_NUMERO_PEDIDO_PK = TPED_HISTORICO_VENDA_ITEM.TPED_NUMERO_PEDIDO_FK_PK
            LEFT JOIN TMER_ESTOQUE
                ON TMER_ESTOQUE.TMER_CODIGO_PRI_FK_PK = TPED_HISTORICO_VENDA_ITEM.TPED_CODIGO_PRI_FK_PK
            LEFT JOIN TMER_MERCADORIA
                ON TMER_MERCADORIA.TMER_CODIGO_PRI_PK = TMER_ESTOQUE.TMER_CODIGO_PRI_FK_PK
            LEFT JOIN TMER_GRUPO_MERCADORIA
                ON TMER_GRUPO_MERCADORIA.TMER_GRUPO_MERCADORIA_PK = TMER_MERCADORIA.TMER_GRUPO_MERCADORIA_FK
        WHERE
            TPED_HISTORICO_VENDA_ITEM.TPED_UNIDADE_FK_PK IN ({in_clause})
            AND TMER_ESTOQUE.TMER_UNIDADE_FK_PK IN ({in_clause})
            AND TPED_HISTORICO_VENDA.TPED_UNIDADE_FK_PK IN ({in_clause})
            AND TPED_HISTORICO_VENDA.TPED_DATA_EMISSAO >= :start_date
            AND TPED_HISTORICO_VENDA.TPED_DATA_EMISSAO < (:end_date + 1)
            AND TPED_STATUS_PEDIDO = 'MA'
            AND TPED_HISTORICO_VENDA.TPED_NATUREZA_MOVIMENTACAO = 'VM'
            AND TPED_HISTORICO_VENDA_ITEM.TPED_UNIDADE_FK_PK = TMER_ESTOQUE.TMER_UNIDADE_FK_PK
            AND TPED_HISTORICO_VENDA_ITEM.TPED_UNIDADE_FK_PK = TPED_HISTORICO_VENDA.TPED_UNIDADE_FK_PK
            AND TMER_ESTOQUE.TMER_UNIDADE_FK_PK = TPED_HISTORICO_VENDA.TPED_UNIDADE_FK_PK
        GROUP BY
            TMER_GRUPO_MERCADORIA.TMER_DESCRICAO,
            TMER_GRUPO_MERCADORIA.TMER_GRUPO_MERCADORIA_PK
    )
    GROUP BY COD || ' - ' || GRUPO
    ORDER BY 2 DESC
)
WHERE ROWNUM <= 10
"""

    params: Dict[str, Any] = {"start_date": start_date, "end_date": end_date}
    params.update(in_params)

    try:
        df = pd.read_sql(query, con=conn, params=params)
    except Exception as exc:
        raise HTTPException(status_code=500, detail=f"Erro ao executar vendas/grupos: {exc}")

    df = df.rename(columns={"GRUPO": "grupo", "VALOR": "valor"})
    return _sanitize_records(df.to_dict(orient="records"))

Al-HUWAITI Shell