Add the delivery of products by seller🛍
Published: 2020-11-10 13:48:53

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)
  )
```
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] })
    }, {})
  }
```
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' }],
  })
```

# 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'))

  )
```

#### mapOverCategory 
```
  const mapOverCategory = (category: string) => (arr) => arr[category]
```

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
    }
  }
```

#### Where stateOverNumber(n)(state) ❔
```
  const stateOverNumber = (num: number) => (state: number) => 
    (state < num ? num : state)

```
**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)
  )
```

## 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
    }
  }
```

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 }
}
```

***And now***

```
  const { composeProcess } = useAbstraction('sellerID')
  const delivery = useCallback( () 
   => composeProcess(state.products)
   , [state.products])
```

### Outro

![Stonks](https://i.kym-cdn.com/photos/images/newsfeed/001/499/826/2f0.png)

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](https://www.instagram.com/midnightouizard/)🧙🏼‍♂️ and you can contact me on [twitter](https://twitter.com/jesusacova) or jesuscovam@gmail.com


### Thanks for your time!