diff --git a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee index 6c4ec5d2cb..3b6e56f679 100644 --- a/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/checkout_controller.js.coffee @@ -2,6 +2,7 @@ angular.module("Checkout").controller "CheckoutCtrl", ($scope, $rootScope) -> $scope.require_ship_address = false $scope.shipping_method = -1 $scope.payment_method = -1 + $scope.same_as_billing = true $scope.shippingMethodChanged = -> $scope.require_ship_address = $("#order_shipping_method_id_" + $scope.shipping_method).attr("data-require-ship-address") diff --git a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee index f1fdb224b0..784aed539c 100644 --- a/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee +++ b/app/assets/javascripts/darkswarm/controllers/products_controller.js.coffee @@ -2,3 +2,10 @@ angular.module("Shop").controller "ProductsCtrl", ($scope, $rootScope, Product, $scope.data = Product.data $scope.order_cycle = OrderCycle.order_cycle Product.update() + + $scope.productPrice = (product) -> + if product.variants.length > 0 + prices = (v.price for v in product.variants) + Math.min.apply(null, prices) + else + product.price diff --git a/app/controllers/shop/shop_controller.rb b/app/controllers/shop/shop_controller.rb index e0862d205e..b7f124acfa 100644 --- a/app/controllers/shop/shop_controller.rb +++ b/app/controllers/shop/shop_controller.rb @@ -11,10 +11,11 @@ class Shop::ShopController < BaseController def products unless @products = current_order_cycle.andand - .products_distributed_by(@distributor).andand - .select(&:has_stock?).andand + .products_distributed_by(current_distributor).andand + .select { |p| p.has_stock_for_distribution?(current_order_cycle, current_distributor) }.andand .sort_by {|p| p.name } - render json: "", status: 404 + + render json: "", status: 404 end end diff --git a/app/models/order_cycle.rb b/app/models/order_cycle.rb index 519e693b1e..1f98e8f7c4 100644 --- a/app/models/order_cycle.rb +++ b/app/models/order_cycle.rb @@ -94,8 +94,11 @@ class OrderCycle < ActiveRecord::Base end def variants_distributed_by(distributor) - self.exchanges.where(:sender_id => self.coordinator, :receiver_id => distributor). - map(&:variants).flatten.uniq + Spree::Variant. + joins(:exchanges). + merge(Exchange.outgoing). + where('exchanges.order_cycle_id = ?', self). + where('exchanges.receiver_id = ?', distributor) end def products_distributed_by(distributor) @@ -145,6 +148,11 @@ class OrderCycle < ActiveRecord::Base # -- Fees + + # TODO: The boundary of this class is ill-defined here. OrderCycle should not know about + # EnterpriseFeeApplicator. Clients should be able to query it for relevant EnterpriseFees. + # This logic would fit better in another service object. + def fees_for(variant, distributor) per_item_enterprise_fee_applicators_for(variant, distributor).sum do |applicator| # Spree's Calculator interface accepts Orders or LineItems, diff --git a/app/models/spree/ability_decorator.rb b/app/models/spree/ability_decorator.rb index c6407c1f98..668791f555 100644 --- a/app/models/spree/ability_decorator.rb +++ b/app/models/spree/ability_decorator.rb @@ -15,7 +15,7 @@ class AbilityDecorator end can [:admin, :index, :read, :create, :edit, :update, :search, :destroy], Spree::Variant - can [:admin, :index, :read, :create, :edit], Spree::ProductProperty + can [:admin, :index, :read, :create, :edit, :destroy], Spree::ProductProperty can [:admin, :index, :read, :create, :edit], Spree::Image can [:admin, :index, :read, :search], Spree::Taxon diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 33ccf73a81..7682720708 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -99,12 +99,27 @@ Spree::Product.class_eval do def product_distribution_for(distributor) self.product_distributions.find_by_distributor_id(distributor) end - + + def variants_for(order_cycle, distributor) + self.variants.where('spree_variants.id IN (?)', order_cycle.variants_distributed_by(distributor)) + end + # overriding to check self.on_demand as well def has_stock? has_variants? ? variants.any?(&:in_stock?) : (on_demand || master.in_stock?) end + def has_stock_for_distribution?(order_cycle, distributor) + # This product has stock for a distribution if it is available on-demand + # or if one of its variants in the distribution is in stock + (!has_variants? && on_demand) || + variants_distributed_by(order_cycle, distributor).any? { |v| v.in_stock? } + end + + def variants_distributed_by(order_cycle, distributor) + order_cycle.variants_distributed_by(distributor).where(product_id: self) + end + # Build a product distribution for each distributor def build_product_distributions_for_user user Enterprise.is_distributor.managed_by(user).each do |distributor| diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index 12a4ca1a0e..923dce4e11 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -1,4 +1,7 @@ Spree::Variant.class_eval do + has_many :exchange_variants + has_many :exchanges, through: :exchange_variants + attr_accessible :unit_value, :unit_description validates_presence_of :unit_value, @@ -11,6 +14,8 @@ Spree::Variant.class_eval do after_save :update_units + scope :in_stock, where('spree_variants.count_on_hand > 0 OR spree_variants.on_demand=?', true) + def price_with_fees(distributor, order_cycle) price + fees_for(distributor, order_cycle) diff --git a/app/overrides/spree/admin/shipping_methods/_form/add_require_ship_address.html.haml.deface b/app/overrides/spree/admin/shipping_methods/_form/add_require_ship_address.html.haml.deface new file mode 100644 index 0000000000..b7bffd4c84 --- /dev/null +++ b/app/overrides/spree/admin/shipping_methods/_form/add_require_ship_address.html.haml.deface @@ -0,0 +1,5 @@ +/ insert_bottom "[data-hook='admin_shipping_method_form_availability_fields'] > fieldset" + += f.field_container :shipping_requirements do + = f.label :require_ship_address, "Requires shipping address?" + = f.check_box :require_ship_address diff --git a/app/views/shop/checkout/_form.html.haml b/app/views/shop/checkout/_form.html.haml index 2add9a5c39..5b0d377071 100644 --- a/app/views/shop/checkout/_form.html.haml +++ b/app/views/shop/checkout/_form.html.haml @@ -21,20 +21,27 @@ = f.fields_for :bill_address do |ba| .row .large-12.columns - = ba.text_field :address1, label: "Billing Address" + = ba.text_field :address1, label: "Billing Address", + "ng-model" => "billing_address1" .row .large-12.columns - = ba.text_field :address2 + = ba.text_field :address2, + "ng-model" => "billing_address2" .row .large-6.columns - = ba.text_field :city + = ba.text_field :city, + "ng-model" => "billing_city" + .large-6.columns - = ba.select :country_id, Spree::Country.order(:name).select([:id, :name]).map{|c| [c.name, c.id]} + = ba.select :country_id, Spree::Country.order(:name).select([:id, :name]).map{|c| [c.name, c.id]}, + "ng-model" => "billing_country_id" .row .large-6.columns - = ba.select :state_id, Spree::State.order(:name).select([:id, :name]).map{|c| [c.name, c.id]} + = ba.select :state_id, Spree::State.order(:name).select([:id, :name]).map{|c| [c.name, c.id]}, + "ng-model" => "billing_state_id" .large-6.columns.right - = ba.text_field :zipcode + = ba.text_field :zipcode, + "ng-model" => "billing_zipcode" %fieldset#shipping @@ -52,26 +59,42 @@ = f.fields_for :ship_address do |sa| #ship_address{"ng-show" => "require_ship_address"} - .row - .large-12.columns - = sa.text_field :address1 - .row - .large-12.columns - = sa.text_field :address2 + %label + = check_box_tag :same_as_billing, true, true, + "ng-model" => "same_as_billing" + Shipping address same as billing address? + + %div.visible{"ng-show" => "!same_as_billing"} + .row + .large-12.columns + = sa.text_field :address1 + .row + .large-12.columns + = sa.text_field :address2 + + .row + .large-6.columns + = sa.text_field :city + .large-6.columns + = sa.select :country_id, Spree::Country.order(:name).select([:id, :name]).map{|c| [c.name, c.id]} + .row + .large-6.columns.right + = sa.text_field :zipcode + .row + .large-6.columns + = sa.text_field :firstname + .large-6.columns + = sa.text_field :lastname + + #ship_address_hidden{"ng-show" => "same_as_billing"} + = sa.hidden_field :address1, "ng-value" => "billing_address1" + = sa.hidden_field :address2, "ng-value" => "billing_address2" + = sa.hidden_field :city, "ng-value" => "billing_city" + = sa.hidden_field :country_id, "ng-value" => "billing_country_id" + = sa.hidden_field :zipcode, "ng-value" => "billing_zipcode" + = sa.hidden_field :firstname, "ng-value" => "billing_firstname" + = sa.hidden_field :lastname, "ng-value" => "billing_lastname" - .row - .large-6.columns - = sa.text_field :city - .large-6.columns - = sa.select :country_id, Spree::Country.order(:name).select([:id, :name]).map{|c| [c.name, c.id]} - .row - .large-6.columns.right - = sa.text_field :zipcode - .row - .large-6.columns - = sa.text_field :firstname - .large-6.columns - = sa.text_field :lastname %fieldset#payment %legend Payment Details - current_order.available_payment_methods.each do |method| diff --git a/app/views/shop/shop/_products.html.haml b/app/views/shop/shop/_products.html.haml index 3456ec29e7..434ba9718c 100644 --- a/app/views/shop/shop/_products.html.haml +++ b/app/views/shop/shop/_products.html.haml @@ -12,7 +12,7 @@ %th.bulk Bulk %th.price.text-right Price %tbody{"ng-repeat" => "product in data.products | filter:query"} - %tr.product + %tr{"class" => "product product-{{ product.id }}"} %td.name %img{"ng-src" => "{{ product.master.images[0].small_url }}"} %div @@ -46,9 +46,9 @@ name: "variant_attributes[{{product.master.id}}][max_quantity]"} %td.price.text-right %small{"ng-show" => "(product.variants.length > 0)"} from - {{ product.price | currency }} + {{ productPrice(product) | currency }} %tr.product-description %td{colspan: 2}{{ product.notes | truncate:80 }} - %tr{"ng-repeat" => "variant in product.variants", "ng-show" => "product.show_variants"} + %tr.variant{"ng-repeat" => "variant in product.variants", "ng-show" => "product.show_variants"} = render partial: "shop/shop/variant" %input.button.right{type: :submit, value: "Check Out"} diff --git a/app/views/shop/shop/products.rabl b/app/views/shop/shop/products.rabl index 7d5f1711fe..662196953e 100644 --- a/app/views/shop/shop/products.rabl +++ b/app/views/shop/shop/products.rabl @@ -22,19 +22,17 @@ child :master => :master do end end -child :variants => :variants do |variant| - attributes :id, :is_master, :count_on_hand, :options_text, :count_on_hand, :on_demand, :group_buy - node do |variant| - { - price: variant.price_with_fees(current_distributor, current_order_cycle) +node :variants do |product| + product.variants_for(current_order_cycle, current_distributor).in_stock.map do |v| + {id: v.id, + is_master: v.is_master, + count_on_hand: v.count_on_hand, + options_text: v.options_text, + on_demand: v.on_demand, + price: v.price_with_fees(current_distributor, current_order_cycle), + images: v.images.map { |i| {id: i.id, alt: i.alt, small_url: i.attachment.url(:small, false)} } } end - child :images => :images do - attributes :id, :alt - node do |img| - {:small_url => img.attachment.url(:small, false)} - end - end end child :taxons => :taxons do |taxon| diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index 28cefe564f..faac29fa79 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -122,7 +122,7 @@ %input{ 'ng-model' => 'product.master.unit_value_with_description', :name => 'master_unit_value_with_description', 'ofn-track-product' => 'master.unit_value_with_description', :type => 'text', :placeholder => 'value', 'ng-show' => "!hasVariants(product) && hasUnit(product)" } %input{ 'ng-model' => 'product.variant_unit_name', :name => 'variant_unit_name', 'ofn-track-product' => 'variant_unit_name', :placeholder => 'unit', 'ng-show' => "product.variant_unit_with_scale == 'items'", :type => 'text' } %td{ 'ng-show' => 'columns.price.visible' } - %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text' } + %input{ 'ng-model' => 'product.price', 'ofn-decimal' => :true, :name => 'price', 'ofn-track-product' => 'price', :type => 'text', 'ng-hide' => 'hasVariants(product)' } %td{ 'ng-show' => 'columns.on_hand.visible' } %span{ 'ng-bind' => 'product.on_hand', :name => 'on_hand', 'ng-show' => '!hasOnDemandVariants(product) && (hasVariants(product) || product.on_demand)' } %input.field{ 'ng-model' => 'product.on_hand', :name => 'on_hand', 'ofn-track-product' => 'on_hand', 'ng-hide' => 'hasVariants(product) || product.on_demand', :type => 'number' } diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 4644aae8c1..336792a3ae 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -85,18 +85,21 @@ feature %q{ page.should have_field "available_on", with: p2.available_on.strftime("%F %T") end - it "displays a price input for each product (ie. for master variant)" do + it "displays a price input for each product without variants (ie. for master variant)" do p1 = FactoryGirl.create(:product) p2 = FactoryGirl.create(:product) - p1.price = 22.00 - p2.price = 44.00 - p1.save! - p2.save! + p3 = FactoryGirl.create(:product) + v = FactoryGirl.create(:variant, product: p3) + + p1.update_attribute :price, 22.0 + p2.update_attribute :price, 44.0 + p3.update_attribute :price, 66.0 visit '/admin/products/bulk_edit' page.should have_field "price", with: "22.0" page.should have_field "price", with: "44.0" + page.should_not have_field "price", with: "66.0", visible: true end it "displays an on hand count input for each product (ie. for master variant) if no regular variants exist" do diff --git a/spec/features/admin/shipping_methods_spec.rb b/spec/features/admin/shipping_methods_spec.rb index 575e2b7da4..ab7940acc2 100644 --- a/spec/features/admin/shipping_methods_spec.rb +++ b/spec/features/admin/shipping_methods_spec.rb @@ -70,6 +70,14 @@ feature 'shipping methods' do login_to_admin_as enterprise_user end + it "lets me choose whether a shipping address is required" do + click_link "Enterprises" + within(".enterprise-#{distributor1.id}") { click_link 'Shipping Methods' } + click_link 'New Shipping Method' + + page.should have_content "Requires shipping address?" + end + it "creates shipping methods" do click_link 'Enterprises' within(".enterprise-#{distributor1.id}") { click_link 'Shipping Methods' } diff --git a/spec/features/admin/variants_spec.rb b/spec/features/admin/variants_spec.rb index d05aa75ef8..51aefd9e69 100644 --- a/spec/features/admin/variants_spec.rb +++ b/spec/features/admin/variants_spec.rb @@ -83,4 +83,34 @@ feature %q{ page.should_not have_field "variant_unit_value" page.should_not have_field "variant_unit_description" end + + + context "as an enterprise user" do + before(:each) do + @new_user = create_enterprise_user + @supplier = create(:supplier_enterprise) + @new_user.enterprise_roles.build(enterprise: @supplier).save + + login_to_admin_as @new_user + end + + scenario "deleting product properties", js: true do + # Given a product with a property + p = create(:simple_product, supplier: @supplier) + p.set_property('fooprop', 'fooval') + + # When I navigate to the product properties page + visit spree.admin_product_product_properties_path(p) + page.should have_field 'product_product_properties_attributes_0_property_name', with: 'fooprop', visible: true + page.should have_field 'product_product_properties_attributes_0_value', with: 'fooval', visible: true + + # And I delete the property + page.all('a.remove_fields').first.click + wait_until { p.reload.property('fooprop').nil? } + + # Then the property should have been deleted + page.should_not have_field 'product_product_properties_attributes_0_property_name', with: 'fooprop', visible: true + page.should_not have_field 'product_product_properties_attributes_0_value', with: 'fooval', visible: true + end + end end diff --git a/spec/features/consumer/shopping/checkout_spec.rb b/spec/features/consumer/shopping/checkout_spec.rb index 4e08b9d5e4..8393543031 100644 --- a/spec/features/consumer/shopping/checkout_spec.rb +++ b/spec/features/consumer/shopping/checkout_spec.rb @@ -133,15 +133,35 @@ feature "As a consumer I want to check out my cart", js: true do page.should have_content "Donkeys" end - it "doesn't show ship address forms " do + it "doesn't show ship address forms when a shipping method wants no address" do choose(sm2.name) find("#ship_address").visible?.should be_false end - it "shows ship address forms when selected shipping method requires one" do + context "When shipping method requires an address" do + before do + choose(sm1.name) + end + it "shows the hidden ship address fields by default" do + check "Shipping address same as billing address?" + find("#ship_address_hidden").visible?.should be_true + find("#ship_address > div.visible").visible?.should be_false + end + + it "shows ship address forms when 'same as billing address' is unchecked" do + uncheck "Shipping address same as billing address?" + find("#ship_address_hidden").visible?.should be_false + find("#ship_address > div.visible").visible?.should be_true + end + end + + it "copies billing address to hidden shipping address fields" do choose(sm1.name) - save_and_open_page - find("#ship_address").visible?.should be_true + check "Shipping address same as billing address?" + fill_in "Billing Address", with: "testy" + within "#ship_address_hidden" do + find("#order_ship_address_attributes_address1").value.should == "testy" + end end describe "with payment methods" do @@ -204,7 +224,6 @@ def select_order_cycle end def add_product_to_cart - - fill_in "variants[#{product.master.id}]", with: 5 + fill_in "variants[#{product.master.id}]", with: product.master.on_hand - 1 first("form.custom > input.button.right").click end diff --git a/spec/features/consumer/shopping/shopping_spec.rb b/spec/features/consumer/shopping/shopping_spec.rb index b7e085db10..4476f69571 100644 --- a/spec/features/consumer/shopping/shopping_spec.rb +++ b/spec/features/consumer/shopping/shopping_spec.rb @@ -12,6 +12,7 @@ feature "As a consumer I want to shop with a distributor", js: true do visit "/" click_link distributor.name end + it "shows a distributor" do visit shop_path page.should have_text distributor.name @@ -23,7 +24,7 @@ feature "As a consumer I want to shop with a distributor", js: true do first("#about img")['src'].should == distributor.promo_image.url(:large) end - describe "With products in order cycles" do + describe "with products in order cycles" do let(:supplier) { create(:supplier_enterprise) } let(:product) { create(:product, supplier: supplier) } let(:order_cycle) { create(:order_cycle, distributors: [distributor], coordinator: create(:distributor_enterprise)) } @@ -112,16 +113,18 @@ feature "As a consumer I want to shop with a distributor", js: true do end end - describe "After selecting an order cycle with products visible" do + describe "after selecting an order cycle with products visible" do let(:oc) { create(:simple_order_cycle, distributors: [distributor]) } - let(:product) { create(:simple_product) } - let(:variant) { create(:variant, product: product) } + let(:product) { create(:simple_product, price: 10) } + let(:variant1) { create(:variant, product: product, price: 20) } + let(:variant2) { create(:variant, product: product, price: 30) } let(:exchange) { Exchange.find(oc.exchanges.to_enterprises(distributor).outgoing.first.id) } before do exchange.update_attribute :pickup_time, "frogs" exchange.variants << product.master - exchange.variants << variant + exchange.variants << variant1 + exchange.variants << variant2 visit shop_path select "frogs", :from => "order_cycle_id" exchange @@ -132,14 +135,14 @@ feature "As a consumer I want to shop with a distributor", js: true do end it "collapses variants by default" do - page.should_not have_text variant.options_text + page.should_not have_text variant1.options_text end it "expands variants" do find(".expand").trigger "click" - page.should have_text variant.options_text + page.should have_text variant1.options_text find(".collapse").trigger "click" - page.should_not have_text variant.options_text + page.should_not have_text variant1.options_text end it "uses the adjusted price" do @@ -150,17 +153,35 @@ feature "As a consumer I want to shop with a distributor", js: true do visit shop_path select "frogs", :from => "order_cycle_id" - page.should have_content "$#{(product.price + 23.00)}" + + # All prices are as above plus $23 in fees + + # Page should not have product.price (with or without fee) + page.should_not have_selector 'tr.product > td', text: "from $10.00" + page.should_not have_selector 'tr.product > td', text: "from $33.00" + + # Page should have variant prices (with fee) + find(".expand").trigger 'click' + page.should have_selector 'tr.variant > td.price', text: "$43.00" + page.should have_selector 'tr.variant > td.price', text: "$53.00" + + # Product price should be listed as the lesser of these + page.should have_selector 'tr.product > td', text: "from $43.00" end end - describe "Filtering on hand and on demand products" do + describe "filtering on hand and on demand products" do let(:oc) { create(:simple_order_cycle, distributors: [distributor]) } let(:p1) { create(:simple_product, on_demand: false) } let(:p2) { create(:simple_product, on_demand: true) } let(:p3) { create(:simple_product, on_demand: false) } let(:p4) { create(:simple_product, on_demand: false) } - let(:v1) { create(:variant, product: p4) } + let(:p5) { create(:simple_product, on_demand: false) } + let(:v1) { create(:variant, product: p4, unit_value: 2) } + let(:v2) { create(:variant, product: p4, unit_value: 3, on_demand: false) } + let(:v3) { create(:variant, product: p4, unit_value: 4, on_demand: true) } + let(:v4) { create(:variant, product: p5) } + let(:v5) { create(:variant, product: p5) } before do p1.master.count_on_hand = 1 @@ -169,26 +190,47 @@ feature "As a consumer I want to shop with a distributor", js: true do p2.master.update_attribute(:count_on_hand, 0) p3.master.update_attribute(:count_on_hand, 0) v1.update_attribute(:count_on_hand, 1) + v2.update_attribute(:count_on_hand, 0) + v3.update_attribute(:count_on_hand, 0) + v4.update_attribute(:count_on_hand, 1) + v5.update_attribute(:count_on_hand, 0) exchange = Exchange.find(oc.exchanges.to_enterprises(distributor).outgoing.first.id) exchange.update_attribute :pickup_time, "frogs" exchange.variants << p1.master exchange.variants << p2.master exchange.variants << p3.master - exchange.variants << v1 + exchange.variants << v1 + exchange.variants << v2 + exchange.variants << v3 + # v4 is in stock but not in distribution + # v5 is out of stock and in the distribution + # Neither should display, nor should their product, p5 + exchange.variants << v5 visit shop_path select "frogs", :from => "order_cycle_id" exchange end - it "shows on hand products" do + it "filters products based on availability" do + # It shows on hand products page.should have_content p1.name page.should have_content p4.name - end - it "shows on demand products" do + + # It shows on demand products page.should have_content p2.name - end - it "does not show products that are neither on hand or on demand" do + + # It does not show products that are neither on hand or on demand page.should_not have_content p3.name + + # It shows on demand variants + within(".product-#{p4.id}") { find(".expand", visible: true).trigger "click" } + page.should have_content v3.options_text + + # It does not show variants that are neither on hand or on demand + page.should_not have_content v2.options_text + + # It does not show products that have no available variants in this distribution + page.should_not have_content p5.name end end diff --git a/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee b/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee index 0ea7488225..f7717b6737 100644 --- a/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee +++ b/spec/javascripts/unit/darkswarm/controllers_spec.js.coffee @@ -3,7 +3,6 @@ describe 'All controllers', -> ctrl = null scope = null event = null - rootScope = null Product = null beforeEach -> @@ -12,29 +11,38 @@ describe 'All controllers', -> all: -> update: -> data: "testy mctest" + OrderCycle = + order_cycle: {} - inject ($controller, $rootScope) -> - rootScope = $rootScope - scope = $rootScope.$new() - ctrl = $controller 'ProductsCtrl', {$scope: scope, Product : Product} + inject ($controller) -> + scope = {} + ctrl = $controller 'ProductsCtrl', {$scope: scope, Product: Product, OrderCycle: OrderCycle} - it 'Fetches products from Product', -> + it 'fetches products from Product', -> expect(scope.data).toEqual 'testy mctest' + describe "determining the price to display for a product", -> + it "displays the product price when the product does not have variants", -> + product = {variants: [], price: 12.34} + expect(scope.productPrice(product)).toEqual 12.34 + + it "displays the minimum variant price when the product has variants", -> + product = + price: 11 + variants: [{price: 22}, {price: 33}] + expect(scope.productPrice(product)).toEqual 22 describe 'OrderCycleCtrl', -> ctrl = null scope = null event = null - rootScope = null product_ctrl = null OrderCycle = null beforeEach -> module 'Shop' scope = {} - inject ($controller, $rootScope) -> - rootScope = $rootScope - scope = $rootScope.$new() + inject ($controller) -> + scope = {} ctrl = $controller 'OrderCycleCtrl', {$scope: scope} diff --git a/spec/models/spree/ability_spec.rb b/spec/models/spree/ability_spec.rb index 35020aede5..193a6c0fd5 100644 --- a/spec/models/spree/ability_spec.rb +++ b/spec/models/spree/ability_spec.rb @@ -48,7 +48,7 @@ module Spree end it "should be able to read/write their enterprises' product properties" do - should have_ability([:admin, :index, :read, :create, :edit], for: Spree::ProductProperty) + should have_ability([:admin, :index, :read, :create, :edit, :destroy], for: Spree::ProductProperty) end it "should be able to read/write their enterprises' product images" do diff --git a/spec/models/spree/product_spec.rb b/spec/models/spree/product_spec.rb index 7e8f679f26..d42f811422 100644 --- a/spec/models/spree/product_spec.rb +++ b/spec/models/spree/product_spec.rb @@ -303,6 +303,42 @@ module Spree end end + describe "finding variants for an order cycle and hub" do + let(:oc) { create(:simple_order_cycle) } + let(:s) { create(:supplier_enterprise) } + let(:d1) { create(:distributor_enterprise) } + let(:d2) { create(:distributor_enterprise) } + + let(:p1) { create(:simple_product) } + let(:p2) { create(:simple_product) } + let(:v1) { create(:variant, product: p1) } + let(:v2) { create(:variant, product: p2) } + + let(:p_external) { create(:simple_product) } + let(:v_external) { create(:variant, product: p_external) } + + let!(:ex_in) { create(:exchange, order_cycle: oc, sender: s, receiver: oc.coordinator, + variants: [v1, v2]) } + let!(:ex_out1) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d1, + variants: [v1]) } + let!(:ex_out2) { create(:exchange, order_cycle: oc, sender: oc.coordinator, receiver: d2, + variants: [v2]) } + + it "returns variants in the order cycle and distributor" do + p1.variants_for(oc, d1).should == [v1] + p2.variants_for(oc, d2).should == [v2] + end + + it "does not return variants in the order cycle but not the distributor" do + p1.variants_for(oc, d2).should be_empty + p2.variants_for(oc, d1).should be_empty + end + + it "does not return variants not in the order cycle" do + p_external.variants_for(oc, d1).should be_empty + end + end + describe "variant units" do context "when the product initially has no variant unit" do let!(:p) { create(:simple_product, @@ -441,12 +477,65 @@ module Spree end end - describe "Stock filtering" do + describe "stock filtering" do it "considers products that are on_demand as being in stock" do product = create(:simple_product, on_demand: true) product.master.update_attribute(:count_on_hand, 0) product.has_stock?.should == true end + + describe "finding products in stock for a particular distribution" do + it "returns in-stock products without variants" do + p = create(:simple_product) + p.master.update_attribute(:count_on_hand, 1) + d = create(:distributor_enterprise) + oc = create(:simple_order_cycle, distributors: [d]) + oc.exchanges.outgoing.first.variants << p.master + + p.should have_stock_for_distribution(oc, d) + end + + it "returns on-demand products" do + p = create(:simple_product, on_demand: true) + p.master.update_attribute(:count_on_hand, 0) + d = create(:distributor_enterprise) + oc = create(:simple_order_cycle, distributors: [d]) + oc.exchanges.outgoing.first.variants << p.master + + p.should have_stock_for_distribution(oc, d) + end + + it "returns products with in-stock variants" do + p = create(:simple_product) + v = create(:variant, product: p) + v.update_attribute(:count_on_hand, 1) + d = create(:distributor_enterprise) + oc = create(:simple_order_cycle, distributors: [d]) + oc.exchanges.outgoing.first.variants << v + + p.should have_stock_for_distribution(oc, d) + end + + it "returns products with on-demand variants" do + p = create(:simple_product) + v = create(:variant, product: p, on_demand: true) + v.update_attribute(:count_on_hand, 0) + d = create(:distributor_enterprise) + oc = create(:simple_order_cycle, distributors: [d]) + oc.exchanges.outgoing.first.variants << v + + p.should have_stock_for_distribution(oc, d) + end + + it "does not return products that have stock not in the distribution" do + p = create(:simple_product) + p.master.update_attribute(:count_on_hand, 1) + d = create(:distributor_enterprise) + oc = create(:simple_order_cycle, distributors: [d]) + + p.should_not have_stock_for_distribution(oc, d) + end + end end end end diff --git a/spec/models/spree/variant_spec.rb b/spec/models/spree/variant_spec.rb index 2031a4d22b..fee4dc1edd 100644 --- a/spec/models/spree/variant_spec.rb +++ b/spec/models/spree/variant_spec.rb @@ -2,6 +2,25 @@ require 'spec_helper' module Spree describe Variant do + describe "scopes" do + describe "finding variants in stock" do + before do + p = create(:product) + @v_in_stock = create(:variant, product: p) + @v_on_demand = create(:variant, product: p, on_demand: true) + @v_no_stock = create(:variant, product: p) + + @v_in_stock.update_attribute(:count_on_hand, 1) + @v_on_demand.update_attribute(:count_on_hand, 0) + @v_no_stock.update_attribute(:count_on_hand, 0) + end + + it "returns variants in stock or on demand, but not those that are neither" do + Variant.where(is_master: false).in_stock.should == [@v_in_stock, @v_on_demand] + end + end + end + describe "calculating the price with enterprise fees" do it "returns the price plus the fees" do distributor = double(:distributor)