From 0fa67c69fd56735705598e56d1f5e1b2ef8e1646 Mon Sep 17 00:00:00 2001 From: Ahmed Ejaz Date: Sun, 27 Jul 2025 06:03:14 +0500 Subject: [PATCH] Remove bulk product update functionality Removes the bulk product update feature and its associated components: - Removes Angular-based bulk product editing controller and views - Deletes bulk product API endpoints and related controller actions - Removes product cloning and variant deletion functionality - Removes associated JavaScript tests and specs This appears to be part of a larger effort to modernize/simplify the product management interface, removing legacy Angular-based bulk editing in favor of a different approach. --- .../admin/bulk_product_update.js.coffee | 390 ------ .../resources/product_resource.js.coffee | 6 - .../admin/services/bulk_products.js.coffee | 76 -- app/controllers/api/v0/products_controller.rb | 27 - .../spree/admin/products_controller.rb | 27 - .../spree/admin/variants_controller.rb | 17 - .../spree/admin/products/index.html.haml | 14 - .../admin/products/index/_actions.html.haml | 11 - .../admin/products/index/_data.html.haml | 5 - .../admin/products/index/_filters.html.haml | 33 - .../admin/products/index/_header.html.haml | 10 - .../products/index/_indicators.html.haml | 14 - .../admin/products/index/_products.html.haml | 14 - .../products/index/_products_head.html.haml | 41 - .../index/_products_product.html.haml | 33 - .../index/_products_variant.html.haml | 39 - .../products/index/_save_button_row.html.haml | 3 - config/routes/api.rb | 1 - config/routes/spree.rb | 2 +- .../admin/bulk_product_update_spec.js.coffee | 1080 ----------------- .../services/bulk_products_spec.js.coffee | 188 --- 21 files changed, 1 insertion(+), 2030 deletions(-) delete mode 100644 app/assets/javascripts/admin/bulk_product_update.js.coffee delete mode 100644 app/assets/javascripts/admin/resources/resources/product_resource.js.coffee delete mode 100644 app/assets/javascripts/admin/services/bulk_products.js.coffee delete mode 100644 app/views/spree/admin/products/index.html.haml delete mode 100644 app/views/spree/admin/products/index/_actions.html.haml delete mode 100644 app/views/spree/admin/products/index/_data.html.haml delete mode 100644 app/views/spree/admin/products/index/_filters.html.haml delete mode 100644 app/views/spree/admin/products/index/_header.html.haml delete mode 100644 app/views/spree/admin/products/index/_indicators.html.haml delete mode 100644 app/views/spree/admin/products/index/_products.html.haml delete mode 100644 app/views/spree/admin/products/index/_products_head.html.haml delete mode 100644 app/views/spree/admin/products/index/_products_product.html.haml delete mode 100644 app/views/spree/admin/products/index/_products_variant.html.haml delete mode 100644 app/views/spree/admin/products/index/_save_button_row.html.haml delete mode 100644 spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee delete mode 100644 spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee deleted file mode 100644 index ab62e3f321..0000000000 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ /dev/null @@ -1,390 +0,0 @@ -angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $filter, $http, $window, $location, BulkProducts, DisplayProperties, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, Columns, tax_categories, RequestMonitor, SortOptions, ErrorsParser, ProductFiltersUrl) -> - $scope.StatusMessage = StatusMessage - - $scope.columns = Columns.columns - - $scope.variant_unit_options = VariantUnitManager.variantUnitOptions() - - $scope.RequestMonitor = RequestMonitor - $scope.pagination = BulkProducts.pagination - $scope.per_page_options = [ - {id: 15, name: t('js.admin.orders.index.per_page', results: 15)}, - {id: 50, name: t('js.admin.orders.index.per_page', results: 50)}, - {id: 100, name: t('js.admin.orders.index.per_page', results: 100)} - ] - - $scope.q = { - producerFilter: "" - categoryFilter: "" - importDateFilter: "" - query: "" - sorting: "" - } - - $scope.sorting = "name asc" - $scope.producers = producers - $scope.taxons = Taxons.all - $scope.tax_categories = tax_categories - $scope.page = 1 - $scope.per_page = 15 - $scope.products = BulkProducts.products - $scope.DisplayProperties = DisplayProperties - - $scope.sortOptions = SortOptions - - $scope.initialise = -> - $scope.q = ProductFiltersUrl.loadFromUrl($location.search()) - $scope.fetchProducts() - - $scope.$watchCollection '[q.query, q.producerFilter, q.categoryFilter, q.importDateFilter, per_page]', -> - $scope.page = 1 # Reset page when changing filters for new search - - $scope.changePage = (newPage) -> - $scope.page = newPage - $scope.fetchProducts() - - $scope.fetchProducts = -> - removeClearedValues() - params = { - 'q[name_cont]': $scope.q.query, - 'q[variants_supplier_id_eq]': $scope.q.producerFilter, - 'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter, - 'q[s]': $scope.sorting, - import_date: $scope.q.importDateFilter, - page: $scope.page, - per_page: $scope.per_page - } - RequestMonitor.load(BulkProducts.fetch(params).$promise).then -> - # update url with the filters used - $location.search(ProductFiltersUrl.generate($scope.q)) - $scope.resetProducts() - - removeClearedValues = -> - delete $scope.q.producerFilter if $scope.q.producerFilter == "0" - delete $scope.q.categoryFilter if $scope.q.categoryFilter == "0" - delete $scope.q.importDateFilter if $scope.q.importDateFilter == "0" - - $timeout -> - if $scope.showLatestImport - $scope.q.importDateFilter = $scope.importDates[1].id - - $scope.resetProducts = -> - DirtyProducts.clear() - StatusMessage.clear() - - $scope.updateOnHand = (product) -> - on_demand_variants = [] - if product.variants - on_demand_variants = (variant for id, variant of product.variants when variant.on_demand) - - unless product.on_demand || on_demand_variants.length > 0 - product.on_hand = $scope.onHand(product) - - - $scope.onHand = (product) -> - onHand = 0 - if product.hasOwnProperty("variants") and product.variants instanceof Object - for id, variant of product.variants - onHand = onHand + parseInt(if variant.on_hand > 0 then variant.on_hand else 0) - else - onHand = "error" - onHand - - $scope.shiftTab = (tab) -> - $scope.visibleTab.visible = false unless $scope.visibleTab == tab || $scope.visibleTab == undefined - tab.visible = !tab.visible - $scope.visibleTab = tab - - $scope.resetSelectFilters = -> - $scope.q.query = "" - $scope.q.producerFilter = "0" - $scope.q.categoryFilter = "0" - $scope.q.importDateFilter = "0" - $scope.fetchProducts() - - $scope.$watch 'sortOptions', (sort) -> - return unless sort && sort.predicate != "" - - $scope.sorting = sort.getSortingExpr(defaultDirection: "asc") - $scope.fetchProducts() - , true - - confirm_unsaved_changes = () -> - (DirtyProducts.count() > 0 and confirm(t("unsaved_changes_confirmation"))) or (DirtyProducts.count() == 0) - - editProductUrl = (product, variant) -> - "/admin/products/" + product.id + ((if variant then "/variants/" + variant.id else "")) + "/edit" - - $scope.editWarn = (product, variant) -> - if confirm_unsaved_changes() - $window.location.href = ProductFiltersUrl.buildUrl(editProductUrl(product, variant), $scope.q) - - $scope.toggleShowAllVariants = -> - showVariants = !DisplayProperties.showVariants 0 - $scope.products.forEach (product) -> - DisplayProperties.setShowVariants product.id, showVariants - DisplayProperties.setShowVariants 0, showVariants - - $scope.addVariant = (product) -> - # Set new variant category to same as last product variant category to keep compactibility with deleted variant callback to set new variant category - newVariantId = $scope.nextVariantId(); - newVariantCategoryId = product.variants[product.variants.length - 1]?.category_id - product.variants.push - id: newVariantId - unit_value: null - unit_description: null - on_demand: false - display_as: null - display_name: null - on_hand: null - price: null - tax_category_id: null - category_id: newVariantCategoryId - DisplayProperties.setShowVariants product.id, true - DirtyProducts.addVariantProperty(product.id, newVariantId, 'category_id', newVariantCategoryId) - - - $scope.nextVariantId = -> - $scope.variantIdCounter = 0 unless $scope.variantIdCounter? - $scope.variantIdCounter -= 1 - $scope.variantIdCounter - - $scope.deleteProduct = (product) -> - if confirm(t('are_you_sure')) - $http( - method: "DELETE" - url: "/api/v0/products/" + product.id - ).then (response) -> - $scope.products.splice $scope.products.indexOf(product), 1 - DirtyProducts.deleteProduct product.id - $scope.displayDirtyProducts() - - - $scope.deleteVariant = (product, variant) -> - if product.variants.length > 1 - if !$scope.variantSaved(variant) - $scope.removeVariant(product, variant) - else - if confirm(t("are_you_sure")) - $http( - method: "DELETE" - url: "/api/v0/products/" + product.id + "/variants/" + variant.id - ).then (response) -> - $scope.removeVariant(product, variant) - else - alert(t("delete_product_variant")) - - $scope.removeVariant = (product, variant) -> - product.variants.splice product.variants.indexOf(variant), 1 - DirtyProducts.deleteVariant product.id, variant.id - $scope.displayDirtyProducts() - - - $scope.cloneProduct = (product) -> - BulkProducts.cloneProduct product - - $scope.hasVariants = (product) -> - product.variants.length > 0 - - - $scope.hasUnit = (variant) -> - variant.variant_unit_with_scale? - - $scope.variantSaved = (variant) -> - variant.hasOwnProperty('id') && variant.id > 0 - - - $scope.hasOnDemandVariants = (product) -> - (variant for id, variant of product.variants when variant.on_demand).length > 0 - - - $scope.submitProducts = -> - # Pack pack $scope.products, so they will match the list returned from the server, - # then pack $scope.dirtyProducts, ensuring that the correct product info is sent to the server. - $scope.packProduct product for id, product of $scope.products - $scope.packProduct product for id, product of DirtyProducts.all() - - productsToSubmit = filterSubmitProducts(DirtyProducts.all()) - if productsToSubmit.length > 0 - $scope.updateProducts productsToSubmit # Don't submit an empty list - else - StatusMessage.display 'alert', t("products_change") - - - $scope.updateProducts = (productsToSubmit) -> - $scope.displayUpdating() - $http( - method: "POST" - url: "/admin/products/bulk_update" - data: - products: productsToSubmit - filters: - 'q[name_cont]': $scope.q.query - 'q[variants_supplier_id_eq]': $scope.q.producerFilter - 'q[variants_primary_taxon_id_eq]': $scope.q.categoryFilter - 'q[s]': $scope.sorting - import_date: $scope.q.importDateFilter - page: $scope.page - per_page: $scope.per_page - ).then((response) -> - DirtyProducts.clear() - BulkProducts.updateVariantLists(response.data.products || []) - $timeout -> $scope.displaySuccess() - ).catch (response) -> - if response.status == 400 && response.data.errors? - errorsString = ErrorsParser.toString(response.data.errors, response.status) - $scope.displayFailure t("products_update_error") + "\n" + errorsString - else - $scope.displayFailure t("products_update_error_data") + response.status - - $scope.cancel = (destination) -> - $window.location = destination - - $scope.packProduct = (product) -> - if product.variants - for id, variant of product.variants - $scope.packVariant variant - - - $scope.packVariant = (variant) -> - if variant.variant_unit_with_scale - match = variant.variant_unit_with_scale.match(/^([^_]+)_([\d\.]+)$/) - if match - variant.variant_unit = match[1] - variant.variant_unit_scale = parseFloat(match[2]) - else - variant.variant_unit = variant.variant_unit_with_scale - variant.variant_unit_scale = null - - if variant.hasOwnProperty("unit_value_with_description") - match = variant.unit_value_with_description.match(/^([\d\.\,]+(?= |$)|)( |)(.*)$/) - if match - variant.unit_value = parseFloat(match[1].replace(",", ".")) - variant.unit_value = null if isNaN(variant.unit_value) - if variant.unit_value && variant.variant_unit_scale - variant.unit_value = parseFloat(window.bigDecimal.multiply(variant.unit_value, variant.variant_unit_scale, 2)) - variant.unit_description = match[3] - - $scope.incrementLimit = -> - if $scope.limit < $scope.products.length - $scope.limit = $scope.limit + 5 - - - $scope.displayUpdating = -> - StatusMessage.display 'progress', t("saving") - - - $scope.displaySuccess = -> - StatusMessage.display 'success',t("products_changes_saved") - $scope.bulk_product_form.$setPristine() - - - $scope.displayFailure = (failMessage) -> - StatusMessage.display 'failure', t("products_update_error_msg") + " #{failMessage}" - - - $scope.displayDirtyProducts = -> - count = DirtyProducts.count() - switch count - when 0 then StatusMessage.clear() - when 1 then StatusMessage.display 'notice', t("one_product_unsaved") - else StatusMessage.display 'notice', t("products_unsaved", n: count) - - -filterSubmitProducts = (productsToFilter) -> - filteredProducts = [] - if productsToFilter instanceof Object - angular.forEach productsToFilter, (product) -> - if product.hasOwnProperty("id") - filteredProduct = {id: product.id} - filteredVariants = [] - hasUpdatableProperty = false - - if product.hasOwnProperty("variants") - angular.forEach product.variants, (variant) -> - result = filterSubmitVariant variant - filteredVariant = result.filteredVariant - variantHasUpdatableProperty = result.hasUpdatableProperty - filteredVariants.push filteredVariant if variantHasUpdatableProperty - - if product.hasOwnProperty("name") - filteredProduct.name = product.name - hasUpdatableProperty = true - if product.hasOwnProperty("price") - filteredProduct.price = product.price - hasUpdatableProperty = true - if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present - filteredProduct.on_hand = product.on_hand - hasUpdatableProperty = true - if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present - filteredProduct.on_demand = product.on_demand - hasUpdatableProperty = true - if product.hasOwnProperty("inherits_properties") - filteredProduct.inherits_properties = product.inherits_properties - hasUpdatableProperty = true - if filteredVariants.length > 0 # Note that the name of the property changes to enable mass assignment of variants attributes with rails - filteredProduct.variants_attributes = filteredVariants - hasUpdatableProperty = true - filteredProducts.push filteredProduct if hasUpdatableProperty - - filteredProducts - - -filterSubmitVariant = (variant) -> - hasUpdatableProperty = false - filteredVariant = {} - if not variant.deleted_at? and variant.hasOwnProperty("id") - filteredVariant.id = variant.id unless variant.id <= 0 - if variant.hasOwnProperty("sku") - filteredVariant.sku = variant.sku - hasUpdatableProperty = true - if variant.hasOwnProperty("on_hand") - filteredVariant.on_hand = variant.on_hand - hasUpdatableProperty = true - if variant.hasOwnProperty("on_demand") - filteredVariant.on_demand = variant.on_demand - hasUpdatableProperty = true - if variant.hasOwnProperty("price") - filteredVariant.price = variant.price - hasUpdatableProperty = true - if variant.hasOwnProperty("unit_value") - filteredVariant.unit_value = variant.unit_value - hasUpdatableProperty = true - if variant.hasOwnProperty("unit_description") - filteredVariant.unit_description = variant.unit_description - hasUpdatableProperty = true - if variant.hasOwnProperty("display_name") - filteredVariant.display_name = variant.display_name - hasUpdatableProperty = true - if variant.hasOwnProperty("tax_category_id") - filteredVariant.tax_category_id = variant.tax_category_id - hasUpdatableProperty = true - if variant.hasOwnProperty("category_id") - filteredVariant.primary_taxon_id = variant.category_id - hasUpdatableProperty = true - if variant.hasOwnProperty("display_as") - filteredVariant.display_as = variant.display_as - hasUpdatableProperty = true - if variant.hasOwnProperty("producer_id") - filteredVariant.supplier_id = variant.producer_id - hasUpdatableProperty = true - if variant.hasOwnProperty("variant_unit_with_scale") - filteredVariant.variant_unit = variant.variant_unit - filteredVariant.variant_unit_scale = variant.variant_unit_scale - hasUpdatableProperty = true - if variant.hasOwnProperty("variant_unit_name") - filteredVariant.variant_unit_name = variant.variant_unit_name - hasUpdatableProperty = true - - {filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty} - - -toObjectWithIDKeys = (array) -> - object = {} - - for i of array - if array[i] instanceof Object and array[i].hasOwnProperty("id") - object[array[i].id] = angular.copy(array[i]) - object[array[i].id].variants = toObjectWithIDKeys(array[i].variants) if array[i].hasOwnProperty("variants") and array[i].variants instanceof Array - - object diff --git a/app/assets/javascripts/admin/resources/resources/product_resource.js.coffee b/app/assets/javascripts/admin/resources/resources/product_resource.js.coffee deleted file mode 100644 index 7d842d8eed..0000000000 --- a/app/assets/javascripts/admin/resources/resources/product_resource.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -angular.module("admin.resources").factory 'ProductResource', ($resource) -> - $resource('/admin/product/:id/:action.json', {}, { - 'index': - url: '/api/v0/products/bulk_products.json' - method: 'GET' - }) diff --git a/app/assets/javascripts/admin/services/bulk_products.js.coffee b/app/assets/javascripts/admin/services/bulk_products.js.coffee deleted file mode 100644 index a2823bf997..0000000000 --- a/app/assets/javascripts/admin/services/bulk_products.js.coffee +++ /dev/null @@ -1,76 +0,0 @@ -angular.module("ofn.admin").factory "BulkProducts", (ProductResource, dataFetcher, $http) -> - new class BulkProducts - products: [] - pagination: {} - - fetch: (params) -> - ProductResource.index params, (data) => - @products.length = 0 - @addProducts data.products - angular.extend(@pagination, data.pagination) - - cloneProduct: (product) -> - $http.post("/api/v0/products/" + product.id + "/clone").then (response) => - dataFetcher("/api/v0/products/" + response.data.id + "?template=bulk_show").then (newProduct) => - @unpackProduct newProduct - @insertProductAfter(product, newProduct) - - updateVariantLists: (serverProducts) -> - for server_product in serverProducts - product = @findProductInList(server_product.id, @products) - product.variants = server_product.variants - @loadVariantUnitValues product.variants - - find: (id) -> - @findProductInList id, @products - - findProductInList: (id, product_list) -> - products = (product for product in product_list when product.id == id) - if products.length == 0 then null else products[0] - - addProducts: (products) -> - for product in products - @unpackProduct product - @products.push product - - insertProductAfter: (product, newProduct) -> - index = @products.indexOf(product) - @products.splice(index + 1, 0, newProduct) - - unpackProduct: (product) -> - @loadVariantUnit product - - loadVariantUnit: (product) -> - @loadVariantUnitValues product.variants if product.variants - - loadVariantUnitValues: (variants) -> - for variant in variants - @loadVariantUnitValue variant - - loadVariantUnitValue: (variant) -> - variant.variant_unit_with_scale = - if variant.variant_unit && variant.variant_unit_scale && variant.variant_unit != 'items' - "#{variant.variant_unit}_#{variant.variant_unit_scale}" - else if variant.variant_unit - variant.variant_unit - else - null - - unit_value = @variantUnitValue variant - unit_value = if unit_value? then unit_value else '' - variant.unit_value_with_description = "#{unit_value} #{variant.unit_description || ''}".trim() - - variantUnitValue: (variant) -> - if variant.unit_value? - if variant.variant_unit_scale - variant_unit_value = @divideAsInteger variant.unit_value, variant.variant_unit_scale - parseFloat(window.bigDecimal.round(variant_unit_value, 2)) - else - variant.unit_value - else - null - - # forces integer division to avoid javascript floating point imprecision - # using one billion as the multiplier so that it works for numbers with up to 9 decimal places - divideAsInteger: (a, b) -> - (a * 1000000000) / (b * 1000000000) diff --git a/app/controllers/api/v0/products_controller.rb b/app/controllers/api/v0/products_controller.rb index 6f95e1e64e..1ba016cc5a 100644 --- a/app/controllers/api/v0/products_controller.rb +++ b/app/controllers/api/v0/products_controller.rb @@ -38,39 +38,12 @@ module Api end end - def destroy - authorize! :delete, Spree::Product - @product = product_finder.find_product - authorize! :delete, @product - @product.destroyed_by = current_api_user - @product.destroy - head :no_content - end - - def bulk_products - @products = product_finder.bulk_products - - render_paged_products @products - end - def overridable @products = product_finder.products_for_producers render_paged_products @products, ::Api::Admin::ProductSimpleSerializer end - # POST /api/products/:product_id/clone - # - def clone - authorize! :create, Spree::Product - original_product = product_finder.find_product_to_be_cloned - authorize! :update, original_product - - @product = original_product.duplicate - - render json: @product, serializer: Api::Admin::ProductSerializer, status: :created - end - private def product_finder diff --git a/app/controllers/spree/admin/products_controller.rb b/app/controllers/spree/admin/products_controller.rb index f51ba5503e..2e036ffdb6 100644 --- a/app/controllers/spree/admin/products_controller.rb +++ b/app/controllers/spree/admin/products_controller.rb @@ -19,11 +19,6 @@ module Spree before_action :load_spree_api_key, only: [:index, :variant_overrides] before_action :strip_new_properties, only: [:create, :update] - def index - @current_user = spree_current_user - @show_latest_import = params[:latest_import] || false - end - def show session[:return_to] ||= request.referer redirect_to( action: :edit ) @@ -65,28 +60,6 @@ module Spree end end - def bulk_update - product_set = product_set_from_params - - product_set.collection.each { |p| authorize! :update, p } - - if product_set.save - redirect_to main_app.bulk_products_api_v0_products_path(bulk_index_query) - elsif product_set.errors.present? - render json: { errors: product_set.errors }, status: :bad_request - else - render body: nil, status: :internal_server_error - end - end - - def clone - @new = @product.duplicate - raise "Clone failed" unless @new.save - - flash[:success] = t('.success') - redirect_to spree.admin_products_url - end - def group_buy_options @url_filters = ::ProductFilters.new.extract(request.query_parameters) end diff --git a/app/controllers/spree/admin/variants_controller.rb b/app/controllers/spree/admin/variants_controller.rb index 7ec309ea93..83fe131045 100644 --- a/app/controllers/spree/admin/variants_controller.rb +++ b/app/controllers/spree/admin/variants_controller.rb @@ -72,25 +72,8 @@ module Spree render json: @variants, each_serializer: ::Api::Admin::VariantSerializer end - def destroy - @url_filters = ::ProductFilters.new.extract(request.query_parameters) - - @variant = Spree::Variant.find(params[:id]) - flash[:success] = delete_variant - - redirect_to spree.admin_product_variants_url(params[:product_id], @url_filters) - end - protected - def delete_variant - if VariantDeleter.new.delete(@variant) - Spree.t('notice_messages.variant_deleted') - else - Spree.t('notice_messages.variant_not_deleted') - end - end - def create_before @object.save end diff --git a/app/views/spree/admin/products/index.html.haml b/app/views/spree/admin/products/index.html.haml deleted file mode 100644 index b78c73cb4f..0000000000 --- a/app/views/spree/admin/products/index.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -= render 'spree/admin/products/index/header' -= render 'spree/admin/products/index/data' -= admin_inject_available_units - -%div{ "ng-app": 'ofn.admin', "ng-controller": 'AdminProductEditCtrl', "ng-init": 'initialise()' } - - = render 'spree/admin/products/index/filters' - %div{ 'ng-cloak' => true } - = render 'spree/admin/products/index/actions' - = render 'spree/admin/products/index/indicators' - = render 'spree/admin/products/index/products' - - %div{'ng-show' => "!RequestMonitor.loading && products.length > 0" } - = render partial: 'admin/shared/angular_pagination' diff --git a/app/views/spree/admin/products/index/_actions.html.haml b/app/views/spree/admin/products/index/_actions.html.haml deleted file mode 100644 index aea4ebeb61..0000000000 --- a/app/views/spree/admin/products/index/_actions.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -.controls.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' } - .ten.columns.alpha.index-controls - .per-page{'ng-show' => '!RequestMonitor.loading && products.length > 0'} - %input.per-page-select.ofn-select2{type: 'number', data: 'per_page_options', 'min-search' => 999, 'ng-model' => 'per_page', 'ng-change' => 'fetchProducts()'} - - %span.per-page-feedback - {{ 'spree.admin.orders.index.results_found' | t:{number: pagination.results} }} - {{ 'spree.admin.orders.index.viewing' | t:{start: ((pagination.page -1) * pagination.per_page) +1, end: ((pagination.page -1) * pagination.per_page) + products.length} }} - - .six.columns.omega - %columns-dropdown{ action: "#{controller_name}_#{action_name}" } diff --git a/app/views/spree/admin/products/index/_data.html.haml b/app/views/spree/admin/products/index/_data.html.haml deleted file mode 100644 index 13a6dc780e..0000000000 --- a/app/views/spree/admin/products/index/_data.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -= admin_inject_producers(@producers) -= admin_inject_taxons(@taxons) -= admin_inject_tax_categories(@tax_categories) -= admin_inject_spree_api_key(@spree_api_key) -= admin_inject_column_preferences diff --git a/app/views/spree/admin/products/index/_filters.html.haml b/app/views/spree/admin/products/index/_filters.html.haml deleted file mode 100644 index 57615eb09e..0000000000 --- a/app/views/spree/admin/products/index/_filters.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -%fieldset - %legend{align: 'center'}= t(:search) - - .filters.sixteen.columns.alpha.omega - .quick_search.three.columns.alpha - %label{ for: 'quick_filter' } - %br - %input.quick-search.fullwidth{ name: "quick_filter", type: 'text', placeholder: t('admin.quick_search'), "ng-keypress": "$event.keyCode === 13 && fetchProducts()", "ng-model": 'q.query' } - .one.columns   - .filter_select.three.columns - %label{ for: 'producer_filter' }= t 'producer' - %br - %select.fullwidth{ id: 'producer_filter', "ofn-select2-min-search": 5, "ng-model": 'q.producerFilter', "ng-options": 'producer.id as producer.name for producer in producers' } - .filter_select.three.columns - %label{ for: 'category_filter' }= t 'category' - %br - %select.fullwidth{ id: 'category_filter', "ofn-select2-min-search": 5, "ng-model": 'q.categoryFilter', "ng-options": 'taxon.id as taxon.name for taxon in taxons' } - .filter_select.three.columns - %label{ for: 'import_filter' }= t 'import_date' - %br - %select.fullwidth{ id: 'import_date_filter', "ofn-select2-min-search": 5, "ng-model": 'q.importDateFilter', "ng-init": "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}", "ng-options": 'date.id as date.name for date in importDates' } - - .filter_clear.three.columns.omega - %label{ for: 'clear_all_filters' } - %br - %input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" } - - .clearfix - - .actions.filter-actions - %div - %a.button.icon-search{'ng-click' => 'fetchProducts()'} - = t(:filter_results) diff --git a/app/views/spree/admin/products/index/_header.html.haml b/app/views/spree/admin/products/index/_header.html.haml deleted file mode 100644 index f3314a1a39..0000000000 --- a/app/views/spree/admin/products/index/_header.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -- content_for :page_title do - = t('.title') - -- content_for :page_actions do - %div{ :class => "toolbar" } - %ul{ :class => "actions header-action-links inline-menu" } - %li#new_product_link - = button_link_to t(:new_product), new_object_url, { :icon => 'icon-plus', :id => 'admin_new_product' } - -= render partial: 'spree/admin/shared/product_sub_menu' diff --git a/app/views/spree/admin/products/index/_indicators.html.haml b/app/views/spree/admin/products/index/_indicators.html.haml deleted file mode 100644 index 2907c04074..0000000000 --- a/app/views/spree/admin/products/index/_indicators.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -.sixteen.columns.alpha#loading{ 'ng-if' => 'RequestMonitor.loading' } - = render partial: "components/admin_spinner" - %h1 - = t('.title') - -.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length == 0' } - %h1#no_results= t('.no_products') - -.sixteen.columns.alpha{ 'ng-show' => '!RequestMonitor.loading && products.length == 0 && q.query.length != 0' } - %h1#no_results - = t('.no_results') - ' - {{q.query}} - ' diff --git a/app/views/spree/admin/products/index/_products.html.haml b/app/views/spree/admin/products/index/_products.html.haml deleted file mode 100644 index 032c783e7f..0000000000 --- a/app/views/spree/admin/products/index/_products.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0' } - %form{ name: 'bulk_product_form', autocomplete: "off" } - %save-bar{ dirty: "bulk_product_form.$dirty", persist: "false" } - %input.red{ type: "button", value: t(:save_changes), "ng-click": "submitProducts()", "ng-disabled": "!bulk_product_form.$dirty" } - %input{ type: "button", value: t(:close), 'ng-click' => "cancel('#{admin_products_path}')" } - - %table.index#listing_products.bulk - - = render 'spree/admin/products/index/products_head' - - %tbody{ 'ng-repeat' => 'product in products', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } - - = render 'spree/admin/products/index/products_product' - = render 'spree/admin/products/index/products_variant' diff --git a/app/views/spree/admin/products/index/_products_head.html.haml b/app/views/spree/admin/products/index/_products_head.html.haml deleted file mode 100644 index 9e6d7e7065..0000000000 --- a/app/views/spree/admin/products/index/_products_head.html.haml +++ /dev/null @@ -1,41 +0,0 @@ -%colgroup - %col.actions - %col.image{ "ng-show": 'columns.image.visible' } - %col.producer{ "ng-show": 'columns.producer.visible' } - %col.sku{ "ng-show": 'columns.sku.visible' } - %col.name{ "ng-show": 'columns.name.visible' } - %col.unit{ "ng-show": 'columns.unit.visible' } - %col.display_as{ "ng-show": 'columns.unit.visible' } - %col.price{ "ng-show": 'columns.price.visible' } - %col.on_hand{ "ng-show": 'columns.on_hand.visible' } - %col.on_demand{ "ng-show": 'columns.on_demand.visible' } - %col.category{ "ng-show": 'columns.category.visible' } - %col.tax_category{ "ng-show": 'columns.tax_category.visible' } - %col.inherits_properties{ "ng-show": 'columns.inherits_properties.visible' } - %col.import_date{ "ng-show": 'columns.import_date.visible' } - %col.actions - %col.actions - %col.actions - -%thead - %tr{ "ng-controller": "ColumnsCtrl" } - %th.left-actions - %a{ 'ng-click' => 'toggleShowAllVariants()', :style => 'color: red; cursor: pointer' } - = t(:expand_all) - %th.image{ 'ng-show' => 'columns.image.visible' } - %th.producer{ 'ng-show' => 'columns.producer.visible' }=t('admin.producer') - %th.sku{ 'ng-show' => 'columns.sku.visible' }=t('admin.sku') - %th.name{ 'ng-show' => 'columns.name.visible' } - = render partial: 'spree/admin/shared/sortable_header', locals: {column_name: 'name'} - %th.unit{ 'ng-show' => 'columns.unit.visible' }=t('.unit') - %th.display_as{ 'ng-show' => 'columns.unit.visible' }=t('.display_as') - %th.price{ 'ng-show' => 'columns.price.visible' }=t('admin.price') - %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' }=t('admin.on_hand') - %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' }=t('admin.on_demand?') - %th.category{ 'ng-show' => 'columns.category.visible' }=t('.category') - %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' }=t('.tax_category') - %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' }=t('.inherits_properties?') - %th.import_date{ 'ng-show' => 'columns.import_date.visible' }=t('.import_date') - %th.actions - %th.actions - %th.actions diff --git a/app/views/spree/admin/products/index/_products_product.html.haml b/app/views/spree/admin/products/index/_products_product.html.haml deleted file mode 100644 index aa10e5e29a..0000000000 --- a/app/views/spree/admin/products/index/_products_product.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -%tr.product{ :id => "p_{{product.id}}" } - %td.left-actions - %a{ 'ofn-toggle-variants' => 'true', :class => "view-variants", 'ng-show' => 'hasVariants(product)' } - %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "!hasVariants(product) && hasUnit(product)" } - %td.image{ 'ng-show' => 'columns.image.visible' } - %a{class: 'image-modal'} - %img{'ng-src' => '{{ product.thumb_url }}'} - %td.producer{ 'ng-show' => 'columns.producer.visible' } - %td.sku{ 'ng-show' => 'columns.sku.visible' } - %input{ 'ng-model' => "product.sku", :name => 'product_sku', 'ofn-track-product' => 'sku', :type => 'text' } - %td.name{ 'ng-show' => 'columns.name.visible' } - %input{ 'ng-model' => "product.name", :name => 'product_name', 'ofn-track-product' => 'name', :type => 'text' } - %td.unit{ 'ng-show' => 'columns.unit.visible' } - %td.display_as{ 'ng-show' => 'columns.unit.visible' } - %td.price{ 'ng-show' => 'columns.price.visible' } - %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' } - %td.on_hand{ 'ng-show' => 'columns.on_hand.visible' } - %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-if' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } - %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-if' => '!(hasVariants(product) || product.on_demand)', :type => 'number' } - %td.on_demand{ 'ng-show' => 'columns.on_demand.visible' } - %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' } - %td.category{ 'ng-if' => 'columns.category.visible' } - %td.tax_category{ 'ng-if' => 'columns.tax_category.visible' } - %td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } - %input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" } - %td.import_date{ 'ng-show' => 'columns.import_date.visible' } - %span {{(product.import_date | date:"MMMM dd, yyyy HH:mm") || ""}} - %td.actions - %a{ 'ng-click' => 'editWarn(product)', :class => "edit-product icon-edit no-text", 'ofn-with-tip' => t(:edit) } - %td.actions - %a{ 'ng-click' => 'cloneProduct(product)', :class => "clone-product icon-copy no-text", 'ofn-with-tip' => t(:clone) } - %td.actions - %a{ 'ng-click' => 'deleteProduct(product)', :class => "delete-product icon-trash no-text", 'ofn-with-tip' => t(:remove) } diff --git a/app/views/spree/admin/products/index/_products_variant.html.haml b/app/views/spree/admin/products/index/_products_variant.html.haml deleted file mode 100644 index 4aa5e13f78..0000000000 --- a/app/views/spree/admin/products/index/_products_variant.html.haml +++ /dev/null @@ -1,39 +0,0 @@ -%tr.variant{ :id => "v_{{variant.id}}", 'ng-repeat' => 'variant in product.variants', 'ng-show' => 'DisplayProperties.showVariants(product.id)', 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'" } - %td.left-actions - %a{ :class => "variant-item icon-caret-right", 'ng-hide' => "$last" } - %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last", 'ofn-with-tip' => t('.new_variant') } - %td{ 'ng-show' => 'columns.image.visible' } - %td{ 'ng-show' => 'columns.producer.visible' } - %select.fullwidth{ "data-controller": "tom-select", 'ng-model' => 'variant.producer_id', :name => 'producer_id', 'ofn-track-variant' => 'producer_id', 'ng-options' => 'producer.id as producer.name for producer in producers' } - %td{ 'ng-show' => 'columns.sku.visible' } - %input{ 'ng-model' => "variant.sku", :name => 'variant_sku', 'ofn-track-variant' => 'sku', :type => 'text' } - %td{ 'ng-show' => 'columns.name.visible' } - %input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" } - %td.unit_value{ 'ng-show' => 'columns.unit.visible' } - %select.no-search{ "data-controller": "tom-select", 'ng-model' => 'variant.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-variant' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' } - %input{ 'ng-model' => 'variant.unit_value_with_description', :name => 'variant_unit_value_with_description', 'ofn-track-variant' => 'unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "hasUnit(variant)" } - %input{ 'ng-model' => 'variant.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-variant' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "variant.variant_unit_with_scale == 'items'", :type => 'text' } - %td.display_as{ 'ng-show' => 'columns.unit.visible' } - %input{ 'ofn-display-as' => 'variant', 'ng-model' => 'variant.display_as', name: 'variant_display_as', 'ofn-track-variant' => 'display_as', type: 'text', placeholder: '{{ placeholder_text }}' } - %td{ 'ng-show' => 'columns.price.visible' } - %input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' } - %td{ 'ng-show' => 'columns.on_hand.visible' } - %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-if' => '!variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } - %span{ :name => 'variant_on_hand', 'ng-if' => 'variant.on_demand' } - = t(:on_demand) - %td{ 'ng-show' => 'columns.on_demand.visible' } - %input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' } - %td{ 'ng-show' => 'columns.category.visible' } - %input.fullwidth{ type: 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'variant.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-variant' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' } - %td{ 'ng-show' => 'columns.tax_category.visible' } - %select.select2{ name: 'variant_tax_category_id', "ofn-track-variant": 'tax_category_id', "ng-model": 'variant.tax_category_id', "ng-options": 'tax_category.id as tax_category.name for tax_category in tax_categories' } - %option{ value: '' }= t(:none) - %td{ 'ng-show' => 'columns.inherits_properties.visible' } - %td{ 'ng-show' => 'columns.import_date.visible' } - %span {{variant.import_date | date:"MMMM dd, yyyy HH:mm"}} - %td.actions - %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)", 'ofn-with-tip' => t(:edit) } - %td.actions - %span.icon-warning-sign{ 'ng-if' => 'variant.variant_overrides_count > 0', 'ofn-with-tip' => "{{ 'spree.admin.products.index.products_variant.variant_has_n_overrides' | t:{n: variant.variant_overrides_count} }}" } - %td.actions - %a{ 'ng-click' => 'deleteVariant(product,variant)', "ng-class" => '{disabled: product.variants.length < 2}', :class => "delete-variant icon-trash no-text", 'ofn-with-tip' => t(:remove) } diff --git a/app/views/spree/admin/products/index/_save_button_row.html.haml b/app/views/spree/admin/products/index/_save_button_row.html.haml deleted file mode 100644 index 8818bf84b3..0000000000 --- a/app/views/spree/admin/products/index/_save_button_row.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -.sixteen.columns.alpha{ 'ng-hide' => 'RequestMonitor.loading || products.length == 0', style: "margin-bottom: 10px" } - .four.columns.alpha - %input.four.columns.alpha{ :type => 'button', :value => t(:save_changes), 'ng-click' => 'submitProducts()'} diff --git a/config/routes/api.rb b/config/routes/api.rb index 30bd7cd1b1..2545554739 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -16,7 +16,6 @@ Openfoodnetwork::Application.routes.draw do namespace :v0 do resources :products do collection do - get :bulk_products get :overridable end post :clone diff --git a/config/routes/spree.rb b/config/routes/spree.rb index 512f708b14..a133ff7418 100644 --- a/config/routes/spree.rb +++ b/config/routes/spree.rb @@ -68,7 +68,7 @@ Spree::Core::Engine.routes.draw do end end - resources :variants do + resources :variants, except: :destroy do collection do post :update_positions end diff --git a/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee deleted file mode 100644 index 532f569d69..0000000000 --- a/spec/javascripts/unit/admin/bulk_product_update_spec.js.coffee +++ /dev/null @@ -1,1080 +0,0 @@ -describe "filtering products for submission to database", -> - it "accepts an object or an array and only returns an array", -> - expect(filterSubmitProducts([])).toEqual [] - expect(filterSubmitProducts({})).toEqual [] - expect(filterSubmitProducts(1: - id: 1 - name: "lala" - )).toEqual [ - id: 1 - name: "lala" - ] - expect(filterSubmitProducts([ - id: 1 - name: "lala" - ])).toEqual [ - id: 1 - name: "lala" - ] - expect(filterSubmitProducts(1)).toEqual [] - expect(filterSubmitProducts("2")).toEqual [] - expect(filterSubmitProducts(null)).toEqual [] - - it "only returns products which have an id property", -> - expect(filterSubmitProducts([ - { - id: 1 - name: "p1" - } - { - notanid: 2 - name: "p2" - } - ])).toEqual [ - id: 1 - name: "p1" - ] - - it "does not return a product object for products which have no propeties other than an id", -> - expect(filterSubmitProducts([ - { - id: 1 - someunwantedproperty: "something" - } - { - id: 2 - name: "p2" - } - ])).toEqual [ - id: 2 - name: "p2" - ] - - it "does not return an on_hand count when a product has variants", -> - testProduct = - id: 1 - on_hand: 5 - variants: [ - id: 1 - on_hand: 5 - price: 12.0 - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - id: 1 - on_hand: 5 - price: 12.0 - ] - ] - - it "returns variants as variants_attributes", -> - testProduct = - id: 1 - variants: [ - id: 1 - on_hand: 5 - price: 12.0 - unit_value: 250 - unit_description: "(bottle)" - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - id: 1 - on_hand: 5 - price: 12.0 - unit_value: 250 - unit_description: "(bottle)" - ] - ] - - it "ignores variants without an id, and those for which deleted_at is not null", -> - testProduct = - id: 1 - variants: [ - { - id: 1 - on_hand: 3 - price: 5.0 - } - { - on_hand: 1 - price: 15.0 - } - { - id: 2 - on_hand: 2 - deleted_at: new Date() - price: 20.0 - } - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - id: 1 - on_hand: 3 - price: 5.0 - ] - ] - - it "returns variants with a negative id without that id", -> - testProduct = - id: 1 - variants: [ - id: -1 - on_hand: 5 - price: 12.0 - unit_value: 250 - unit_description: "(bottle)" - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - on_hand: 5 - price: 12.0 - unit_value: 250 - unit_description: "(bottle)" - ] - ] - - it "does not return variants_attributes property if variants is an empty array", -> - testProduct = - id: 1 - price: 10 - variants: [] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - price: 10 - ] - - it "returns variant_unit_with_scale as variant_unit and variant_unit_scale", -> - testProduct = - id: 1 - variants: [ - id: 1 - variant_unit: 'weight' - variant_unit_scale: 1 - variant_unit_with_scale: 'weight_1' - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - variants_attributes: [ - id: 1 - variant_unit: 'weight' - variant_unit_scale: 1 - ] - ] - - it "returns stock properties of a product if no variant is provided", -> - testProduct = - id: 1 - name: "TestProduct" - on_hand: 0 - on_demand: false - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - name: "TestProduct" - on_hand: 0 - on_demand: false - ] - - it "only returns the properties of products which ought to be updated", -> - testProduct = - id: 1 - name: "TestProduct" - description: "" - deleted_at: null - meta_keywords: null - shipping_category_id: null - created_at: null - updated_at: null - on_hand: 0 - on_demand: false - group_buy: null - group_buy_unit_size: null - variants: [ - id: 1 - on_hand: 2 - price: 10.0 - unit_value: 250 - tax_category_id: null - unit_description: "(bottle)" - display_as: "bottle" - display_name: "nothing" - producer_id: 5 - variant_unit: 'volume' - variant_unit_scale: 1 - variant_unit_name: 'loaf' - variant_unit_with_scale: 'volume_1' - ] - - expect(filterSubmitProducts([testProduct])).toEqual [ - id: 1 - name: "TestProduct" - variants_attributes: [ - id: 1 - on_hand: 2 - price: 10.0 - unit_value: 250 - tax_category_id: null - unit_description: "(bottle)" - display_as: "bottle" - display_name: "nothing" - supplier_id: 5 - variant_unit: 'volume' - variant_unit_scale: 1 - variant_unit_name: 'loaf' - ] - ] - -describe "AdminProductEditCtrl", -> - $ctrl = $scope = $timeout = $httpBackend = BulkProducts = DirtyProducts = DisplayProperties = ProductFiltersUrl = windowStub = null - - beforeEach -> - module "ofn.admin" - module ($provide)-> - $provide.value "producers", [] - $provide.value "taxons", [] - $provide.value "tax_categories", [] - $provide.value 'SpreeApiKey', 'API_KEY' - $provide.value 'columns', [] - null - module "admin.products" - module ($provide)-> - $provide.value "availableUnits", "g,kg,T,mL,L,kL" - null - - beforeEach inject((_$controller_, _$timeout_, $rootScope, _$httpBackend_, _BulkProducts_, _DirtyProducts_, _DisplayProperties_, _ProductFiltersUrl_) -> - $scope = $rootScope.$new() - $ctrl = _$controller_ - $timeout = _$timeout_ - $httpBackend = _$httpBackend_ - BulkProducts = _BulkProducts_ - DirtyProducts = _DirtyProducts_ - DisplayProperties = _DisplayProperties_ - ProductFiltersUrl = _ProductFiltersUrl_ - - # Stub the window object so we don't get redirected when href is updated - windowStub = {navigator: {userAgent: 'foo'}, location: {href: ''}} - - $ctrl "AdminProductEditCtrl", {$scope: $scope, $timeout: $timeout, $window: windowStub} - ) - - describe "loading data upon initialisation", -> - beforeEach -> - spyOn($scope, "fetchProducts").and.returnValue "nothing" - - it "gets a list of producers and then resets products with a list of data", -> - $scope.initialise() - expect($scope.fetchProducts.calls.count()).toBe 1 - - it "gets a list of products applying filters from the url", inject ($location) -> - query = 'lala' - producerFilter = 2 - categoryFilter = 5 - sorting = 'name desc' - importDateFilter = '2020-06-08' - $location.search({query: query, producerFilter: producerFilter, categoryFilter: categoryFilter, sorting: sorting, importDateFilter: importDateFilter}) - - $scope.initialise() - - expect($scope.q.query).toBe query - expect($scope.q.categoryFilter).toBe categoryFilter - expect($scope.q.sorting).toBe sorting - expect($scope.q.importDateFilter).toBe importDateFilter - - describe "fetching products", -> - $q = null - deferred = null - - beforeEach inject((_$q_) -> - $q = _$q_ - ) - - beforeEach -> - deferred = $q.defer() - deferred.resolve() - spyOn $scope, "resetProducts" - spyOn(BulkProducts, "fetch").and.returnValue deferred.promise - - it "calls resetProducts after data has been received", -> - $scope.fetchProducts() - $scope.$digest() - expect($scope.resetProducts).toHaveBeenCalled() - - it "updates url with filter after data has been received", inject ($location, $window) -> - query = 'lala' - producerFilter = 2 - categoryFilter = 5 - sorting = 'name desc' - importDateFilter = '2020-06-08' - - $scope.q.query = query - $scope.q.producerFilter = producerFilter - $scope.q.categoryFilter = categoryFilter - $scope.q.sorting = sorting - $scope.q.importDateFilter = importDateFilter - - $scope.fetchProducts() - $scope.$digest() - - encodedSorting = $window.encodeURIComponent(sorting) - encodedDate = $window.encodeURIComponent(importDateFilter) - expect($location.url()).toBe( - "?producerFilter=#{producerFilter}&categoryFilter=#{categoryFilter}&query=#{query}&sorting=#{encodedSorting}&importDateFilter=#{encodedDate}" - ) - - describe "resetting products", -> - beforeEach -> - spyOn DirtyProducts, "clear" - $scope.products = {} - $scope.resetProducts [ - { - id: 1 - name: "P1" - } - { - id: 3 - name: "P2" - } - ] - - it "resets dirtyProducts", -> - expect(DirtyProducts.clear).toHaveBeenCalled() - - describe "sorting products", -> - it "sorts products", -> - spyOn $scope, "fetchProducts" - - $scope.sortOptions.toggle('name') - $scope.$apply() - - expect($scope.sorting).toEqual 'name desc' - expect($scope.fetchProducts).toHaveBeenCalled() - - describe "updating the product on hand count", -> - it "updates when product is not available on demand", -> - spyOn($scope, "onHand").and.returnValue 123 - product = {on_demand: false} - $scope.updateOnHand(product) - expect(product.on_hand).toEqual 123 - - it "updates when product's variants are not available on demand", -> - spyOn($scope, "onHand").and.returnValue 123 - product = {on_demand: false, variants: [{on_demand: false}]} - $scope.updateOnHand(product) - expect(product.on_hand).toEqual 123 - - it "does nothing when the product is available on demand", -> - product = {on_demand: true} - $scope.updateOnHand(product) - expect(product.on_hand).toBeUndefined() - - it "does nothing when one of the variants is available on demand", -> - product = - on_demand: false - variants: [ - {on_demand: false, on_hand: 10} - {on_demand: true, on_hand: Infinity} - ] - $scope.updateOnHand(product) - expect(product.on_hand).toBeUndefined() - - - describe "getting on_hand counts when products have variants", -> - p1 = undefined - p2 = undefined - p3 = undefined - beforeEach -> - p1 = variants: - 1: - id: 1 - on_hand: 1 - - 2: - id: 2 - on_hand: 2 - - 3: - id: 3 - on_hand: 3 - - p2 = variants: - 4: - id: 4 - not_on_hand: 1 - - 5: - id: 5 - on_hand: 2 - - 6: - id: 6 - on_hand: 3 - - p3 = - not_variants: - 7: - id: 7 - on_hand: 1 - - 8: - id: 8 - on_hand: 2 - - variants: - 9: - id: 9 - on_hand: 3 - - it "sums variant on_hand properties", -> - expect($scope.onHand(p1)).toEqual 6 - - it "ignores items in variants without an on_hand property (adds 0)", -> - expect($scope.onHand(p2)).toEqual 5 - - it "ignores on_hand properties of objects in arrays which are not named 'variants' (adds 0)", -> - expect($scope.onHand(p3)).toEqual 3 - - it "returns 'error' if not given an object with a variants property that is an object", -> - expect($scope.onHand([])).toEqual "error" - expect($scope.onHand(not_variants: [])).toEqual "error" - - - describe "determining whether a product has variants that are available on demand", -> - it "returns true when at least one variant does", -> - product = - variants: [ - {on_demand: false} - {on_demand: true} - ] - expect($scope.hasOnDemandVariants(product)).toBe(true) - - it "returns false otherwise", -> - product = - variants: [ - {on_demand: false} - {on_demand: false} - ] - expect($scope.hasOnDemandVariants(product)).toBe(false) - - - describe "determining whether a product has variants", -> - it "returns true when it does", -> - product = - variants: [{id: 1}, {id: 2}] - expect($scope.hasVariants(product)).toBe(true) - - it "returns false when it does not", -> - product = - variants: [] - expect($scope.hasVariants(product)).toBe(false) - - - describe "determining whether a product has a unit", -> - it "returns true when it does", -> - variant ={variant_unit_with_scale: 'weight_1000'} - - expect($scope.hasUnit(variant)).toBe(true) - - it "returns false when its unit is undefined", -> - variant = {} - expect($scope.hasUnit(variant)).toBe(false) - - - describe "determining whether a variant has been saved", -> - it "returns true when it has a positive id", -> - variant = {id: 1} - expect($scope.variantSaved(variant)).toBe(true) - - it "returns false when it has no id", -> - variant = {} - expect($scope.variantSaved(variant)).toBe(false) - - it "returns false when it has a negative id", -> - variant = {id: -1} - expect($scope.variantSaved(variant)).toBe(false) - - - describe "submitting products to be updated", -> - describe "packing products", -> - beforeEach -> - window.bigDecimal = jasmine.createSpyObj "bigDecimal", ["multiply"] - window.bigDecimal.multiply.and.callFake (a, b, c) -> (a * b).toFixed(c) - - it "packs each variant", -> - spyOn $scope, "packVariant" - testVariant = {id: 1} - testProduct = - id: 1 - variants: {1: testVariant} - - $scope.packProduct(testProduct) - - expect($scope.packVariant).toHaveBeenCalledWith(testVariant) - - describe "packing variants", -> - beforeEach -> - window.bigDecimal = jasmine.createSpyObj "bigDecimal", ["multiply"] - window.bigDecimal.multiply.and.callFake (a, b, c) -> (a * b).toFixed(c) - - it "extracts variant_unit_with_scale into variant_unit and variant_unit_scale", -> - testVariant = - id: 1 - variant_unit: 'weight' - variant_unit_scale: 1 - variant_unit_with_scale: 'volume_1000' - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - id: 1 - variant_unit: 'volume' - variant_unit_scale: 1000 - variant_unit_with_scale: 'volume_1000' - - it "extracts when variant_unit_with_scale is 'items'", -> - testVariant = - id: 1 - variant_unit: 'weight' - variant_unit_scale: 1 - variant_unit_with_scale: 'items' - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - id: 1 - variant_unit: 'items' - variant_unit_scale: null - variant_unit_with_scale: 'items' - - it "extracts unit_value and unit_description from unit_value_with_description", -> - testVariant = {unit_value_with_description: "250.5 (bottle)"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 250.5 - unit_description: "(bottle)" - unit_value_with_description: "250.5 (bottle)" - - it "extracts into unit_value when only a number is provided", -> - testVariant = {variant_unit_scale: 1.0, unit_value_with_description: "250.5"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 250.5 - unit_description: '' - unit_value_with_description: "250.5" - variant_unit_scale: 1.0 - - it "extracts into unit_description when only a string is provided", -> - testVariant = {unit_value_with_description: "Medium"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: null - unit_description: 'Medium' - unit_value_with_description: "Medium" - - it "extracts into unit_description when a string starting with a number is provided", -> - testVariant = {unit_value_with_description: "1kg"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: null - unit_description: '1kg' - unit_value_with_description: "1kg" - - it "sets blank values when no value provided", -> - testVariant = {unit_value_with_description: ""} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: null - unit_description: '' - unit_value_with_description: "" - - it "sets nothing when the field is undefined", -> - testVariant = {} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual({}) - - it "sets zero when the field is zero", -> - testVariant = {variant_unit_scale: 1.0, unit_value_with_description: "0"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 0 - unit_description: '' - unit_value_with_description: "0" - variant_unit_scale: 1.0 - - it "converts value from chosen unit to base unit", -> - testVariant = {variant_unit_scale: 1000, unit_value_with_description: "250.5"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 250500 - unit_description: '' - unit_value_with_description: "250.5" - variant_unit_scale: 1000 - - it "does not convert value when using a non-scaled unit", -> - testVariant = {unit_value_with_description: "12"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 12 - unit_description: '' - unit_value_with_description: "12" - - it "converts unit_value into a float when a comma separated number is provided", -> - testVariant = {variant_unit_scale: 1.0, unit_value_with_description: "250,5"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 250.5 - unit_description: '' - unit_value_with_description: "250,5" - variant_unit_scale: 1.0 - - it "rounds off the unit_value upto 2 decimal places", -> - testVariant = {variant_unit_scale: 1.0, unit_value_with_description: "1234.567"} - - $scope.packVariant(testVariant) - - expect(testVariant).toEqual - unit_value: 1234.57 - unit_description: '' - unit_value_with_description: "1234.567" - variant_unit_scale: 1.0 - - - describe "filtering products", -> - beforeEach -> - spyOn $scope, "packProduct" - spyOn(window, "filterSubmitProducts").and.returnValue [ - { - id: 1 - value: 3 - } - { - id: 2 - value: 4 - } - ] - spyOn $scope, "updateProducts" - DirtyProducts.addProductProperty 1, "propName", "something" - DirtyProducts.addProductProperty 2, "propName", "something" - $scope.products = - 1: - id: 1 - 2: - id: 2 - - $scope.submitProducts() - - it "packs all products and all dirty products", -> - expect($scope.packProduct.calls.count()).toBe 4 - - it "filters returned dirty products", -> - expect(filterSubmitProducts).toHaveBeenCalledWith - 1: - id: 1 - propName: "something" - 2: - id: 2 - propName: "something" - - - it "sends dirty and filtered objects to submitProducts()", -> - expect($scope.updateProducts).toHaveBeenCalledWith [ - { - id: 1 - value: 3 - } - { - id: 2 - value: 4 - } - ] - - - describe "updating products", -> - it "submits products to be updated with a http post request to /admin/products/bulk_update", -> - $httpBackend.expectPOST("/admin/products/bulk_update").respond "list of products" - $scope.updateProducts "list of products" - $httpBackend.flush() - - it "runs displaySuccess() when post returns success", -> - spyOn $scope, "displaySuccess" - spyOn BulkProducts, "updateVariantLists" - spyOn DirtyProducts, "clear" - - $scope.bulk_product_form = jasmine.createSpyObj('bulk_product_form', ['$setPristine']) - - $scope.products = [ - { - id: 1 - name: "P1" - } - { - id: 2 - name: "P2" - } - ] - $httpBackend.expectPOST("/admin/products/bulk_update").respond 200, [ - { - id: 1 - name: "P1" - } - { - id: 2 - name: "P2" - } - ] - $scope.updateProducts "list of dirty products" - $httpBackend.flush() - $timeout.flush() - expect($scope.displaySuccess).toHaveBeenCalled() - expect($scope.bulk_product_form.$setPristine).toHaveBeenCalled - expect(DirtyProducts.clear).toHaveBeenCalled() - expect(BulkProducts.updateVariantLists).toHaveBeenCalled() - - it "runs displayFailure() when post returns an error", -> - spyOn $scope, "displayFailure" - $scope.products = "updated list of products" - $httpBackend.expectPOST("/admin/products/bulk_update").respond 500, "updated list of products" - $scope.updateProducts "updated list of products" - $httpBackend.flush() - expect($scope.displayFailure).toHaveBeenCalled() - - describe "displaying the error information when post returns 400", -> - beforeEach -> - spyOn $scope, "displayFailure" - $scope.products = "updated list of products" - - it "displays errors in an array", -> - $httpBackend.expectPOST("/admin/products/bulk_update").respond 400, { "errors": ["an error"] } - $scope.updateProducts "updated list of products" - $httpBackend.flush() - expect($scope.displayFailure).toHaveBeenCalledWith("Saving failed with the following error(s):\nan error\n") - - it "displays errors in a hash", -> - $httpBackend.expectPOST("/admin/products/bulk_update").respond 400, { "errors": { "base": ["a basic error"] } } - $scope.updateProducts "updated list of products" - $httpBackend.flush() - expect($scope.displayFailure).toHaveBeenCalledWith("Saving failed with the following error(s):\na basic error\n") - - - describe "adding variants", -> - beforeEach -> - spyOn DisplayProperties, 'setShowVariants' - - it "adds first and subsequent variants", -> - product = {id: 123, variants: [ - { - id: 1, price: 10.0, unit_value: 1, tax_category_id: null, unit_description: '1kg', - on_demand: false, on_hand: null, display_as: null, display_name: null, category_id: 2 - } - ]} - $scope.addVariant(product) - $scope.addVariant(product) - expect(product).toEqual - id: 123 - variants: [ - {id: 1, price: 10.0, unit_value: 1, tax_category_id: null, unit_description: '1kg', on_demand: false, on_hand: null, display_as: null, display_name: null, category_id: 2} - {id: -1, price: null, unit_value: null, tax_category_id: null, unit_description: null, on_demand: false, on_hand: null, display_as: null, display_name: null, category_id: 2} - {id: -2, price: null, unit_value: null, tax_category_id: null, unit_description: null, on_demand: false, on_hand: null, display_as: null, display_name: null, category_id: 2} - ] - - it "shows the variant(s)", -> - product = {id: 123, variants: []} - $scope.addVariant(product) - expect(DisplayProperties.setShowVariants).toHaveBeenCalledWith 123, true - - - describe "deleting products", -> - it "deletes products with a http delete request to /api/products/id", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - { - id: 9 - } - { - id: 13 - } - ] - $scope.dirtyProducts = {} - $httpBackend.expectDELETE("/api/v0/products/13").respond 200, "data" - $scope.deleteProduct $scope.products[1] - expect(window.confirm).toHaveBeenCalledWith "Are you sure?" - $httpBackend.flush() - - it "removes the specified product from both $scope.products and $scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - { - id: 9 - } - { - id: 13 - } - ] - DirtyProducts.addProductProperty 9, "someProperty", "something" - DirtyProducts.addProductProperty 13, "name", "P1" - - $httpBackend.expectDELETE("/api/v0/products/13").respond 200, "data" - $scope.deleteProduct $scope.products[1] - $httpBackend.flush() - expect($scope.products).toEqual [ - id: 9 - ] - expect(DirtyProducts.all()).toEqual 9: - id: 9 - someProperty: "something" - - - - describe "deleting variants", -> - describe "when the variant is the only one left on the product", -> - it "alerts the user", -> - spyOn(window, "alert") - $scope.products = [ - {id: 1, variants: [{id: 1}]} - ] - $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] - expect(window.alert).toHaveBeenCalledWith "The last variant cannot be deleted!" - - describe "when the variant has not been saved", -> - it "removes the variant from products and dirtyProducts", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - {id: 1, variants: [{id: -1},{id: -2}]} - ] - DirtyProducts.addVariantProperty 1, -1, "something", "something" - DirtyProducts.addProductProperty 1, "something", "something" - $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] - expect($scope.products).toEqual([ - {id: 1, variants: [{id: -2}]} - ]) - expect(DirtyProducts.all()).toEqual - 1: { id: 1, something: 'something'} - - - describe "when the variant has been saved", -> - it "deletes variants with a http delete request to /api/products/(id)/variants/(variant_id)", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - { - id: 9 - variants: [{ - id: 3 - price: 12 - }, - { - id: 4 - price: 15 - } - ] - } - { - id: 13 - } - ] - $scope.dirtyProducts = {} - $httpBackend.expectDELETE("/api/v0/products/9/variants/3").respond 200, "data" - $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] - $httpBackend.flush() - - it "removes the specified variant from both the variants object and $scope.dirtyProducts (if it exists there)", -> - spyOn(window, "confirm").and.returnValue true - $scope.products = [ - { - id: 9 - variants: [ - { - id: 3 - price: 12.0 - } - { - id: 4 - price: 6.0 - } - ] - } - { - id: 13 - } - ] - DirtyProducts.addVariantProperty 9, 3, "price", 12.0 - DirtyProducts.addVariantProperty 9, 4, "price", 6.0 - DirtyProducts.addProductProperty 13, "name", "P1" - - $httpBackend.expectDELETE("/api/v0/products/9/variants/3").respond 200, "data" - $scope.deleteVariant $scope.products[0], $scope.products[0].variants[0] - $httpBackend.flush() - expect($scope.products[0].variants).toEqual [ - id: 4 - price: 6.0 - ] - expect(DirtyProducts.all()).toEqual - 9: - id: 9 - variants: - 4: - id: 4 - price: 6.0 - - 13: - id: 13 - name: "P1" - - describe "editWarn", -> - testProduct = testVariant = null - - beforeEach -> - testProduct = - id: 1 - name: "TestProduct" - description: "" - deleted_at: null - meta_keywords: null - shipping_category_id: null - created_at: null - updated_at: null - on_hand: 0 - on_demand: false - producer_id: 5 - group_buy: null - group_buy_unit_size: null - - describe 'product has variant', -> - it 'should load the edit product variant page', -> - testVariant = - id: 2 - name: "TestVariant" - - $scope.editWarn(testProduct, testVariant) - - expect(windowStub.location.href).toBe( - "/admin/products/#{testProduct.id}/variants/#{testVariant.id}/edit" - ) - - describe 'product has no variant', -> - it 'should display unsaved changes confirmation if there are any DirtyProduct', inject ($window, DirtyProducts) -> - spyOn($window, 'confirm') - spyOn(DirtyProducts, 'count').and.returnValue 2 - - $scope.editWarn(testProduct, null) - expect($window.confirm).toHaveBeenCalled() - - it 'should load the edit product page', inject -> - $scope.editWarn(testProduct, null) - - expect(windowStub.location.href).toBe( - "/admin/products/#{testProduct.id}/edit" - ) - - it 'should load edit product page including the selected filters', inject ($httpParamSerializer) -> - query = 'lala' - category = 3 - $scope.q.query = query - $scope.q.categoryFilter = category - - # use $httpParamSerializer as it will sort parameters alphabetically - expectedFilter = $httpParamSerializer({ query: query, categoryFilter: category }) - - $scope.editWarn(testProduct, null) - - expect(windowStub.location.href).toBe( - "/admin/products/#{testProduct.id}/edit?#{expectedFilter}" - ) - - describe "filtering products", -> - describe "clearing filters", -> - it "resets filter variables", -> - $scope.q.query = "lala" - $scope.q.producerFilter = "5" - $scope.q.categoryFilter = "6" - $scope.resetSelectFilters() - expect($scope.q.query).toBe "" - expect($scope.q.producerFilter).toBeUndefined - expect($scope.q.categoryFilter).toBeUndefined - - -describe "converting arrays of objects with ids to an object with ids as keys", -> - it "returns an object", -> - array = [] - expect(toObjectWithIDKeys(array)).toEqual {} - - it "adds each object in the array provided with an id to the returned object with the id as its key", -> - array = [ - { - id: 1 - } - { - id: 3 - } - ] - expect(toObjectWithIDKeys(array)).toEqual - 1: - id: 1 - - 3: - id: 3 - - - it "ignores items which are not objects and those which do not possess ids", -> - array = [ - { - id: 1 - } - "not an object" - { - notanid: 3 - } - ] - expect(toObjectWithIDKeys(array)).toEqual 1: - id: 1 - - - it "sends arrays with the key 'variants' to itself", -> - spyOn(window, "toObjectWithIDKeys").and.callThrough() - array = [ - { - id: 1 - variants: [id: 17] - } - { - id: 2 - variants: - 12: - id: 12 - } - ] - products = toObjectWithIDKeys(array) - expect(products["1"].variants).toEqual 17: - id: 17 - - expect(toObjectWithIDKeys).toHaveBeenCalledWith [id: 17] - expect(toObjectWithIDKeys).not.toHaveBeenCalledWith {12: {id: 12}} diff --git a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee deleted file mode 100644 index ea741adc35..0000000000 --- a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee +++ /dev/null @@ -1,188 +0,0 @@ -describe "BulkProducts service", -> - BulkProducts = $httpBackend = null - - beforeEach -> - module "ofn.admin" - window.bigDecimal = jasmine.createSpyObj "bigDecimal", ["round"] - window.bigDecimal.round.and.callFake (a, b) -> a.toFixed(b) - - beforeEach inject (_BulkProducts_, _$httpBackend_) -> - BulkProducts = _BulkProducts_ - $httpBackend = _$httpBackend_ - - describe "cloning products", -> - it "clones products using a http post request to /api/products/(id)/clone", -> - BulkProducts.products = [ - id: 13 - ] - $httpBackend.expectPOST("/api/v0/products/13/clone").respond 201, - id: 17 - $httpBackend.expectGET("/api/v0/products/17?template=bulk_show").respond 200, [ - id: 17 - ] - BulkProducts.cloneProduct BulkProducts.products[0] - $httpBackend.flush() - - it "adds the product", -> - originalProduct = - id: 16 - clonedProduct = - id: 17 - - spyOn(BulkProducts, "insertProductAfter") - spyOn(BulkProducts, "unpackProduct") - BulkProducts.products = [originalProduct] - $httpBackend.expectPOST("/api/v0/products/16/clone").respond 201, clonedProduct - $httpBackend.expectGET("/api/v0/products/17?template=bulk_show").respond 200, clonedProduct - BulkProducts.cloneProduct BulkProducts.products[0] - $httpBackend.flush() - expect(BulkProducts.unpackProduct).toHaveBeenCalledWith clonedProduct - BulkProducts.unpackProduct(clonedProduct) - expect(BulkProducts.insertProductAfter).toHaveBeenCalledWith originalProduct, clonedProduct - - - describe "preparing products", -> - beforeEach -> - spyOn BulkProducts, "loadVariantUnit" - - it "calls loadVariantUnit for the product", -> - product = {id: 123} - BulkProducts.unpackProduct product - expect(BulkProducts.loadVariantUnit).toHaveBeenCalled() - - - describe "loading variant unit", -> - describe "setting product variant_unit_with_scale field", -> - it "HERE 2 sets by combining variant_unit and variant_unit_scale", -> - product = - variants:[ - id: 10 - variant_unit: "volume" - variant_unit_scale: .001 - ] - BulkProducts.loadVariantUnit product - expect(product.variants[0].variant_unit_with_scale).toEqual "volume_0.001" - - it "sets to null when variant_unit is null", -> - product = - variants: [ - {variant_unit: null, variant_unit_scale: 1000} - ] - BulkProducts.loadVariantUnit product - - expect(product.variants[0].variant_unit_with_scale).toBeNull() - - it "sets to variant_unit when variant_unit_scale is null", -> - product = - variants: [ - {variant_unit: 'items', variant_unit_scale: null, variant_unit_name: 'foo'} - ] - BulkProducts.loadVariantUnit product - expect(product.variants[0].variant_unit_with_scale).toEqual "items" - - it "sets to variant_unit when variant_unit is 'items'", -> - product = - variants: [ - {variant_unit: 'items', variant_unit_scale: 1000, variant_unit_name: 'foo'} - ] - BulkProducts.loadVariantUnit product - expect(product.variants[0].variant_unit_with_scale).toEqual "items" - - it "loads data for variants (excl. master)", -> - spyOn BulkProducts, "loadVariantUnitValue" - - product = - variants: [ - {id: 2, variant_unit_scale: 1.0, unit_value: 2, unit_description: '(two)'} - ] - BulkProducts.loadVariantUnitValues product.variants - - expect(BulkProducts.loadVariantUnitValue).toHaveBeenCalledWith product.variants[0] - - describe "setting variant unit_value_with_description", -> - it "sets by combining unit_value and unit_description", -> - product = - variants: [ - {id: 1, variant_unit_scale: 1.0, unit_value: 1, unit_description: '(bottle)'} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0]).toEqual - id: 1 - variant_unit_scale: 1.0, - variant_unit_with_scale: null, - unit_value: 1 - unit_description: '(bottle)' - unit_value_with_description: '1 (bottle)' - - it "uses unit_value when description is missing", -> - product = - variants: [ - {id: 1, variant_unit_scale: 1.0, unit_value: 1} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual '1' - - it "uses unit_description when value is missing", -> - product = - variants: [ - {id: 1, variant_unit_scale: 1.0, unit_description: 'Small'} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual 'Small' - - it "converts values from base value to chosen unit", -> - product = - variants: [ - id: 1, variant_unit_scale: 1000.0, unit_value: 2500 - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual '2.5' - - it "converts values from base value to chosen unit without breaking precision", -> - product = - variants: [ - {id: 1,variant_unit_scale: 0.001, unit_value: 0.35} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual '350' - - it "displays a unit_value of zero", -> - product = - variants: [ - {id: 1, variant_unit_scale: 1.0, unit_value: 0} - ] - BulkProducts.loadVariantUnitValues product.variants - expect(product.variants[0].unit_value_with_description).toEqual '0' - - - describe "calculating the scaled unit value for a variant", -> - it "returns the scaled value when variant has a unit_value", -> - variant = {variant_unit_scale: 0.001, unit_value: 5} - expect(BulkProducts.variantUnitValue(variant)).toEqual 5000 - - it "returns the scaled value rounded off upto 2 decimal points", -> - variant = {variant_unit_scale: 28.35, unit_value: 1234.5} - expect(BulkProducts.variantUnitValue(variant)).toEqual 43.54 - - it "returns the unscaled value when the product has no scale", -> - variant = {unit_value: 5} - expect(BulkProducts.variantUnitValue(variant)).toEqual 5 - - it "returns zero when the value is zero", -> - variant = {unit_value: 0} - expect(BulkProducts.variantUnitValue(variant)).toEqual 0 - - it "returns null when the variant has no unit_value", -> - variant = {} - expect(BulkProducts.variantUnitValue(variant)).toEqual null - - - describe "fetching a product by id", -> - it "returns the product when it is present", -> - product = {id: 123} - BulkProducts.products = [product] - expect(BulkProducts.find(123)).toEqual product - - it "returns null when the product is not present", -> - BulkProducts.products = [] - expect(BulkProducts.find(123)).toBeNull()