From 4c526e91aea002d907f461314810edd4a268749f Mon Sep 17 00:00:00 2001 From: Jannik Zinkl Date: Tue, 7 Nov 2023 23:36:20 +0100 Subject: [PATCH 1/2] feat: saleor swatch DRAFT --- .../src/products/index.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pkg/integration-saleor-entities/src/products/index.ts b/pkg/integration-saleor-entities/src/products/index.ts index 2d5d8c93d..843f5cbcd 100644 --- a/pkg/integration-saleor-entities/src/products/index.ts +++ b/pkg/integration-saleor-entities/src/products/index.ts @@ -914,7 +914,6 @@ export class SaleorProductSyncService { }); } } - // TODO: handle the case when we have a hexcode value if (attr.attribute.type === "PRODUCT_REFERENCE") { /// We store our internal product Id in value of product reference attributes. /// We need to aks our DB for the saleor product id @@ -971,6 +970,18 @@ export class SaleorProductSyncService { saleorVariantId.saleorProductVariant[0].id, ], }); + // Handle the special case SWATCH where the hex code and the name is given + } else if ( + attr.attribute.type === "SWATCH" && + attr.hexColor && + attr.value + ) { + attributes.push({ + id: attr.attribute.saleorAttributes[0].id, + swatch: { + value: attr.hexColor, + }, + }); } else { attributes.push({ id: attr.attribute.saleorAttributes[0].id, From d3e7fd34b4bcefd295a7004dc4609478e7fbe594 Mon Sep 17 00:00:00 2001 From: Jannik Zinkl Date: Thu, 16 Nov 2023 17:22:24 +0100 Subject: [PATCH 2/2] feat: more complex handling SWATCH --- .../src/products/index.ts | 78 ++++++++++- pkg/saleor/src/api/generated/graphql.ts | 122 +++++++++++++++++- pkg/saleor/src/api/queries/attributes.gql | 35 +++++ 3 files changed, 228 insertions(+), 7 deletions(-) diff --git a/pkg/integration-saleor-entities/src/products/index.ts b/pkg/integration-saleor-entities/src/products/index.ts index 662f9fe4f..06c584cf4 100644 --- a/pkg/integration-saleor-entities/src/products/index.ts +++ b/pkg/integration-saleor-entities/src/products/index.ts @@ -632,6 +632,65 @@ export class SaleorProductSyncService { } } + /** + * Unfortunately, we can't set swatch attribute values + * directly in the product create / update mutation. We need to + * first check, if the attribute value does already exist in saleor, + * create it if not and return the slug of the attribute value, so + * that it can be used as value in the product create / update mutation + */ + private async handleSwatchAttributeInSaleor( + saleorAttributeId: string, + attributeValueName: string, + attributeValueHex: string, + ): Promise { + const searchResult = await this.saleorClient.attributeValueSearch({ + attributeId: saleorAttributeId, + searchvalue: attributeValueName, + }); + if (!searchResult.attribute?.choices?.edges?.[0].node.id) { + this.logger.info( + `Creating swatch attribute value ${attributeValueName} for saleor attribute ${saleorAttributeId}`, + ); + const resp = await this.saleorClient.attributeHexValueCreate({ + attributeId: saleorAttributeId, + attributeValueHex, + attributeValueName, + }); + + if ( + resp.attributeValueCreate?.errors && + resp.attributeValueCreate?.errors.length > 0 + ) { + this.logger.error( + `Error creating swatch attribute value ${attributeValueName} for saleor attribute ${saleorAttributeId}: ${JSON.stringify( + resp.attributeValueCreate.errors, + )}`, + ); + return ""; + } + if (!resp.attributeValueCreate?.attributeValue?.slug) { + this.logger.error( + `Error creating swatch attribute value ${attributeValueName} for saleor attribute ${saleorAttributeId}: No slug returned`, + ); + return ""; + } + + this.logger.info( + `Returning swatch slug ${resp.attributeValueCreate?.attributeValue?.slug} for swatch attribute value ${attributeValueName} for saleor attribute ${saleorAttributeId}`, + ); + return resp.attributeValueCreate?.attributeValue?.slug; + } + + if (!searchResult.attribute?.choices?.edges?.[0].node.slug) { + this.logger.error( + `Error getting swatch attribute value ${attributeValueName} for saleor attribute ${saleorAttributeId}: No slug returned`, + ); + return ""; + } + return searchResult.attribute?.choices?.edges?.[0].node.slug; + } + /** * Find and create all products, that are not yet created in Saleor. * Update products, that got changed since the last run @@ -903,6 +962,8 @@ export class SaleorProductSyncService { * Prepare the attributes to fit the saleor schema */ for (const attr of attributesWithSaleorAttributes) { + const saleorAttributeId = + attr.attribute.saleorAttributes[0].id; if (attr.attribute.type === "BOOLEAN") { const value = parseBoolean(attr.value); if (value === undefined) { @@ -912,7 +973,7 @@ export class SaleorProductSyncService { continue; } else { attributes.push({ - id: attr.attribute.saleorAttributes[0].id, + id: saleorAttributeId, boolean: value, }); } @@ -941,7 +1002,7 @@ export class SaleorProductSyncService { continue; } attributes.push({ - id: attr.attribute.saleorAttributes[0].id, + id: saleorAttributeId, references: [saleorProductId.saleorProducts[0].id], }); } else if (attr.attribute.type === "VARIANT_REFERENCE") { @@ -968,7 +1029,7 @@ export class SaleorProductSyncService { continue; } attributes.push({ - id: attr.attribute.saleorAttributes[0].id, + id: saleorAttributeId, references: [ saleorVariantId.saleorProductVariant[0].id, ], @@ -979,15 +1040,20 @@ export class SaleorProductSyncService { attr.hexColor && attr.value ) { + const value = await this.handleSwatchAttributeInSaleor( + saleorAttributeId, + attr.value, + attr.hexColor, + ); attributes.push({ - id: attr.attribute.saleorAttributes[0].id, + id: saleorAttributeId, swatch: { - value: attr.hexColor, + value, }, }); } else { attributes.push({ - id: attr.attribute.saleorAttributes[0].id, + id: saleorAttributeId, values: [attr.value], }); } diff --git a/pkg/saleor/src/api/generated/graphql.ts b/pkg/saleor/src/api/generated/graphql.ts index a1d6e6754..25a751bd6 100644 --- a/pkg/saleor/src/api/generated/graphql.ts +++ b/pkg/saleor/src/api/generated/graphql.ts @@ -21460,6 +21460,7 @@ export enum ProductVariantBulkErrorCode { NotProductsVariant = "NOT_PRODUCTS_VARIANT", ProductNotAssignedToChannel = "PRODUCT_NOT_ASSIGNED_TO_CHANNEL", Required = "REQUIRED", + StockAlreadyExists = "STOCK_ALREADY_EXISTS", Unique = "UNIQUE", } @@ -32251,6 +32252,58 @@ export type AttributeSyncQuery = { } | null; }; +export type AttributeValueSearchQueryVariables = Exact<{ + attributeId: Scalars["ID"]; + searchvalue?: InputMaybe; +}>; + +export type AttributeValueSearchQuery = { + __typename?: "Query"; + attribute?: { + __typename?: "Attribute"; + id: string; + name?: string | null; + choices?: { + __typename?: "AttributeValueCountableConnection"; + edges: Array<{ + __typename?: "AttributeValueCountableEdge"; + node: { + __typename?: "AttributeValue"; + id: string; + name?: string | null; + slug?: string | null; + value?: string | null; + }; + }>; + } | null; + } | null; +}; + +export type AttributeHexValueCreateMutationVariables = Exact<{ + attributeId: Scalars["ID"]; + attributeValueName: Scalars["String"]; + attributeValueHex: Scalars["String"]; +}>; + +export type AttributeHexValueCreateMutation = { + __typename?: "Mutation"; + attributeValueCreate?: { + __typename?: "AttributeValueCreate"; + errors: Array<{ + __typename?: "AttributeError"; + field?: string | null; + code: AttributeErrorCode; + message?: string | null; + }>; + attributeValue?: { + __typename?: "AttributeValue"; + id: string; + slug?: string | null; + value?: string | null; + } | null; + } | null; +}; + export type CategoryValuesFragment = { __typename?: "Category"; id: string; @@ -33146,7 +33199,7 @@ export const AttributeCreateDocument = gql` export const BulkOrderCreateDocument = gql` mutation bulkOrderCreate($orders: [OrderBulkCreateInput!]!) { orderBulkCreate( - errorPolicy: REJECT_FAILED_ROWS + errorPolicy: REJECT_EVERYTHING orders: $orders stockUpdatePolicy: SKIP ) { @@ -33547,6 +33600,47 @@ export const AttributeSyncDocument = gql` } } `; +export const AttributeValueSearchDocument = gql` + query attributeValueSearch($attributeId: ID!, $searchvalue: String) { + attribute(id: $attributeId) { + choices(first: 2, filter: { search: $searchvalue }) { + edges { + node { + id + name + slug + value + } + } + } + id + name + } + } +`; +export const AttributeHexValueCreateDocument = gql` + mutation attributeHexValueCreate( + $attributeId: ID! + $attributeValueName: String! + $attributeValueHex: String! + ) { + attributeValueCreate( + attribute: $attributeId + input: { name: $attributeValueName, value: $attributeValueHex } + ) { + errors { + field + code + message + } + attributeValue { + id + slug + value + } + } + } +`; export const SaleorCronCategoriesDocument = gql` query saleorCronCategories($first: Int, $last: Int, $after: String) { categories(first: $first, last: $last, after: $after) { @@ -34436,6 +34530,32 @@ export function getSdk(requester: Requester) { options, ) as Promise; }, + attributeValueSearch( + variables: AttributeValueSearchQueryVariables, + options?: C, + ): Promise { + return requester< + AttributeValueSearchQuery, + AttributeValueSearchQueryVariables + >( + AttributeValueSearchDocument, + variables, + options, + ) as Promise; + }, + attributeHexValueCreate( + variables: AttributeHexValueCreateMutationVariables, + options?: C, + ): Promise { + return requester< + AttributeHexValueCreateMutation, + AttributeHexValueCreateMutationVariables + >( + AttributeHexValueCreateDocument, + variables, + options, + ) as Promise; + }, saleorCronCategories( variables?: SaleorCronCategoriesQueryVariables, options?: C, diff --git a/pkg/saleor/src/api/queries/attributes.gql b/pkg/saleor/src/api/queries/attributes.gql index 5775a06e7..1d8aa3fa8 100644 --- a/pkg/saleor/src/api/queries/attributes.gql +++ b/pkg/saleor/src/api/queries/attributes.gql @@ -27,3 +27,38 @@ query attributeSync($first: Int!, $after: String) { } } } + +query attributeValueSearch($attributeId: ID!, $searchvalue: String) { + attribute(id: $attributeId) { + choices(first: 2, filter: {search: $searchvalue}) { + edges { + node { + id + name + slug + value + } + } + } + id + name + } +} + +mutation attributeHexValueCreate($attributeId: ID!, $attributeValueName: String!, $attributeValueHex: String!) { + attributeValueCreate( + attribute: $attributeId + input: {name: $attributeValueName, value: $attributeValueHex} + ) { + errors { + field + code + message + } + attributeValue { + id + slug + value + } + } +} \ No newline at end of file