Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 49 additions & 42 deletions vtex/utils/extensions/simulation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { AppContext } from "../../mod.ts";
import { batch } from "../batch.ts";
import { OpenAPI } from "../openapi/vcs.openapi.gen.ts";
import { getSegmentFromBag, isAnonymous } from "../segment.ts";
import { aggregateOffers } from "../transform.ts";
import {
aggregateOffers,
SCHEMA_LIST_PRICE,
SCHEMA_SALE_PRICE,
} from "../transform.ts";

type Item = NonNullable<
OpenAPI["POST /api/checkout/pub/orderForms/simulation"]["response"]["items"]
Expand Down Expand Up @@ -91,50 +95,53 @@ export const extension = async (products: Product[], ctx: AppContext) => {
}
}

const fixOffer = (product: ProductLeaf): ProductLeaf => {
if (!product.offers) {
return product;
}

const offers = product.offers.offers.map((o) => {
const offer = mapped.get(product.productID)?.get(o.seller!);
const fixOffer = (product: ProductLeaf): void => {
if (!product.offers) return;

const skuOffers = mapped.get(product.productID);
if (!skuOffers) return;

let changed = false;
for (const o of product.offers.offers) {
const simulated = skuOffers.get(o.seller!);
if (!simulated) continue;

const salePrice = simulated.price != null
? simulated.price / 100
: o.price;
const listPrice = simulated.listPrice != null
? simulated.listPrice / 100
: undefined;
if (salePrice !== o.price) {
o.price = salePrice;
changed = true;
}

if (!offer) {
return o;
for (const spec of o.priceSpecification) {
if (spec.priceType === SCHEMA_SALE_PRICE) {
spec.price = salePrice;
} else if (spec.priceType === SCHEMA_LIST_PRICE) {
spec.price = listPrice ?? spec.price;
}
}
}

const salePrice = offer.price ? offer.price / 100 : o.price;
const listPrice = offer.listPrice && offer.listPrice / 100;

return {
...o,
price: salePrice,
priceSpecification: o.priceSpecification.map((spec) =>
spec.priceType === "https://schema.org/SalePrice"
? ({ ...spec, price: salePrice })
: spec.priceType === "https://schema.org/ListPrice"
? ({ ...spec, price: listPrice ?? spec.price })
: spec
),
};
});

const aggregated = aggregateOffers(
offers,
product.offers.priceCurrency,
);

return {
...product,
offers: aggregated,
};
if (changed) {
product.offers = aggregateOffers(
product.offers.offers,
product.offers.priceCurrency,
);
}
};

return products?.map((p) => ({
...fixOffer(p),
isVariantOf: p.isVariantOf && {
...p.isVariantOf,
hasVariant: p.isVariantOf.hasVariant.map(fixOffer),
},
})) ?? null;
for (const p of products) {
fixOffer(p);
if (p.isVariantOf) {
for (const variant of p.isVariantOf.hasVariant) {
fixOffer(variant);
}
}
}

return products;
};
127 changes: 76 additions & 51 deletions vtex/utils/transform.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@ import type {
DayOfWeek,
Filter,
FilterToggleValue,
ItemAvailability,
Offer,
OpeningHoursSpecification,
PageType,
Place,
PostalAddress,
PriceComponentTypeEnumeration,
PriceTypeEnumeration,
Product,
ProductDetailsPage,
ProductGroup,
Expand Down Expand Up @@ -50,6 +53,17 @@ import type {

const DEFAULT_CATEGORY_SEPARATOR = ">";

export const SCHEMA_LIST_PRICE: PriceTypeEnumeration =
"https://schema.org/ListPrice";
export const SCHEMA_SALE_PRICE: PriceTypeEnumeration =
"https://schema.org/SalePrice";
export const SCHEMA_SRP: PriceTypeEnumeration = "https://schema.org/SRP";
export const SCHEMA_INSTALLMENT: PriceComponentTypeEnumeration =
"https://schema.org/Installment";
export const SCHEMA_IN_STOCK: ItemAvailability = "https://schema.org/InStock";
export const SCHEMA_OUT_OF_STOCK: ItemAvailability =
"https://schema.org/OutOfStock";

const isLegacySku = (sku: LegacySkuVTEX | SkuVTEX): sku is LegacySkuVTEX =>
typeof (sku as LegacySkuVTEX).variations?.[0] === "string" ||
!!(sku as LegacySkuVTEX).Videos;
Expand Down Expand Up @@ -170,8 +184,7 @@ export const toProductPage = <T extends ProductVTEX | LegacyProductVTEX>(
};
};

export const inStock = (offer: Offer) =>
offer.availability === "https://schema.org/InStock";
export const inStock = (offer: Offer) => offer.availability === SCHEMA_IN_STOCK;

// Smallest Available Spot Price First
export const bestOfferFirst = (a: Offer, b: Offer) => {
Expand Down Expand Up @@ -386,12 +399,15 @@ export const toProduct = <P extends LegacyProductVTEX | ProductVTEX>(
isLegacyProduct(product) ? toOfferLegacy : toOffer,
);

const variantOptions = imagesByKey !== options.imagesByKey
? { ...options, imagesByKey }
: options;
const isVariantOf = level < 1
? ({
"@type": "ProductGroup",
productGroupID: productId,
hasVariant: items.map((sku) =>
toProduct(product, sku, 1, { ...options, imagesByKey })
toProduct(product, sku, 1, variantOptions)
),
url: getProductGroupURL(baseUrl, product).href,
name: product.productName,
Expand Down Expand Up @@ -440,10 +456,18 @@ export const toProduct = <P extends LegacyProductVTEX | ProductVTEX>(
const categoryAdditionalProperties = toAdditionalPropertyCategories(product);
const clusterAdditionalProperties = toAdditionalPropertyClusters(product);

const additionalProperty = specificationsAdditionalProperty
.concat(categoryAdditionalProperties ?? [])
.concat(clusterAdditionalProperties ?? [])
.concat(referenceIdAdditionalProperty ?? []);
const additionalProperty: PropertyValue[] = [
...specificationsAdditionalProperty,
];
if (categoryAdditionalProperties) {
additionalProperty.push(...categoryAdditionalProperties);
}
if (clusterAdditionalProperties) {
additionalProperty.push(...clusterAdditionalProperties);
}
if (referenceIdAdditionalProperty) {
additionalProperty.push(...referenceIdAdditionalProperty);
}

estimatedDateArrival && additionalProperty.push({
"@type": "PropertyValue",
Expand Down Expand Up @@ -632,15 +656,15 @@ const toAdditionalPropertiesLegacy = (sku: LegacySkuVTEX): PropertyValue[] => {
}) as const,
);

return [...specificationProperties, ...attachmentProperties];
if (attachmentProperties.length === 0) return specificationProperties;
specificationProperties.push(...attachmentProperties);
return specificationProperties;
};

const toOffer = ({
commertialOffer: offer,
sellerId,
sellerName,
sellerDefault,
}: SellerVTEX): Offer => ({
const buildOffer = (
{ commertialOffer: offer, sellerId, sellerName, sellerDefault }: SellerVTEX,
teasers: Teasers[],
): Offer => ({
"@type": "Offer",
identifier: sellerDefault ? "default" : undefined,
price: offer.spotPrice ?? offer.Price,
Expand All @@ -649,28 +673,28 @@ const toOffer = ({
priceValidUntil: offer.PriceValidUntil,
inventoryLevel: { value: offer.AvailableQuantity },
giftSkuIds: offer.GiftSkuIds ?? [],
teasers: offer.teasers ?? [],
teasers,
priceSpecification: [
{
"@type": "UnitPriceSpecification",
priceType: "https://schema.org/ListPrice",
priceType: SCHEMA_LIST_PRICE,
price: offer.ListPrice,
},
{
"@type": "UnitPriceSpecification",
priceType: "https://schema.org/SalePrice",
priceType: SCHEMA_SALE_PRICE,
price: offer.Price,
},
{
"@type": "UnitPriceSpecification",
priceType: "https://schema.org/SRP",
priceType: SCHEMA_SRP,
price: offer.PriceWithoutDiscount,
},
...offer.Installments.map(
(installment): UnitPriceSpecification => ({
"@type": "UnitPriceSpecification",
priceType: "https://schema.org/SalePrice",
priceComponentType: "https://schema.org/Installment",
priceType: SCHEMA_SALE_PRICE,
priceComponentType: SCHEMA_INSTALLMENT,
name: installment.PaymentSystemName,
description: installment.Name,
billingDuration: installment.NumberOfInstallments,
Expand All @@ -680,10 +704,13 @@ const toOffer = ({
),
],
availability: offer.AvailableQuantity > 0
? "https://schema.org/InStock"
: "https://schema.org/OutOfStock",
? SCHEMA_IN_STOCK
: SCHEMA_OUT_OF_STOCK,
});

const toOffer = (seller: SellerVTEX): Offer =>
buildOffer(seller, seller.commertialOffer.teasers ?? []);

const toOfferLegacy = (seller: SellerVTEX): Offer => {
const otherTeasers = seller.commertialOffer.DiscountHighLight?.map((i) => {
const discount = i as Record<string, string>;
Expand All @@ -704,35 +731,33 @@ const toOfferLegacy = (seller: SellerVTEX): Offer => {
return teasers;
}) ?? [];

return {
...toOffer(seller),
teasers: [
...otherTeasers,
...(seller.commertialOffer.Teasers ?? []).map((teaser) => ({
name: teaser["<Name>k__BackingField"],
generalValues: teaser["<GeneralValues>k__BackingField"],
conditions: {
minimumQuantity: teaser["<Conditions>k__BackingField"][
"<MinimumQuantity>k__BackingField"
],
parameters: teaser["<Conditions>k__BackingField"][
"<Parameters>k__BackingField"
].map((parameter) => ({
name: parameter["<Name>k__BackingField"],
value: parameter["<Value>k__BackingField"],
})),
},
effects: {
parameters: teaser["<Effects>k__BackingField"][
"<Parameters>k__BackingField"
].map((parameter) => ({
name: parameter["<Name>k__BackingField"],
value: parameter["<Value>k__BackingField"],
})),
},
})),
],
};
const legacyTeasers = (seller.commertialOffer.Teasers ?? []).map(
(teaser) => ({
name: teaser["<Name>k__BackingField"],
generalValues: teaser["<GeneralValues>k__BackingField"],
conditions: {
minimumQuantity: teaser["<Conditions>k__BackingField"][
"<MinimumQuantity>k__BackingField"
],
parameters: teaser["<Conditions>k__BackingField"][
"<Parameters>k__BackingField"
].map((parameter) => ({
name: parameter["<Name>k__BackingField"],
value: parameter["<Value>k__BackingField"],
})),
},
effects: {
parameters: teaser["<Effects>k__BackingField"][
"<Parameters>k__BackingField"
].map((parameter) => ({
name: parameter["<Name>k__BackingField"],
value: parameter["<Value>k__BackingField"],
})),
},
}),
);

return buildOffer(seller, [...otherTeasers, ...legacyTeasers]);
};

export const legacyFacetToFilter = (
Expand Down
Loading
Loading