Skip to content

Commit

Permalink
feat: add taxes in PDP and Cart
Browse files Browse the repository at this point in the history
  • Loading branch information
ArthurTriis1 committed May 10, 2024
1 parent 83ceac1 commit 266a76f
Show file tree
Hide file tree
Showing 14 changed files with 195 additions and 49 deletions.
6 changes: 6 additions & 0 deletions packages/api/src/__generated__/schema.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

54 changes: 39 additions & 15 deletions packages/api/src/platforms/vtex/resolvers/aggregateOffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,56 @@ import { inStock, price } from '../utils/productStock'
import type { StoreProduct } from './product'
import type { PromiseType } from '../../../typings'
import type { Resolver } from '..'
import { withTax } from '../utils/withTaxes'

export type Root = PromiseType<ReturnType<typeof StoreProduct.offers>>

const getHighPrice = (
offers: Root,
options: { includeTaxes: boolean } = { includeTaxes: false }
) => {
const availableOffers = offers.filter(inStock)
const highOffer = availableOffers[availableOffers.length - 1]
options
const highPrice = highOffer != null ? price(highOffer) : 0

if (!options.includeTaxes) {
return highPrice
}

return withTax(highPrice, highOffer.Tax, highOffer.product.unitMultiplier)
}

const getLowPrice = (
offers: Root,
options: { includeTaxes: boolean } = { includeTaxes: false }
) => {
const [lowOffer] = offers.filter(inStock)

const lowPrice = lowOffer ? price(lowOffer) : 0

if (!options.includeTaxes) {
return lowPrice
}

return withTax(lowPrice, lowOffer.Tax, lowOffer.product.unitMultiplier)
}

export const StoreAggregateOffer: Record<string, Resolver<Root>> & {
offers: Resolver<Root, any, Root>
} = {
highPrice: (offers) => {
const availableOffers = offers.filter(inStock)
const highOffer = availableOffers[availableOffers.length - 1]

return highOffer != null ? price(highOffer) : 0
},
lowPrice: (offers) => {
const [lowOffer] = offers.filter(inStock)

return lowOffer ? price(lowOffer) : 0
},
highPrice: (offers) => getHighPrice(offers),
lowPrice: (offers) => getLowPrice(offers),
lowPriceWithTaxes: (offers) => getLowPrice(offers, { includeTaxes: true }),
offerCount: (offers) => offers.length,
priceCurrency: async (_, __, ctx) => {
const {
loaders: { salesChannelLoader },
storage: { channel }
const {
loaders: { salesChannelLoader },
storage: { channel }
} = ctx

const sc = await salesChannelLoader.load(channel.salesChannel);

return sc.CurrencyCode ?? '';
},
offers: (offers) => offers,
Expand Down
23 changes: 23 additions & 0 deletions packages/api/src/platforms/vtex/resolvers/offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { StoreAggregateOffer } from './aggregateOffer'
import type { ArrayElementType } from '../../../typings'
import type { EnhancedSku } from '../utils/enhanceSku'
import type { OrderFormItem } from '../clients/commerce/types/OrderForm'
import { withTax } from '../utils/withTaxes'

type OrderFormProduct = OrderFormItem & { product: EnhancedSku }
type SearchProduct = ArrayElementType<
Expand Down Expand Up @@ -83,6 +84,17 @@ export const StoreOffer: Record<string, Resolver<Root>> = {

return null
},
priceWithTaxes: (root) => {
if (isSearchItem(root)) {
return withTax(price(root), root.Tax, root.product.unitMultiplier)
}

if (isOrderFormItem(root)) {
return withTax(root.sellingPrice, root.tax, root.unitMultiplier)
}

return null
},
sellingPrice: (root) => {
if (isSearchItem(root)) {
return sellingPrice(root)
Expand All @@ -105,6 +117,17 @@ export const StoreOffer: Record<string, Resolver<Root>> = {

return null
},
listPriceWithTaxes: (root) => {
if (isSearchItem(root)) {
return withTax(root.ListPrice, root.Tax, root.product.unitMultiplier)
}

if (isOrderFormItem(root)) {
return withTax(root.listPrice, root.tax, root.unitMultiplier)
}

return null
},
itemOffered: (root) => {
if (isSearchItem(root)) {
return root.product
Expand Down
5 changes: 5 additions & 0 deletions packages/api/src/platforms/vtex/utils/withTaxes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const withTax = (price: number, tax: number, unitMultiplier: number = 1) => {
const unitTax = tax / unitMultiplier

return Math.round((price + unitTax) * 100) / 100
}
4 changes: 4 additions & 0 deletions packages/api/src/typeDefs/aggregateOffer.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ type StoreAggregateOffer {
"""
lowPrice: Float!
"""
Lowest price among all sellers with current Taxes.
"""
lowPriceWithTaxes: Float!
"""
Number of sellers selling this SKU.
"""
offerCount: Int!
Expand Down
8 changes: 8 additions & 0 deletions packages/api/src/typeDefs/offer.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ type StoreOffer {
"""
listPrice: Float!
"""
List price among with current taxes.
"""
listPriceWithTaxes: Float!
"""
Computed price before applying coupons, taxes or benefits. This may change before it reaches the shelf.
"""
sellingPrice: Float!
Expand All @@ -19,6 +23,10 @@ type StoreOffer {
"""
price: Float!
"""
Also known as spot price with taxes.
"""
priceWithTaxes: Float!
"""
Next date in which price is scheduled to change. If there is no scheduled change, this will be set a year in the future from current time.
"""
priceValidUntil: String!
Expand Down
12 changes: 6 additions & 6 deletions packages/core/@generated/gql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const documents = {
types.ProductSummary_ProductFragmentDoc,
'\n fragment Filter_facets on StoreFacet {\n ... on StoreFacetRange {\n key\n label\n\n min {\n selected\n absolute\n }\n\n max {\n selected\n absolute\n }\n\n __typename\n }\n ... on StoreFacetBoolean {\n key\n label\n values {\n label\n value\n selected\n quantity\n }\n\n __typename\n }\n }\n':
types.Filter_FacetsFragmentDoc,
'\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n offers {\n availability\n price\n listPrice\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n':
'\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n':
types.ProductDetailsFragment_ProductFragmentDoc,
'\n fragment ClientManyProducts on Query {\n search(\n first: $first\n after: $after\n sort: $sort\n term: $term\n selectedFacets: $selectedFacets\n ) {\n products {\n pageInfo {\n totalCount\n }\n }\n }\n }\n':
types.ClientManyProductsFragmentDoc,
Expand All @@ -36,9 +36,9 @@ const documents = {
types.ServerProductFragmentDoc,
'\n query ServerCollectionPageQuery($slug: String!) {\n ...ServerCollectionPage\n collection(slug: $slug) {\n seo {\n title\n description\n }\n breadcrumbList {\n itemListElement {\n item\n name\n position\n }\n }\n meta {\n selectedFacets {\n key\n value\n }\n }\n }\n }\n':
types.ServerCollectionPageQueryDocument,
'\n query ServerProductQuery($locator: [IStoreSelectedFacet!]!) {\n ...ServerProduct\n product(locator: $locator) {\n id: productID\n\n seo {\n title\n description\n canonical\n }\n\n brand {\n name\n }\n\n sku\n gtin\n name\n description\n releaseDate\n\n breadcrumbList {\n itemListElement {\n item\n name\n position\n }\n }\n\n image {\n url\n alternateName\n }\n\n offers {\n lowPrice\n highPrice\n priceCurrency\n offers {\n availability\n price\n priceValidUntil\n priceCurrency\n itemCondition\n seller {\n identifier\n }\n }\n }\n\n isVariantOf {\n productGroupID\n }\n\n ...ProductDetailsFragment_product\n }\n }\n':
'\n query ServerProductQuery($locator: [IStoreSelectedFacet!]!) {\n ...ServerProduct\n product(locator: $locator) {\n id: productID\n\n seo {\n title\n description\n canonical\n }\n\n brand {\n name\n }\n\n sku\n gtin\n name\n description\n releaseDate\n\n breadcrumbList {\n itemListElement {\n item\n name\n position\n }\n }\n\n image {\n url\n alternateName\n }\n\n offers {\n lowPrice\n highPrice\n lowPriceWithTaxes\n priceCurrency\n offers {\n availability\n price\n priceValidUntil\n priceCurrency\n itemCondition\n seller {\n identifier\n }\n }\n }\n\n isVariantOf {\n productGroupID\n }\n\n ...ProductDetailsFragment_product\n }\n }\n':
types.ServerProductQueryDocument,
'\n mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) {\n validateCart(cart: $cart, session: $session) {\n order {\n orderNumber\n acceptedOffer {\n ...CartItem\n }\n }\n messages {\n ...CartMessage\n }\n }\n }\n\n fragment CartMessage on StoreCartMessage {\n text\n status\n }\n\n fragment CartItem on StoreOffer {\n seller {\n identifier\n }\n quantity\n price\n listPrice\n itemOffered {\n ...CartProductItem\n }\n }\n\n fragment CartProductItem on StoreProduct {\n sku\n name\n unitMultiplier\n image {\n url\n alternateName\n }\n brand {\n name\n }\n isVariantOf {\n productGroupID\n name\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n gtin\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n':
'\n mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) {\n validateCart(cart: $cart, session: $session) {\n order {\n orderNumber\n acceptedOffer {\n ...CartItem\n }\n }\n messages {\n ...CartMessage\n }\n }\n }\n\n fragment CartMessage on StoreCartMessage {\n text\n status\n }\n\n fragment CartItem on StoreOffer {\n seller {\n identifier\n }\n quantity\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n itemOffered {\n ...CartProductItem\n }\n }\n\n fragment CartProductItem on StoreProduct {\n sku\n name\n unitMultiplier\n image {\n url\n alternateName\n }\n brand {\n name\n }\n isVariantOf {\n productGroupID\n name\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n gtin\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n':
types.ValidateCartMutationDocument,
'\n mutation SubscribeToNewsletter($data: IPersonNewsletter!) {\n subscribeToNewsletter(data: $data) {\n id\n }\n }\n':
types.SubscribeToNewsletterDocument,
Expand Down Expand Up @@ -76,7 +76,7 @@ export function gql(
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(
source: '\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n offers {\n availability\n price\n listPrice\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n'
source: '\n fragment ProductDetailsFragment_product on StoreProduct {\n id: productID\n sku\n name\n gtin\n description\n unitMultiplier\n isVariantOf {\n name\n productGroupID\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n\n image {\n url\n alternateName\n }\n\n brand {\n name\n }\n\n offers {\n lowPrice\n lowPriceWithTaxes\n offers {\n availability\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n seller {\n identifier\n }\n }\n }\n\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n\n # Contains necessary info to add this item to cart\n ...CartProductItem\n }\n'
): typeof import('./graphql').ProductDetailsFragment_ProductFragmentDoc
/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
Expand Down Expand Up @@ -136,13 +136,13 @@ export function gql(
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(
source: '\n query ServerProductQuery($locator: [IStoreSelectedFacet!]!) {\n ...ServerProduct\n product(locator: $locator) {\n id: productID\n\n seo {\n title\n description\n canonical\n }\n\n brand {\n name\n }\n\n sku\n gtin\n name\n description\n releaseDate\n\n breadcrumbList {\n itemListElement {\n item\n name\n position\n }\n }\n\n image {\n url\n alternateName\n }\n\n offers {\n lowPrice\n highPrice\n priceCurrency\n offers {\n availability\n price\n priceValidUntil\n priceCurrency\n itemCondition\n seller {\n identifier\n }\n }\n }\n\n isVariantOf {\n productGroupID\n }\n\n ...ProductDetailsFragment_product\n }\n }\n'
source: '\n query ServerProductQuery($locator: [IStoreSelectedFacet!]!) {\n ...ServerProduct\n product(locator: $locator) {\n id: productID\n\n seo {\n title\n description\n canonical\n }\n\n brand {\n name\n }\n\n sku\n gtin\n name\n description\n releaseDate\n\n breadcrumbList {\n itemListElement {\n item\n name\n position\n }\n }\n\n image {\n url\n alternateName\n }\n\n offers {\n lowPrice\n highPrice\n lowPriceWithTaxes\n priceCurrency\n offers {\n availability\n price\n priceValidUntil\n priceCurrency\n itemCondition\n seller {\n identifier\n }\n }\n }\n\n isVariantOf {\n productGroupID\n }\n\n ...ProductDetailsFragment_product\n }\n }\n'
): typeof import('./graphql').ServerProductQueryDocument
/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
*/
export function gql(
source: '\n mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) {\n validateCart(cart: $cart, session: $session) {\n order {\n orderNumber\n acceptedOffer {\n ...CartItem\n }\n }\n messages {\n ...CartMessage\n }\n }\n }\n\n fragment CartMessage on StoreCartMessage {\n text\n status\n }\n\n fragment CartItem on StoreOffer {\n seller {\n identifier\n }\n quantity\n price\n listPrice\n itemOffered {\n ...CartProductItem\n }\n }\n\n fragment CartProductItem on StoreProduct {\n sku\n name\n unitMultiplier\n image {\n url\n alternateName\n }\n brand {\n name\n }\n isVariantOf {\n productGroupID\n name\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n gtin\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n'
source: '\n mutation ValidateCartMutation($cart: IStoreCart!, $session: IStoreSession!) {\n validateCart(cart: $cart, session: $session) {\n order {\n orderNumber\n acceptedOffer {\n ...CartItem\n }\n }\n messages {\n ...CartMessage\n }\n }\n }\n\n fragment CartMessage on StoreCartMessage {\n text\n status\n }\n\n fragment CartItem on StoreOffer {\n seller {\n identifier\n }\n quantity\n price\n priceWithTaxes\n listPrice\n listPriceWithTaxes\n itemOffered {\n ...CartProductItem\n }\n }\n\n fragment CartProductItem on StoreProduct {\n sku\n name\n unitMultiplier\n image {\n url\n alternateName\n }\n brand {\n name\n }\n isVariantOf {\n productGroupID\n name\n skuVariants {\n activeVariations\n slugsMap\n availableVariations\n }\n }\n gtin\n additionalProperty {\n propertyID\n name\n value\n valueReference\n }\n }\n'
): typeof import('./graphql').ValidateCartMutationDocument
/**
* The gql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
Expand Down
Loading

0 comments on commit 266a76f

Please sign in to comment.