diff --git a/packages/api/mocks/ValidateCartMutation.ts b/packages/api/mocks/ValidateCartMutation.ts index 1aafdc6095..ccd42d77bc 100644 --- a/packages/api/mocks/ValidateCartMutation.ts +++ b/packages/api/mocks/ValidateCartMutation.ts @@ -299,7 +299,7 @@ export const checkoutOrderFormItemsValidFetch = { init: { method: 'PATCH', headers: { 'content-type': 'application/json' }, - body: `{"orderItems":[{"quantity":1,"seller":"1","id":"18643698"},{"quantity":1,"seller":"1","id":"97907082"},{"quantity":1,"seller":"1","id":"64953394"},{"quantity":1,"seller":"1","id":"85095548"},{"quantity":1,"seller":"1","id":"1191988"},{"quantity":0,"seller":"1","id":"2737806","index":0}]}`, + body: `{"orderItems":[{"quantity":1,"seller":"1","id":"18643698","attachments":[]},{"quantity":1,"seller":"1","id":"97907082","attachments":[]},{"quantity":1,"seller":"1","id":"64953394","attachments":[]},{"quantity":1,"seller":"1","id":"85095548","attachments":[]},{"quantity":1,"seller":"1","id":"1191988","attachments":[]},{"quantity":0,"seller":"1","id":"2737806","index":0,"attachments":[]}]}`, }, result: JSON.parse( @@ -339,7 +339,7 @@ export const checkoutOrderFormItemsInvalidFetch = { method: 'PATCH', headers: { 'content-type': 'application/json' }, body: - '{"orderItems":[{"quantity":1,"seller":"1","id":"18643698","index":0},{"quantity":1,"seller":"1","id":"97907082","index":1},{"quantity":1,"seller":"1","id":"64953394","index":2},{"quantity":1,"seller":"1","id":"85095548","index":3},{"quantity":1,"seller":"1","id":"1191988","index":4}]}', + '{"orderItems":[{"quantity":1,"seller":"1","id":"18643698","index":0,"attachments":[]},{"quantity":1,"seller":"1","id":"97907082","index":1,"attachments":[]},{"quantity":1,"seller":"1","id":"64953394","index":2,"attachments":[]},{"quantity":1,"seller":"1","id":"85095548","index":3,"attachments":[]},{"quantity":1,"seller":"1","id":"1191988","index":4,"attachments":[]}]}', }, result: JSON.parse( '{"orderFormId":"edbe3b03c8c94827a37ec5a6a4648fd2","salesChannel":"1","loggedIn":false,"isCheckedIn":false,"storeId":null,"checkedInPickupPointId":null,"allowManualPrice":false,"canEditData":true,"userProfileId":null,"userType":null,"ignoreProfileData":false,"value":69824,"messages":[],"items":[{"uniqueId":"90276D2ADB274F12B61A4ADE11874A0A","id":"2737806","productId":"43559243","productRefId":"6327601885574","refId":"6464716212392","ean":null,"name":"Fantastic Soft Cheese plum","skuName":"plum","modalType":null,"parentItemIndex":null,"parentAssemblyBinding":null,"assemblies":[],"priceValidUntil":"2023-03-29T14:32:10Z","tax":0,"price":34912,"listPrice":55757,"manualPrice":null,"manualPriceAppliedBy":null,"sellingPrice":34912,"rewardValue":0,"isGift":false,"additionalInfo":{"dimension":null,"brandName":"Acer","brandId":"2000002","offeringInfo":null,"offeringType":null,"offeringTypeId":null},"preSaleDate":null,"productCategoryIds":"/9285/9294/","productCategories":{"9285":"Kitchen and Home Appliances","9294":"Appliances"},"quantity":2,"seller":"1","sellerChain":["1"],"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/168396-55-55/nihil.jpg?v=637753027573130000","detailUrl":"/fantastic-soft-cheese/p","components":[],"bundleItems":[],"attachments":[],"attachmentOfferings":[],"offerings":[],"priceTags":[],"availability":"available","measurementUnit":"un","unitMultiplier":1,"manufacturerCode":null,"priceDefinition":{"calculatedSellingPrice":34912,"total":69824,"sellingPrices":[{"value":34912,"quantity":2}]}}],"selectableGifts":[],"totalizers":[{"id":"Items","name":"Items Total","value":69824}],"shippingData":{"address":null,"logisticsInfo":[{"itemIndex":0,"selectedSla":null,"selectedDeliveryChannel":null,"addressId":null,"slas":[],"shipsTo":["BRA","USA"],"itemId":"2737806","deliveryChannels":[{"id":"delivery"}]}],"selectedAddresses":[],"availableAddresses":[],"pickupPoints":[]},"clientProfileData":null,"paymentData":{"updateStatus":"updated","installmentOptions":[{"paymentSystem":"6","bin":null,"paymentName":null,"paymentGroupName":null,"value":69824,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":69824,"total":69824,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":69824,"total":69824}]}]},{"paymentSystem":"201","bin":null,"paymentName":null,"paymentGroupName":null,"value":69824,"installments":[{"count":1,"hasInterestRate":false,"interestRate":0,"value":69824,"total":69824,"sellerMerchantInstallments":[{"id":"STOREFRAMEWORK","count":1,"hasInterestRate":false,"interestRate":0,"value":69824,"total":69824}]}]}],"paymentSystems":[{"id":6,"name":"Boleto Bancário","groupName":"bankInvoicePaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"6","template":"bankInvoicePaymentGroup-template","requiresDocument":false,"isCustom":false,"description":null,"requiresAuthentication":false,"dueDate":"2022-04-05T14:18:23.1569301Z","availablePayments":null},{"id":201,"name":"Free","groupName":"custom201PaymentGroupPaymentGroup","validator":{"regex":null,"mask":null,"cardCodeRegex":null,"cardCodeMask":null,"weights":null,"useCvv":false,"useExpirationDate":false,"useCardHolderName":false,"useBillingAddress":false},"stringId":"201","template":"custom201PaymentGroupPaymentGroup-template","requiresDocument":false,"isCustom":true,"description":"Free pay to test checkout payments","requiresAuthentication":false,"dueDate":"2022-04-05T14:18:23.1569301Z","availablePayments":null}],"payments":[],"giftCards":[],"giftCardMessages":[],"availableAccounts":[],"availableTokens":[],"availableAssociations":{}},"marketingData":null,"sellers":[{"id":"1","name":"VTEX","logo":""}],"clientPreferencesData":{"locale":"en-US","optinNewsLetter":null},"commercialConditionData":null,"storePreferencesData":{"countryCode":"USA","saveUserData":true,"timeZone":"Central Standard Time","currencyCode":"USD","currencyLocale":1033,"currencySymbol":"$","currencyFormatInfo":{"currencyDecimalDigits":2,"currencyDecimalSeparator":".","currencyGroupSeparator":",","currencyGroupSize":3,"startsWithCurrencySymbol":true}},"giftRegistryData":null,"openTextField":null,"invoiceData":null,"customData":{"customApps":[{"fields":{"cartEtag":"b7442f1dbacbc50fb2eb0e512b7bfdf0"},"id":"faststore","major":1}]},"itemMetadata":{"items":[{"id":"2737806","seller":"1","name":"Fantastic Soft Cheese plum","skuName":"plum","productId":"43559243","refId":"6464716212392","ean":null,"imageUrl":"http://storeframework.vteximg.com.br/arquivos/ids/168396-55-55/nihil.jpg?v=637753027573130000","detailUrl":"/fantastic-soft-cheese/p","assemblyOptions":[]}]},"hooksData":null,"ratesAndBenefitsData":{"rateAndBenefitsIdentifiers":[],"teaser":[]},"subscriptionData":null,"itemsOrdination":null}' diff --git a/packages/api/src/__generated__/schema.ts b/packages/api/src/__generated__/schema.ts index a7787b7315..59f25144f3 100644 --- a/packages/api/src/__generated__/schema.ts +++ b/packages/api/src/__generated__/schema.ts @@ -472,6 +472,8 @@ export type StorePropertyValue = { __typename?: 'StorePropertyValue'; /** Property name. */ name: Scalars['String']; + /** Property id. This propert changes according to the content of the object. */ + propertyID: Scalars['String']; /** Property value. May hold a string or the string representation of an object. */ value: Scalars['ObjectOrString']; /** Specifies the nature of the value */ diff --git a/packages/api/src/platforms/vtex/index.ts b/packages/api/src/platforms/vtex/index.ts index 7143019e03..516fbcc997 100644 --- a/packages/api/src/platforms/vtex/index.ts +++ b/packages/api/src/platforms/vtex/index.ts @@ -14,6 +14,7 @@ import { StoreReview } from './resolvers/review' import { StoreSearchResult } from './resolvers/searchResult' import { StoreSeo } from './resolvers/seo' import { ObjectOrString } from './resolvers/objectOrString' +import { StorePropertyValue } from './resolvers/propertyValue' import type { Loaders } from './loaders' import type { Clients } from './clients' import type { Channel } from './utils/channel' @@ -68,6 +69,7 @@ const Resolvers = { StoreReview, StoreProductGroup, StoreSearchResult, + StorePropertyValue, ObjectOrString, Query, Mutation, diff --git a/packages/api/src/platforms/vtex/resolvers/product.ts b/packages/api/src/platforms/vtex/resolvers/product.ts index 21d0c6acac..87f66246ee 100644 --- a/packages/api/src/platforms/vtex/resolvers/product.ts +++ b/packages/api/src/platforms/vtex/resolvers/product.ts @@ -5,7 +5,10 @@ import type { EnhancedCommercialOffer } from '../utils/enhanceCommercialOffer' import type { Resolver } from '..' import type { PromiseType } from '../../../typings' import type { Query } from './query' -import { VALUE_REFERENCES } from '../utils/propertyValue' +import { + attachmentToPropertyValue, + VALUE_REFERENCES, +} from '../utils/propertyValue' import type { Attachment } from '../clients/commerce/types/OrderForm' type QueryProduct = PromiseType> @@ -91,23 +94,24 @@ export const StoreProduct: Record> & { ) .sort(bestOfferFirst), isVariantOf: (root) => root, - additionalProperty: ({ variations = [], attachmentsValues }) => { - const propertyValueVariations = variations.flatMap(({ name, values }) => - values.map((value) => ({ - name, - value, - valueReference: VALUE_REFERENCES.variation, - })) + additionalProperty: ({ + // Search uses the name variations for specifications + variations: specifications = [], + attachmentsValues = [], + }) => { + const propertyValueSpecifications = specifications.flatMap( + ({ name, values }) => + values.map((value) => ({ + name, + value, + valueReference: VALUE_REFERENCES.specification, + })) ) - const propertyValueAttachments = (attachmentsValues ?? []).map( - (attachment) => ({ - name: attachment.name, - value: attachment.content, - valueReference: VALUE_REFERENCES.attachment, - }) + const propertyValueAttachments = attachmentsValues.map( + attachmentToPropertyValue ) - return [...propertyValueVariations, ...propertyValueAttachments] + return [...propertyValueSpecifications, ...propertyValueAttachments] }, } diff --git a/packages/api/src/platforms/vtex/resolvers/propertyValue.ts b/packages/api/src/platforms/vtex/resolvers/propertyValue.ts new file mode 100644 index 0000000000..3707143b08 --- /dev/null +++ b/packages/api/src/platforms/vtex/resolvers/propertyValue.ts @@ -0,0 +1,12 @@ +import type { Resolver } from '..' +import type { IStorePropertyValue } from '../../../__generated__/schema' +import { getPropertyId } from '../utils/propertyValue' + +type Root = IStorePropertyValue + +export const StorePropertyValue: Record> = { + propertyID: (root) => getPropertyId(root), + name: ({ name }) => name, + value: ({ value }) => value, + valueReference: ({ valueReference }) => valueReference, +} diff --git a/packages/api/src/platforms/vtex/resolvers/validateCart.ts b/packages/api/src/platforms/vtex/resolvers/validateCart.ts index 546d514056..d224d4158c 100644 --- a/packages/api/src/platforms/vtex/resolvers/validateCart.ts +++ b/packages/api/src/platforms/vtex/resolvers/validateCart.ts @@ -12,7 +12,11 @@ import type { OrderFormItem, } from '../clients/commerce/types/OrderForm' import type { Context } from '..' -import { VALUE_REFERENCES } from '../utils/propertyValue' +import { + attachmentToPropertyValue, + getPropertyId, + VALUE_REFERENCES, +} from '../utils/propertyValue' type Indexed = T & { index?: number } @@ -21,26 +25,12 @@ const getAttachments = (item: IStoreOffer) => (i) => i.valueReference === VALUE_REFERENCES.attachment ) -const serializeAttachment = (item: IStoreOffer) => { - const attachments = getAttachments(item) - - if (attachments?.length === 0) { - return null - } - - return attachments - ?.map( - (attachment) => `${attachment.name}:${JSON.stringify(attachment.value)}` - ) - .join('-') -} - const getId = (item: IStoreOffer) => [ item.itemOffered.sku, item.seller.identifier, item.price, - serializeAttachment(item), + item.itemOffered.additionalProperty?.map(getPropertyId).join('-'), ] .filter(Boolean) .join('::') @@ -57,6 +47,7 @@ const orderFormItemToOffer = ( sku: item.id, image: [], name: item.name, + additionalProperty: item.attachments.map(attachmentToPropertyValue), }, index, }) @@ -68,7 +59,7 @@ const offerToOrderItemInput = ( seller: offer.seller.identifier, id: offer.itemOffered.sku, index: offer.index, - attachments: getAttachments(offer)?.map((attachment) => ({ + attachments: (getAttachments(offer) ?? []).map((attachment) => ({ name: attachment.name, content: attachment.value, })), diff --git a/packages/api/src/platforms/vtex/utils/propertyValue.ts b/packages/api/src/platforms/vtex/utils/propertyValue.ts index 1086d3ea14..6c240213ca 100644 --- a/packages/api/src/platforms/vtex/utils/propertyValue.ts +++ b/packages/api/src/platforms/vtex/utils/propertyValue.ts @@ -1,5 +1,22 @@ +import type { IStorePropertyValue } from '../../../__generated__/schema' +import type { Attachment } from '../clients/commerce/types/OrderForm' +import { md5 } from './md5' + export const VALUE_REFERENCES = { - variation: 'VARIATION', attachment: 'ATTACHMENT', specification: 'SPECIFICATION', } as const + +export function attachmentToPropertyValue(attachment: Attachment) { + return { + name: attachment.name, + value: attachment.content, + valueReference: VALUE_REFERENCES.attachment, + } +} + +export function getPropertyId(item: IStorePropertyValue) { + return md5( + `${item.name}:${JSON.stringify(item.value)}:${item.valueReference}` + ) +} diff --git a/packages/api/src/typeDefs/propertyValue.graphql b/packages/api/src/typeDefs/propertyValue.graphql index 665c166eb1..a91cff45a0 100644 --- a/packages/api/src/typeDefs/propertyValue.graphql +++ b/packages/api/src/typeDefs/propertyValue.graphql @@ -2,6 +2,10 @@ Properties that can be associated with products and products groups. """ type StorePropertyValue { + """ + Property id. This propert changes according to the content of the object. + """ + propertyID: String! """ Property value. May hold a string or the string representation of an object. """