require 'spec_helper' require 'open_food_network/permissions' describe ProductImport::ProductImporter do include AuthenticationWorkflow let!(:admin) { create(:admin_user) } let!(:user) { create_enterprise_user } let!(:user2) { create_enterprise_user } let!(:user3) { create_enterprise_user } let!(:enterprise) { create(:enterprise, is_primary_producer: true, owner: user, name: "User Enterprise") } let!(:enterprise2) { create(:distributor_enterprise, is_primary_producer: true, owner: user2, name: "Another Enterprise") } let!(:enterprise3) { create(:distributor_enterprise, is_primary_producer: true, owner: user3, name: "And Another Enterprise") } let!(:enterprise4) { create(:enterprise, is_primary_producer: false, owner: user, name: "Non-Producer") } let!(:relationship) { create(:enterprise_relationship, parent: enterprise, child: enterprise2, permissions_list: [:create_variant_overrides]) } let!(:category) { create(:taxon, name: 'Vegetables') } let!(:category2) { create(:taxon, name: 'Cake') } let!(:category3) { create(:taxon, name: 'Meat') } let!(:category4) { create(:taxon, name: 'Cereal') } let!(:tax_category) { create(:tax_category) } let!(:tax_category2) { create(:tax_category) } let!(:shipping_category) { create(:shipping_category) } let!(:product) { create(:simple_product, supplier: enterprise2, name: 'Hypothetical Cake', description: nil, primary_taxon_id: category2.id) } let!(:variant) { create(:variant, product_id: product.id, price: '8.50', on_hand: '100', unit_value: '500', display_name: 'Preexisting Banana') } let!(:product2) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Beans', unit_value: '500', primary_taxon_id: category.id, description: nil) } let!(:product3) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Sprouts', unit_value: '500', primary_taxon_id: category.id) } let!(:product4) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Cabbage', unit_value: '1', variant_unit_scale: nil, variant_unit: "items", variant_unit_name: "Whole", primary_taxon_id: category.id) } let!(:product5) { create(:simple_product, supplier: enterprise2, on_hand: '100', name: 'Lettuce', unit_value: '500', primary_taxon_id: category.id) } let!(:product6) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Beetroot', unit_value: '500', on_demand: true, variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category.id, description: nil) } let!(:product7) { create(:simple_product, supplier: enterprise3, on_hand: '100', name: 'Tomato', unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category.id, description: nil) } let!(:product8) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Oats', description: "", unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category4.id) } let!(:product9) { create(:simple_product, supplier: enterprise, on_hand: '100', name: 'Oats', description: "", unit_value: '500', variant_unit_scale: 1, variant_unit: 'weight', primary_taxon_id: category4.id) } let!(:variant2) { create(:variant, product_id: product8.id, price: '4.50', on_hand: '100', unit_value: '500', display_name: 'Porridge Oats') } let!(:variant3) { create(:variant, product_id: product8.id, price: '5.50', on_hand: '100', unit_value: '500', display_name: 'Rolled Oats') } let!(:variant4) { create(:variant, product_id: product9.id, price: '6.50', on_hand: '100', unit_value: '500', display_name: 'Flaked Oats') } let!(:variant_override) { create(:variant_override, variant_id: product4.variants.first.id, hub: enterprise2, count_on_hand: 42) } let!(:variant_override2) { create(:variant_override, variant_id: product5.variants.first.id, hub: enterprise, count_on_hand: 96) } let(:permissions) { OpenFoodNetwork::Permissions.new(user) } after(:each) do File.delete('/tmp/test-m.csv') end describe "importing products from a spreadsheet" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "variant_unit_name", "on_demand", "shipping_category"] csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", "", "", shipping_category.name] csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "2", "kg", "", "", shipping_category.name] csv << ["Pea Soup", "User Enterprise", "Vegetables", "8", "5.50", "750", "ml", "", "0", shipping_category.name] csv << ["Salad", "User Enterprise", "Vegetables", "7", "4.50", "1", "", "bags", "", shipping_category.name] csv << ["Hot Cross Buns", "User Enterprise", "Cake", "7", "3.50", "1", "", "buns", "1", shipping_category.name] end } let(:importer) { import_data csv_data } it "returns the number of entries" do expect(importer.item_count).to eq(5) end it "validates entries and returns the results as json" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 5 expect(filter('invalid', entries)).to eq 0 expect(filter('create_product', entries)).to eq 5 expect(filter('update_product', entries)).to eq 0 end it "saves the results and returns info on updated products" do importer.save_entries expect(importer.products_created_count).to eq 5 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 5 carrots = Spree::Product.find_by_name('Carrots') expect(carrots.supplier).to eq enterprise expect(carrots.on_hand).to eq 5 expect(carrots.price).to eq 3.20 expect(carrots.unit_value).to eq 500 expect(carrots.variant_unit).to eq 'weight' expect(carrots.variant_unit_scale).to eq 1 expect(carrots.on_demand).to_not eq true expect(carrots.variants.first.import_date).to be_within(1.minute).of Time.zone.now potatoes = Spree::Product.find_by_name('Potatoes') expect(potatoes.supplier).to eq enterprise expect(potatoes.on_hand).to eq 6 expect(potatoes.price).to eq 6.50 expect(potatoes.unit_value).to eq 2000 expect(potatoes.variant_unit).to eq 'weight' expect(potatoes.variant_unit_scale).to eq 1000 expect(potatoes.on_demand).to_not eq true expect(potatoes.variants.first.import_date).to be_within(1.minute).of Time.zone.now pea_soup = Spree::Product.find_by_name('Pea Soup') expect(pea_soup.supplier).to eq enterprise expect(pea_soup.on_hand).to eq 8 expect(pea_soup.price).to eq 5.50 expect(pea_soup.unit_value).to eq 0.75 expect(pea_soup.variant_unit).to eq 'volume' expect(pea_soup.variant_unit_scale).to eq 0.001 expect(pea_soup.on_demand).to_not eq true expect(pea_soup.variants.first.import_date).to be_within(1.minute).of Time.zone.now salad = Spree::Product.find_by_name('Salad') expect(salad.supplier).to eq enterprise expect(salad.on_hand).to eq 7 expect(salad.price).to eq 4.50 expect(salad.unit_value).to eq 1 expect(salad.variant_unit).to eq 'items' expect(salad.variant_unit_scale).to eq nil expect(salad.on_demand).to_not eq true expect(salad.variants.first.import_date).to be_within(1.minute).of Time.zone.now buns = Spree::Product.find_by_name('Hot Cross Buns') expect(buns.supplier).to eq enterprise expect(buns.on_hand).to eq 7 expect(buns.price).to eq 3.50 expect(buns.unit_value).to eq 1 expect(buns.variant_unit).to eq 'items' expect(buns.variant_unit_scale).to eq nil expect(buns.on_demand).to eq true expect(buns.variants.first.import_date).to be_within(1.minute).of Time.zone.now end end describe "when uploading a spreadsheet with some invalid entries" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "shipping_category"] csv << ["Good Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", shipping_category.name] csv << ["Bad Potatoes", "", "Vegetables", "6", "6.50", "1", "", shipping_category.name] end } let(:importer) { import_data csv_data } it "validates entries" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 1 expect(filter('invalid', entries)).to eq 1 expect(filter('create_product', entries)).to eq 1 expect(filter('update_product', entries)).to eq 0 end it "allows saving of the valid entries" do importer.save_entries expect(importer.products_created_count).to eq 1 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 1 carrots = Spree::Product.find_by_name('Good Carrots') expect(carrots.supplier).to eq enterprise expect(carrots.on_hand).to eq 5 expect(carrots.price).to eq 3.20 expect(carrots.variants.first.import_date).to be_within(1.minute).of Time.zone.now expect(Spree::Product.find_by_name('Bad Potatoes')).to eq nil end end describe "when shipping category is missing" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "variant_unit_name", "on_demand", "shipping_category"] csv << ["Shipping Test", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", "", nil, nil] end } let(:importer) { import_data csv_data } it "raises an error" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(entries['2']['errors']['shipping_category']).to eq "Shipping_category can't be blank" end end describe "when enterprises are not valid" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type"] csv << ["Product 1", "Non-existent Enterprise", "Vegetables", "5", "5.50", "500", "g"] csv << ["Product 2", "Non-Producer", "Vegetables", "5", "5.50", "500", "g"] end } let(:importer) { import_data csv_data } it "adds enterprise errors" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(entries['2']['errors']['producer']).to include "not found in database" expect(entries['3']['errors']['producer']).to include "not enabled as a producer" end end describe "adding new variants to existing products and updating exiting products" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "display_name", "shipping_category"] csv << ["Hypothetical Cake", "Another Enterprise", "Cake", "5", "5.50", "500", "g", "Preexisting Banana", shipping_category.name] csv << ["Hypothetical Cake", "Another Enterprise", "Cake", "6", "3.50", "500", "g", "Emergent Coffee", shipping_category.name] end } let(:importer) { import_data csv_data } it "validates entries" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 2 expect(filter('invalid', entries)).to eq 0 expect(filter('create_product', entries)).to eq 1 expect(filter('update_product', entries)).to eq 1 end it "saves and updates" do importer.save_entries expect(importer.products_created_count).to eq 1 expect(importer.products_updated_count).to eq 1 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 2 added_coffee = Spree::Variant.find_by_display_name('Emergent Coffee') expect(added_coffee.product.name).to eq 'Hypothetical Cake' expect(added_coffee.price).to eq 3.50 expect(added_coffee.on_hand).to eq 6 expect(added_coffee.import_date).to be_within(1.minute).of Time.zone.now updated_banana = Spree::Variant.find_by_display_name('Preexisting Banana') expect(updated_banana.product.name).to eq 'Hypothetical Cake' expect(updated_banana.price).to eq 5.50 expect(updated_banana.on_hand).to eq 5 expect(updated_banana.import_date).to be_within(1.minute).of Time.zone.now end end describe "updating an exiting variant" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "description" ,"category", "on_hand", "price", "units", "unit_type", "display_name", "shipping_category"] csv << ["Hypothetical Cake", "Another Enterprise", "New Description", "Cake", "5", "5.50", "500", "g", "Preexisting Banana", shipping_category.name] end } let(:importer) { import_data csv_data } it "ignores (non-updatable) description field if it doesn't match the current description" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 1 expect(filter('invalid', entries)).to eq 0 expect(filter('update_product', entries)).to eq 1 end end describe "adding new product and sub-variant at the same time" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "display_name", "shipping_category"] csv << ["Potatoes", "User Enterprise", "Vegetables", "5", "3.50", "500", "g", "Small Bag", shipping_category.name] csv << ["Chives", "User Enterprise", "Vegetables", "6", "4.50", "500", "g", "Small Bag", shipping_category.name] csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "5.50", "2", "kg", "Big Bag", shipping_category.name] csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "22.00", "10000", "g", "Small Sack", shipping_category.name] csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "60.00", "30000", "", "Big Sack", shipping_category.name] end } let(:importer) { import_data csv_data } it "validates entries" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 3 expect(filter('invalid', entries)).to eq 2 expect(filter('create_product', entries)).to eq 3 end it "saves and updates" do importer.save_entries expect(importer.products_created_count).to eq 3 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 3 small_bag = Spree::Variant.find_by_display_name('Small Bag') expect(small_bag.product.name).to eq 'Potatoes' expect(small_bag.price).to eq 3.50 expect(small_bag.on_hand).to eq 5 big_bag = Spree::Variant.find_by_display_name("Big Bag") expect(big_bag).to be_blank small_sack = Spree::Variant.find_by_display_name("Small Sack") expect(small_sack.product.name).to eq "Potatoes" expect(small_sack.price).to eq 22.00 expect(small_sack.on_hand).to eq 6 expect(small_sack.product.id).to eq small_bag.product.id big_sack = Spree::Variant.find_by_display_name("Big Sack") expect(big_sack).to be_blank end end describe "updating various fields" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "on_demand", "sku", "shipping_category"] csv << ["Beetroot", "And Another Enterprise", "Vegetables", "5", "3.50", "500", "g", "0", nil, shipping_category.name] csv << ["Tomato", "And Another Enterprise", "Vegetables", "6", "5.50", "500", "g", "1", "TOMS", shipping_category.name] end } let(:importer) { import_data csv_data } it "validates entries" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 2 expect(filter('invalid', entries)).to eq 0 expect(filter('create_product', entries)).to eq 0 expect(filter('update_product', entries)).to eq 2 end it "saves and updates" do importer.save_entries expect(importer.products_created_count).to eq 0 expect(importer.products_updated_count).to eq 2 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 2 beetroot = Spree::Product.find_by_name('Beetroot').variants.first expect(beetroot.price).to eq 3.50 expect(beetroot.on_demand).to_not eq true tomato = Spree::Product.find_by_name('Tomato').variants.first expect(tomato.price).to eq 5.50 expect(tomato.on_demand).to eq true end end describe "updating non-updatable fields on existing products" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type"] csv << ["Beetroot", "And Another Enterprise", "Meat", "5", "3.50", "500", "g"] csv << ["Tomato", "And Another Enterprise", "Vegetables", "6", "5.50", "500", "Kg"] end } let(:importer) { import_data csv_data } it "does not allow updating" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 0 expect(filter('invalid', entries)).to eq 2 importer.entries.each do |entry| expect(entry.errors.messages.values).to include [I18n.t('admin.product_import.model.not_updatable')] end end end describe "when more than one product of the same name already exists with multiple variants each" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "description", "on_hand", "price", "units", "unit_type", "display_name", "shipping_category"] csv << ["Oats", "User Enterprise", "Cereal", "", "50", "3.50", "500", "g", "Rolled Oats", shipping_category.name] # Update csv << ["Oats", "User Enterprise", "Cereal", "", "80", "3.75", "500", "g", "Flaked Oats", shipping_category.name] # Update csv << ["Oats", "User Enterprise", "Cereal", "", "60", "5.50", "500", "g", "Magic Oats", shipping_category.name] # Add csv << ["Oats", "User Enterprise", "Cereal", "", "70", "8.50", "500", "g", "French Oats", shipping_category.name] # Add csv << ["Oats", "User Enterprise", "Cereal", "", "70", "8.50", "500", "g", "Scottish Oats", shipping_category.name] # Add end } let(:importer) { import_data csv_data } it "validates entries" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 5 expect(filter('invalid', entries)).to eq 0 expect(filter('create_product', entries)).to eq 3 expect(filter('update_product', entries)).to eq 2 expect(filter('create_inventory', entries)).to eq 0 expect(filter('update_inventory', entries)).to eq 0 end it "saves and updates" do importer.save_entries expect(importer.products_created_count).to eq 3 expect(importer.products_updated_count).to eq 2 expect(importer.inventory_created_count).to eq 0 expect(importer.inventory_updated_count).to eq 0 expect(importer.updated_ids.count).to eq 5 end end describe "when importer processes create and update across multiple stages" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "display_name", "shipping_category"] csv << ["Bag of Oats", "User Enterprise", "Cereal", "60", "5.50", "500", "g", "Magic Oats", shipping_category.name] # Add csv << ["Bag of Oats", "User Enterprise", "Cereal", "70", "8.50", "500", "g", "French Oats", shipping_category.name] # Add csv << ["Bag of Oats", "User Enterprise", "Cereal", "80", "9.50", "500", "g", "Organic Oats", shipping_category.name] # Add csv << ["Bag of Oats", "User Enterprise", "Cereal", "90", "7.50", "500", "g", "Scottish Oats", shipping_category.name] # Add csv << ["Bag of Oats", "User Enterprise", "Cereal", "30", "6.50", "500", "g", "Breakfast Oats", shipping_category.name] # Add end } it "processes the validation in stages" do # Using settings of start: 1, end: 3 to simulate import over multiple stages importer = import_data csv_data, start: 1, end: 3 importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 3 expect(filter('invalid', entries)).to eq 0 expect(filter('create_product', entries)).to eq 3 expect(filter('update_product', entries)).to eq 0 expect(filter('create_inventory', entries)).to eq 0 expect(filter('update_inventory', entries)).to eq 0 importer = import_data csv_data, start: 4, end: 6 importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 2 expect(filter('invalid', entries)).to eq 0 expect(filter('create_product', entries)).to eq 2 expect(filter('update_product', entries)).to eq 0 expect(filter('create_inventory', entries)).to eq 0 expect(filter('update_inventory', entries)).to eq 0 end it "processes saving in stages" do importer = import_data csv_data, start: 1, end: 3 importer.save_entries expect(importer.products_created_count).to eq 3 expect(importer.products_updated_count).to eq 0 expect(importer.inventory_created_count).to eq 0 expect(importer.inventory_updated_count).to eq 0 expect(importer.updated_ids.count).to eq 3 importer = import_data csv_data, start: 4, end: 6 importer.save_entries expect(importer.products_created_count).to eq 2 expect(importer.products_updated_count).to eq 0 expect(importer.inventory_created_count).to eq 0 expect(importer.inventory_updated_count).to eq 0 expect(importer.updated_ids.count).to eq 2 products = Spree::Product.find_all_by_name('Bag of Oats') expect(products.count).to eq 1 expect(products.first.variants.count).to eq 5 end end describe "importing items into inventory" do describe "creating and updating inventory" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "distributor", "producer", "on_hand", "price", "units", "unit_type", "variant_unit_name"] csv << ["Beans", "Another Enterprise", "User Enterprise", "5", "3.20", "500", "g", ""] csv << ["Sprouts", "Another Enterprise", "User Enterprise", "6", "6.50", "500", "g", ""] csv << ["Cabbage", "Another Enterprise", "User Enterprise", "2001", "1.50", "1", "", "Whole"] end } let(:importer) { import_data csv_data, import_into: 'inventories' } it "validates entries" do importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 3 expect(filter('invalid', entries)).to eq 0 expect(filter('create_inventory', entries)).to eq 2 expect(filter('update_inventory', entries)).to eq 1 end it "saves and updates inventory" do importer.save_entries expect(importer.inventory_created_count).to eq 2 expect(importer.inventory_updated_count).to eq 1 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 3 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 expect(Float(beans_override.price)).to eq 3.20 expect(beans_override.count_on_hand).to eq 5 expect(Float(sprouts_override.price)).to eq 6.50 expect(sprouts_override.count_on_hand).to eq 6 expect(Float(cabbage_override.price)).to eq 1.50 expect(cabbage_override.count_on_hand).to eq 2001 end end describe "updating existing inventory referenced by display_name" do let(:csv_data) { CSV.generate do |csv| csv << ["name", "display_name", "distributor", "producer", "on_hand", "price", "units"] csv << ["Oats", "Porridge Oats", "Another Enterprise", "User Enterprise", "900", "", "500"] end } let(:importer) { import_data csv_data, import_into: 'inventories' } it "updates inventory item correctly" do importer.save_entries expect(importer.inventory_created_count).to eq 1 override = VariantOverride.where(variant_id: variant2.id, hub_id: enterprise2.id).first visible = InventoryItem.where(variant_id: variant2.id, enterprise_id: enterprise2.id).first.visible expect(override.count_on_hand).to eq 900 expect(visible).to be_truthy end end describe "updating existing item that was set to hidden in inventory" do let!(:inventory) { InventoryItem.create(variant_id: product4.variants.first.id, enterprise_id: enterprise2.id, visible: false) } let(:csv_data) { CSV.generate do |csv| csv << ["name", "distributor", "producer", "on_hand", "price", "units", "variant_unit_name"] csv << ["Cabbage", "Another Enterprise", "User Enterprise", "900", "", "1", "Whole"] end } let(:importer) { import_data csv_data, import_into: 'inventories' } it "sets the item to visible in inventory when the item is updated" do importer.save_entries expect(importer.inventory_updated_count).to eq 1 override = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first visible = InventoryItem.where(variant_id: product4.variants.first.id, enterprise_id: enterprise2.id).first.visible expect(override.count_on_hand).to eq 900 expect(visible).to be_truthy end end end describe "handling enterprise permissions" do it "only allows product import into enterprises the user is permitted to manage" do csv_data = CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "shipping_category"] csv << ["My Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", shipping_category.name] csv << ["Your Potatoes", "Another Enterprise", "Vegetables", "6", "6.50", "1", "kg", shipping_category.name] end importer = import_data csv_data, import_user: user importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 1 expect(filter('invalid', entries)).to eq 1 expect(filter('create_product', entries)).to eq 1 importer.save_entries expect(importer.products_created_count).to eq 1 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 1 expect(Spree::Product.find_by_name('My Carrots')).to be_a Spree::Product expect(Spree::Product.find_by_name('Your Potatoes')).to eq nil end it "allows creating inventories for producers that a user's hub has permission for" do csv_data = CSV.generate do |csv| csv << ["name", "producer", "distributor", "on_hand", "price", "units", "unit_type"] csv << ["Beans", "User Enterprise", "Another Enterprise", "777", "3.20", "500", "g"] end importer = import_data csv_data, import_into: 'inventories' importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 1 expect(filter('invalid', entries)).to eq 0 expect(filter('create_inventory', entries)).to eq 1 importer.save_entries expect(importer.inventory_created_count).to eq 1 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 1 beans = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first expect(beans.count_on_hand).to eq 777 end it "does not allow creating inventories for producers that a user's hubs don't have permission for" do csv_data = CSV.generate do |csv| csv << ["name", "producer", "on_hand", "price", "units", "unit_type"] csv << ["Beans", "User Enterprise", "5", "3.20", "500", "g"] csv << ["Sprouts", "User Enterprise", "6", "6.50", "500", "g"] end importer = import_data csv_data, import_into: 'inventories' importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 0 expect(filter('invalid', entries)).to eq 2 expect(filter('create_inventory', entries)).to eq 0 importer.save_entries expect(importer.inventory_created_count).to eq 0 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 0 end end describe "applying settings and defaults on import" do it "can reset all products for an enterprise that are not present in the uploaded file to zero stock" do csv_data = CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "shipping_category"] csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", shipping_category.name] csv << ["Beans", "User Enterprise", "Vegetables", "6", "6.50", "500", "g", shipping_category.name] end importer = import_data csv_data, reset_all_absent: true importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 2 expect(filter('invalid', entries)).to eq 0 expect(filter('create_product', entries)).to eq 1 expect(filter('update_product', entries)).to eq 1 importer.save_entries expect(importer.products_created_count).to eq 1 expect(importer.products_updated_count).to eq 1 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 2 updated_ids = importer.updated_ids importer = import_data csv_data, reset_all_absent: true, updated_ids: updated_ids, enterprises_to_reset: [enterprise.id] importer.reset_absent(updated_ids) expect(importer.products_reset_count).to eq 7 expect(Spree::Product.find_by_name('Carrots').on_hand).to eq 5 # Present in file, added expect(Spree::Product.find_by_name('Beans').on_hand).to eq 6 # Present in file, updated expect(Spree::Product.find_by_name('Sprouts').on_hand).to eq 0 # In enterprise, not in file expect(Spree::Product.find_by_name('Cabbage').on_hand).to eq 0 # In enterprise, not in file expect(Spree::Product.find_by_name('Lettuce').on_hand).to eq 100 # In different enterprise; unchanged end it "can reset all inventory items for an enterprise that are not present in the uploaded file to zero stock" do csv_data = CSV.generate do |csv| csv << ["name", "distributor", "producer", "on_hand", "price", "units", "unit_type"] csv << ["Beans", "Another Enterprise", "User Enterprise", "6", "3.20", "500", "g"] csv << ["Sprouts", "Another Enterprise", "User Enterprise", "7", "6.50", "500", "g"] end importer = import_data csv_data, import_into: 'inventories', reset_all_absent: true importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 2 expect(filter('invalid', entries)).to eq 0 expect(filter('create_inventory', entries)).to eq 2 importer.save_entries expect(importer.inventory_created_count).to eq 2 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 2 updated_ids = importer.updated_ids importer = import_data csv_data, import_into: 'inventories', reset_all_absent: true, updated_ids: updated_ids, enterprises_to_reset: [enterprise2.id] importer.reset_absent(updated_ids) beans = VariantOverride.where(variant_id: product2.variants.first.id, hub_id: enterprise2.id).first sprouts = VariantOverride.where(variant_id: product3.variants.first.id, hub_id: enterprise2.id).first cabbage = VariantOverride.where(variant_id: product4.variants.first.id, hub_id: enterprise2.id).first lettuce = VariantOverride.where(variant_id: product5.variants.first.id, hub_id: enterprise.id).first expect(beans.count_on_hand).to eq 6 # Present in file, created expect(sprouts.count_on_hand).to eq 7 # Present in file, created expect(cabbage.count_on_hand).to eq 0 # In enterprise, not in file (reset) expect(lettuce.count_on_hand).to eq 96 # In different enterprise; unchanged end it "can overwrite fields with selected defaults when importing to product list" do csv_data = CSV.generate do |csv| csv << ["name", "producer", "category", "on_hand", "price", "units", "unit_type", "tax_category_id", "available_on", "shipping_category"] csv << ["Carrots", "User Enterprise", "Vegetables", "5", "3.20", "500", "g", tax_category.id, "", shipping_category.name] csv << ["Potatoes", "User Enterprise", "Vegetables", "6", "6.50", "1", "kg", "", "", shipping_category.name] end settings = { enterprise.id.to_s => { 'import_into' => 'product_list', 'defaults' => { 'on_hand' => { 'active' => true, 'mode' => 'overwrite_all', 'value' => '9000' }, 'tax_category_id' => { 'active' => true, 'mode' => 'overwrite_empty', 'value' => tax_category2.id }, 'shipping_category_id' => { 'active' => true, 'mode' => 'overwrite_all', 'value' => shipping_category.id }, 'available_on' => { 'active' => true, 'mode' => 'overwrite_all', 'value' => '2020-01-01' } } } } importer = import_data csv_data, settings: settings importer.validate_entries entries = JSON.parse(importer.entries_json) expect(filter('valid', entries)).to eq 2 expect(filter('invalid', entries)).to eq 0 expect(filter('create_product', entries)).to eq 2 importer.save_entries expect(importer.products_created_count).to eq 2 expect(importer.updated_ids).to be_a(Array) expect(importer.updated_ids.count).to eq 2 carrots = Spree::Product.find_by_name('Carrots') expect(carrots.on_hand).to eq 9000 expect(carrots.tax_category_id).to eq tax_category.id expect(carrots.shipping_category_id).to eq shipping_category.id expect(carrots.available_on).to be_within(1.day).of(Time.zone.local(2020, 1, 1)) potatoes = Spree::Product.find_by_name('Potatoes') expect(potatoes.on_hand).to eq 9000 expect(potatoes.tax_category_id).to eq tax_category2.id expect(potatoes.shipping_category_id).to eq shipping_category.id expect(potatoes.available_on).to be_within(1.day).of(Time.zone.local(2020, 1, 1)) end end end private def import_data(csv_data, args = {}) import_user = args[:import_user] || admin import_into = args[:import_into] || 'product_list' start_row = args[:start] || 1 end_row = args[:end] || 100 reset_all_absent = args[:reset_all_absent] || false updated_ids = args[:updated_ids] || nil enterprises_to_reset = args[:enterprises_to_reset] || nil settings = args[:settings] || { 'import_into' => import_into, 'reset_all_absent' => reset_all_absent } File.write('/tmp/test-m.csv', csv_data) @file ||= File.new('/tmp/test-m.csv') ProductImport::ProductImporter.new(@file, import_user, start: start_row, end: end_row, updated_ids: updated_ids, enterprises_to_reset: enterprises_to_reset, settings: settings) end def filter(type, entries) valid_count = 0 entries.each do |_line_number, entry| validates_as = entry['validates_as'] valid_count += 1 if type == 'valid' && (validates_as != '') valid_count += 1 if type == 'invalid' && (validates_as == '') valid_count += 1 if type == 'create_product' && (validates_as == 'new_product' || validates_as == 'new_variant') valid_count += 1 if type == 'update_product' && validates_as == 'existing_variant' valid_count += 1 if type == 'create_inventory' && validates_as == 'new_inventory_item' valid_count += 1 if type == 'update_inventory' && validates_as == 'existing_inventory_item' end valid_count end