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:
- Determinar el Período: Llama a
FindPeriod
para ajustar el rango de fechas del artículo. - Copiar Datos del Artículo: Utiliza
Item.Copy(NewItem)
para duplicar la información del artículo en cuestión. - Construir Lista de Números de Lote: Llama a
BuildLotNoList
para generar una lista de lotes disponibles para el artículo. - 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. - 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:
- Inicializa y Limpia el Buffer: Asegura que
AvailabilityInfoBuffer
esté limpio antes de comenzar. - Configura Filtros y Rango: Establece rangos y filtros basados en el número de artículo, variante y ubicación.
- Consulta Movimientos de Inventario: Utiliza dos consultas (
ItemByLotNoItemLedg
yItemByLotNoRes
) para obtener información sobre los movimientos de inventario y reservas de artículos por lote. - 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:
- Configura el Filtro de Lote: Aplica un filtro de número de lote en el buffer temporal.
- Calcula Campos de Inventario: Utiliza
CalcFields
para actualizar los campos necesarios para el cálculo de disponibilidad. - 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!