mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-28 01:53:25 +00:00
Enterprise users can create tag rules
This commit is contained in:
@@ -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: [] }
|
||||
|
||||
@@ -1 +1 @@
|
||||
angular.module("admin.enterprises", [
|
||||
angular.module("admin.enterprises", [
|
||||
@@ -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
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.margin-bottom-30 {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.margin-bottom-50 {
|
||||
margin-bottom: 50px;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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>" },
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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 #{{ 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()' } }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user