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)
)
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] })
}, {})
}
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' }],
})
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'))
)
mapOverCategory
const mapOverCategory = (category: string) => (arr) => arr[category]
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
}
}
Donde stateOverNumber(n)(state) ❔
const stateOverNumber = (num: number) => (state: number) =>
(state < num ? num : state)
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)
)
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
}
}
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 }
}
Y ahora solo usamos
const { composeProcess } = useAbstraction('sellerID')
const delivery = useMemo( ()
=> composeProcess(state.products)
, [state.products])
Outro
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
Top comments (0)