import { Callout } from "zudoku/ui/Callout";

# Checking orders from an external system

A complete, compilable example that validates sales order addresses from an external system without showing the user interface.

<Callout type="caution" title="Why this needs its own code">
The app validates `Sales Header` and `Ship-to Address` automatically, but only on modify. An order that arrives through a web service call with a complete address already in place, and that is never modified afterwards, is not covered by that automatic check. Call the public API yourself for this case.
</Callout>

## What it does

The example subscribes to two standard table events and reacts only when a record was just inserted:

- On `Sales Header`, it validates the sell-to address with the **buffer overload** of [Address Validation API](../address-validation-api.mdx#validaterecord-record), because `Sales Header` has no `Validate(Record)` overload of its own.
- On `Ship-to Address`, it validates the record directly with the `Validate(Record)` overload, because that table is one of the six the API publishes an overload for.

Both paths run with [SetHideAddressSelection](../address-validation-api.mdx#sethideaddressselectionboolean) turned on and without raising an error to a user who is not there to see it - a failed validation is logged and the order is kept with the address it arrived with.

```pascal
codeunit 50100 "PTE External Order Addr. Val."
{
    [EventSubscriber(ObjectType::Table, Database::"Sales Header", 'OnAfterInsertEvent', '', false, false)]
    local procedure ValidateAddressOnAfterInsertSalesHeader(var Rec: Record "Sales Header"; RunTrigger: Boolean)
    var
        addressValidation: Codeunit "bdev.Address Validation API";
    begin
        if (Rec."Document Type" <> Rec."Document Type"::Order) then
            exit;

        if (not addressValidation.AddressValidationEnabled(Database::"Sales Header")) then
            exit;

        addressValidation.SetHideAddressSelection(true);
        ValidateSellToAddress(Rec, addressValidation);
    end;

    local procedure ValidateSellToAddress(var salesHeader: Record "Sales Header"; var addressValidation: Codeunit "bdev.Address Validation API")
    var
        address, validatedAddress : Record "bdev.Address Validation Buffer" temporary;
    begin
        if (salesHeader."Sell-to Address" = '') then
            exit;
        if ((salesHeader."Sell-to City" = '') and (salesHeader."Sell-to Post Code" = '')) then
            exit;

        address.Init();
        address."Primary Key" := CreateGuid();
        address.Address := salesHeader."Sell-to Address";
        address."Address 2" := salesHeader."Sell-to Address 2";
        address."Post Code" := salesHeader."Sell-to Post Code";
        address.City := salesHeader."Sell-to City";
        address.County := salesHeader."Sell-to County";
        address."Country/Region Code" := salesHeader."Sell-to Country/Region Code";
        address."Source Type" := Database::"Sales Header";
        address."Source System ID" := salesHeader.SystemId;

        if (not addressValidation.Validate(address, validatedAddress)) then begin
            LogValidationFailure(salesHeader."No.", 'Sell-to', GetLastErrorText());
            exit;
        end;

        if (validatedAddress.Address = '') then
            exit;

        salesHeader."Sell-to Address" := validatedAddress.Address;
        salesHeader."Sell-to Post Code" := validatedAddress."Post Code";
        salesHeader."Sell-to City" := validatedAddress.City;
        salesHeader."Sell-to County" := validatedAddress.County;
        salesHeader.Modify(true);
    end;

    [EventSubscriber(ObjectType::Table, Database::"Ship-to Address", 'OnAfterInsertEvent', '', false, false)]
    local procedure ValidateAddressOnAfterInsertShipToAddress(var Rec: Record "Ship-to Address"; RunTrigger: Boolean)
    var
        addressValidation: Codeunit "bdev.Address Validation API";
    begin
        if (not addressValidation.AddressValidationEnabled(Database::"Ship-to Address")) then
            exit;

        addressValidation.SetHideAddressSelection(true);

        if (not TryValidateShipToAddress(Rec, addressValidation)) then
            LogValidationFailure(Rec."Customer No.", Rec.Code, GetLastErrorText());
    end;

    [TryFunction]
    local procedure TryValidateShipToAddress(var shipToAddress: Record "Ship-to Address"; var addressValidation: Codeunit "bdev.Address Validation API")
    begin
        addressValidation.Validate(shipToAddress);
    end;

    local procedure LogValidationFailure(documentOrCustomerNo: Code[20]; addressPart: Text; errorText: Text)
    begin
        Session.LogMessage('PTE001', StrSubstNo(ValidationFailedTxt, addressPart, documentOrCustomerNo, errorText),
            Verbosity::Warning, DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'PTE Address Validation');
    end;

    var
        ValidationFailedTxt: Label '%1 address validation for %2 failed: %3', Locked = true;
}
```

## Why the two tables need different code

[Address Validation API](../address-validation-api.mdx) publishes `Validate(Record)` only for `Contact`, `Customer`, `Vendor`, `Alternative Address`, `Order Address` and `Ship-To Address`. `Sales Header` is not one of them, even though the app validates it automatically on modify - so the example builds a `bdev.Address Validation Buffer` from the sell-to fields by hand and uses the **buffer overload**, which is a `[TryFunction]` and never raises.

`Validate(Record)` for `Ship-to Address` is not a try function: a failed validation raises an error. Wrapping the call in a local `[TryFunction]` catches it before it reaches the caller, so the insert that triggered the subscriber still succeeds.

<Callout type="info" title="AddressValidationEnabled only answers for the app's own tables">
`AddressValidationEnabled(Database::"Sales Header")` and `AddressValidationEnabled(Database::"Ship-to Address")` answer according to the setup fields the app itself evaluates for those tables - they are a convenient way to respect the tenant's own on/off switch and scope setting before calling the API, not a general-purpose check for a table of your own.
</Callout>

## See also

- [Address Validation API](../address-validation-api.mdx)
- [Address Data API](../address-data-api.mdx)
- [Address Validation Setup Reference](../setup-reference.mdx)
- [365 business Address Validation - Overview](../readme.mdx)
