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 d3a1797643..5e34e42c72 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,5 +1,27 @@ -angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $controller, $location, Enterprise, ocInstance) -> +angular.module('admin.orderCycles').controller 'AdminOrderCycleIncomingCtrl', ($scope, $controller, $location, Enterprise, OrderCycle, ocInstance) -> $controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location}) - $scope.enterpriseTotalVariants = (enterprise) -> - Enterprise.totalVariants(enterprise) + $scope.view = 'incoming' + + $scope.exchangeTotalVariants = (exchange) -> + return unless $scope.enterprises? && $scope.enterprises[exchange.enterprise_id]? + + enterprise = $scope.enterprises[exchange.enterprise_id] + return enterprise.numVariants if enterprise.numVariants? + + $scope.loadExchangeProducts(exchange) + return unless enterprise.supplied_products? + + 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 + + $scope.addSupplier = ($event) -> + $event.preventDefault() + OrderCycle.addSupplier $scope.new_supplier_id 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 82aea0a434..066bcaf25c 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 @@ -1,10 +1,9 @@ angular.module('admin.orderCycles') - .controller 'AdminOrderCycleExchangesCtrl', ($scope, $controller, $filter, $window, $location, $timeout, OrderCycle, Enterprise, EnterpriseFee, Schedules, RequestMonitor, ocInstance, StatusMessage) -> + .controller 'AdminOrderCycleExchangesCtrl', ($scope, $controller, $filter, $window, $location, $timeout, OrderCycle, ExchangeProduct, Enterprise, EnterpriseFee, Schedules, RequestMonitor, ocInstance, StatusMessage) -> $controller('AdminEditOrderCycleCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location}) $scope.supplier_enterprises = Enterprise.producer_enterprises $scope.distributor_enterprises = Enterprise.hub_enterprises - $scope.supplied_products = Enterprise.supplied_products $scope.exchangeSelectedVariants = (exchange) -> OrderCycle.exchangeSelectedVariants(exchange) @@ -29,14 +28,6 @@ angular.module('admin.orderCycles') OrderCycle.removeExchangeFee(exchange, index) $scope.order_cycle_form.$dirty = true - $scope.addSupplier = ($event) -> - $event.preventDefault() - OrderCycle.addSupplier($scope.new_supplier_id) - - $scope.addDistributor = ($event) -> - $event.preventDefault() - OrderCycle.addDistributor($scope.new_distributor_id) - $scope.setPickupTimeFieldDirty = (index) -> $timeout -> pickup_time_field_name = "order_cycle_outgoing_exchange_" + index + "_pickup_time" @@ -44,3 +35,19 @@ 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 + + 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 + + # initialize exchange products panel if not yet done + $scope.exchangeProdutsPanelInitialized = [] + $scope.initializeExchangeProductsPanel = (exchange) -> + return if $scope.exchangeProdutsPanelInitialized[exchange.enterprise_id] + $scope.loadExchangeProducts(exchange) + $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 893666b1a5..157a723672 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 @@ -1,8 +1,7 @@ angular.module('admin.orderCycles').controller 'AdminOrderCycleOutgoingCtrl', ($scope, $controller, $filter, $location, OrderCycle, ocInstance, StatusMessage) -> $controller('AdminOrderCycleExchangesCtrl', {$scope: $scope, ocInstance: ocInstance, $location: $location}) - $scope.productSuppliedToOrderCycle = (product) -> - OrderCycle.productSuppliedToOrderCycle(product) + $scope.view = 'outgoing' $scope.variantSuppliedToOrderCycle = (variant) -> OrderCycle.variantSuppliedToOrderCycle(variant) @@ -10,6 +9,10 @@ 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.addDistributor = ($event) -> + $event.preventDefault() + OrderCycle.addDistributor $scope.new_distributor_id + $scope.submit = ($event, destination) -> $event.preventDefault() StatusMessage.display 'progress', t('js.saving') diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee index 545b35e97b..a05c1f01a9 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_create.js.coffee @@ -1,23 +1,33 @@ -angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl", ($scope, $controller, $window, OrderCycle, Enterprise, EnterpriseFee, StatusMessage, Schedules, RequestMonitor, ocInstance) -> +angular.module('admin.orderCycles').controller "AdminSimpleCreateOrderCycleCtrl", ($scope, $controller, $window, OrderCycle, Enterprise, EnterpriseFee, ExchangeProduct, StatusMessage, Schedules, RequestMonitor, ocInstance) -> $controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance}) $scope.order_cycle = OrderCycle.new {coordinator_id: ocInstance.coordinator_id}, => - # TODO: make this a get method, which only fetches one enterprise $scope.enterprises = Enterprise.index {coordinator_id: ocInstance.coordinator_id}, (enterprises) => $scope.init(enterprises) $scope.enterprise_fees = EnterpriseFee.index(coordinator_id: ocInstance.coordinator_id) $scope.init = (enterprises) -> enterprise = enterprises[Object.keys(enterprises)[0]] - OrderCycle.addSupplier enterprise.id - OrderCycle.addDistributor enterprise.id + OrderCycle.order_cycle.coordinator_id = enterprise.id + + OrderCycle.addDistributor enterprise.id, $scope.setOutgoingExchange + OrderCycle.addSupplier enterprise.id, $scope.loadExchangeProducts + + $scope.setOutgoingExchange = -> $scope.outgoing_exchange = OrderCycle.order_cycle.outgoing_exchanges[0] - # All variants start as checked - OrderCycle.setExchangeVariants(OrderCycle.order_cycle.incoming_exchanges[0], - Enterprise.suppliedVariants(enterprise.id), true) + $scope.loadExchangeProducts = -> + $scope.incoming_exchange = OrderCycle.order_cycle.incoming_exchanges[0] - OrderCycle.order_cycle.coordinator_id = enterprise.id + params = { enterprise_id: $scope.incoming_exchange.enterprise_id, incoming: true } + ExchangeProduct.index params, $scope.storeProductsAndSelectAllVariants + + $scope.storeProductsAndSelectAllVariants = (products) -> + $scope.enterprises[$scope.incoming_exchange.enterprise_id].supplied_products = products + + # All variants start as checked + OrderCycle.setExchangeVariants($scope.incoming_exchange, + Enterprise.suppliedVariants($scope.incoming_exchange.enterprise_id), true) $scope.removeDistributionOfVariant = angular.noop diff --git a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee index 29cefaceed..fc043afaaa 100644 --- a/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/controllers/simple_edit.js.coffee @@ -1,4 +1,4 @@ -angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $controller, $location, $window, OrderCycle, Enterprise, EnterpriseFee, Schedules, RequestMonitor, StatusMessage, ocInstance) -> +angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", ($scope, $controller, $location, $window, OrderCycle, Enterprise, EnterpriseFee, ExchangeProduct, Schedules, RequestMonitor, StatusMessage, ocInstance) -> $controller('AdminOrderCycleBasicCtrl', {$scope: $scope, ocInstance: ocInstance}) $scope.orderCycleId = -> @@ -11,6 +11,12 @@ angular.module('admin.orderCycles').controller "AdminSimpleEditOrderCycleCtrl", $scope.init = -> $scope.outgoing_exchange = OrderCycle.order_cycle.outgoing_exchanges[0] + $scope.loadExchangeProducts() + + $scope.loadExchangeProducts = -> + exchange = OrderCycle.order_cycle.incoming_exchanges[0] + ExchangeProduct.index { exchange_id: exchange.id }, (products) -> + $scope.enterprises[exchange.enterprise_id].supplied_products = products $scope.removeDistributionOfVariant = angular.noop diff --git a/app/assets/javascripts/admin/order_cycles/services/enterprise.js.coffee b/app/assets/javascripts/admin/order_cycles/services/enterprise.js.coffee index 17f5bb385c..96a303c4b3 100644 --- a/app/assets/javascripts/admin/order_cycles/services/enterprise.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/enterprise.js.coffee @@ -12,7 +12,6 @@ angular.module('admin.orderCycles').factory('Enterprise', ($resource) -> enterprises: {} producer_enterprises: [] hub_enterprises: [] - supplied_products: [] loaded: false index: (params={}, callback=null) -> @@ -22,9 +21,6 @@ angular.module('admin.orderCycles').factory('Enterprise', ($resource) -> @producer_enterprises.push(enterprise) if enterprise.is_primary_producer @hub_enterprises.push(enterprise) if enterprise.sells == 'any' - for product in enterprise.supplied_products - @supplied_products.push(product) - @loaded = true (callback || angular.noop)(@enterprises) @@ -39,13 +35,4 @@ angular.module('admin.orderCycles').factory('Enterprise', ($resource) -> variant.id for variant in product.variants else [product.master_id] - - totalVariants: (enterprise) -> - numVariants = 0 - - if enterprise - counts = for product in enterprise.supplied_products - numVariants += if product.variants.length == 0 then 1 else product.variants.length - - numVariants }) 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 new file mode 100644 index 0000000000..dfc8adeca5 --- /dev/null +++ b/app/assets/javascripts/admin/order_cycles/services/exchange_product.js.coffee @@ -0,0 +1,15 @@ +angular.module('admin.orderCycles').factory('ExchangeProduct', ($resource) -> + ExchangeProductResource = $resource('/api/exchanges/:exchange_id/products.json', {}, { + 'index': + method: 'GET' + isArray: true + }) + { + ExchangeProductResource: ExchangeProductResource + loaded: false + + index: (params={}, callback=null) -> + ExchangeProductResource.index params, (data) => + @loaded = true + (callback || angular.noop)(data) + }) diff --git a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee index 58493d0214..d811984fb8 100644 --- a/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee +++ b/app/assets/javascripts/admin/order_cycles/services/order_cycle.js.coffee @@ -1,4 +1,4 @@ -angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, StatusMessage, Panels) -> +angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, $timeout, StatusMessage, Panels) -> OrderCycleResource = $resource '/admin/order_cycles/:action_name/:order_cycle_id.json', {}, { 'index': { method: 'GET', isArray: true} 'new' : { method: 'GET', params: { action_name: "new" } } @@ -44,11 +44,15 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S @removeDistributionOfVariant(variant.id) if exchange.incoming - addSupplier: (new_supplier_id) -> - this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, incoming: true, active: true, variants: {}, enterprise_fees: []}) + addSupplier: (new_supplier_id, callback) -> + this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, incoming: true, active: true, variants: {}, enterprise_fees: []}) + $timeout -> + (callback || angular.noop)() - addDistributor: (new_distributor_id) -> - this.order_cycle.outgoing_exchanges.push({enterprise_id: new_distributor_id, incoming: false, active: true, variants: {}, enterprise_fees: []}) + addDistributor: (new_distributor_id, callback) -> + this.order_cycle.outgoing_exchanges.push({ enterprise_id: new_distributor_id, incoming: false, active: true, variants: {}, enterprise_fees: [] }) + $timeout -> + (callback || angular.noop)() removeExchange: (exchange) -> if exchange.incoming @@ -71,18 +75,6 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S removeExchangeFee: (exchange, index) -> exchange.enterprise_fees.splice(index, 1) - productSuppliedToOrderCycle: (product) -> - product_variant_ids = (variant.id for variant in product.variants) - variant_ids = [product.master_id].concat(product_variant_ids) - incomingExchangesVariants = this.incomingExchangesVariants() - - # TODO: This is an O(n^2) implementation of set intersection and thus is slooow. - # Use a better algorithm if needed. - # Also, incomingExchangesVariants is called every time, when it only needs to be - # called once per change to incoming variants. Some sort of caching? - ids = (variant_id for variant_id in variant_ids when incomingExchangesVariants.indexOf(variant_id) != -1) - ids.length > 0 - variantSuppliedToOrderCycle: (variant) -> this.incomingExchangesVariants().indexOf(variant.id) != -1 @@ -143,7 +135,8 @@ angular.module('admin.orderCycles').factory 'OrderCycle', ($resource, $window, S delete(service.order_cycle.exchanges) service.loaded = true - (callback || angular.noop)(service.order_cycle) + $timeout -> + (callback || angular.noop)(service.order_cycle) this.order_cycle diff --git a/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml b/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml index 4253f54aae..4dad8310ce 100644 --- a/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml +++ b/app/assets/javascripts/templates/admin/panels/exchange_distributed_products.html.haml @@ -1,4 +1,4 @@ -.row.exchange-distributed-products +.row.exchange-distributed-products{'ng-init' => 'initializeExchangeProductsPanel(exchange)'} .sixteen.columns.alpha.omega .exchange-select-all-variants %label @@ -11,7 +11,7 @@ .exchange-products -# Scope product list based on permissions the current user has to view variants in this exchange - .exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle | visibleProducts:exchange:order_cycle.visible_variants_for_outgoing_exchanges' } + .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 %img{'ng-src' => '{{ product.image_url }}'} 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 index 10950258f9..e6c80e973a 100644 --- a/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml +++ b/app/assets/javascripts/templates/admin/panels/exchange_supplied_products.html.haml @@ -1,4 +1,4 @@ -.row.exchange-supplied-products +.row.exchange-supplied-products{'ng-init' => 'initializeExchangeProductsPanel(exchange)'} .sixteen.columns.alpha.omega .exchange-select-all-variants %label diff --git a/app/controllers/api/exchange_products_controller.rb b/app/controllers/api/exchange_products_controller.rb new file mode 100644 index 0000000000..c87fb95c26 --- /dev/null +++ b/app/controllers/api/exchange_products_controller.rb @@ -0,0 +1,55 @@ +# This controller lists products that can be added to an exchange +module Api + class ExchangeProductsController < Api::BaseController + skip_authorization_check only: [:index] + + # If exchange_id is present in the URL: + # Lists Products that can be added to that Exchange + # + # If exchange_id is not present in the URL: + # Lists Products of the Enterprise given that can be added to the given Order Cycle + # In this case parameters are: enterprise_id, order_cycle_id and incoming + # (order_cycle_id is not necessary for incoming exchanges) + def index + if params[:exchange_id].present? + load_data_from_exchange + else + load_data_from_other_params + end + + render_products + end + + private + + def render_products + products = ExchangeProductsRenderer. + new(@order_cycle, spree_current_user). + exchange_products(@incoming, @enterprise) + + render json: products, + each_serializer: Api::Admin::ForOrderCycle::SuppliedProductSerializer, + order_cycle: @order_cycle, + status: :ok + end + + def load_data_from_exchange + exchange = Exchange.find_by_id(params[:exchange_id]) + + @order_cycle = exchange.order_cycle + @incoming = exchange.incoming + @enterprise = exchange.sender + end + + def load_data_from_other_params + @enterprise = Enterprise.find_by_id(params[:enterprise_id]) + + if params[:order_cycle_id] + @order_cycle = OrderCycle.find_by_id(params[:order_cycle_id]) + elsif !params[:incoming] + raise "order_cycle_id is required to list products for new outgoing exchange" + end + @incoming = params[:incoming] + end + end +end diff --git a/app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb b/app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb index cdd78f5e5a..360f135680 100644 --- a/app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb +++ b/app/serializers/api/admin/for_order_cycle/enterprise_serializer.rb @@ -1,7 +1,7 @@ require 'open_food_network/enterprise_issue_validator' class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer - attributes :id, :name, :managed, :supplied_products, + attributes :id, :name, :managed, :issues_summary_supplier, :issues_summary_distributor, :is_primary_producer, :is_distributor, :sells @@ -25,12 +25,6 @@ class Api::Admin::ForOrderCycle::EnterpriseSerializer < ActiveModel::Serializer Enterprise.managed_by(options[:spree_current_user]).include? object end - def supplied_products - serializer = Api::Admin::ForOrderCycle::SuppliedProductSerializer - ActiveModel::ArraySerializer.new(products, each_serializer: serializer, - order_cycle: order_cycle) - end - private def products_scope diff --git a/app/serializers/api/admin/for_order_cycle/supplied_product_serializer.rb b/app/serializers/api/admin/for_order_cycle/supplied_product_serializer.rb index 8ed387dd2e..e52d555b6f 100644 --- a/app/serializers/api/admin/for_order_cycle/supplied_product_serializer.rb +++ b/app/serializers/api/admin/for_order_cycle/supplied_product_serializer.rb @@ -14,7 +14,8 @@ class Api::Admin::ForOrderCycle::SuppliedProductSerializer < ActiveModel::Serial end def variants - variants = if order_cycle.prefers_product_selection_from_coordinator_inventory_only? + variants = if order_cycle.present? && + order_cycle.prefers_product_selection_from_coordinator_inventory_only? object.variants.visible_for(order_cycle.coordinator) else object.variants diff --git a/app/services/exchange_products_renderer.rb b/app/services/exchange_products_renderer.rb new file mode 100644 index 0000000000..faadba34f1 --- /dev/null +++ b/app/services/exchange_products_renderer.rb @@ -0,0 +1,86 @@ +class ExchangeProductsRenderer + def initialize(order_cycle, user) + @order_cycle = order_cycle + @user = user + end + + def exchange_products(incoming, enterprise) + if incoming + products_for_incoming_exchange(enterprise) + else + products_for_outgoing_exchange + end + end + + private + + def products_for_incoming_exchange(enterprise) + supplied_products(enterprise.id) + end + + def supplied_products(enterprises_query_matcher) + products_relation = Spree::Product.where(supplier_id: enterprises_query_matcher) + + if @order_cycle.present? && + @order_cycle.prefers_product_selection_from_coordinator_inventory_only? + products_relation = products_relation.visible_for(@order_cycle.coordinator) + end + + products_relation + end + + def products_for_outgoing_exchange + supplied_products(enterprises_for_outgoing_exchange.select(:id)). + includes(:variants). + where("spree_variants.id": incoming_exchanges_variants) + end + + def incoming_exchanges_variants + return @incoming_exchanges_variants if @incoming_exchanges_variants.present? + + @incoming_exchanges_variants = [] + visible_incoming_exchanges.each do |incoming_exchange| + @incoming_exchanges_variants.push( + *incoming_exchange.variants.merge( + visible_incoming_variants(incoming_exchange.sender) + ).map(&:id).to_a + ) + end + @incoming_exchanges_variants + end + + def visible_incoming_exchanges + OpenFoodNetwork::OrderCyclePermissions. + new(@user, @order_cycle). + visible_exchanges. + by_enterprise_name. + incoming + end + + def visible_incoming_variants(incoming_exchange_sender) + variants_relation = permitted_incoming_variants(incoming_exchange_sender) + + if @order_cycle.prefers_product_selection_from_coordinator_inventory_only? + variants_relation = variants_relation.visible_for(@order_cycle.coordinator) + end + + variants_relation + end + + def permitted_incoming_variants(incoming_exchange_sender) + OpenFoodNetwork::OrderCyclePermissions. + new(@user, @order_cycle). + visible_variants_for_incoming_exchanges_from(incoming_exchange_sender) + end + + def enterprises_for_outgoing_exchange + enterprises = OpenFoodNetwork::OrderCyclePermissions. + new(@user, @order_cycle) + .visible_enterprises + return enterprises if enterprises.empty? + + enterprises.includes( + supplied_products: [:supplier, :variants, master: [:images]] + ) + 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 6e5a2c3df1..63a44e9a3b 100644 --- a/app/views/admin/order_cycles/_exchange_form.html.haml +++ b/app/views/admin/order_cycles/_exchange_form.html.haml @@ -3,7 +3,7 @@ %td.products.panel-toggle.text-center{ name: "products" } {{ exchangeSelectedVariants(exchange) }} / - if type == 'supplier' - {{ enterpriseTotalVariants(enterprises[exchange.enterprise_id]) }} + {{ exchangeTotalVariants(exchange) }} - else {{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }} = t('.selected') @@ -35,10 +35,10 @@ - if type == 'supplier' %tr.panel-row{ object: "exchange", panels: "{products: 'exchange_supplied_products'}", - locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,suppliedVariants,removeDistributionOfVariant", + locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,suppliedVariants,removeDistributionOfVariant,initializeExchangeProductsPanel", colspan: 4 } - if type == 'distributor' %tr.panel-row{ object: "exchange", panels: "{products: 'exchange_distributed_products', tags: 'exchange_tags'}", - locals: "$index,order_cycle,exchange,supplied_products,setExchangeVariants,incomingExchangeVariantsFor,productSuppliedToOrderCycle,variantSuppliedToOrderCycle", + locals: "$index,order_cycle,exchange,enterprises,setExchangeVariants,incomingExchangeVariantsFor,variantSuppliedToOrderCycle,initializeExchangeProductsPanel", colspan: 5 } diff --git a/config/routes/api.rb b/config/routes/api.rb index 34f80b7b13..1861e084a2 100644 --- a/config/routes/api.rb +++ b/config/routes/api.rb @@ -43,6 +43,10 @@ Openfoodnetwork::Application.routes.draw do get :properties, on: :member end + resources :exchanges, only: [:show], to: 'exchange_products#index' do + get :products, to: 'exchange_products#index' + end + resource :status do get :job_queue end diff --git a/spec/controllers/api/exchange_products_controller_spec.rb b/spec/controllers/api/exchange_products_controller_spec.rb new file mode 100644 index 0000000000..93fcf670f4 --- /dev/null +++ b/spec/controllers/api/exchange_products_controller_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +module Api + describe ExchangeProductsController, type: :controller do + include AuthenticationWorkflow + + let!(:order_cycle) { create(:order_cycle) } + let!(:coordinator) { order_cycle.coordinator } + + before do + allow(controller).to receive_messages spree_current_user: coordinator.owner + 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 + + expect(json_response.first["supplier_name"]).to eq exchange.variants.first.product.supplier.name + end + end + + describe "for outgoing exchanges" do + it "loads data" do + exchange = order_cycle.exchanges.outgoing.first + spree_get :index, exchange_id: exchange.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"] + end + end + end + end +end diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/edit_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/edit_spec.js.coffee index 2e762a17c9..bd60efb299 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/edit_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/edit_spec.js.coffee @@ -38,9 +38,6 @@ describe 'AdminEditOrderCycleCtrl', -> expect(EnterpriseFee.index).toHaveBeenCalled() expect(scope.enterprise_fees).toEqual('enterprise fees list') - it 'Loads order cycles', -> - expect(OrderCycle.load).toHaveBeenCalledWith('27') - it 'Removes coordinator fees', -> scope.removeCoordinatorFee(event, 0) expect(event.preventDefault).toHaveBeenCalled() 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 77f17eb419..e650317e44 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 @@ -13,14 +13,35 @@ describe 'AdminOrderCycleIncomingCtrl', -> location = absUrl: -> 'example.com/admin/order_cycles/27/edit' - Enterprise = - totalVariants: jasmine.createSpy('totalVariants').and.returnValue('variants total') + event = + preventDefault: jasmine.createSpy('preventDefault') + OrderCycle = + addSupplier: jasmine.createSpy('addSupplier') ocInstance = {} module('admin.orderCycles') inject ($controller) -> ctrl = $controller 'AdminOrderCycleIncomingCtrl', {$scope: scope, $location: location, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee, ocInstance: ocInstance} - it 'Delegates totalVariants to Enterprise', -> - expect(scope.enterpriseTotalVariants('enterprise')).toEqual('variants total') - expect(Enterprise.totalVariants).toHaveBeenCalledWith('enterprise') + 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' + + scope.addSupplier(event) + + expect(event.preventDefault).toHaveBeenCalled() + expect(OrderCycle.addSupplier).toHaveBeenCalledWith('new supplier id') diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/order_cycle_exchanges_controller_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/order_cycle_exchanges_controller_spec.js.coffee index adfa3b207c..f0e348ea31 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/order_cycle_exchanges_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/order_cycle_exchanges_controller_spec.js.coffee @@ -19,14 +19,11 @@ describe 'AdminOrderCycleExchangesCtrl', -> OrderCycle = exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').and.returnValue('variants selected') exchangeDirection: jasmine.createSpy('exchangeDirection').and.returnValue('exchange direction') - addSupplier: jasmine.createSpy('addSupplier') - addDistributor: jasmine.createSpy('addDistributor') removeExchange: jasmine.createSpy('removeExchange') addExchangeFee: jasmine.createSpy('addExchangeFee') removeExchangeFee: jasmine.createSpy('removeExchangeFee') removeDistributionOfVariant: jasmine.createSpy('removeDistributionOfVariant') - Enterprise = - supplied_products: 'supplied products' + Enterprise = {} EnterpriseFee = forEnterprise: jasmine.createSpy('forEnterprise').and.returnValue('enterprise fees for enterprise') ocInstance = {} @@ -35,9 +32,6 @@ describe 'AdminOrderCycleExchangesCtrl', -> inject ($controller) -> ctrl = $controller 'AdminOrderCycleExchangesCtrl', {$scope: scope, $location: location, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee, ocInstance: ocInstance} - it 'Loads supplied products', -> - expect(scope.supplied_products).toEqual('supplied products') - it 'Delegates exchangeSelectedVariants to OrderCycle', -> expect(scope.exchangeSelectedVariants('exchange')).toEqual('variants selected') expect(OrderCycle.exchangeSelectedVariants).toHaveBeenCalledWith('exchange') @@ -77,18 +71,6 @@ describe 'AdminOrderCycleExchangesCtrl', -> expect(OrderCycle.removeExchangeFee).toHaveBeenCalledWith('exchange', 0) expect(scope.order_cycle_form.$dirty).toEqual true - it 'Adds order cycle suppliers', -> - scope.new_supplier_id = 'new supplier id' - scope.addSupplier(event) - expect(event.preventDefault).toHaveBeenCalled() - expect(OrderCycle.addSupplier).toHaveBeenCalledWith('new supplier id') - - it 'Adds order cycle distributors', -> - scope.new_distributor_id = 'new distributor id' - scope.addDistributor(event) - expect(event.preventDefault).toHaveBeenCalled() - expect(OrderCycle.addDistributor).toHaveBeenCalledWith('new distributor id') - it 'Removes distribution of a variant', -> scope.removeDistributionOfVariant('variant') expect(OrderCycle.removeDistributionOfVariant).toHaveBeenCalledWith('variant') diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/outgoing_controller_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/outgoing_controller_spec.js.coffee index 1b206bb683..5268a9e8f4 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/outgoing_controller_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/outgoing_controller_spec.js.coffee @@ -13,19 +13,25 @@ describe 'AdminOrderCycleOutgoingCtrl', -> location = absUrl: -> 'example.com/admin/order_cycles/27/edit' + event = + preventDefault: jasmine.createSpy('preventDefault') OrderCycle = - productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').and.returnValue('product supplied') variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').and.returnValue('variant supplied') + addDistributor: jasmine.createSpy('addDistributor') ocInstance = {} module('admin.orderCycles') inject ($controller) -> ctrl = $controller 'AdminOrderCycleOutgoingCtrl', {$scope: scope, $location: location, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee, ocInstance: ocInstance} - it 'Delegates productSuppliedToOrderCycle to OrderCycle', -> - expect(scope.productSuppliedToOrderCycle('product')).toEqual('product supplied') - expect(OrderCycle.productSuppliedToOrderCycle).toHaveBeenCalledWith('product') - it 'Delegates variantSuppliedToOrderCycle to OrderCycle', -> expect(scope.variantSuppliedToOrderCycle('variant')).toEqual('variant supplied') expect(OrderCycle.variantSuppliedToOrderCycle).toHaveBeenCalledWith('variant') + + it 'Adds order cycle distributors', -> + scope.new_distributor_id = 'new distributor id' + + scope.addDistributor(event) + + expect(event.preventDefault).toHaveBeenCalled() + expect(OrderCycle.addDistributor).toHaveBeenCalledWith('new distributor id') diff --git a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create_spec.js.coffee index 70508bc519..0461f921d8 100644 --- a/spec/javascripts/unit/admin/order_cycles/controllers/simple_create_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/controllers/simple_create_spec.js.coffee @@ -1,8 +1,9 @@ describe "AdminSimpleCreateOrderCycleCtrl", -> ctrl = null - scope = {} + scope = null OrderCycle = {} Enterprise = {} + ExchangeProduct = {} EnterpriseFee = {} incoming_exchange = {} outgoing_exchange = {} @@ -10,27 +11,32 @@ describe "AdminSimpleCreateOrderCycleCtrl", -> beforeEach -> scope = $watch: jasmine.createSpy('$watch') + setOutgoingExchange: jasmine.createSpy('setOutgoingExchange') + loadExchangeProducts: jasmine.createSpy('loadExchangeProducts') + storeProductsAndSelectAllVariants: jasmine.createSpy('storeProductsAndSelectAllVariants') order_cycle = coordinator_id: 123 incoming_exchanges: [incoming_exchange] outgoing_exchanges: [outgoing_exchange] OrderCycle = order_cycle: order_cycle - addSupplier: jasmine.createSpy() - addDistributor: jasmine.createSpy() - setExchangeVariants: jasmine.createSpy() + addSupplier: jasmine.createSpy('addSupplier') + addDistributor: jasmine.createSpy('addDistributor') + setExchangeVariants: jasmine.createSpy('setExchangeVariants') new: jasmine.createSpy().and.returnValue order_cycle Enterprise = get: jasmine.createSpy().and.returnValue {id: 123} index: jasmine.createSpy() suppliedVariants: jasmine.createSpy().and.returnValue('supplied variants') + ExchangeProduct = + index: jasmine.createSpy() EnterpriseFee = index: jasmine.createSpy() ocInstance = {} module('admin.orderCycles') inject ($controller) -> - ctrl = $controller 'AdminSimpleCreateOrderCycleCtrl', {$scope: scope, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee, ocInstance: ocInstance} + ctrl = $controller 'AdminSimpleCreateOrderCycleCtrl', {$scope: scope, OrderCycle: OrderCycle, Enterprise: Enterprise, EnterpriseFee: EnterpriseFee, ExchangeProduct: ExchangeProduct, ocInstance: ocInstance} describe "initialisation", -> enterprise = {id: 123} @@ -39,15 +45,26 @@ describe "AdminSimpleCreateOrderCycleCtrl", -> beforeEach -> scope.init(enterprises) - it "sets up an incoming and outgoing exchange", -> - expect(OrderCycle.addSupplier).toHaveBeenCalledWith(enterprise.id) - expect(OrderCycle.addDistributor).toHaveBeenCalledWith(enterprise.id) - expect(scope.outgoing_exchange).toEqual outgoing_exchange + it "adds enterprise as both the supplier and the distributor", -> + expect(OrderCycle.addSupplier).toHaveBeenCalledWith(enterprise.id, scope.loadExchangeProducts) + expect(OrderCycle.addDistributor).toHaveBeenCalledWith(enterprise.id, scope.setOutgoingExchange) + + it "loads exchange products", -> + incoming_exchange.enterprise_id = enterprise.id + + scope.loadExchangeProducts() + + expect(ExchangeProduct.index).toHaveBeenCalledWith({ enterprise_id: enterprise.id, incoming: true }, scope.storeProductsAndSelectAllVariants) + + it "stores products and selects all variants", -> + scope.incoming_exchange = incoming_exchange + incoming_exchange.enterprise_id = enterprise.id + scope.enterprises = { "#{enterprise.id}": {}} + + scope.storeProductsAndSelectAllVariants() - it "selects all variants", -> expect(Enterprise.suppliedVariants). toHaveBeenCalledWith(enterprise.id) - expect(OrderCycle.setExchangeVariants). toHaveBeenCalledWith(incoming_exchange, 'supplied variants', true) diff --git a/spec/javascripts/unit/admin/order_cycles/services/enterprise_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/services/enterprise_spec.js.coffee index b3a7d122de..959a9483bb 100644 --- a/spec/javascripts/unit/admin/order_cycles/services/enterprise_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/services/enterprise_spec.js.coffee @@ -8,18 +8,18 @@ describe 'Enterprise service', -> Enterprise = $injector.get('Enterprise') $httpBackend = _$httpBackend_ $httpBackend.whenGET('/admin/enterprises/for_order_cycle.json').respond [ - {id: 1, name: 'One', supplied_products: [1, 2], is_primary_producer: true} - {id: 2, name: 'Two', supplied_products: [3, 4]} - {id: 3, name: 'Three', supplied_products: [5, 6], sells: 'any'} + {id: 1, name: 'One', is_primary_producer: true} + {id: 2, name: 'Two'} + {id: 3, name: 'Three', sells: 'any'} ] it 'loads enterprises as a hash', -> enterprises = Enterprise.index() $httpBackend.flush() expect(enterprises).toEqual - 1: new Enterprise.Enterprise({id: 1, name: 'One', supplied_products: [1, 2], is_primary_producer: true}) - 2: new Enterprise.Enterprise({id: 2, name: 'Two', supplied_products: [3, 4]}) - 3: new Enterprise.Enterprise({id: 3, name: 'Three', supplied_products: [5, 6], sells: 'any'}) + 1: new Enterprise.Enterprise({id: 1, name: 'One', is_primary_producer: true}) + 2: new Enterprise.Enterprise({id: 2, name: 'Two'}) + 3: new Enterprise.Enterprise({id: 3, name: 'Three', sells: 'any'}) it 'reports its loadedness', -> expect(Enterprise.loaded).toBe(false) @@ -30,22 +30,18 @@ describe 'Enterprise service', -> it 'loads producers as an array', -> Enterprise.index() $httpBackend.flush() - expect(Enterprise.producer_enterprises).toEqual [new Enterprise.Enterprise({id: 1, name: 'One', supplied_products: [1, 2], is_primary_producer: true})] + expect(Enterprise.producer_enterprises).toEqual [new Enterprise.Enterprise({id: 1, name: 'One', is_primary_producer: true})] it 'loads hubs as an array', -> Enterprise.index() $httpBackend.flush() - expect(Enterprise.hub_enterprises).toEqual [new Enterprise.Enterprise({id: 3, name: 'Three', supplied_products: [5, 6], sells: 'any'})] - - it 'collates all supplied products', -> - enterprises = Enterprise.index() - $httpBackend.flush() - expect(Enterprise.supplied_products).toEqual [1, 2, 3, 4, 5, 6] + expect(Enterprise.hub_enterprises).toEqual [new Enterprise.Enterprise({id: 3, name: 'Three', sells: 'any'})] it "finds supplied variants for an enterprise", -> spyOn(Enterprise, 'variantsOf').and.returnValue(10) - Enterprise.index() + enterprises = Enterprise.index() $httpBackend.flush() + Enterprise.enterprises[1].supplied_products = [1, 2] expect(Enterprise.suppliedVariants(1)).toEqual [10, 10] describe "finding the variants of a product", -> @@ -60,16 +56,3 @@ describe 'Enterprise service', -> master_id: 1 variants: [{id: 2}, {id: 3}] expect(Enterprise.variantsOf(p)).toEqual [2, 3] - - it 'counts total variants supplied by an enterprise', -> - enterprise = - supplied_products: [ - {variants: []}, - {variants: []}, - {variants: [{}, {}, {}]} - ] - - expect(Enterprise.totalVariants(enterprise)).toEqual(5) - - it 'returns zero when enterprise is null', -> - expect(Enterprise.totalVariants(null)).toEqual(0) diff --git a/spec/javascripts/unit/admin/order_cycles/services/order_cycle_spec.js.coffee b/spec/javascripts/unit/admin/order_cycles/services/order_cycle_spec.js.coffee index 4570f8d545..a078ec2bf4 100644 --- a/spec/javascripts/unit/admin/order_cycles/services/order_cycle_spec.js.coffee +++ b/spec/javascripts/unit/admin/order_cycles/services/order_cycle_spec.js.coffee @@ -232,42 +232,6 @@ describe 'OrderCycle service', -> ] expect(OrderCycle.incomingExchangesVariants()).toEqual [1, 4, 5] - describe 'checking whether a product is supplied to the order cycle', -> - product_master_present = product_variant_present = product_master_absent = product_variant_absent = null - - beforeEach -> - product_master_present = - name: "Linseed (500g)" - master_id: 1 - variants: [] - product_variant_present = - name: "Linseed (500g)" - master_id: 2 - variants: [{id: 3}, {id: 4}] - product_master_absent = - name: "Linseed (500g)" - master_id: 5 - variants: [] - product_variant_absent = - name: "Linseed (500g)" - master_id: 6 - variants: [{id: 7}, {id: 8}] - - spyOn(OrderCycle, 'incomingExchangesVariants').and.returnValue([1, 3]) - - it 'returns true for products whose master is supplied', -> - expect(OrderCycle.productSuppliedToOrderCycle(product_master_present)).toBeTruthy() - - it 'returns true for products for whom a variant is supplied', -> - expect(OrderCycle.productSuppliedToOrderCycle(product_variant_present)).toBeTruthy() - - it 'returns false for products whose master is not supplied', -> - expect(OrderCycle.productSuppliedToOrderCycle(product_master_absent)).toBeFalsy() - - it 'returns false for products whose variants are not supplied', -> - expect(OrderCycle.productSuppliedToOrderCycle(product_variant_absent)).toBeFalsy() - - describe 'checking whether a variant is supplied to the order cycle', -> beforeEach -> spyOn(OrderCycle, 'incomingExchangesVariants').and.returnValue([1, 3]) diff --git a/spec/serializers/api/admin/for_order_cycle/enterprise_serializer_spec.rb b/spec/serializers/api/admin/for_order_cycle/enterprise_serializer_spec.rb deleted file mode 100644 index f33f33d0a5..0000000000 --- a/spec/serializers/api/admin/for_order_cycle/enterprise_serializer_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require "spec_helper" - -describe Api::Admin::ForOrderCycle::EnterpriseSerializer do - let(:coordinator) { create(:distributor_enterprise) } - let(:order_cycle) { double(:order_cycle, coordinator: coordinator) } - let(:enterprise) { create(:distributor_enterprise) } - - let(:non_inventory_product) { create(:simple_product, supplier: enterprise) } - let!(:non_inventory_variant) { non_inventory_product.variants.first } - - let(:inventory_product) { create(:simple_product, supplier: enterprise) } - let(:inventory_variant) { inventory_product.variants.first } - - let(:deleted_product) { create(:product, supplier: enterprise, deleted_at: 24.hours.ago ) } - let(:deleted_variant) { deleted_product.variants.first } - - let(:serialized_enterprise) do - Api::Admin::ForOrderCycle::EnterpriseSerializer.new( - enterprise.reload, # load the products that were created after the enterprise - order_cycle: order_cycle, - spree_current_user: enterprise.owner - ).to_json - end - - let!(:inventory_item1) { create(:inventory_item, enterprise: coordinator, variant: inventory_variant, visible: true) } - let!(:inventory_item2) { create(:inventory_item, enterprise: coordinator, variant: deleted_variant, visible: true) } - - context "when order cycle shows only variants in the coordinator's inventory" do - before do - allow(order_cycle).to receive(:prefers_product_selection_from_coordinator_inventory_only?) { true } - end - - describe "supplied products" do - it "renders only non-deleted variants that are in the coordinators inventory" do - expect(serialized_enterprise).to have_json_size(1).at_path 'supplied_products' - expect(serialized_enterprise).to have_json_size(1).at_path 'supplied_products/0/variants' - expect(serialized_enterprise).to be_json_eql(inventory_variant.id).at_path 'supplied_products/0/variants/0/id' - end - end - end - - context "when order cycle shows all available products" do - before do - allow(order_cycle).to receive(:prefers_product_selection_from_coordinator_inventory_only?) { false } - end - - describe "supplied products" do - it "renders variants that are not in the coordinators inventory but not variants of deleted products" do - expect(serialized_enterprise).to have_json_size(2).at_path 'supplied_products' - expect(serialized_enterprise).to have_json_size(1).at_path 'supplied_products/0/variants' - expect(serialized_enterprise).to have_json_size(1).at_path 'supplied_products/1/variants' - variant_ids = parse_json(serialized_enterprise)['supplied_products'].map{ |p| p['variants'].first['id'] } - expect(variant_ids).to include non_inventory_variant.id, inventory_variant.id - end - end - end -end