Skip to main content
Solved

Shopify Connector Line Item Properties


Hi Guys,

I need help to sync Line Item properties to Acumatica. Shopify apparently stores the variant options in and array format. Default connector only syncs the 1st option from line item properties but it could have up to 10+ options in that field.

Is there a way we can create a loop to loop through the array and show all the options in Acumatica?

 

Thanks for your help.

21 replies

Userlevel 5
Badge +1

@sams,  could you provide more details? Which version are you using? The question is related to the product import or sales order import? Any screen shots? 

Userlevel 1
Badge

@simonliang91 we are on 23R2. It is sales order import.
Our products have customizable options see attached screen shot.  Wood, Stain Color, and all the options are stored in Line Item properties. 

I can mapped Line Item properties to the notes in sales orders but it only brings over the 1st option and doesn’t loop through and get the others.

 

Userlevel 4
Badge

We had the same exact issue on a store we opened, but didn’t have time to figure out a solution. 

With another Shopify site coming soon, I would also love to see a solution, if there is one. 

Userlevel 5
Badge +1

@sams, because item properties is an array, the note field in SalesOrder is a single field, so system only gets the first value of item properties and assigns it to note field. 

In this case, you can create a customization code to collect all values from item properties and then assign to note field.

Userlevel 4
Badge

@sams, because item properties is an array, the note field in SalesOrder is a single field, so system only gets the first value of item properties and assigns it to note field. 

In this case, you can create a customization code to collect all values from item properties and then assign to note field.

@simonliang91 
Thanks for the info! Would we be customizing the e-commerce connector itself? Is there any guides for something similar you’re aware of?

Thanks again, 

Userlevel 5
Badge +1

@gorazem , Yes you can customize the connector by yourself. You can follow the standard Acumatica customization development process. The following methods are the key methods in the each entity Processor:

  1.  FetchBucketsForImport/FetchBucketsForExport is the key method for screen BC501000(Prepare Data), it will fetch the key data to create the sync record.
  2. GetBucketForImport is the key method for fetching external data during the import data process. If you want to fetch extra data, you can add your own code here. 
  3. MapBucketImport is the key method for the default mapping between external data and ERP data object. If you want to do your own mapping during the import process, you can add the code here.
  4. RemapBucketImport is the key method for the custom mapping, all custom mappings in the screen BC202000(Entities) will be executed in this method during the import process.
  5. SaveBucketImport is the key method for saving data to ERP during the import process. 
     
  6. GetBucketForExport is the key method for fetching ERP data during the export data process. If you want to fetch extra data from ERP, you can add your own code here. 
  7. MapBucketExport is the key method for the default mapping between ERP data and external data object. If you want to do your own mapping during the export process, you can add the code here.
  8. RemapBucketExport is the key method for the custom mapping, all custom mappings in the screen BC202000(Entities) will be executed in this method during the export process.
  9. SaveBucketExport is the key method for saving data to External platform during the export process.  
Userlevel 4
Badge

@gorazem , Yes you can customize the connector by yourself. You can follow the standard Acumatica customization development process. The following methods are the key methods in the each entity Processor:

  1.  FetchBucketsForImport/FetchBucketsForExport is the key method for screen BC501000(Prepare Data), it will fetch the key data to create the sync record.
  2. GetBucketForImport is the key method for fetching external data during the import data process. If you want to fetch extra data, you can add your own code here. 
  3. MapBucketImport is the key method for the default mapping between external data and ERP data object. If you want to do your own mapping during the import process, you can add the code here.
  4. RemapBucketImport is the key method for the custom mapping, all custom mappings in the screen BC202000(Entities) will be executed in this method during the import process.
  5. SaveBucketImport is the key method for saving data to ERP during the import process. 
     
  6. GetBucketForExport is the key method for fetching ERP data during the export data process. If you want to fetch extra data from ERP, you can add your own code here. 
  7. MapBucketExport is the key method for the default mapping between ERP data and external data object. If you want to do your own mapping during the export process, you can add the code here.
  8. RemapBucketExport is the key method for the custom mapping, all custom mappings in the screen BC202000(Entities) will be executed in this method during the export process.
  9. SaveBucketExport is the key method for saving data to External platform during the export process.  

Thanks for that info that’s great to have! 

I was looking at the GetExternCustomFieldValue method under the SPSalesOrderProcessor graph, I noticed the FirstOrDefault method getting called here and think it might be related to the line items properties. I haven’t done a ton of customization yet so fumbling through it lol.

But thanks again for that info that’s great to have to help learn the customization options more. I think the Salesforce connector isn’t available when I try to view it under “view business logic” so I assumed shopify would be too at first. 

Userlevel 1
Badge

@simonliang91 I had a developer on shopify create a custom field that puts a ll the line item properties in one field as text and not array but now Acumatica doesn’t see that field.

How would the code look to loop through the array and import it to Acumatica?

 

 

Userlevel 5
Badge +1

@gorazem, the GetExternCustomFieldValue method uses to handle the order item properties mapping, but the main functionality is ensure the value from item property can be assigned to the correct SalesOrder detail in ERP. 

Userlevel 5
Badge +1

@sams, if you create a customization package for it, it’s easy to do the mapping by yourself:

  1. The bucket.Order.Extern is the Shopify order data model
  2. The bucket.Order.Extern.LineItems represents the array of line items
  3. In each bucket.Order.Extern.LineItems, you can find the properties list in the OrderLineItem.Properties field
  4. The bucket.Order.Local is the ERP order data model
  5. bucket.Order.Local.Note is the note field of SalesOrder
  6. So you can loop through the OrderLineItem.Properties and assign it to bucket.Order.Local.Note

 

Userlevel 1
Badge

Hi @simonliang91,
Thanks for your help. I get an error message.

 

using PX.Api.ContractBased.Models;
using PX.Commerce.Core;
using PX.Commerce.Core.API;
using PX.Commerce.Objects;
using PX.Commerce.Shopify.API.GraphQL;
using PX.Commerce.Shopify.API.REST;
using PX.Common;
using PX.Data;
using PX.Objects.Common;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.IN;
using PX.Objects.SO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using PX.Objects;
using PX.Commerce.Shopify;


public class ShopifyOrderLineItem
{
public int ShopifyLineId { get; set; } // Unique identifier from Shopify
public List<ShopifyProperty> Properties { get; set; } // List of properties
}

public class ShopifyProperty
{
public string Name { get; set; }
public string Value { get; set; }
}

namespace PX.Commerce.Shopify
{
public class SPSalesOrderProcessor_Extension : PXGraphExtension<PX.Commerce.Shopify.SPSalesOrderProcessor>
{
// Your method to sync Shopify order data to Acumatica

public delegate void MapBucketImportDelegate(SPSalesOrderBucket bucket, IMappedEntity existing);
public void MapBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, MapBucketImportDelegate baseMethod)
{
// Example Shopify Order Data (this should come from your integration logic)
var shopifyOrder = bucket.Order.Extern;

// Get the Acumatica Sales Order
var acumaticaOrder = bucket.Order.Local;

// Loop through each line item in the Shopify order
foreach (var shopifyLineItem in shopifyOrder.LineItems)
{
// Find the corresponding line item in Acumatica Sales Order
var acumaticaLineItem = acumaticaOrder
.Details
.FirstOrDefault(line => line.LineNbr == shopifyLineItem.OrderLineItem.Properties);

if (acumaticaLineItem != null)
{
// Loop through properties of the Shopify line item
foreach (var property in shopifyLineItem.Properties)
{
// Assuming each property has a 'Name' and 'Value'
var propertyName = property.Name;
var propertyValue = property.Value;

// Map the Shopify property to Acumatica line item note
// Modify the following code based on your specific field mappings
if (propertyName == "CustomField1")
{
acumaticaLineItem.Note = propertyValue;
}
// Add more mappings as needed
}
}
}

// Save the updated Acumatica Sales Order

}
}
}

This is the error
 

\App_RuntimeCode\SPSalesOrderProcessor.cs(56): error CS1061: 'OrderLineItem' does not contain a definition for 'OrderLineItem' and no accessible extension method 'OrderLineItem' accepting a first argument of type 'OrderLineItem' could be found (are you missing a using directive or an assembly reference?)
Userlevel 5
Badge +1

@sams

  1. You should add the code at the beginning to use the default mapping first, otherwise the local object could not be created : 
    baseMethod(bucket, existing);

     

  2. shopifyLineItem is a OrderLineItem type object. You can find the corresponding order detail by: 
    // Find the corresponding line item in Acumatica Sales Order
    var acumaticaLineItem = acumaticaOrder
    .Details
    .FirstOrDefault(line => string.Equals(line.ExternalRef?.Value, shopifyLineItem.Id.ToString()));

     

Userlevel 1
Badge

@simonliang91 
Sorry I am not very knowledgeable with the code.

Where do I insert this line? What line number?
 

await baseMethod(bucket, existing,cancellationToken);

 

Userlevel 5
Badge +1

@sams , yes, you should insert this following line at the beginning of the method. Because you override the MapBucketImport method, original code will not be executed without the following code.

baseMethod(bucket, existing);

Userlevel 1
Badge

@simonliang91 
See attached screenshot.

 

Userlevel 5
Badge +1

@sams , you should put the code in line 44. I have modified the code to 
 

baseMethod(bucket, existing);

 

Userlevel 1
Badge

@simonliang91 Thank you for your help. I got the code validated and published but it is not putting the shopify line item properties in the note box.

I modified the code to get all line item properties.

using PX.Api.ContractBased.Models;
using PX.Commerce.Core;
using PX.Commerce.Core.API;
using PX.Commerce.Objects;
using PX.Commerce.Shopify.API.GraphQL;
using PX.Commerce.Shopify.API.REST;
using PX.Common;
using PX.Data;
using PX.Objects.Common;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.IN;
using PX.Objects.SO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using PX.Objects;
using PX.Commerce.Shopify;

public class ShopifyOrderLineItem
{
public int ShopifyLineId { get; set; } // Unique identifier from Shopify
public List<ShopifyProperty> Properties { get; set; } // List of properties
}

public class ShopifyProperty
{
public string Name { get; set; }
public string Value { get; set; }
}

namespace PX.Commerce.Shopify
{
public class SPSalesOrderProcessor_Extension : PXGraphExtension<PX.Commerce.Shopify.SPSalesOrderProcessor>
{
// Your method to sync Shopify order data to Acumatica
public delegate void MapBucketImportDelegate(SPSalesOrderBucket bucket, IMappedEntity existing);

public void MapBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, MapBucketImportDelegate baseMethod)

{
baseMethod(bucket, existing);
// Example Shopify Order Data (this should come from your integration logic)
var shopifyOrder = bucket.Order.Extern;

// Get the Acumatica Sales Order
var acumaticaOrder = bucket.Order.Local;

// Loop through each line item in the Shopify order
foreach (var shopifyLineItem in shopifyOrder.LineItems)
{
var acumaticaLineItem = acumaticaOrder
.Details
.FirstOrDefault(line => string.Equals(line.ExternalRef?.Value, shopifyLineItem.Id.ToString()));

if (acumaticaLineItem != null)
{
// Use reflection to get all properties of ShopifyProperty class
var shopifyProperties = typeof(ShopifyProperty).GetProperties();

foreach (var property in shopifyLineItem.Properties)
{
// Iterate through each property dynamically
foreach (var shopifyProp in shopifyProperties)
{
// Get the name and value of each property dynamically
var propertyName = shopifyProp.Name;
var propertyValue = shopifyProp.GetValue(property)?.ToString();

// Map the Shopify property to Acumatica line item note
// Modify the following code based on your specific field mappings
if (!string.IsNullOrEmpty(propertyValue))
{
// Example: Map dynamically based on property name
switch (propertyName)
{
case "Name":
acumaticaLineItem.Note = propertyValue;
break;
case "Value":
// Handle mapping based on other property names dynamically
break;
// Add more cases for additional properties as needed
}
}
}
}
}
}

Let me know what I am missing

Userlevel 5
Badge +1

@sams , your code looks good. You can just assign a single value to Note field without loop through the Properties to test the mapping first, and please check whether there is a custom mapping in the Entities screen(BC202000), it will overwrite the same mapping.

acumaticaLineItem.Note = "Test value";

And there is another option for you if you only want to map the specific property to the item note, you can create a custom mapping in the Entities screen as below, the “External Field” is the name of your property:
 

 

Userlevel 4
Badge

I tried something similar using your code. I’m not getting any errors, but I’m also not getting anything to work either lol. None of my trace logs are logging at all.

 

using PX.Api.ContractBased.Models;
using PX.Commerce.Core;
using PX.Commerce.Core.API;
using PX.Commerce.Objects;
using PX.Commerce.Shopify.API.REST;
using PX.Common;
using PX.Data;
using PX.Objects.Common;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.IN;
using PX.Objects.SO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using PX.Commerce.Shopify.API.GraphQL.DataProviders;
using System.Threading.Tasks;
using PX.Commerce.Shopify.API.GraphQL;
using PX.Objects;
using PX.Commerce.Shopify;

public class ShopifyOrderLineItem
{
public int ShopifyLineId { get; set; } // Unique identifier from Shopify
public List<ShopifyProperty> Properties { get; set; } // List of properties
}

public class ShopifyProperty
{
public string Name { get; set; }
public string Value { get; set; }
}

namespace PX.Commerce.Shopify
{
public class SPSalesOrderProcessor_Extension : PXGraphExtension<PX.Commerce.Shopify.SPSalesOrderProcessor>
{

public delegate void MapBucketImportDelegate(SPSalesOrderBucket bucket, IMappedEntity existing);
public void MapBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, MapBucketImportDelegate baseMethod)
{

PXTrace.WriteInformation("MapBucketImport");
baseMethod(bucket, existing);

// Get Shopify order data model
var shopifyOrder = bucket.Order.Extern;

// Get ERP order data model
var acumaticaOrder = bucket.Order.Local;

// Loop through each line item in the Shopify order
foreach (var shopifyLineItem in shopifyOrder.LineItems)
{
// Find the corresponding line item in Acumatica Sales Order
var acumaticaLineItem = acumaticaOrder
.Details
.FirstOrDefault(line => string.Equals(line.ExternalRef?.Value, shopifyLineItem.Id.ToString()));

if (acumaticaLineItem != null)
{
PXTrace.WriteInformation("Line Items Found!");
// Loop through properties of the Shopify line item
foreach (var property in shopifyLineItem.Properties)
{
// Assuming each property has a 'Name' and 'Value'
var propertyName = property.Name;
var propertyValue = property.Value;

// Map the Shopify property to Acumatica line item note

acumaticaLineItem.Note += propertyValue;

}
} else
{
PXTrace.WriteInformation("No Line Items Found");
acumaticaOrder.Note = "No Line Item Object found";
}
}

PXTrace.WriteInformation("Exiting MapBucketImport method");
// Need to Save the updated Acumatica Sales Order?

}
}
}

 

Userlevel 4
Badge

I haven’t gotten to test it with multiple items, or multiple line items, but so far I got this to work:

 

using PX.Api.ContractBased.Models;
using PX.Commerce.Core;
using PX.Commerce.Core.API;
using PX.Commerce.Objects;
using PX.Commerce.Shopify.API.REST;
using PX.Common;
using PX.Data;
using PX.Objects.Common;
using PX.Objects.CS;
using PX.Objects.GL;
using PX.Objects.IN;
using PX.Objects.SO;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Threading;
using PX.Commerce.Shopify.API.GraphQL.DataProviders;
using System.Threading.Tasks;
using PX.Commerce.Shopify.API.GraphQL;
using PX.Objects;
using PX.Commerce.Shopify;


namespace PX.Commerce.Shopify
{
public class SPSalesOrderProcessor_Extension : PXGraphExtension<PX.Commerce.Shopify.SPSalesOrderProcessor>
{
public delegate Task MapBucketImportDelegate(SPSalesOrderBucket bucket, IMappedEntity existing, CancellationToken cancellationToken);

[PXOverride]
public async Task MapBucketImport(SPSalesOrderBucket bucket, IMappedEntity existing, CancellationToken cancellationToken, MapBucketImportDelegate baseMethod)
{
PXTrace.WriteInformation("MapBucketImport method started");

await baseMethod(bucket, existing, cancellationToken);

// Get Shopify order data model
var shopifyOrder = bucket.Order.Extern;
PXTrace.WriteInformation($"Shopify Order ID: {shopifyOrder.Id}");

// Get ERP order data model
var acumaticaOrder = bucket.Order.Local;
PXTrace.WriteInformation($"Acumatica Order ID: {acumaticaOrder.OrderNbr?.Value}");

// Loop through each line item in the Shopify order
foreach (var shopifyLineItem in shopifyOrder.LineItems)
{
PXTrace.WriteInformation($"Processing Shopify Line Item ID: {shopifyLineItem.Id}");

// Find the corresponding line item in Acumatica Sales Order
var acumaticaLineItem = acumaticaOrder
.Details
.FirstOrDefault(line => string.Equals(line.ExternalRef?.Value, shopifyLineItem.Id.ToString()));

if (acumaticaLineItem != null)
{
PXTrace.WriteInformation("Matching Acumatica Line Item Found");
// Loop through properties of the Shopify line item
foreach (var property in shopifyLineItem.Properties)
{
var propertyName = property.Name;
var propertyValue = property.Value;
PXTrace.WriteInformation($"Property: {propertyName} = {propertyValue}");

// Map the Shopify property to Acumatica line item note
acumaticaLineItem.Note += propertyValue;
}
}
else
{
PXTrace.WriteInformation("No Matching Acumatica Line Item Found");
acumaticaOrder.Note = "No Line Item Object found";
}
}

PXTrace.WriteInformation("Exiting MapBucketImport method");
}
}
}

 

Userlevel 1
Badge

@gorazem This works with multiple line items. 
Thanks for you help.

@simonliang91 Thanks for your help.

Reply