web
You’re offline. This is a read only version of the page.
close
Skip to main content

Notifications

Announcements

No record found.

Community site session details

Community site session details

Session Id :
Dynamics 365 Community / Blogs / Jesús Almaraz blog / Otro post sobre las interfa...

Otro post sobre las interfaces en AL/BC

Jalmaraz Profile Picture Jalmaraz 669

Acerca de la publicación

Cuando estaba escribiendo este post, leí dos nuevos (y buenos) post sobre interfaces AL. Cuando lo termine, puede que se publiquen otros dos, tres o cuatro. Así que espero que este post aporte algo nuevo. No voy a hablar de interfaces desde un punto de vista OOP, sólo voy a hablar sobre cómo extender BC con interfaces con lo que creo que es un buen ejemplo y comparar con otras formas de modificar la funcionalidad estándar de la aplicación. Y hacerlo en mi español, un idioma en el que creo que no había nada al respecto.

Cuando llegaron las interfaces no les presté la mínima atención. Pero las cosas cambiaron cuando caigo en la cuenta de que tenemos un nuevo método para ampliar la aplicación. Me interesa la aplicación práctica por encima de la teoría.

Un ejemplo

El caso del usuario es el siguiente: haremos un sistema para configurar el cálculo de comisiones en cada ficha de vendedor. Inicialmente queremos implementar dos métodos de cálculo de comisiones, y esos van a ser los que inicialmente se podrán configurar en la ficha del vendedor:

  • En el primer sistema la base de comisiones será el importe base del IVA.
  • En el segundo, la base de comisión será el igualmente el importe base del IVA, pero sólo de las líneas de venta de artículos, no de otros tipos de líneas (cuenta, recurso o cargo).
  • Finalmente para mostrar las comisiones calculadas, generaremos una página nueva de lista de facturas de ventas con el cálculo de la comisión de cada artículo, en una columna de importe de comisión.

Codificación del ejemplo

El primer paso es hacer la interface. Para ello en BC tenemos un nuevo tipo de objeto llamado interface, que es una declaración abstracta de uno o varios métodos sin implementación, es decir sólo nombre, sus parámetros y su valor de retorno:
interface "Commission Calculation"
{
    procedure GetInvCommission(SalesInvoiceHeader: Record "Sales Invoice Header"ComisionAmount: Decimal;
    procedure LookupCommission(SalesInvoiceHeader: Record "Sales Invoice Header")
}

Una vez que tenemos la interface, que es el método abstracto, podemos declarar distintas Codeunits para implementar esta interface. Para ello ponemos la cláusula implements después del nombre Codeunit:


codeunit 69007 "Commission Vat Base" implements "Commission Calculation"

Y dentro de la Codeunit, definimos la implementación de los métodos de la interface.

Debemos hacer la implementación de todos los métodos de la interface con el mismo nombre, parámetros y variable de retorno, si no el compilador nos dará un error:
    procedure GetInvCommission(SalesInvoiceHeader: Record "Sales Invoice Header")Decimal;
    begin
        if not Salesperson.get(SalesInvoiceHeader."Salesperson Code"then
            exit;
        with SalesInvoiceLine do begin
            SetRange("Document No.", SalesInvoiceHeader."No.");
            CalcSums(Amount);
            BaseCommission :Amount;
            exit(Round(BaseCommission * Salesperson."Commission %" / 100));
        end;
    end;
Hacemos la otra implantación de la misma interface, la que toma como base solo las líneas de productos (no la desarrollaré entera aquí, para no alargarme):
codeunit 69008 "Commission Items Only" implements "Commission Calculation"
{
    procedure GetInvCommission(SalesInvoiceHeader: Record "Sales Invoice Header")Decimal;

La conexión Enum

La última pieza de este puzle es un poco extraña, una conexión entre interfaces y objetos de tipo Enum:

Definimos un objeto de tipo Enum y declaramos que va a ser una implementación de la interface de cálculo de comisión:
enum 69000 "Calc. Commission Method" implements "Commission Calculation"

Y en cada valor del enum se puede asignar una implementación de la interface:

    value(0; Default)
    {
        Implementation = "Commission Calculation" = "Commission Vat Base";
    }
 

Es importante pararse en el significado de la segunda línea de código. Su estructura es esta: Implementation = <Interface> = <Implementation Codeunit>

De esta forma podemos definir un nuevo campo en una tabla con esta enumeración, y así configurar una implementación diferente por registro en la tabla de vendedores:
tableextension 69000 "COM Salesperson" extends "Salesperson/Purchaser"
………….
        field(69000; "Calc. Commission Method"; enum "Calc. Commission Method")
A la hora de hacer el cálculo de la comisión de una factura, podemos hacer un GET del vendedor, obtener su método de cálculo de comisión y asignarlo directamente a la interface:
        ICommissionCalculation: Interface "Commission Calculation";
        SalespersonPurchaserRecord "Salesperson/Purchaser";
    Begin
……….
        ICommissionCalculation :SalespersonPurchaser."Calc. Commission Method";
        exit(ICommissionCalculation.GetInvCommission(Rec));

Y esto es suficiente para establecer el método.

Recapitulando: Asigno Interface=<Valor Enum> antes de calcular. Ese valor del Enum le dice a la interface que Codeunit va a implementarla, porque lo hemos establecido en la definición del valor del Enum. Con este código, si el valor del enum es defecto o base IVA, coge la primera Codeunit para implementar la Interface. Si en la ficha de vendedor hemos configurado la opción “Sólo productos”, cogerá la segunda Codeunit que hemos creado para implementar el código. Sin duda para mí fue la parte más extraña, asociar una Codeunit a una Interface para implementarla.

Ampliación de funcionalidad desde otra App

Esto adquiere más sentido si llega una tercera app y quiere extender la funcionalidad del cálculo de comisiones. Los pasos para extender desde otra aplicación serían estos:

Declarar la dependencia en el app.json de la App de cálculo de comisiones y bajar símbolos después. El objetivo de esta nueva extensión va a ser poder configurar e implementar en los vendedores un nuevo método de comisión en el que la base de la comisión será Importe-Coste, es decir la base será el beneficio de las líneas de factura.

Creamos una nueva Codeunit para implementar la interface con base coste:
codeunit 69009 "Commission Base Profit" implements "Commission Calculation"
{
    procedure GetInvCommission(SalesInvoiceHeader: Record "Sales Invoice Header")Decimal;
    var
        SalesInvoiceLineRecord "Sales Invoice Line";
        Salesperson: Record "Salesperson/Purchaser";
        BaseCommissionDecimal;
        CostAmountdecimal;
    begin
        if not Salesperson.get(SalesInvoiceHeader."Salesperson Code"then
            exit;
        with SalesInvoiceLine do begin
            SetRange("Document No.", SalesInvoiceHeader."No.");
            if not FindSet() then
                exit;
            repeat
                CostAmount := "Unit Cost" * Quantity;
                BaseCommission := BaseCommission + (Amount - CostAmount)
            until next = 0;
            exit(Round(BaseCommission * Salesperson."Commission %" / 100));
        end;
    end;
 
Y en segundo paso, extendemos el enum de método de cálculo de comisión para esta opción:
enumextension 69000 "Commission over profit" extends "Calc. Commission Method"
{
    value(69009; "Profit based")
    {
        Implementation = "Commission Calculation" = "Commission Base Profit";
    }
}
 

Y de esta forma ya nos permite en la ficha de vendedor asignar este nuevo valor de método de cálculo:

2110.Interface.jpg

Es importante saber como extender una interface, porque puede que tengamos que llevarla a cabo para nuestros propios métodos de cálculo de precios de venta.

Opinión: ¿Una nueva idea en el mundo NAV/BC? Un gran paso en OOP, un pequeño paso en la extensibilidad

Aunque me parece una gran aportación respecto a organización de código, no me lo parece tanto respecto a extender la aplicación.

Veamos que teníamos hasta ahora para modificar el funcionamiento de la aplicación:

  • Eventos. Ideal para el colectivo de vagos al que pertenezco. La verdad es que no quiero casi nunca hacer todo el código de un proceso para sustituir al existente, prefiero la suscripción, aprovechar lo que hay y hacer pequeños cambios en el resultado. Además antes de las interfaces si se podían hacer sustituciones completas de la funcionalidad con eventos gracias al patrón Handled. Pienso que NAV nos da casi siempre la respuesta correcta. Digo casi siempre, exceptuando la idea de que el mejor precio de venta es el más barato en vez del más específico (cliente mejor que tarifa). Esto es lo que puede haber provocado que este sistema de interfaces lo primero para lo que se ha usado es para los precios de venta en el estándar.
  • Configurar Codeunits, como en el SEPA. En los bancos se configuraba un número de Codeunit para implementar el manejo de la exportación del formato bancario. Para mi eso era Interface versión Beta. No es mala idea y a veces la he utilizado.

De todas formas conviene como mínimo saber defenderse con los interfaces, ya que va a ser la principal forma de extender el cálculo de los precios de venta y vendrán más cosas después.

Todo el código en los repositorios GIT:

https://github.com/JalmarazMartn/CommissionInterface

https://github.com/JalmarazMartn/CommissionInterfaceExtension

Comments

*This post is locked for comments