bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
696 lines (574 loc) • 24.4 kB
Markdown
# Facade Pattern AL Implementation - Code Examples
This sample demonstrates implementing facade patterns to simplify complex subsystems in Business Central.
## Basic Facade Interface
```al
// Simple customer management facade
codeunit 50400 "Customer Management Facade"
{
procedure CreateCustomer(CustomerData: Dictionary of [Text, Variant]): Code[20]
var
Customer: Record Customer;
CustomerNo: Code[20];
begin
CustomerNo := CreateNewCustomer(CustomerData);
SetupCustomerDefaults(CustomerNo);
InitializeCustomerRelationships(CustomerNo);
exit(CustomerNo);
end;
procedure ProcessCustomerOrder(CustomerNo: Code[20]; OrderData: Dictionary of [Text, Variant]): Code[20]
var
SalesOrderNo: Code[20];
begin
ValidateCustomerEligibility(CustomerNo);
SalesOrderNo := CreateSalesOrder(CustomerNo, OrderData);
ApplyCustomerDiscounts(SalesOrderNo);
SetupOrderApprovals(SalesOrderNo);
exit(SalesOrderNo);
end;
procedure GetCustomerAnalytics(CustomerNo: Code[20]): Dictionary of [Text, Variant]
var
Analytics: Dictionary of [Text, Variant];
begin
Analytics := CalculateCustomerMetrics(CustomerNo);
AddSalesHistory(CustomerNo, Analytics);
AddPaymentHistory(CustomerNo, Analytics);
AddCreditAnalysis(CustomerNo, Analytics);
exit(Analytics);
end;
local procedure CreateNewCustomer(CustomerData: Dictionary of [Text, Variant]): Code[20]
var
Customer: Record Customer;
CustomerName: Text[100];
ContactName: Text[100];
begin
Customer.Init();
Customer."No." := '';
Customer.Insert(true);
if CustomerData.Get('Name', CustomerName) then
Customer.Validate(Name, CustomerName);
if CustomerData.Get('Contact', ContactName) then
Customer.Validate(Contact, ContactName);
Customer.Modify(true);
exit(Customer."No.");
end;
local procedure SetupCustomerDefaults(CustomerNo: Code[20])
var
Customer: Record Customer;
begin
Customer.Get(CustomerNo);
Customer.Validate("Customer Posting Group", 'DOMESTIC');
Customer.Validate("Gen. Bus. Posting Group", 'DOMESTIC');
Customer.Validate("Payment Terms Code", '30 DAYS');
Customer.Modify(true);
end;
local procedure InitializeCustomerRelationships(CustomerNo: Code[20])
begin
// Setup default contacts, ship-to addresses, etc.
CreateDefaultShipToAddress(CustomerNo);
AssignCustomerToSalesperson(CustomerNo);
end;
local procedure ValidateCustomerEligibility(CustomerNo: Code[20])
var
Customer: Record Customer;
begin
Customer.Get(CustomerNo);
if Customer.Blocked <> Customer.Blocked::" " then
Error('Customer %1 is blocked and cannot place orders', CustomerNo);
CheckCreditLimit(Customer);
end;
local procedure CreateSalesOrder(CustomerNo: Code[20]; OrderData: Dictionary of [Text, Variant]): Code[20]
var
SalesHeader: Record "Sales Header";
Items: List of [Dictionary of [Text, Variant]];
ItemData: Dictionary of [Text, Variant];
begin
// Create sales header
SalesHeader.Init();
SalesHeader."Document Type" := SalesHeader."Document Type"::Order;
SalesHeader."No." := '';
SalesHeader.Insert(true);
SalesHeader.Validate("Sell-to Customer No.", CustomerNo);
SalesHeader.Modify(true);
// Add lines from order data
if OrderData.Get('Items', Items) then begin
foreach ItemData in Items do
CreateSalesLine(SalesHeader, ItemData);
end;
exit(SalesHeader."No.");
end;
local procedure CreateSalesLine(var SalesHeader: Record "Sales Header"; ItemData: Dictionary of [Text, Variant])
var
SalesLine: Record "Sales Line";
ItemNo: Code[20];
Quantity: Decimal;
NextLineNo: Integer;
begin
NextLineNo := GetNextLineNo(SalesHeader);
SalesLine.Init();
SalesLine."Document Type" := SalesHeader."Document Type";
SalesLine."Document No." := SalesHeader."No.";
SalesLine."Line No." := NextLineNo;
SalesLine.Insert(true);
if ItemData.Get('ItemNo', ItemNo) then
SalesLine.Validate("No.", ItemNo);
if ItemData.Get('Quantity', Quantity) then
SalesLine.Validate(Quantity, Quantity);
SalesLine.Modify(true);
end;
local procedure CalculateCustomerMetrics(CustomerNo: Code[20]): Dictionary of [Text, Variant]
var
Metrics: Dictionary of [Text, Variant];
Customer: Record Customer;
TotalSales: Decimal;
OrderCount: Integer;
begin
Customer.Get(CustomerNo);
Customer.CalcFields("Sales (LCY)", "Balance (LCY)");
Metrics.Set('TotalSales', Customer."Sales (LCY)");
Metrics.Set('CurrentBalance', Customer."Balance (LCY)");
Metrics.Set('CreditLimit', Customer."Credit Limit (LCY)");
Metrics.Set('LastOrderDate', GetLastOrderDate(CustomerNo));
Metrics.Set('OrderCount', GetOrderCount(CustomerNo));
exit(Metrics);
end;
// Additional helper procedures...
local procedure CreateDefaultShipToAddress(CustomerNo: Code[20])
begin
// Implementation for default ship-to address
end;
local procedure AssignCustomerToSalesperson(CustomerNo: Code[20])
begin
// Implementation for salesperson assignment
end;
local procedure CheckCreditLimit(Customer: Record Customer)
begin
// Implementation for credit limit checking
end;
local procedure ApplyCustomerDiscounts(SalesOrderNo: Code[20])
begin
// Implementation for discount application
end;
local procedure SetupOrderApprovals(SalesOrderNo: Code[20])
begin
// Implementation for approval workflow setup
end;
local procedure AddSalesHistory(CustomerNo: Code[20]; var Analytics: Dictionary of [Text, Variant])
begin
// Implementation for sales history analysis
end;
local procedure AddPaymentHistory(CustomerNo: Code[20]; var Analytics: Dictionary of [Text, Variant])
begin
// Implementation for payment history analysis
end;
local procedure AddCreditAnalysis(CustomerNo: Code[20]; var Analytics: Dictionary of [Text, Variant])
begin
// Implementation for credit analysis
end;
local procedure GetNextLineNo(SalesHeader: Record "Sales Header"): Integer
var
SalesLine: Record "Sales Line";
begin
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
if SalesLine.FindLast() then
exit(SalesLine."Line No." + 10000)
else
exit(10000);
end;
local procedure GetLastOrderDate(CustomerNo: Code[20]): Date
var
SalesHeader: Record "Sales Header";
begin
SalesHeader.SetRange("Sell-to Customer No.", CustomerNo);
SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Order);
if SalesHeader.FindLast() then
exit(SalesHeader."Order Date");
exit(0D);
end;
local procedure GetOrderCount(CustomerNo: Code[20]): Integer
var
SalesHeader: Record "Sales Header";
begin
SalesHeader.SetRange("Sell-to Customer No.", CustomerNo);
SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Order);
exit(SalesHeader.Count);
end;
}
```
## Integration Facade
```al
// Facade for external system integration
codeunit 50401 "External Integration Facade"
{
var
HttpClient: HttpClient;
BaseUrl: Text;
procedure SynchronizeCustomer(CustomerNo: Code[20]): Boolean
var
CustomerData: Dictionary of [Text, Variant];
SyncResult: Boolean;
begin
CustomerData := ExtractCustomerData(CustomerNo);
SyncResult := SendCustomerToExternalSystem(CustomerData);
if SyncResult then
UpdateSynchronizationStatus(CustomerNo, true)
else
LogSynchronizationError(CustomerNo);
exit(SyncResult);
end;
procedure ImportExternalOrders(): Integer
var
ExternalOrders: List of [Dictionary of [Text, Variant]];
OrderData: Dictionary of [Text, Variant];
ImportedCount: Integer;
CustomerFacade: Codeunit "Customer Management Facade";
begin
ExternalOrders := FetchOrdersFromExternalSystem();
ImportedCount := 0;
foreach OrderData in ExternalOrders do begin
if ProcessExternalOrder(OrderData, CustomerFacade) then
ImportedCount += 1;
end;
exit(ImportedCount);
end;
procedure ExportInventoryLevels(): Boolean
var
InventoryData: List of [Dictionary of [Text, Variant]];
ExportResult: Boolean;
begin
InventoryData := ExtractInventoryData();
ExportResult := SendInventoryToExternalSystem(InventoryData);
if ExportResult then
UpdateInventoryExportTimestamp();
exit(ExportResult);
end;
local procedure ExtractCustomerData(CustomerNo: Code[20]): Dictionary of [Text, Variant]
var
Customer: Record Customer;
CustomerData: Dictionary of [Text, Variant];
begin
Customer.Get(CustomerNo);
CustomerData.Set('CustomerNumber', Customer."No.");
CustomerData.Set('Name', Customer.Name);
CustomerData.Set('Address', Customer.Address);
CustomerData.Set('City', Customer.City);
CustomerData.Set('PostCode', Customer."Post Code");
CustomerData.Set('Country', Customer."Country/Region Code");
CustomerData.Set('PhoneNumber', Customer."Phone No.");
CustomerData.Set('Email', Customer."E-Mail");
CustomerData.Set('CreditLimit', Customer."Credit Limit (LCY)");
exit(CustomerData);
end;
local procedure SendCustomerToExternalSystem(CustomerData: Dictionary of [Text, Variant]): Boolean
var
JsonPayload: Text;
HttpContent: HttpContent;
HttpResponseMessage: HttpResponseMessage;
Headers: HttpHeaders;
begin
JsonPayload := ConvertToJson(CustomerData);
HttpContent.WriteFrom(JsonPayload);
HttpContent.GetHeaders(Headers);
Headers.Remove('Content-Type');
Headers.Add('Content-Type', 'application/json');
if HttpClient.Post(BaseUrl + '/api/customers', HttpContent, HttpResponseMessage) then
exit(HttpResponseMessage.IsSuccessStatusCode)
else
exit(false);
end;
local procedure FetchOrdersFromExternalSystem(): List of [Dictionary of [Text, Variant]]
var
HttpResponseMessage: HttpResponseMessage;
ResponseText: Text;
Orders: List of [Dictionary of [Text, Variant]];
begin
if HttpClient.Get(BaseUrl + '/api/orders', HttpResponseMessage) then begin
if HttpResponseMessage.IsSuccessStatusCode then begin
HttpResponseMessage.Content.ReadAs(ResponseText);
Orders := ParseOrdersFromJson(ResponseText);
end;
end;
exit(Orders);
end;
local procedure ProcessExternalOrder(OrderData: Dictionary of [Text, Variant]; CustomerFacade: Codeunit "Customer Management Facade"): Boolean
var
CustomerNo: Code[20];
OrderNo: Code[20];
begin
// Extract and validate customer
if not OrderData.Get('CustomerNumber', CustomerNo) then
exit(false);
if not CustomerExists(CustomerNo) then
CustomerNo := CreateCustomerFromOrderData(OrderData, CustomerFacade);
// Process the order
OrderNo := CustomerFacade.ProcessCustomerOrder(CustomerNo, OrderData);
exit(OrderNo <> '');
end;
local procedure ExtractInventoryData(): List of [Dictionary of [Text, Variant]]
var
Item: Record Item;
InventoryList: List of [Dictionary of [Text, Variant]];
ItemData: Dictionary of [Text, Variant];
begin
Item.SetRange(Blocked, false);
if Item.FindSet() then
repeat
Item.CalcFields(Inventory);
Clear(ItemData);
ItemData.Set('ItemNumber', Item."No.");
ItemData.Set('Description', Item.Description);
ItemData.Set('Inventory', Item.Inventory);
ItemData.Set('UnitOfMeasure', Item."Base Unit of Measure");
ItemData.Set('LastUpdated', CurrentDateTime);
InventoryList.Add(ItemData);
until Item.Next() = 0;
exit(InventoryList);
end;
local procedure SendInventoryToExternalSystem(InventoryData: List of [Dictionary of [Text, Variant]]): Boolean
var
JsonPayload: Text;
HttpContent: HttpContent;
HttpResponseMessage: HttpResponseMessage;
Headers: HttpHeaders;
begin
JsonPayload := ConvertInventoryToJson(InventoryData);
HttpContent.WriteFrom(JsonPayload);
HttpContent.GetHeaders(Headers);
Headers.Remove('Content-Type');
Headers.Add('Content-Type', 'application/json');
if HttpClient.Post(BaseUrl + '/api/inventory', HttpContent, HttpResponseMessage) then
exit(HttpResponseMessage.IsSuccessStatusCode)
else
exit(false);
end;
// Helper procedures
local procedure ConvertToJson(Data: Dictionary of [Text, Variant]): Text
begin
// JSON conversion implementation
exit('{}'); // Placeholder
end;
local procedure ParseOrdersFromJson(JsonText: Text): List of [Dictionary of [Text, Variant]]
var
Orders: List of [Dictionary of [Text, Variant]];
begin
// JSON parsing implementation
exit(Orders);
end;
local procedure ConvertInventoryToJson(InventoryData: List of [Dictionary of [Text, Variant]]): Text
begin
// JSON conversion for inventory data
exit('[]'); // Placeholder
end;
local procedure CustomerExists(CustomerNo: Code[20]): Boolean
var
Customer: Record Customer;
begin
exit(Customer.Get(CustomerNo));
end;
local procedure CreateCustomerFromOrderData(OrderData: Dictionary of [Text, Variant]; CustomerFacade: Codeunit "Customer Management Facade"): Code[20]
var
CustomerData: Dictionary of [Text, Variant];
begin
// Extract customer info from order data
CustomerData.Set('Name', OrderData.Get('CustomerName'));
CustomerData.Set('Contact', OrderData.Get('ContactName'));
exit(CustomerFacade.CreateCustomer(CustomerData));
end;
local procedure UpdateSynchronizationStatus(CustomerNo: Code[20]; Success: Boolean)
begin
// Update sync status tracking
end;
local procedure LogSynchronizationError(CustomerNo: Code[20])
begin
// Log sync errors for troubleshooting
end;
local procedure UpdateInventoryExportTimestamp()
begin
// Update last export timestamp
end;
}
```
## Multi-Layer Business Process Facade
```al
// Complex facade coordinating multiple business processes
codeunit 50402 "Sales Process Facade"
{
var
CustomerFacade: Codeunit "Customer Management Facade";
IntegrationFacade: Codeunit "External Integration Facade";
procedure ProcessCompleteOrder(OrderRequest: Dictionary of [Text, Variant]): Dictionary of [Text, Variant]
var
ProcessingResult: Dictionary of [Text, Variant];
CustomerNo: Code[20];
OrderNo: Code[20];
ProcessingSteps: List of [Text];
begin
ProcessingSteps.Add('Validate Customer');
ProcessingSteps.Add('Create/Update Customer');
ProcessingSteps.Add('Process Order');
ProcessingSteps.Add('Apply Business Rules');
ProcessingSteps.Add('External Synchronization');
ProcessingSteps.Add('Generate Confirmations');
ProcessingResult.Set('Steps', ProcessingSteps);
ProcessingResult.Set('StartTime', CurrentDateTime);
try
// Step 1: Customer handling
CustomerNo := HandleCustomerProcess(OrderRequest, ProcessingResult);
// Step 2: Order processing
OrderNo := HandleOrderProcess(CustomerNo, OrderRequest, ProcessingResult);
// Step 3: Business rules and approvals
HandleBusinessRulesProcess(OrderNo, ProcessingResult);
// Step 4: External integration
HandleExternalIntegration(CustomerNo, OrderNo, ProcessingResult);
// Step 5: Confirmations and notifications
HandleConfirmationProcess(OrderNo, ProcessingResult);
ProcessingResult.Set('Success', true);
ProcessingResult.Set('OrderNumber', OrderNo);
ProcessingResult.Set('CustomerNumber', CustomerNo);
except
ProcessingResult.Set('Success', false);
ProcessingResult.Set('Error', GetLastErrorText);
end;
ProcessingResult.Set('EndTime', CurrentDateTime);
exit(ProcessingResult);
end;
procedure GetOrderStatus(OrderNo: Code[20]): Dictionary of [Text, Variant]
var
StatusInfo: Dictionary of [Text, Variant];
SalesHeader: Record "Sales Header";
begin
if SalesHeader.Get(SalesHeader."Document Type"::Order, OrderNo) then begin
StatusInfo.Set('OrderNumber', OrderNo);
StatusInfo.Set('Status', Format(SalesHeader.Status));
StatusInfo.Set('CustomerNumber', SalesHeader."Sell-to Customer No.");
StatusInfo.Set('OrderDate', SalesHeader."Order Date");
StatusInfo.Set('Amount', SalesHeader."Amount Including VAT");
StatusInfo.Set('PaymentTerms', SalesHeader."Payment Terms Code");
// Add processing history
AddProcessingHistory(OrderNo, StatusInfo);
end else begin
StatusInfo.Set('Found', false);
StatusInfo.Set('Error', 'Order not found');
end;
exit(StatusInfo);
end;
local procedure HandleCustomerProcess(OrderRequest: Dictionary of [Text, Variant]; var ProcessingResult: Dictionary of [Text, Variant]): Code[20]
var
CustomerNo: Code[20];
CustomerData: Dictionary of [Text, Variant];
begin
ProcessingResult.Set('CurrentStep', 'Processing Customer');
if OrderRequest.Get('CustomerNumber', CustomerNo) then begin
if not CustomerExists(CustomerNo) then begin
ExtractCustomerDataFromOrder(OrderRequest, CustomerData);
CustomerNo := CustomerFacade.CreateCustomer(CustomerData);
ProcessingResult.Set('CustomerCreated', true);
end else begin
ProcessingResult.Set('CustomerCreated', false);
end;
end else begin
Error('Customer number is required for order processing');
end;
ProcessingResult.Set('CustomerNumber', CustomerNo);
exit(CustomerNo);
end;
local procedure HandleOrderProcess(CustomerNo: Code[20]; OrderRequest: Dictionary of [Text, Variant]; var ProcessingResult: Dictionary of [Text, Variant]): Code[20]
var
OrderNo: Code[20];
begin
ProcessingResult.Set('CurrentStep', 'Processing Order');
OrderNo := CustomerFacade.ProcessCustomerOrder(CustomerNo, OrderRequest);
if OrderNo <> '' then begin
ProcessingResult.Set('OrderCreated', true);
ProcessingResult.Set('OrderNumber', OrderNo);
end else begin
Error('Failed to create sales order');
end;
exit(OrderNo);
end;
local procedure HandleBusinessRulesProcess(OrderNo: Code[20]; var ProcessingResult: Dictionary of [Text, Variant])
var
ApprovalRequired: Boolean;
CreditCheckPassed: Boolean;
begin
ProcessingResult.Set('CurrentStep', 'Applying Business Rules');
// Check if approval is required
ApprovalRequired := CheckApprovalRequirement(OrderNo);
ProcessingResult.Set('ApprovalRequired', ApprovalRequired);
// Perform credit check
CreditCheckPassed := PerformCreditCheck(OrderNo);
ProcessingResult.Set('CreditCheckPassed', CreditCheckPassed);
if ApprovalRequired then
InitiateApprovalWorkflow(OrderNo);
if not CreditCheckPassed then
Error('Credit check failed for order %1', OrderNo);
end;
local procedure HandleExternalIntegration(CustomerNo: Code[20]; OrderNo: Code[20]; var ProcessingResult: Dictionary of [Text, Variant])
var
SyncResult: Boolean;
begin
ProcessingResult.Set('CurrentStep', 'External Synchronization');
// Synchronize customer with external system
SyncResult := IntegrationFacade.SynchronizeCustomer(CustomerNo);
ProcessingResult.Set('CustomerSyncSuccess', SyncResult);
// Additional integration as needed
ProcessingResult.Set('ExternalIntegrationCompleted', true);
end;
local procedure HandleConfirmationProcess(OrderNo: Code[20]; var ProcessingResult: Dictionary of [Text, Variant])
begin
ProcessingResult.Set('CurrentStep', 'Generating Confirmations');
SendOrderConfirmation(OrderNo);
NotifyWarehouse(OrderNo);
UpdateCustomerStatistics(OrderNo);
ProcessingResult.Set('ConfirmationsGenerated', true);
end;
// Additional helper procedures...
local procedure CustomerExists(CustomerNo: Code[20]): Boolean
var
Customer: Record Customer;
begin
exit(Customer.Get(CustomerNo));
end;
local procedure ExtractCustomerDataFromOrder(OrderRequest: Dictionary of [Text, Variant]; var CustomerData: Dictionary of [Text, Variant])
begin
// Extract customer information from order request
CustomerData.Set('Name', OrderRequest.Get('CustomerName'));
CustomerData.Set('Contact', OrderRequest.Get('ContactName'));
CustomerData.Set('Address', OrderRequest.Get('CustomerAddress'));
end;
local procedure CheckApprovalRequirement(OrderNo: Code[20]): Boolean
var
SalesHeader: Record "Sales Header";
begin
if SalesHeader.Get(SalesHeader."Document Type"::Order, OrderNo) then
exit(SalesHeader."Amount Including VAT" > 10000); // Approval required for orders > 10,000
exit(false);
end;
local procedure PerformCreditCheck(OrderNo: Code[20]): Boolean
begin
// Credit check implementation
exit(true); // Simplified for example
end;
local procedure InitiateApprovalWorkflow(OrderNo: Code[20])
begin
// Approval workflow initiation
end;
local procedure SendOrderConfirmation(OrderNo: Code[20])
begin
// Order confirmation implementation
end;
local procedure NotifyWarehouse(OrderNo: Code[20])
begin
// Warehouse notification implementation
end;
local procedure UpdateCustomerStatistics(OrderNo: Code[20])
begin
// Customer statistics update implementation
end;
local procedure AddProcessingHistory(OrderNo: Code[20]; var StatusInfo: Dictionary of [Text, Variant])
begin
// Add processing history to status information
end;
}
```
This comprehensive facade implementation demonstrates how to simplify complex business processes by providing clean, unified interfaces that coordinate multiple subsystems while hiding implementation complexity from consumers.