diff --git a/client/modules/i18n/currency.js b/client/modules/i18n/currency.js index c61e7d83d66..f20db9a6f2f 100644 --- a/client/modules/i18n/currency.js +++ b/client/modules/i18n/currency.js @@ -1,6 +1,5 @@ -import accounting from "accounting-js"; -import { Reaction, Logger } from "/client/api"; -import ReactionError from "@reactioncommerce/reaction-error"; +import { formatMoney } from "accounting-js"; +import { Reaction } from "/client/api"; import { Shops, Accounts } from "/lib/collections"; import { currencyDep } from "./main"; @@ -48,17 +47,9 @@ export function findCurrency(defaultCurrency, useDefaultShopCurrency) { * @memberof i18n * @method * @param {String} formatPrice - currentPrice or "xx.xx - xx.xx" formatted String - * @param {Boolean} useDefaultShopCurrency - flag for displaying shop's currency in Admin view of PDP * @return {String} returns locale formatted and exchange rate converted values */ -export function formatPriceString(formatPrice, useDefaultShopCurrency) { - let defaultShopCurrency = useDefaultShopCurrency; - - // in case useDefaultShopCurrency is a Spacebars.kw we have this check - if (typeof useDefaultShopCurrency === "object" || !useDefaultShopCurrency) { - defaultShopCurrency = false; - } - +export function formatPriceString(formatPrice) { currencyDep.depend(); const locale = Reaction.Locale.get(); @@ -72,93 +63,47 @@ export function formatPriceString(formatPrice, useDefaultShopCurrency) { } // get user currency instead of locale currency - const userCurrency = findCurrency(locale.currency, defaultShopCurrency); + const userCurrency = findCurrency(locale.currency, true); - // for the cases then we have only one price. It is a number. const currentPrice = formatPrice.toString(); - let price = 0; const prices = currentPrice.indexOf(" - ") >= 0 ? - currentPrice.split(" - ") : [currentPrice]; - - // basic "for" is faster then "for ...of" for arrays. We need more speed here - const len = prices.length; - for (let i = 0; i < len; i += 1) { - const originalPrice = prices[i]; - try { - // we know the locale, but we don"t know exchange rate. In that case we - // should return to default shop currency - if (typeof userCurrency.rate !== "number") { - throw new ReactionError("invalid-exchange-rate", "Exchange rate is invalid"); - } - // Only convert for non-admin view. - if (!defaultShopCurrency) { - prices[i] *= userCurrency.rate; - } + currentPrice.split(" - ") : [currentPrice, currentPrice]; - price = _formatPrice( - price, originalPrice, prices[i], - currentPrice, userCurrency, i, len - ); - } catch (error) { - Logger.debug("currency error, fallback to shop currency"); - price = _formatPrice( - price, originalPrice, prices[i], - currentPrice, locale.shopCurrency, i, len - ); - } - } - return price; + return getDisplayPrice(Number(prices[0]), Number(prices[1]), userCurrency); } /** - * _formatPrice - * private function for formatting locale currency - * @private - * @param {Number} price price - * @param {Number} originalPrice originalPrice - * @param {Number} actualPrice actualPrice - * @param {Number} currentPrice currentPrice - * @param {Number} currency currency - * @param {Number} pos position - * @param {Number} len length - * @return {Number} formatted price + * @name getDisplayPrice + * @method + * @summary Returns a price for front-end display in the given currency + * @param {Number} minPrice Minimum price + * @param {Number} maxPrice Maximum price + * @param {Object} currencyInfo Currency object from Reaction shop schema + * @returns {String} Display price with currency symbol(s) */ -function _formatPrice( - price, originalPrice, actualPrice, currentPrice, currency, - pos, len -) { - // this checking for locale.shopCurrency mostly - if (typeof currency !== "object") { - return false; - } - - let adjustedPrice = actualPrice; - let formattedPrice; - - // Precision is mis-used in accounting js. Scale is the propery term for number - // of decimal places. Let's adjust it here so accounting.js does not break. - if (currency.scale !== undefined) { - currency.precision = currency.scale; - } - - // If there are no decimal places, in the case of the Japanese Yen, we adjust it here. - if (currency.scale === 0) { - adjustedPrice = actualPrice * 100; - } +function getDisplayPrice(minPrice, maxPrice, currencyInfo = { symbol: "" }) { + let displayPrice; - // @param {string} currency.where: If it presents - in situation then two - // prices in string, currency sign will be placed just outside the right price. - // For now it should be manually added to fixtures shop data. - if (typeof currency.where === "string" && currency.where === "right" && - len > 1 && pos === 0) { - const modifiedCurrency = Object.assign({}, currency, { - symbol: "" - }); - formattedPrice = accounting.formatMoney(adjustedPrice, modifiedCurrency); + if (minPrice === maxPrice) { + // Display 1 price (min = max) + displayPrice = formatMoney(minPrice, currencyInfo); } else { - // accounting api: http://openexchangerates.github.io/accounting.js/ - formattedPrice = accounting.formatMoney(adjustedPrice, currency); + // Display range + let minFormatted; + + // Account for currencies where only one currency symbol should be displayed. Ex: 680,18 - 1 359,68 руб. + if (currencyInfo.where === "right") { + const modifiedCurrencyInfo = Object.assign({}, currencyInfo, { + symbol: "" + }); + minFormatted = formatMoney(minPrice, modifiedCurrencyInfo).trim(); + } else { + minFormatted = formatMoney(minPrice, currencyInfo); + } + + const maxFormatted = formatMoney(maxPrice, currencyInfo); + displayPrice = `${minFormatted} - ${maxFormatted}`; } - return price === 0 ? currentPrice.replace(originalPrice, formattedPrice) : price.replace(originalPrice, formattedPrice); + return displayPrice; } diff --git a/imports/plugins/core/core/server/publications/collections/products.js b/imports/plugins/core/core/server/publications/collections/products.js index 6ded800faad..e986f4c31c8 100644 --- a/imports/plugins/core/core/server/publications/collections/products.js +++ b/imports/plugins/core/core/server/publications/collections/products.js @@ -392,33 +392,17 @@ Meteor.publish("ProductsAdminList", function (page = 0, limit = 24, productFilte delete selector.isVisible; // in edit mode, you should see all products - Counts.publish(this, "products-count", Products.find(selector)); + // noReady and nonReactive are needed for good performance on + // large data sets + Counts.publish(this, "products-count", Products.find(selector), { + noReady: true, + nonReactive: true + }); - // Get the IDs of the first N (limit) top-level products that match the query - const productIds = Products.find(selector, { + // Get the first N (limit) top-level products that match the query + return Products.find(selector, { sort, skip: page * limit, limit - }, { - fields: { - _id: 1 - } - }).map((product) => product._id); - - // Return a cursor for the matching products plus all their variants - return Products.find({ - $or: [{ - ancestors: { - $in: productIds - } - }, { - _id: { - $in: productIds - } - }] - }, { - sort - // We shouldn't limit here. Otherwise we are limited to 24 total products which - // could be far less than 24 top-level products. }); }); diff --git a/lib/api/catalog.js b/lib/api/catalog.js index 35884bf23fa..3a334b18182 100644 --- a/lib/api/catalog.js +++ b/lib/api/catalog.js @@ -1,4 +1,3 @@ -import _ from "lodash"; import { Products } from "/lib/collections"; /** @@ -19,51 +18,8 @@ export default { * @return {Object} range, min, max */ getProductPriceRange(productId) { - const product = Products.findOne(productId); - if (!product) { - return { - range: "0", - min: 0, - max: 0 - }; - } - - const variants = this.getTopVariants(product._id); - // if we have variants we have a price range. - // this processing will default on the server - const visibileVariant = variants.filter((variant) => variant.isVisible === true); - - if (visibileVariant.length > 0) { - const variantPrices = []; - variants.forEach((variant) => { - if (variant.isVisible === true) { - const range = this.getVariantPriceRange(variant._id); - if (typeof range === "string") { - const firstPrice = parseFloat(range.substr(0, range.indexOf(" "))); - const lastPrice = parseFloat(range.substr(range.lastIndexOf(" ") + 1)); - variantPrices.push(firstPrice, lastPrice); - } else { - variantPrices.push(range); - } - } else { - variantPrices.push(0, 0); - } - }); - const priceMin = _.min(variantPrices); - const priceMax = _.max(variantPrices); - let priceRange = `${priceMin.toFixed(2)} - ${priceMax.toFixed(2)}`; - // if we don't have a range - if (priceMin === priceMax) { - priceRange = priceMin.toFixed(2); - } - return { - range: priceRange, - min: priceMin, - max: priceMax - }; - } - - if (!product.price) { + const product = Products.findOne({ _id: productId }); + if (!product || !product.price) { return { range: "0", min: 0,