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

# Address Validation API

Codeunit `5523600` `bdev.Address Validation API` validates and completes a postal address. It offers two ways in:

- **a record** of one of the supported standard tables. The app builds the address from the record, validates it, shows the user what changed if a dialog is allowed, and writes the result back to the record.
- **the validation buffer**, a temporary `bdev.Address Validation Buffer` record. You fill it, you get the validated address back, and you decide what to do with it. This is the way to validate a table of your own.

```pascal
    addressValidation: Codeunit "bdev.Address Validation API";
```

## Validate(Record, Record)

Validates the address in the buffer and returns the validated address in a second buffer.

```pascal
    [TryFunction]
    procedure Validate(address: Record "bdev.Address Validation Buffer" temporary; var validatedAddress: Record "bdev.Address Validation Buffer" temporary)
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `address` | Record "bdev.Address Validation Buffer" temporary | The address to validate. |
| `validatedAddress` | Record "bdev.Address Validation Buffer" temporary | The validated and completed address. |

**Returns** `false` when the validation failed; `GetLastErrorText()` then holds the reason. It is a `[TryFunction]`, so nothing is raised to the user.

### What goes in

Fill the address fields of the buffer. `Address` is mandatory, and so is `City` **or** `Post Code` - the app refuses to validate an address that has neither, because there is nothing to match against.

| Field | Type | Description |
| ----- | ---- | ----------- |
| `Address` | Text[100] | Street and house number. Mandatory. |
| `Address 2` | Text[50] | Additional address information. |
| `Post Code` | Code[20] | Mandatory unless `City` is filled. |
| `City` | Text[30] | Mandatory unless `Post Code` is filled. |
| `County` | Text[30] | State, province or county. |
| `Country/Region Code` | Code[10] | The country of the address. |
| `Source Type` | Integer | The table id of the record the address belongs to. Optional; used to resolve the record in the result list. |
| `Source System ID` | Guid | The `SystemId` of that record. Optional. |

### What comes back

Read the result from the fields of `validatedAddress` after the call - the record variable is positioned on the validated address:

| Field | Type | Description |
| ----- | ---- | ----------- |
| `Address` | Text[100] | Street and house number, as the service returned them. |
| `Post Code`, `City`, `County` | | The corrected values. `County` is cleared for countries that do not use one. |
| `Country/Region Code` | Code[10] | The ISO alpha-2 code, cleared when no `Country/Region` record exists for it. |
| `Match Score` | Integer | How well the address matched. |
| `Changes applied` | Text[100] | A short summary of what the service changed. |
| `Source Type`, `Source System ID` | | Carried over from the address you passed in. |

The `<field> Changed` flags, `Status` and `Validation failed` belong to the comparison dialog and the validation batch of the app. They are **not** filled by this overload - compare the values yourself to find out what changed.

<Callout type="caution" title="Two results that look like nothing happened">
When the address has already been validated on this tenant, the app answers from `bdev.Address Data` without calling the service: `validatedAddress` is then a copy of what you passed in, with no `Match Score` and no `Changes applied`. And when several candidates come back while a dialog is allowed, the user picks one - cancelling the dialog makes the call return `false` with an empty error text. Neither case is an error; both mean there is nothing to apply.
</Callout>

`Address 2` is not part of the service result and stays empty in `validatedAddress`. Keep the value of your source record.

<Callout type="caution" title="The buffer must stay temporary">
`bdev.Address Validation Buffer` refuses to be written to the database - its insert, modify, delete and rename triggers raise an error on a non-temporary record. Declare it `temporary`, as in the signature.
</Callout>

### Example: validating a table of your own

```pascal
    procedure ValidateBrokerAddress(var broker: Record Broker)
    var
        address, validatedAddress : Record "bdev.Address Validation Buffer" temporary;
        addressValidation: Codeunit "bdev.Address Validation API";
    begin
        address.Init();
        address."Primary Key" := CreateGuid();
        address.Address := broker.Address;
        address."Address 2" := broker."Address 2";
        address."Post Code" := broker."Post Code";
        address.City := broker.City;
        address.County := broker.County;
        address."Country/Region Code" := broker."Country/Region Code";
        address."Source Type" := Database::Broker;
        address."Source System ID" := broker.SystemId;

        if (not addressValidation.Validate(address, validatedAddress)) then begin
            Session.LogMessage('BRK001', GetLastErrorText(), Verbosity::Warning,
                DataClassification::SystemMetadata, TelemetryScope::ExtensionPublisher, 'Category', 'Broker');
            exit;
        end;

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

        broker.Address := validatedAddress.Address;
        broker."Post Code" := validatedAddress."Post Code";
        broker.City := validatedAddress.City;
        broker.County := validatedAddress.County;
        broker.Modify(true);
    end;
```

Whether to apply the result silently or to show it to the user is your decision - the buffer overload does not ask.

## Validate(Record)

Validates the address of a record of a supported table and writes the result back to it.

```pascal
    procedure Validate(var contact: Record Contact)
    procedure Validate(var customer: Record Customer)
    procedure Validate(var vendor: Record Vendor)
    procedure Validate(var altAddress: Record "Alternative Address")
    procedure Validate(var orderAddress: Record "Order Address")
    procedure Validate(var shipToAddress: Record "Ship-To Address")
```

Each overload takes the record by reference and modifies it in place. Unlike the buffer overload, these are not try functions: a failed validation raises an error unless the app suppresses it.

When a dialog is allowed and the service returns more than one candidate, the user is asked to pick one. [SetHideAddressSelection](#sethideaddressselection) turns that off.

<Callout type="info" title="Employee, Resource and the documents">
The app validates `Resource`, `Employee`, `Sales Header` and `Purchase Header` automatically on modify, but publishes no overload for them. Use the buffer overload if you need to validate one of those on demand.
</Callout>

## GetValidationBuffer(Record, Record)

Fills the validation buffer from a record, so that you do not have to map the address fields yourself.

```pascal
    procedure GetValidationBuffer(contact: Record Contact; var buffer: Record "bdev.Address Validation Buffer" temporary)
    procedure GetValidationBuffer(customer: Record Customer; var buffer: Record "bdev.Address Validation Buffer" temporary)
    procedure GetValidationBuffer(vendor: Record Vendor; var buffer: Record "bdev.Address Validation Buffer" temporary)
    procedure GetValidationBuffer(altAddress: Record "Alternative Address"; var buffer: Record "bdev.Address Validation Buffer" temporary)
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| the record | Record | The record whose address is transferred. |
| `buffer` | Record "bdev.Address Validation Buffer" temporary | The buffer to fill. |

Useful when you want the address of a standard record but want to run the validation yourself - to compare the result before applying it, for example.

## AddressValidationEnabled(Integer)

States whether automatic address validation is switched on for a table.

```pascal
    procedure AddressValidationEnabled(tableNo: Integer): Boolean
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `tableNo` | Integer | The table id to ask about. |

**Returns** `true` when validation is enabled in `Address Validation Setup`, the current scope matches the session - `UI only`, `Non-UI only` or `All` - and the table is switched on.

<Callout type="caution" title="Always false for your own table">
The method answers for the tables the app validates itself and returns `false` for every other table id. It is not a switch you can extend: use a setup field of your own to decide whether your extension validates.
</Callout>

## SetHideAddressSelection(Boolean)

Suppresses the dialog that lets the user pick between several candidate addresses.

```pascal
    procedure SetHideAddressSelection(value: Boolean)
```

| Parameter | Type | Description |
| --------- | ---- | ----------- |
| `value` | Boolean | `true` hides the selection, `false` shows it. |

Set it before calling `Validate` when your code runs without a user - in a job queue, in an API call, or in a test. The app sets it by itself when no GUI is allowed.

## See also

- [365 business Address Validation - Overview](readme.mdx)
- [Address Prediction API](address-prediction-api.mdx)
- [Documentation - Address Validation](../../en-us/365-business-address-validation/address-validation.mdx)
