Esta es la decimocuarta entrega de la serie sobre utilidades para Business Central. Ya vimos en la Parte 8 cómo calcular el PVP de un artículo desde una lista de precios concreta. En esta ocasión presento una función más completa y precisa: GetUnitPriceAndDiscount, que usa el motor de precios completo de Business Central teniendo en cuenta el cliente, su grupo de precios, grupo de descuentos, contacto primario y la moneda.
¡Vamos manos a la obra! 💶
💰 Obtener precio y descuento reales: GetUnitPriceAndDiscount
procedure GetUnitPriceAndDiscount(CustNo: Code[20]; ItemNo: Code[20]; VariantCode: Code[10]; var UnitPrice: Decimal; var LineDiscount: Decimal)
var
Item: Record Item;
Customer: Record Customer;
Contact: Record Contact;
TempSalesHeader: Record "Sales Header" temporary;
TempSalesLine: Record "Sales Line" temporary;
PriceSourceList: Codeunit "Price Source List";
SalesLinePrice: Codeunit "Sales Line - Price";
PriceCalcMgt: Codeunit "Price Calculation Mgt.";
PriceCalc: Interface "Price Calculation";
Line: Variant;
begin
//----------------------------------------------------------------------
// inicializar Variables
//----------------------------------------------------------------------
Clear(UnitPrice);
Clear(LineDiscount);
Clear(PriceSourceList);
Clear(SalesLinePrice);
Clear(PriceCalcMgt);
Clear(PriceCalc);
if not Item.Get(ItemNo) then
exit;
if not Customer.Get(CustNo) then
exit;
//----------------------------------------------------------------------
// Cabecera temporal
//----------------------------------------------------------------------
TempSalesHeader.Init();
TempSalesHeader."Document Type" := TempSalesHeader."Document Type"::Order;
TempSalesHeader."No." := 'TEMP';
TempSalesHeader."Sell-to Customer No." := CustNo;
TempSalesHeader."Bill-to Customer No." := CustNo;
// Fechas: usar hoy para evitar sorpresas con validez de precios
TempSalesHeader."Posting Date" := Today();
TempSalesHeader."Document Date" := Today();
// Datos del cliente
TempSalesHeader."Currency Code" := Customer."Currency Code";
TempSalesHeader."Price Calculation Method" := Customer."Price Calculation Method";
TempSalesHeader."Location Code" := Customer."Location Code";
TempSalesHeader."Salesperson Code" := Customer."Salesperson Code";
if Customer."Primary Contact No." <> '' then
TempSalesHeader."Sell-to Contact No." := Customer."Primary Contact No.";
//----------------------------------------------------------------------
// Línea temporal
//----------------------------------------------------------------------
TempSalesLine.Init();
TempSalesLine."Document Type" := TempSalesHeader."Document Type";
TempSalesLine."Document No." := TempSalesHeader."No.";
TempSalesLine."Line No." := 10000;
TempSalesLine.Type := TempSalesLine.Type::Item;
TempSalesLine."No." := ItemNo;
TempSalesLine.Quantity := 1;
TempSalesLine."Quantity (Base)" := 1;
TempSalesLine."Outstanding Quantity" := 1;
TempSalesLine."Posting Date" := Today();
TempSalesLine."Variant Code" := VariantCode;
TempSalesLine."Sell-to Customer No." := CustNo;
// Unidad de venta del artículo para evitar conversiones
TempSalesLine."Unit of Measure Code" := Item."Sales Unit of Measure";
TempSalesLine."Qty. per Unit of Measure" := 1;
// Datos del cliente
TempSalesLine."Customer Price Group" := Customer."Customer Price Group";
TempSalesLine."Customer Disc. Group" := Customer."Customer Disc. Group";
TempSalesLine."Allow Line Disc." := Customer."Allow Line Disc.";
TempSalesLine."Location Code" := Customer."Location Code";
//----------------------------------------------------------------------
// Enlazar cabecera + línea con el motor de precios
// (a partir de este punto SalesLinePrice actúa como "contexto")
//----------------------------------------------------------------------
SalesLinePrice.SetLine(Enum::"Price Type"::Sale, TempSalesHeader, TempSalesLine);
//----------------------------------------------------------------------
// Construir PriceSourceList en orden de prioridad
//
// ❖ El motor de precios buscará la 1.ª coincidencia en esta lista.
// ❖ Agregar ordenadamente evita que reglas genéricas sobrescriban
// reglas específicas.
//----------------------------------------------------------------------
PriceSourceList.Init();
// Todos los clientes (Regla super-genérica)
PriceSourceList.Add(Enum::"Price Source Type"::"All Customers");
// Contacto primario — puede tener un precio negociado
if Customer."Primary Contact No." <> '' then
if Contact.Get(Customer."Primary Contact No.") then
PriceSourceList.Add(
Enum::"Price Source Type"::Contact,
Customer."Primary Contact No.");
// Grupo de precios de cliente
if Customer."Customer Price Group" <> '' then
PriceSourceList.Add(
Enum::"Price Source Type"::"Customer Price Group",
Customer."Customer Price Group");
// Grupo de descuentos de cliente
if Customer."Customer Disc. Group" <> '' then
PriceSourceList.Add(
Enum::"Price Source Type"::"Customer Disc. Group",
Customer."Customer Disc. Group");
// Precio específico para el propio cliente (mayor prioridad)
PriceSourceList.Add(Enum::"Price Source Type"::Customer, CustNo);
// Ligar la lista al contexto de precios
SalesLinePrice.SetSources(PriceSourceList);
//----------------------------------------------------------------------
// Obtener el "handler" concreto:
// — BC selecciona internamente el algoritmo apropiado
// (descuentos integrados vs. nuevo Pricing v2) —
//----------------------------------------------------------------------
if not PriceCalcMgt.GetHandler(SalesLinePrice, PriceCalc) then
exit; // No hay motor disponible (caso muy excepcional)
//----------------------------------------------------------------------
// Calcular: primero precio ➜ luego descuento
//----------------------------------------------------------------------
PriceCalc.ApplyPrice(TempSalesLine.FieldNo("No."));
PriceCalc.ApplyDiscount();
// El motor devuelve la línea vía Variant para mantener desacoplamiento
PriceCalc.GetLine(Line);
TempSalesLine := Line;
//----------------------------------------------------------------------
// Copiar resultados a parámetros de salida
//----------------------------------------------------------------------
UnitPrice := TempSalesLine."Unit Price";
LineDiscount := TempSalesLine."Line Discount %";
end;
¿En qué se diferencia de CalcPVP?
La función CalcPVP de la Parte 8 busca el precio en una lista de precios específica. GetUnitPriceAndDiscount es más completa: usa el motor nativo de precios de BC, respetando toda la jerarquía de prioridades del sistema:
- Todos los clientes (regla más genérica)
- Contacto primario del cliente
- Grupo de precios del cliente
- Grupo de descuentos del cliente
- Precio específico para ese cliente (mayor prioridad)
Parámetros
CustNo: Código del cliente.ItemNo: Código del artículo.VariantCode: Variante del artículo (puede estar vacío).UnitPrice: Variable de salida con el precio unitario calculado.LineDiscount: Variable de salida con el descuento de línea en porcentaje.
¿Cómo funciona internamente?
Cabecera y línea temporales
Se crean registros temporales de Sales Header y Sales Line para simular un pedido de venta sin persistirlo en base de datos. Esto es necesario porque el motor de precios de BC trabaja con estos objetos como contexto.
Construcción de la lista de fuentes de precio
Se construye un PriceSourceList en orden de prioridad ascendente. El motor de precios usará la primera coincidencia válida. Al añadir el cliente concreto al final, su precio tiene mayor prioridad que el grupo.
Aplicación de precio y descuento
Se llama a ApplyPrice y ApplyDiscount por separado. Esto garantiza que ambos valores se calculen correctamente siguiendo la lógica estándar de BC, incluyendo descuentos por grupo y reglas de Allow Line Disc.
Extracción del resultado
El resultado se recupera de la línea temporal actualizada, que el motor devuelve vía Variant para mantener el desacoplamiento entre capas.
💡 Aplicaciones prácticas
- Mostrar el precio de venta esperado en una ficha de artículo o cliente.
- Validar precios antes de crear un pedido.
- Calcular importes en procesos automáticos (generación masiva de pedidos, presupuestos automáticos).
- Integrar con sistemas externos que necesitan conocer el precio neto por cliente.
Conclusión
En esta decimocuarta entrega de la serie Utils, muestro cómo obtener el precio y descuento real que BC aplicaría a un cliente para un artículo concreto, usando el motor nativo de precios y respetando toda la jerarquía de reglas configurada. Es la función de precios más completa de la biblioteca.
Para seguir todos los posts de esta serie, puedes encontrarlos bajo la etiqueta #UtilsBc.
Si quieres ver el código completo, está en GitHub.
¡Nos vemos en la próxima!