SAP Business One: Política de Preços Customizada

Quando as listas de preços padrão do SAP Business One não são suficientes para expressar a política comercial da empresa, o Vendelo pode delegar o cálculo do preço de venda a uma stored procedure criada por você no próprio banco do SAP. Este artigo é para consultores e administradores do SAP B1: ao final, você saberá habilitar o recurso, escrever a procedure no contrato esperado e montar uma tabela de determinação de preços com vários fatores combinados: cliente, grupo, filial, território, condição de pagamento, moeda e faixas de quantidade.

Visão geral

No comportamento padrão, o Vendelo busca o preço de venda dos itens nas listas de preços do SAP Business One (as mesmas do cadastro de itens). Esse modelo atende bem quando o preço depende apenas da lista associada ao cliente ou ao documento.

Com o recurso de determinação de preços por procedure, o cálculo passa a ser feito por uma stored procedure sua, no banco do SAP B1. A cada pesquisa de itens em um documento de venda, o Vendelo envia para a procedure o contexto comercial completo (cliente, grupo de clientes, filial, lista de preços, território, condição de pagamento, moeda, desconto global e quantidades) e usa o preço devolvido como preço unitário do item.

A procedure substitui somente a origem do preço unitário. A política de preços e descontos do Vendelo continua valendo e é aplicada depois, sobre o preço retornado, exatamente como hoje acontece sobre o preço da lista: grupos de descontos (campanhas), descontos por período e quantidade, desconto adicional manual do vendedor e desconto padrão manual seguem funcionando normalmente.

A regra de preço fica 100% sob seu controle, dentro do banco do SAP. O Vendelo não impõe um modelo de tabela: você pode ler tabelas de usuário (UDTs), tabelas padrão do SAP, faixas de quantidade, vigências ou qualquer combinação, desde que a procedure respeite o contrato de entrada e saída descrito neste artigo.

Quando usar

O recurso foi criado para cenários em que a formação de preço depende de vários fatores simultâneos, algo que as listas de preços padrão do SAP B1 não expressam sozinhas:

  • Preço negociado por cliente: acordos comerciais específicos por CardCode, com ou sem vigência.
  • Política por grupo de clientes: atacado, varejo, revenda, key accounts, cada um com sua régua.
  • Faixas de quantidade: preço escalonado conforme a quantidade digitada no pedido.
  • Preço por filial ou território: diferenciação regional ou por unidade de negócio.
  • Condição de pagamento: bonificação para pagamento à vista, acréscimo para prazos longos.
  • Moeda: tabela própria em moeda estrangeira para exportação, sem depender de conversão por taxa.
  • Regras combinadas: qualquer cruzamento dos fatores acima, com prioridade entre regras.

O recurso não substitui tudo: a política de descontos interna do Vendelo (grupos de descontos, campanhas por período e quantidade, desconto adicional do vendedor, desconto padrão manual e vouchers) continua sendo aplicada pelo próprio Vendelo, sobre o preço devolvido pela procedure. Regras fiscais e de impostos também seguem o fluxo normal do SAP B1.

Como funciona

  1. O vendedor pesquisa itens no documento. Em uma cotação ou pedido de venda, ao pesquisar itens ou recarregar preços, o documento já tem (total ou parcialmente) cliente, filial, lista de preços, condição de pagamento, moeda e quantidades.

  2. O Vendelo monta o contexto comercial. Os dados do documento são convertidos para os códigos nativos do SAP B1: o cliente vira o CardCode, o grupo de clientes vira o GroupCode, a lista de preços vira o ListNum, e assim por diante. Campos ainda não preenchidos no documento seguem vazios.

  3. A procedure configurada é executada. O serviço de integração instalado no ambiente do SAP executa a procedure, e somente ela: apenas o nome configurado para a empresa pode ser chamado, o que impede a execução de comandos arbitrários no banco.

  4. A procedure devolve o preço por item. O resultado traz uma linha por item, com o preço na moeda solicitada. O Vendelo não recalcula nem converte esse valor: ele entra como o preço unitário base do item.

  5. Os preços aparecem na tela. Os valores são exibidos na pesquisa de itens e aplicados às linhas do documento, exatamente como acontecia com as listas de preços; a experiência do vendedor não muda.

  6. O Vendelo aplica sua política de descontos. Sobre o preço devolvido pela procedure, o Vendelo aplica normalmente os grupos de descontos, os descontos por período e quantidade, o desconto adicional manual do vendedor e o desconto padrão manual, como faz hoje sobre o preço da lista.

Como a procedure recebe o contexto do documento, o mesmo item pode ter preços diferentes conforme o cliente, a quantidade ou a condição de pagamento selecionada. Se o vendedor alterar esses campos e repesquisar, o preço acompanha.

Configuração

A habilitação tem três passos: criar a procedure no banco do SAP, apontar o nome dela no serviço de integração e ativar o recurso na configuração da empresa no Vendelo.

  1. Crie a procedure no banco do SAP B1 (SQL Server ou HANA), seguindo o contrato da seção Contrato da procedure. Um exemplo completo está na seção Exemplo de procedure.

  2. Configure o serviço de integração. No arquivo de configuração do serviço de integração local (o serviço instalado no ambiente do SAP), informe o nome da procedure no campo PriceProcedureName da conexão da empresa e reinicie o serviço. O nome deve ser um identificador simples, com schema opcional (por exemplo VDO_GET_PRICES ou DBO.VDO_GET_PRICES); nomes com espaços, aspas ou outros caracteres são rejeitados. A configuração é por empresa (por banco): em ambientes multiempresa, cada conexão pode ter sua própria procedure, ou nenhuma.

  3. Ative o recurso no Vendelo. Na configuração da integração SAP B1 da empresa, é ativada a opção UsePriceProcedure. Essa ativação é feita pela equipe de suporte do Vendelo: basta solicitar, informando a empresa e o nome da procedure já configurada no passo anterior.

Exemplo do trecho de configuração do serviço de integração:

"Companies": [
  {
    "Company": {
      "DBProvider": "MSSQL",
      "DBName": "SBO_MINHAEMPRESA",
      "DBHost": "servidor-sap",
      "...": "...",
      "PriceProcedureName": "VDO_GET_PRICES"
    }
  }
]

Os dois lados precisam estar ativos. Com UsePriceProcedure ativo no Vendelo mas sem PriceProcedureName no serviço de integração, a busca de preços retorna erro de configuração. Com o nome configurado mas o recurso desativado no Vendelo, a procedure é ignorada e as listas de preços padrão continuam valendo. Ative sempre em conjunto.

Contrato da procedure

Parâmetros de entrada

A procedure recebe sempre os dez parâmetros abaixo, nesta ordem. Campos não preenchidos no documento chegam como NULL, e a procedure deve tratá-los.

ParâmetroTipoConteúdoReferência SAP B1
ItemCodestextoCódigos dos itens pesquisados, separados por vírgula.OITM.ItemCode
BranchIdinteiroFilial do documento.OBPL.BPLId
BusinessPartnerIdtextoCliente do documento.OCRD.CardCode
BusinessPartnerGroupIdinteiroGrupo do cliente.OCRG.GroupCode
PriceListIdinteiroLista de preços do documento.OPLN.ListNum
TerritoryIdinteiroTerritório do cliente.OTER.territryID
PaymentConditionsIdinteiroCondição de pagamento.OCTG.GroupNum
CurrencytextoMoeda de referência do documento.Códigos de moeda do B1
GlobalDiscountPercentagedecimalDesconto global do documento (contexto: o Vendelo o aplica depois, sobre o preço retornado).Informado no Vendelo
BaseQuantitiestexto (JSON)Quantidade e desconto adicional por item (a quantidade serve para faixas; o desconto é contexto, aplicado depois pelo Vendelo).Informado no Vendelo

O parâmetro BaseQuantities chega como um array JSON, com o código SAP do item, a quantidade da linha e um eventual desconto adicional percentual:

[
  { "ItemId": "ITEM-001", "Qty": 60,  "AdditionalDiscountPercentage": null },
  { "ItemId": "ITEM-014", "Qty": 250, "AdditionalDiscountPercentage": 2.5 }
]

Os percentuais de desconto (GlobalDiscountPercentage e AdditionalDiscountPercentage) chegam apenas como informação de contexto. Não os reaplique no cálculo da procedure: o Vendelo aplica sua política de descontos sobre o preço retornado, e o desconto sairia em dobro.

No SQL Server os parâmetros são passados por nome (@ItemCodes, @BranchId, …), então basta declará-los com esses nomes. No HANA a chamada é posicional: declare os parâmetros de entrada exatamente na ordem da tabela acima.

Retorno esperado

A procedure deve devolver um único conjunto de resultados, com uma linha por item, contendo as colunas abaixo. Use exatamente esses nomes de coluna, entre aspas, para garantir a grafia em ambos os bancos:

ColunaObrigatoriedadeConteúdo
ItemIdObrigatórioCódigo SAP do item (ItemCode).
PriceObrigatórioPreço unitário calculado pelas suas regras (a política de descontos do Vendelo é aplicada depois, sobre ele).
CurrencyObrigatórioMoeda do preço; devolva a mesma moeda recebida.
ListIdOpcionalCódigo da lista associada, se quiser ecoar a lista do documento.

Se a procedure não devolver linha para um item, ele aparece sem preço no Vendelo. Inclua sempre um fallback, como o preço da lista padrão (ITM1), para os itens que não se encaixarem em nenhuma regra da sua tabela.

Case: tabela de determinação de preços

O case a seguir mostra como substituir as listas de preços padrão do SAP B1 por uma tabela de usuário (UDT) que concentra a política comercial da empresa, com vários fatores combinados e prioridade entre regras.

Isto é apenas um exemplo. A tabela, os campos, os pesos e a regra de desempate mostrados aqui são uma sugestão didática e não fazem parte do produto. O contrato do Vendelo se resume à entrada e à saída da procedure (seção anterior); o miolo é inteiramente seu: você pode modelar a procedure da forma que preferir para calcular os preços com base na política interna da sua empresa: outras tabelas, outros fatores, outra lógica de prioridade, ou nenhuma tabela nova.

A tabela de usuário @VDL_PRECOS

Crie no SAP B1 uma tabela de usuário do tipo “Sem objeto” chamada VDL_PRECOS, com os campos abaixo. Em todos os campos de contexto, vazio significa “vale para todos”:

CampoTipoSignificado
U_ItemCodetextoItem ao qual a regra se aplica (vazio = todos os itens).
U_BPLIdinteiroFilial (vazio = todas).
U_CardCodetextoCliente específico (vazio = todos).
U_GroupCodeinteiroGrupo de clientes (vazio = todos).
U_TerritryIDinteiroTerritório (vazio = todos).
U_PayGroupNuminteiroCondição de pagamento (vazio = todas).
U_ListNuminteiroLista de preços do documento (vazio = todas).
U_CurrencytextoMoeda da regra (ex.: BRL, USD).
U_QtyMindecimalQuantidade mínima para a regra valer (faixas de quantidade).
U_PricedecimalPreço unitário da regra (vazio em regras que só dão desconto).
U_DiscPctdecimalDesconto % aplicado sobre a regra de preço vencedora (modificador).
U_ValidFrom / U_ValidTodataVigência da regra (vazio = sempre válida).

Regra de prioridade: o mais específico vence

Quando mais de uma regra de preço atende ao contexto, vence a mais específica. Uma forma simples e determinística de implementar isso é atribuir pesos por campo preenchido e somar:

Campo preenchidoPeso
U_CardCode (cliente)32
U_BPLId (filial)16
U_GroupCode (grupo de clientes)8
U_TerritryID (território)4
U_PayGroupNum (condição de pagamento)2
U_ListNum (lista de preços)1
  • Entre regras de mesmo peso, vence a de maior quantidade mínima atendida (faixa mais vantajosa).
  • Persistindo o empate, vence o menor preço (postura conservadora a favor do cliente; inverta se a política for outra).
  • Regras só de desconto (U_Price vazio e U_DiscPct preenchido) não competem: elas se aplicam sobre o preço da regra vencedora, de forma cumulativa.

Exemplo de conteúdo da tabela

Cadastro de exemplo para o item ITEM-001 (preço de tabela R$ 100,00) e uma regra geral de grupo:

RegraCondiçõesQtde mín.PreçoDesc. %
R01Item ITEM-001 · lista 1 · BRL100,00
R02Item ITEM-001 · grupo 102 Atacado · BRL95,00
R03Item ITEM-001 · grupo 102 Atacado · BRL5089,90
R04Item ITEM-001 · grupo 102 Atacado · BRL20084,50
R05Item ITEM-001 · cliente C20000 · BRL92,00
R06Item ITEM-001 · cliente C20000 · filial 2 (SP) · BRL90,00
R07Item ITEM-001 · território 5 Sul · BRL97,50
R08Item ITEM-001 · condição -1 À vista · BRL3,0
R09Item ITEM-001 · USD19,90
R10Todos os itens · grupo 104 Revenda · BRL5,0

Como o preço é resolvido na prática

CenárioRegras candidatasResultado
Cliente C20000 (grupo Atacado), filial 2, território Sul, à vista, 60 un, BRLCandidatas de preço: R01 (peso 1), R02 (8), R03 (8, faixa 50), R05 (32), R06 (48), R07 (4).Vence R06 = 90,00 (cliente + filial). Modificador R08 (à vista, −3%) → 87,30.
Cliente avulso do grupo Varejo, 10 un, BRL, lista 1Só R01 atende.Preço de tabela: 100,00.
Cliente do grupo Atacado, a prazo, 250 un, BRLR01, R02, R03 e R04, todas com o mesmo peso (8); vence a maior faixa atendida.Vence R04 = 84,50 (faixa de 200 un).
Cliente de exportação, documento em USDSó R09 tem moeda USD.Preço USD 19,90, sem conversão de câmbio.
Cliente do grupo Revenda comprando qualquer item, BRLRegra de preço vencedora conforme o item, mais o modificador R10 (−5% em tudo para o grupo).Preço da regra vencedora × 0,95.

Além das regras da tabela, a procedure recebe o GlobalDiscountPercentage do documento e o AdditionalDiscountPercentage por item (via BaseQuantities). Use-os apenas como contexto (a quantidade, por exemplo, define a faixa de preço): esses descontos são aplicados pelo próprio Vendelo, depois, sobre o preço retornado.

Case com dados: do pedido ao preço

Para fechar o raciocínio, vamos acompanhar um pedido de ponta a ponta: o que o vendedor preenche, o que a procedure recebe, como cada preço é calculado, o que volta para o Vendelo e o que o Vendelo aplica depois. Os valores e regras usados aqui são os do case anterior, um exemplo ilustrativo; na sua implementação, o cálculo seguirá a lógica que você definir na procedure, conforme a política de preços da sua empresa.

1. O pedido no Vendelo

O vendedor monta um pedido de venda com dois itens para um cliente atacadista:

Campo do pedidoValor selecionadoCódigo SAP enviado
ClienteMercado Bom Preço Ltda. (grupo Atacado)C20000
Grupo do clienteAtacado102
FilialSão Paulo2
TerritórioSul5
Condição de pagamentoÀ vista-1
Lista de preçosLista de venda1
MoedaRealBRL
Desconto global2%2.0
Item 1ITEM-001, 60 unidadesITEM-001
Item 2ITEM-014, 250 unidades, com 2,5% de desconto adicional na linhaITEM-014

2. O que a procedure recebe

Ao pesquisar os itens, a procedure é executada com estes valores:

@ItemCodes                = 'ITEM-001,ITEM-014'
@BranchId                 = 2
@BusinessPartnerId        = 'C20000'
@BusinessPartnerGroupId   = 102
@PriceListId              = 1
@TerritoryId              = 5
@PaymentConditionsId      = -1
@Currency                 = 'BRL'
@GlobalDiscountPercentage = 2.0
@BaseQuantities           = '[
  { "ItemId": "ITEM-001", "Qty": 60,  "AdditionalDiscountPercentage": null },
  { "ItemId": "ITEM-014", "Qty": 250, "AdditionalDiscountPercentage": 2.5 }
]'

3. O cálculo, item a item

Cálculo do ITEM-001, que tem regras próprias na @VDL_PRECOS:

  1. Regras de preço candidatas: R01 (peso 1), R02 (8), R03 (8, faixa 50 atendida com 60 un), R05 (32), R06 (48) e R07 (4). Vence R06 (cliente C20000 + filial 2): preço base 90,00.

  2. Modificador da tabela: R08 (à vista, −3%) se aplica → 90,00 × 0,97 = 87,30.

  3. Preço devolvido: 87,30. O desconto global de 2% do pedido não entra aqui: ele é contexto, e será aplicado depois, pelo próprio Vendelo.

Cálculo do ITEM-014, que não tem nenhuma regra na tabela (R10 não se aplica, pois o cliente é do grupo Atacado, não Revenda):

  1. Fallback: a procedure busca o preço na lista padrão do SAP (ITM1, lista 1): 42,00.

  2. Preço devolvido: 42,00. O desconto global de 2% e o desconto adicional de 2,5% da linha ficam com o Vendelo, na etapa seguinte.

4. O que volta para o Vendelo

ItemIdPriceCurrencyListId
ITEM-00187.3000BRL1
ITEM-01442.0000BRL1

5. O que o Vendelo aplica depois

O valor devolvido pela procedure entra como preço unitário do item. Sobre ele, o Vendelo aplica sua política de preços e descontos, exatamente como faria sobre o preço da lista:

ItemPreço da procedureDescontos aplicados pelo VendeloPreço final na linha
ITEM-00187,30Desconto global do pedido (2%)87,30 × 0,98 = 85,55
ITEM-01442,00Desconto global (2%) e desconto adicional da linha (2,5%)42,00 × 0,98 × 0,975 = 40,13

Se houver grupos de descontos ou descontos por período e quantidade cadastrados no Vendelo, eles também entram nessa etapa. E se o vendedor trocar a condição de pagamento para “28 dias” e repesquisar, o modificador R08 deixa de valer: a procedure passa a devolver 90,00 e, com o desconto global de 2%, a linha fica em 88,20.

Use este roteiro como massa de teste da sua implementação: cadastre as regras R01 a R10, execute a procedure no banco com os parâmetros acima e confira se o resultado bate com as duas linhas da tabela da etapa 4 (87,30 e 42,00). Os descontos da etapa 5 só aparecem dentro do Vendelo.

Exemplo de procedure

Três modelos prontos para começar. As duas primeiras são cascas de referência, uma para cada banco: elas apenas reproduzem a busca padrão na lista de preços do SAP (ITM1), ou seja, entregam o mesmo resultado do comportamento atual. Servem como ponto de partida seguro: crie a casca, valide o fluxo de ponta a ponta e depois evolua a regra por dentro, mantendo apenas as colunas do SELECT final. O terceiro modelo é o exemplo completo da tabela de determinação.

Casca de referência: SAP HANA (SQLScript)

CREATE PROCEDURE "VDO_GET_PRICES" (
    IN  "ItemCodes"                NCLOB,           -- ItemCode separados por virgula
    IN  "BranchId"                 INTEGER,         -- OBPL.BPLId
    IN  "BusinessPartnerId"        NVARCHAR(50),    -- OCRD.CardCode
    IN  "BusinessPartnerGroupId"   INTEGER,         -- OCRG.GroupCode
    IN  "PriceListId"              INTEGER,         -- OPLN.ListNum
    IN  "TerritoryId"              INTEGER,         -- OTER.territryID
    IN  "PaymentConditionsId"      INTEGER,         -- OCTG.GroupNum
    IN  "Currency"                 NVARCHAR(3),     -- Moeda de referencia
    IN  "GlobalDiscountPercentage" DECIMAL(19,6),   -- Desconto global (contexto)
    IN  "BaseQuantities"           NCLOB            -- JSON: [{"ItemId":"","Qty":0,"AdditionalDiscountPercentage":0}]
)
LANGUAGE SQLSCRIPT
SQL SECURITY INVOKER
READS SQL DATA
AS
BEGIN
    /* Exemplo funcional: reproduz a busca padrao na lista de precos
       original do B1 (ITM1). Edite a regra a partir deste ponto,
       mantendo as colunas ItemId, ListId, Price e Currency.

       A comparacao por LOCATE concatena virgulas nas duas pontas para
       garantir o match exato do codigo inteiro (evita que 'P10' case
       com 'P100') e funciona em qualquer versao do HANA. Em HANA 2.0
       SPS03 ou superior, uma alternativa com melhor plano de execucao
       e converter a lista em linhas com SPLIT_TO_ARRAY + UNNEST e
       filtrar com IN. */
    SELECT
        T1."ItemCode"                        AS "ItemId",    -- ItemCode do item
        T1."PriceList"                       AS "ListId",    -- ListNum da lista de referencia
        COALESCE(T1."Price", 0)              AS "Price",     -- Preco unitario calculado
        COALESCE(T1."Currency", :Currency)   AS "Currency"   -- Moeda do preco
    FROM ITM1 T1
    WHERE
        T1."PriceList" = :PriceListId
        AND LOCATE(',' || :ItemCodes || ',', ',' || T1."ItemCode" || ',') > 0;
END;

Casca de referência: SQL Server (T-SQL)

CREATE PROCEDURE dbo.VDO_GET_PRICES
    @ItemCodes                NVARCHAR(MAX),   -- ItemCode separados por virgula
    @BranchId                 INT,             -- OBPL.BPLId
    @BusinessPartnerId        NVARCHAR(50),    -- OCRD.CardCode
    @BusinessPartnerGroupId   INT,             -- OCRG.GroupCode
    @PriceListId              INT,             -- OPLN.ListNum
    @TerritoryId              INT,             -- OTER.territryID
    @PaymentConditionsId      INT,             -- OCTG.GroupNum
    @Currency                 NVARCHAR(3),     -- Moeda de referencia
    @GlobalDiscountPercentage DECIMAL(19,6),   -- Desconto global (contexto)
    @BaseQuantities           NVARCHAR(MAX)    -- JSON: [{"ItemId":"","Qty":0,"AdditionalDiscountPercentage":0}]
AS
BEGIN
    SET NOCOUNT ON;

    /* Exemplo funcional: reproduz a busca padrao na lista de precos
       original do B1 (ITM1). Edite a regra a partir deste ponto,
       mantendo as colunas ItemId, ListId, Price e Currency. */
    SELECT
        T1.ItemCode                        AS ItemId,    -- ItemCode do item
        T1.PriceList                       AS ListId,    -- ListNum da lista de referencia
        COALESCE(T1.Price, 0)              AS Price,     -- Preco unitario calculado
        COALESCE(T1.Currency, @Currency)   AS Currency   -- Moeda do preco
    FROM ITM1 T1
    WHERE
        T1.PriceList = @PriceListId
        AND T1.ItemCode IN (SELECT value FROM STRING_SPLIT(@ItemCodes, ','));
END

Exemplo completo: tabela de determinação (T-SQL)

Implementação de referência da determinação descrita nos cases, com fallback para a lista de preços padrão. Adapte nomes, pesos e política de desempate à sua realidade; em HANA a lógica é a mesma em SQLScript.

CREATE PROCEDURE VDO_GET_PRICES
  @ItemCodes                nvarchar(max),
  @BranchId                 int            = NULL,
  @BusinessPartnerId        nvarchar(50)   = NULL,
  @BusinessPartnerGroupId   int            = NULL,
  @PriceListId              int            = NULL,
  @TerritoryId              int            = NULL,
  @PaymentConditionsId      int            = NULL,
  @Currency                 nvarchar(3)    = NULL,
  @GlobalDiscountPercentage decimal(19,6)  = NULL,
  @BaseQuantities           nvarchar(max)  = NULL
AS
BEGIN
  SET NOCOUNT ON;

  -- Itens pesquisados + quantidade e desconto adicional por item
  ;WITH Itens AS (
    SELECT LTRIM(RTRIM(value)) AS ItemCode
    FROM STRING_SPLIT(@ItemCodes, ',')
    WHERE LTRIM(RTRIM(value)) <> ''
  ),
  Qtds AS (
    SELECT ItemId, Qty, AddDisc
    FROM OPENJSON(COALESCE(@BaseQuantities, '[]'))
      WITH (ItemId  nvarchar(50)  '$.ItemId',
            Qty     decimal(19,6) '$.Qty',
            AddDisc decimal(19,6) '$.AdditionalDiscountPercentage')
  ),

  -- Regras de PRECO aplicaveis, pontuadas por especificidade
  Regras AS (
    SELECT i.ItemCode, r.U_Price,
           Score = CASE WHEN r.U_CardCode    IS NOT NULL THEN 32 ELSE 0 END
                 + CASE WHEN r.U_BPLId       IS NOT NULL THEN 16 ELSE 0 END
                 + CASE WHEN r.U_GroupCode   IS NOT NULL THEN  8 ELSE 0 END
                 + CASE WHEN r.U_TerritryID  IS NOT NULL THEN  4 ELSE 0 END
                 + CASE WHEN r.U_PayGroupNum IS NOT NULL THEN  2 ELSE 0 END
                 + CASE WHEN r.U_ListNum     IS NOT NULL THEN  1 ELSE 0 END,
           QtyMin = COALESCE(r.U_QtyMin, 0)
    FROM Itens i
    LEFT JOIN Qtds q ON q.ItemId = i.ItemCode
    JOIN [@VDL_PRECOS] r
      ON (r.U_ItemCode    IS NULL OR r.U_ItemCode    = i.ItemCode)
     AND (r.U_BPLId       IS NULL OR r.U_BPLId       = @BranchId)
     AND (r.U_CardCode    IS NULL OR r.U_CardCode    = @BusinessPartnerId)
     AND (r.U_GroupCode   IS NULL OR r.U_GroupCode   = @BusinessPartnerGroupId)
     AND (r.U_TerritryID  IS NULL OR r.U_TerritryID  = @TerritoryId)
     AND (r.U_PayGroupNum IS NULL OR r.U_PayGroupNum = @PaymentConditionsId)
     AND (r.U_ListNum     IS NULL OR r.U_ListNum     = @PriceListId)
     AND (r.U_Currency    IS NULL OR r.U_Currency    = @Currency)
     AND (r.U_QtyMin      IS NULL OR r.U_QtyMin     <= COALESCE(q.Qty, 0))
     AND (r.U_ValidFrom   IS NULL OR r.U_ValidFrom  <= CAST(GETDATE() AS date))
     AND (r.U_ValidTo     IS NULL OR r.U_ValidTo    >= CAST(GETDATE() AS date))
     AND r.U_Price IS NOT NULL
  ),
  Vencedora AS (
    SELECT ItemCode, U_Price,
           rn = ROW_NUMBER() OVER (PARTITION BY ItemCode
                                   ORDER BY Score DESC, QtyMin DESC, U_Price ASC)
    FROM Regras
  ),

  -- Modificadores: regras so de desconto, cumulativas sobre a vencedora
  Modif AS (
    SELECT i.ItemCode,
           Fator = EXP(SUM(LOG(1 - r.U_DiscPct / 100.0)))
    FROM Itens i
    JOIN [@VDL_PRECOS] r
      ON (r.U_ItemCode    IS NULL OR r.U_ItemCode    = i.ItemCode)
     AND (r.U_BPLId       IS NULL OR r.U_BPLId       = @BranchId)
     AND (r.U_CardCode    IS NULL OR r.U_CardCode    = @BusinessPartnerId)
     AND (r.U_GroupCode   IS NULL OR r.U_GroupCode   = @BusinessPartnerGroupId)
     AND (r.U_TerritryID  IS NULL OR r.U_TerritryID  = @TerritoryId)
     AND (r.U_PayGroupNum IS NULL OR r.U_PayGroupNum = @PaymentConditionsId)
     AND (r.U_Currency    IS NULL OR r.U_Currency    = @Currency)
     AND r.U_Price IS NULL AND r.U_DiscPct IS NOT NULL
    GROUP BY i.ItemCode
  ),

  -- Preco resolvido: regra vencedora ou fallback na lista padrao (ITM1)
  Resolvido AS (
    SELECT i.ItemCode,
           Preco = COALESCE(v.U_Price, itm1.Price)
    FROM Itens i
    LEFT JOIN Vencedora v ON v.ItemCode = i.ItemCode AND v.rn = 1
    LEFT JOIN ITM1 itm1   ON itm1.ItemCode = i.ItemCode
                         AND itm1.PriceList = @PriceListId
  )

  -- Retorno final: preco base + modificadores da propria tabela.
  -- GlobalDiscountPercentage e AdditionalDiscountPercentage NAO sao
  -- reaplicados aqui: o Vendelo aplica sua politica de descontos
  -- depois, sobre o preco retornado.
  SELECT r.ItemCode AS "ItemId",
         CAST(r.Preco * COALESCE(m.Fator, 1) AS decimal(19,6)) AS "Price",
         @Currency                 AS "Currency",
         CAST(@PriceListId AS nvarchar(10)) AS "ListId"
  FROM Resolvido r
  LEFT JOIN Modif m ON m.ItemCode = r.ItemCode
  WHERE r.Preco IS NOT NULL;
END

O que este exemplo garante: uma linha por item com preço na moeda do documento; regra mais específica vencendo; faixas de quantidade; modificadores de desconto da própria tabela; e fallback para a lista de preços padrão quando nenhuma regra se aplica. Os descontos do documento (global e adicional) não são reaplicados: ficam com o Vendelo, que os aplica sobre o preço retornado.

Boas práticas

TemaRecomendação
PerformanceA procedure roda a cada pesquisa de itens. Mantenha-a leve: índices nos campos de filtro da UDT (item, cliente, grupo), sem cursores e sem gravações.
Somente leituraA procedure deve apenas consultar dados. Não faça INSERT/UPDATE/DELETE nem chamadas externas dentro dela.
FallbackSempre devolva um preço para todo item recebido; em último caso, o da lista padrão. Item sem linha fica sem preço na tela.
NulosTodo parâmetro de contexto pode chegar NULL (documento ainda incompleto). Trate NULL como “sem restrição” e garanta um preço mesmo assim.
MoedaDevolva o preço na moeda recebida em Currency. O Vendelo não converte câmbio sobre o valor retornado.
DescontosNão reaplique na procedure os descontos que o Vendelo aplica depois (política de descontos, desconto global, desconto adicional): o desconto sairia em dobro.
DeterminismoDefina desempate explícito entre regras (pesos e ordem). Duas execuções com o mesmo contexto devem devolver o mesmo preço.
GovernançaVersione o script da procedure e controle quem altera a UDT de preços: ela passa a ser a fonte oficial da política comercial.
HomologaçãoValide primeiro no banco de testes: execute a procedure direto no SSMS ou no HANA Studio com contextos reais antes de ativar em produção.

Validação e diagnóstico

Roteiro de teste

  1. Teste a procedure no banco. Execute-a diretamente, simulando um contexto real (cliente, filial, quantidades), e confira o resultado: colunas ItemId, Price e Currency, com uma linha por item.

  2. Configure e reinicie o serviço de integração com o PriceProcedureName apontando para a procedure criada.

  3. Valide no Vendelo. Com o recurso ativo, abra um pedido de venda, selecione um cliente com regra específica e pesquise um item coberto pela tabela: o preço exibido deve ser o da regra, não o da lista padrão.

  4. Varie o contexto. Altere quantidade, condição de pagamento e cliente, repesquisando a cada mudança, e confirme que o preço acompanha as regras cadastradas.

  5. Confirme o fallback. Pesquise um item sem regra na tabela e verifique que ele recebe o preço da lista padrão.

Problemas comuns

SintomaCausa provável
Erro “PriceProcedureName não configurada”Recurso ativo no Vendelo, mas nome da procedure ausente na conexão da empresa no serviço de integração.
Erro “PriceProcedureName inválida”Nome com caracteres não permitidos; use apenas identificador simples, com schema opcional.
Itens sem preço na pesquisaProcedure não devolveu linha para o item (falta fallback) ou nome das colunas diferente do contrato.
Preço não muda com o contextoParâmetro chegando NULL (campo ainda não preenchido no documento) ou regra sem correspondência exata de código SAP.
Erro interno ao pesquisar itensFalha de execução da procedure (sintaxe, permissão, timeout). Teste-a isoladamente no banco com os mesmos parâmetros.
Preços continuam vindo da lista padrãoOpção UsePriceProcedure não ativada na configuração da empresa no Vendelo.

Perguntas frequentes

Preciso mudar alguma coisa nas telas do Vendelo para usar o recurso?

Não. A experiência do vendedor não muda: a pesquisa de itens e as linhas do documento continuam iguais; apenas a origem do preço passa a ser a sua procedure.

O que acontece se a procedure não devolver preço para um item?

O item aparece sem preço na pesquisa. Por isso a recomendação é sempre incluir um fallback dentro da procedure, como o preço da lista padrão do SAP (ITM1).

Posso continuar usando as listas de preços do SAP para parte dos itens?

Sim. Basta a procedure devolver, para esses itens, o próprio preço da lista padrão (é exatamente o que o fallback do exemplo faz). A decisão de quais itens seguem regra própria e quais seguem a lista fica dentro da procedure.

Funciona tanto em SQL Server quanto em SAP HANA?

Sim. A diferença está na forma de passagem dos parâmetros: por nome no SQL Server e por posição no HANA, onde os parâmetros devem ser declarados exatamente na ordem do contrato.

Os descontos do Vendelo (grupos, campanhas, desconto adicional) passam pela procedure?

Não. A procedure devolve o preço unitário base do item. Sobre ele, o Vendelo aplica normalmente sua política de descontos: grupos de descontos (campanhas), descontos por período e quantidade, desconto adicional manual do vendedor, desconto padrão manual e vouchers. Como funciona a hierarquia entre eles está no artigo Política de Descontos no Vendelo.

Sou obrigado a usar uma tabela de usuário (UDT)?

Não. A UDT do case é uma sugestão de organização. A procedure pode consultar qualquer fonte dentro do banco do SAP: tabelas padrão, outras UDTs, visões ou combinações delas.

Como testo sem afetar o ambiente de produção?

Crie a procedure primeiro no banco de testes e execute-a diretamente no SSMS ou no HANA Studio com contextos reais. Só depois configure o serviço de integração do ambiente produtivo e solicite a ativação do recurso.

Se o vendedor mudar a quantidade ou a condição de pagamento, o preço atualiza?

Sim: ao repesquisar itens ou recarregar os preços do documento, o Vendelo envia o novo contexto para a procedure e exibe o preço recalculado.

A procedure pode gravar dados ou chamar outros sistemas?

Não é recomendado. Ela roda a cada pesquisa de itens e deve ser somente leitura, rápida e determinística. Gravações e chamadas externas trazem lentidão e efeitos colaterais difíceis de diagnosticar.

Quem ativa o recurso e o que preciso fornecer?

A criação da procedure e a configuração do serviço de integração são feitas pelo consultor no ambiente do SAP. A ativação da opção UsePriceProcedure na configuração da empresa é feita pela equipe de suporte do Vendelo; informe a empresa e o nome da procedure configurada.

Em um ambiente com várias empresas, todas precisam usar a mesma procedure?

Não. A configuração é por empresa (por banco): cada uma pode ter sua própria procedure, e empresas sem o recurso continuam usando as listas de preços padrão normalmente.

O preço devolvido pela procedure sofre alguma conversão de moeda?

Não. O valor devolvido é usado, sem conversão, como preço unitário na moeda informada na coluna Currency. Se o documento estiver em outra moeda, é responsabilidade da procedure devolver o preço já na moeda solicitada.