DEV Community

jesuscovam
jesuscovam

Posted on • Updated on

Sumar el delivery de productos por vendedor🛍

Recientemente me pidieron en una codebase qué mantengo hacer un carrito de compras donde el precio del envío sea el precio de envío más caro por productos de un mismo vendedor + el precio más caro de los otros vendedores.

Ejemplo, si en el carrito hay 4 productos, y 3 son de 1 vendedor, calcular el delivery mas costoso de ese vendedor + el delivery del otro vendedor.

La categoría de delivery puede ser
moto | auto | van

El precio de auto y van para este uso es el mismo.

Codigo

Este es un vistaso a la composición final👀

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

El primer paso es agrupar los productos por categoría, en este caso la categoría será 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

Esta función nos va a devolver un árbol de vendedores con productos

Esta base de datos solo tiene un vendedor, entonces agregaremos un poco de ruido para probar esta función

Agregar ruido 🔈

 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 sobre los productos de cada vendedor 🛍

Lo que nos importa de toda esta función es calcular el delivery más costoso de cada categoria/nodo/vendedor

La siguiente linea es una composición de 3 funciones para conseguir ese delivery más costoso.

mapObjIndexed(mapOverProducts)

Donde 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') va a devolver [moto, auto, ...NDeliveries] por cada vendedor

setOf

va a devolver el mismo arreglo quitando los deliveries repetidos

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

reduce(deliveryReducer, 0) ➕

Ok aquí tenemos 3 escenarios posibles, entonces por comodidad lo manejé con un 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

Donde stateOverNumber(n)(state) ❔

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

Enter fullscreen mode Exit fullscreen mode

Esta es la pieza clave que da categoría a cada producto, lo demás fue llegar aquí.

En este punto podemos regresar a la primera función compuesta, ya terminamos de mapear sobre los productos de cada nodo/vendedor

Recordando que viene 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

Ya no necesitamos el id del vendedor, solo sus valores.

map(converToMoney) 💰

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

1 siendo moto, y por el momento los otros valores tienen el mismo precio

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

La última función nos da la suma de estos valores para tener la suma del delivery más costoso de cada vendedor 😁

Listo, vamos a hacer una abstracción

Ok spoiler alert: uso react. ⚛️

voy a abstraer toda esta lógica en un custom Hook que solo tome la categoría y sobre la cual queremos agrupar los productos

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

Y ahora solo usamos

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

Outro

Stonks
Probablemente pueda mejorar el reducer, pero ya esto soporta la posibilidad de agrupar en un futuro por ciudad o estado.

Mi nombre es Jesus Cova, trabajo con el equipo de midnightouizard🧙🏼‍♂️ y me pueden contactar en twitter o en jesuscovam@gmail.com

¡Gracias por tu tiempo!

Top comments (0)