UNPKG

@open-tender/utils

Version:

A library of utils for use with Open Tender applications that utilize our cloud-based Order API.

911 lines (910 loc) 37.7 kB
import { isCreditTender, isHouseAccountTender, isOrderCustomer } from '@open-tender/types'; import { convertStringToArray } from './helpers'; import { makeMenuItemLookup } from './favorites'; import { isoToDate } from './datetimes'; import { fromUnixTime } from 'date-fns'; // TODO: refactor for nested mods (if needed) export const getItemOptions = (item) => { if (!item.groups.length) return []; const options = item.groups .map(group => group.options.filter(option => option.quantity > 0)) .reduce((arr, i) => [...arr, ...i], []); return options; }; export const makeModifierNames = (item) => { return item.groups .map(group => group.options.filter(option => option.quantity > 0)) .reduce((arr, i) => [...arr, ...i], []) .map(i => { const optionNames = makeModifierNames(i); const name = optionNames ? `${i.name} (${optionNames})` : i.name; return Object.assign(Object.assign({}, i), { name }); }) .map(option => option.name) .join(', '); }; export const makeModifierNamesForGroups = (groups) => { return groups .map(group => group.options.filter(option => option.quantity > 0)) .reduce((arr, i) => [...arr, ...i], []) .map(i => { const optionNames = makeModifierNamesForGroups(i.groups); const name = optionNames ? `${i.name} (${optionNames})` : i.name; return Object.assign(Object.assign({}, i), { name }); }) .map(option => option.name) .join(', '); }; // export const makeModifierNames = <T extends BaseItem>(item: T): string => { // return makeModifierNamesForGroups(item.groups) // } export const hasGroupsBelowMin = (option, groups) => { if (!groups || !groups.length) return false; if (option && option.quantity === 0) return false; const childGroupsBelow = groups.filter(g => { const optionsBelowMin = g.options.filter(o => hasGroupsBelowMin(o, o.groups)); return optionsBelowMin.length > 0; }); if (childGroupsBelow.length > 0) return true; return groups.filter(g => ((g === null || g === void 0 ? void 0 : g.quantity) || 0) < g.min).length > 0; }; export const makeModifierGroups = (groups) => { if (!groups || !groups.length) return [[], []]; const otherGroups = groups.filter(g => !g.isSize); if (otherGroups.length) return [groups, []]; const sizeGroup = groups.find(g => g.isSize); if (!sizeGroup) return [[], []]; const sizeGroupNested = sizeGroup.options.filter(o => o.groups && o.groups.length); if (!sizeGroupNested || !sizeGroupNested.length) return [[], []]; const selectedOption = sizeGroup.options.find(o => o.quantity > 0); return selectedOption ? [selectedOption.groups, [[sizeGroup.id, selectedOption.id]]] : [[], []]; }; export const makeUpsellItems = (itemIds, itemLookup) => { return itemIds .map(id => itemLookup[id]) .filter(i => i) .map(i => makeOrderItem(i)) .filter(i => !i.isSoldOut) .filter(i => { const belowMin = i.groups.filter(g => !g.isSize && (g.quantity || 0) < g.min); return belowMin.length === 0; }); }; export const makeUpsellItemIds = (item, cartIds, soldOut) => { const upsellItems = item ? item.upsellItems : null; return upsellItems ? upsellItems.filter(id => !cartIds.includes(id) && !soldOut.includes(id)) : []; }; export const makeUpsellItemsForCart = (categories, cartIds, soldOut) => { const itemLookup = makeMenuItemLookup(categories); const menuItems = cartIds.map(id => itemLookup[id]).filter(Boolean); const upsellItemIds = menuItems.reduce((arr, i) => { const upsellsItems = i.upsell_items.slice(0, cartIds.length > 2 ? 2 : undefined); return [...arr, ...upsellsItems]; }, []); const uniqueIds = Array.from(new Set(upsellItemIds)); const itemIds = uniqueIds.filter(id => !cartIds.includes(id) && !soldOut.includes(id)); return makeUpsellItems(itemIds, itemLookup); }; const nutritionKeys = [ 'calories', 'cholesterol', 'dietary_fiber', 'protein', 'saturated_fat', 'sodium', 'sugars', 'total_carbs', 'total_fat', 'trans_fat' ]; export const calcNutrition = (item) => { const { nutritionalInfo, groups } = item; let nutrition = (nutritionalInfo ? Object.assign({}, nutritionalInfo) : {}); const options = groups.reduce((arr, g) => { return [...arr, ...g.options.filter(o => o.quantity > 0)]; }, []); options.forEach(option => { const optionNutrition = calcNutrition(option); nutritionKeys.forEach(key => { const optionPer = optionNutrition ? optionNutrition[key] || 0 : 0; const optionVal = optionPer * option.quantity; const itemVal = nutrition[key] || 0; nutrition = Object.assign(Object.assign({}, nutrition), { [key]: itemVal + optionVal }); }); }); return nutrition; }; export const calcNutritionalInfo = (item) => { const nutrition = calcNutrition(item); const { serving_size = '0.00' } = item.nutritionalInfo || {}; return Object.assign(Object.assign({}, nutrition), { serving_size }); }; export const calcPrices = (item) => { const groups = item.groups.map(g => { let groupQuantity = 0; const optionsWithPrices = g.options.map((o, index) => { return Object.assign(Object.assign({}, calcPrices(o)), { index }); }); const sortedOptions = optionsWithPrices.sort((a, b) => { const pricePerA = a.totalPrice && a.quantity ? a.totalPrice / a.quantity : 0; const pricePerB = b.totalPrice && b.quantity ? b.totalPrice / b.quantity : 0; return pricePerA - pricePerB; }); const pricedOptions = sortedOptions.map(o => { const pricePer = o.totalPrice && o.quantity ? o.totalPrice / o.quantity : 0; const pointsPer = o.totalPoints && o.quantity ? o.totalPoints / o.quantity : 0; const includedRemaining = Math.max(g.included - groupQuantity, 0); const priceQuantity = Math.max(o.quantity - includedRemaining, 0); const option = Object.assign(Object.assign({}, o), { totalPrice: priceQuantity * pricePer, totalPoints: priceQuantity * pointsPer, totalCals: o.totalCals || 0 }); groupQuantity += o.quantity || 0; return option; }); const options = pricedOptions.sort((a, b) => a.index - b.index); return Object.assign(Object.assign({}, g), { quantity: groupQuantity, options }); }); const optionsPrice = groups.reduce((t, g) => { return t + g.options.reduce((ot, o) => ot + o.totalPrice, 0.0); }, 0.0); const totalPrice = item.quantity * (item.price + optionsPrice); const optionsPoints = groups.reduce((t, g) => { return t + g.options.reduce((ot, o) => ot + o.totalPoints, 0); }, 0.0); const totalPoints = item.points ? item.quantity * (item.points + optionsPoints) : null; const optionsCals = groups.reduce((t, g) => { return t + g.options.reduce((ot, o) => ot + o.totalCals, 0); }, 0.0); const totalCals = item.cals || item.cals === 0 ? item.quantity * (item.cals + optionsCals) : null; const nonSizeGroups = groups ? groups.filter(i => !i.isSize) : null; const currentOptions = nonSizeGroups ? getItemOptions(Object.assign(Object.assign({}, item), { groups: nonSizeGroups })) : []; const modifiersAllergens = currentOptions.reduce((t, o) => { return [...t, ...o.allergens]; }, []); const totalAllergens = [...new Set(item.allergens.concat(modifiersAllergens))]; return Object.assign(Object.assign({}, item), { groups, totalPrice, totalPoints, totalCals, totalAllergens }); }; export const checkSoldOut = (id, suspendUntil, soldOut = [], requestedAt) => { if (soldOut.includes(id)) return true; if (!suspendUntil || !requestedAt) return false; const now = new Date(); const suspendDate = fromUnixTime(suspendUntil); const requestedDate = requestedAt === null || requestedAt === 'asap' ? now : isoToDate(requestedAt); return requestedDate < suspendDate; }; const makeOrderItemGroups = (optionGroups, isEdit, soldOut = [], requestedAt = null) => { if (!optionGroups) return []; const groups = optionGroups.map(g => { var _a, _b; const options = g.option_items.map(o => { var _a, _b, _c; const isSoldOut = checkSoldOut(o.id, o.suspend_until, soldOut, requestedAt); const quantity = isSoldOut ? 0 : o.opt_is_default && !isEdit ? o.min_quantity || 1 : 0; const cals = ((_a = o.nutritional_info) === null || _a === void 0 ? void 0 : _a.calories) ? parseInt(o.nutritional_info.calories.toString()) : null; const groups = makeOrderItemGroups(o.option_groups, isEdit, soldOut, requestedAt); const option = { allergens: convertStringToArray(o.allergens), allergens_list: o.allergens_list || [], cals: cals, description: o.description, groups: groups, id: o.id, imageUrl: o.small_image_url, appImageUrl: o.app_image_url, increment: o.increment, ingredients: o.ingredients, isDefault: o.opt_is_default, isSoldOut: isSoldOut, suspend_until: o.suspend_until, max: isSoldOut ? 0 : o.max_quantity, min: o.min_quantity, name: o.name, nutritionalInfo: o.nutritional_info, points: o.points || 0, // @ts-ignore pos_ext_id: o.pos_ext_id, price: parseFloat(o.price), quantity: quantity, shorthand: o.shorthand, shortDescription: o.short_description, shortName: o.short_name, slug: o.slug, tags: convertStringToArray(o.tags), tags_list: o.tags_list || [], totalAllergens: [], totalCals: null, totalPoints: null, totalPrice: null, itemShape: (_b = o.item_shape) !== null && _b !== void 0 ? _b : null, excludeFromBuilder: (_c = o.exclude_from_builder) !== null && _c !== void 0 ? _c : false }; return option; }); const group = { description: g.description, id: g.id, imageUrl: g.small_image_url, included: g.included_options, isPizza: !!g.is_pizza, isSize: !!g.is_size, max: g.max_options, min: g.min_options, name: g.name, options: options, quantity: options.reduce((t, o) => t + o.quantity, 0), itemShape: (_a = g.item_shape) !== null && _a !== void 0 ? _a : null, excludeFromBuilder: (_b = g.exclude_from_builder) !== null && _b !== void 0 ? _b : false }; return group; }); return groups; }; export const makeOrderItem = (item, isEdit, soldOut = [], simpleItem, hasPoints = false, requestedAt = null) => { var _a, _b, _c; const groups = makeOrderItemGroups(item.option_groups, isEdit, soldOut, requestedAt); const cals = ((_a = item.nutritional_info) === null || _a === void 0 ? void 0 : _a.calories) ? parseInt(item.nutritional_info.calories.toString()) : null; const isSoldOut = checkSoldOut(item.id, item.suspend_until, soldOut, requestedAt); const orderItem = { allergens: convertStringToArray(item.allergens), allergens_list: item.allergens_list || [], cals: cals, cartGuestId: simpleItem ? simpleItem.cart_guest_id || null : null, customerId: simpleItem ? simpleItem.customer_id || null : null, description: item.description, groups: groups, id: item.id, imageUrl: item.large_image_url, increment: item.increment, //index: null, ingredients: item.ingredients, isSoldOut: isSoldOut, suspend_until: item.suspend_until, madeFor: simpleItem ? simpleItem.made_for || null : null, max: item.max_quantity, min: item.min_quantity, name: item.name, notes: simpleItem ? simpleItem.notes || null : null, nutritionalInfo: item.nutritional_info, points: hasPoints ? item.points || null : null, // @ts-ignore pos_ext_id: item.pos_ext_id, price: parseFloat(item.price), quantity: item.min_quantity || 1 * item.increment, shorthand: item.shorthand, shortDescription: item.short_description, shortName: item.short_name, similarItems: item.similar_items, slug: item.slug, tags: convertStringToArray(item.tags), tags_list: item.tags_list || [], totalAllergens: [], totalCals: null, totalPoints: null, totalPrice: null, upsellItems: item.upsell_items, itemShape: (_b = item.item_shape) !== null && _b !== void 0 ? _b : null, excludeFromBuilder: (_c = item.exclude_from_builder) !== null && _c !== void 0 ? _c : false, list_id: item.list_id, list_name: item.list_name, menu_position: item.menu_position }; const pricedItem = calcPrices(orderItem); return pricedItem; }; export const calcCartCounts = (cart) => { return cart.reduce((obj, item) => { const newCount = (obj[item.id.toString()] || 0) + item.quantity; return Object.assign(Object.assign({}, obj), { [item.id]: newCount }); }, {}); }; const makeSimpleGroupsLookup = (groups) => { if (!groups || !groups.length) return null; return groups.reduce((grpObj, g) => { const options = g.options.reduce((optObj, o) => { optObj[o.id] = { quantity: o.quantity, groupsLookup: makeSimpleGroupsLookup(o.groups) }; return optObj; }, {}); grpObj[g.id] = { options }; return grpObj; }, {}); }; const rehydrateGroups = (groups, groupsLookup) => { if (!groupsLookup) return groups; groups.forEach(group => { const simpleGroup = groupsLookup[group.id]; if (simpleGroup) { group.options.forEach(option => { const simpleOption = simpleGroup.options[option.id]; if (simpleOption) { option.quantity = simpleOption.quantity; option.groups = rehydrateGroups(option.groups, simpleOption.groupsLookup); } }); } }); return groups; }; export const rehydrateOrderItem = (menuItem, simpleCartItem, soldOut = [], requestedAt = null) => { const orderItem = makeOrderItem(menuItem, true, soldOut, simpleCartItem, undefined, requestedAt); orderItem.quantity = simpleCartItem.quantity || 1; const groupsLookup = makeSimpleGroupsLookup(simpleCartItem.groups); if (groupsLookup) { orderItem.groups = rehydrateGroups(orderItem.groups, groupsLookup); } const pricedItem = calcPrices(orderItem); return pricedItem; }; export const rehydrateCart = (menuItems, simpleCartItems, soldOut = []) => { const orderItems = []; simpleCartItems.forEach(item => { const menuItem = menuItems.find(i => i.id === item.id); if (menuItem) { const orderItem = rehydrateOrderItem(menuItem, item, soldOut); orderItems.push(orderItem); } }); const cart = orderItems.map((i, index) => (Object.assign(Object.assign({}, i), { index }))); const cartCounts = calcCartCounts(cart); return { cart, cartCounts }; }; // TODO: maybe refactor const rehydrateDetails = (details) => { return { cart_id: details === null || details === void 0 ? void 0 : details.cart_id, eating_utensils: details === null || details === void 0 ? void 0 : details.eating_utensils, notes: details === null || details === void 0 ? void 0 : details.notes, person_count: details === null || details === void 0 ? void 0 : details.person_count, serving_utensils: details === null || details === void 0 ? void 0 : details.serving_utensils, tax_exempt_id: details === null || details === void 0 ? void 0 : details.tax_exempt_id }; }; // TODO: maybe refactor const rehydrateCustomer = (customer) => { return { customer_id: customer.customer_id, first_name: customer.first_name, last_name: customer.last_name, email: customer.email, phone: customer.phone, company: customer.company || '' }; }; // TODO: maybe refactor const rehydrateAddress = (address) => { if (!address) return {}; return { unit: address.unit, company: address.company, contact: address.contact, phone: address.phone }; }; const rehydrateSurcharges = (surcharges) => { return (surcharges === null || surcharges === void 0 ? void 0 : surcharges.filter(i => i.is_optional).map(i => ({ id: i.id }))) || []; }; const rehydrateDiscounts = (discounts) => { return ((discounts === null || discounts === void 0 ? void 0 : discounts.filter(i => !i.is_promo_code).map(i => ({ id: i.id, ext_id: i.ext_id }))) || []); }; const rehydratePromoCodes = (discounts) => { return (discounts === null || discounts === void 0 ? void 0 : discounts.filter(i => i.is_promo_code).map(i => i.name)) || []; }; const rehydrateTenders = (tenders) => { const other = tenders === null || tenders === void 0 ? void 0 : tenders.map(i => { if (isCreditTender(i)) { return Object.assign(Object.assign({}, i), i.credit_card); } else if (isHouseAccountTender(i)) { return Object.assign(Object.assign({}, i), i.house_account); } else { return i; } }); return other; }; export const rehydrateCheckoutForm = (order) => { const form = { details: rehydrateDetails(order.details), customer: order.customer && isOrderCustomer(order.customer) ? rehydrateCustomer(order.customer) : {}, address: rehydrateAddress(order.address), surcharges: rehydrateSurcharges(order.surcharges), discounts: rehydrateDiscounts(order.discounts), promoCodes: rehydratePromoCodes(order.discounts), tenders: rehydrateTenders(order.tenders), tip: order.totals.tip }; return form; }; // group orders export const makeGuestLookup = (cartGuests) => { return cartGuests.reduce((obj, i) => i.cart_guest_id ? Object.assign(Object.assign({}, obj), { [i.cart_guest_id]: i }) : Object.assign({}, obj), {}); }; export const compareCarts = (cart) => { if (!cart) return null; const customerCart = cart.filter(i => !i.cartGuestId); return customerCart.length < cart.length ? customerCart : null; }; export const combineCarts = (cart, guestCart, cartOwner, cartGuests) => { const guestLookup = makeGuestLookup(cartGuests); const withGuestNames = guestCart .filter(i => i.cartGuestId && guestLookup[i.cartGuestId]) .map(i => { const guest = i.cartGuestId ? guestLookup[i.cartGuestId] : null; const madeFor = guest ? `${guest === null || guest === void 0 ? void 0 : guest.first_name} ${guest === null || guest === void 0 ? void 0 : guest.last_name}` : null; return Object.assign(Object.assign({}, i), { madeFor }); }); const withOwnerName = cart.map(i => { return Object.assign(Object.assign({}, i), { customerId: cartOwner.customer_id, madeFor: `${cartOwner.first_name} ${cartOwner.last_name}` }); }); return [...withOwnerName, ...withGuestNames]; }; // display items from past orders export const makeItemImageUrl = (images) => { const imageMap = images .filter(i => i.url) .reduce((obj, i) => (Object.assign(Object.assign({}, obj), { [i.type]: i.url })), {}); return (imageMap.SMALL_IMAGE || imageMap.LARGE_IMAGE || imageMap.APP_IMAGE || null); }; export const makeDisplayItemGroups = (optionGroups) => { if (!optionGroups || !optionGroups.length) return []; return optionGroups.map(g => { var _a; const options = (_a = g.options) === null || _a === void 0 ? void 0 : _a.map(o => makeDisplayItem(o)); return Object.assign(Object.assign({}, g), { options }); }); }; export const makeDisplayItem = (item, isOption = false) => { var _a; const cals = item.nutritional_info ? (_a = item.nutritional_info) === null || _a === void 0 ? void 0 : _a.calories : null; let displayItem = { allergens: item.allergens, cals: cals, description: item.description, groups: makeDisplayItemGroups(item.groups), imageUrl: makeItemImageUrl(item.images), id: item.id, name: item.name, nutritionalInfo: item.nutritional_info, quantity: item.quantity, points: null, price: item.price, shortDescription: item.short_description, tags: item.tags, totalPrice: item.price_total }; if (!isOption) { const itemDetails = { madeFor: item.made_for, notes: item.notes, signature: makeItemSignature(displayItem) }; displayItem = Object.assign(Object.assign({}, displayItem), itemDetails); } return displayItem; }; export const makeDisplayItems = (orders) => { const items = orders.reduce((items, order) => [...items, ...order.cart], []); return items.map(i => makeDisplayItem(i)); }; export const makeUniqueDisplayItems = (orders) => { const items = makeDisplayItems(orders); const unique = []; const signatures = []; items.forEach(item => { var _a; if (signatures.includes((_a = item.signature) !== null && _a !== void 0 ? _a : '')) return; item.signature && signatures.push(item.signature); unique.push(item); }); return unique; }; export const makeFavoritesLookup = (favorites) => { if (!favorites) return {}; return favorites .filter(i => i.cart) .reduce((obj, i) => (Object.assign(Object.assign({}, obj), { [makeItemSignature(i.cart)]: i.favorite_id })), {}); }; const makeCartItemOptionIds = (groups) => { if (!groups || !groups.length) return []; return groups .reduce((arr, group) => { const ids = group.options .filter(o => o.quantity > 0) .reduce((optionsArr, o) => { const optionArr = new Array(o.quantity); optionArr.fill(o.id); const nestedIds = makeCartItemOptionIds(o.groups); return [...optionsArr, ...optionArr, ...nestedIds]; }, []); return [...arr, ...ids]; }, []) .sort((a, b) => a - b); }; export const makeItemSignature = (item) => { const optionIds = makeCartItemOptionIds(item.groups); return [item.id, ...optionIds].join('.'); }; export const makeCartItemSignature = (item) => { const optionIds = makeCartItemOptionIds(item.groups); const itemIds = [item.id, ...optionIds].join('.'); const signature = `${itemIds}.${item.madeFor}.${item.notes}`; return signature; }; export const addItem = (cart, item) => { if (typeof item.index === 'undefined') { const itemSignature = makeCartItemSignature(item); const itemSignatures = cart.map(i => (Object.assign({ signature: makeCartItemSignature(i) }, i))); const match = itemSignatures.find(i => i.signature === itemSignature); if (match && match.index !== undefined) { const quantity = item.quantity + match.quantity; const newItem = Object.assign(Object.assign({}, item), { quantity }); const updated = calcPrices(newItem); cart[match.index] = Object.assign(Object.assign({}, updated), { index: match.index }); } else { cart.push(Object.assign(Object.assign({}, item), { index: cart.length })); } } else { cart[item.index] = item; } const cartCounts = calcCartCounts(cart); return { cart, cartCounts }; }; const getItemRealIndex = (cart, index) => cart.findIndex(item => item.index === index); export const removeItem = (cart, index) => { const realIndex = getItemRealIndex(cart, index); if (realIndex > -1) { cart.splice(realIndex, 1); cart = cart.map((i, _index) => (Object.assign(Object.assign({}, i), { index: _index }))); } const cartCounts = calcCartCounts(cart); return { cart, cartCounts }; }; export const incrementItem = (cart, index) => { const realIndex = getItemRealIndex(cart, index); if (realIndex > -1) { const item = cart[realIndex]; if (item.max === 0 || item.quantity < item.max) { let newQuantity = item.quantity + item.increment; newQuantity = item.max === 0 ? newQuantity : Math.min(item.max, newQuantity); const newItem = calcPrices(Object.assign(Object.assign({}, item), { quantity: newQuantity })); cart[realIndex] = newItem; } } const cartCounts = calcCartCounts(cart); return { cart, cartCounts }; }; export const decrementItem = (cart, index) => { const realIndex = getItemRealIndex(cart, index); if (realIndex > -1) { const item = cart[realIndex]; const newQuantity = Math.max(item.quantity - item.increment, 0); if (newQuantity === 0 || newQuantity < item.min) { cart.splice(realIndex, 1); cart = cart.map((i, _index) => (Object.assign(Object.assign({}, i), { index: _index }))); } else { const newItem = calcPrices(Object.assign(Object.assign({}, item), { quantity: newQuantity })); cart[realIndex] = newItem; } } const cartCounts = calcCartCounts(cart); return { cart, cartCounts }; }; const makeSimpleGroups = (groups) => { return groups.map(g => { const options = g.options .filter(o => o.quantity !== 0) .map(o => ({ id: o.id, quantity: o.quantity, groups: makeSimpleGroups(o.groups || []) })); return { id: g.id, options: options }; }); }; export const makeSimpleCart = (cart) => { const simpleCart = cart.map(i => { return { id: i.id, quantity: i.quantity, groups: makeSimpleGroups(i.groups), made_for: i.madeFor || i.made_for || '', notes: i.notes || '' }; }); return simpleCart; }; /* cart validation */ // TODO: refactor for nested modifiers // convert menu items to order items for validation but only include // items that are already in the cart to save wasted effort const makeItemLookup = (categories, itemIds) => { const itemLookup = {}; categories.forEach(category => { category.items.forEach(item => { if (itemIds.includes(item.id)) { itemLookup[item.id] = makeOrderItem(item, true); } }); category.children.forEach(child => { child.items.forEach(item => { if (itemIds.includes(item.id)) { itemLookup[item.id] = makeOrderItem(item, true); } }); }); }); return itemLookup; }; // TODO: refactor for nested modifiers const makeGroupsLookup = (item) => { return item.groups.reduce((grpObj, i) => { const options = i.options.reduce((optObj, o) => { if (o.id) { optObj[o.id] = o; } return optObj; }, {}); grpObj[i.id] = Object.assign(Object.assign({}, i), { options }); return grpObj; }, {}); }; // TODO: refactor for nested modifiers export const validateCart = (cart, categories, soldOut, requestedAt) => { const itemIds = cart.map(item => item.id); const itemLookup = makeItemLookup(categories, itemIds); let errors = null; const missingItems = []; const invalidItems = []; let newCart = cart.map(oldItem => { var _a, _b, _c, _d; const newItem = itemLookup[oldItem.id]; if (!newItem || checkSoldOut(oldItem.id, newItem.suspend_until, soldOut, requestedAt)) { missingItems.push(oldItem); return null; } let missingGroups = []; const invalidGroups = []; const missingOptions = []; const invalidOptions = []; const newGroups = makeGroupsLookup(newItem); // check if any required groups for the new item are missing from the old item const newRequiredGroups = Object.values(newGroups).filter(i => i.min > 0); const oldGroupIds = (_a = oldItem.groups) === null || _a === void 0 ? void 0 : _a.map(i => i.id); missingGroups = newRequiredGroups.filter(i => !(oldGroupIds === null || oldGroupIds === void 0 ? void 0 : oldGroupIds.includes(i.id))); const newEmptyGroups = Object.values(newGroups) .filter((i) => { return i.min === 0 && !(oldGroupIds === null || oldGroupIds === void 0 ? void 0 : oldGroupIds.includes(i.id)); }) .map((i) => (Object.assign(Object.assign({}, i), { options: Object.values(i.options) }))); const updatedGroups = (_b = oldItem.groups) === null || _b === void 0 ? void 0 : _b.map(group => { const optionCount = group.options.reduce((t, i) => (t += i.quantity), 0); const newGroup = newGroups[group.id]; // check if option group is still part of the item if (!newGroup) { if (optionCount > 0) { invalidGroups.push(group); return group; } else { return null; } } else { const updatedOptions = group.options.map(option => { const newOption = newGroup.options[option.id]; // check to see if any OLD options aren't avaiilable on the current menu // or if any of the options are currently sold out if (!newOption || (option.quantity > 0 && checkSoldOut(option.id, option.suspend_until, soldOut, requestedAt))) { missingOptions.push(option); return option; } else { // check to see if option quantity is valid relative to option min & max // ignore the option increment setting for the time being if ((newOption.max && option.quantity > newOption.max) || (newOption.min && option.quantity < newOption.min)) { invalidOptions.push(option); return option; } else { // update the old option price to the new option price const updatedOption = Object.assign(Object.assign({}, option), { price: newOption.price }); return updatedOption; } } }); // check to see if the option count is greater than the max (if one exists) // or less than the min (if one exists) if ((newGroup.max && optionCount > newGroup.max) || (newGroup.min && optionCount < newGroup.min)) { invalidGroups.push(group); } const updatedGroup = Object.assign(Object.assign({}, group), { options: updatedOptions }); return updatedGroup; } }); if (missingGroups.length || invalidGroups.length || missingOptions.length || invalidOptions.length) { const invalidItem = Object.assign(Object.assign({}, oldItem), { missingGroups, invalidGroups, missingOptions, invalidOptions }); invalidItems.push(invalidItem); return null; } else { // check to see if this item's quantity is below the min or // above the max from the current menu if ((newItem.max && ((_c = oldItem.quantity) !== null && _c !== void 0 ? _c : 0) > newItem.max) || (newItem.min && ((_d = oldItem.quantity) !== null && _d !== void 0 ? _d : 0) < newItem.min)) { const invalidItem = Object.assign(Object.assign({}, oldItem), { missingGroups, invalidGroups, missingOptions, invalidOptions }); invalidItems.push(invalidItem); return null; } const nonNullGroups = updatedGroups === null || updatedGroups === void 0 ? void 0 : updatedGroups.filter(i => i !== null); const allNewGroups = [...(nonNullGroups !== null && nonNullGroups !== void 0 ? nonNullGroups : []), ...newEmptyGroups]; const updatedItem = Object.assign(Object.assign({}, oldItem), { groups: allNewGroups }); // update the old item price to the new item price updatedItem.price = newItem.price; const pricedItem = calcPrices(updatedItem); return pricedItem; } }); newCart = newCart.filter(i => i !== null); if (missingItems.length || invalidItems.length) { errors = { missingItems, invalidItems }; } return { newCart: newCart, errors }; }; /* order submission */ export const getDefaultTip = (config) => { if (!config || !config.gratuity || !config.gratuity.has_tip) return null; return config.gratuity.default ? config.gratuity.default.amount : null; }; export const prepareOrder = (data) => { const order = { revenue_center_id: data.revenueCenterId, service_type: data.serviceType, requested_at: data.requestedAt, cart: makeSimpleCart(data.cart) }; if (data.customer) order.customer = data.customer; if (data.details) { const details = Object.assign({}, data.details); // person_count must be submitted as integer if (details.person_count) details.person_count = parseInt(`${details.person_count}`) || null; if (data.deviceType) details.device_type = data.deviceType; if (data.table || data.prepType) { const prepType = data.prepType ? data.prepType : data.table ? 'EAT_HERE' : 'TAKE_OUT'; details.notes_internal = prepType === 'EAT_HERE' ? data.table ? `DINE IN -- TABLE ${data.table}` : 'DINE IN' : 'TAKE OUT'; } order.details = Object.assign({}, details); } if (data.surcharges) order.surcharges = data.surcharges; if (data.discounts) order.discounts = data.discounts; if (data.discountsInternal) order.discounts_internal = data.discountsInternal; if (data.promoCodes) order.promo_codes = data.promoCodes; if (data.points) order.points = data.points; if (data.tip) order.tip = data.tip; if (data.deposit) order.deposit = data.deposit; if (data.tenders) order.tenders = data.tenders; if (data.address && data.serviceType === 'DELIVERY') { order.address = data.address; } else { order.address = null; } if (data.orderId) order.order_id = data.orderId; if (data.cartId) order.cart_id = data.cartId; if (data.save) order.save = data.save; if (data.token) order.token = data.token; if (data.kount_device_session_id) order.kount_device_session_id = data.kount_device_session_id; return order; }; export const checkAmountRemaining = (total, tenders) => { if (!tenders) return total ? parseFloat(total) : 0.0; let remaining = parseFloat(total) - tenders.reduce((t, i) => { var _a; return (t += parseFloat((_a = i.amount) !== null && _a !== void 0 ? _a : '0')); }, 0.0); remaining = Math.round((remaining + Number.EPSILON) * 100) / 100; return Object.is(remaining, -0) ? 0.0 : remaining; }; export const makeDeviceType = (deviceType, deviceId) => { // when app is only built in phone mode (not Universal) react-native-device-info shows device type as "Handset" // so override it to "Tablet" if the deviceId has "iPad" in the name deviceType = deviceType === 'Handset' && !!deviceId && /iPad/.test(deviceId) ? 'Tablet' : deviceType; switch (deviceType === null || deviceType === void 0 ? void 0 : deviceType.toLowerCase()) { case 'tablet': return 'TABLET'; case 'mobile': case 'handset': return 'MOBILE'; case 'browser': case 'desktop': default: return 'DESKTOP'; } };