diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index bc7d01066b..177859235b 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -1,4 +1,4 @@ -angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth) -> +angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout, $http, BulkProducts, DisplayProperties, dataFetcher, DirtyProducts, VariantUnitManager, StatusMessage, producers, Taxons, SpreeApiAuth, tax_categories) -> $scope.loading = true $scope.StatusMessage = StatusMessage @@ -10,7 +10,9 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout unit: {name: "Unit", visible: true} price: {name: "Price", visible: true} on_hand: {name: "On Hand", visible: true} + on_demand: {name: "On Demand", visible: false} category: {name: "Category", visible: false} + tax_category: {name: "Tax Category", visible: false} inherits_properties: {name: "Inherits Properties?", visible: false} available_on: {name: "Available On", visible: false} @@ -32,6 +34,7 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.producers = producers $scope.taxons = Taxons.taxons + $scope.tax_categories = tax_categories $scope.filterProducers = [{id: "0", name: ""}].concat $scope.producers $scope.filterTaxons = [{id: "0", name: ""}].concat $scope.taxons $scope.producerFilter = "0" @@ -312,9 +315,15 @@ filterSubmitProducts = (productsToFilter) -> if product.hasOwnProperty("on_hand") and filteredVariants.length == 0 #only update if no variants present filteredProduct.on_hand = product.on_hand hasUpdatableProperty = true + if product.hasOwnProperty("on_demand") and filteredVariants.length == 0 #only update if no variants present + filteredProduct.on_demand = product.on_demand + hasUpdatableProperty = true if product.hasOwnProperty("category_id") filteredProduct.primary_taxon_id = product.category_id hasUpdatableProperty = true + if product.hasOwnProperty("tax_category_id") + filteredProduct.tax_category_id = product.tax_category_id + hasUpdatableProperty = true if product.hasOwnProperty("inherits_properties") filteredProduct.inherits_properties = product.inherits_properties hasUpdatableProperty = true @@ -340,6 +349,9 @@ filterSubmitVariant = (variant) -> if variant.hasOwnProperty("on_hand") filteredVariant.on_hand = variant.on_hand hasUpdatableProperty = true + if variant.hasOwnProperty("on_demand") + filteredVariant.on_demand = variant.on_demand + hasUpdatableProperty = true if variant.hasOwnProperty("price") filteredVariant.price = variant.price hasUpdatableProperty = true diff --git a/app/assets/javascripts/admin/services/bulk_products.js.coffee b/app/assets/javascripts/admin/services/bulk_products.js.coffee index d898356846..6085d48fcc 100644 --- a/app/assets/javascripts/admin/services/bulk_products.js.coffee +++ b/app/assets/javascripts/admin/services/bulk_products.js.coffee @@ -19,7 +19,7 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher) # when a respond_overrride for the clone action is used. id = data.product.id dataFetcher("/api/products/" + id + "?template=bulk_show").then (newProduct) => - @addProducts [newProduct] + @insertProductAfter(product, newProduct) updateVariantLists: (serverProducts, productsWithUnsavedVariants) -> for product in productsWithUnsavedVariants @@ -39,6 +39,10 @@ angular.module("ofn.admin").factory "BulkProducts", (PagedFetcher, dataFetcher) @unpackProduct product @products.push product + insertProductAfter: (product, newProduct) -> + index = @products.indexOf(product) + @products.splice(index + 1, 0, newProduct) + unpackProduct: (product) -> #$scope.matchProducer product @loadVariantUnit product diff --git a/app/controllers/spree/admin/orders_controller_decorator.rb b/app/controllers/spree/admin/orders_controller_decorator.rb index 247e780deb..f7d674048e 100644 --- a/app/controllers/spree/admin/orders_controller_decorator.rb +++ b/app/controllers/spree/admin/orders_controller_decorator.rb @@ -23,6 +23,13 @@ Spree::Admin::OrdersController.class_eval do distributed_by_user(spree_current_user). page(params[:page]). per(params[:per_page] || Spree::Config[:orders_per_page]) + # Filter orders by distributor + if params[:distributor_ids] + @orders = @orders.where(distributor_id: params[:distributor_ids]) + end + if params[:order_cycle_ids] + @orders = @orders.where(order_cycle_id: params[:order_cycle_ids]) + end } } } # Overwrite to use confirm_email_for_customer instead of confirm_email. diff --git a/app/helpers/admin/injection_helper.rb b/app/helpers/admin/injection_helper.rb index 36ddccdb9a..961bc60afb 100644 --- a/app/helpers/admin/injection_helper.rb +++ b/app/helpers/admin/injection_helper.rb @@ -50,6 +50,10 @@ module Admin admin_inject_json_ams_array "ofn.admin", "products", @products, Api::Admin::ProductSerializer end + def admin_inject_tax_categories + admin_inject_json_ams_array "ofn.admin", "tax_categories", @tax_categories, Api::Admin::TaxCategorySerializer + end + def admin_inject_taxons admin_inject_json_ams_array "admin.taxons", "taxons", @taxons, Api::Admin::TaxonSerializer end diff --git a/app/models/spree/product_decorator.rb b/app/models/spree/product_decorator.rb index 82330c61cf..c789d81963 100644 --- a/app/models/spree/product_decorator.rb +++ b/app/models/spree/product_decorator.rb @@ -109,6 +109,12 @@ Spree::Product.class_eval do # -- Methods + # Called by Spree::Product::duplicate before saving. + def duplicate_extra(parent) + # Spree sets the SKU to "COPY OF #{parent sku}". + self.master.sku = '' + end + def properties_including_inherited # Product properties override producer properties ps = product_properties.all diff --git a/app/models/spree/variant_decorator.rb b/app/models/spree/variant_decorator.rb index 5cc7b22f75..72a7fb4dec 100644 --- a/app/models/spree/variant_decorator.rb +++ b/app/models/spree/variant_decorator.rb @@ -7,6 +7,7 @@ Spree::Variant.class_eval do has_many :exchange_variants, dependent: :destroy has_many :exchanges, through: :exchange_variants + has_many :variant_overrides attr_accessible :unit_value, :unit_description, :images_attributes, :display_as, :display_name accepts_nested_attributes_for :images diff --git a/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface b/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface new file mode 100644 index 0000000000..f9e5e54e88 --- /dev/null +++ b/app/overrides/spree/admin/orders/index/add_distributor_and_order_cycle_filter_inputs.html.haml.deface @@ -0,0 +1,13 @@ +/ insert_before "div.clearfix" + +.field-block.alpha.eight.columns + = label_tag nil, t(:distributors) + = select_tag(:distributor_ids, + options_for_select(Enterprise.is_distributor.managed_by(spree_current_user).map {|e| [e.name, e.id]}, params[:distributor_ids]), + {class: "select2 fullwidth", multiple: true}) + +.field-block.alpha.eight.columns + = label_tag nil, t(:order_cycles) + = select_tag(:order_cycle_ids, + options_for_select(OrderCycle.managed_by(spree_current_user).map {|oc| [oc.name, oc.id]}, params[:order_cycle_ids]), + {class: "select2 fullwidth", multiple: true}) diff --git a/app/overrides/spree/admin/products/new/replace_form.html.haml.deface b/app/overrides/spree/admin/products/new/replace_form.html.haml.deface index 33c70b728e..3fd5a74cb1 100644 --- a/app/overrides/spree/admin/products/new/replace_form.html.haml.deface +++ b/app/overrides/spree/admin/products/new/replace_form.html.haml.deface @@ -38,20 +38,26 @@ .twelve.columns.alpha .six.columns.alpha = render 'spree/admin/products/primary_taxon_form', f: f - .three.columns + .two.columns = f.field_container :price do = f.label :price, t(:price) %span.required * %br/ = f.text_field :price, class: 'fullwidth' = f.error_message_on :price - .three.columns.omega + .two.columns = f.field_container :on_hand do = f.label :on_hand, t(:on_hand) %span.required * %br/ = f.text_field :on_hand, class: 'fullwidth' = f.error_message_on :on_hand + .two.columns.omega + = f.field_container :on_demand do + = f.label :on_demand, t(:on_demand) + %br/ + = f.check_box :on_demand + = f.error_message_on :on_demand .twelve.columns.alpha .six.columns.alpha   .three.columns diff --git a/app/serializers/api/admin/product_serializer.rb b/app/serializers/api/admin/product_serializer.rb index b1f233c044..65dd34ba6b 100644 --- a/app/serializers/api/admin/product_serializer.rb +++ b/app/serializers/api/admin/product_serializer.rb @@ -1,7 +1,7 @@ class Api::Admin::ProductSerializer < ActiveModel::Serializer attributes :id, :name, :sku, :variant_unit, :variant_unit_scale, :variant_unit_name, :on_demand, :inherits_properties - attributes :on_hand, :price, :available_on, :permalink_live + attributes :on_hand, :price, :available_on, :permalink_live, :tax_category_id has_one :supplier, key: :producer_id, embed: :id has_one :primary_taxon, key: :category_id, embed: :id diff --git a/app/serializers/api/admin/tax_category_serializer.rb b/app/serializers/api/admin/tax_category_serializer.rb new file mode 100644 index 0000000000..37e99578b8 --- /dev/null +++ b/app/serializers/api/admin/tax_category_serializer.rb @@ -0,0 +1,3 @@ +class Api::Admin::TaxCategorySerializer < ActiveModel::Serializer + attributes :id, :name +end diff --git a/app/serializers/api/admin/variant_serializer.rb b/app/serializers/api/admin/variant_serializer.rb index 26ad2d7f37..510f7af333 100644 --- a/app/serializers/api/admin/variant_serializer.rb +++ b/app/serializers/api/admin/variant_serializer.rb @@ -1,6 +1,7 @@ class Api::Admin::VariantSerializer < ActiveModel::Serializer attributes :id, :options_text, :unit_value, :unit_description, :unit_to_display, :on_demand, :display_as, :display_name, :name_to_display attributes :on_hand, :price + has_many :variant_overrides def on_hand object.on_hand.nil? ? 0 : ( object.on_hand.to_f.finite? ? object.on_hand : "On demand" ) diff --git a/app/views/spree/admin/products/bulk_edit.html.haml b/app/views/spree/admin/products/bulk_edit.html.haml index f230d1c331..22b0a195da 100644 --- a/app/views/spree/admin/products/bulk_edit.html.haml +++ b/app/views/spree/admin/products/bulk_edit.html.haml @@ -8,3 +8,4 @@ = render 'spree/admin/products/bulk_edit/actions' = render 'spree/admin/products/bulk_edit/indicators' = render 'spree/admin/products/bulk_edit/products' + = render 'spree/admin/products/bulk_edit/save_button_row' diff --git a/app/views/spree/admin/products/bulk_edit/_data.html.haml b/app/views/spree/admin/products/bulk_edit/_data.html.haml index 8b727be3eb..3624421870 100644 --- a/app/views/spree/admin/products/bulk_edit/_data.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_data.html.haml @@ -1,3 +1,4 @@ = admin_inject_producers = admin_inject_taxons += admin_inject_tax_categories = admin_inject_spree_api_key diff --git a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml index 2e37da2bc8..6e22aef8ff 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_head.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_head.html.haml @@ -7,7 +7,9 @@ %col.display_as{ ng: { show: 'columns.unit.visible' } } %col.price{ ng: { show: 'columns.price.visible'} } %col.on_hand{ ng: { show: 'columns.on_hand.visible' } } + %col.on_demand{ ng: { show: 'columns.on_demand.visible' } } %col.category{ ng: { show: 'columns.category.visible' } } + %col.tax_category{ ng: { show: 'columns.tax_category.visible' } } %col.inherits_properties{ ng: { show: 'columns.inherits_properties.visible' } } %col.available_on{ ng: { show: 'columns.available_on.visible' } } %col.actions @@ -24,7 +26,9 @@ %th.display_as{ 'ng-show' => 'columns.unit.visible' } Display As %th.price{ 'ng-show' => 'columns.price.visible' } Price %th.on_hand{ 'ng-show' => 'columns.on_hand.visible' } On Hand + %th.on_demand{ 'ng-show' => 'columns.on_demand.visible' } On Demand %th.category{ 'ng-show' => 'columns.category.visible' } Category + %th.tax_category{ 'ng-show' => 'columns.tax_category.visible' } Tax Category %th.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } Inherits Properties? %th.available_on{ 'ng-show' => 'columns.available_on.visible' } Av. On %th.actions diff --git a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml index 0eb9ff5f58..376e88071a 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_product.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_product.html.haml @@ -20,8 +20,13 @@ %td.on_hand{ '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' } + %td.on_demand{ 'ng-show' => 'columns.on_demand.visible' } + %input.field{ 'ng-model' => 'product.on_demand', :name => 'on_demand', 'ofn-track-product' => 'on_demand', :type => 'checkbox', 'ng-hide' => 'hasVariants(product)' } %td.category{ 'ng-if' => 'columns.category.visible' } %input.fullwidth{ :type => 'text', id: "p{{product.id}}_category_id", 'ng-model' => 'product.category_id', 'ofn-taxon-autocomplete' => '', 'ofn-track-product' => 'category_id', 'multiple-selection' => 'false', placeholder: 'Category' } + %td.tax_category{ 'ng-if' => 'columns.tax_category.visible' } + %select.select2{ name: 'product_tax_category_id', 'ofn-track-product' => 'tax_category_id', ng: {model: 'product.tax_category_id', options: 'tax_category.id as tax_category.name for tax_category in tax_categories'} } + %option{value: ''} None %td.inherits_properties{ 'ng-show' => 'columns.inherits_properties.visible' } %input{ 'ng-model' => 'product.inherits_properties', :name => 'inherits_properties', 'ofn-track-product' => 'inherits_properties', type: "checkbox" } %td.available_on{ 'ng-show' => 'columns.available_on.visible' } diff --git a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml index 3a783329c0..cc85566577 100644 --- a/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_products_variant.html.haml @@ -15,11 +15,15 @@ %td{ 'ng-show' => 'columns.on_hand.visible' } %input.field{ 'ng-model' => 'variant.on_hand', 'ng-change' => 'updateOnHand(product)', :name => 'variant_on_hand', 'ng-hide' => 'variant.on_demand', 'ofn-track-variant' => 'on_hand', :type => 'number' } %span{ 'ng-bind' => 'variant.on_hand', :name => 'variant_on_hand', 'ng-show' => 'variant.on_demand' } + %td{ 'ng-show' => 'columns.on_demand.visible' } + %input.field{ 'ng-model' => 'variant.on_demand', :name => 'variant_on_demand', 'ofn-track-variant' => 'on_demand', :type => 'checkbox' } %td{ 'ng-show' => 'columns.category.visible' } + %td{ 'ng-show' => 'columns.tax_category.visible' } %td{ 'ng-show' => 'columns.inherits_properties.visible' } %td{ 'ng-show' => 'columns.available_on.visible' } %td.actions %a{ 'ng-click' => 'editWarn(product,variant)', :class => "edit-variant icon-edit no-text", 'ng-show' => "variantSaved(variant)" } %td.actions + %span.icon-warning-sign.with-tip{ 'ng-if' => 'variant.variant_overrides', title: "This variant has {{variant.variant_overrides.length}} override(s)" } %td.actions %a{ 'ng-click' => 'deleteVariant(product,variant)', "ng-class" => '{disabled: product.variants.length < 2}', :class => "delete-variant icon-trash no-text" } diff --git a/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml b/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml new file mode 100644 index 0000000000..a5f18986c0 --- /dev/null +++ b/app/views/spree/admin/products/bulk_edit/_save_button_row.html.haml @@ -0,0 +1,3 @@ +%div.sixteen.columns.alpha{ 'ng-hide' => 'loading || products.length == 0', style: "margin-bottom: 10px" } + %div.four.columns.alpha + %input.four.columns.alpha{ :type => 'button', :value => 'Save Changes', 'ng-click' => 'submitProducts()'} diff --git a/spec/features/admin/bulk_product_update_spec.rb b/spec/features/admin/bulk_product_update_spec.rb index 079279e5cf..4c52ec2256 100644 --- a/spec/features/admin/bulk_product_update_spec.rb +++ b/spec/features/admin/bulk_product_update_spec.rb @@ -216,7 +216,7 @@ feature %q{ fill_in "variant_price", with: "4.0" fill_in "variant_on_hand", with: "10" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." updated_variant = Spree::Variant.where(deleted_at: nil).last @@ -266,7 +266,7 @@ feature %q{ fill_in "product_sku", with: "NEW SKU" end - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -292,7 +292,7 @@ feature %q{ select "Items", from: "variant_unit_with_scale" fill_in "variant_unit_name", with: "loaf" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload @@ -326,7 +326,7 @@ feature %q{ expect(page).to have_selector "span[name='on_hand']", text: "10" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -352,7 +352,7 @@ feature %q{ fill_in "variant_price", with: "10.0" end - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." v.reload @@ -369,21 +369,21 @@ feature %q{ fill_in "product_name", with: "new name 1" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 1" fill_in "product_name", with: "new name 2" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new name 2" fill_in "product_name", with: "original name" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "original name" @@ -399,7 +399,7 @@ feature %q{ fill_in "product_name", :with => "new product name" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload expect(p.name).to eq "new product name" @@ -412,7 +412,7 @@ feature %q{ visit '/admin/products/bulk_edit' - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "No changes to save." end end @@ -431,7 +431,7 @@ feature %q{ expect(page).to have_no_field "product_name", with: p2.name fill_in "product_name", :with => "new product1" - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p1.reload expect(p1.name).to eq "new product1" @@ -709,7 +709,7 @@ feature %q{ fill_in "variant_display_as", with: "Big Bag" end - click_button 'Save Changes' + first(:button, 'Save Changes').click expect(page.find("#status-message")).to have_content "Changes saved." p.reload diff --git a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee index 70c22c80e0..54b57aed48 100644 --- a/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee +++ b/spec/javascripts/unit/admin/services/bulk_products_spec.js.coffee @@ -61,14 +61,14 @@ describe "BulkProducts service", -> clonedProduct = id: 17 - spyOn(BulkProducts, "addProducts") + spyOn(BulkProducts, "insertProductAfter") BulkProducts.products = [originalProduct] $httpBackend.expectGET("/admin/products/oranges/clone.json").respond 200, product: clonedProduct $httpBackend.expectGET("/api/products/17?template=bulk_show").respond 200, clonedProduct BulkProducts.cloneProduct BulkProducts.products[0] $httpBackend.flush() - expect(BulkProducts.addProducts).toHaveBeenCalledWith [clonedProduct] + expect(BulkProducts.insertProductAfter).toHaveBeenCalledWith originalProduct, clonedProduct describe "preparing products", -> diff --git a/spec/javascripts/unit/bulk_product_update_spec.js.coffee b/spec/javascripts/unit/bulk_product_update_spec.js.coffee index eb3830204c..1f761418d2 100644 --- a/spec/javascripts/unit/bulk_product_update_spec.js.coffee +++ b/spec/javascripts/unit/bulk_product_update_spec.js.coffee @@ -215,6 +215,7 @@ describe "filtering products for submission to database", -> variant_unit_scale: 1 variant_unit_name: 'loaf' available_on: available_on + tax_category_id: null master_attributes: id: 2 unit_value: 250 @@ -238,6 +239,7 @@ describe "AdminProductEditCtrl", -> module ($provide)-> $provide.value "producers", [] $provide.value "taxons", [] + $provide.value "tax_categories", [] $provide.value 'SpreeApiKey', 'API_KEY' null