diff --git a/app/assets/javascripts/admin/bulk_product_update.js.coffee b/app/assets/javascripts/admin/bulk_product_update.js.coffee index d7fe743904..500b4547d4 100644 --- a/app/assets/javascripts/admin/bulk_product_update.js.coffee +++ b/app/assets/javascripts/admin/bulk_product_update.js.coffee @@ -53,6 +53,9 @@ angular.module("ofn.admin").controller "AdminProductEditCtrl", ($scope, $timeout $scope.resetProducts() $scope.loading = false + $timeout -> + if $scope.showLatestImport + $scope.importDateFilter = $scope.importDates[1].id $scope.resetProducts = -> DirtyProducts.clear() diff --git a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee index ac6c993fef..0fe051f496 100644 --- a/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/controllers/variant_overrides_controller.js.coffee @@ -25,6 +25,7 @@ angular.module("admin.variantOverrides").controller "AdminVariantOverridesCtrl", $scope.resetSelectFilters = -> $scope.producerFilter = 0 + $scope.importDateFilter = '0' $scope.query = '' $scope.resetSelectFilters() diff --git a/app/assets/javascripts/admin/variant_overrides/filters/import_date_filter.js.coffee b/app/assets/javascripts/admin/variant_overrides/filters/import_date_filter.js.coffee new file mode 100644 index 0000000000..4392d6b3f6 --- /dev/null +++ b/app/assets/javascripts/admin/variant_overrides/filters/import_date_filter.js.coffee @@ -0,0 +1,12 @@ +angular.module("admin.variantOverrides").filter "importDate", ($filter, variantOverrides) -> + return (products, hub_id, date) -> + return [] if !hub_id + return $filter('filter')(products, (product) -> + return true if date == 0 or date == undefined or date == '0' or date == '' + + for variant in product.variants + for vo in variantOverrides + if vo.variant_id == variant.id and vo.import_date == date + return true + false + , true) \ No newline at end of file diff --git a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee index 4d875cbd33..84e6537a64 100644 --- a/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee +++ b/app/assets/javascripts/admin/variant_overrides/variant_overrides.js.coffee @@ -1 +1 @@ -angular.module("admin.variantOverrides", ["admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems", 'ngTagsInput']) +angular.module("admin.variantOverrides", ["ofn.admin", "admin.indexUtils", "admin.utils", "admin.dropdown", "admin.inventoryItems", 'ngTagsInput']) diff --git a/app/controllers/admin/variant_overrides_controller.rb b/app/controllers/admin/variant_overrides_controller.rb index 5aa1975e8d..cf978881a3 100644 --- a/app/controllers/admin/variant_overrides_controller.rb +++ b/app/controllers/admin/variant_overrides_controller.rb @@ -3,6 +3,7 @@ require 'open_food_network/spree_api_key_loader' module Admin class VariantOverridesController < ResourceController include OpenFoodNetwork::SpreeApiKeyLoader + include EnterprisesHelper prepend_before_filter :load_data before_filter :load_collection, only: [:bulk_update] @@ -55,6 +56,10 @@ module Admin variant_override_enterprises_per_hub @inventory_items = InventoryItem.where(enterprise_id: @hubs) + + import_dates = [{id: '0', name: 'All'}] + inventory_import_dates.map {|i| import_dates.push({id: i, name: i.to_formatted_s(:long)}) } + @import_dates = import_dates.to_json end def load_collection diff --git a/app/controllers/spree/admin/products_controller_decorator.rb b/app/controllers/spree/admin/products_controller_decorator.rb index 6061fe02b5..b6a71273f3 100644 --- a/app/controllers/spree/admin/products_controller_decorator.rb +++ b/app/controllers/spree/admin/products_controller_decorator.rb @@ -5,6 +5,7 @@ Spree::Admin::ProductsController.class_eval do include OpenFoodNetwork::SpreeApiKeyLoader include OrderCyclesHelper include EnterprisesHelper + before_filter :latest_import, only: [:bulk_edit] before_filter :load_form_data, :only => [:bulk_edit, :new, :create, :edit, :update] before_filter :load_spree_api_key, :only => [:bulk_edit, :variant_overrides] before_filter :strip_new_properties, only: [:create, :update] @@ -93,6 +94,10 @@ Spree::Admin::ProductsController.class_eval do private + def latest_import + @show_latest_import = params[:latest_import] || false + end + def load_form_data @producers = OpenFoodNetwork::Permissions.new(spree_current_user).managed_product_enterprises.is_primary_producer.by_name @taxons = Spree::Taxon.order(:name) diff --git a/app/models/product_importer.rb b/app/models/product_importer.rb index 509ecabf7e..549ef875cc 100644 --- a/app/models/product_importer.rb +++ b/app/models/product_importer.rb @@ -53,7 +53,7 @@ class ProductImporter end def persisted? - false #ActiveModel, not ActiveRecord + false # ActiveModel end def has_entries? @@ -188,7 +188,7 @@ class ProductImporter rows.each_with_index do |row, i| row_data = Hash[[headers, row].transpose] entry = SpreadsheetEntry.new(row_data) - entry.line_number = i+2 + entry.line_number = i + 2 @entries.push entry end @entries @@ -238,12 +238,7 @@ class ProductImporter def create_inventory_item(entry, existing_variant) existing_variant_override = VariantOverride.where(variant_id: existing_variant.id, hub_id: entry.supplier_id).first - if existing_variant_override - variant_override = existing_variant_override - else - variant_override = VariantOverride.new(variant_id: existing_variant.id, hub_id: entry.supplier_id) - end - + variant_override = existing_variant_override || VariantOverride.new(variant_id: existing_variant.id, hub_id: entry.supplier_id) variant_override.assign_attributes(count_on_hand: entry.on_hand, import_date: @import_time) check_on_hand_nil(entry, variant_override) variant_override.assign_attributes(entry.attributes.slice('price', 'on_demand')) @@ -260,7 +255,7 @@ class ProductImporter end def mark_as_inventory_item(entry, variant_override) - if variant_override.id? + if variant_override.id entry.is_a_valid('existing_inventory_item') entry.product_object = variant_override updates_count_per_supplier(entry.supplier_id) unless entry.has_errors? @@ -279,7 +274,8 @@ class ProductImporter where('variant_overrides.hub_id IN (?)', supplier_id). count else - products_count = Spree::Variant.joins(:product). + products_count = Spree::Variant. + joins(:product). where('spree_products.supplier_id IN (?) AND spree_variants.is_master = false AND spree_variants.deleted_at IS NULL', supplier_id). @@ -377,7 +373,7 @@ class ProductImporter @entries.each do |entry| supplier_name = entry.supplier supplier_id = @suppliers_index[supplier_name] || - Enterprise.find_by_name(supplier_name, :select => 'id, name').try(:id) + Enterprise.find_by_name(supplier_name, select: 'id, name').try(:id) @suppliers_index[supplier_name] = supplier_id end @suppliers_index @@ -388,7 +384,7 @@ class ProductImporter @entries.each do |entry| producer_name = entry.producer producer_id = @producers_index[producer_name] || - Enterprise.find_by_name(producer_name, :select => 'id, name').try(:id) + Enterprise.find_by_name(producer_name, select: 'id, name').try(:id) @producers_index[producer_name] = producer_id end @producers_index @@ -437,6 +433,7 @@ class ProductImporter product = Spree::Product.new() product.assign_attributes(entry.attributes.except('id')) assign_defaults(product, entry) + if product.save ensure_variant_updated(product, entry) @products_created += 1 @@ -473,6 +470,7 @@ class ProductImporter new_item = entry.product_object assign_defaults(new_item, entry) new_item.import_date = @import_time + if new_item.valid? and new_item.save display_in_inventory(new_item, true) @inventory_created += 1 @@ -486,6 +484,7 @@ class ProductImporter existing_item = entry.product_object assign_defaults(existing_item, entry) existing_item.import_date = @import_time + if existing_item.valid? and existing_item.save display_in_inventory(existing_item) @inventory_updated += 1 @@ -499,6 +498,7 @@ class ProductImporter new_variant = entry.product_object assign_defaults(new_variant, entry) new_variant.import_date = @import_time + if new_variant.valid? and new_variant.save @variants_created += 1 @updated_ids.push new_variant.id @@ -511,6 +511,7 @@ class ProductImporter variant = entry.product_object assign_defaults(variant, entry) variant.import_date = @import_time + if variant.valid? and variant.save @variants_updated += 1 @updated_ids.push variant.id @@ -584,7 +585,10 @@ class ProductImporter # Otherwise, if a variant exists with matching display_name and unit_value, update it match.variants.each do |existing_variant| - if existing_variant.display_name == entry.display_name and existing_variant.unit_value == Float(entry.unit_value) + if existing_variant.display_name == entry.display_name \ + and existing_variant.unit_value == Float(entry.unit_value) \ + and existing_variant.deleted_at == nil + mark_as_existing_variant(entry, existing_variant) return end @@ -597,6 +601,7 @@ class ProductImporter def mark_as_new_product(entry) new_product = Spree::Product.new() new_product.assign_attributes(entry.attributes.except('id')) + if new_product.valid? entry.is_a_valid 'new_product' unless entry.has_errors? else @@ -607,6 +612,7 @@ class ProductImporter def mark_as_existing_variant(entry, existing_variant) existing_variant.assign_attributes(entry.attributes.except('id', 'product_id')) check_on_hand_nil(entry, existing_variant) + if existing_variant.valid? entry.product_object = existing_variant entry.is_a_valid 'existing_variant' unless entry.has_errors? @@ -620,6 +626,7 @@ class ProductImporter new_variant = Spree::Variant.new(entry.attributes.except('id', 'product_id')) new_variant.product_id = product_id check_on_hand_nil(entry, new_variant) + if new_variant.valid? entry.product_object = new_variant entry.is_a_valid 'new_variant' unless entry.has_errors? @@ -629,7 +636,8 @@ class ProductImporter end def updates_count_per_supplier(supplier_id) - if @reset_counts[supplier_id] and @reset_counts[supplier_id][:updates_count] + if @reset_counts[supplier_id] \ + and @reset_counts[supplier_id][:updates_count] @reset_counts[supplier_id][:updates_count] += 1 else @reset_counts[supplier_id] = {updates_count: 1} @@ -637,17 +645,17 @@ class ProductImporter end def check_on_hand_nil(entry, object) - if entry.on_hand.blank? - object.on_hand = 0 if object.respond_to?(:on_hand) - object.count_on_hand = 0 if object.respond_to?(:count_on_hand) - entry.on_hand_nil = true - end + return unless entry.on_hand.blank? + + object.on_hand = 0 if object.respond_to?(:on_hand) + object.count_on_hand = 0 if object.respond_to?(:count_on_hand) + entry.on_hand_nil = true end def delete_uploaded_file # Only delete if file is in '/tmp/product_import' directory - if @file.path == Rails.root.join('tmp', 'product_import').to_s - File.delete(@file) - end + return unless @file.path == Rails.root.join('tmp', 'product_import').to_s + + File.delete(@file) end end diff --git a/app/views/admin/product_import/save.html.haml b/app/views/admin/product_import/save.html.haml index 1178617901..f60ada34ed 100644 --- a/app/views/admin/product_import/save.html.haml +++ b/app/views/admin/product_import/save.html.haml @@ -46,10 +46,18 @@ - if @importer.errors.count == 0 %p All #{@importer.total_saved_count} items saved successfully - else + %p #{@importer.total_saved_count} items saved successfully + %br %h5 Save errors - @importer.errors.full_messages.each do |error| %p.save-error  -  #{error} %br + - if @importer.total_saved_count > 0 + - if @import_into == 'inventories' + %a.button{href: main_app.admin_inventory_path} View Inventory + - else + %a.button{href: bulk_edit_admin_products_path + '?latest_import=true'} View Products + %a.button{href: main_app.admin_product_import_path} Back diff --git a/app/views/admin/variant_overrides/_filters.html.haml b/app/views/admin/variant_overrides/_filters.html.haml index b7a1c948ba..4126be9317 100644 --- a/app/views/admin/variant_overrides/_filters.html.haml +++ b/app/views/admin/variant_overrides/_filters.html.haml @@ -1,17 +1,22 @@ .filters.sixteen.columns.alpha.omega .filter.four.columns.alpha - %label{ :for => 'query', ng: {class: '{disabled: !hub_id}'} }=t('admin.quick_search') + %label{for: 'query', ng: {class: '{disabled: !hub_id}'} }=t('admin.quick_search') %br - %input.fullwidth{ :type => "text", :id => 'query', ng: { model: 'query', disabled: '!hub_id'} } - .two.columns   - .filter_select.four.columns - %label{ :for => 'hub_id', ng: { bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.variant_overrides.index.select_a_shop')}'" } } + %input.fullwidth{type: "text", id: 'query', ng: {model: 'query', disabled: '!hub_id'} } + .one.columns   + .filter_select.three.columns + %label{for: 'hub_id', ng: {bind: "hub_id ? '#{t('admin.shop')}' : '#{t('admin.variant_overrides.index.select_a_shop')}'" } } %br - %select.select2.fullwidth#hub_id{ 'ng-model' => 'hub_id', name: 'hub_id', ng: { options: 'hub.id as hub.name for (id, hub) in hubs' } } - .filter_select.four.columns - %label{ :for => 'producer_filter', ng: {class: '{disabled: !hub_id}'} }=t('admin.producer') + %select.select2.fullwidth#hub_id{name: 'hub_id', ng: {model: 'hub_id', options: 'hub.id as hub.name for (id, hub) in hubs' } } + .filter_select.three.columns + %label{for: 'producer_filter', ng: {class: '{disabled: !hub_id}'} }=t('admin.producer') %br - %input.ofn-select2.fullwidth{ :id => 'producer_filter', type: 'number', data: 'producers', blank: "{id: 0, name: '#{t(:all)}'}", ng: { model: 'producerFilter', disabled: '!hub_id' } } + %input.ofn-select2.fullwidth{id: 'producer_filter', type: 'number', data: 'producers', blank: "{id: 0, name: '#{t(:all)}'}", ng: {model: 'producerFilter', disabled: '!hub_id' } } + .filter_select.three.columns + %label{ :for => 'import_date_filter', ng: {class: '{disabled: !hub_id}'} } #{t('admin.variant_overrides.index.import_date')} + %br + %select.fullwidth{id: 'import_date_filter', 'ofn-select2-min-search' => 5, ng: {model: 'importDateFilter', options: 'date.id as date.name for date in import_dates', disabled: '!hub_id', init: "import_dates = #{@import_dates}"} } + %options{value: '0', selected: 'selected'} #{t(:all)} -# .filter_select{ :class => "three columns" } -# %label{ :for => 'distributor_filter' }Hub -# %br diff --git a/app/views/admin/variant_overrides/_products.html.haml b/app/views/admin/variant_overrides/_products.html.haml index 73848c5b6c..1acbfd2924 100644 --- a/app/views/admin/variant_overrides/_products.html.haml +++ b/app/views/admin/variant_overrides/_products.html.haml @@ -27,6 +27,6 @@ %th.tags{ ng: { show: 'columns.tags.visible' } }=t('admin.tags') %th.visibility{ ng: { show: 'columns.visibility.visible' } }=t('admin.variant_overrides.index.hide') %th.import_date{ ng: { show: 'columns.import_date.visible' } }=t('admin.variant_overrides.index.import_date') - %tbody{ ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | filter:query) | limitTo:productLimit' } } + %tbody{ ng: {repeat: 'product in filteredProducts = (products | hubPermissions:hubPermissions:hub_id | inventoryProducts:hub_id:views | attrFilter:{producer_id:producerFilter} | importDate:hub_id:importDateFilter | filter:query) | limitTo:productLimit' } } = render 'admin/variant_overrides/products_product' = render 'admin/variant_overrides/products_variants' diff --git a/app/views/spree/admin/products/bulk_edit/_filters.html.haml b/app/views/spree/admin/products/bulk_edit/_filters.html.haml index 3bbed52e05..72458b3391 100644 --- a/app/views/spree/admin/products/bulk_edit/_filters.html.haml +++ b/app/views/spree/admin/products/bulk_edit/_filters.html.haml @@ -1,23 +1,25 @@ .filters.sixteen.columns.alpha.omega .quick_search.three.columns.alpha - %label{ :for => 'quick_filter' } + %label{ for: 'quick_filter' } %br - %input.quick-search.fullwidth{ 'ng-model' => 'query', :name => "quick_filter", :type => 'text', 'placeholder' => t('admin.quick_search') } + %input.quick-search.fullwidth{ ng: {model: 'query'}, name: "quick_filter", type: 'text', placeholder: t('admin.quick_search') } + .one.columns   .filter_select.three.columns - %label{ :for => 'producer_filter' }= t 'producer' + %label{ for: 'producer_filter' }= t 'producer' %br - %select.fullwidth{ :id => 'producer_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'producerFilter', 'ng-options' => 'producer.id as producer.name for producer in filterProducers' } + %select.fullwidth{ id: 'producer_filter', 'ofn-select2-min-search' => 5, ng: {model: 'producerFilter', options: 'producer.id as producer.name for producer in filterProducers'} } .filter_select.three.columns - %label{ :for => 'category_filter' }= t 'category' + %label{ for: 'category_filter' }= t 'category' %br - %select.fullwidth{ :id => 'category_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'categoryFilter', 'ng-options' => 'taxon.id as taxon.name for taxon in filterTaxons'} + %select.fullwidth{ id: 'category_filter', 'ofn-select2-min-search' => 5, ng: {model: 'categoryFilter', options: 'taxon.id as taxon.name for taxon in filterTaxons'} } .filter_select.three.columns - %label{ :for => 'import_filter' } Import Date + %label{ for: 'import_filter' } Import Date %br - %select.fullwidth{ :id => 'import_date_filter', 'ofn-select2-min-search' => 5, 'ng-model' => 'importDateFilter', 'ng-init' => "import_dates = #{@import_dates}", 'ng-options' => 'import.id as import.name for import in import_dates'} + %select.fullwidth{ id: 'import_date_filter', 'ofn-select2-min-search' => 5, ng: {model: 'importDateFilter', init: "importDates = #{@import_dates}; showLatestImport = #{@show_latest_import}"}} + %option{value: "{{date.id}}", ng: {repeat: "date in importDates track by date.id" }} + {{date.name}} - %div{ :class => "one column" }   .filter_clear.three.columns.omega - %label{ :for => 'clear_all_filters' } + %label{ for: 'clear_all_filters' } %br %input.fullwidth.red{ :type => 'button', :id => 'clear_all_filters', :value => t('admin.clear_filters'), 'ng-click' => "resetSelectFilters()" } diff --git a/spec/features/admin/product_import_spec.rb b/spec/features/admin/product_import_spec.rb index 331b18c78b..401abd872c 100644 --- a/spec/features/admin/product_import_spec.rb +++ b/spec/features/admin/product_import_spec.rb @@ -31,7 +31,7 @@ feature "Product Import", js: true do before { quick_login_as_admin } after { File.delete('/tmp/test.csv') } - it "validates entries and saves them if they are all valid" do + it "validates entries and saves them if they are all valid and allows viewing new items in Bulk Products" do csv_data = CSV.generate do |csv| csv << ["name", "supplier", "category", "on_hand", "price", "unit_value", "variant_unit", "variant_unit_scale"] csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "weight", "1"] @@ -54,11 +54,19 @@ feature "Product Import", js: true do expect(page).to have_selector '.created-count', text: '2' expect(page).to_not have_selector '.updated-count' + carrots = Spree::Product.find_by_name('Carrots') potatoes = Spree::Product.find_by_name('Potatoes') potatoes.supplier.should == enterprise potatoes.on_hand.should == 6 potatoes.price.should == 6.50 potatoes.variants.first.import_date.should be_within(1.minute).of DateTime.now + + click_link 'View Products' + + expect(page).to have_content 'Bulk Edit Products' + wait_until { page.find("#p_#{potatoes.id}").present? } + expect(page).to have_field "product_name", with: carrots.name + expect(page).to have_field "product_name", with: potatoes.name end it "displays info about invalid entries but still allows saving of valid entries" do @@ -195,7 +203,7 @@ feature "Product Import", js: true do carrots = Spree::Product.find_by_name('Carrots') carrots.variants.first.import_date.should be_within(1.minute).of DateTime.now - visit 'admin/products/bulk_edit' + click_link 'View Products' wait_until { page.find("#p_#{carrots.id}").present? } @@ -246,7 +254,6 @@ feature "Product Import", js: true do expect(page).to have_selector '.inv-created-count', text: '2' expect(page).to have_selector '.inv-updated-count', text: '1' - beans_override = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first sprouts_override = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first cabbage_override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first @@ -259,6 +266,17 @@ feature "Product Import", js: true do Float(cabbage_override.price).should == 1.50 cabbage_override.count_on_hand.should == 2001 + + click_link 'View Inventory' + expect(page).to have_content 'Inventory' + + select enterprise2.name, from: "hub_id", visible: false + + within '#variant-overrides' do + expect(page).to have_content 'Beans' + expect(page).to have_content 'Sprouts' + expect(page).to have_content 'Cabbage' + end end end @@ -349,8 +367,6 @@ feature "Product Import", js: true do expect(page).to_not have_selector '.invalid-count' expect(page).to have_selector '.inv-create-count', text: "1" - #expect(page.body).to have_content 'you do not have permission' - click_button 'Save' expect(page).to have_selector '.inv-created-count', text: '1' @@ -379,8 +395,7 @@ feature "Product Import", js: true do expect(page).to_not have_selector '.inv-create-count' expect(page.body).to have_content 'you do not have permission' - - + expect(page).to_not have_selector 'input[type=submit][value="Save"]' end end