Skip to main content

SAPB1SL - Customer sync

SAPB1SL Connector - Customer Update This document details the Customer Update pipeline for the SAP Business One Service Layer (B1SL)...

Updated over a week ago

SAPB1SL Connector - Customer Update

This document details the Customer Update pipeline for the SAP Business One Service Layer (B1SL) connector. It outlines how customer-related data from SAP B1SL is synchronized with App4Sales, covering Customers, Addresses, Contact Persons, Item Class Value Discounts, and Dashboards. The process ensures data consistency, handles transformations, and manages various synchronization settings.

Data Source Configuration

Customer data is retrieved from SAP Business One Service Layer (B1SL) using OData queries. The base URL and authentication details are configured within the connector settings. Salesperson data is also retrieved from SAP B1SL.

The synchronization can be a full sync (e.g., weekly on Saturday, or when explicitly configured) or a partial sync based on the `UpdateDate` of customer records since the last successful synchronization.

Additional customer data, including free fields and item filters, can be sourced from external FTP/CSV files.

Customer Core Fields

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

CustomerCode

SapB1SLCustomer.CustomerCode

Direct mapping. For new customers, a code is generated based on `NewCustomersCodePrefix` and `NewCustomerCodeNumber` settings, ensuring sequential numbering. If `SendEmptyCardCode` setting is true, an empty string is sent to SAP.

CustomerName

SapB1SLCustomer.CustomerName

Direct mapping. Required field; customers without a name are skipped.

Email

SapB1SLCustomer.Email

Direct mapping.

Phone

SapB1SLCustomer.Phone1

Direct mapping.

VatLiable

SapB1SLCustomer.VatLiable (enum), SapB1SLCustomer.VatNumber, SapB1SLAddress.FederalTaxID

Determined by connector settings:

  • If `UseVatLiableForCustomerVatLiable` is true: mapped if SAP `VatLiable` is 'Y' or 'vLiable'.

  • Else if `UseBillAddressFederalTaxIDAsCustomerVATLiableIndicator` is false: mapped if `SapB1SLCustomer.VatNumber` is not empty AND SAP `VatLiable` is 'Y' or 'vLiable'.

  • Else: mapped if any 'BillTo' or 'ShipTo' address has a `FederalTaxID`.

If the App4Sales administration is set to USA, `VatLiable` is always set to `false`.

UsesPriceField

SapB1SLCustomer.PriceListCode

Direct mapping. If price deduplication is enabled, the PriceListCode may be migrated to a new ID based on internal lookup tables. Fallbacks to default price list (ID 1) if migrated ID not found.

Currency

SapB1SLCustomer.Currency

Direct mapping.

Discount

SapB1SLCustomer.DiscountPercent

Mapped directly if `UseCustomerDiscount` setting is true, otherwise defaults to 0.0m.

LanguageCode

SapB1SLCustomer.LanguageCode

Mapped from SAP LanguageCode (converted to string). Further mapped to App4Sales internal language codes using `MappedLanguagesContext`.

VatCode

SapB1SLCustomer.VatNumber, SapB1SLCustomer.FederalTaxID

Mapped from `SapB1SLCustomer.VatNumber` or `SapB1SLCustomer.FederalTaxID`. When sending to SAP, if `SendVatNumberInFederalTaxIDField` is true, it is sent as `FederalTaxID`, otherwise as `VatNumber`.

PaymentConditionCode

SapB1SLCustomer.PaymentTermsGroupCode

Mapped from integer `PaymentTermsGroupCode` to string. If empty string is received, it's converted to null to prevent foreign key issues.

CustomerEnabled

Derived

Set to `true` if customer is active in SAP, or based on `setAsActive` parameter in conversion. In `SapB1SLConnector.cs`, customers with `Valid` as "False" or `Frozen` as "True" in SAP B1SL are excluded from synchronization (set as `false` for `setAsActive`).

CustomerClassification

SapB1SLCustomer.GroupCode, or property defined by `CustomerClassificationProperty` setting

If `CustomerClassificationProperty` setting is defined, value is retrieved from the specified property (either a direct property or an unknown element/UDF) of the `SapB1SLCustomer`. Otherwise, mapped from `SapB1SLCustomer.GroupCode`.

CustomerGuid

Derived

Retrieved from local cache (`_customerCodeLinks`), Portal API, or a new GUID is generated. Existing GUIDs for inactive customers (prefixed with '~') are reused.

Created

Derived

Defaults to `DateTime.Now` if `MinValue`. Truncated to seconds.

Sysmodified

Derived

Truncated to seconds.

PasswordWebshop

Hardcoded

Set to `null` if `UpdaterHelper.IsUpdater` is true.

CustomerDashboard

SapB1SLCustomer (Base64 string)

If not empty, the Base64 string is decoded and stored separately via `CustomerDataManager`. The field itself is then set to `null` on the customer object for memory.

CustomerNote

SapB1SLCustomer (via an extra field)

If present, existing notes from "BackOffice" are deleted, and a new note is inserted into `CustomerNotes` table.

ActionPriceList

SapB1SLCustomer (via an extra field or custom property)

If `DefaultActionPriceListCode` setting is configured and `ActionPriceList` is not set, it's retrieved from `PriceListsContext`. If price deduplication is enabled, it may be migrated to a new ID.

InternalCode

Existing App4Sales Customer.InternalCode

If syncing from App4Sales and `customer.InternalCode` is empty, it retains the existing value from the database.

ExtraData

Existing App4Sales Customer.ExtraData

If syncing from App4Sales and `customer.ExtraData` is empty, it retains the existing value from the database.

ItemFilter

Derived from `extraCustomerData.UnknownElements` (prefixed with "ItemFilter_") or existing App4Sales Customer.ItemFilter

Item filters can be extracted from external CSV data. If `CustomerItemFilter` setting is enabled and the update is not from the updater, the existing `ItemFilter` from the database is retained.

FreeFields

Derived from `customer.FreeFieldList` or `extraCustomerData.UnknownElements` (prefixed with "FreeField_")

If `customer.FreeFieldList` is not empty, it's serialized to XML. `UnknownElements` from extra data are converted to `CustomerFreeField` objects.

Addresses

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

IsMainAddress

SapB1SLCustomer.Address, SapB1SLAddress.Name

Set if `SapB1SLCustomer.Address` (main address name) matches `SapB1SLAddress.Name`. When sending to SAP, the `sapAddress.Name` is constructed using Customer Name and a readable address type.

AddressLine.Street

SapB1SLAddress.Street

Direct mapping.

AddressLine.Number

SapB1SLAddress.Number

Direct mapping.

AddressLine1

SapB1SLAddress.Street, SapB1SLAddress.Number

Concatenation of `Street` and `Number` when converting from SAP. Parsed into `AddressLine.Street`, `AddressLine.Number`, `AddressLine.Addition` if `AddressLine` is null. Formatted from `AddressLine` components when sending to SAP.

City

SapB1SLAddress.City

Direct mapping.

Country

SapB1SLAddress.Country

Direct mapping. If empty, derived from `Iso2`.

Iso2

SapB1SLAddress.Country

Converted from `SapB1SLAddress.Country` using `CultureHelper.GetCountryCodeByCountry`. Mapped using `MappedCountriesContext`. When sending to SAP, mapped from `address.Iso2`. If empty, derived from `Country`.

State

SapB1SLAddress.State

Direct mapping.

PostCode

SapB1SLAddress.ZipCode

Direct mapping.

AddressType

SapB1SLAddress.AddressType

Mapped from SAP B1SL to App4Sales:

  • `BillTo` -> `Invoice`

  • `ShipTo` -> `Delivery`

When sending to SAP B1SL:

  • `Invoice`, `Visit` -> `BillTo`

  • `Delivery` -> `ShipTo`

If `AddressType` is 'Invoice' from SAP, a 'Visit' address is created as a copy. Automatically creates corresponding 'Visit' or 'Delivery' addresses if only one exists. Defaults to 'Visit' if empty.

Email

SapB1SLCustomer.Email

Inherits customer's email if address email is empty and `IsMainAddress` is true. Also inherits from other customer addresses or contact persons if no email is found. Trimmed and invalid XML characters removed.

ExternalId

SapB1SLAddress.Name, SapB1SLAddress.RowNumber

A serialized string of `AddressName` and `RowNumber` from SAP. Used for updating existing addresses in SAP.

Contacts

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

ContactId

SapB1SLContactPerson.ContactId

Mapped from `SapB1SLContactPerson.ContactId`. If `UpdaterHelper.IsUpdater` is true and `ContactId` is empty, a hash code-based `ContactId` is generated.

IsMainContactPerson

SapB1SLCustomer.ContactPersonName, SapB1SLContactPerson.FullName

Set if `SapB1SLCustomer.ContactPersonName` matches `SapB1SLContactPerson.FullName`. If a `MainContactPerson` is provided from App4Sales, it's marked as `true`. If no main contact is specified and multiple contacts exist, the first contact is marked as main.

Email

SapB1SLContactPerson.Email

Direct mapping. Trimmed and invalid XML characters removed.

FirstName

SapB1SLContactPerson.FirstName

Direct mapping. If individual name components are empty but `FullName` exists, `FullName` is parsed into `FirstName`, `MiddleName`, `LastName`.

MiddleName

SapB1SLContactPerson.MiddleName

Direct mapping. If individual name components are empty but `FullName` exists, `FullName` is parsed into `FirstName`, `MiddleName`, `LastName`.

LastName

SapB1SLContactPerson.LastName

Direct mapping. If individual name components are empty but `FullName` exists, `FullName` is parsed into `FirstName`, `MiddleName`, `LastName`.

FullName

SapB1SLContactPerson.FirstName, MiddleName, LastName

Constructed from `FirstName`, `MiddleName`, `LastName`. If `FullName` is empty but individual name components exist, it's constructed.

MobileNumber

SapB1SLContactPerson.MobilePhone

Direct mapping.

CustomerGuid

Derived

Inherits `CustomerGuid` from the parent customer.

DynamicFreeFields

App4Sales ContactPerson.DynamicFreeFields (XML payload)

Converted to extra fields based on `CustomCustomerFieldsMapping` setting.

Gender

App4Sales ContactPerson.Gender

Direct mapping to SAP B1SL.

Item Class Value Discounts

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

discount

App4Sales Customer.DiscountsPerItemCLassValue.discount

Direct mapping. Only discounts with a value greater than 0 are stored.

CustomerGuid

Derived

Inherits `CustomerGuid` from the parent customer.

Extra Data & Free Fields

Customer records can be enriched with extra data originating from external FTP/CSV files.

  • Property Overrides: If the `extraCustomerData` (from FTP/CSV) contains properties with names matching existing `Customer` object properties, these values will overwrite the corresponding `Customer` properties.

  • Free Fields: Any unknown elements in `extraCustomerData` that are prefixed with "FreeField_" will be extracted and stored as `CustomerFreeField` objects in `customer.FreeFieldList`. If `customer.FreeFieldList` contains data, it will be serialized to XML and stored in `customer.FreeFields`.

  • Dynamic Free Fields: The `DynamicFreeFields` (an XML payload) on the App4Sales `Customer` object can be mapped to SAP B1SL properties or User-Defined Fields (UDFs) on `SapB1SLCustomer`, `SapB1SLContactPerson`, or `SapB1SLAddress` based on the `CustomCustomerFieldsMapping` connector setting.

Discounts & Dashboards

  • Item Class Value Discounts: Discounts defined per item class for a customer (`DiscountsPerItemCLassValue`) are stored in the App4Sales database. Only discounts with a value greater than 0 are processed.

  • Customer Dashboards: A Base64-encoded string representing a customer-specific dashboard layout can be provided. This string is decoded and saved separately using the `CustomerDataManager`. After saving, the `CustomerDashboard` field on the customer object is cleared to optimize memory usage.

  • Customer Notes: If `customer.CustomerNote` is present, any existing "BackOffice" notes for that customer are deleted from the `CustomerNotes` table, and the new note is inserted.

Special Logic & Filters

  • Invalid Customer Handling: Customers with empty `CustomerName` or `CustomerCode` are logged as warnings and skipped.

  • OData Filtering: Customer retrieval from SAP B1SL uses OData filters. These can include a `UpdateDate` filter based on the last synchronization time and an additional filter specified in the `FilterCustomersUsingOData` setting.

  • Customer Type Filtering: Only customers matching the `CardType` specified in `SynchronizeOnlyСustomersTypes` setting are processed (defaults to `Customer` type).

  • Exclusion of Invalid Customers: If `ExcludeInvalidCustomersFromSync` setting is enabled, customers marked as `Valid = "False"` or `Frozen = "True"` in SAP B1SL are excluded from synchronization.

  • Batch Processing: Customers are processed in batches of 100 to optimize performance.

  • Scripting Hooks: The `BeforeUpdateCustomers` and `UpdateCustomers` scripts are executed before and after processing customer batches, respectively, allowing for custom logic injection.

  • GUID Management: Existing `CustomerGuid` values are prioritized, either from a local cache or the Portal API. For "updater" scenarios, `CustomerGuid` values of previously inactive customers are reused. If no GUID is found, a new one is generated.

  • Address Standardization: Addresses undergo normalization, including country code mapping, email inheritance, and parsing of address lines. Logic exists to ensure both "Visit" and "Delivery" addresses are present, even if they need to be duplicated from an existing address. Invalid XML characters are removed from address fields.

  • Contact Person Name Parsing: If a contact person's individual name fields (FirstName, MiddleName, LastName) are empty but `FullName` is present, the `FullName` is parsed to populate the individual fields. Conversely, `FullName` is constructed from individual fields if it's empty.

  • Contact ID Generation: In "updater" mode, if a contact person's `ContactId` is empty, a hash code-based ID is generated.

Related Settings & Prerequisites

  • `DoNextSyncAsFullSync`: Boolean. If true, the next synchronization will be a full sync, ignoring `UpdateDate` filters.

  • `FilterCustomersUsingOData`: String. An additional OData filter applied when retrieving customers from SAP B1SL.

  • `SynchronizeOnlyСustomersTypes`: String (comma-separated). Specifies which SAP B1SL customer card types to synchronize (e.g., "Customer", "Lead"). Defaults to "Customer".

  • `ExcludeInvalidCustomersFromSync`: Boolean. If true, customers marked as invalid or frozen in SAP B1SL will not be synchronized to App4Sales.

  • `UseVatLiableForCustomerVatLiable`: Boolean. Influences how `VatLiable` is determined from SAP B1SL.

  • `UseBillAddressFederalTaxIDAsCustomerVATLiableIndicator`: Boolean. Influences how `VatLiable` is determined from SAP B1SL based on address FederalTaxID.

  • `UseCustomerDiscount`: Boolean. If true, `DiscountPercent` from SAP B1SL is mapped to `Customer.Discount`, otherwise `Customer.Discount` is 0.

  • `DefaultActionPriceListCode`: String. Specifies a default action price list to apply to customers if none is set.

  • `EnablePriceDeduplication`: Boolean. Enables logic to migrate price list IDs if deduplication has occurred.

  • `SendVatNumberInFederalTaxIDField`: Boolean. If true, `VatCode` is sent to SAP as `FederalTaxID`, otherwise as `VatNumber`.

  • `CustomerSeries`: Integer. Specifies the series to use for new customers created in SAP.

  • `SendEmptyCardCode`: Boolean. If true, an empty customer code is sent to SAP for new customer creation.

  • `NewCustomersCodePrefix`: String. Prefix used when generating new customer codes.

  • `NewCustomerCodeNumber`: Integer. Starting number for new customer codes.

  • `CustomerClassificationProperty`: String. Defines the SAP B1SL property (direct or UDF) to use for `CustomerClassification`.

  • `CustomCustomerFieldsMapping`: JSON string. Defines mappings for `DynamicFreeFields` to SAP B1SL properties/UDFs on Customer, Contact Person, or Address.

  • `CustomerItemFilter`: Boolean. Enables processing of item filters from extra data and retention of existing item filters.

Known Limitations

  • SAP B1SL does not natively support "Visit" addresses. The connector creates a copy of the "Invoice" address and labels it as "Visit" to ensure compatibility with App4Sales.

  • The `LanguageCode` mapping to SAP B1SL is noted as a TODO in the code, indicating potential future enhancements or complexities in determining the correct integer values for SAP's language codes.

Did this answer your question?