Optimización de Disponibilidad de Artículos por Lote en Business Central

Aprende cómo gestionar eficientemente la disponibilidad de artículos por lote en Microsoft Dynamics 365 Business Central utilizando AL, con un enfoque detallado en filtros, periodos y cálculos específicos.

En proyectos de Business Central, gestionar la disponibilidad de artículos por lote puede ser crucial para muchas empresas. Este código en AL demuestra cómo se puede estructurar y ejecutar un proceso para verificar y calcular la disponibilidad de artículos específicos por lotes, considerando varios filtros y condiciones. Este desarrollo es especialmente útil para aquellos negocios que manejan inventarios con lotes específicos, ya que permite obtener una visión detallada de la disponibilidad y planificar mejor la producción y distribución.

El código está dividido en varias secciones, cada una encargada de una tarea específica dentro del proceso de cálculo de disponibilidad. Desde la identificación del período relevante hasta la construcción de listas de lotes y la generación de resultados en formato JSON, este codeunit proporciona una solución integral para la gestión de inventarios por lotes.

¡Vamos manos a la obra! 🫡

Procedimiento Principal: EjecuteProcess

procedure EjecuteProcess(var NewItem: Record Item)
begin
    FindPeriod(NewItem);

    Item.Copy(NewItem);
    BuildLotNoList(TempAvailabilityInfoBuffer, Item."No.");

    if Item.GetFilter("Location Filter") <> '' then
        TempAvailabilityInfoBuffer.SetRange("Location Code Filter", Item.GetFilter("Location Filter"));

    if Item.GetFilter("Variant Filter") <> '' then
        TempAvailabilityInfoBuffer.SetRange("Variant Code Filter", Item.GetFilter("Variant Filter"));

    TempAvailabilityInfoBuffer.SetRange("Date Filter", 0D, Item.GetRangeMax("Date Filter"));
end;

El procedimiento EjecuteProcess es el punto de entrada principal para el cálculo de disponibilidad. Este procedimiento realiza los siguientes pasos:

  1. Determinar el Período: Llama a FindPeriod para ajustar el rango de fechas del artículo.
  2. Copiar Datos del Artículo: Utiliza Item.Copy(NewItem) para duplicar la información del artículo en cuestión.
  3. Construir Lista de Números de Lote: Llama a BuildLotNoList para generar una lista de lotes disponibles para el artículo.
  4. Aplicar Filtros de Ubicación y Variante: Ajusta los filtros de ubicación y variante en TempAvailabilityInfoBuffer si estos están definidos en el artículo.
  5. Establecer Rango de Fechas: Finalmente, establece el rango de fechas para la consulta de disponibilidad.

Encontrar el Período: FindPeriod

local procedure FindPeriod(var FindPeriodItem: Record Item)
var
    CalendarDate: Record Date;
    PeriodPageMgt: Codeunit PeriodPageManagement;
    PeriodType: Enum "Analysis Period Type";
begin
    PeriodType := PeriodType::Day;

    if FindPeriodItem.GetFilter("Date Filter") <> '' then begin
        CalendarDate.SetFilter("Period Start", FindPeriodItem.GetFilter("Date Filter"));
        if not PeriodPageMgt.FindDate('+', CalendarDate, PeriodType) then
            PeriodPageMgt.FindDate('+', CalendarDate, PeriodType::Day);
        CalendarDate.SetRange("Period Start");
    end;

    PeriodPageMgt.FindDate('', CalendarDate, PeriodType);

    FindPeriodItem.SetRange("Date Filter", 0D, CalendarDate."Period End");
end;

En FindPeriod, se determina el rango de fechas aplicable utilizando filtros preexistentes en el artículo. Este procedimiento es esencial para limitar la consulta de disponibilidad a un período relevante. Utiliza la PeriodPageManagement para buscar y establecer las fechas correctas, asegurando que las operaciones posteriores solo consideren datos dentro del período especificado.

Construcción de la Lista de Lotes: BuildLotNoList

local procedure BuildLotNoList(var AvailabilityInfoBuffer: Record "Availability Info. Buffer"; ItemNo: Code[20])
var
    ItemByLotNoRes: Query "Item By Lot No. Res.";
    ItemByLotNoItemLedg: Query "Item By Lot No. Item Ledg.";
    LotDictionary: Dictionary of [Code[50], Text];
begin
    Clear(AvailabilityInfoBuffer);
    AvailabilityInfoBuffer.DeleteAll();

    ItemByLotNoItemLedg.SetRange(Item_No, ItemNo);
    ItemByLotNoItemLedg.SetFilter(Variant_Code, Item.GetFilter("Variant Filter"));
    ItemByLotNoItemLedg.SetFilter(Location_Code, Item.GetFilter("Location Filter"));
    ItemByLotNoItemLedg.Open();
    while ItemByLotNoItemLedg.Read() do
        if ItemByLotNoItemLedg.Lot_No <> '' then
            if not LotDictionary.ContainsKey(ItemByLotNoItemLedg.Lot_No) then begin
                LotDictionary.Add(ItemByLotNoItemLedg.Lot_No, '');
                AvailabilityInfoBuffer.Init();
                AvailabilityInfoBuffer."Item No." := Item."No.";
                AvailabilityInfoBuffer."Lot No." := ItemByLotNoItemLedg.Lot_No;
                AvailabilityInfoBuffer."Expiration Date" := ItemByLotNoItemLedg.Expiration_Date;
                AvailabilityInfoBuffer.Insert();
            end;

    // Expected Receipt Date for positive reservation entries.
    ItemByLotNoRes.SetRange(Item_No, ItemNo);
    ItemByLotNoRes.SetFilter(Quantity__Base_, '>0');
    ItemByLotNoRes.SetFilter(Expected_Receipt_Date, Item.GetFilter("Date Filter"));
    ItemByLotNoRes.SetFilter(Variant_Code, Item.GetFilter("Variant Filter"));
    ItemByLotNoRes.SetFilter(Location_Code, Item.GetFilter("Location Filter"));
    ItemByLotNoRes.Open();
    AddReservationEntryLotNos(AvailabilityInfoBuffer, ItemByLotNoRes, LotDictionary);

    // Shipment date for negative reservation entries.
    ItemByLotNoRes.SetRange(Item_No, ItemNo);
    ItemByLotNoRes.SetFilter(Quantity__Base_, '<0');
    ItemByLotNoRes.SetFilter(Expected_Receipt_Date, '');
    ItemByLotNoRes.SetFilter(Shipment_Date, Item.GetFilter("Date Filter"));
    ItemByLotNoRes.SetFilter(Variant_Code, Item.GetFilter("Variant Filter"));
    ItemByLotNoRes.SetFilter(Location_Code, Item.GetFilter("Location Filter"));
    AddReservationEntryLotNos(AvailabilityInfoBuffer, ItemByLotNoRes, LotDictionary);
end;

BuildLotNoList recopila y organiza información sobre los lotes de artículos. Este procedimiento:

  1. Inicializa y Limpia el Buffer: Asegura que AvailabilityInfoBuffer esté limpio antes de comenzar.
  2. Configura Filtros y Rango: Establece rangos y filtros basados en el número de artículo, variante y ubicación.
  3. Consulta Movimientos de Inventario: Utiliza dos consultas (ItemByLotNoItemLedg y ItemByLotNoRes) para obtener información sobre los movimientos de inventario y reservas de artículos por lote.
  4. Añade Lotes al Diccionario y Buffer: Agrega números de lote únicos al diccionario y buffer de disponibilidad, evitando duplicados.

Añadir Lotes de Entradas de Reserva: AddReservationEntryLotNos

local procedure AddReservationEntryLotNos(
    var AvailabilityInfoBuffer: Record "Availability Info. Buffer";
    var ItemByLotNoRes: Query "Item By Lot No. Res.";
    var LotDictionary: Dictionary of [Code[50], Text]
)
begin
    ItemByLotNoRes.Open();
    while ItemByLotNoRes.Read() do
        if ItemByLotNoRes.Lot_No <> '' then
            if not LotDictionary.ContainsKey(ItemByLotNoRes.Lot_No) then begin
                LotDictionary.Add(ItemByLotNoRes.Lot_No, '');
                AvailabilityInfoBuffer.Init();
                AvailabilityInfoBuffer."Item No." := Item."No.";
                AvailabilityInfoBuffer."Lot No." := ItemByLotNoRes.Lot_No;
                AvailabilityInfoBuffer."Expiration Date" := ItemByLotNoRes.Expiration_Date;
                AvailabilityInfoBuffer.Insert();
            end;
end;

Este procedimiento complementa BuildLotNoList añadiendo lotes de entradas de reserva al buffer de disponibilidad. Se asegura de no duplicar números de lote ya presentes en el diccionario, manteniendo la integridad de los datos de disponibilidad.

Cálculo de Disponibilidad: Calculate

local procedure Calculate()
var
    IsHandled: Boolean;
begin
    TempAvailabilityInfoBuffer.SetRange("Lot No. Filter", TempAvailabilityInfoBuffer."Lot No.");

    if not IsHandled then
        TempAvailabilityInfoBuffer.CalcFields(
            Inventory,
            "Qty. on Sales Order",
            "Qty. on Service Order",
            "Qty. on Job Order",
            "Qty. on Component Lines",
            "Qty. on Trans. Order Shipment",
            "Qty. on Asm. Component",
            "Qty. on Purch. Return",
            "Planned Order Receipt (Qty.)",
            "Purch. Req. Receipt (Qty.)",
            "Qty. on Purch. Order",
            "Qty. on Prod. Receipt",
            "Qty. on Trans. Order Receipt",
            "Qty. on Assembly Order",
            "Qty. on Sales Return"
        );

    GrossRequirement :=
        TempAvailabilityInfoBuffer."Qty. on Sales Order" +
        TempAvailabilityInfoBuffer."Qty. on Service Order" +
        TempAvailabilityInfoBuffer."Qty. on Job Order" +
        TempAvailabilityInfoBuffer."Qty. on Component Lines" +


 TempAvailabilityInfoBuffer."Qty. on Trans. Order Shipment" +
        TempAvailabilityInfoBuffer."Qty. on Asm. Component" +
        TempAvailabilityInfoBuffer."Qty. on Purch. Return";

    PlannedOrderRcpt :=
        TempAvailabilityInfoBuffer."Planned Order Receipt (Qty.)" +
        TempAvailabilityInfoBuffer."Purch. Req. Receipt (Qty.)";

    ScheduledRcpt :=
        TempAvailabilityInfoBuffer."Qty. on Prod. Receipt" +
        TempAvailabilityInfoBuffer."Qty. on Purch. Order" +
        TempAvailabilityInfoBuffer."Qty. on Trans. Order Receipt" +
        TempAvailabilityInfoBuffer."Qty. on Assembly Order" +
        TempAvailabilityInfoBuffer."Qty. on Sales Return";

    TempAvailabilityInfoBuffer."Qty. In Hand" := TempAvailabilityInfoBuffer.Inventory;
    TempAvailabilityInfoBuffer."Gross Requirement" := GrossRequirement;
    TempAvailabilityInfoBuffer."Planned Order Receipt" := PlannedOrderRcpt;
    TempAvailabilityInfoBuffer."Scheduled Receipt" := ScheduledRcpt;
    TempAvailabilityInfoBuffer."Available Inventory" := TempAvailabilityInfoBuffer.Inventory + PlannedOrderRcpt + ScheduledRcpt - GrossRequirement;
end;

La función Calculate es responsable de calcular la disponibilidad real del inventario considerando varias cantidades y reservas. Este procedimiento:

  1. Configura el Filtro de Lote: Aplica un filtro de número de lote en el buffer temporal.
  2. Calcula Campos de Inventario: Utiliza CalcFields para actualizar los campos necesarios para el cálculo de disponibilidad.
  3. Calcula Requerimientos y Recepciones: Suma y resta cantidades de órdenes de venta, servicio, producción, compras y devoluciones para determinar la disponibilidad neta.

Obtener JSON: GetJson

procedure GetJson() ReturnValue: Text
var
    JsonObject: JsonObject;
    JsonArray: JsonArray;
begin
    Clear(ReturnValue);
    Clear(JsonArray);

    if TempAvailabilityInfoBuffer.FindSet() then
        repeat

            Calculate();

            Clear(JsonObject);
            JsonObject.Add('itemNo', Item."No.");
            JsonObject.Add('lotNo', TempAvailabilityInfoBuffer."Lot No.");
            JsonObject.Add('qtyAvailable', TempAvailabilityInfoBuffer."Available Inventory");

            JsonArray.Add(JsonObject);

        until TempAvailabilityInfoBuffer.Next() = 0;

    JsonArray.WriteTo(ReturnValue);
end;

GetJson convierte los datos de disponibilidad en un formato JSON, facilitando su uso en integraciones con otros sistemas o aplicaciones. Recorre el buffer temporal, calcula la disponibilidad y agrega los resultados a un array JSON que se devuelve como texto.

Obtener Registro: GetRecord

procedure GetRecord(var TempGETAvailabilityInfoBuffer: Record "Availability Info. Buffer" temporary)
begin
    TempGETAvailabilityInfoBuffer.Reset();
    TempGETAvailabilityInfoBuffer.DeleteAll();

    if TempAvailabilityInfoBuffer.FindSet() then
        repeat
            TempGETAvailabilityInfoBuffer.transferfield(TempAvailabilityInfoBuffer);
            TempGETAvailabilityInfoBuffer.Insert();
        until TempAvailabilityInfoBuffer.Next() = 0;
end;

Este procedimiento transfiere los datos del buffer temporal de disponibilidad a otro buffer temporal, permitiendo su reutilización o consulta posterior.

Ejemplo de Uso: Ejemplo

procedure Ejemplo()
var
    OnRunItem: Record Item;
    MgtItemAvailabilityByLotNo: Codeunit "MgtItemAvailabilityByLotNo";
begin
    OnRunItem.SetRange("No.", '1896-S');
    OnRunItem.SetFilter("Location Filter", 'PRINCIPAL');
    OnRunItem.SetFilter("Date Filter", '''..%2', Today());

    Clear(MgtItemAvailabilityByLotNo);
    MgtItemAvailabilityByLotNo.EjecuteProcess(OnRunItem);
    Message(MgtItemAvailabilityByLotNo.GetJson());
end;

Este es un ejemplo práctico de cómo utilizar la codeunit MgtItemAvailabilityByLotNo. Configura rangos y filtros específicos para un artículo, ejecuta el proceso de disponibilidad y muestra los resultados en formato JSON.

Beneficios y Aplicaciones Prácticas

Este código permite una gestión precisa de la disponibilidad de artículos por lotes, ayudando a mantener un control riguroso sobre el inventario. Las empresas pueden beneficiarse al tener información exacta y en tiempo real sobre sus productos, mejorando la planificación y evitando rupturas de stock.

Beneficios Directos:

  • Precisión en la Gestión de Inventarios: Mejora la precisión al conocer exactamente cuántos artículos están disponibles por lote.
  • Optimización de Procesos: Facilita la planificación de la producción y distribución, evitando sobrestock y faltantes.
  • Toma de Decisiones Informada: Proporciona datos exactos para decisiones rápidas y efectivas.

Aplicaciones Prácticas:

  • Fabricación: Control de materias primas y productos terminados por lote.
  • Distribución: Gestión de inventarios en múltiples ubicaciones.
  • Comercio: Manejo de productos perecederos y control de fechas de caducidad.

Conclusión

Este post ha desglosado un completo codeunit en AL para la gestión de disponibilidad de artículos por lote en Business Central. La implementación de este código en entornos reales puede optimizar significativamente la gestión del inventario.

Si quieres ver el código completo, está en GitHub.

¡Hasta la próxima!

Share your love

Leave a Reply

Your email address will not be published. Required fields are marked *