DEV Community

jesuscovam
jesuscovam

Posted on • Updated on

Add the delivery of products by seller🛍

I was recently asked for a codebase to make a shopping cart where the shipping price is the most expensive shipping price for the products by the same seller + the most expensive price from the other sellers.

For example, if there are 4 products in the cart, and 3 are from 1 seller, calculate the most expensive delivery from that seller + the delivery from the other seller.

the delivery category could be one of these
moto | auto | van

The price for auto and van is the same

Code

A look at the final composed function👀

const composeProcess = compose(
    reduce((a, b: number) => a + b, 0),
    map(convertToMoney),
    values,
    mapObjIndexed(mapOverProducts),
    addNoise,
    groupByCategory(category)
  )
Enter fullscreen mode Exit fullscreen mode

The first step is to group the products by category, for this case is the sellerID

const groupByCategory = (category: string) => (products: { item: Product }[]) => {
    return products.reduce((acc, { item }) => {
      return (acc = { ...acc, [item[category]]: [...(acc[item[category]] || []), item] })
    }, {})
  }
Enter fullscreen mode Exit fullscreen mode

This function will return a tree of sellers with products

This DB only has 1 seller, so we are going to add some noise to test the function

Add noise 🔈

 const addNoise = (nodes) => ({
    ...nodes,
    bbc: [
      { id: 10, title: 'jaja', delivery: 'moto' },
      { id: 20, title: 'jeje', delivery: 'auto' },
    ],
    ccc: [{ id: 14, title: 'jiji', delivery: 'auto' }],
  })
Enter fullscreen mode Exit fullscreen mode

Map over the products of each seller 🛍

What matters from this function is to return the most expensive delivery for each tree/node/seller.

this function is composed of 3 more that apply only to products of each tree/node/seller

mapObjIndexed(mapOverProducts)

where mapOverProducts:

  const mapOverProducts = compose(
    reduce(deliveryReducer, 0), 
    setOf, 
    map(mapOverCategory('delivery'))

  )
Enter fullscreen mode Exit fullscreen mode

mapOverCategory

  const mapOverCategory = (category: string) => (arr) => arr[category]
Enter fullscreen mode Exit fullscreen mode

mapOverCategory('delivery') will return [moto, auto, ...NDeliveries] for each node

setOf

will return the same array but avoiding repetitive delivery methods

const setOf = (values) => [...new Set(values)]

reduce(deliveryReducer, 0) ➕

As we only have 3 possible scenarios, we are going to use a reducer.

const deliveryReducer = (state, action) => {
    switch (action) {
      case 'moto':
        return stateOverNumber(1)(state)

      case 'auto':
        return stateOverNumber(2)(state)

      case 'van':
        return stateOverNumber(3)(state)

      default:
        return state
    }
  }
Enter fullscreen mode Exit fullscreen mode

Where stateOverNumber(n)(state) ❔

  const stateOverNumber = (num: number) => (state: number) => 
    (state < num ? num : state)

Enter fullscreen mode Exit fullscreen mode

This is the moment we obtain which delivery is the most expensive from each node

At this point, we continue with the first composed function as we ended the work inside each node of products by a seller.

It's time for values

const composeProcess = compose(
    reduce((a, b: number) => a + b, 0),
    map(convertToMoney),
    values,
    mapObjIndexed(mapOverProducts),
    addNoise,
    groupByCategory(category)
  )
Enter fullscreen mode Exit fullscreen mode

Values

We no longer need the id of each seller, only its biggest delivery method

map(converToMoney) 💰

const convertToMoney = (deliveryTypes: number) => {
    if (deliveryTypes === 1) {
      return 2.9
    } else {
      return 4.9
    }
  }
Enter fullscreen mode Exit fullscreen mode

1 being 'moto' and for the moment the price for the other methods is the same.

reduce((a, b: number) => a + b, 0) ➕

The las function adds the value of all the possible prices by a node 😁

Now, let's make an abstraction

Ok spoiler alert, I work with react ⚛️

We are going to abstract all of this logic in a hook that only takes as an argument the category for which we are going to split the nodes

const useAbstraction = (category: string) => {
  const groupByCategory = (category: string) => (products: { item: Product }[]) => {
    return products.reduce((acc, { item }) => {
      return (acc = { ...acc, [item[category]]: [...(acc[item[category]] || []), item] })
    }, {})
  }

  const mapOverCategory = (category: string) => (arr) => arr[category]
  const setOf = (values) => [...new Set(values)]

  const stateOverNumber = (num: number) => (state: number) => (state < num ? num : state)

  const deliveryReducer = (state, action) => {
    switch (action) {
      case 'moto':
        return stateOverNumber(1)(state)

      case 'auto':
        return stateOverNumber(2)(state)

      case 'van':
        return stateOverNumber(3)(state)

      default:
        return state
    }
  }
  const mapOverProducts = compose(
    reduce(deliveryReducer, 0),
    setOf,
    map(mapOverCategory('delivery'))
  )
  const addNoise = (sellers) => ({
    ...sellers,
    bbc: [
      { id: 10, title: 'jaja', delivery: 'moto' },
      { id: 20, title: 'jeje', delivery: 'auto' },
    ],
    ccc: [{ id: 14, title: 'jiji', delivery: 'auto' }],
  })

  const convertToMoney = (deliveryTypes: number) => {
    if (deliveryTypes === 1) {
      return 2.9
    } else {
      return 4.9
    }
  }

  const composeProcess = compose(
    reduce((a, b: number) => a + b, 0),
    map(convertToMoney),
    values,
    mapObjIndexed(mapOverProducts),
    addNoise,
    groupByCategory(category)
  )

  return { composeProcess }
}
Enter fullscreen mode Exit fullscreen mode

And now

  const { composeProcess } = useAbstraction('sellerID')
  const delivery = useCallback( () 
   => composeProcess(state.products)
   , [state.products])
Enter fullscreen mode Exit fullscreen mode

Outro

Stonks

Probably there are better ways to give order hierarchy to the delivery methods, but this way works and it could be adapted to even group by cities or states.

My name is Jesus Cova, I work with the team at midnightouizard🧙🏼‍♂️ and you can contact me on twitter or jesuscovam@gmail.com

Thanks for your time!

Top comments (0)