-
Notifications
You must be signed in to change notification settings - Fork 655
[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
base: main
Are you sure you want to change the base?
[Shopify] Connector - Staff to Salesperson mapping #28743
Conversation
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
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. |
There was a problem hiding this 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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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" | ||
{ |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Code updated
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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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 }}}}}"}'); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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'; |
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"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ShpfyStaffMember: Record "Shpfy Staff Member"; | |
StaffMember: Record "Shpfy Staff Member"; |
This pull request does not have a related issue as it's part of the delivery for development agreed directly with @AndreiPanko