<commit_before>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

(function() {

    'use strict';

    /**
     * @ngdoc service
     * @name requisition-calculations.calculationFactory
     *
     * @description
     * Responsible for storing all the calculations related with the line item and product grid.
     */
    angular
    .module('requisition-calculations')
    .factory('calculationFactory', factory);

    factory.$inject = ['TEMPLATE_COLUMNS', 'COLUMN_SOURCES', '$filter', 'stockReasonsCalculations'];

    function factory(TEMPLATE_COLUMNS, COLUMN_SOURCES, $filter, stockReasonsCalculations) {
        var A = TEMPLATE_COLUMNS.BEGINNING_BALANCE,
        B = TEMPLATE_COLUMNS.TOTAL_RECEIVED_QUANTITY,
        C = TEMPLATE_COLUMNS.TOTAL_CONSUMED_QUANTITY,
        D = TEMPLATE_COLUMNS.TOTAL_LOSSES_AND_ADJUSTMENTS,
        E = TEMPLATE_COLUMNS.STOCK_ON_HAND,
        H = TEMPLATE_COLUMNS.MAXIMUM_STOCK_QUANTITY,
        J = TEMPLATE_COLUMNS.REQUESTED_QUANTITY,
        K = TEMPLATE_COLUMNS.APPROVED_QUANTITY,
        N = TEMPLATE_COLUMNS.ADJUSTED_CONSUMPTION,
        M = TEMPLATE_COLUMNS.CALCULATED_ORDER_QUANTITY,
        P = TEMPLATE_COLUMNS.AVERAGE_CONSUMPTION,
        Q = TEMPLATE_COLUMNS.TOTAL_COST,
        T = TEMPLATE_COLUMNS.PRICE_PER_PACK,
        V = TEMPLATE_COLUMNS.PACKS_TO_SHIP,
        Y = TEMPLATE_COLUMNS.TOTAL;

        var calculationFactory = {
            calculatedOrderQuantity: calculateOrderQuantity,
            totalConsumedQuantity: calculateTotalConsumedQuantity,
            stockOnHand: calculateStockOnHand,
            totalLossesAndAdjustments: calculateTotalLossesAndAdjustments,
            total: calculateTotal,
            packsToShip: calculatePacksToShip,
            totalCost: calculateTotalCost,
            adjustedConsumption: calculateAdjustedConsumption,
            maximumStockQuantity: calculateMaximumStockQuantity,
            averageConsumption: calculateAverageConsumption
        };
        return calculationFactory;

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name totalConsumedQuantity
         *
         * @description
         * Calculates the value of the Total Consumed Quantity column based on the given line item.
         *
         * @param  {Object} lineItem the line item to calculate the value from
         * @return {Number}          the calculated Total Consumed Quantity value
         */
        function calculateTotalConsumedQuantity(lineItem, requisition) {
            return getItem(lineItem, A) + getItem(lineItem, B) + getItem(lineItem, D) - getItem(lineItem, E);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name stockOnHand
         *
         * @description
         * Calculates the value of the Stock On Hand column based on the given line item.
         *
         * @param  {Object} lineItem the line item to calculate the value from
         * @return {Number}          the calculated Stock On Hand value
         */
        function calculateStockOnHand(lineItem) {
            return getItem(lineItem, A) + getItem(lineItem, B) - getItem(lineItem, C) + getItem(lineItem, D);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name total
         *
         * @description
         * Calculates the value of the Total column based on the given line item.
         *
         * @param  {Object} lineItem the line item to calculate the value from
         * @return {Number}          the calculated Total value
         */
        function calculateTotal(lineItem) {
            return getItem(lineItem, A) + getItem(lineItem, B);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name totalLossesAndAdjustments
         *
         * @description
         * Calculates the value of the Total Losses and Adjustments column based on the
         * given line item and adjustment reasons.
         *
         * @param  {Array}  adjustments     the list of adjustments to sum up
         * @param  {Array}  reasons         the list of stock adjustment reasons
         * @return {Number}                 the calculated Total Losses and Adjustments value
         */
        function calculateTotalLossesAndAdjustments(adjustments, reasons) {
            var adjustmentsForCaculations = angular.copy(adjustments);

            angular.forEach(adjustmentsForCaculations, function(adjustment) {
                var filteredReasons = $filter('filter')(reasons, {id: adjustment.reasonId}, true);
                adjustment.reason = (filteredReasons) ? filteredReasons[0] : null;
            });

            return stockReasonsCalculations.calculateTotal(adjustmentsForCaculations);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name packsToShip
         *
         * @description
         * Calculates the value of the Packs to Ship column based on the given line item and requisition status.
         *
         * @param  {Object} lineItem    the line item to calculate the value from
         * @param  {Object} requisition the requisition
         * @return {Number}             the calculated Packs to Ship value
         */
        function calculatePacksToShip(lineItem, requisition) {
            var orderQuantity = getOrderQuantity(lineItem, requisition),
            netContent = lineItem.orderable.netContent;

            if (!orderQuantity || !netContent) {
                return 0;
            } else {
                var remainderQuantity = orderQuantity % netContent,
                packsToShip = (orderQuantity - remainderQuantity) / netContent;

                if (remainderQuantity > 0 && remainderQuantity > lineItem.orderable.packRoundingThreshold) {
                    packsToShip += 1;
                }

                if (packsToShip === 0 && !lineItem.orderable.roundToZero) {
                    packsToShip = 1;
                }

                return packsToShip;
            }
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name calculateTotalCost
         *
         * @description
         * Calculates the total cost by multiplying price per pack and packs to ship.
         *
         * @param  {Object} lineItem the line item to get the values from
         * @param  {Object} requisition the requisition
         * @return {Number}          the total cost of this line item
         */
        function calculateTotalCost(lineItem, requisition) {
            var packsToShip = this.packsToShip(lineItem, requisition);
            var pricePerPack = lineItem[T];
            if (pricePerPack === undefined) {
                pricePerPack = 0;
            }

            return pricePerPack * packsToShip;
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name adjustedConsumption
         *
         * @description
         * Calculates the value of the Adjusted Consumption column based on the given line item. If
         * consumed quantity is calculated, the calculated value will be used.
         *
         * @param  {Object} lineItem    the line item to calculate the value from
         * @param  {Object} requisition the requisition with required period
         * @return {Number}             the calculated Adjusted Consumption value
         */
        function calculateAdjustedConsumption(lineItem, requisition) {
            var cColumn = requisition.template.getColumn(C),
                consumedQuantity = getColumnValue(lineItem, requisition, cColumn);

            if (consumedQuantity === undefined) {
                return 0;
            }

            var totalDays = 30 * requisition.processingPeriod.durationInMonths;
            var stockoutDays = lineItem.totalStockoutDays === undefined ? 0: lineItem.totalStockoutDays;
            var nonStockoutDays = totalDays - stockoutDays;
            if (nonStockoutDays === 0) {
                return consumedQuantity;
            }

            var adjustedConsumption = Math.ceil(consumedQuantity * (totalDays / nonStockoutDays));
            return adjustedConsumption;
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name averageConsumption
         *
         * @description
         * Calculates the value of the Average Consumption column based on the given line item.
         *
         * @param  {Object} lineItem    the line item to calculate the value from
         * @param  {Object} requisition the requisition with required period
         * @return {Number}             the calculated Average Consumption value
         */
        function calculateAverageConsumption(lineItem, requisition) {
            var adjustedConsumptions = angular.copy(lineItem.previousAdjustedConsumptions);
            adjustedConsumptions.push(calculateAdjustedConsumption(lineItem, requisition));

            var numberOfPeriods = adjustedConsumptions.length;

            //if there is no previous adjusted consumption
            if (numberOfPeriods === 1) {
                return adjustedConsumptions[0];
            }
            //if there is only one previous adjusted consumption
            else if (numberOfPeriods === 2) {
                return Math.ceil((adjustedConsumptions[0] + adjustedConsumptions[1])/2);
            }
            //if more than one previous adjusted consumption
            else {
                var sum = 0;
                adjustedConsumptions.forEach(function (adjustedConsumption) {
                    sum += adjustedConsumption;
                });
                return Math.ceil(sum / numberOfPeriods);
            }
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name getOrderQuantity
         * @private
         *
         * @description
         * Returns the value of the order quantity based on the requisition status.
         *
         * @param  {Object} lineItem    the line item to get the order quantity from
         * @param  {String} requisition the requisition with required status
         * @return {Number}             the value of the order quantity
         */
        function getOrderQuantity(lineItem, requisition) {
            var orderQuantity = null;

            if (requisition.$isAfterAuthorize()) {
                orderQuantity = lineItem[K];
            } else {
                var jColumn = requisition.template.getColumn(J),
                    mColumn = requisition.template.getColumn(M);

                if (shouldReturnRequestedQuantity(lineItem, jColumn)) {
                    orderQuantity = lineItem[J];
                } else if (mColumn) {
                    orderQuantity = calculateOrderQuantity(lineItem, requisition);
                }
            }

            return orderQuantity;
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name maximumStockQuantity
         *
         * @description
         * Calculates the value of the Maximum Stock Quantity column based on the given line item.
         *
         * @param  {Object} lineItem    the line item to get the order quantity from
         * @param  {String} requisition the requisition with template
         * @return {Number}             the calculated Maximum Stock Quantity value
         */
        function calculateMaximumStockQuantity(lineItem, requisition) {
            var hColumn = requisition.template.getColumn(H),
                pColumn = requisition.template.getColumn(P),
                pValue;

            if (pColumn) {
                pValue = getColumnValue(lineItem, requisition, pColumn);
                pValue = pValue === undefined ? 0 : pValue
            }

            return hColumn && hColumn.option.optionName === 'default' ?
                Math.round(pValue * lineItem.maxPeriodsOfStock) : 0;
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name calculatedOrderQuantity
         *
         * @description
         * Calculates the value of the Calculated Order Quantity column.
         *
         * @param  {Object} lineItem    the line item to calculate the value for
         * @param  {Object} requisition the requisition used with calculation
         * @return {Number}             the calculated order quantity
         */
        function calculateOrderQuantity(lineItem, requisition) {
            var eColumn = requisition.template.getColumn(E),
                hColumn = requisition.template.getColumn(H);

            if (!eColumn || !hColumn) {
                return null;
            }

            var stockOnHand = getColumnValue(lineItem, requisition, eColumn),
                maximumStockQuantity = getColumnValue(lineItem, requisition, hColumn);

            stockOnHand = stockOnHand === undefined ? 0 : stockOnHand;

            return Math.max(maximumStockQuantity - stockOnHand, 0);
        }

        function getColumnValue(lineItem, requisition, column) {
            if (column.source === COLUMN_SOURCES.CALCULATED) {
                return calculationFactory[column.name](lineItem, requisition);
            }
            return lineItem[column.name];
        }

        function getItem(lineItem, name) {
            return lineItem[name] === undefined ? 0 : lineItem[name];
        }

        function shouldReturnRequestedQuantity(lineItem, jColumn) {
            return lineItem.isNonFullSupply() || (isDisplayed(jColumn) && isFilled(lineItem[J]));
        }

        function isFilled(value) {
            //We want to treat 0 as a valid value thus not using return value
            return value !== null && value !== undefined;
        }

        function isDisplayed(column) {
            return column && column.$display;
        }
    }
})();
<commit_msg>fix typo<commit_after>/*
 * This program is part of the OpenLMIS logistics management information system platform software.
 * Copyright © 2017 VillageReach
 *
 * This program is free software: you can redistribute it and/or modify it under the terms
 * of the GNU Affero General Public License as published by the Free Software Foundation, either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Affero General Public License for more details. You should have received a copy of
 * the GNU Affero General Public License along with this program. If not, see
 * http://www.gnu.org/licenses.  For additional information contact info@OpenLMIS.org.
 */

(function() {

    'use strict';

    /**
     * @ngdoc service
     * @name requisition-calculations.calculationFactory
     *
     * @description
     * Responsible for storing all the calculations related with the line item and product grid.
     */
    angular
    .module('requisition-calculations')
    .factory('calculationFactory', factory);

    factory.$inject = ['TEMPLATE_COLUMNS', 'COLUMN_SOURCES', '$filter', 'stockReasonsCalculations'];

    function factory(TEMPLATE_COLUMNS, COLUMN_SOURCES, $filter, stockReasonsCalculations) {
        var A = TEMPLATE_COLUMNS.BEGINNING_BALANCE,
        B = TEMPLATE_COLUMNS.TOTAL_RECEIVED_QUANTITY,
        C = TEMPLATE_COLUMNS.TOTAL_CONSUMED_QUANTITY,
        D = TEMPLATE_COLUMNS.TOTAL_LOSSES_AND_ADJUSTMENTS,
        E = TEMPLATE_COLUMNS.STOCK_ON_HAND,
        H = TEMPLATE_COLUMNS.MAXIMUM_STOCK_QUANTITY,
        J = TEMPLATE_COLUMNS.REQUESTED_QUANTITY,
        K = TEMPLATE_COLUMNS.APPROVED_QUANTITY,
        N = TEMPLATE_COLUMNS.ADJUSTED_CONSUMPTION,
        M = TEMPLATE_COLUMNS.CALCULATED_ORDER_QUANTITY,
        P = TEMPLATE_COLUMNS.AVERAGE_CONSUMPTION,
        Q = TEMPLATE_COLUMNS.TOTAL_COST,
        T = TEMPLATE_COLUMNS.PRICE_PER_PACK,
        V = TEMPLATE_COLUMNS.PACKS_TO_SHIP,
        Y = TEMPLATE_COLUMNS.TOTAL;

        var calculationFactory = {
            calculatedOrderQuantity: calculateOrderQuantity,
            totalConsumedQuantity: calculateTotalConsumedQuantity,
            stockOnHand: calculateStockOnHand,
            totalLossesAndAdjustments: calculateTotalLossesAndAdjustments,
            total: calculateTotal,
            packsToShip: calculatePacksToShip,
            totalCost: calculateTotalCost,
            adjustedConsumption: calculateAdjustedConsumption,
            maximumStockQuantity: calculateMaximumStockQuantity,
            averageConsumption: calculateAverageConsumption
        };
        return calculationFactory;

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name totalConsumedQuantity
         *
         * @description
         * Calculates the value of the Total Consumed Quantity column based on the given line item.
         *
         * @param  {Object} lineItem the line item to calculate the value from
         * @return {Number}          the calculated Total Consumed Quantity value
         */
        function calculateTotalConsumedQuantity(lineItem, requisition) {
            return getItem(lineItem, A) + getItem(lineItem, B) + getItem(lineItem, D) - getItem(lineItem, E);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name stockOnHand
         *
         * @description
         * Calculates the value of the Stock On Hand column based on the given line item.
         *
         * @param  {Object} lineItem the line item to calculate the value from
         * @return {Number}          the calculated Stock On Hand value
         */
        function calculateStockOnHand(lineItem) {
            return getItem(lineItem, A) + getItem(lineItem, B) - getItem(lineItem, C) + getItem(lineItem, D);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name total
         *
         * @description
         * Calculates the value of the Total column based on the given line item.
         *
         * @param  {Object} lineItem the line item to calculate the value from
         * @return {Number}          the calculated Total value
         */
        function calculateTotal(lineItem) {
            return getItem(lineItem, A) + getItem(lineItem, B);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name totalLossesAndAdjustments
         *
         * @description
         * Calculates the value of the Total Losses and Adjustments column based on the
         * given line item and adjustment reasons.
         *
         * @param  {Array}  adjustments     the list of adjustments to sum up
         * @param  {Array}  reasons         the list of stock adjustment reasons
         * @return {Number}                 the calculated Total Losses and Adjustments value
         */
        function calculateTotalLossesAndAdjustments(adjustments, reasons) {
            var adjustmentsForCalculations = angular.copy(adjustments);

            angular.forEach(adjustmentsForCalculations, function(adjustment) {
                var filteredReasons = $filter('filter')(reasons, {id: adjustment.reasonId}, true);
                adjustment.reason = (filteredReasons) ? filteredReasons[0] : null;
            });

            return stockReasonsCalculations.calculateTotal(adjustmentsForCalculations);
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name packsToShip
         *
         * @description
         * Calculates the value of the Packs to Ship column based on the given line item and requisition status.
         *
         * @param  {Object} lineItem    the line item to calculate the value from
         * @param  {Object} requisition the requisition
         * @return {Number}             the calculated Packs to Ship value
         */
        function calculatePacksToShip(lineItem, requisition) {
            var orderQuantity = getOrderQuantity(lineItem, requisition),
            netContent = lineItem.orderable.netContent;

            if (!orderQuantity || !netContent) {
                return 0;
            } else {
                var remainderQuantity = orderQuantity % netContent,
                packsToShip = (orderQuantity - remainderQuantity) / netContent;

                if (remainderQuantity > 0 && remainderQuantity > lineItem.orderable.packRoundingThreshold) {
                    packsToShip += 1;
                }

                if (packsToShip === 0 && !lineItem.orderable.roundToZero) {
                    packsToShip = 1;
                }

                return packsToShip;
            }
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name calculateTotalCost
         *
         * @description
         * Calculates the total cost by multiplying price per pack and packs to ship.
         *
         * @param  {Object} lineItem the line item to get the values from
         * @param  {Object} requisition the requisition
         * @return {Number}          the total cost of this line item
         */
        function calculateTotalCost(lineItem, requisition) {
            var packsToShip = this.packsToShip(lineItem, requisition);
            var pricePerPack = lineItem[T];
            if (pricePerPack === undefined) {
                pricePerPack = 0;
            }

            return pricePerPack * packsToShip;
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name adjustedConsumption
         *
         * @description
         * Calculates the value of the Adjusted Consumption column based on the given line item. If
         * consumed quantity is calculated, the calculated value will be used.
         *
         * @param  {Object} lineItem    the line item to calculate the value from
         * @param  {Object} requisition the requisition with required period
         * @return {Number}             the calculated Adjusted Consumption value
         */
        function calculateAdjustedConsumption(lineItem, requisition) {
            var cColumn = requisition.template.getColumn(C),
                consumedQuantity = getColumnValue(lineItem, requisition, cColumn);

            if (consumedQuantity === undefined) {
                return 0;
            }

            var totalDays = 30 * requisition.processingPeriod.durationInMonths;
            var stockoutDays = lineItem.totalStockoutDays === undefined ? 0: lineItem.totalStockoutDays;
            var nonStockoutDays = totalDays - stockoutDays;
            if (nonStockoutDays === 0) {
                return consumedQuantity;
            }

            var adjustedConsumption = Math.ceil(consumedQuantity * (totalDays / nonStockoutDays));
            return adjustedConsumption;
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name averageConsumption
         *
         * @description
         * Calculates the value of the Average Consumption column based on the given line item.
         *
         * @param  {Object} lineItem    the line item to calculate the value from
         * @param  {Object} requisition the requisition with required period
         * @return {Number}             the calculated Average Consumption value
         */
        function calculateAverageConsumption(lineItem, requisition) {
            var adjustedConsumptions = angular.copy(lineItem.previousAdjustedConsumptions);
            adjustedConsumptions.push(calculateAdjustedConsumption(lineItem, requisition));

            var numberOfPeriods = adjustedConsumptions.length;

            //if there is no previous adjusted consumption
            if (numberOfPeriods === 1) {
                return adjustedConsumptions[0];
            }
            //if there is only one previous adjusted consumption
            else if (numberOfPeriods === 2) {
                return Math.ceil((adjustedConsumptions[0] + adjustedConsumptions[1])/2);
            }
            //if more than one previous adjusted consumption
            else {
                var sum = 0;
                adjustedConsumptions.forEach(function (adjustedConsumption) {
                    sum += adjustedConsumption;
                });
                return Math.ceil(sum / numberOfPeriods);
            }
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name getOrderQuantity
         * @private
         *
         * @description
         * Returns the value of the order quantity based on the requisition status.
         *
         * @param  {Object} lineItem    the line item to get the order quantity from
         * @param  {String} requisition the requisition with required status
         * @return {Number}             the value of the order quantity
         */
        function getOrderQuantity(lineItem, requisition) {
            var orderQuantity = null;

            if (requisition.$isAfterAuthorize()) {
                orderQuantity = lineItem[K];
            } else {
                var jColumn = requisition.template.getColumn(J),
                    mColumn = requisition.template.getColumn(M);

                if (shouldReturnRequestedQuantity(lineItem, jColumn)) {
                    orderQuantity = lineItem[J];
                } else if (mColumn) {
                    orderQuantity = calculateOrderQuantity(lineItem, requisition);
                }
            }

            return orderQuantity;
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name maximumStockQuantity
         *
         * @description
         * Calculates the value of the Maximum Stock Quantity column based on the given line item.
         *
         * @param  {Object} lineItem    the line item to get the order quantity from
         * @param  {String} requisition the requisition with template
         * @return {Number}             the calculated Maximum Stock Quantity value
         */
        function calculateMaximumStockQuantity(lineItem, requisition) {
            var hColumn = requisition.template.getColumn(H),
                pColumn = requisition.template.getColumn(P),
                pValue;

            if (pColumn) {
                pValue = getColumnValue(lineItem, requisition, pColumn);
                pValue = pValue === undefined ? 0 : pValue
            }

            return hColumn && hColumn.option.optionName === 'default' ?
                Math.round(pValue * lineItem.maxPeriodsOfStock) : 0;
        }

        /**
         * @ngdoc method
         * @methodOf requisition-calculations.calculationFactory
         * @name calculatedOrderQuantity
         *
         * @description
         * Calculates the value of the Calculated Order Quantity column.
         *
         * @param  {Object} lineItem    the line item to calculate the value for
         * @param  {Object} requisition the requisition used with calculation
         * @return {Number}             the calculated order quantity
         */
        function calculateOrderQuantity(lineItem, requisition) {
            var eColumn = requisition.template.getColumn(E),
                hColumn = requisition.template.getColumn(H);

            if (!eColumn || !hColumn) {
                return null;
            }

            var stockOnHand = getColumnValue(lineItem, requisition, eColumn),
                maximumStockQuantity = getColumnValue(lineItem, requisition, hColumn);

            stockOnHand = stockOnHand === undefined ? 0 : stockOnHand;

            return Math.max(maximumStockQuantity - stockOnHand, 0);
        }

        function getColumnValue(lineItem, requisition, column) {
            if (column.source === COLUMN_SOURCES.CALCULATED) {
                return calculationFactory[column.name](lineItem, requisition);
            }
            return lineItem[column.name];
        }

        function getItem(lineItem, name) {
            return lineItem[name] === undefined ? 0 : lineItem[name];
        }

        function shouldReturnRequestedQuantity(lineItem, jColumn) {
            return lineItem.isNonFullSupply() || (isDisplayed(jColumn) && isFilled(lineItem[J]));
        }

        function isFilled(value) {
            //We want to treat 0 as a valid value thus not using return value
            return value !== null && value !== undefined;
        }

        function isDisplayed(column) {
            return column && column.$display;
        }
    }
})();
<|endoftext|>


<commit_before>import React from 'react';
import PropTypes from 'prop-types';
import Logo from 'rsg-components/Logo';
import Markdown from 'rsg-components/Markdown';
import Styled from 'rsg-components/Styled';
import cx from 'classnames';

const styles = ({ color, fontFamily, fontSize, sidebarWidth, mq, space, maxWidth }) => ({
	root: {
		color: color.base,
		backgroundColor: color.baseBackground,
	},
	hasSidebar: {
		paddingLeft: sidebarWidth,
		[mq.small]: {
			paddingLeft: 0,
		},
	},
	content: {
		maxWidth,
		padding: [[space[2], space[4]]],
		margin: [[0, 'auto']],
		[mq.small]: {
			padding: space[2],
		},
		display: 'block',
	},
	sidebar: {
		backgroundColor: color.sidebarBackground,
		border: [[color.border, 'solid']],
		borderWidth: [[0, 1, 0, 0]],
		position: 'fixed',
		top: 0,
		left: 0,
		bottom: 0,
		width: sidebarWidth,
		overflow: 'auto',
		'-webkit-overflow-scrolling': 'touch',
		[mq.small]: {
			position: 'static',
			width: 'auto',
			borderWidth: [[1, 0, 0, 0]],
			paddingBottom: space[0],
		},
	},
	logo: {
		padding: space[2],
		borderBottom: [[1, color.border, 'solid']],
	},
	footer: {
		display: 'block',
		color: color.light,
		fontFamily: fontFamily.base,
		fontSize: fontSize.small,
	},
});

export function StyleGuideRenderer({ classes, title, homepageUrl, children, toc, hasSidebar }) {
	return (

				{children}

					<Markdown text={`Generated with [React Styleguidist](${homepageUrl})`} />

			{hasSidebar && (

						<Logo>{title}</Logo>

					{toc}

			)}

	);
}

StyleGuideRenderer.propTypes = {
	classes: PropTypes.object.isRequired,
	title: PropTypes.string.isRequired,
	homepageUrl: PropTypes.string.isRequired,
	children: PropTypes.node.isRequired,
	toc: PropTypes.node.isRequired,
	hasSidebar: PropTypes.bool,
};

export default Styled(styles)(StyleGuideRenderer);
<commit_msg>Fix: Remove color from the styleguide root element<commit_after>import React from 'react';
import PropTypes from 'prop-types';
import Logo from 'rsg-components/Logo';
import Markdown from 'rsg-components/Markdown';
import Styled from 'rsg-components/Styled';
import cx from 'classnames';

const styles = ({ color, fontFamily, fontSize, sidebarWidth, mq, space, maxWidth }) => ({
	root: {
		backgroundColor: color.baseBackground,
	},
	hasSidebar: {
		paddingLeft: sidebarWidth,
		[mq.small]: {
			paddingLeft: 0,
		},
	},
	content: {
		maxWidth,
		padding: [[space[2], space[4]]],
		margin: [[0, 'auto']],
		[mq.small]: {
			padding: space[2],
		},
		display: 'block',
	},
	sidebar: {
		backgroundColor: color.sidebarBackground,
		border: [[color.border, 'solid']],
		borderWidth: [[0, 1, 0, 0]],
		position: 'fixed',
		top: 0,
		left: 0,
		bottom: 0,
		width: sidebarWidth,
		overflow: 'auto',
		'-webkit-overflow-scrolling': 'touch',
		[mq.small]: {
			position: 'static',
			width: 'auto',
			borderWidth: [[1, 0, 0, 0]],
			paddingBottom: space[0],
		},
	},
	logo: {
		padding: space[2],
		borderBottom: [[1, color.border, 'solid']],
	},
	footer: {
		display: 'block',
		color: color.light,
		fontFamily: fontFamily.base,
		fontSize: fontSize.small,
	},
});

export function StyleGuideRenderer({ classes, title, homepageUrl, children, toc, hasSidebar }) {
	return (

				{children}

					<Markdown text={`Generated with [React Styleguidist](${homepageUrl})`} />

			{hasSidebar && (

						<Logo>{title}</Logo>

					{toc}

			)}

	);
}

StyleGuideRenderer.propTypes = {
	classes: PropTypes.object.isRequired,
	title: PropTypes.string.isRequired,
	homepageUrl: PropTypes.string.isRequired,
	children: PropTypes.node.isRequired,
	toc: PropTypes.node.isRequired,
	hasSidebar: PropTypes.bool,
};

export default Styled(styles)(StyleGuideRenderer);
<|endoftext|>


<commit_before>'use strict';

angular.module('confRegistrationWebApp')
  .controller('ReviewRegistrationCtrl', function ($scope, $rootScope, $location, $route, modalMessage, $http, registration, conference, RegistrationCache, validateRegistrant, $filter) {
    $rootScope.globalPage = {
      type: 'registration',
      mainClass: 'container front-form',
      bodyClass: 'user-registration',
      conference: conference,
      confId: conference.id,
      footer: false
    };

    if(_.isEmpty(registration.registrants)) {
      $location.path('/' + ($rootScope.registerMode || 'register') + '/' + conference.id + '/page/');
    }

    $scope.conference = conference;
    $scope.currentRegistration = registration;
    $scope.blocks = [];
    $scope.regValidate = [];

    //check if group registration is allowed based on registrants already in registration
    if(!_.isEmpty(registration.registrants)){
      $scope.allowGroupRegistration = false;
      angular.forEach(registration.registrants, function(r){
        if($scope.allowGroupRegistration){
          return;
        }
        var regType = _.find(conference.registrantTypes, { 'id': r.registrantTypeId });
        $scope.allowGroupRegistration = regType.allowGroupRegistrations;
      });
    }

    if (angular.isUndefined($scope.currentPayment)) {
      var paymentType;
      if(conference.acceptCreditCards) {
        paymentType = 'CREDIT_CARD';
      }else if(conference.acceptChecks){
        paymentType = 'CHECK';
      }else if(conference.acceptTransfers){
        paymentType = 'TRANSFER';
      }else if(conference.acceptScholarships){
        paymentType = 'SCHOLARSHIP';
      }

      $scope.currentPayment = {
        amount: $scope.currentRegistration.remainingBalance,
        paymentType: paymentType,
        creditCard: {},
        transfer: {},
        scholarship: {}
      };
    }

    angular.forEach(_.flatten(conference.registrationPages, 'blocks'), function (block) {
      if (block.type.indexOf('Content') === -1) {
        $scope.blocks.push(block);
      }
    });

    angular.forEach(registration.registrants, function (r) {
      $scope.regValidate[r.id] = validateRegistrant.validate(conference, r);
    });

    $scope.findAnswer = function (blockId) {
      return _.find($scope.answers, {blockId: blockId});
    };

    $scope.getBlock = function (blockId) {
      return _.find($scope.blocks, {id: blockId});
    };

    $scope.confirmRegistration = function () {
      $scope.submittingRegistration = true;

      /*if the totalPaid (previously) AND the amount of this payment are less than the minimum required deposit, then
        show and error message. the first payment must be at least the minimum deposit amount.  subsequent payments
        can be less than the amount.  this is confirmed by making sure the total previously paid is above the min deposit amount.
        */
      if ($scope.currentRegistration.pastPayments.length === 0 && Number($scope.currentPayment.amount) < $scope.currentRegistration.calculatedMinimumDeposit) {
        $scope.currentPayment.errors.push('You are required to pay at least the minimum deposit of ' + $filter('currency')(registration.calculatedMinimumDeposit, '$') + ' to register for this event.');
      }

      if(Number($scope.currentPayment.amount) > $scope.currentRegistration.remainingBalance) {
        $scope.currentPayment.errors.push('You are paying more than the total due of ' + $filter('currency')(registration.remainingBalance, '$') + ' to register for this event.');
      }
      if (Number($scope.currentPayment.amount) === 0 || !$scope.acceptedPaymentMethods()) {
        setRegistrationAsCompleted();
        return;
      }

      if (!_.isEmpty($scope.currentPayment.errors)) {
        modalMessage.error({
          'title': 'Please correct the following errors:',
          'message': $scope.currentPayment.errors
        });
        $scope.submittingRegistration = false;
        return;
      }

      if($scope.currentPayment.paymentType === 'CHECK' || $scope.currentPayment.paymentType === 'PAY_ON_SITE'){
        if(!$scope.currentRegistration.completed){
          setRegistrationAsCompleted();
        }else{
          $scope.submittingRegistration = false;
        }
        return;
      }

      var currentPayment = angular.copy($scope.currentPayment);
      currentPayment.readyToProcess = true;
      currentPayment.registrationId =  registration.id;
      delete currentPayment.errors;
      if(currentPayment.paymentType === 'CREDIT_CARD'){
        $http.get('payments/ccp-client-encryption-key').success(function(ccpClientEncryptionKey) {
          ccp.initialize(ccpClientEncryptionKey);
          currentPayment.creditCard.lastFourDigits = ccp.getAbbreviatedNumber(currentPayment.creditCard.number);
          currentPayment.creditCard.number = ccp.encrypt(currentPayment.creditCard.number);
          currentPayment.creditCard.cvvNumber = ccp.encrypt(currentPayment.creditCard.cvvNumber);
          postPayment(currentPayment);
        }).error(function() {
          modalMessage.error('An error occurred while requesting the ccp encryption key. Please try your payment again.');
        });
      }else{
        postPayment(currentPayment);
      }
    };

    var postPayment = function(currentPayment){
      $http.post('payments/', currentPayment).success(function () {
        delete $scope.currentPayment;
        if(!$scope.currentRegistration.completed) {
          setRegistrationAsCompleted();
        } else {
          $route.reload();
        }
      }).error(function () {
        $scope.submittingRegistration = false;
        modalMessage.error({
          'message': 'Your payment was declined, please verify your details or use a different payment method.',
          'forceAction': true
        });
      });
    };

    var setRegistrationAsCompleted = function() {
      registration.completed = true;

      RegistrationCache.update('registrations/' + registration.id, registration, function () {
        RegistrationCache.emptyCache();
        $route.reload();
      }, function (data) {
        $scope.currentRegistration.completed = false;
        $scope.submittingRegistration = false;
        modalMessage.error('An error occurred while submitting your registration. ' + (data.data.errorMessage ? data.data.errorMessage : ''));
      });
    };

    $scope.editRegistrant = function (id) {
        $location.path('/' + ($rootScope.registerMode || 'register') + '/' + conference.id + '/page/' + conference.registrationPages[0].id).search('reg', id);
    };

    $scope.removeRegistrant = function (id) {
      modalMessage.confirm({
        'title': 'Delete registrant?',
        'question': 'Are you sure you want to delete this registrant?'
      }).then(function(){
        $http({
          method: 'DELETE',
          url: 'registrants/' + id
        }).success(function () {
          $route.reload();
        }).error(function(){
          modalMessage.error({
            'message': 'An error occurred while removing registrant.'
          });
        });
      });
    };

    $scope.getRegistrantType = function(id){
      return _.find(conference.registrantTypes, { 'id': id });
    };

    $scope.isBlockInvalid = function(rId, bId){
      return _.contains($scope.regValidate[rId], bId);
    };

    $scope.allRegistrantsValid = function(){
      var returnVal = true;
      angular.forEach(registration.registrants, function (r) {
        if (angular.isUndefined($scope.regValidate[r.id])) {
          returnVal = false;
        }else{
          if($scope.regValidate[r.id].length){
            returnVal = false;
          }
        }
      });
      return returnVal;
    };

    $scope.blockVisibleForRegistrant = function(block, registrant){
      return validateRegistrant.blockVisible(block, registrant);
    };

    $scope.acceptedPaymentMethods = function(){
      var regTypesInRegistration = [];
      angular.forEach(_.uniq(_.pluck(registration.registrants, 'registrantTypeId')), function(registrantTypeId) {
        regTypesInRegistration.push($scope.getRegistrantType(registrantTypeId));
      });

      var paymentMethods = {
        acceptCreditCards: _.some(regTypesInRegistration, 'acceptCreditCards'),
        acceptChecks:_.some(regTypesInRegistration, 'acceptChecks'),
        acceptTransfers: _.some(regTypesInRegistration, 'acceptTransfers'),
        acceptScholarships: _.some(regTypesInRegistration, 'acceptScholarships'),
        acceptPayOnSite: _.some(regTypesInRegistration, 'acceptPayOnSite') && !registration.completed
      };
      return (!_.some(paymentMethods) ? false : paymentMethods);
    };

    $scope.registrantDeletable = function(r){
      if(registration.completed){
        return false;
      }
      var groupRegistrants = 0, noGroupRegistrants = 0;
      angular.forEach(registration.registrants, function(r){
        var regType = _.find(conference.registrantTypes, { 'id': r.registrantTypeId });
        if(regType.allowGroupRegistrations){
          groupRegistrants++;
        }else{
          noGroupRegistrants++;
        }
      });

      var regType = _.find(conference.registrantTypes, { 'id': r.registrantTypeId });
      if(regType.allowGroupRegistrations && groupRegistrants === 1 && noGroupRegistrants){
        return false;
      }
      return true;
    };

    $scope.validatePromo = function(inputCode){
      $scope.addingPromoCode = true;
      $http.post('registrations/' + registration.id + '/promotions', {code: inputCode}).success(function () {
        $route.reload();
      }).error(function (data, status) {
        $scope.addingPromoCode = false;
        modalMessage.error({
          'message': status === 404 ? 'The promo code you have entered is invalid or does not apply to your registration.' : data,
          'title': 'Invalid Code',
          'forceAction': true
        });
      });
    };

    $scope.deletePromotion = function (promoId) {
      modalMessage.confirm({
        'title': 'Delete Promotion',
        'question': 'Are you sure you want to delete this promotion?'
      }).then(function(){
        var regCopy = angular.copy($scope.currentRegistration);
        _.remove(regCopy.promotions, {id: promoId});
        $http.put('registrations/' + registration.id, regCopy).success(function () {
          $route.reload();
        }).error(function () {
          modalMessage.error('An error occurred while deleting promotion.');
        });
      });
    };
  });
<commit_msg>Allow registrant to be deleted after registration is complete<commit_after>'use strict';

angular.module('confRegistrationWebApp')
  .controller('ReviewRegistrationCtrl', function ($scope, $rootScope, $location, $route, modalMessage, $http, registration, conference, RegistrationCache, validateRegistrant, $filter) {
    $rootScope.globalPage = {
      type: 'registration',
      mainClass: 'container front-form',
      bodyClass: 'user-registration',
      conference: conference,
      confId: conference.id,
      footer: false
    };

    if(_.isEmpty(registration.registrants)) {
      $location.path('/' + ($rootScope.registerMode || 'register') + '/' + conference.id + '/page/');
    }

    $scope.conference = conference;
    $scope.currentRegistration = registration;
    $scope.blocks = [];
    $scope.regValidate = [];

    //check if group registration is allowed based on registrants already in registration
    if(!_.isEmpty(registration.registrants)){
      $scope.allowGroupRegistration = false;
      angular.forEach(registration.registrants, function(r){
        if($scope.allowGroupRegistration){
          return;
        }
        var regType = _.find(conference.registrantTypes, { 'id': r.registrantTypeId });
        $scope.allowGroupRegistration = regType.allowGroupRegistrations;
      });
    }

    if (angular.isUndefined($scope.currentPayment)) {
      var paymentType;
      if(conference.acceptCreditCards) {
        paymentType = 'CREDIT_CARD';
      }else if(conference.acceptChecks){
        paymentType = 'CHECK';
      }else if(conference.acceptTransfers){
        paymentType = 'TRANSFER';
      }else if(conference.acceptScholarships){
        paymentType = 'SCHOLARSHIP';
      }

      $scope.currentPayment = {
        amount: $scope.currentRegistration.remainingBalance,
        paymentType: paymentType,
        creditCard: {},
        transfer: {},
        scholarship: {}
      };
    }

    angular.forEach(_.flatten(conference.registrationPages, 'blocks'), function (block) {
      if (block.type.indexOf('Content') === -1) {
        $scope.blocks.push(block);
      }
    });

    angular.forEach(registration.registrants, function (r) {
      $scope.regValidate[r.id] = validateRegistrant.validate(conference, r);
    });

    $scope.findAnswer = function (blockId) {
      return _.find($scope.answers, {blockId: blockId});
    };

    $scope.getBlock = function (blockId) {
      return _.find($scope.blocks, {id: blockId});
    };

    $scope.confirmRegistration = function () {
      $scope.submittingRegistration = true;

      /*if the totalPaid (previously) AND the amount of this payment are less than the minimum required deposit, then
        show and error message. the first payment must be at least the minimum deposit amount.  subsequent payments
        can be less than the amount.  this is confirmed by making sure the total previously paid is above the min deposit amount.
        */
      if ($scope.currentRegistration.pastPayments.length === 0 && Number($scope.currentPayment.amount) < $scope.currentRegistration.calculatedMinimumDeposit) {
        $scope.currentPayment.errors.push('You are required to pay at least the minimum deposit of ' + $filter('currency')(registration.calculatedMinimumDeposit, '$') + ' to register for this event.');
      }

      if(Number($scope.currentPayment.amount) > $scope.currentRegistration.remainingBalance) {
        $scope.currentPayment.errors.push('You are paying more than the total due of ' + $filter('currency')(registration.remainingBalance, '$') + ' to register for this event.');
      }
      if (Number($scope.currentPayment.amount) === 0 || !$scope.acceptedPaymentMethods()) {
        setRegistrationAsCompleted();
        return;
      }

      if (!_.isEmpty($scope.currentPayment.errors)) {
        modalMessage.error({
          'title': 'Please correct the following errors:',
          'message': $scope.currentPayment.errors
        });
        $scope.submittingRegistration = false;
        return;
      }

      if($scope.currentPayment.paymentType === 'CHECK' || $scope.currentPayment.paymentType === 'PAY_ON_SITE'){
        if(!$scope.currentRegistration.completed){
          setRegistrationAsCompleted();
        }else{
          $scope.submittingRegistration = false;
        }
        return;
      }

      var currentPayment = angular.copy($scope.currentPayment);
      currentPayment.readyToProcess = true;
      currentPayment.registrationId =  registration.id;
      delete currentPayment.errors;
      if(currentPayment.paymentType === 'CREDIT_CARD'){
        $http.get('payments/ccp-client-encryption-key').success(function(ccpClientEncryptionKey) {
          ccp.initialize(ccpClientEncryptionKey);
          currentPayment.creditCard.lastFourDigits = ccp.getAbbreviatedNumber(currentPayment.creditCard.number);
          currentPayment.creditCard.number = ccp.encrypt(currentPayment.creditCard.number);
          currentPayment.creditCard.cvvNumber = ccp.encrypt(currentPayment.creditCard.cvvNumber);
          postPayment(currentPayment);
        }).error(function() {
          modalMessage.error('An error occurred while requesting the ccp encryption key. Please try your payment again.');
        });
      }else{
        postPayment(currentPayment);
      }
    };

    var postPayment = function(currentPayment){
      $http.post('payments/', currentPayment).success(function () {
        delete $scope.currentPayment;
        if(!$scope.currentRegistration.completed) {
          setRegistrationAsCompleted();
        } else {
          $route.reload();
        }
      }).error(function () {
        $scope.submittingRegistration = false;
        modalMessage.error({
          'message': 'Your payment was declined, please verify your details or use a different payment method.',
          'forceAction': true
        });
      });
    };

    var setRegistrationAsCompleted = function() {
      registration.completed = true;

      RegistrationCache.update('registrations/' + registration.id, registration, function () {
        RegistrationCache.emptyCache();
        $route.reload();
      }, function (data) {
        $scope.currentRegistration.completed = false;
        $scope.submittingRegistration = false;
        modalMessage.error('An error occurred while submitting your registration. ' + (data.data.errorMessage ? data.data.errorMessage : ''));
      });
    };

    $scope.editRegistrant = function (id) {
        $location.path('/' + ($rootScope.registerMode || 'register') + '/' + conference.id + '/page/' + conference.registrationPages[0].id).search('reg', id);
    };

    $scope.removeRegistrant = function (id) {
      modalMessage.confirm({
        'title': 'Delete registrant?',
        'question': 'Are you sure you want to delete this registrant?'
      }).then(function(){
        $http({
          method: 'DELETE',
          url: 'registrants/' + id
        }).success(function () {
          $route.reload();
        }).error(function(){
          modalMessage.error({
            'message': 'An error occurred while removing registrant.'
          });
        });
      });
    };

    $scope.getRegistrantType = function(id){
      return _.find(conference.registrantTypes, { 'id': id });
    };

    $scope.isBlockInvalid = function(rId, bId){
      return _.contains($scope.regValidate[rId], bId);
    };

    $scope.allRegistrantsValid = function(){
      var returnVal = true;
      angular.forEach(registration.registrants, function (r) {
        if (angular.isUndefined($scope.regValidate[r.id])) {
          returnVal = false;
        }else{
          if($scope.regValidate[r.id].length){
            returnVal = false;
          }
        }
      });
      return returnVal;
    };

    $scope.blockVisibleForRegistrant = function(block, registrant){
      return validateRegistrant.blockVisible(block, registrant);
    };

    $scope.acceptedPaymentMethods = function(){
      var regTypesInRegistration = [];
      angular.forEach(_.uniq(_.pluck(registration.registrants, 'registrantTypeId')), function(registrantTypeId) {
        regTypesInRegistration.push($scope.getRegistrantType(registrantTypeId));
      });

      var paymentMethods = {
        acceptCreditCards: _.some(regTypesInRegistration, 'acceptCreditCards'),
        acceptChecks:_.some(regTypesInRegistration, 'acceptChecks'),
        acceptTransfers: _.some(regTypesInRegistration, 'acceptTransfers'),
        acceptScholarships: _.some(regTypesInRegistration, 'acceptScholarships'),
        acceptPayOnSite: _.some(regTypesInRegistration, 'acceptPayOnSite') && !registration.completed
      };
      return (!_.some(paymentMethods) ? false : paymentMethods);
    };

    $scope.registrantDeletable = function(r){
      if(registration.completed && !conference.allowEditRegistrationAfterComplete){
        return false;
      }
      var groupRegistrants = 0, noGroupRegistrants = 0;
      angular.forEach(registration.registrants, function(r){
        var regType = _.find(conference.registrantTypes, { 'id': r.registrantTypeId });
        if(regType.allowGroupRegistrations){
          groupRegistrants++;
        }else{
          noGroupRegistrants++;
        }
      });

      var regType = _.find(conference.registrantTypes, { 'id': r.registrantTypeId });
      if(regType.allowGroupRegistrations && groupRegistrants === 1 && noGroupRegistrants){
        return false;
      }
      return true;
    };

    $scope.validatePromo = function(inputCode){
      $scope.addingPromoCode = true;
      $http.post('registrations/' + registration.id + '/promotions', {code: inputCode}).success(function () {
        $route.reload();
      }).error(function (data, status) {
        $scope.addingPromoCode = false;
        modalMessage.error({
          'message': status === 404 ? 'The promo code you have entered is invalid or does not apply to your registration.' : data,
          'title': 'Invalid Code',
          'forceAction': true
        });
      });
    };

    $scope.deletePromotion = function (promoId) {
      modalMessage.confirm({
        'title': 'Delete Promotion',
        'question': 'Are you sure you want to delete this promotion?'
      }).then(function(){
        var regCopy = angular.copy($scope.currentRegistration);
        _.remove(regCopy.promotions, {id: promoId});
        $http.put('registrations/' + registration.id, regCopy).success(function () {
          $route.reload();
        }).error(function () {
          modalMessage.error('An error occurred while deleting promotion.');
        });
      });
    };
  });
<|endoftext|>


<commit_before>/* eslint-disable react/forbid-prop-types */
/* eslint-disable import/prefer-default-export */

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';
import { View, ScrollView, TouchableOpacity } from 'react-native';

import Modal from 'react-native-modalbox';

import { formatExpiryDate } from '../utilities';

import { PageInfo, CloseIcon } from '.';

import { tableStrings } from '../localization/index';
import { DARKER_GREY, SUSSOL_ORANGE } from '../globalStyles';

export const ItemDetails = ({ isOpen, item, onClose, ...modalProps }) => {
  if (!item) return null;

  const headers = {
    batch: 'Batch',
    expiryDate: 'Expiry',
    numberOfPacks: 'Quantity',
    category: 'Category',
    department: 'Department',
    usage: 'Monthly Usage',
  };

  const formatters = {
    expiryDate: expiryDate => formatExpiryDate(expiryDate),
  };

  const getRow = (title, info) => ({ info, title });

  const getBatchColumn = field =>
    item.batchesWithStock.map(itemBatch => {
      const title = headers[field];

      const data = itemBatch[field];
      const info = (formatters[field] && formatters[field](data)) || data || 'N/A';

      return getRow(title, info);
    });

  const getBatchInfo = () => {
    const batchNameColumn = getBatchColumn('batch');
    const expiryDateColumn = getBatchColumn('expiryDate');
    const quantityColumn = getBatchColumn('numberOfPacks');

    return [batchNameColumn, expiryDateColumn, quantityColumn];
  };

  const getItemInfo = () => {
    const { categoryName, departmentName, monthlyUsage } = item;

    const categoryRow = { title: `${tableStrings.category}:`, info: categoryName || 'N/A' };
    const departmentRow = { title: `${tableStrings.department}:`, info: departmentName || 'N/A' };
    const usageRow = { title: `${tableStrings.monthly_usage_s}:`, info: Math.round(monthlyUsage) };

    return [[categoryRow, departmentRow, usageRow]];
  };

  const { modalContainer, scrollView, scrollViewContentContainer } = localStyles;
  return (
    <Modal style={modalContainer} isOpen={isOpen} {...modalProps}>
      <View style={{ flexDirection: 'row', justifyContent: 'flex-end', marginRight: 10 }}>
        <TouchableOpacity onPress={onClose}>
          <CloseIcon />
        </TouchableOpacity>
      </View>

      <ScrollView
        indicatorStyle="white"
        contentContainerStyle={scrollViewContentContainer}
        style={scrollView}
      >
        <PageInfo titleColor={SUSSOL_ORANGE} infoColor="white" columns={getItemInfo()} />
        <PageInfo titleColor={SUSSOL_ORANGE} infoColor="white" columns={getBatchInfo()} />
      </ScrollView>
    </Modal>
  );
};

const localStyles = {
  scrollView: {
    height: 170,
    marginBottom: 15,
    marginRight: 10,
    marginLeft: 10,
  },
  modalContainer: { height: 200, backgroundColor: DARKER_GREY },
  scrollViewContentContainer: { paddingLeft: 50 },
  headerRow: { flexDirection: 'row', justifyContent: 'flex-end', marginRight: 10 },
};

ItemDetails.defaultProps = {
  item: null,
  swipeToClose: false,
  backdropPressToClose: false,
  position: 'bottom',
  backdrop: false,
};

ItemDetails.propTypes = {
  item: PropTypes.object,
  onClose: PropTypes.func.isRequired,
  isOpen: PropTypes.func.isRequired,
  swipeToClose: PropTypes.bool,
  backdropPressToClose: PropTypes.bool,
  position: PropTypes.string,
  backdrop: PropTypes.bool,
};
<commit_msg>Add corrected ItemDetails prop types<commit_after>/* eslint-disable react/forbid-prop-types */
/* eslint-disable import/prefer-default-export */

/**
 * mSupply Mobile
 * Sustainable Solutions (NZ) Ltd. 2019
 */

import React from 'react';
import PropTypes from 'prop-types';
import { View, ScrollView, TouchableOpacity } from 'react-native';

import Modal from 'react-native-modalbox';

import { formatExpiryDate } from '../utilities';

import { PageInfo, CloseIcon } from '.';

import { tableStrings } from '../localization/index';
import { DARKER_GREY, SUSSOL_ORANGE } from '../globalStyles';

export const ItemDetails = ({ isOpen, item, onClose, ...modalProps }) => {
  if (!item) return null;

  const headers = {
    batch: 'Batch',
    expiryDate: 'Expiry',
    numberOfPacks: 'Quantity',
    category: 'Category',
    department: 'Department',
    usage: 'Monthly Usage',
  };

  const formatters = {
    expiryDate: expiryDate => formatExpiryDate(expiryDate),
  };

  const getRow = (title, info) => ({ info, title });

  const getBatchColumn = field =>
    item.batchesWithStock.map(itemBatch => {
      const title = headers[field];

      const data = itemBatch[field];
      const info = (formatters[field] && formatters[field](data)) || data || 'N/A';

      return getRow(title, info);
    });

  const getBatchInfo = () => {
    const batchNameColumn = getBatchColumn('batch');
    const expiryDateColumn = getBatchColumn('expiryDate');
    const quantityColumn = getBatchColumn('numberOfPacks');

    return [batchNameColumn, expiryDateColumn, quantityColumn];
  };

  const getItemInfo = () => {
    const { categoryName, departmentName, monthlyUsage } = item;

    const categoryRow = { title: `${tableStrings.category}:`, info: categoryName || 'N/A' };
    const departmentRow = { title: `${tableStrings.department}:`, info: departmentName || 'N/A' };
    const usageRow = { title: `${tableStrings.monthly_usage_s}:`, info: Math.round(monthlyUsage) };

    return [[categoryRow, departmentRow, usageRow]];
  };

  const { modalContainer, scrollView, scrollViewContentContainer } = localStyles;
  return (
    <Modal style={modalContainer} isOpen={isOpen} {...modalProps}>
      <View style={{ flexDirection: 'row', justifyContent: 'flex-end', marginRight: 10 }}>
        <TouchableOpacity onPress={onClose}>
          <CloseIcon />
        </TouchableOpacity>
      </View>

      <ScrollView
        indicatorStyle="white"
        contentContainerStyle={scrollViewContentContainer}
        style={scrollView}
      >
        <PageInfo titleColor={SUSSOL_ORANGE} infoColor="white" columns={getItemInfo()} />
        <PageInfo titleColor={SUSSOL_ORANGE} infoColor="white" columns={getBatchInfo()} />
      </ScrollView>
    </Modal>
  );
};

const localStyles = {
  scrollView: {
    height: 170,
    marginBottom: 15,
    marginRight: 10,
    marginLeft: 10,
  },
  modalContainer: { height: 200, backgroundColor: DARKER_GREY },
  scrollViewContentContainer: { paddingLeft: 50 },
  headerRow: { flexDirection: 'row', justifyContent: 'flex-end', marginRight: 10 },
};

ItemDetails.defaultProps = {
  item: null,
  swipeToClose: false,
  backdropPressToClose: false,
  position: 'bottom',
  backdrop: false,
};

ItemDetails.propTypes = {
  item: PropTypes.object,
  onClose: PropTypes.func.isRequired,
  isOpen: PropTypes.bool.isRequired,
  swipeToClose: PropTypes.bool,
  backdropPressToClose: PropTypes.bool,
  position: PropTypes.string,
  backdrop: PropTypes.bool,
};
<|endoftext|>


<commit_before>$(function(){

    var editor_has_been_updated = false;

    $('code').addClass('prettyprint');
    prettyPrint();

    CodeMirror.defineMode("markdown-math", function() {
        var markdown_mode = CodeMirror.getMode({}, "markdown");
        var latex_mode = CodeMirror.getMode({}, "mathjax");

        var block_latex = {
          open: "$$",
          close: "$$",
          mode: latex_mode,
          delimStyle: "mathjax-delimit",
          innerStyle: "mathjax-block",
          escapeDelimiters: true
        };

        var inner_latex = {
          open: "$",
          close: "$",
          mode: latex_mode,
          delimStyle: "mathjax-delimit",
          innerStyle: "mathjax-inline",
          escapeDelimiters: true
        };

        return CodeMirror.multiplexingMode(
          markdown_mode,
          block_latex,
          inner_latex
          );
    });

    /* from https://github.com/marijnh/CodeMirror/issues/988#issuecomment-14921785 */
    function betterTab(cm) {
      if (cm.somethingSelected()) {
        cm.indentSelection("add");
      } else {
        cm.replaceSelection(cm.getOption("indentWithTabs")? "\t":
          Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");
      }
    }

    var textarea;
    if (textarea = document.getElementById("id_content")) {
        var codeMirror = CodeMirror.fromTextArea(textarea, {
            mode: CodeMirror.getMode({}, "markdown-math"),
            theme: "wikipendium",
            indentUnit: 4,
            extraKeys: {
                Tab: betterTab
            },
            lineWrapping: true
        })

        codeMirror.on("change", function(){
            editor_has_been_updated = true;
        });

        !function(){
            var line_number = window.location.hash && +window.location.hash.substring(1);
            if(line_number){
                /* scroll to bottom first, so that the line we want will appear in the top of the window */
                codeMirror.doc.setCursor(1e1000);
                codeMirror.doc.setCursor(line_number - 1);
            }
        }();

        $('form').submit(function(){
            // Allow form to submit without being bugged about unsaved changes
            editor_has_been_updated = false;
        });
    }

    $(".select_chosen").chosen({
        width:'100%',
        no_results_text: "Language already in use, or doesn't exist."
    });

    function create_article() {
        var redirect_url = $('#create-article-slug').val() + "/edit/";
        window.location.href = redirect_url;
        return false;
    }
    $('#create-button').click( create_article );
    $('#create-article-slug').on('keypress', function(e) {
        if(e.keyCode==13){
            create_article();
        }
    });

    window.onbeforeunload = function(e) {
        if (editor_has_been_updated) {
            return "You have unsaved changes!";
        }
    };

    $('#help-tabs a').click(function (e) {
        e.preventDefault();
        $(this).tab('show');
        $('.tab-container .tab').removeClass('active');
        var tabId = $(this).attr('href');
        $(tabId).addClass('active');
    });

    $('.section1 h1').click(function(e) {
        $(this).parent('.section1').toggleClass('open');
    });

    $('#toggle-toc').on('click', function(e) {
        $('body').toggleClass('toc-hidden');
    });

    $('[data-source-line-number]').each(function(i, el){
        var a = $('Edit');
        var url = (window.location.pathname + '/edit/#').replace('//', '/');
        a.attr('href', url + $(el).attr('data-source-line-number'));
        $(el).prepend(a);

        var semaphor = 0;
        function show(){ semaphor++, a.css('opacity', 1); }
        function hide(){ setTimeout(function(){--semaphor || a.css('opacity', 0);}, 300); }
        $(a).hover(show, hide);
        $(el).hover(show, hide);
    });

    (function() {
        this.html("Last updated: " + moment(this.text()).fromNow() + ".")
    }).apply($('.last-updated'));
});

// Stay in web-app on iOS
(function(a,b,c){if(c in b&&b[c]){var d,e=a.location,f=/^(a|html)$/i;a.addEventListener("click",function(a){d=a.target;while(!f.test(d.nodeName))d=d.parentNode;"href"in d&&(d.href.indexOf("http")||~d.href.indexOf(e.host))&&(a.preventDefault(),e.href=d.href)},!1)}})(document,window.navigator,"standalone");

<commit_msg>Fix scrolling problems in safari<commit_after>$(function(){

    var editor_has_been_updated = false;

    $('code').addClass('prettyprint');
    prettyPrint();

    CodeMirror.defineMode("markdown-math", function() {
        var markdown_mode = CodeMirror.getMode({}, "markdown");
        var latex_mode = CodeMirror.getMode({}, "mathjax");

        var block_latex = {
          open: "$$",
          close: "$$",
          mode: latex_mode,
          delimStyle: "mathjax-delimit",
          innerStyle: "mathjax-block",
          escapeDelimiters: true
        };

        var inner_latex = {
          open: "$",
          close: "$",
          mode: latex_mode,
          delimStyle: "mathjax-delimit",
          innerStyle: "mathjax-inline",
          escapeDelimiters: true
        };

        return CodeMirror.multiplexingMode(
          markdown_mode,
          block_latex,
          inner_latex
          );
    });

    /* from https://github.com/marijnh/CodeMirror/issues/988#issuecomment-14921785 */
    function betterTab(cm) {
      if (cm.somethingSelected()) {
        cm.indentSelection("add");
      } else {
        cm.replaceSelection(cm.getOption("indentWithTabs")? "\t":
          Array(cm.getOption("indentUnit") + 1).join(" "), "end", "+input");
      }
    }

    var textarea;
    if (textarea = document.getElementById("id_content")) {
        var codeMirror = CodeMirror.fromTextArea(textarea, {
            mode: CodeMirror.getMode({}, "markdown-math"),
            theme: "wikipendium",
            indentUnit: 4,
            extraKeys: {
                Tab: betterTab
            },
            lineWrapping: true
        })

        codeMirror.on("change", function(){
            editor_has_been_updated = true;
        });

        !function(){
            var line_number = window.location.hash && +window.location.hash.substring(1);
            if(line_number){
                /* scroll to bottom first, so that the line we want will appear in the top of the window */
                codeMirror.doc.setCursor(1e1000);
                codeMirror.doc.setCursor(line_number - 1);
            }
        }();

        $('form').submit(function(){
            // Allow form to submit without being bugged about unsaved changes
            editor_has_been_updated = false;
        });
    }

    $(".select_chosen").chosen({
        width:'100%',
        no_results_text: "Language already in use, or doesn't exist."
    });

    function create_article() {
        var redirect_url = $('#create-article-slug').val() + "/edit/";
        window.location.href = redirect_url;
        return false;
    }
    $('#create-button').click( create_article );
    $('#create-article-slug').on('keypress', function(e) {
        if(e.keyCode==13){
            create_article();
        }
    });

    window.onbeforeunload = function(e) {
        if (editor_has_been_updated) {
            return "You have unsaved changes!";
        }
    };

    $('#help-tabs a').click(function (e) {
        e.preventDefault();
        $(this).tab('show');
        $('.tab-container .tab').removeClass('active');
        var tabId = $(this).attr('href');
        $(tabId).addClass('active');
    });

    $(window).on('hashchange', function(e) {
        // Impressively, this ensures scrolling doesn't break (as much) in Safari
        $('.toc').toggleClass('safari-reload-scroll-hack');
    });

    $('.section1 h1').click(function(e) {
        $(this).parent('.section1').toggleClass('open');
    });

    $('#toggle-toc').on('click', function(e) {
        $('body').toggleClass('toc-hidden');
    });

    $('[data-source-line-number]').each(function(i, el){
        var a = $('Edit');
        var url = (window.location.pathname + '/edit/#').replace('//', '/');
        a.attr('href', url + $(el).attr('data-source-line-number'));
        $(el).prepend(a);

        var semaphor = 0;
        function show(){ semaphor++, a.css('opacity', 1); }
        function hide(){ setTimeout(function(){--semaphor || a.css('opacity', 0);}, 300); }
        $(a).hover(show, hide);
        $(el).hover(show, hide);
    });

    (function() {
        this.html("Last updated: " + moment(this.text()).fromNow() + ".")
    }).apply($('.last-updated'));
});

// Stay in web-app on iOS
(function(a,b,c){if(c in b&&b[c]){var d,e=a.location,f=/^(a|html)$/i;a.addEventListener("click",function(a){d=a.target;while(!f.test(d.nodeName))d=d.parentNode;"href"in d&&(d.href.indexOf("http")||~d.href.indexOf(e.host))&&(a.preventDefault(),e.href=d.href)},!1)}})(document,window.navigator,"standalone");

<|endoftext|>


<commit_before>(function($, undefined) {

    /**
     * Plugin to force OpenSeadragon to use the legacy mouse pointer event model
     */

    $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];

    if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
        // Older Firefox
        $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" );
    }

    $.MouseTracker.subscribeEvents.push( "mousedown", "mouseup", "mousemove" );
    if ( 'onmouseenter' in window ) {
        $.MouseTracker.subscribeEvents.push( "mouseenter", "mouseleave" );
        $.MouseTracker.haveMouseEnter = true;
    } else {
        $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout" );
        $.MouseTracker.haveMouseEnter = false;
    }
    if ( 'ontouchstart' in window ) {
        // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505)
        $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );
        if ( 'ontouchenter' in window ) {
            $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" );
            $.MouseTracker.haveTouchEnter = true;
        } else {
            $.MouseTracker.haveTouchEnter = false;
        }
    } else {
        $.MouseTracker.haveTouchEnter = false;
    }
    if ( 'ongesturestart' in window ) {
        // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html)
        //   Subscribe to these to prevent default gesture handling
        $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" );
    }
    $.MouseTracker.mousePointerId = "legacy-mouse";
    $.MouseTracker.maxTouchPoints = 10;

}(OpenSeadragon));
<commit_msg>Removed mousenter/mouseleave DOM Event Handling from Unit Tests<commit_after>(function($, undefined) {

    /**
     * Plugin to force OpenSeadragon to use the legacy mouse pointer event model
     */

    $.MouseTracker.subscribeEvents = [ "click", "dblclick", "keypress", "focus", "blur", $.MouseTracker.wheelEventName ];

    if( $.MouseTracker.wheelEventName == "DOMMouseScroll" ) {
        // Older Firefox
        $.MouseTracker.subscribeEvents.push( "MozMousePixelScroll" );
    }

    $.MouseTracker.subscribeEvents.push( "mouseover", "mouseout", "mousedown", "mouseup", "mousemove" );
    $.MouseTracker.haveMouseEnter = false;
    if ( 'ontouchstart' in window ) {
        // iOS, Android, and other W3c Touch Event implementations (see http://www.w3.org/TR/2011/WD-touch-events-20110505)
        $.MouseTracker.subscribeEvents.push( "touchstart", "touchend", "touchmove", "touchcancel" );
        if ( 'ontouchenter' in window ) {
            $.MouseTracker.subscribeEvents.push( "touchenter", "touchleave" );
            $.MouseTracker.haveTouchEnter = true;
        } else {
            $.MouseTracker.haveTouchEnter = false;
        }
    } else {
        $.MouseTracker.haveTouchEnter = false;
    }
    if ( 'ongesturestart' in window ) {
        // iOS (see https://developer.apple.com/library/safari/documentation/UserExperience/Reference/GestureEventClassReference/GestureEvent/GestureEvent.html)
        //   Subscribe to these to prevent default gesture handling
        $.MouseTracker.subscribeEvents.push( "gesturestart", "gesturechange" );
    }
    $.MouseTracker.mousePointerId = "legacy-mouse";
    $.MouseTracker.maxTouchPoints = 10;

}(OpenSeadragon));
<|endoftext|>


<commit_before>var expect = require('chai').expect;
var Checker = require('../../lib/checker');

var AssertHelpers = {

    /**
     * Assert that the input fails with a certain number of errors and when fixed matches output with no errors.
     *
     * @param {Object} options
     * @param {String} options.name the name of the test that will be put in the describe block
     * @param {Object} options.rules the object that will be passed to checker.configure
     * @param {String} options.input the input string to check
     * @param {String} options.output the output string that input should match after being fixed
     * @param {Number} [options.errors='1'] the expected number of errors when checking input
     */
    reportAndFix: function(options) {
        if (!options.name) {
            options.name = options.input;
        }

        expect(options).to.be.a('object');
        expect(options.name).to.be.a('string');
        expect(options.rules).to.be.a('object');
        expect(options.input).to.be.a('string');
        expect(options.output).to.be.a('string');

        if (options.errors !== undefined) {
            expect(options.errors).to.be.a('number');
        }

        options.errors = options.errors === undefined ? 1 : options.errors;

        function check() {
            var checker;

            beforeEach(function() {
                checker = new Checker();
                checker.registerDefaultRules();
                checker.configure(options.rules);
            });

            it('report', function() {
                expect(checker.checkString(options.input)).to.have.error.count.equal(options.errors);
            });

            it('fix', function() {
                var result = checker.fixString(options.input);
                expect(result.errors).to.have.no.errors();
                expect(result.output).to.equal(options.output);
            });
        }

        if (options.only) {
            describe.only(options.name, check);

        } else if (options.skip) {
            describe.skip(options.name, check);

        } else {
            describe(options.name, check);
        }
    }
};

module.exports = AssertHelpers;
<commit_msg>Misc: show codes for whitespaces specs reports<commit_after>var expect = require('chai').expect;
var Checker = require('../../lib/checker');

var AssertHelpers = {

    /**
     * Assert that the input fails with a certain number of errors and when fixed matches output with no errors.
     *
     * @param {Object} options
     * @param {String} options.name the name of the test that will be put in the describe block
     * @param {Object} options.rules the object that will be passed to checker.configure
     * @param {String} options.input the input string to check
     * @param {String} options.output the output string that input should match after being fixed
     * @param {Number} [options.errors='1'] the expected number of errors when checking input
     */
    reportAndFix: function(options) {
        if (!options.name) {
            options.name = options.input;
        }

        expect(options).to.be.a('object');
        expect(options.name).to.be.a('string');
        expect(options.rules).to.be.a('object');
        expect(options.input).to.be.a('string');
        expect(options.output).to.be.a('string');

        if (options.errors !== undefined) {
            expect(options.errors).to.be.a('number');
        }

        options.errors = options.errors === undefined ? 1 : options.errors;

        function check() {
            var checker;

            beforeEach(function() {
                checker = new Checker();
                checker.registerDefaultRules();
                checker.configure(options.rules);
            });

            it('report', function() {
                expect(checker.checkString(options.input)).to.have.error.count.equal(options.errors);
            });

            it('fix', function() {
                var result = checker.fixString(options.input);
                expect(result.errors).to.have.no.errors();
                expect(highlightws(result.output)).to.equal(highlightws(options.output));
            });
        }

        if (options.only) {
            describe.only(options.name, check);

        } else if (options.skip) {
            describe.skip(options.name, check);

        } else {
            describe(options.name, check);
        }

        function highlightws(str) {
            return str.replace(/\t/g, '→')
                .replace(/\r/g, '␍')
                .replace(/\n/g, '␊')
                .replace(/\s/g, '·');
        }
    }
};

module.exports = AssertHelpers;
<|endoftext|>


<commit_before>angular.module(PKG.name + '.services')
  .service('myNamespaceMediator', function myNameSpaceMediator($q) {
    this.currentNamespace = null;
    this.namespaceList = [];
    this.defferedList = [];
    this.deferredNsList = [];
    this.subscribers = [];

    this.setCurrentNamespace = function(ns) {
      this.currentNamespace = ns;
      angular.forEach(this.defferedList, function(deferred) {
        deferred.resolve(this.currentNamespace);
      }.bind(this));
      this.defferedList = [];
    };

    this.setNamespaceList = function (nsList) {
      this.namespaceList = nsList;
      angular.forEach(this.deferredNsList, function(deferred) {
        deferred.resolve(this.namespaceList);
      });
      this.deferredNsList = [];
      this.setCurrentNamespace(nsList[0]);
    };

    this.getCurrentNamespace = function() {
      var deferred = $q.defer();
      if (this.currentNamespace) {
        deferred.resolve(this.currentNamespace);
      } else {
        this.defferedList.push(deferred);
      }
      return deferred.promise;
    };

    this.getNamespaceList = function() {
      var deferred = $q.defer();
      if (this.namespaceList.length > 0) {
        deferred.resolve(this.namespaceList);
      } else {
        this.deferredNsList.push(deferred);
      }
      return deferred.promise;
    };

  });
<commit_msg>Modifies myNamespaceMediator to use single promise<commit_after>angular.module(PKG.name + '.services')
  .service('myNamespaceMediator', function myNameSpaceMediator($q) {
    this.currentNamespace = null;
    this.namespaceList = [];
    var currentNsdeferred = $q.defer(),
        nsListdeferred = $q.defer();;

    this.setCurrentNamespace = function(ns) {
      this.currentNamespace = ns;
      currentNsdeferred.resolve(this.currentNamespace);
    };

    this.setNamespaceList = function (nsList) {
      this.namespaceList = nsList;
      this.setCurrentNamespace(nsList[0]);
      nsListdeferred.resolve(this.namespaceList);
    };

    this.getCurrentNamespace = function() {
      if (this.currentNamespace) {
        currentNsdeferred.resolve(this.currentNamespace);
      }
      return currentNsdeferred.promise;
    };

    this.getNamespaceList = function() {
      if (this.namespaceList.length > 0) {
        nsListdeferred.resolve(this.namespaceList);
      }
      return nsListdeferred.promise;
    };

  });
<|endoftext|>


<commit_before>/* eslint-disable no-unused-vars */
import React from "react";
/* eslint-enable no-unused-vars */
import { routerShape } from "react-router";
import mixin from "reactjs-mixin";

import JobFormModalContainer from "#PLUGINS/jobs/src/js/JobFormModalContainer";

import Page from "#SRC/js/components/Page";
import TabsMixin from "#SRC/js/mixins/TabsMixin";
import Job from "#SRC/js/structs/Job";
import JobConfiguration from "./JobConfiguration";
import { DIALOGS } from "./JobDetailPageContainer";
import JobRunHistoryTable from "./JobRunHistoryTable";

class JobDetailPage extends mixin(TabsMixin) {
  constructor() {
    super(...arguments);

    this.tabs_tabs = {
      runHistory: "Run History",
      configuration: "Configuration"
    };

    this.state = {
      currentTab: Object.keys(this.tabs_tabs).shift()
    };
  }

  getNavigationTabs() {
    return
{this.tabs_getUnroutedTabs()}
;
  }

  getDestroyConfirmDialog() {
    return null;
  }

  renderConfigurationTabView(job) {
    return <JobConfiguration job={job} />;
  }

  renderRunHistoryTabView(job) {
    return <JobRunHistoryTable job={job} />;
  }

  getActions() {
    return [
      {
        label: "Edit",
        onItemSelect: this.props.handleEditButtonClick
      }
    ];
  }

  getTabs() {
    const activeTab = this.state.currentTab;

    return [
      {
        label: "Run History",
        callback: () => {
          this.setState({ currentTab: "runHistory" });
        },
        isActive: activeTab === "runHistory"
      },
      {
        label: "Configuration",
        callback: () => {
          this.setState({ currentTab: "configuration" });
        },
        isActive: activeTab === "configuration"
      }
    ];
  }

  render() {
    if (this.props.params.taskID) {
      return this.props.children;
    }

    const { job } = this.props;

    return (
      <Page>
        <Page.Header
          actions={this.getActions()}
          breadcrumbs={null}
          tabs={this.getTabs()}
        />
        {this.tabs_getTabView(job)}
        <JobFormModalContainer
          isEdit={true}
          job={new Job(JSON.parse(job.json))}
          open={this.props.jobActionDialog === DIALOGS.EDIT}
          onClose={this.props.closeDialog}
        />
        {this.getDestroyConfirmDialog()}
      </Page>
    );
  }
}

JobDetailPage.contextTypes = {
  router: routerShape
};

module.exports = JobDetailPage;
<commit_msg>chore(JobDetailPage): add proptypes<commit_after>/* eslint-disable no-unused-vars */
import React from "react";
/* eslint-enable no-unused-vars */
import { routerShape } from "react-router";
import PropTypes from "prop-types";

import mixin from "reactjs-mixin";

import JobFormModalContainer from "#PLUGINS/jobs/src/js/JobFormModalContainer";

import Page from "#SRC/js/components/Page";
import TabsMixin from "#SRC/js/mixins/TabsMixin";
import Job from "#SRC/js/structs/Job";
import JobConfiguration from "./JobConfiguration";
import { DIALOGS } from "./JobDetailPageContainer";
import JobRunHistoryTable from "./JobRunHistoryTable";

class JobDetailPage extends mixin(TabsMixin) {
  constructor() {
    super(...arguments);

    this.tabs_tabs = {
      runHistory: "Run History",
      configuration: "Configuration"
    };

    this.state = {
      currentTab: Object.keys(this.tabs_tabs).shift()
    };
  }

  getNavigationTabs() {
    return
{this.tabs_getUnroutedTabs()}
;
  }

  getDestroyConfirmDialog() {
    return null;
  }

  renderConfigurationTabView(job) {
    return <JobConfiguration job={job} />;
  }

  renderRunHistoryTabView(job) {
    return <JobRunHistoryTable job={job} />;
  }

  getActions() {
    return [
      {
        label: "Edit",
        onItemSelect: this.props.handleEditButtonClick
      }
    ];
  }

  getTabs() {
    const activeTab = this.state.currentTab;

    return [
      {
        label: "Run History",
        callback: () => {
          this.setState({ currentTab: "runHistory" });
        },
        isActive: activeTab === "runHistory"
      },
      {
        label: "Configuration",
        callback: () => {
          this.setState({ currentTab: "configuration" });
        },
        isActive: activeTab === "configuration"
      }
    ];
  }

  render() {
    if (this.props.params.taskID) {
      return this.props.children;
    }

    const { job } = this.props;

    return (
      <Page>
        <Page.Header
          actions={this.getActions()}
          breadcrumbs={null}
          tabs={this.getTabs()}
        />
        {this.tabs_getTabView(job)}
        <JobFormModalContainer
          isEdit={true}
          job={new Job(JSON.parse(job.json))}
          open={this.props.jobActionDialog === DIALOGS.EDIT}
          onClose={this.props.closeDialog}
        />
        {this.getDestroyConfirmDialog()}
      </Page>
    );
  }
}

JobDetailPage.contextTypes = {
  router: routerShape
};

JobDetailPage.propTypes = {
  children: PropTypes.any,
  closeDialog: PropTypes.func,
  job: PropTypes.shape({
    json: PropTypes.string.isRequired
  }),
  jobActionDialog: PropTypes.any
};

module.exports = JobDetailPage;
<|endoftext|>


<commit_before>import {assert} from 'chai'
import {unitFormatter} from '../../lib/unitFormatter'

describe('unitFormatter', () => {
  it('returns an object with format and axisFormat functions', () => {
    const formatters = unitFormatter()
    assert.property(formatters, 'format')
    assert.typeOf(formatters.format, 'function')
    assert.property(formatters, 'axisFormat')
    assert.typeOf(formatters.axisFormat, 'function')
  })

  it('formats money with toLocaleString()', () => {
    const formatters = unitFormatter('kroner')

    const expected = `${parseFloat(1999).toLocaleString()} kr`
    assert.equal(formatters.format(1999), expected)
    assert.equal(formatters.axisFormat(1999), expected)
  })

  it('formats per mil with ‰ character and 2 decimals', () => {
    const formatters = unitFormatter('promille')
    assert.equal(formatters.format(1.9), '1.90 ‰')
    assert.equal(formatters.format(19), '19.00 ‰')
    assert.equal(formatters.format(0.7512312), '0.75 ‰')

    assert.equal(formatters.axisFormat(1.9), '1.90 ‰')
    assert.equal(formatters.axisFormat(19), '19.00 ‰')
    assert.equal(formatters.axisFormat(0.7512312), '0.75 ‰')
  })

  it('formats per cent using 2 decimals and adding % character', () => {
    const formatters = unitFormatter('prosent')
    assert.equal(formatters.format(190), '190.00 %')
    assert.equal(formatters.format(29), '29.00 %')
    assert.equal(formatters.format(29.29), '29.29 %')
    assert.equal(formatters.format(0.9), '0.90 %')
  })

  it('formats per cent on axis with no decimals', () => {
    const formatters = unitFormatter('prosent')
    assert.equal(formatters.axisFormat(19), '19 %')
    assert.equal(formatters.axisFormat(0.29), '0 %')
  })

  it('formats unknown units with toLocaleString()', () => {
    const formatters = unitFormatter('something')
    assert.equal(formatters.format(1.9), `${parseFloat(1.9).toLocaleString()}`)
    assert.equal(formatters.format(19), `${parseFloat(19).toLocaleString()}`)
    assert.equal(formatters.format(1.292929), `${parseFloat(1.292929).toLocaleString()}`)
    assert.equal(formatters.format(292929), `${parseFloat(292929).toLocaleString()}`)

    assert.equal(formatters.axisFormat(1.9), `${parseFloat(1.9).toLocaleString()}`)
    assert.equal(formatters.axisFormat(19), `${parseFloat(19).toLocaleString()}`)
    assert.equal(formatters.axisFormat(1.292929), `${parseFloat(1.292929).toLocaleString()}`)
    assert.equal(formatters.axisFormat(292929), `${parseFloat(292929).toLocaleString()}`)
  })
})
<commit_msg>Add new tests for unitFormatter<commit_after>import {assert} from 'chai'
import {unitFormatter} from '../../lib/unitFormatter'

describe('unitFormatter', () => {
  it('returns an object with format and axisFormat functions', () => {
    const formatters = unitFormatter()
    assert.property(formatters, 'format')
    assert.typeOf(formatters.format, 'function')
    assert.property(formatters, 'axisFormat')
    assert.typeOf(formatters.axisFormat, 'function')
  })

  it('formats money with toLocaleString()', () => {
    const formatters = unitFormatter('kroner')

    const expected = `${parseFloat(1999).toLocaleString()} kr`
    assert.equal(formatters.format(1999), expected)
    assert.equal(formatters.axisFormat(1999), expected)
  })

  it('shortens billions to mrd', () => {
    const formatters = unitFormatter('kroner')
    assert.equal(formatters.format(9223716271), '9.2 mrd kr')
  })

  it('shortens millions to mill', () => {
    const formatters = unitFormatter('kroner')
    assert.equal(formatters.format(9283716), '9.3 mill kr')
  })

  it('formats per mil with ‰ character and 2 decimals', () => {
    const formatters = unitFormatter('promille')
    assert.equal(formatters.format(1.9), '1.9 ‰')
    assert.equal(formatters.format(19), '19.0 ‰')
    // Rounds
    assert.equal(formatters.format(0.7552312), '0.8 ‰')

    assert.equal(formatters.axisFormat(1.9), '1.9 ‰')
    assert.equal(formatters.axisFormat(19), '19.0 ‰')
    // Rounds
    assert.equal(formatters.axisFormat(0.7212312), '0.7 ‰')
  })

  it('formats per cent using 2 decimals and adding % character', () => {
    const formatters = unitFormatter('prosent')
    assert.equal(formatters.format(190), '190.0 %')
    assert.equal(formatters.format(29), '29.0 %')
    assert.equal(formatters.format(29.29), '29.3 %')
    assert.equal(formatters.format(0.9), '0.9 %')
  })

  it('formats per cent on axis with no decimals', () => {
    const formatters = unitFormatter('prosent')
    assert.equal(formatters.axisFormat(19), '19 %')
    assert.equal(formatters.axisFormat(0.29), '0 %')
  })

  it('formats unknown units with toLocaleString()', () => {
    const formatters = unitFormatter('something')
    assert.equal(formatters.format(1.9), `${parseFloat(1.9).toLocaleString()}`)
    assert.equal(formatters.format(19), `${parseFloat(19).toLocaleString()}`)
    assert.equal(formatters.format(1.292929), `${parseFloat(1.292929).toLocaleString()}`)
    assert.equal(formatters.format(292929), `${parseFloat(292929).toLocaleString()}`)

    assert.equal(formatters.axisFormat(1.9), `${parseFloat(1.9).toLocaleString()}`)
    assert.equal(formatters.axisFormat(19), `${parseFloat(19).toLocaleString()}`)
    assert.equal(formatters.axisFormat(1.292929), `${parseFloat(1.292929).toLocaleString()}`)
    assert.equal(formatters.axisFormat(292929), `${parseFloat(292929).toLocaleString()}`)
  })
})
<|endoftext|>


<commit_before>function Deck() {
  this.deque = [];
  this.direction = false;
}

Deck.prototype.push = function(card) {
  this.deque.push(card);
}

Deck.prototype.pop = function() {
  return this.deque.pop();
}

Deck.prototype.unshift = function(card) {
  this.deque.unshift(card);
}

Deck.prototype.shift = function() {
  return this.deque.shift();
}

Deck.prototype.reverse = function() {
  this.deque.reverse();
  this.direction = !this.direction;
}

Object.defineProperty(Deck.prototype, 'length', {
  get: function() {
    return this.deque.length;
  }
});

Deck.prototype.get = function(index) {
  return this.deque[index];
}

Deck.prototype.set = function(index, value) {
  this.deque[index] = value;
}

Deck.prototype.forEach = function(callback, thisArg) {
  this.deque.forEach(callback, thisArg);
}

function randomInt(min, max) {
  return Math.random() * (max-min) + min | 0;
}

// Fisher-Yates shuffle
Deck.prototype.shuffle = function() {
  for(var i = 0; i < this.deque.length; ++i) {
    var j = randomInt(i, this.deque.length);
    var swap = this.deque[j];
    this.deque[j] = this.deque[i];
    this.deque[i] = swap;
  }
}

Deck.prototype.applyIndex = function(index) {
  var newDeque = [];
  index.forEach(function(value, i) {
    newDeque[i] = this.deque[value];
  }, this);
  this.deque = newDeque;
}

Deck.createShuffleIndex = function(length) {
  var index = [];
  for(var i = 0; i < length; ++i) {
    var j = randomInt(0, i);
    if(j != i) index[i] = index[j];
    index[j] = i;
  }
  return index;
}

if(typeof module != 'undefined') {
  module.exports = Deck;
}
<commit_msg>Implement Deck slicing<commit_after>function Deck() {
  this.deque = [];
  this.direction = false;
}

Deck.prototype.push = function(card) {
  this.deque.push(card);
}

Deck.prototype.pop = function() {
  return this.deque.pop();
}

Deck.prototype.unshift = function(card) {
  this.deque.unshift(card);
}

Deck.prototype.shift = function() {
  return this.deque.shift();
}

Deck.prototype.reverse = function() {
  this.deque.reverse();
  this.direction = !this.direction;
}

Object.defineProperty(Deck.prototype, 'length', {
  get: function() {
    return this.deque.length;
  }
});

Deck.prototype.get = function(index) {
  return this.deque[index];
}

Deck.prototype.set = function(index, value) {
  this.deque[index] = value;
}

Deck.prototype.forEach = function(callback, thisArg) {
  this.deque.forEach(callback, thisArg);
}

function randomInt(min, max) {
  return Math.random() * (max-min) + min | 0;
}

// Fisher-Yates shuffle
Deck.prototype.shuffle = function() {
  for(var i = 0; i < this.deque.length; ++i) {
    var j = randomInt(i, this.deque.length);
    var swap = this.deque[j];
    this.deque[j] = this.deque[i];
    this.deque[i] = swap;
  }
}

Deck.prototype.slice = function(quantity, deck) {
  if(deck == null) deck = new Deck();
  for(var i = 0; i < quantity; ++i) deck.push(this.pop());
  return deck;
}

Deck.prototype.applyIndex = function(index) {
  var newDeque = [];
  index.forEach(function(value, i) {
    newDeque[i] = this.deque[value];
  }, this);
  this.deque = newDeque;
}

Deck.createShuffleIndex = function(length) {
  var index = [];
  for(var i = 0; i < length; ++i) {
    var j = randomInt(0, i);
    if(j != i) index[i] = index[j];
    index[j] = i;
  }
  return index;
}

if(typeof module != 'undefined') {
  module.exports = Deck;
}
<|endoftext|>


<commit_before>import angular from 'angular';
import _ from 'underscore';

angular.module('dimApp')
  .directive('dimItemDiscuss', ItemDiscuss);

function ItemDiscuss() {
  return {
    controller: ItemDiscussCtrl,
    controllerAs: 'vm',
    bindToController: true,
    scope: {},
    templateUrl: require('./dimItemDiscuss.directive.template.html')
  };
}

function ItemDiscussCtrl($scope, $rootScope, toaster, dimItemDiscussService, dimItemService, dimFeatureFlags, $translate) {
  var vm = this;
  vm.featureFlags = dimFeatureFlags;
  vm.show = dimItemDiscussService.dialogOpen;

  $scope.$on('dim-store-item-discuss', function(event, args) {
    console.log("Received discuss event.");
    vm.show = true;
    dimItemDiscussService.dialogOpen = true;
  });
}<commit_msg>Debug stub removed.<commit_after>import angular from 'angular';
import _ from 'underscore';

angular.module('dimApp')
  .directive('dimItemDiscuss', ItemDiscuss);

function ItemDiscuss() {
  return {
    controller: ItemDiscussCtrl,
    controllerAs: 'vm',
    bindToController: true,
    scope: {},
    templateUrl: require('./dimItemDiscuss.directive.template.html')
  };
}

function ItemDiscussCtrl($scope, $rootScope, toaster, dimItemDiscussService, dimItemService, dimFeatureFlags) {
  var vm = this;
  vm.featureFlags = dimFeatureFlags;
  vm.show = dimItemDiscussService.dialogOpen;

  $scope.$on('dim-store-item-discuss', function(event, args) {
    vm.show = true;
    dimItemDiscussService.dialogOpen = true;
  });
}<|endoftext|>