diff --git a/app/assets/javascripts/admin/enterprises/directives/invert_number.js.coffee b/app/assets/javascripts/admin/enterprises/directives/invert_number.js.coffee new file mode 100644 index 0000000000..0557d3ed22 --- /dev/null +++ b/app/assets/javascripts/admin/enterprises/directives/invert_number.js.coffee @@ -0,0 +1,11 @@ +angular.module("admin.enterprises").directive "invertNumber", -> + restrict: "A" + require: "ngModel" + link: (scope, element, attrs, ngModel) -> + ngModel.$parsers.push (viewValue) -> + return -parseInt(viewValue) unless isNaN(parseInt(viewValue)) + viewValue + + ngModel.$formatters.push (modelValue) -> + return -parseInt(modelValue) unless isNaN(parseInt(modelValue)) + modelValue diff --git a/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml b/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml index bc2ddc5c75..90886b2dab 100644 --- a/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml +++ b/app/assets/javascripts/templates/admin/tag_rules/discount_order.html.haml @@ -24,13 +24,16 @@ name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][id]", ng: { value: "rule.calculator.id" } } + %input{ type: "hidden", + name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][preferred_flat_percent]", + ng: { value: "rule.calculator.preferred_flat_percent" } } + %span.text-normal {{ $index + 1 }}. Apply a discount of %span.input-symbol.after %span.text-normal % %input.text-big{ type: "number", id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_preferred_flat_percent", - name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][preferred_flat_percent]", - min: 0, + min: -100, max: 100, - ng: { model: "rule.calculator.preferred_flat_percent" } } + ng: { model: "rule.calculator.preferred_flat_percent" }, 'invert-number' => true } %span.text-normal to order subtotals diff --git a/app/models/spree/order_decorator.rb b/app/models/spree/order_decorator.rb index 8f1c0381b1..5fc96c34c2 100644 --- a/app/models/spree/order_decorator.rb +++ b/app/models/spree/order_decorator.rb @@ -17,7 +17,8 @@ Spree::Order.class_eval do attr_accessible :order_cycle_id, :distributor_id before_validation :shipping_address_from_distributor - before_validation :associate_customer, unless: :customer_is_valid? + before_validation :associate_customer, unless: :customer_id? + before_validation :ensure_customer, unless: :customer_is_valid? checkout_flow do go_to_state :address @@ -179,6 +180,10 @@ Spree::Order.class_eval do if order_cycle OpenFoodNetwork::EnterpriseFeeCalculator.new.create_order_adjustments_for self end + + if distributor.present? && customer.present? + distributor.apply_tag_rules_to(self, customer: customer) + end end end @@ -289,14 +294,18 @@ Spree::Order.class_eval do customer.present? && customer.enterprise_id == distributor_id && customer.email == (user.andand.email || email) end + def email_for_customer + user.andand.email || email + end + def associate_customer - email_for_customer = user.andand.email || email - existing_customer = Customer.of(distributor).find_by_email(email_for_customer) - if existing_customer - self.customer = existing_customer - else - new_customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user) - self.customer = new_customer + return customer if customer.present? + self.customer = Customer.of(distributor).find_by_email(email_for_customer) + end + + def ensure_customer + unless associate_customer + self.customer = Customer.create(enterprise: distributor, email: email_for_customer, user: user) end end end diff --git a/app/models/tag_rule.rb b/app/models/tag_rule.rb index cf0a105e80..0237b74eb2 100644 --- a/app/models/tag_rule.rb +++ b/app/models/tag_rule.rb @@ -31,6 +31,7 @@ class TagRule < ActiveRecord::Base def customer_tags_match? context_customer_tags = context.andand[:customer].andand.tag_list || [] - ( context_customer_tags & preferred_customer_tags.split(",") ).any? + preferred_tags = preferred_customer_tags.split(",") + ( context_customer_tags & preferred_tags ).any? end end diff --git a/app/models/tag_rule/discount_order.rb b/app/models/tag_rule/discount_order.rb index 1ee9b4263a..c1e440760a 100644 --- a/app/models/tag_rule/discount_order.rb +++ b/app/models/tag_rule/discount_order.rb @@ -5,9 +5,7 @@ class TagRule::DiscountOrder < TagRule # Warning: this should only EVER be called via TagRule#apply def apply! - percentage = "%.2f" % (calculator.preferred_flat_percent * -1) - label = I18n.t("tag_rules.discount_order.label", percentage: percentage) - create_adjustment(label, subject, subject) + create_adjustment(I18n.t("tag_rules.discount_order.discount"), subject, subject) end def subject_class diff --git a/config/locales/en.yml b/config/locales/en.yml index 7892ecdce1..4a9d8fe546 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -140,7 +140,7 @@ en: # Tag Rules tag_rules: discount_order: - label: "%{percentage}% discount" + discount: "Discount" logo: "Logo (640x130)" #FIXME logo_mobile: "Mobile logo (75x26)" #FIXME diff --git a/spec/features/admin/enterprises_spec.rb b/spec/features/admin/enterprises_spec.rb index cc923cd89e..a39a3b8acb 100644 --- a/spec/features/admin/enterprises_spec.rb +++ b/spec/features/admin/enterprises_spec.rb @@ -301,13 +301,13 @@ feature %q{ expect(page).to have_content 'No rules apply to this tag yet' click_button '+ Add A New Rule' - fill_in "enterprise[tag_rules_attributes][0][calculator_attributes][preferred_flat_percent]", with: 22 + fill_in "enterprise_tag_rules_attributes_0_calculator_attributes_preferred_flat_percent", with: 22 click_button 'Update' tag_rule = TagRule::DiscountOrder.last expect(tag_rule.preferred_customer_tags).to eq "volunteer" - expect(tag_rule.calculator.preferred_flat_percent).to eq 22 + expect(tag_rule.calculator.preferred_flat_percent).to eq -22 end end @@ -325,13 +325,13 @@ feature %q{ expect(first('.customer_tag .header')).to have_content "For customers tagged:" expect(first('tags-input .tag-list ti-tag-item')).to have_content "member" find(:css, "tags-input .tags input").set "volunteer\n" - expect(page).to have_input "enterprise[tag_rules_attributes][0][calculator_attributes][preferred_flat_percent]", with: "0" - fill_in "enterprise[tag_rules_attributes][0][calculator_attributes][preferred_flat_percent]", with: 45 + expect(page).to have_field "enterprise_tag_rules_attributes_0_calculator_attributes_preferred_flat_percent", with: '0' + fill_in "enterprise_tag_rules_attributes_0_calculator_attributes_preferred_flat_percent", with: 45 click_button 'Update' expect(tag_rule.preferred_customer_tags).to eq "member,volunteer" - expect(tag_rule.calculator.preferred_flat_percent).to eq 45 + expect(tag_rule.calculator.preferred_flat_percent).to eq -45 end end diff --git a/spec/models/spree/order_spec.rb b/spec/models/spree/order_spec.rb index 4d681e01cc..e72fc7a1c4 100644 --- a/spec/models/spree/order_spec.rb +++ b/spec/models/spree/order_spec.rb @@ -108,44 +108,76 @@ describe Spree::Order do subject.update_distribution_charge! end - describe "looking up whether a line item can be provided by an order cycle" do - it "returns true when the variant is provided" do - v = double(:variant) - line_item = double(:line_item, variant: v) - order_cycle = double(:order_cycle, variants: [v]) - subject.stub(:order_cycle) { order_cycle } + context "appying tag rules" do + let(:enterprise) { create(:distributor_enterprise) } + let(:customer) { create(:customer, enterprise: enterprise, tag_list: "tagtagtag") } + let(:tag_rule) { create(:tag_rule, enterprise: enterprise, preferred_customer_tags: "tagtagtag") } + let(:order) { create(:order_with_totals_and_distribution, distributor: enterprise, customer: customer) } - subject.send(:provided_by_order_cycle?, line_item).should be_true + before do + tag_rule.calculator.update_attribute(:preferred_flat_percent, -10) end - it "returns false otherwise" do - v = double(:variant) - line_item = double(:line_item, variant: v) - order_cycle = double(:order_cycle, variants: []) - subject.stub(:order_cycle) { order_cycle } - - subject.send(:provided_by_order_cycle?, line_item).should be_false + context "when the rule applies" do + it "applies the rule" do + order.update_distribution_charge! + order.reload + discount = order.adjustments.find_by_label("Discount") + expect(discount).to be_a Spree::Adjustment + expect(discount.amount).to eq (order.item_total / -10).round(2) + end end - it "returns false when there is no order cycle" do - v = double(:variant) - line_item = double(:line_item, variant: v) - subject.stub(:order_cycle) { nil } + context "when the rule does not apply" do + before { tag_rule.update_attribute(:preferred_customer_tags, "tagtag") } - subject.send(:provided_by_order_cycle?, line_item).should be_false + it "does not apply the rule" do + order.update_distribution_charge! + order.reload + discount = order.adjustments.find_by_label("Discount") + expect(discount).to be_nil + end end end + end - it "looks up product distribution enterprise fees for a line item" do - product = double(:product) - variant = double(:variant, product: product) - line_item = double(:line_item, variant: variant) + describe "looking up whether a line item can be provided by an order cycle" do + it "returns true when the variant is provided" do + v = double(:variant) + line_item = double(:line_item, variant: v) + order_cycle = double(:order_cycle, variants: [v]) + subject.stub(:order_cycle) { order_cycle } - product_distribution = double(:product_distribution) - product.should_receive(:product_distribution_for).with(subject.distributor) { product_distribution } - - subject.send(:product_distribution_for, line_item).should == product_distribution + subject.send(:provided_by_order_cycle?, line_item).should be_true end + + it "returns false otherwise" do + v = double(:variant) + line_item = double(:line_item, variant: v) + order_cycle = double(:order_cycle, variants: []) + subject.stub(:order_cycle) { order_cycle } + + subject.send(:provided_by_order_cycle?, line_item).should be_false + end + + it "returns false when there is no order cycle" do + v = double(:variant) + line_item = double(:line_item, variant: v) + subject.stub(:order_cycle) { nil } + + subject.send(:provided_by_order_cycle?, line_item).should be_false + end + end + + it "looks up product distribution enterprise fees for a line item" do + product = double(:product) + variant = double(:variant, product: product) + line_item = double(:line_item, variant: variant) + + product_distribution = double(:product_distribution) + product.should_receive(:product_distribution_for).with(subject.distributor) { product_distribution } + + subject.send(:product_distribution_for, line_item).should == product_distribution end describe "getting the admin and handling charge" do @@ -559,39 +591,76 @@ describe Spree::Order do end describe "associating a customer" do - let(:user) { create(:user) } let(:distributor) { create(:distributor_enterprise) } + let!(:order) { create(:order, distributor: distributor) } - context "when a user has been set on the order" do - let!(:order) { create(:order, distributor: distributor, user: user) } - context "and a customer for order.distributor and order.user.email already exists" do - let!(:customer) { create(:customer, enterprise: distributor, email: user.email) } - it "associates the order with the existing customer" do - order.send(:associate_customer) + context "when an email address is available for the order" do + before { allow(order).to receive(:email_for_customer) { "existing@email.com" }} + + context "and a customer for order.distributor and order#email_for_customer already exists" do + let!(:customer) { create(:customer, enterprise: distributor, email: "existing@email.com" ) } + + it "associates the order with the existing customer, and returns the customer" do + result = order.send(:associate_customer) expect(order.customer).to eq customer + expect(result).to eq customer end end + context "and a customer for order.distributor and order.user.email does not alread exist" do let!(:customer) { create(:customer, enterprise: distributor, email: 'some-other-email@email.com') } - it "creates a new customer" do - expect{order.send(:associate_customer)}.to change{Customer.count}.by 1 + + it "does not set the customer and returns nil" do + result = order.send(:associate_customer) + expect(order.customer).to be_nil + expect(result).to be_nil end end end - context "when a user has not been set on the order" do - let!(:order) { create(:order, distributor: distributor, user: nil) } - context "and a customer for order.distributor and order.email already exists" do - let!(:customer) { create(:customer, enterprise: distributor, email: order.email) } - it "creates a new customer" do - order.send(:associate_customer) + context "when an email address is not available for the order" do + let!(:customer) { create(:customer, enterprise: distributor) } + before { allow(order).to receive(:email_for_customer) { nil }} + + it "does not set the customer and returns nil" do + result = order.send(:associate_customer) + expect(order.customer).to be_nil + expect(result).to be_nil + end + end + end + + describe "ensuring a customer is linked" do + let(:distributor) { create(:distributor_enterprise) } + let!(:order) { create(:order, distributor: distributor) } + + context "when a customer has already been linked to the order" do + let!(:customer) { create(:customer, enterprise: distributor, email: "existing@email.com" ) } + before { order.update_attribute(:customer_id, customer.id) } + + it "does nothing" do + order.send(:ensure_customer) + expect(order.customer).to eq customer + end + end + + context "when a customer not been linked to the order" do + context "but one matching order#email_for_customer already exists" do + let!(:customer) { create(:customer, enterprise: distributor, email: 'some-other-email@email.com') } + before { allow(order).to receive(:email_for_customer) { 'some-other-email@email.com' } } + + it "links the customer customer to the order" do + expect(order.customer).to be_nil + expect{order.send(:ensure_customer)}.to_not change{Customer.count} expect(order.customer).to eq customer end end - context "and a customer for order.distributor and order.email does not alread exist" do - let!(:customer) { create(:customer, enterprise: distributor, email: 'some-other-email@email.com') } + + context "and order#email_for_customer does not match any existing customers" do it "creates a new customer" do - expect{order.send(:associate_customer)}.to change{Customer.count}.by 1 + expect(order.customer).to be_nil + expect{order.send(:ensure_customer)}.to change{Customer.count}.by 1 + expect(order.customer).to be_a Customer end end end diff --git a/spec/models/tag_rule/discount_order_spec.rb b/spec/models/tag_rule/discount_order_spec.rb index 32f2de8874..dab901dfbf 100644 --- a/spec/models/tag_rule/discount_order_spec.rb +++ b/spec/models/tag_rule/discount_order_spec.rb @@ -66,7 +66,7 @@ describe TagRule::DiscountOrder, type: :model do tag_rule.send(:apply!) expect(adjustment).to be_a Spree::Adjustment expect(adjustment.amount).to eq -10.00 - expect(adjustment.label).to eq "10.00% discount" + expect(adjustment.label).to eq "Discount" expect(order.adjustment_total).to eq -10.00 expect(order.total).to eq 90.00 end @@ -84,7 +84,7 @@ describe TagRule::DiscountOrder, type: :model do tag_rule.send(:apply!) expect(adjustment).to be_a Spree::Adjustment expect(adjustment.amount).to eq -10.00 - expect(adjustment.label).to eq "10.00% discount" + expect(adjustment.label).to eq "Discount" expect(order.adjustment_total).to eq 15.00 expect(order.total).to eq 115.00 end