Skip to content

[Shopify] Connector - Staff to Salesperson mapping #28743

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

GediminasGaubys
Copy link
Contributor

This pull request does not have a related issue as it's part of the delivery for development agreed directly with @AndreiPanko

  • New functionality is only available for B2B stores. Hide (don’t use if the store is not B2B, see example – Companies/ Catalogs)
  • Create the new Shopify Staff Mapping page (similar to Languages/Markets).
  • Add action to read staff list. Allow user to map Staff entry to the Salesperson/purchaser entry.
  • When importing orders, for B2B stores also import Staff node (id only). Only for B2B stores to avoid errors during calls placed by non-plus stores.
  • Add the Sales Person Code field to the Shopify Order header table/page and initialize during mapping routine (similar to Map Customer / Payment Method).
  • When converting order from Shopify Order to Business Central sales document populate the Sales Person in the created sales order, similar to Payment Method.
  • Out of scope – export sales person to Shopify as Staff

New functionality is only available for B2B stores. Hide (don’t use if the store is not B2B, see example – Companies/ Catalogs)
Create the new Shopify Staff Mapping page (similar to Languages/Markets).
Add action to read staff list. Allow user to map Staff entry to the Salesperson/purchaser entry.
When importing orders, for B2B stores also import Staff node (id only). Only for B2B stores to avoid errors during calls placed by non-plus stores.
Add the Sales Person Code field to the Shopify Order header table/page and initialize during mapping routine (similar to Map Customer / Payment Method).
When converting order from Shopify Order to Business Central sales document populate the Sales Person in the created sales order, similar to Payment Method.
Out of scope – export sales person to Shopify as Staff
@GediminasGaubys GediminasGaubys requested a review from a team as a code owner June 6, 2025 09:24
Copy link
Contributor

github-actions bot commented Jun 6, 2025

Could not find linked issues in the pull request description. Please make sure the pull request description contains a line that contains 'Fixes #' followed by the issue number being fixed. Use that pattern for every issue you want to link.

Copy link
Contributor

@pri-kise pri-kise left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see any updated permission sets in this pull request.

@@ -464,6 +465,10 @@ codeunit 30161 "Shpfy Import Order"
JsonHelper.GetValueIntoField(JOrder, 'purchasingEntity.company.mainContact.customer.phone', OrderHeaderRecordRef, OrderHeader.FieldNo("Company Main Contact Phone No."));
if Format(OrderHeaderRecordRef.Field(OrderHeader.FieldNo("Sell-to Customer Name")).Value) = '' then
JsonHelper.GetValueIntoField(JOrder, 'purchasingEntity.company.name', OrderHeaderRecordRef, OrderHeader.FieldNo("Sell-to Customer Name"));
if Shop."B2B Enabled" then begin
StaffMemberId := CommunicationMgt.GetIdOfGId(JsonHelper.GetValueAsText(JOrder, 'staffMember.id'));
this.SetSalespersonOnOrderHeader(OrderHeader."Shop Code", StaffMemberId, OrderHeaderRecordRef);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think we don't have clear guidelines on the usage of this but we should try to align with the syntax in this object. Therefore I'd suggest that you don't use this for local prcoedures in a codeunit.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This object was create before MSFT introduced this. Omitting it triggers CodeCop AA0248

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AA0248 is not enforced for this app's ruleset. this should be removed

field(1; "Shop Code"; Code[20])
{
Caption = 'Shop Code';
DataClassification = CustomerContent;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The DataClassification = CustomerContent; on field level could be removed, since it's definied on object level.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. Code updated

/// Implements the IGraphQL interface for retrieving Shopify staff members using GraphQL.
/// </summary>
codeunit 30400 "Shpfy GQL GetStaffMembers" implements "Shpfy IGraphQL"
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the other shopy Graph Codeunits could be set to Access=Internnal.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

/// </summary>
enum 30168 "Shpfy Staff Account Type"
{
Extensible = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this Enum made extensible on purpose?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed

ApplicationArea = All;
Caption = 'Refresh';
Image = Refresh;
Promoted = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would suggest to use the new Syntax for Promoted Actions here.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code updated

@AndriusA-Companial
Copy link

I don't see any updated permission sets in this pull request.

Good catch - permissions updated

local procedure SetSalespersonOnOrderHeader(ShopCode: Code[20]; StaffMemberId: BigInteger; OrderHeaderRecordRef: RecordRef)
var
StaffMember: Record "Shpfy Staff Member";
ShpfyOrderHeader: Record "Shpfy Order Header";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ShpfyOrderHeader: Record "Shpfy Order Header";
OrderHeader: Record "Shpfy Order Header";

@@ -6,7 +6,7 @@ codeunit 30207 "Shpfy GQL OrderHeader" implements "Shpfy IGraphQL"

procedure GetGraphQL(): Text
begin
exit('{"query": "query { order(id: \"gid:\/\/shopify\/Order\/{{OrderId}}\") { legacyResourceId name createdAt confirmed updatedAt cancelReason cancelledAt closed closedAt currentSubtotalLineItemsQuantity test email phone poNumber customer { legacyResourceId email phone defaultAddress { id phone }} displayAddress { id firstName lastName company address1 address2 zip city provinceCode province countryCode country phone } shippingAddress { id name firstName lastName company address1 address2 zip city provinceCode province countryCode country phone latitude longitude } billingAddressMatchesShippingAddress billingAddress { id name firstName lastName company address1 address2 zip city provinceCode province countryCode country phone } publication { name } app { name } currencyCode presentmentCurrencyCode fullyPaid unpaid note customAttributes { key value } discountCode displayFinancialStatus displayFulfillmentStatus edited totalWeight refundable returnStatus risk { assessments { facts { description sentiment } provider { title } riskLevel }} tags paymentGatewayNames processedAt requiresShipping sourceIdentifier paymentTerms { id dueInDays paymentTermsName paymentTermsType translatedName paymentSchedules(first: 1) { nodes { dueAt } } } taxesIncluded cartDiscountAmountSet { presentmentMoney { amount } shopMoney { amount }} currentCartDiscountAmountSet { presentmentMoney { amount } shopMoney { amount }} currentSubtotalPriceSet { presentmentMoney { amount } shopMoney { amount }} currentTotalDiscountsSet { presentmentMoney { amount } shopMoney { amount }} currentTotalDutiesSet { presentmentMoney { amount } shopMoney { amount }} currentTotalPriceSet { presentmentMoney { amount } shopMoney { amount }} currentTotalTaxSet { presentmentMoney { amount } shopMoney { amount }} netPaymentSet { presentmentMoney { amount } shopMoney { amount }} originalTotalDutiesSet { presentmentMoney { amount } shopMoney { amount }} originalTotalPriceSet { presentmentMoney { amount } shopMoney { amount }} refundDiscrepancySet { presentmentMoney { amount } shopMoney { amount }} subtotalPriceSet { presentmentMoney { amount } shopMoney { amount }} totalCapturableSet { presentmentMoney { amount } shopMoney { amount }} totalDiscountsSet { presentmentMoney { amount } shopMoney { amount }} totalOutstandingSet { presentmentMoney { amount } shopMoney { amount }} totalPriceSet { presentmentMoney { amount } shopMoney { amount }} totalReceivedSet { presentmentMoney { amount } shopMoney { amount }} totalRefundedSet { presentmentMoney { amount } shopMoney { amount }} totalRefundedShippingSet { presentmentMoney { amount } shopMoney { amount }} totalShippingPriceSet { presentmentMoney { amount } shopMoney { amount }} totalTaxSet { presentmentMoney { amount } shopMoney { amount }} totalTipReceivedSet { presentmentMoney { amount } shopMoney { amount }} taxLines { channelLiable rate ratePercentage title priceSet { presentmentMoney { amount } shopMoney { amount }}} refunds { legacyResourceId updatedAt } returns(first: 20) { pageInfo { endCursor hasNextPage } nodes { id }} purchasingEntity { ... on PurchasingCompany { company { id name mainContact { id customer { legacyResourceId email phone }}} location { id }}}}}"}');
exit('{"query": "query { order(id: \"gid:\/\/shopify\/Order\/{{OrderId}}\") { legacyResourceId name createdAt confirmed updatedAt cancelReason cancelledAt closed closedAt currentSubtotalLineItemsQuantity test email phone poNumber customer { legacyResourceId email phone defaultAddress { id phone }} displayAddress { id firstName lastName company address1 address2 zip city provinceCode province countryCode country phone } shippingAddress { id name firstName lastName company address1 address2 zip city provinceCode province countryCode country phone latitude longitude } billingAddressMatchesShippingAddress billingAddress { id name firstName lastName company address1 address2 zip city provinceCode province countryCode country phone } publication { name } app { name } currencyCode presentmentCurrencyCode fullyPaid unpaid note customAttributes { key value } discountCode displayFinancialStatus displayFulfillmentStatus edited totalWeight refundable returnStatus risk { assessments { facts { description sentiment } provider { title } riskLevel }} tags paymentGatewayNames processedAt requiresShipping sourceIdentifier staffMember { id } paymentTerms { id dueInDays paymentTermsName paymentTermsType translatedName paymentSchedules(first: 1) { nodes { dueAt } } } taxesIncluded cartDiscountAmountSet { presentmentMoney { amount } shopMoney { amount }} currentCartDiscountAmountSet { presentmentMoney { amount } shopMoney { amount }} currentSubtotalPriceSet { presentmentMoney { amount } shopMoney { amount }} currentTotalDiscountsSet { presentmentMoney { amount } shopMoney { amount }} currentTotalDutiesSet { presentmentMoney { amount } shopMoney { amount }} currentTotalPriceSet { presentmentMoney { amount } shopMoney { amount }} currentTotalTaxSet { presentmentMoney { amount } shopMoney { amount }} netPaymentSet { presentmentMoney { amount } shopMoney { amount }} originalTotalDutiesSet { presentmentMoney { amount } shopMoney { amount }} originalTotalPriceSet { presentmentMoney { amount } shopMoney { amount }} refundDiscrepancySet { presentmentMoney { amount } shopMoney { amount }} subtotalPriceSet { presentmentMoney { amount } shopMoney { amount }} totalCapturableSet { presentmentMoney { amount } shopMoney { amount }} totalDiscountsSet { presentmentMoney { amount } shopMoney { amount }} totalOutstandingSet { presentmentMoney { amount } shopMoney { amount }} totalPriceSet { presentmentMoney { amount } shopMoney { amount }} totalReceivedSet { presentmentMoney { amount } shopMoney { amount }} totalRefundedSet { presentmentMoney { amount } shopMoney { amount }} totalRefundedShippingSet { presentmentMoney { amount } shopMoney { amount }} totalShippingPriceSet { presentmentMoney { amount } shopMoney { amount }} totalTaxSet { presentmentMoney { amount } shopMoney { amount }} totalTipReceivedSet { presentmentMoney { amount } shopMoney { amount }} taxLines { channelLiable rate ratePercentage title priceSet { presentmentMoney { amount } shopMoney { amount }}} refunds { legacyResourceId updatedAt } returns(first: 20) { pageInfo { endCursor hasNextPage } nodes { id }} purchasingEntity { ... on PurchasingCompany { company { id name mainContact { id customer { legacyResourceId email phone }}} location { id }}}}}"}');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will not work for all shops. Querying staffMember will fail on shops without Shopify Plus, however with current implementation it will be queried for all stores. You need to implement some sort of conditional querying of staffMembers.

{
Caption = 'Salesperson Code';
DataClassification = CustomerContent;
TableRelation = "Salesperson/Purchaser" where(Blocked = const(false));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This means importing an order with a staff member mapped to a blocked salesperson will fail... I don't think we would want that

@AndreiPanko waiting for your input here

@@ -71,5 +71,6 @@ permissionset 30102 "Shpfy - Edit"
tabledata "Shpfy Tax Area" = IMD,
tabledata "Shpfy Transaction Gateway" = IMD,
tabledata "Shpfy Translation" = IMD,
tabledata "Shpfy Variant" = IMD;
tabledata "Shpfy Variant" = IMD,
tabledata "Shpfy Staff Member" = IMD;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow alphabetical order in permission sets

@@ -68,6 +68,7 @@ permissionset 30104 "Shpfy - Objects"
table "Shpfy Translation" = X,
table "Shpfy Transaction Gateway" = X,
table "Shpfy Variant" = X,
table "Shpfy Staff Member" = X,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please follow alphabetical order in permission sets

TempShpfyStaffMember.Phone := CopyStr(JsonHelper.GetValueAsText(JStaffMemberInfo, 'phone'), 1, MaxStrLen(TempShpfyStaffMember.Phone));
TempShpfyStaffMember.Insert(false);
end;
exit(true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This procedure never exits false, why is the return value needed?

Modified: Boolean;
ProcessedStaffMembers: List of [BigInteger];
begin
TempShpfyStaffMember.SetRange("Shop Code", ShopCode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this needed? Aren't you already querying by shop when you create records in this temp record?

/// </summary>
/// <param name="ShopCode">The code of the Shopify shop.</param>
/// <param name="TempShpfyStaffMember">The temporary staff member record.</param>
local procedure CreateUpdateStaffMembers(ShopCode: Code[20]; var TempShpfyStaffMember: Record "Shpfy Staff Member" temporary)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
local procedure CreateUpdateStaffMembers(ShopCode: Code[20]; var TempShpfyStaffMember: Record "Shpfy Staff Member" temporary)
local procedure CreateUpdateStaffMembers(ShopCode: Code[20]; var TempStaffMember: Record "Shpfy Staff Member" temporary)

var
SalespersonPurchaser: Record "Salesperson/Purchaser";
ShpfyStaffMember: Record "Shpfy Staff Member";
SalespersonPurchaserMappingErr: Label '%1 = %2 already mapped for the Shopify Staff Member %3.', Comment = '%1 = Salesperson/Purchaser table caption, %2 = Salesperson/Purchaser code, %3 = Shopify Staff Member name';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
SalespersonPurchaserMappingErr: Label '%1 = %2 already mapped for the Shopify Staff Member %3.', Comment = '%1 = Salesperson/Purchaser table caption, %2 = Salesperson/Purchaser code, %3 = Shopify Staff Member name';
SalespersonPurchaserMappingErr: Label '%1 %2 already mapped to Shopify Staff Member %3.', Comment = '%1 = Salesperson/Purchaser table caption, %2 = Salesperson/Purchaser code, %3 = Shopify Staff Member name';
Suggested change
SalespersonPurchaserMappingErr: Label '%1 = %2 already mapped for the Shopify Staff Member %3.', Comment = '%1 = Salesperson/Purchaser table caption, %2 = Salesperson/Purchaser code, %3 = Shopify Staff Member name';
SalespersonPurchaserMappingErr: Label '%1 = %2 already mapped for the Shopify Staff Member %3.', Comment = '%1 = Salesperson/Purchaser table caption, %2 = Salesperson/Purchaser code, %3 = Shopify Staff Member name';

trigger OnValidate()
var
SalespersonPurchaser: Record "Salesperson/Purchaser";
ShpfyStaffMember: Record "Shpfy Staff Member";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ShpfyStaffMember: Record "Shpfy Staff Member";
StaffMember: Record "Shpfy Staff Member";

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants