Enterprise users can create tag rules

This commit is contained in:
Rob Harrington
2016-03-11 15:56:54 +11:00
parent 066190c16f
commit 4c2552e0bf
15 changed files with 232 additions and 41 deletions

View File

@@ -1,7 +1,26 @@
angular.module("admin.enterprises").controller "TagRulesCtrl", ($scope) ->
$scope.groupedTagRules = $scope.Enterprise.tag_rules.reduce (groupedTagRules, rule) ->
key = rule.preferred_customer_tags
groupedTagRules[key] ||= []
groupedTagRules[key].push rule
groupedTagRules
, {}
$scope.tagGroups = $scope.Enterprise.tag_groups
updateRuleCounts = ->
index = 0
for tagGroup in $scope.tagGroups
tagGroup.startIndex = index
index = index + tagGroup.rules.length
updateRuleCounts()
$scope.updateTagsRulesFor = (tagGroup) ->
for tagRule in tagGroup.rules
tagRule.preferred_customer_tags = (tag.text for tag in tagGroup.tags).join(",")
$scope.addNewRuleTo = (tagGroup) ->
tagGroup.rules.push
id: null
preferred_customer_tags: (tag.text for tag in tagGroup.tags).join(",")
type: "TagRule::DiscountOrder"
calculator:
preferred_flat_percent: 0
updateRuleCounts()
$scope.addNewTag = ->
$scope.tagGroups.push { tags: [], rules: [] }

View File

@@ -1 +1 @@
angular.module("admin.enterprises", [
angular.module("admin.enterprises", [

View File

@@ -1,26 +1,36 @@
%div
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{rule.id}}_id",
name: "enterprise[tag_rules_attributes][{{rule.id}}][id]",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_id",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][id]",
ng: { value: "rule.id" } }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{rule.id}}_calculator_attributes_id",
name: "enterprise[tag_rules_attributes][{{rule.id}}][calculator_attributes][id]",
ng: { value: "rule.calculator.id" } }
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_type",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][type]",
value: "TagRule::DiscountOrder" }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{rule.id}}_calculator_attributes_type",
name: "enterprise[tag_rules_attributes][{{rule.id}}][calculator_attributes][type]",
value: "TagRule::FlatPercentItemTotal" }
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_preferred_customer_tags",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][preferred_customer_tags]",
ng: { value: "rule.preferred_customer_tags" } }
%span.text-big Apply a discount of
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_type",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_type]",
value: "Spree::Calculator::FlatPercentItemTotal" }
%input{ type: "hidden",
id: "enterprise_tag_rules_attributes_{{tagGroup.startIndex + $index}}_calculator_attributes_id",
name: "enterprise[tag_rules_attributes][{{tagGroup.startIndex + $index}}][calculator_attributes][id]",
ng: { value: "rule.calculator.id" } }
%span.text-normal {{ $index + 1 }}. Apply a discount of
%span.input-symbol.after
%span.text-big %
%span.text-normal %
%input.text-big{ type: "number",
id: "enterprise_tag_rules_attributes_{{rule.id}}_calculator_attributes_preferred_flat_percent",
name: "enterprise[tag_rules_attributes][{{rule.id}}][calculator_attributes][preferred_flat_percent]",
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,
max: 100,
ng: { model: "rule.calculator.preferred_flat_percent" } }
%span.text-big to order subtotals
%span.text-normal to order subtotals

View File

@@ -2,6 +2,10 @@
margin-bottom: 20px;
}
.margin-bottom-30 {
margin-bottom: 30px;
}
.margin-bottom-50 {
margin-bottom: 50px;
}

View File

@@ -1,5 +1,13 @@
.no_tags {
margin-bottom: 40px;
color: #aeaeae;
font-size: 1rem;
font-weight: bold;
}
.customer_tag {
border: 1px solid #cee1f4;
margin-bottom: 40px;
.header {
padding: 8px 10px;
@@ -7,7 +15,21 @@
border-bottom: 1px solid #cee1f4;
}
.no_rules {
padding: 8px 10px;
margin-bottom: 10px;
color: #aeaeae;
font-size: 1rem;
font-weight: bold;
}
.tag_rule {
padding: 8px 10px;
margin-bottom: 10px;
}
.add_rule {
padding: 8px 10px;
margin-bottom: 10px;
}
}

View File

@@ -35,6 +35,8 @@ module Admin
def update
invoke_callbacks(:update, :before)
tag_rules_attributes = params[object_name].delete :tag_rules_attributes
update_tag_rules(tag_rules_attributes) if tag_rules_attributes.present?
if @object.update_attributes(params[object_name])
invoke_callbacks(:update, :after)
flash[:success] = flash_message_for(@object, :successfully_updated)
@@ -180,6 +182,27 @@ module Admin
@taxons = Spree::Taxon.order(:name)
end
def update_tag_rules(tag_rules_attributes)
# Due to the combination of trying to use nested attributes and type inheritance
# we cannot apply all attributes to tag rules in one hit because mass assignment
# methods that are specific to each class do not become available until after the
# record is persisted. This problem is compounded by the use of calculators.
@object.transaction do
tag_rules_attributes.select{ |i, attrs| attrs[:type].present? }.each do |i, attrs|
rule = @object.tag_rules.find_by_id(attrs.delete :id) || attrs[:type].constantize.new(enterprise: @object)
create_calculator_for(rule, attrs) if rule.type == "TagRule::DiscountOrder" && rule.calculator.nil?
rule.update_attributes(attrs)
end
end
end
def create_calculator_for(rule, attrs)
if attrs[:calculator_type].present? && attrs[:calculator_attributes].present?
rule.update_attributes(calculator_type: attrs[:calculator_type])
attrs[:calculator_attributes].merge!( { id: rule.calculator.id } )
end
end
def check_can_change_bulk_sells
unless spree_current_user.admin?
params[:enterprise_set][:collection_attributes].each do |i, enterprise_params|

View File

@@ -48,7 +48,7 @@ class Enterprise < ActiveRecord::Base
accepts_nested_attributes_for :address
accepts_nested_attributes_for :producer_properties, allow_destroy: true, reject_if: lambda { |pp| pp[:property_name].blank? }
accepts_nested_attributes_for :tag_rules, allow_destroy: true
accepts_nested_attributes_for :tag_rules, allow_destroy: true, reject_if: lambda { |tag_rule| tag_rule[:preferred_customer_tags].blank? }
has_attached_file :logo,
styles: { medium: "300x300>", small: "180x180>", thumb: "100x100>" },

View File

@@ -7,6 +7,8 @@ class TagRule < ActiveRecord::Base
validates :enterprise, presence: true
attr_accessible :enterprise, :enterprise_id, :preferred_customer_tags
def set_context(subject, context)
@subject = subject
@context = context

View File

@@ -3,9 +3,25 @@ class Api::Admin::EnterpriseSerializer < ActiveModel::Serializer
attributes :producer_profile_only, :email, :long_description, :permalink
attributes :preferred_shopfront_message, :preferred_shopfront_closed_message, :preferred_shopfront_taxon_order, :preferred_shopfront_order_cycle_order
attributes :preferred_product_selection_from_inventory_only
attributes :owner, :users
attributes :owner, :users, :tag_groups
has_one :owner, serializer: Api::Admin::UserSerializer
has_many :users, serializer: Api::Admin::UserSerializer
has_many :tag_rules, serializer: Api::Admin::TagRuleSerializer
def tag_groups
tag_groups = []
object.tag_rules.each do |tag_rule|
tag_group = find_match(tag_groups, tag_rule.preferred_customer_tags.split(",").map{ |t| { text: t } })
tag_groups << tag_group if tag_group[:rules].empty?
tag_group[:rules] << Api::Admin::TagRuleSerializer.new(tag_rule).serializable_hash
end
tag_groups
end
def find_match(tag_groups, tags)
tag_groups.each do |tag_group|
return tag_group if tag_group[:tags].length == tags.length && (tag_group[:tags] & tags) == tag_group[:tags]
end
return { tags: tags, rules: [] }
end
end

View File

@@ -55,6 +55,6 @@
%legend Shop Preferences
= render 'admin/enterprises/form/shop_preferences', f: f
%fieldset.alpha.no-border-bottom{ ng: { show: "menu.selected.name=='Tag Rules'" } }
%fieldset.alpha.no-border-bottom{ ng: { if: "menu.selected.name=='Tag Rules'" } }
%legend Tag Rules
= render 'admin/enterprises/form/tag_rules', f: f

View File

@@ -7,6 +7,7 @@
- content_for :page_actions do
%li= button_link_to "Back to enterprises list", main_app.admin_enterprises_path, icon: 'icon-arrow-left'
= render 'admin/enterprises/form_data'
= render 'admin/enterprises/ng_form', action: 'edit'

View File

@@ -1,10 +1,21 @@
.row{ ng: { controller: "TagRulesCtrl" } }
.eleven.columns.alpha.omega
.eleven.columns.alpha.omega
.customer_tag{ ng: { repeat: "(tags, rules) in groupedTagRules" }, bindonce: true }
.no_tags{ ng: { show: "tagGroups.length == 0" } }
No tags apply to this enterprise yet
.customer_tag{ ng: { repeat: "tagGroup in tagGroups" }, bindonce: true }
.header
%h3
For customers tagged
%span.text-red &#35;{{ tags.split(",").join(", #") }}
.tag_rule{ ng: { repeat: "rule in rules" } }
For customers tagged:
%tags-input{ ng: { model: 'tagGroup.tags'},
min: { tags: "1" },
on: { tag: { added: "updateTagsRulesFor(tagGroup)", removed: "updateTagsRulesFor(tagGroup)" } } }
.no_rules{ ng: { show: "tagGroup.rules.length == 0" } }
No rules apply to this tag yet
.tag_rule{ ng: { repeat: "rule in tagGroup.rules" } }
%discount-order{ bo: { if: "rule.type == 'TagRule::DiscountOrder'" } }
.add_rule
%input.button.icon-plus{ type: 'button', value: "+ Add A New Rule", ng: { click: 'addNewRuleTo(tagGroup)' } }
.add_tag
%input.button.icon-plus{ type: 'button', value: "+ Add A New Tag", ng: { click: 'addNewTag()' } }

View File

@@ -181,6 +181,58 @@ module Admin
end
end
end
describe "tag rules" do
let(:enterprise) { create(:distributor_enterprise) }
let!(:tag_rule) { create(:tag_rule, enterprise: enterprise) }
before do
login_as_enterprise_user [enterprise]
end
context "discount order rules" do
it "updates the existing rule with new attributes" do
spree_put :update, {
id: enterprise,
enterprise: {
tag_rules_attributes: {
'0' => {
id: tag_rule,
type: "TagRule::DiscountOrder",
preferred_customer_tags: "some,new,tags",
calculator_type: "Spree::Calculator::FlatPercentItemTotal",
calculator_attributes: { id: tag_rule.calculator.id, preferred_flat_percent: "15" }
}
}
}
}
tag_rule.reload
expect(tag_rule.preferred_customer_tags).to eq "some,new,tags"
expect(tag_rule.calculator.preferred_flat_percent).to eq 15
end
it "creates new rules with new attributes" do
spree_put :update, {
id: enterprise,
enterprise: {
tag_rules_attributes: {
'0' => {
id: "",
type: "TagRule::DiscountOrder",
preferred_customer_tags: "tags,are,awesome",
calculator_type: "Spree::Calculator::FlatPercentItemTotal",
calculator_attributes: { id: "", preferred_flat_percent: "24" }
}
}
}
}
expect(tag_rule.reload).to be
new_tag_rule = TagRule::DiscountOrder.last
expect(new_tag_rule.preferred_customer_tags).to eq "tags,are,awesome"
expect(new_tag_rule.calculator.preferred_flat_percent).to eq 24
end
end
end
end
context "as owner" do

View File

@@ -285,6 +285,32 @@ feature %q{
describe "tag rules", js: true do
let!(:enterprise) { create(:distributor_enterprise) }
context "creating" do
before do
login_to_admin_section
visit main_app.edit_admin_enterprise_path(enterprise)
end
it "creates a new rule" do
click_link "Tag Rules"
expect(page).to_not have_selector '.customer_tag'
expect(page).to have_content 'No tags apply to this enterprise yet'
click_button '+ Add A New Tag'
find(:css, "tags-input .tags input").set "volunteer\n"
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
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
end
end
context "updating" do
let!(:tag_rule) { create(:tag_rule, enterprise: enterprise, preferred_customer_tags: "member" ) }
@@ -296,12 +322,15 @@ feature %q{
it "saves changes to the rule" do
click_link "Tag Rules"
expect(first('.customer_tag .header')).to have_content "For customers tagged #member"
expect(page).to have_input "enterprise[tag_rules_attributes][#{tag_rule.id}][calculator_attributes][preferred_flat_percent]", with: "0"
fill_in "enterprise[tag_rules_attributes][#{tag_rule.id}][calculator_attributes][preferred_flat_percent]", with: 45
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
click_button 'Update'
expect(tag_rule.preferred_customer_tags).to eq "member,volunteer"
expect(tag_rule.calculator.preferred_flat_percent).to eq 45
end
end

View File

@@ -6,10 +6,9 @@ describe "TagRulesCtrl", ->
beforeEach ->
module('admin.enterprises')
enterprise =
tag_rules: [
{ id: 1, preferred_customer_tags: "member" },
{ id: 2, preferred_customer_tags: "member" },
{ id: 3, preferred_customer_tags: "local" }
tag_groups: [
{ tags: "member", rules: [{ id: 1, preferred_customer_tags: "member" }, { id: 2, preferred_customer_tags: "member" }] },
{ tags: "volunteer", rules: [{ id: 3, preferred_customer_tags: "local" }] }
]
inject ($rootScope, $controller) ->
@@ -17,9 +16,12 @@ describe "TagRulesCtrl", ->
scope.Enterprise = enterprise
ctrl = $controller 'TagRulesCtrl', {$scope: scope}
describe "initialization", ->
it "groups rules by preferred_customer_tags", ->
expect(scope.groupedTagRules).toEqual {
member: [{ id: 1, preferred_customer_tags: "member" }, { id: 2, preferred_customer_tags: "member" }],
local: [{ id: 3, preferred_customer_tags: "local" }]
}
describe "tagGroup start indices", ->
it "updates on initialization", ->
expect(scope.tagGroups[0].startIndex).toEqual 0
expect(scope.tagGroups[1].startIndex).toEqual 2
it "updates when tags are added to a tagGroup", ->
scope.addNewRuleTo(scope.tagGroups[0])
expect(scope.tagGroups[0].startIndex).toEqual 0
expect(scope.tagGroups[1].startIndex).toEqual 3