diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index 8d50a3dc1f..93cf4b430b 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -196,6 +196,8 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", [ unit_value: null unit_description: null on_demand: false + display_as: null + display_name: null on_hand: null price: null $scope.displayProperties[product.id].showVariants = true @@ -425,6 +427,7 @@ filterSubmitProducts = (productsToFilter) -> if product.hasOwnProperty("id") filteredProduct = {id: product.id} filteredVariants = [] + filteredMaster = null hasUpdatableProperty = false if product.hasOwnProperty("variants") @@ -435,11 +438,14 @@ filterSubmitProducts = (productsToFilter) -> filteredVariants.push filteredVariant if variantHasUpdatableProperty if product.master?.hasOwnProperty("unit_value") - filteredProduct.unit_value = product.master.unit_value - hasUpdatableProperty = true + filteredMaster ?= { id: product.master.id } + filteredMaster.unit_value = product.master.unit_value if product.master?.hasOwnProperty("unit_description") - filteredProduct.unit_description = product.master.unit_description - hasUpdatableProperty = true + filteredMaster ?= { id: product.master.id } + filteredMaster.unit_description = product.master.unit_description + if product.master?.hasOwnProperty("display_as") + filteredMaster ?= { id: product.master.id } + filteredMaster.display_as = product.master.display_as if product.hasOwnProperty("name") filteredProduct.name = product.name @@ -466,6 +472,9 @@ filterSubmitProducts = (productsToFilter) -> if product.hasOwnProperty("available_on") filteredProduct.available_on = product.available_on hasUpdatableProperty = true + if filteredMaster? + filteredProduct.master_attributes = filteredMaster + hasUpdatableProperty = true if filteredVariants.length > 0 # Note that the name of the property changes to enable mass assignment of variants attributes with rails filteredProduct.variants_attributes = filteredVariants hasUpdatableProperty = true @@ -491,6 +500,12 @@ filterSubmitVariant = (variant) -> if variant.hasOwnProperty("unit_description") filteredVariant.unit_description = variant.unit_description hasUpdatableProperty = true + if variant.hasOwnProperty("display_name") + filteredVariant.display_name = variant.display_name + hasUpdatableProperty = true + if variant.hasOwnProperty("display_as") + filteredVariant.display_as = variant.display_as + hasUpdatableProperty = true {filteredVariant: filteredVariant, hasUpdatableProperty: hasUpdatableProperty} diff --git a/app/assets/javascripts/admin/directives/track_master.js.coffee b/app/assets/javascripts/admin/directives/track_master.js.coffee new file mode 100644 index 0000000000..ce26a4aa32 --- /dev/null +++ b/app/assets/javascripts/admin/directives/track_master.js.coffee @@ -0,0 +1,9 @@ +angular.module("ofn.admin").directive "ofnTrackMaster", ["DirtyProducts", (DirtyProducts) -> + require: "ngModel" + link: (scope, element, attrs, ngModel) -> + ngModel.$parsers.push (viewValue) -> + if ngModel.$dirty + DirtyProducts.addMasterProperty scope.product.id, scope.product.master.id, attrs.ofnTrackMaster, viewValue + scope.displayDirtyProducts() + viewValue + ] \ No newline at end of file diff --git a/app/assets/javascripts/admin/services/dirty_products.js.coffee b/app/assets/javascripts/admin/services/dirty_products.js.coffee index 16c10e1e34..1c8d987dbc 100644 --- a/app/assets/javascripts/admin/services/dirty_products.js.coffee +++ b/app/assets/javascripts/admin/services/dirty_products.js.coffee @@ -12,6 +12,11 @@ angular.module("ofn.admin").factory "DirtyProducts", ($parse) -> addProductProperty: (productID, propertyName, propertyValue) -> addDirtyProperty dirtyProducts, productID, propertyName, propertyValue + + addMasterProperty: (productID, masterID, propertyName, propertyValue) -> + if !dirtyProducts.hasOwnProperty(productID) or !dirtyProducts[productID].hasOwnProperty("master") + addDirtyProperty dirtyProducts, productID, "master", { id: masterID } + $parse(propertyName).assign(dirtyProducts[productID]["master"], propertyValue) addVariantProperty: (productID, variantID, propertyName, propertyValue) -> if !dirtyProducts.hasOwnProperty(productID) or !dirtyProducts[productID].hasOwnProperty("variants") diff --git a/app/models/spree/product_set.rb b/app/models/spree/product_set.rb index 0f2ebd511e..d6f361c82e 100644 --- a/app/models/spree/product_set.rb +++ b/app/models/spree/product_set.rb @@ -14,18 +14,24 @@ class Spree::ProductSet < ModelSet if e.nil? @klass.new(attributes).save unless @reject_if.andand.call(attributes) else - e.update_attributes(attributes.except(:id, :variants_attributes)) and (attributes[:variants_attributes] ? update_variants_attributes(e, attributes[:variants_attributes]) : true ) + e.update_attributes(attributes.except(:id, :variants_attributes, :master_attributes)) and + (attributes[:variants_attributes] ? update_variants_attributes(e, attributes[:variants_attributes]) : true ) and + (attributes[:master_attributes] ? update_variant(e, attributes[:master_attributes]) : true ) end end def update_variants_attributes(product, variants_attributes) variants_attributes.each do |attributes| - e = product.variants.detect { |e| e.id.to_s == attributes[:id].to_s && !e.id.nil? } - if e.present? - e.update_attributes(attributes.except(:id)) - else - product.variants.create attributes - end + update_variant(product, attributes) + end + end + + def update_variant(product, variant_attributes) + e = product.variants_including_master.detect { |e| e.id.to_s == variant_attributes[:id].to_s && !e.id.nil? } + if e.present? + e.update_attributes(variant_attributes.except(:id)) + else + product.variants.create variant_attributes end end diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index bded64b9e8..2ffb07d4e1 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -4,7 +4,7 @@ Spree::Variant.class_eval do has_many :exchange_variants, dependent: :destroy has_many :exchanges, through: :exchange_variants - attr_accessible :unit_value, :unit_description, :images_attributes + attr_accessible :unit_value, :unit_description, :images_attributes, :display_as, :display_name accepts_nested_attributes_for :images validates_presence_of :unit_value, diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index fa295adb47..8d943adb8e 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -93,6 +93,7 @@ %col{'style' => 'width: 20%;'} %col{'style' => 'width: 12%;'} %col{'style' => 'width: 12%;'} + %col{'style' => 'width: 12%;'} %col %col %col @@ -106,6 +107,7 @@ %th{ 'ng-show' => 'columns.supplier.visible' } Supplier %th{ 'ng-show' => 'columns.name.visible' } Name %th{ 'ng-show' => 'columns.unit.visible' } Unit / Value + %th{ 'ng-show' => 'columns.unit.visible' } Display As %th{ 'ng-show' => 'columns.price.visible' } Price %th{ 'ng-show' => 'columns.on_hand.visible' } On Hand %th{ 'ng-show' => 'columns.taxons.visible' } Taxons @@ -125,8 +127,10 @@ %td.unit{ 'ng-show' => 'columns.unit.visible' } %select.select2{ 'ng-model' => 'product.variant_unit_with_scale', :name => 'variant_unit_with_scale', 'ofn-track-product' => 'variant_unit_with_scale', 'ng-options' => 'unit[1] as unit[0] for unit in variant_unit_options' } %option{'value' => '', 'ng-hide' => "hasVariants(product) && hasUnit(product)"} - %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.master.unit_value_with_description', :name => 'master_unit_value_with_description', 'ofn-track-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.display_as{ 'ng-show' => 'columns.unit.visible' } + %input{ name: 'display_as', 'ofn-track-master' => 'display_as', :type => 'text', placeholder: "{{ product.master.options_text }}", ng: { hide: 'hasVariants(product)', model: 'product.master.display_as' } } %td{ 'ng-show' => 'columns.price.visible' } %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' } @@ -148,9 +152,11 @@ %a{ :class => "add-variant icon-plus-sign", 'ng-click' => "addVariant(product)", 'ng-show' => "$last" } %td{ 'ng-show' => 'columns.supplier.visible' } %td{ 'ng-show' => 'columns.name.visible' } - {{ variant.options_text }} - %td{ 'ng-show' => 'columns.unit.visible' } + %input{ 'ng-model' => 'variant.display_name', :name => 'variant_display_name', 'ofn-track-variant' => 'display_name', :type => 'text', placeholder: "{{ product.name }}" } + %td.unit_value{ 'ng-show' => 'columns.unit.visible' } %input{ 'ng-model' => 'variant.unit_value_with_description', :name => 'variant_unit_value_with_description', 'ofn-track-variant' => 'unit_value_with_description', :type => 'text' } + %td.display_as{ 'ng-show' => 'columns.unit.visible' } + %input{ 'ng-model' => 'variant.display_as', :name => 'variant_display_as', 'ofn-track-variant' => 'display_as', :type => 'text', placeholder: "{{ variant.options_text }}" } %td{ 'ng-show' => 'columns.price.visible' } %input{ 'ng-model' => 'variant.price', 'ofn-decimal' => :true, :name => 'variant_price', 'ofn-track-variant' => 'price', :type => 'text' } %td{ 'ng-show' => 'columns.on_hand.visible' } diff --git a/app/views/spree/api/variants/bulk_show.v1.rabl b/app/views/spree/api/variants/bulk_show.v1.rabl index 52e293a890..4a2a6bae9c 100644 --- a/app/views/spree/api/variants/bulk_show.v1.rabl +++ b/app/views/spree/api/variants/bulk_show.v1.rabl @@ -1,6 +1,6 @@ object @variant -attributes :id, :options_text, :unit_value, :unit_description, :on_demand +attributes :id, :options_text, :unit_value, :unit_description, :on_demand, :display_as, :display_name # Infinity is not a valid JSON object, but Rails encodes it anyway node( :on_hand ) { |v| v.on_hand.nil? ? 0 : ( v.on_hand.to_f.finite? ? v.on_hand : "On demand" ) } diff --git a/spec/controllers/spree/api/variants_controller_spec.rb b/spec/controllers/spree/api/variants_controller_spec.rb index 818e10457a..5df1a40dde 100644 --- a/spec/controllers/spree/api/variants_controller_spec.rb +++ b/spec/controllers/spree/api/variants_controller_spec.rb @@ -8,7 +8,7 @@ module Spree let!(:variant1) { FactoryGirl.create(:variant) } let!(:variant2) { FactoryGirl.create(:variant) } let!(:variant3) { FactoryGirl.create(:variant) } - let(:attributes) { [:id, :options_text, :price, :on_hand] } + let(:attributes) { [:id, :options_text, :price, :on_hand, :unit_value, :unit_description, :on_demand, :display_as, :display_name] } let(:unit_attributes) { [:id, :unit_text, :unit_value] } before do diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 9e0f24cd15..79cff455a7 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -166,8 +166,8 @@ feature %q{ end it "displays a list of variants for each product" do - v1 = FactoryGirl.create(:variant) - v2 = FactoryGirl.create(:variant) + v1 = FactoryGirl.create(:variant, display_name: "something1" ) + v2 = FactoryGirl.create(:variant, display_name: "something2" ) visit '/admin/products/bulk_edit' page.should have_selector "a.view-variants" @@ -175,8 +175,8 @@ feature %q{ page.should have_field "product_name", with: v1.product.name page.should have_field "product_name", with: v2.product.name - page.should have_selector "td", text: v1.options_text - page.should have_selector "td", text: v2.options_text + page.should have_field "variant_display_name", with: v1.display_name + page.should have_field "variant_display_name", with: v2.display_name end it "displays an on_hand input (for each variant) for each product" do @@ -208,14 +208,16 @@ feature %q{ it "displays a unit value field (for each variant) for each product" do p1 = FactoryGirl.create(:product, price: 2.0, variant_unit: "weight", variant_unit_scale: "1000") - v1 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 12.75, unit_value: 1200, unit_description: "(small bag)") - v2 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 2.50, unit_value: 4800, unit_description: "(large bag)") + v1 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 12.75, unit_value: 1200, unit_description: "(small bag)", display_as: "bag") + v2 = FactoryGirl.create(:variant, product: p1, is_master: false, price: 2.50, unit_value: 4800, unit_description: "(large bag)", display_as: "bin") visit '/admin/products/bulk_edit' all("a.view-variants").each { |e| e.trigger('click') } page.should have_field "variant_unit_value_with_description", with: "1.2 (small bag)" page.should have_field "variant_unit_value_with_description", with: "4.8 (large bag)" + page.should have_field "variant_display_as", with: "bag" + page.should have_field "variant_display_as", with: "bin" end end @@ -274,10 +276,13 @@ feature %q{ page.all("tr.variant").count.should == 1 # When I fill out variant details and hit update + fill_in "variant_display_name", with: "Case of 12 Bottles" fill_in "variant_unit_value_with_description", with: "4000 (12x250 mL bottles)" + fill_in "variant_display_as", with: "Case" fill_in "variant_price", with: "4.0" fill_in "variant_on_hand", with: "10" click_button 'Update' + page.find("span#update-status-message").should have_content "Update complete" # Then I should see edit buttons for the new variant @@ -288,7 +293,9 @@ feature %q{ page.should have_selector "a.view-variants" first("a.view-variants").trigger('click') + page.should have_field "variant_display_name", with: "Case of 12 Bottles" page.should have_field "variant_unit_value_with_description", with: "4000 (12x250 mL bottles)" + page.should have_field "variant_display_as", with: "Case" page.should have_field "variant_price", with: "4.0" page.should have_field "variant_on_hand", with: "10" end @@ -322,6 +329,7 @@ feature %q{ fill_in "price", with: "20" select "Weight (kg)", from: "variant_unit_with_scale" fill_in "on_hand", with: "18" + fill_in "display_as", with: "Big Bag" click_button 'Update' page.find("span#update-status-message").should have_content "Update complete" @@ -334,6 +342,7 @@ feature %q{ page.should have_field "price", with: "20.0" page.should have_select "variant_unit_with_scale", selected: "Weight (kg)" page.should have_field "on_hand", with: "18" + page.should have_field "display_as", with: "Big Bag" end scenario "updating a product with a variant unit of 'items'" do