Files
openfoodnetwork/spec/models/product_importer_spec.rb
2020-06-22 17:39:20 +01:00

774 lines
35 KiB
Ruby

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", enterprise.name, "Vegetables", "5", "3.20", "500", "g", "", "", shipping_category.name]
csv << ["Potatoes", enterprise.name, "Vegetables", "6", "6.50", "2", "kg", "", "", shipping_category.name]
csv << ["Pea Soup", enterprise.name, "Vegetables", "8", "5.50", "750", "ml", "", "0", shipping_category.name]
csv << ["Salad", enterprise.name, "Vegetables", "7", "4.50", "1", "", "bags", "", shipping_category.name]
csv << ["Hot Cross Buns", enterprise.name, "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", enterprise.name, "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 uploading a spreadsheet with some malformed data" do
# Use a simple string as CSV.generate will do some escaping
let(:csv_data) {
csv = "name,producer,category,on_hand,price,units,unit_type,shipping_category\n"
csv += "Good Carrots,#{enterprise.name},Vegetables,5,3.20,500,g,#{shipping_category.name}\n"
csv += "Malformed \rBrocolli,#{enterprise.name},Vegetables,8,2.50,200,g,#{shipping_category.name}\n"
}
let(:importer) { import_data csv_data }
# an unquoted \n will create a non valid line which will fail entry validation hence why we are only testing with \r
it "should raise an unquoted field error if data include unquoted field with \r character" do
expect(importer.errors.messages.values).to include(
[I18n.t('admin.product_import.model.malformed_csv', error_message: "Unquoted fields do not allow \\r or \\n (line 3).")]
)
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", enterprise.name, "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", enterprise4.name, "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", enterprise2.name, "Cake", "5", "5.50", "500", "g", "Preexisting Banana", shipping_category.name]
csv << ["Hypothetical Cake", enterprise2.name, "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", enterprise2.name, "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", enterprise.name, "Vegetables", "5", "3.50", "500", "g", "Small Bag", shipping_category.name]
csv << ["Chives", enterprise.name, "Vegetables", "6", "4.50", "500", "g", "Bunch", shipping_category.name]
csv << ["Potatoes", enterprise.name, "Vegetables", "6", "5.50", "2", "kg", "Big Bag", shipping_category.name]
csv << ["Potatoes", enterprise.name, "Vegetables", "6", "22.00", "10000", "g", "Small Sack", shipping_category.name]
csv << ["Potatoes", enterprise.name, "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", enterprise3.name, "Vegetables", "5", "3.50", "500", "g", "0", nil, shipping_category.name]
csv << ["Tomato", enterprise3.name, "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", enterprise3.name, "Meat", "5", "3.50", "500", "g"]
csv << ["Tomato", enterprise3.name, "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", enterprise.name, "Cereal", "", "50", "3.50", "500", "g", "Rolled Oats", shipping_category.name] # Update
csv << ["Oats", enterprise.name, "Cereal", "", "80", "3.75", "500", "g", "Flaked Oats", shipping_category.name] # Update
csv << ["Oats", enterprise.name, "Cereal", "", "60", "5.50", "500", "g", "Magic Oats", shipping_category.name] # Add
csv << ["Oats", enterprise.name, "Cereal", "", "70", "8.50", "500", "g", "French Oats", shipping_category.name] # Add
csv << ["Oats", enterprise.name, "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", enterprise.name, "Cereal", "60", "5.50", "500", "g", "Magic Oats", shipping_category.name] # Add
csv << ["Bag of Oats", enterprise.name, "Cereal", "70", "8.50", "500", "g", "French Oats", shipping_category.name] # Add
csv << ["Bag of Oats", enterprise.name, "Cereal", "80", "9.50", "500", "g", "Organic Oats", shipping_category.name] # Add
csv << ["Bag of Oats", enterprise.name, "Cereal", "90", "7.50", "500", "g", "Scottish Oats", shipping_category.name] # Add
csv << ["Bag of Oats", enterprise.name, "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", enterprise2.name, enterprise.name, "5", "3.20", "500", "g", ""]
csv << ["Sprouts", enterprise2.name, enterprise.name, "6", "6.50", "500", "g", ""]
csv << ["Cabbage", enterprise2.name, enterprise.name, "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", enterprise2.name, enterprise.name, "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", enterprise2.name, enterprise.name, "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", enterprise.name, "Vegetables", "5", "3.20", "500", "g", shipping_category.name]
csv << ["Your Potatoes", enterprise2.name, "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", enterprise.name, enterprise2.name, "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", enterprise.name, "5", "3.20", "500", "g"]
csv << ["Sprouts", enterprise.name, "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", enterprise.name, "Vegetables", "5", "3.20", "500", "g", shipping_category.name]
csv << ["Beans", enterprise.name, "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", enterprise2.name, enterprise.name, "6", "3.20", "500", "g"]
csv << ["Sprouts", enterprise2.name, enterprise.name, "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
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' && ['new_product', 'new_variant'].include?(validates_as)
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