Skip to main content

eAccounting - Customer sync

eAccounting Connector - CustomerUpdate This function synchronizes customer-related data from the eAccounting ERP system into the App4Sa...

Updated over a week ago

eAccounting Connector - CustomerUpdate

This function synchronizes customer-related data from the eAccounting ERP system into the App4Sales platform. It processes customer core information, associated addresses, contact persons, item class discounts, and customer-specific dashboards. The update runs in batches and incorporates various transformations, validations, and conditional logic to ensure data integrity and consistency.

3. Data Source Configuration

The CustomerUpdate function primarily receives customer data as a list of Customer objects from the eAccounting connector. Additionally, supplementary customer data can be sourced from CSV files, accessible via the _databaseManager.ExtraCustomerData. This extra data is provided as a dictionary where the key is the CustomerCode and the value is a CsvCustomer object.

Customer Core Fields

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

CustomerCode

customer.CustomerCode

Mandatory. Customers with an empty CustomerCode are logged as invalid and skipped.

CustomerName

customer.CustomerName

Mandatory. Customers with an empty CustomerName are logged as invalid and skipped.

CustomerGuid

Derived

  • If an inactive customer with the same CustomerCode exists (identified by "~" prefix), its CustomerGuid is reused.

  • If UpdaterHelper.IsUpdater is true, attempts to retrieve from a local cache of customer codes and GUIDs.

  • Otherwise, attempts to retrieve from _portalServerProvider.GetCustomerGuid(customer.CustomerCode, customer.CustomerName).

  • If still empty, a new Guid.NewGuid() is generated.

Created

customer.Created

If DateTime.MinValue, defaults to DateTime.Now. Truncated to seconds precision.

Sysmodified

customer.Sysmodified

Truncated to seconds precision.

PasswordWebshop

customer.PasswordWebshop

Set to null if UpdaterHelper.IsUpdater is true.

LanguageCode

customer.LanguageCode

Mapped via MappedLanguagesContext from ERP language code to App4Sales mapped language code. If source LanguageCode is empty or no mapping is found, no change is applied. If !UpdaterHelper.IsUpdater and incoming LanguageCode is empty, it can be preserved from an existing customer.

ActionPriceList

customer.ActionPriceList

  • If the connector setting DefaultActionPriceListCode is configured and customer.ActionPriceList is not set, the value is derived from the price list code.

  • If price deduplication is enabled, ActionPriceList IDs may be migrated to a new ID based on a lookup table (KeySettings.GenericPriceListMigrations).

  • If after migration, the ActionPriceList is not found in existing price lists, it is set to null.

UsesPriceField

customer.UsesPriceField

  • If price deduplication is enabled, UsesPriceField IDs may be migrated to a new ID based on a lookup table (KeySettings.GenericPriceListMigrations).

  • If after migration, the UsesPriceField is not found in existing price lists, it is defaulted to 1.

PaymentConditionCode

customer.PaymentConditionCode

Set to null if an empty string to prevent foreign key issues.

Email

customer.Email

Whitespace is trimmed from the email address.

DynamicFreeFields

customer.DynamicFreeFields

If the source contains XML other than an empty <extraFields />, it is deserialized into DynamicFreeFields and then converted to a generic list of extra fields, leveraging customerExtraFields defined in _customExtraFieldsContext.

VatCode

customer.VatCode

Set to null if the administration's country is USA (AdministrationSession.CurrentSession.Administration.IsUSA is true).

VatLiable

customer.VatLiable

Set to false if the administration's country is USA (AdministrationSession.CurrentSession.Administration.IsUSA is true).

InternalCode

customer.InternalCode

If !UpdaterHelper.IsUpdater and incoming InternalCode is empty, it can be preserved from an existing customer.

ExtraData

customer.ExtraData

If !UpdaterHelper.IsUpdater and incoming ExtraData is empty, it can be preserved from an existing customer.

FreeFields

customer.FreeFieldList

If customer.FreeFields is empty and customer.FreeFieldList contains items, the FreeFieldList is serialized to XML and stored in customer.FreeFields.

Addresses

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

AddressType

address.AddressType

  • If empty, defaults to "Visit".

  • Ensured that both "Visit" and "Delivery" address types exist. If only one is provided, the other is created as a copy.

  • If no "Visit" or "Delivery" address exists, but other address types are present, copies are made from the highest priority address (first main, then first available) for "Visit" and "Delivery".

CustomerGuid

customer.CustomerGuid

Inherited from the parent customer.

AddressId

address.AddressId

If empty, a default ID is generated via addr.SetIdAsDefault(). Reset to null when creating a copied address (e.g., creating a Delivery address from a Visit address).

ExternalId

address.ExternalId

Used to identify existing addresses. Reset to null when creating a copied address.

Email

address.Email

  • Unicode unit separators () are removed.

  • If empty and IsMainAddress is true, attempts to inherit from customer.Email, then from other customer addresses, then from customer contact persons.

Iso2

address.Iso2

  • Mapped from mappedCountryCodes if available and Iso2 exists.

  • If empty, attempts to derive from address.Country using CultureHelper.GetIso2FromCountry.

  • Whitespace trimmed.

Country

address.Country

If empty, defaults to Iso2.

Street

address.AddressLine.Street or Derived

If AddressLine is null, parsed from AddressLine1 using AddressUtils.ParseAddress.

HouseNumber

address.AddressLine.Number or Derived

If AddressLine is null, parsed from AddressLine1 using AddressUtils.ParseAddress.

Addition

address.AddressLine.Addition or Derived

If AddressLine is null, parsed from AddressLine1 using AddressUtils.ParseAddress.

AddressLine1

address.AddressLine1 or Derived

  • If AddressLine is populated, formatted from Street, HouseNumber, and Addition using AddressUtils.FormatAddressLine, considering the IsUSA administration setting.

  • Invalid XML characters are removed.

IsMainAddress

address.IsMainAddress

  • Ensures at least one "Visit" address and one "Delivery" address has IsMainAddress set to true if none are explicitly marked.

Contacts

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

CustomerGuid

customer.CustomerGuid

Inherited from the parent customer.

FirstName

contactPerson.FirstName

If FullName is provided but FirstName, MiddleName, and LastName are null, attempts to parse from FullName (first word).

MiddleName

contactPerson.MiddleName

If FullName is provided but FirstName, MiddleName, and LastName are null, attempts to parse from FullName (words between first and last).

LastName

contactPerson.LastName

If FullName is provided but FirstName, MiddleName, and LastName are null, attempts to parse from FullName (last word).

FullName

contactPerson.FullName

If null, constructed from FirstName, MiddleName, and LastName.

Initials

Derived

First character of FirstName if FirstName is not empty.

PasswordWebshop

contactPerson.PasswordWebshop

Set to null if UpdaterHelper.IsUpdater is true.

ContactId

contactPerson.ContactId

If empty and UpdaterHelper.IsUpdater is true, a hash code-based ID is generated.

IsMainContactPerson

contactPerson.IsMainContactPerson or Derived

  • If customer.MainContactPerson is provided, it is set to true.

  • If no contact is explicitly marked as main, the first contact person in the list is designated as the main contact.

DynamicFreeFields

contactPerson.DynamicFreeFields

If not empty, deserialized into DynamicFreeFields and converted to extra fields using ConvertToExtraFields, leveraging contactPersonExtraFields.

Extra Data & Free Fields

This section details how additional data, potentially from CSV sources, can extend or override standard customer fields and how generic "free fields" and item filters are processed.

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

(Various Customer Fields)

CsvCustomer properties

Properties of the CsvCustomer object (sourced from _databaseManager.ExtraCustomerData) with non-null values will overwrite corresponding properties in the Customer object if their names match. (e.g., a Street property in CsvCustomer would overwrite customer.Street). Dictionary-type properties are skipped.

customer.FreeFieldList

CsvCustomer.UnknownElements (keys starting with "FreeField_")

Key-value pairs from CsvCustomer.UnknownElements where the key starts with "FreeField_" (case-insensitive) and the value is not empty are extracted. The "FreeField_" prefix is removed from the key to become the Caption, and the value becomes the Content of a CustomerFreeField object, which is added to customer.FreeFieldList.

customer.FreeFields

customer.FreeFieldList

If customer.FreeFields is empty and customer.FreeFieldList contains items, the FreeFieldList is serialized to XML and stored in customer.FreeFields.

customer.ItemFilter

CsvCustomer.UnknownElements (keys starting with "ItemFilter_")

Only processed if the connector setting CustomerItemFilter is enabled. Key-value pairs from CsvCustomer.UnknownElements where the key starts with "ItemFilter_" (case-insensitive) and the value is not empty are extracted. The value is expected to be a '~' delimited string of item class values. These are mapped against configured item classes and their values, then formatted into a query string for customer.ItemFilter.

customer.DynamicFreeFields

customer.DynamicFreeFields (XML string)

If the source customer.DynamicFreeFields contains XML other than an empty <extraFields />, it is deserialized into DynamicFreeFields and then converted to a generic list of extra fields, leveraging customerExtraFields defined in _customExtraFieldsContext.

contactPerson.DynamicFreeFields

contactPerson.DynamicFreeFields (XML string)

Similar to customer dynamic free fields, if the source contactPerson.DynamicFreeFields contains XML, it is deserialized and converted to extra fields leveraging contactPersonExtraFields.

Discounts & Dashboards

This section outlines how customer-specific dashboards are processed and stored, and how discounts based on item class values are handled.

Item Class Value Discounts

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

CustomerGuid

customer.CustomerGuid

Inherited from the parent customer.

discount

icv.discount

Only discounts with a value greater than 0 and not null are processed and stored.

(Other ItemClassValueDiscount fields)

icv properties

The ItemClassValueDiscount objects are stored directly, but only those with a positive discount value.

Customer Dashboards

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

DashboardData

customer.CustomerDashboard

The customer.CustomerDashboard string is expected to be Base64 encoded. It is decoded and stored as binary data. If decoding fails or the source is empty/whitespace, no dashboard is stored, and the field is cleared.

CustomerCode

customer.CustomerCode

Used to associate the dashboard with the customer.

Customer Notes

App4Sales Field

Source Field (API/Excel/DB)

Logic/Notes

CustomerNotes table

customer.CustomerNote

If customer.CustomerNote is not empty, any existing notes for the customer (from salesrep "BackOffice") are deleted, and a new note is inserted with subject "Backoffice note" and the content of customer.CustomerNote.

Special Logic & Filters

  • Invalid Customer Filtering: Customers with empty CustomerName or CustomerCode are filtered out at the beginning of the Update method and logged with a warning.

  • Batch Processing: Customer updates are processed in batches of 100 customers (batchSize = 100) to manage memory and database transactions efficiently.

  • Customer Reactivation Logic: If UpdaterHelper.IsUpdater is true, the system attempts to reactivate previously inactive customers. It looks for CustomerGuids associated with customer codes prefixed with "~" (_customerCodeGuidReactivationLinks) and reuses them to preserve historical data (e.g., notes, order links).

  • Price Deduplication Migration: If EnablePriceDeduplication setting is enabled (either in ConnectorBaseSettings or SyncSettings), existing price list IDs (UsesPriceField and ActionPriceList) are potentially migrated to new IDs based on a lookup table stored in KeySettings.GenericPriceListMigrations. If a migrated price list ID is not found in existing price lists, ActionPriceList is nulled, and UsesPriceField defaults to 1.

  • Error Handling: A try-catch block surrounds the processing of each customer within a batch. Errors are logged, and _portalServerProvider.ResetDataUpdateIntervals("CustomerUpdate") is called, likely to prevent repeated failed updates.

  • Address Validation: In ProcessAddresses, addresses are filtered to ensure they are not empty or have an ExternalId set. Logic exists to ensure at least "Visit" and "Delivery" address types are present, even if dummy addresses need to be created.

  • Item Class Map Loading: itemClassMap is only built (ItemClassesContext.Instance.BuildItemClassMap()) if extraCustomerData is available and AdministrationSession.CurrentSession.SyncSettings.CustomerItemFilter is enabled.

  • USA VAT Handling: For administrations in the USA (AdministrationSession.CurrentSession.Administration.IsUSA), VatCode is set to null and VatLiable to false to prevent storing VAT information not relevant to USA tax regulations.

  • Post-Script Customer Validation: After _scriptingService.ExecuteScript with "UpdateCustomers" (for the batch), addresses and contacts are re-validated to ensure they belong to customers that were not removed by the script.

Script Hooks

The CustomerUpdate function utilizes several scripting hooks, allowing for custom logic execution at different stages of the update process:

  • BeforeUpdateCustomers: Executed before any customer processing begins for a batch. It receives the list of customers and can be used to modify or filter them prior to the main update logic.

  • UpdateCustomers (Batch Specific): Executed after each customer in a batch has been processed internally but before the batch is committed to the database. This hook allows for custom transformations or validations on the processed customer objects before they are saved.

  • UpdateCustomers (Final): Executed once at the very end of the Update method, after all batches have been processed, committed, and contact persons have been updated. This can be used for final post-processing or triggering external notifications.

Related Settings & Prerequisites

  • Customer Item Filter (SyncSettings.CustomerItemFilter):

    • Purpose: Controls whether item filters specified in extra customer data (e.g., from CSV ItemFilter_ fields) are processed and applied to customers.

    • Impact: If enabled, ItemFilter_ values from CsvCustomer.UnknownElements will be used to populate customer.ItemFilter. When updating a single customer, if this setting is enabled, the existing ItemFilter from the database is read to prevent losing fixed item filters during updates from other sources (e.g., mobile apps).

  • Default Action Price List Code (ConnectorBaseSettings.DefaultActionPriceListCode):

    • Purpose: Specifies a default action price list code to be applied to customers if they don't have one explicitly set.

    • Impact: If this setting has a value and customer.ActionPriceList is not set, the system attempts to find a price list ID using this code and assigns it to customer.ActionPriceList.

  • Enable Price Deduplication (ConnectorBaseSettings.EnablePriceDeduplication / SyncSettings.EnablePriceDeduplication):

    • Purpose: Enables a mechanism to deduplicate price lists, which might involve migrating existing price list IDs to new ones.

    • Impact: If enabled, the system checks for price list migration rules defined in KeySettings.GenericPriceListMigrations. It will then attempt to update customer.UsesPriceField and customer.ActionPriceList to their migrated IDs. If a migrated price list ID is not found in the existing price lists, ActionPriceList is nulled, and UsesPriceField is defaulted to 1.

  • Administration Country (Administration.IsUSA):

    • Purpose: Indicates whether the current administration is configured for the USA.

    • Impact: If true, customer.VatCode is set to null and customer.VatLiable is set to false. Also influences address formatting.

Known Limitations

  • Mandatory Customer Fields: The update process strictly requires CustomerCode and CustomerName to be non-empty. Customers lacking these fields are skipped entirely.

  • CustomerDashboard Format: The customer.CustomerDashboard field is expected to be a Base64 encoded string. Any other format will lead to the dashboard data being discarded.

  • Specific CSV Extra Data Structure: The ProcessExtraDataForCustomer method assumes a specific naming convention for free fields (FreeField_) and item filters (ItemFilter_) within the CsvCustomer.UnknownElements dictionary. Deviations will lead to these fields not being processed correctly.

  • Price Deduplication Dependency: The price deduplication feature relies on a KeySettings.GenericPriceListMigrations entry. If this setting is not properly configured, price list IDs might not be correctly migrated, potentially leading to incorrect pricing.

  • Address Deduplication/Uniqueness: While there's logic to create missing Visit/Delivery addresses, the overall address processing does not explicitly prevent the creation of logically duplicate addresses if the incoming data contains them with different AddressId or ExternalId values, beyond the DistinctBy on (e.CustomerGuid, e.AddressId, e.AddressType). It mainly focuses on ensuring certain types exist.

  • Contact Person ID Generation in Updater Mode: In Updater mode, if a contact person lacks a ContactId, a hash code-based ID is generated. This might not be a stable or human-readable identifier.

  • Customer Note Handling: Customer notes are deleted and re-inserted as a single "Backoffice note". This overwrites any previous "Backoffice note" and does not support multiple notes for this specific type of note.

Did this answer your question?