Processor Classes

For each pair of entities that you want to synchronize between an e-commerce system and MYOB Acumatica, you need to create a processor class.

A processor classes perform the following functions:
  • Retrieval of the MYOB Acumatica records and external records
  • Providing of the default mapping logic
  • Providing of the export and import logic

Base Classes and Interface

A processor class implements the PX.Commerce.Core.IProcessor interface and derives from one of the following base abstract classes:
  • PX.Commerce.Core.BCProcessorSingleBase<TGraph, TEntityBucket, TPrimaryMapped>, which is a standard base class for processors that synchronizes records one by one.
  • PX.Commerce.Core.BCProcessorBulkBase<TGraph, TEntityBucket, TPrimaryMapped>, which can mass-process records for the entities that require higher performance during synchronization. For example, this base class is used for the processor classes for the availability and price list entities in the BigCommerce connector.
Both of these classes are graphs. Therefore, the processor class is a graph as well. In the processor base class, you pass as the type parameters of the class the graph itself, the bucket class, and the mapping class for the pair of entities that you want to synchronize.

Attributes of the Processor Class

You assign the following attributes to the processor class:
  • BCProcessor, which specifies the basic functions of the processor, such as which directions of synchronization are supported by the processor
  • Optional: BCProcessorRealtime, which specifies whether the processor works with push notifications and webhooks

Methods of the Processor Class

In the processor class, you implement the methods that perform the following functions:
  • The fetching of the records that have been changed in MYOB Acumatica or the external system

  • The checking and merging of duplicated records

  • The handling of real-time processing

  • Other supplementary functions

For details about the methods, see the API Reference.

Example

The following code shows an example of the processor implementation.

Tip: You can see this code on GitHub.
using PX.Commerce.Core;
using PX.Commerce.Core.API;
using PX.Commerce.Objects;
using PX.Data;
using PX.Objects.AR;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace WooCommerceTest
{
    [BCProcessor(typeof(WooCommerceConnector), BCEntitiesAttribute.Customer, 20, 
        BCCaptions.Customer,
        IsInternal = false,
        Direction = SyncDirection.Import,
        PrimaryDirection = SyncDirection.Import,
        PrimarySystem = PrimarySystem.Extern,
        PrimaryGraph = typeof(PX.Objects.AR.CustomerMaint),
        ExternTypes = new Type[] { typeof(CustomerData) },
        LocalTypes = new Type[] { typeof(PX.Commerce.Core.API.Customer) },
        AcumaticaPrimaryType = typeof(PX.Objects.AR.Customer),
        AcumaticaPrimarySelect = typeof(PX.Objects.AR.Customer.acctCD),
        URL = "user-edit.php?user_id={0}"
    )]
    [BCProcessorRealtime(PushSupported = false, HookSupported = false)]
    public class WooCustomerProcessor : BCProcessorSingleBase<WooCustomerProcessor, 
        WooCustomerEntityBucket, MappedCustomer>, IProcessor
    {
        public WooRestClient client;
        protected CustomerDataProvider customerDataProvider;

        //protected List<Country> countries;
        public CommerceHelper helper = PXGraph.CreateInstance<CommerceHelper>();


        #region Constructor
        public override void Initialise(IConnector iconnector, 
            ConnectorOperation operation)
        {
            base.Initialise(iconnector, operation);
            client = WooCommerceConnector.GetRestClient(
                GetBindingExt<BCBindingWooCommerce>());
            customerDataProvider = new CustomerDataProvider(client);
        }
        #endregion

        #region Import
        public override async Task MapBucketImport(WooCustomerEntityBucket bucket, 
            IMappedEntity existing, CancellationToken cancellationToken = default)
        {
            MappedCustomer customerObj = bucket.Customer;

            PX.Commerce.Core.API.Customer customerImpl = customerObj.Local = 
                new PX.Commerce.Core.API.Customer();
            customerImpl.Custom = GetCustomFieldsForImport();

            customerImpl.CustomerName = GetBillingCustomerName(customerObj.Extern).
                ValueField();
            customerImpl.AccountRef = APIHelper.ReferenceMake(customerObj.Extern.Id, 
                GetBinding().BindingName).ValueField();
            customerImpl.CustomerClass = customerObj.LocalID == null || 
                existing?.Local == null ?
                GetBindingExt<BCBindingExt>().CustomerClassID?.ValueField() : null;

            //Primary Contact
            customerImpl.PrimaryContact = GetPrimaryContact(customerObj.Extern);

            customerImpl.MainContact = SetBillingContact(customerObj.Extern);

            customerImpl.ShippingAddressOverride = true.ValueField();
            customerImpl.ShippingContactOverride = true.ValueField();
            customerImpl.ShippingContact = SetShippingContact(customerObj.Extern);

            BCBindingExt bindingExt = GetBindingExt<BCBindingExt>();
            if (bindingExt.CustomerClassID != null)
            {
                CustomerClass customerClass = PXSelect<CustomerClass, 
                    Where<CustomerClass.customerClassID, 
                    Equal<Required<CustomerClass.customerClassID>>>>.
                    Select(this, bindingExt.CustomerClassID);
                if (customerClass != null)
                {
                    customerClass.ShipVia.ValueField();

                }
            }

        }

        public virtual Contact GetPrimaryContact(CustomerData customer)
        {
            var contact = new Contact();
            contact.FirstName = !string.IsNullOrWhiteSpace(customer.FirstName) && 
                string.IsNullOrWhiteSpace(customer.LastName) ? 
                string.Empty.ValueField() : customer.FirstName.ValueField();
            contact.LastName = !string.IsNullOrWhiteSpace(customer.LastName) ? 
                customer.LastName.ValueField() :
                !string.IsNullOrWhiteSpace(customer.FirstName) && 
                string.IsNullOrWhiteSpace(customer.LastName) ?
                customer.FirstName.ValueField() : customer.Username.ValueField();
            contact.Email = customer.Email.ValueField();

            return contact;
        }

        public virtual Contact SetBillingContact(CustomerData customerObj)
        {
            Contact contactImpl = new Contact();
            contactImpl.DisplayName = GetBillingCustomerName(customerObj).
                ValueField();
            contactImpl.Attention = $"{customerObj.Billing?.FirstName} {
                customerObj.Billing?.LastName}".ValueField(); ;
            contactImpl.FullName = GetBillingCustomerName(customerObj).ValueField();
            contactImpl.FirstName = customerObj.Billing?.FirstName.ValueField();
            contactImpl.LastName = customerObj.Billing.LastName.ValueField();
            contactImpl.Email = customerObj.Billing.Email.ValueField();
            contactImpl.Address = MapAddress(customerObj.Billing);
            contactImpl.Active = true.ValueField();
            contactImpl.Phone1 = customerObj.Billing?.Phone.ValueField();
            contactImpl.Phone1Type = PhoneTypes.Business1.ValueField();

            return contactImpl;
        }

        public virtual Contact SetShippingContact(CustomerData customerObj)
        {
            Contact contactImpl = new Contact();
            contactImpl.DisplayName = GetShippingCustomerName(customerObj).
                ValueField();
            contactImpl.Attention = $"{customerObj.Shipping?.FirstName} {
                customerObj.Shipping?.LastName}".ValueField();
            contactImpl.FullName = GetShippingCustomerName(customerObj).ValueField();
            contactImpl.FirstName = customerObj.Shipping?.FirstName.ValueField();
            contactImpl.LastName = customerObj.Shipping.LastName.ValueField();
            contactImpl.Address = MapAddress(customerObj.Shipping);
            contactImpl.Active = true.ValueField();

            return contactImpl;
        }

        public virtual string GetBillingCustomerName(CustomerData data)
        {
            return !string.IsNullOrWhiteSpace(data.Billing.Company)
                ? data.Billing.Company
                : string.IsNullOrWhiteSpace($"{data.Billing?.FirstName} {
                data.Billing?.LastName}") ? data.Username : $"{
                data.Billing?.FirstName} {data.Billing?.LastName}";
        }

        public virtual string GetShippingCustomerName(CustomerData data)
        {
            return !string.IsNullOrWhiteSpace(data.Shipping?.Company)
                ? data.Shipping.Company
                : $"{data.Shipping?.FirstName} {data.Shipping?.LastName}";
        }

        public virtual Address MapAddress(CustomerAddressData addressObj)
        {
            var address = new Address();
            address.AddressLine1 = addressObj.Address1.ValueField();
            address.AddressLine2 = addressObj.Address2.ValueField();
            address.City = addressObj.City.ValueField();
            address.PostalCode = addressObj.PostalCode.ValueField();
            if (!string.IsNullOrWhiteSpace(addressObj.Country))
            {
                var selectedCountry = addressObj.Country;
                if (selectedCountry != null)
                {
                    address.Country = addressObj.Country.ValueField();
                    if (!string.IsNullOrWhiteSpace(addressObj.State))
                    {
                        address.State = addressObj.State.ValueField();
                    }
                }
            }

            return address;
        }

        public override async Task<PullSimilarResult<MappedCustomer>> PullSimilar(
            IExternEntity entity, CancellationToken cancellationToken = default)
        {
            var uniqueField = string.IsNullOrWhiteSpace(
                ((CustomerData)entity)?.Billing.Email) ? 
                ((CustomerData)entity)?.Email : 
                ((CustomerData)entity)?.Billing.Email;
            if (uniqueField == null) return null;

            List<MappedCustomer> result = new List<MappedCustomer>();
            foreach (PX.Objects.AR.Customer item in helper.CustomerByEmail.Select(
                uniqueField))
            {
                PX.Commerce.Core.API.Customer data = new PX.Commerce.Core.API.Customer() { 
                    SyncID = item.NoteID, SyncTime = item.LastModifiedDateTime };
                result.Add(new MappedCustomer(data, data.SyncID, data.SyncTime));
            }

            if (result == null || result?.Count == 0) return null;

            return new PullSimilarResult<MappedCustomer>() { 
                UniqueField = uniqueField, Entities = result };
        }

        public override async Task<PullSimilarResult<MappedCustomer>> PullSimilar(
            ILocalEntity entity, CancellationToken cancellationToken = default)
        {
            var uniqueField = ((PX.Commerce.Core.API.Customer)entity)?.
                MainContact?.Email?.Value;
            if (uniqueField == null) return null;
            IEnumerable<CustomerData> datas = customerDataProvider.GetAll(null);
            if (datas == null) return null;

            return new PullSimilarResult<MappedCustomer>() { 
                UniqueField = uniqueField, Entities = datas.Select(
                data => new MappedCustomer(data, data.Id.ToString(), 
                data.DateModified.ToDate())) };
        }

        public override async Task SaveBucketImport(WooCustomerEntityBucket bucket, 
            IMappedEntity existing, string operation, 
            CancellationToken cancellationToken = default)
        {
            MappedCustomer obj = bucket.Customer;

            PX.Commerce.Core.API.Customer impl = cbapi.Put(obj.Local, obj.LocalID);

            obj.AddLocal(impl, impl.SyncID, impl.SyncTime);
            UpdateStatus(obj, operation);
        }
        #endregion

        #region Export

        public override async Task MapBucketExport(WooCustomerEntityBucket bucket, 
            IMappedEntity existing, CancellationToken cancellationToken = default)
        {
        }

        public override async Task SaveBucketExport(WooCustomerEntityBucket bucket, 
            IMappedEntity existing, string operation, 
            CancellationToken cancellationToken = default)
        {
        }
        #endregion

        #region Pull
        public override async Task<MappedCustomer> PullEntity(Guid? localID, 
            Dictionary<string, object> externalInfo, 
            CancellationToken cancellationToken = default)
        {
            throw new NotImplementedException();
        }
        public override async Task<MappedCustomer> PullEntity(string externID, 
            string externalInfo, CancellationToken cancellationToken = default)
        {
            throw new NotImplementedException();
        }

        public override async Task FetchBucketsForImport(DateTime? minDateTime, 
            DateTime? maxDateTime, PXFilterRow[] filters, 
            CancellationToken cancellationToken = default)
        {
            IEnumerable<CustomerData> customers = customerDataProvider.GetAll(null);

            foreach (CustomerData data in customers)
            {
                WooCustomerEntityBucket bucket = CreateBucket();

                MappedCustomer mapped = bucket.Customer = bucket.Customer.Set(
                    data, data.Id.ToString(), data.LastName, data.DateModified.ToDate());
                EnsureStatus(mapped, SyncDirection.Import);
            }
        }

        public override async Task FetchBucketsForExport(DateTime? minDateTime, 
            DateTime? maxDateTime, PXFilterRow[] filters, 
            CancellationToken cancellationToken = default)
        {
            throw new NotImplementedException();
        }

        public override async Task<EntityStatus> GetBucketForImport(
            WooCustomerEntityBucket bucket, BCSyncStatus syncstatus, 
            CancellationToken cancellationToken = default)
        {
            CustomerData data = client.Get<CustomerData>(
                string.Format("/customers/{0}", syncstatus.ExternID));

            MappedCustomer obj = bucket.Customer = bucket.Customer.Set(data, 
                data.Id.ToString(), data.LastName, data.DateModified.ToDate());
            EntityStatus status = EnsureStatus(obj, SyncDirection.Import);

            return status;
        }

        public override async Task<EntityStatus> GetBucketForExport(
            WooCustomerEntityBucket bucket, BCSyncStatus bcstatus, 
            CancellationToken cancellationToken = default)
        {
            PX.Commerce.Core.API.Customer impl = 
                cbapi.GetByID<PX.Commerce.Core.API.Customer>(bcstatus.LocalID, 
                GetCustomFieldsForExport());
            if (impl == null) return EntityStatus.None;

            bucket.Customer = bucket.Customer.Set(impl, impl.SyncID, impl.SyncTime);
            EntityStatus status = EnsureStatus(bucket.Customer, SyncDirection.Export);

            return status;
        }
        #endregion
    }
    public static class PhoneTypes
    {
        public static string Business1 = "B1";
    }
}