Implementierung eines benutzerdefinierten, automatischen Bestellabgleichs
Einführung
Der automatische Bestellabgleich ist ein wichtiger Bestandteil der Dokumentenverarbeitung in Squeeze for Business Central. Im Standard vergleicht das System eingehende Belege mit vorhandenen Bestellungen und Wareneingängen. Diese Dokumentation zeigt Ihnen, wie Sie diesen Abgleichsprozess um Ihre eigenen Belegarten erweitern können.
Die Standardimplementierung verwendet einen dreistufigen Prozess:
- Sammeln relevanter Belegnummern basierend auf Geschäftsregeln
- Für jede gefundene Belegnummer wird ein detaillierter Abgleich durchgeführt
- Im Anschluss werden die von Squeeze ausgelesenen Positionen um die gefundenen Daten angereichert
Diesen bewährten Ansatz werden wir in der folgenden, beispielhaften Implementierung beibehalten.
Integrationspunkt
Der zentrale Integrationspunkt ist das OnBeforePerformAutomaticOrdermatch
-Event in Codeunit 70954632 "DXP SQZ Document Mgt.". Dieses Event wird nach der Erstellung von Kopf- und Positionsdaten aufgerufen.
[IntegrationEvent(false, false)]
local procedure OnBeforePerformAutomaticOrdermatch(
DocHeader: Record "DXP SQZ Document Header";
var OrderNoList: List of [Code[20]];
var IsHandled: Boolean)
Parameter
DocHeader
: Der Dokumentenkopf mit den zu vergleichenden DatenOrderNoList
: Eine Liste von Belegnummern für den AbgleichIsHandled
: Steuert, ob die Standardverarbeitung übersprungen werden soll
Implementierungsbeispiel
Technische Umsetzung
1. Erweiterung des Dokumententyp-Enums
Zunächst erweitern wir die möglichen Dokumententypen um unseren eigenen Typ:
enumextension 50100 "Custom Order Match Doc. Type" extends "DXP Order Match Document Type"
{
value(50000; "Custom")
{
Caption = 'Custom Document';
}
}
2. Implementierung der Abgleichslogik
Der zentrale Punkt unserer Implementierung ist eine Codeunit, die den Abgleichsprozess steuert. Integrationspunkt ist :
//codeunit 50100 "Custom Document Matching"
//{
[EventSubscriber(ObjectType::Codeunit, Codeunit::"DXP SQZ Document Mgt.", 'OnBeforePerformAutomaticOrdermatch', '', false, false)]
local procedure OnBeforePerformAutomaticOrdermatch(
DocHeader: Record "DXP SQZ Document Header";
var OrderNoList: List of [Code[20]];
var IsHandled: Boolean)
var
CustomSourceDoc: Record "Custom Source Document";
CustomDocNo: Code[20];
begin
// Take control of the matching process and prevent standard processing
IsHandled := true;
// Find potential matching documents
CustomSourceDoc.SetRange("Vendor No.", DocHeader."Buy-from Vendor No.");
// Add your specific document status or type filters
CustomSourceDoc.SetRange("Document Type", CustomSourceDoc."Document Type"::Order);
CustomSourceDoc.SetRange(Status, CustomSourceDoc.Status::Released);
// Add matching document numbers to the list
if CustomSourceDoc.FindSet() then
repeat
CustomDocNo := CustomSourceDoc."No.";
if IsDocumentEligibleForMatching(CustomSourceDoc, DocHeader) then
if not OrderNoList.Contains(CustomDocNo) then
OrderNoList.Add(CustomDocNo);
until CustomSourceDoc.Next() = 0;
// Process all collected documents
ProcessMatchingDocuments(DocHeader, OrderNoList);
end;
local procedure IsDocumentEligibleForMatching(
CustomSourceDoc: Record "Custom Source Document";
DocHeader: Record "DXP SQZ Document Header"): Boolean
begin
// Implement business rules for document selection
// For example:
if CustomSourceDoc."Document Date" > DocHeader."Document Date" then
exit(false);
if CustomSourceDoc."Currency Code" <> DocHeader."Currency Code" then
exit(false);
// Check for open lines that can be matched
if not HasOpenLinesToMatch(CustomSourceDoc) then
exit(false);
exit(true);
end;
local procedure HasOpenLinesToMatch(CustomSourceDoc: Record "Custom Source Document"): Boolean
var
CustomSourceLine: Record "Custom Source Line";
begin
CustomSourceLine.SetRange("Document No.", CustomSourceDoc."No.");
CustomSourceLine.SetFilter("Outstanding Quantity", '>0');
exit(not CustomSourceLine.IsEmpty());
end;
//}
3. Verarbeitung der gefundenen Belege
Nach dem Sammeln der relevanten Belegnummern erfolgt der eigentliche Abgleich:
local procedure ProcessMatchingDocuments(
DocHeader: Record "DXP SQZ Document Header";
OrderNoList: List of [Code[20]])
var
TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
OrderNo: Code[20];
begin
foreach OrderNo in OrderNoList do begin
// Get matching entries for current document
GetMatchEntries(OrderNo, TempMatchEntry, DocHeader);
if not TempMatchEntry.IsEmpty() then
// Try to find and assign matching lines
TryMatchDocumentLines(TempMatchEntry, DocHeader);
end;
end;
local procedure GetMatchEntries(
DocumentNo: Code[20];
var TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
DocHeader: Record "DXP SQZ Document Header")
var
CustomSourceLine: Record "Custom Source Line";
begin
TempMatchEntry.Reset();
TempMatchEntry.DeleteAll();
// Get lines from custom document
CustomSourceLine.SetRange("Document No.", DocumentNo);
if CustomSourceLine.FindSet() then
repeat
// Create match entry for each relevant line
CreateMatchEntry(CustomSourceLine, TempMatchEntry);
until CustomSourceLine.Next() = 0;
end;
local procedure CreateMatchEntry(
CustomSourceLine: Record "Custom Source Line";
var TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary)
begin
TempMatchEntry.Init();
TempMatchEntry."Document Type" := "DXP Order Match Document Type"::Custom;
TempMatchEntry."Document No." := CustomSourceLine."Document No.";
TempMatchEntry."Document Line No." := CustomSourceLine."Line No.";
TempMatchEntry."No." := CustomSourceLine."Item No.";
TempMatchEntry.Quantity := CustomSourceLine.Quantity;
TempMatchEntry."Direct Unit Cost" := CustomSourceLine."Unit Price";
TempMatchEntry."Line Amount" := CustomSourceLine.Amount;
TempMatchEntry.Insert();
end;
Die Matching-Logik im Detail
Der eigentliche Abgleich der Belegzeilen erfolgt nach definierten Geschäftsregeln:
local procedure TryMatchDocumentLines(
var TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
DocHeader: Record "DXP SQZ Document Header")
var
DocLine: Record "DXP SQZ Document Line";
MatchSetup: Record "Custom Match Setup";
HasCustomSetup: Boolean;
begin
// Get configuration
MatchSetup.Get();
// Process each potential match entry
if TempMatchEntry.FindSet() then
repeat
DocLine.Reset();
DocLine.SetRange("Document No.", DocHeader."No.");
DocLine.SetRange("Buy-from Vendor No.", TempMatchEntry."Buy-from Vendor No.");
DocLine.SetFilter("Allocated Document Line No.", '%1', 0); // Only unallocated lines
// Try matching strategies in order of precision
if TryExactMatch(DocLine, TempMatchEntry) then
CheckTolerancesAndAllocate(TempMatchEntry, DocLine, MatchSetup)
else
if TryItemReferenceMatch(DocLine, TempMatchEntry) then
CheckTolerancesAndAllocate(TempMatchEntry, DocLine, MatchSetup)
else
if TryDescriptionMatch(DocLine, TempMatchEntry) then
CheckTolerancesAndAllocate(TempMatchEntry, DocLine, MatchSetup)
else
if TryBasicValuesMatch(DocLine, TempMatchEntry) then
CheckTolerancesAndAllocate(TempMatchEntry, DocLine, MatchSetup);
until TempMatchEntry.Next() = 0;
end;
local procedure TryExactMatch(
var DocLine: Record "DXP SQZ Document Line";
TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary): Boolean
begin
DocLine.SetRange(Type, TempMatchEntry.Type);
DocLine.SetFilter("No.", '%1|%2', TempMatchEntry."No.", TempMatchEntry."Item Reference No.");
DocLine.SetRange("SQZ Quantity", TempMatchEntry.Quantity);
exit(not DocLine.IsEmpty());
end;
local procedure TryItemReferenceMatch(
var DocLine: Record "DXP SQZ Document Line";
TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary): Boolean
begin
DocLine.SetRange(Type); // Clear previous filters
DocLine.SetRange("Item Reference No.", TempMatchEntry."Item Reference No.");
exit(not DocLine.IsEmpty());
end;
local procedure TryDescriptionMatch(
var DocLine: Record "DXP SQZ Document Line";
TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary): Boolean
begin
DocLine.SetRange("Item Reference No."); // Clear previous filter
DocLine.SetFilter("SQZ Description", '@*' + TempMatchEntry.Description + '*');
exit(not DocLine.IsEmpty());
end;
local procedure TryBasicValuesMatch(
var DocLine: Record "DXP SQZ Document Line";
TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary): Boolean
begin
DocLine.SetRange("SQZ Description"); // Clear previous filter
DocLine.SetRange("SQZ Quantity", TempMatchEntry.Quantity);
DocLine.SetRange("SQZ Unit Price", TempMatchEntry."Direct Unit Cost");
exit(not DocLine.IsEmpty());
end;
local procedure CheckTolerancesAndAllocate(
TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
var DocLine: Record "DXP SQZ Document Line";
MatchSetup: Record "Custom Match Setup")
begin
DocLine.FindFirst(); // We know there is at least one line
if IsMatchWithinTolerances(DocLine, TempMatchEntry, MatchSetup) then
AssignMatchData(DocLine, TempMatchEntry);
end;
local procedure IsMatchWithinTolerances(
DocLine: Record "DXP SQZ Document Line";
TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary;
MatchSetup: Record "Custom Match Setup"): Boolean
var
QtyTolerance: Decimal;
AmountTolerance: Decimal;
begin
QtyTolerance := MatchSetup."Quantity Tolerance";
AmountTolerance := MatchSetup."Amount Tolerance";
if Abs(DocLine."SQZ Quantity" - TempMatchEntry.Quantity) > QtyTolerance then
exit(false);
if Abs(DocLine."SQZ Unit Price" - TempMatchEntry."Direct Unit Cost") > AmountTolerance then
exit(false);
exit(true);
end;
local procedure AssignMatchData(
var DocLine: Record "DXP SQZ Document Line";
TempMatchEntry: Record "DXP SQZ Order Match Entry" temporary)
begin
// Assign the matched data to the document line
DocLine.Validate("Allocated Document Type", "DXP Order Match Document Type"::Custom);
DocLine.Validate("Allocated Document No.", TempMatchEntry."Document No.");
DocLine.Validate("Allocated Document Line No.", TempMatchEntry."Document Line No.");
DocLine.Validate("Allocated Quantity", TempMatchEntry.Quantity);
DocLine.Validate("Allocated Unit Price", TempMatchEntry."Direct Unit Cost");
DocLine.Validate("Allocated Line Amount", TempMatchEntry."Line Amount");
DocLine.Validate("Allocated Line Discount %", TempMatchEntry."Line Discount %");
DocLine.Modify(true);
end;
Bei Fragen zur Implementierung stehen wir Ihnen gerne zur Verfügung.
No Comments