diff --git a/app/assets/javascripts/admin/order_cycles/controllers/incoming_controller.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/incoming_controller.js.coffee index 5e34e42c72..c18cc0e4a3 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/incoming_controller.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/incoming_controller.js.coffee @@ -1,4 +1,4 @@ -angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $controller, $location, Enterprise, OrderCycle, ocInstance) -> +angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $controller, $location, Enterprise, OrderCycle, ExchangeProduct, ocInstance) -> $controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location}) $scope.view = 'incoming' @@ -9,19 +9,23 @@ angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($ enterprise = $scope.enterprises[exchange.enterprise_id] return enterprise.numVariants if enterprise.numVariants? - $scope.loadExchangeProducts(exchange) - return unless enterprise.supplied_products? + enterprise.numVariants = 0 + params = { exchange_id: exchange.id, enterprise_id: exchange.enterprise_id, order_cycle_id: $scope.order_cycle.id, incoming: true} + ExchangeProduct.countVariants params, (variants_count) -> + enterprise.numVariants = variants_count + $scope.setSelectAllVariantsCheckboxValue(exchange, enterprise.numVariants) - enterprise.numVariants = $scope.countVariants(enterprise.supplied_products) - - $scope.countVariants = (products) -> - return 0 unless products - - numVariants = 0 - for product in products - numVariants += product.variants.length - numVariants + return enterprise.numVariants $scope.addSupplier = ($event) -> $event.preventDefault() OrderCycle.addSupplier $scope.new_supplier_id + + # To select all variants we first need to load them all from the server + # + # This is only needed in Incoming exchanges as here we use supplied_products, + # in Outgoing Exchanges the variants are loaded as part of the Exchange payload + $scope.selectAllVariants = (exchange, selected) -> + $scope.loadAllExchangeProducts(exchange).then -> + $scope.setExchangeVariants(exchange, $scope.suppliedVariants(exchange.enterprise_id), selected) + $scope.$apply() diff --git a/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_basic_controller.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_basic_controller.js.coffee index 7a01e1c5e2..ff25bc7d4b 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_basic_controller.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_basic_controller.js.coffee @@ -18,11 +18,11 @@ angular.module('admin.orderCycles') $scope.cancel = (destination) -> $window.location = destination - # Used in panels/exchange_supplied_products.html + # Used in panels/exchange_products_supplied.html $scope.suppliedVariants = (enterprise_id) -> Enterprise.suppliedVariants(enterprise_id) - # Used in panels/exchange_supplied_products.html and panels/exchange_distributed_products.html + # Used in panels/exchange_products_supplied.html and panels/exchange_products_distributed.html $scope.setExchangeVariants = (exchange, variants, selected) -> OrderCycle.setExchangeVariants(exchange, variants, selected) diff --git a/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_exchanges_controller.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_exchanges_controller.js.coffee index 066bcaf25c..fde1436745 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_exchanges_controller.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/order_cycle_exchanges_controller.js.coffee @@ -5,6 +5,12 @@ angular.module('admin.orderCycles') $scope.supplier_enterprises = Enterprise.producer_enterprises $scope.distributor_enterprises = Enterprise.hub_enterprises + $scope.productsLoading = -> + RequestMonitor.loading + + $scope.setSelectAllVariantsCheckboxValue = (exchange, totalNumberOfVariants) -> + exchange.select_all_variants = $scope.exchangeSelectedVariants(exchange) >= totalNumberOfVariants + $scope.exchangeSelectedVariants = (exchange) -> OrderCycle.exchangeSelectedVariants(exchange) @@ -36,18 +42,35 @@ angular.module('admin.orderCycles') $scope.removeDistributionOfVariant = (variant_id) -> OrderCycle.removeDistributionOfVariant(variant_id) - $scope.loadExchangeProducts = (exchange) -> - return if $scope.enterprises[exchange.enterprise_id].supplied_products_fetched? - $scope.enterprises[exchange.enterprise_id].supplied_products_fetched = true + $scope.loadExchangeProducts = (exchange, page = 1) -> + enterprise = $scope.enterprises[exchange.enterprise_id] + enterprise.supplied_products ?= [] + + return if enterprise.last_page_loaded? && enterprise.last_page_loaded >= page + enterprise.last_page_loaded = page incoming = true if $scope.view == 'incoming' - params = { exchange_id: exchange.id, enterprise_id: exchange.enterprise_id, order_cycle_id: $scope.order_cycle.id, incoming: incoming} - ExchangeProduct.index params, (products) -> - $scope.enterprises[exchange.enterprise_id].supplied_products = products + params = { exchange_id: exchange.id, enterprise_id: exchange.enterprise_id, order_cycle_id: $scope.order_cycle.id, incoming: incoming, page: page} + ExchangeProduct.index params, (products, num_of_pages, num_of_products) -> + enterprise.num_of_pages = num_of_pages + enterprise.num_of_products = num_of_products + enterprise.supplied_products.push products... + + $scope.loadMoreExchangeProducts = (exchange) -> + $scope.loadExchangeProducts(exchange, $scope.enterprises[exchange.enterprise_id].last_page_loaded + 1) + + $scope.loadAllExchangeProducts = (exchange) -> + enterprise = $scope.enterprises[exchange.enterprise_id] + + if enterprise.last_page_loaded < enterprise.num_of_pages + for page_to_load in [(enterprise.last_page_loaded + 1)..enterprise.num_of_pages] + RequestMonitor.load $scope.loadExchangeProducts(exchange, page_to_load).$promise + + RequestMonitor.loadQueue # initialize exchange products panel if not yet done $scope.exchangeProdutsPanelInitialized = [] $scope.initializeExchangeProductsPanel = (exchange) -> return if $scope.exchangeProdutsPanelInitialized[exchange.enterprise_id] - $scope.loadExchangeProducts(exchange) + RequestMonitor.load $scope.loadExchangeProducts(exchange).$promise $scope.exchangeProdutsPanelInitialized[exchange.enterprise_id] = true diff --git a/app/assets/javascripts/admin/order_cycles/controllers/outgoing_controller.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/outgoing_controller.js.coffee index 157a723672..31aec9677f 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/outgoing_controller.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/outgoing_controller.js.coffee @@ -9,6 +9,11 @@ angular.module('admin.orderCycles').controller 'AdminOrderCycleOutgoingCtrl', ($ $scope.incomingExchangeVariantsFor = (enterprise_id) -> $filter('filterExchangeVariants')(OrderCycle.incomingExchangesVariants(), $scope.order_cycle.visible_variants_for_outgoing_exchanges[enterprise_id]) + $scope.exchangeTotalVariants = (exchange) -> + totalNumberOfVariants = $scope.incomingExchangeVariantsFor(exchange.enterprise_id).length + $scope.setSelectAllVariantsCheckboxValue(exchange, totalNumberOfVariants) + totalNumberOfVariants + $scope.addDistributor = ($event) -> $event.preventDefault() OrderCycle.addDistributor $scope.new_distributor_id diff --git a/app/assets/javascripts/admin/order_cycles/services/exchange_product.js.coffee b/app/assets/javascripts/admin/order_cycles/services/exchange_product.js.coffee index dfc8adeca5..9750b0f753 100644 --- a/app/assets/javascripts/admin/order_cycles/services/exchange_product.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/exchange_product.js.coffee @@ -1,15 +1,16 @@ angular.module('admin.orderCycles').factory('ExchangeProduct', ($resource) -> ExchangeProductResource = $resource('/api/exchanges/:exchange_id/products.json', {}, { - 'index': - method: 'GET' - isArray: true + 'index': { method: 'GET' } + 'variant_count': { method: 'GET', params: { action_name: "variant_count" }} }) { ExchangeProductResource: ExchangeProductResource - loaded: false index: (params={}, callback=null) -> ExchangeProductResource.index params, (data) => - @loaded = true - (callback || angular.noop)(data) + (callback || angular.noop)(data.products, data.pagination.pages, data.pagination.results) + + countVariants: (params={}, callback=null) -> + ExchangeProductResource.variant_count params, (data) => + (callback || angular.noop)(data.count) }) diff --git a/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_products_distributed.html.haml similarity index 78% rename from app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml rename to app/assets/javascripts/templates/admin/panels/exchange_products_distributed.html.haml index 4dad8310ce..d26bc2346c 100644 --- a/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml +++ b/app/assets/javascripts/templates/admin/panels/exchange_products_distributed.html.haml @@ -1,5 +1,7 @@ .row.exchange-distributed-products{'ng-init' => 'initializeExchangeProductsPanel(exchange)'} - .sixteen.columns.alpha.omega + .sixteen.columns.alpha.omega{ 'ng-show' => 'enterprises[exchange.enterprise_id].supplied_products.length != 0' } + %div{ 'ng-include' => "'admin/panels/exchange_products_panel_header.html'" } + .exchange-select-all-variants %label %input{ type: 'checkbox', name: 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants', @@ -7,10 +9,9 @@ 'ng-model' => 'exchange.select_all_variants', 'ng-change' => 'setExchangeVariants(exchange, incomingExchangeVariantsFor(exchange.enterprise_id), exchange.select_all_variants)', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_select_all_variants' } - {{ 'admin.select_all' | t }} + {{ 'js.admin.panels.exchange_products.select_all_products' | t:{ total_number_of_products: enterprises[exchange.enterprise_id].num_of_products } }} - .exchange-products - -# Scope product list based on permissions the current user has to view variants in this exchange + .exchange-products{ 'ng-hide' => 'productsLoading()' } .exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products | filter:visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges' } .exchange-product-details %label @@ -26,3 +27,5 @@ 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', 'ng-disabled' => '!order_cycle.editable_variants_for_outgoing_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_outgoing_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' } {{ variant.label }} + + %div{ 'ng-include' => "'admin/panels/exchange_products_panel_footer.html'" } diff --git a/app/assets/javascripts/templates/admin/panels/exchange_products_panel_footer.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_products_panel_footer.html.haml new file mode 100644 index 0000000000..b15b8a6d0a --- /dev/null +++ b/app/assets/javascripts/templates/admin/panels/exchange_products_panel_footer.html.haml @@ -0,0 +1,11 @@ +.pagination{ 'ng-show' => 'enterprises[exchange.enterprise_id].last_page_loaded < enterprises[exchange.enterprise_id].num_of_pages && !productsLoading()'} + .button{ 'ng-click' => 'loadMoreExchangeProducts(exchange)' } + {{ 'js.admin.panels.exchange_products.load_more_products' | t }} + .button{ 'ng-click' => 'loadAllExchangeProducts(exchange)' } + {{ 'js.admin.panels.exchange_products.load_all_products' | t }} + +.sixteen.columns.alpha#loading{ 'ng-show' => 'productsLoading()' } + %br + %img.spinner{ src: "/assets/spinning-circles.svg" } + %h1 + {{ 'js.admin.panels.exchange_products.loading_products' | t }} diff --git a/app/assets/javascripts/templates/admin/panels/exchange_products_panel_header.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_products_panel_header.html.haml new file mode 100644 index 0000000000..5d08888456 --- /dev/null +++ b/app/assets/javascripts/templates/admin/panels/exchange_products_panel_header.html.haml @@ -0,0 +1,5 @@ +.exchange-load-all-variants + %div + {{ 'js.admin.panels.exchange_products.products_loaded' | t:{ num_of_products_loaded: enterprises[exchange.enterprise_id].supplied_products.length, total_number_of_products: enterprises[exchange.enterprise_id].num_of_products } }} + %a{ 'ng-click' => 'loadAllExchangeProducts(exchange)', 'ng-show' => 'enterprises[exchange.enterprise_id].last_page_loaded < enterprises[exchange.enterprise_id].num_of_pages' } + {{ 'js.admin.panels.exchange_products.load_all_products' | t }} diff --git a/app/assets/javascripts/templates/admin/panels/exchange_products_simple.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_products_simple.html.haml new file mode 100644 index 0000000000..ee9be41c8c --- /dev/null +++ b/app/assets/javascripts/templates/admin/panels/exchange_products_simple.html.haml @@ -0,0 +1,12 @@ +.row.exchange-supplied-products + .sixteen.columns.alpha.omega + .exchange-select-all-variants + %label + %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants', + value: 1, + 'ng-model' => 'exchange.select_all_variants', + 'ng-change' => 'setExchangeVariants(exchange, suppliedVariants(exchange.enterprise_id), exchange.select_all_variants)', + 'id' => 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants' } + {{ 'admin.select_all' | t }} + + %div{ 'ng-include' => "'admin/panels/exchange_products_supplied_list.html'" } diff --git a/app/assets/javascripts/templates/admin/panels/exchange_products_supplied.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_products_supplied.html.haml new file mode 100644 index 0000000000..ffc5277168 --- /dev/null +++ b/app/assets/javascripts/templates/admin/panels/exchange_products_supplied.html.haml @@ -0,0 +1,16 @@ +.row.exchange-supplied-products{'ng-init' => 'initializeExchangeProductsPanel(exchange)'} + .sixteen.columns.alpha.omega{ 'ng-show' => 'enterprises[exchange.enterprise_id].supplied_products.length != 0' } + %div{ 'ng-include' => "'admin/panels/exchange_products_panel_header.html'" } + + .exchange-select-all-variants + %label + %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants', + value: 1, + 'ng-model' => 'exchange.select_all_variants', + 'ng-change' => 'selectAllVariants(exchange, exchange.select_all_variants)', + 'id' => 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants' } + {{ 'js.admin.panels.exchange_products.select_all_products' | t:{ total_number_of_products: enterprises[exchange.enterprise_id].num_of_products } }} + + %div{ 'ng-include' => "'admin/panels/exchange_products_supplied_list.html'" } + + %div{ 'ng-include' => "'admin/panels/exchange_products_panel_footer.html'" } diff --git a/app/assets/javascripts/templates/admin/panels/exchange_products_supplied_list.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_products_supplied_list.html.haml new file mode 100644 index 0000000000..f5753df343 --- /dev/null +++ b/app/assets/javascripts/templates/admin/panels/exchange_products_supplied_list.html.haml @@ -0,0 +1,16 @@ +.exchange-products{ 'ng-hide' => 'productsLoading()' } + .exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'} + .exchange-product-details + %label + %img{'ng-src' => '{{ product.image_url }}'} + {{ product.name }} + + .exchange-product-variant{'ng-repeat' => 'variant in product.variants'} + %label + %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', + value: 1, + 'ng-model' => 'exchange.variants[variant.id]', + 'ofn-sync-distributions' => '{{ variant.id }}', + 'id' => 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', + 'ng-disabled' => '!order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' } + {{ variant.label }} diff --git a/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml deleted file mode 100644 index e6c80e973a..0000000000 --- a/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml +++ /dev/null @@ -1,29 +0,0 @@ -.row.exchange-supplied-products{'ng-init' => 'initializeExchangeProductsPanel(exchange)'} - .sixteen.columns.alpha.omega - .exchange-select-all-variants - %label - %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants', - value: 1, - 'ng-model' => 'exchange.select_all_variants', - 'ng-change' => 'setExchangeVariants(exchange, suppliedVariants(exchange.enterprise_id), exchange.select_all_variants)', - 'id' => 'order_cycle_incoming_exchange_{{ $index }}_select_all_variants' } - {{ 'admin.select_all' | t }} - - .exchange-products - -# No need to scope product list based on permissions, because if an incoming exchange is visible, - -# then all of the variants within it should be visible. May change in the future? - .exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'} - .exchange-product-details - %label - %img{'ng-src' => '{{ product.image_url }}'} - {{ product.name }} - - .exchange-product-variant{'ng-repeat' => 'variant in product.variants'} - %label - %input{ type: 'checkbox', name: 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', - value: 1, - 'ng-model' => 'exchange.variants[variant.id]', - 'ofn-sync-distributions' => '{{ variant.id }}', - 'id' => 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', - 'ng-disabled' => '!order_cycle.editable_variants_for_incoming_exchanges.hasOwnProperty(exchange.enterprise_id) || order_cycle.editable_variants_for_incoming_exchanges[exchange.enterprise_id].indexOf(variant.id) < 0' } - {{ variant.label }} diff --git a/app/assets/stylesheets/admin/openfoodnetwork.css.scss b/app/assets/stylesheets/admin/openfoodnetwork.css.scss index 8cdc6c3e87..99f4a849bb 100644 --- a/app/assets/stylesheets/admin/openfoodnetwork.css.scss +++ b/app/assets/stylesheets/admin/openfoodnetwork.css.scss @@ -104,9 +104,13 @@ form.order_cycle { width: 20px; } + .exchange-load-all-variants { + margin: 0px 5px 20px 5px; + } + .exchange-select-all-variants { clear: both; - margin: 5px; + margin: 15px 5px 25px 5px; } .exchange-products { @@ -209,6 +213,7 @@ table#listing_enterprise_groups { #loading { text-align: center; img.spinner { + border: 0px; width: 100px; height: 100px; } diff --git a/app/controllers/api/exchange_products_controller.rb b/app/controllers/api/exchange_products_controller.rb index c87fb95c26..e9e3ec537b 100644 --- a/app/controllers/api/exchange_products_controller.rb +++ b/app/controllers/api/exchange_products_controller.rb @@ -1,6 +1,9 @@ # This controller lists products that can be added to an exchange module Api class ExchangeProductsController < Api::BaseController + DEFAULT_PAGE = 1 + DEFAULT_PER_PAGE = 100 + skip_authorization_check only: [:index] # If exchange_id is present in the URL: @@ -17,20 +20,32 @@ module Api load_data_from_other_params end - render_products + render_variant_count && return if params[:action_name] == "variant_count" + + render_paginated_products paginated_products end private - def render_products - products = ExchangeProductsRenderer. + def render_variant_count + render text: { + count: Spree::Variant. + not_master. + where(product_id: products). + count + }.to_json + end + + def products + ExchangeProductsRenderer. new(@order_cycle, spree_current_user). exchange_products(@incoming, @enterprise) + end - render json: products, - each_serializer: Api::Admin::ForOrderCycle::SuppliedProductSerializer, - order_cycle: @order_cycle, - status: :ok + def paginated_products + products. + page(params[:page] || DEFAULT_PAGE). + per(params[:per_page] || DEFAULT_PER_PAGE) end def load_data_from_exchange @@ -51,5 +66,27 @@ module Api end @incoming = params[:incoming] end + + def render_paginated_products(paginated_products) + serializer = ActiveModel::ArraySerializer.new( + paginated_products, + each_serializer: Api::Admin::ForOrderCycle::SuppliedProductSerializer, + order_cycle: @order_cycle + ) + + render text: { + products: serializer, + pagination: pagination_data(paginated_products) + }.to_json + end + + def pagination_data(paginated_products) + { + results: paginated_products.total_count, + pages: paginated_products.num_pages, + page: (params[:page] || DEFAULT_PAGE).to_i, + per_page: (params[:per_page] || DEFAULT_PER_PAGE).to_i + } + end end end diff --git a/app/views/admin/order_cycles/_exchange_form.html.haml b/app/views/admin/order_cycles/_exchange_form.html.haml index 63a44e9a3b..1713555840 100644 --- a/app/views/admin/order_cycles/_exchange_form.html.haml +++ b/app/views/admin/order_cycles/_exchange_form.html.haml @@ -1,11 +1,7 @@ %tr{ ng: { class: "'#{type} #{type}-{{ exchange.enterprise_id }}'" } } %td{:class => "#{type}_name"} {{ enterprises[exchange.enterprise_id].name }} %td.products.panel-toggle.text-center{ name: "products" } - {{ exchangeSelectedVariants(exchange) }} / - - if type == 'supplier' - {{ exchangeTotalVariants(exchange) }} - - else - {{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }} + {{ exchangeSelectedVariants(exchange) }} / {{ exchangeTotalVariants(exchange) }} = t('.selected') - if type == 'supplier' %td.receival-details @@ -34,11 +30,11 @@ - if type == 'supplier' %tr.panel-row{ object: "exchange", - panels: "{products: 'exchange_supplied_products'}", - locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,suppliedVariants,removeDistributionOfVariant,initializeExchangeProductsPanel", + panels: "{products: 'exchange_products_supplied'}", + locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,selectAllVariants,suppliedVariants,removeDistributionOfVariant,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading", colspan: 4 } - if type == 'distributor' %tr.panel-row{ object: "exchange", - panels: "{products: 'exchange_distributed_products', tags: 'exchange_tags'}", - locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel", + panels: "{products: 'exchange_products_distributed', tags: 'exchange_tags'}", + locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel,loadMoreExchangeProducts,loadAllExchangeProducts,productsLoading", colspan: 5 } diff --git a/app/views/admin/order_cycles/_simple_form.html.haml b/app/views/admin/order_cycles/_simple_form.html.haml index 843f27ba78..3f6372175d 100644 --- a/app/views/admin/order_cycles/_simple_form.html.haml +++ b/app/views/admin/order_cycles/_simple_form.html.haml @@ -17,7 +17,7 @@ %table.exchanges %tbody{ng: {repeat: "exchange in order_cycle.incoming_exchanges"}} %tr.products - %td{ ng: { include: "'admin/panels/exchange_supplied_products.html'" } } + %td{ ng: { include: "'admin/panels/exchange_products_simple.html'" } } %br/ = label_tag t('.fees') diff --git a/config/locales/en.yml b/config/locales/en.yml index f1945520a0..d617a41c58 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -2560,6 +2560,12 @@ See the %{link} to find out more about %{sitename}'s features and to start using severity: Severity description: Description resolve: Resolve + exchange_products: + load_more_products: "Load More Products" + load_all_products: "Load All Products" + select_all_products: "Select All %{total_number_of_products} Products" + products_loaded: "%{num_of_products_loaded} of %{total_number_of_products} Products Loaded" + loading_products: "Loading Products" tag_rules: shipping_method_tagged_top: "Shipping methods tagged" shipping_method_tagged_bottom: "are:" diff --git a/spec/controllers/api/exchange_products_controller_spec.rb b/spec/controllers/api/exchange_products_controller_spec.rb index 93fcf670f4..a19ec387fd 100644 --- a/spec/controllers/api/exchange_products_controller_spec.rb +++ b/spec/controllers/api/exchange_products_controller_spec.rb @@ -4,31 +4,59 @@ module Api describe ExchangeProductsController, type: :controller do include AuthenticationWorkflow - let!(:order_cycle) { create(:order_cycle) } - let!(:coordinator) { order_cycle.coordinator } + let(:order_cycle) { create(:order_cycle) } + let(:exchange) { order_cycle.exchanges.incoming.first } + let(:coordinator) { order_cycle.coordinator } + + let!(:renderer) { ExchangeProductsRenderer.new(order_cycle, coordinator.owner) } before do allow(controller).to receive_messages spree_current_user: coordinator.owner + allow(ExchangeProductsRenderer).to receive(:new) { renderer } + allow(renderer). + to receive(:exchange_products). + with(exchange.incoming, exchange.sender). + and_return(products_relation) end describe "#index" do - describe "for incoming exchanges" do - it "loads data" do - exchange = order_cycle.exchanges.incoming.first - spree_get :index, exchange_id: exchange.id + describe "when the product list is empty" do + let(:products_relation) { Spree::Product.where("1=0") } - expect(json_response.first["supplier_name"]).to eq exchange.variants.first.product.supplier.name + it "handles it gracefully" do + spree_get :index, exchange_id: exchange.id + expect(json_response["products"].length).to eq 0 end end - describe "for outgoing exchanges" do - it "loads data" do - exchange = order_cycle.exchanges.outgoing.first - spree_get :index, exchange_id: exchange.id + describe "when a product is returned" do + let(:products_relation) { Spree::Product.where(id: exchange.variants.first.product.id) } - suppliers = [exchange.variants[0].product.supplier.name, exchange.variants[1].product.supplier.name] - expect(suppliers).to include json_response.first["supplier_name"] - expect(suppliers).to include json_response.second["supplier_name"] + describe "when an exchange id param is provided" do + it "uses exchange order_cycle, incoming and enterprise to fetch products" do + spree_get :index, exchange_id: exchange.id, order_cycle_id: 666, enterprise_id: 666, incoming: false + expect(json_response["products"].first["supplier_name"]).to eq exchange.variants.first.product.supplier.name + end + end + + describe "when an exchange id param is not provided" do + it "uses params order_cycle, incoming and enterprise to fetch products" do + spree_get :index, order_cycle_id: order_cycle.id, enterprise_id: exchange.sender_id, incoming: true + expect(json_response["products"].first["supplier_name"]).to eq exchange.variants.first.product.supplier.name + end + end + end + + describe "pagination" do + let(:exchange) { order_cycle.exchanges.outgoing.first } + let(:products_relation) { Spree::Product.includes(:variants).where("spree_variants.id": exchange.variants.map(&:id)) } + + it "paginates results" do + spree_get :index, exchange_id: exchange.id, page: 1, per_page: 1 + + expect(json_response["products"].size).to eq 1 + expect(json_response["pagination"]["results"]).to eq 2 + expect(json_response["pagination"]["pages"]).to eq 2 end end end diff --git a/spec/features/admin/order_cycles_spec.rb b/spec/features/admin/order_cycles_spec.rb index 762e2504cf..e4f83865a8 100644 --- a/spec/features/admin/order_cycles_spec.rb +++ b/spec/features/admin/order_cycles_spec.rb @@ -522,6 +522,45 @@ feature ' expect(page).not_to have_selector 'table.exchanges tr.supplier' end + describe "editing an order cycle with multiple pages of products", js: true do + let(:order_cycle) { create(:order_cycle) } + let(:supplier_enterprise) { order_cycle.exchanges.incoming.first.sender } + let!(:new_product) { create(:product, supplier: supplier_enterprise) } + + before do + stub_const("Api::ExchangeProductsController::DEFAULT_PER_PAGE", 1) + + quick_login_as_admin + visit admin_order_cycle_incoming_path(order_cycle) + expect(page).to have_content "1 / 2 selected" + + page.find("tr.supplier-#{supplier_enterprise.id} td.products").click + expect(page).to have_selector ".exchange-product-details" + + expect(page).to have_content "1 of 2 Products Loaded" + expect(page).to_not have_content new_product.name + end + + scenario "load all products" do + page.find(".exchange-load-all-variants a").click + + expect_all_products_loaded + end + + scenario "select all products" do + check "order_cycle_incoming_exchange_0_select_all_variants" + + expect_all_products_loaded + + expect(page).to have_checked_field "order_cycle_incoming_exchange_0_variants_#{new_product.variants.first.id}", disabled: false + end + + def expect_all_products_loaded + expect(page).to have_content new_product.name.upcase + expect(page).to have_content "2 of 2 Products Loaded" + end + end + scenario "updating many order cycle opening/closing times at once", js: true do # Given three order cycles oc1 = create(:simple_order_cycle) diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/incoming_controller_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/incoming_controller_spec.js.coffee index e650317e44..73deb77adc 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/incoming_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/incoming_controller_spec.js.coffee @@ -23,21 +23,6 @@ describe 'AdminOrderCycleIncomingCtrl', -> inject ($controller) -> ctrl = $controller 'AdminOrderCycleIncomingCtrl', {$scope: scope, $location: location, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee, ocInstance: ocInstance} - it 'counts total variants in a list of products', -> - products = [ - {variants: [{}]}, - {variants: [{}]}, - {variants: [{}, {}, {}]} - ] - - expect(scope.countVariants(products)).toEqual(5) - - it 'returns zero when products list is null', -> - expect(scope.countVariants(null)).toEqual(0) - - it 'returns zero when products list is empty', -> - expect(scope.countVariants([])).toEqual(0) - it 'adds order cycle suppliers', -> scope.new_supplier_id = 'new supplier id' diff --git a/spec/services/exchange_products_renderer_spec.rb b/spec/services/exchange_products_renderer_spec.rb new file mode 100644 index 0000000000..2af3636e3d --- /dev/null +++ b/spec/services/exchange_products_renderer_spec.rb @@ -0,0 +1,29 @@ +require "spec_helper" + +describe ExchangeProductsRenderer do + let(:order_cycle) { create(:order_cycle) } + let(:coordinator) { order_cycle.coordinator } + let(:renderer) { described_class.new(order_cycle, coordinator.owner) } + + describe "#exchange_products" do + describe "for an incoming exchange" do + it "loads products" do + exchange = order_cycle.exchanges.incoming.first + products = renderer.exchange_products(true, exchange.sender) + + expect(products.first.supplier.name).to eq exchange.variants.first.product.supplier.name + end + end + + describe "for an outgoing exchange" do + it "loads products" do + exchange = order_cycle.exchanges.outgoing.first + products = renderer.exchange_products(false, exchange.receiver) + + suppliers = [exchange.variants[0].product.supplier.name, exchange.variants[1].product.supplier.name] + expect(suppliers).to include products.first.supplier.name + expect(suppliers).to include products.second.supplier.name + end + end + end +end