diff --git a/plugins/article_import/app/overrides/controllers/articles_controller_override.rb b/plugins/article_import/app/overrides/controllers/articles_controller_override.rb
index 43e9773b..7777064e 100644
--- a/plugins/article_import/app/overrides/controllers/articles_controller_override.rb
+++ b/plugins/article_import/app/overrides/controllers/articles_controller_override.rb
@@ -1,18 +1,20 @@
-ArticlesController.class_eval do
- def parse_upload
- uploaded_file = params[:articles]['file'] or raise I18n.t('articles.controller.parse_upload.no_file')
- type = params[:articles]['type']
- options = { filename: uploaded_file.original_filename }
- options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1')
- options[:convert_units] = (params[:articles]['convert_units'] == '1')
- options[:update_category] = (params[:articles]['update_category'] == '1')
+if FoodsoftArticleImport.enabled?
+ ArticlesController.class_eval do
+ def parse_upload
+ uploaded_file = params[:articles]['file'] or raise I18n.t('articles.controller.parse_upload.no_file')
+ type = params[:articles]['type']
+ options = { filename: uploaded_file.original_filename }
+ options[:outlist_absent] = (params[:articles]['outlist_absent'] == '1')
+ options[:convert_units] = (params[:articles]['convert_units'] == '1')
+ options[:update_category] = (params[:articles]['update_category'] == '1')
- @updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile, type, options
- if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
- redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.parse_upload.notice')
+ @updated_article_pairs, @outlisted_articles, @new_articles = @supplier.sync_from_file uploaded_file.tempfile, type, options
+ if @updated_article_pairs.empty? && @outlisted_articles.empty? && @new_articles.empty?
+ redirect_to supplier_articles_path(@supplier), :notice => I18n.t('articles.controller.parse_upload.notice')
+ end
+ @ignored_article_count = 0
+ rescue => error
+ redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message)
end
- @ignored_article_count = 0
- rescue => error
- redirect_to upload_supplier_articles_path(@supplier), :alert => I18n.t('errors.general_msg', :msg => error.message)
end
-end
+end
\ No newline at end of file
diff --git a/plugins/article_import/app/overrides/models/article_override.rb b/plugins/article_import/app/overrides/models/article_override.rb
index 2018a4e7..5ca177ad 100644
--- a/plugins/article_import/app/overrides/models/article_override.rb
+++ b/plugins/article_import/app/overrides/models/article_override.rb
@@ -1,37 +1,39 @@
-Article.class_eval do
- def unequal_attributes(new_article, options = {})
- # try to convert different units when desired
- if options[:convert_units] == false
- new_price = nil
- new_unit_quantity = nil
- else
- new_price, new_unit_quantity = convert_units(new_article)
- end
- if new_price && new_unit_quantity
- new_unit = self.unit
- else
- new_price = new_article.price
- new_unit_quantity = new_article.unit_quantity
- new_unit = new_article.unit
- end
+if FoodsoftArticleImport.enabled?
+ Article.class_eval do
+ def unequal_attributes(new_article, options = {})
+ # try to convert different units when desired
+ if options[:convert_units] == false
+ new_price = nil
+ new_unit_quantity = nil
+ else
+ new_price, new_unit_quantity = convert_units(new_article)
+ end
+ if new_price && new_unit_quantity
+ new_unit = self.unit
+ else
+ new_price = new_article.price
+ new_unit_quantity = new_article.unit_quantity
+ new_unit = new_article.unit
+ end
- attribute_hash = {
- :name => [self.name, new_article.name],
- :manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
- :origin => [self.origin, new_article.origin],
- :unit => [self.unit, new_unit],
- :price => [self.price.to_f.round(2), new_price.to_f.round(2)],
- :tax => [self.tax, new_article.tax],
- :deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
- # take care of different num-objects.
- :unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
- :note => [self.note.to_s, new_article.note.to_s]
- }
- if options[:update_category] == true
- new_article_category = new_article.article_category
- attribute_hash[:article_category] = [self.article_category, new_article_category] unless new_article_category.blank?
- end
+ attribute_hash = {
+ :name => [self.name, new_article.name],
+ :manufacturer => [self.manufacturer, new_article.manufacturer.to_s],
+ :origin => [self.origin, new_article.origin],
+ :unit => [self.unit, new_unit],
+ :price => [self.price.to_f.round(2), new_price.to_f.round(2)],
+ :tax => [self.tax, new_article.tax],
+ :deposit => [self.deposit.to_f.round(2), new_article.deposit.to_f.round(2)],
+ # take care of different num-objects.
+ :unit_quantity => [self.unit_quantity.to_s.to_f, new_unit_quantity.to_s.to_f],
+ :note => [self.note.to_s, new_article.note.to_s]
+ }
+ if options[:update_category] == true
+ new_article_category = new_article.article_category
+ attribute_hash[:article_category] = [self.article_category, new_article_category] unless new_article_category.blank?
+ end
- Article.compare_attributes(attribute_hash)
+ Article.compare_attributes(attribute_hash)
+ end
end
-end
+end
\ No newline at end of file
diff --git a/plugins/article_import/app/overrides/models/supplier_override.rb b/plugins/article_import/app/overrides/models/supplier_override.rb
index 504bc26d..62885aac 100644
--- a/plugins/article_import/app/overrides/models/supplier_override.rb
+++ b/plugins/article_import/app/overrides/models/supplier_override.rb
@@ -1,51 +1,53 @@
-Supplier.class_eval do
- # Synchronise articles with spreadsheet.
- #
- # @param file [File] Spreadsheet file to parse
- # @param options [Hash] Options passed to {FoodsoftArticleImport#parse} except when listed here.
- # @option options [Boolean] :outlist_absent Set to +true+ to remove articles not in spreadsheet.
- # @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price.
- def sync_from_file(file, type, options = {})
- all_order_numbers = []
- updated_article_pairs, outlisted_articles, new_articles = [], [], []
- custom_codes_path = File.join(Rails.root, "config", "custom_codes.yml")
- opts = options.except(:convert_units, :outlist_absent)
- custom_codes_file_path = custom_codes_path if File.exist?(custom_codes_path)
- FoodsoftArticleImport.parse(file, custom_file_path: custom_codes_file_path, type: type, **opts) do |new_attrs, status, line|
- article = articles.undeleted.where(order_number: new_attrs[:order_number]).first
+if FoodsoftArticleImport.enabled?
+ Supplier.class_eval do
+ # Synchronise articles with spreadsheet.
+ #
+ # @param file [File] Spreadsheet file to parse
+ # @param options [Hash] Options passed to {FoodsoftArticleImport#parse} except when listed here.
+ # @option options [Boolean] :outlist_absent Set to +true+ to remove articles not in spreadsheet.
+ # @option options [Boolean] :convert_units Omit or set to +true+ to keep current units, recomputing unit quantity and price.
+ def sync_from_file(file, type, options = {})
+ all_order_numbers = []
+ updated_article_pairs, outlisted_articles, new_articles = [], [], []
+ custom_codes_path = File.join(Rails.root, "config", "custom_codes.yml")
+ opts = options.except(:convert_units, :outlist_absent)
+ custom_codes_file_path = custom_codes_path if File.exist?(custom_codes_path)
+ FoodsoftArticleImport.parse(file, custom_file_path: custom_codes_file_path, type: type, **opts) do |new_attrs, status, line|
+ article = articles.undeleted.where(order_number: new_attrs[:order_number]).first
- if new_attrs[:article_category].present? && options[:update_category]
- new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category]) || ArticleCategory.create_or_find_by!(name: new_attrs[:article_category])
- else
- new_attrs[:article_category] = nil
- end
-
- new_attrs[:tax] ||= FoodsoftConfig[:tax_default]
- new_article = articles.build(new_attrs)
- if status.nil?
- if article.nil?
- new_articles << new_article
+ if new_attrs[:article_category].present? && options[:update_category]
+ new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category]) || ArticleCategory.create_or_find_by!(name: new_attrs[:article_category])
else
- unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units, :update_category))
- unless unequal_attributes.empty?
- article.attributes = unequal_attributes
- updated_article_pairs << [article, unequal_attributes]
- end
+ new_attrs[:article_category] = nil
end
- elsif status == :outlisted && article.present?
- outlisted_articles << article
- # stop when there is a parsing error
- elsif status.is_a? String
- # @todo move I18n key to model
- raise I18n.t('articles.model.error_parse', :msg => status, :line => line.to_s)
+ new_attrs[:tax] ||= FoodsoftConfig[:tax_default]
+ new_article = articles.build(new_attrs)
+ if status.nil?
+ if article.nil?
+ new_articles << new_article
+ else
+ unequal_attributes = article.unequal_attributes(new_article, options.slice(:convert_units, :update_category))
+ unless unequal_attributes.empty?
+ article.attributes = unequal_attributes
+ updated_article_pairs << [article, unequal_attributes]
+ end
+ end
+ elsif status == :outlisted && article.present?
+ outlisted_articles << article
+
+ # stop when there is a parsing error
+ elsif status.is_a? String
+ # @todo move I18n key to model
+ raise I18n.t('articles.model.error_parse', :msg => status, :line => line.to_s)
+ end
+
+ all_order_numbers << article.order_number if article
end
-
- all_order_numbers << article.order_number if article
+ if options[:outlist_absent]
+ outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil])
+ end
+ [updated_article_pairs, outlisted_articles, new_articles]
end
- if options[:outlist_absent]
- outlisted_articles += articles.undeleted.where.not(order_number: all_order_numbers + [nil])
- end
- [updated_article_pairs, outlisted_articles, new_articles]
end
end
diff --git a/plugins/article_import/lib/foodsoft_article_import/engine.rb b/plugins/article_import/lib/foodsoft_article_import/engine.rb
index 49c54bab..a2eee118 100644
--- a/plugins/article_import/lib/foodsoft_article_import/engine.rb
+++ b/plugins/article_import/lib/foodsoft_article_import/engine.rb
@@ -6,7 +6,7 @@ module FoodsoftArticleImport
end
end
def default_foodsoft_config(cfg)
- cfg[:use_article_import] = true
+ cfg[:use_article_import] = false
end
end
end
diff --git a/plugins/article_import/spec/app_config.yml b/plugins/article_import/spec/app_config.yml
new file mode 100644
index 00000000..e31af571
--- /dev/null
+++ b/plugins/article_import/spec/app_config.yml
@@ -0,0 +1,34 @@
+# Minimal Foodsoft configuration
+#
+# Without those settings, Foodsoft may not even work.
+# This file is used when running tests. When plugins would modify foodsoft behaviour
+# and they are enabled in the sample configuration, there is stable base to test with.
+
+default: &defaults
+ multi_coop_install: false
+ use_self_service: true
+ default_scope: 'f'
+
+ name: FC Minimal
+
+ # true by default to keep compat with older installations, but test with false here
+ use_nick: false
+ use_article_import: true
+
+ price_markup: 5
+
+ # do we really need the following ones?
+ tax_default: 6.0
+ email_sender: noreply@minimal.test
+
+ host: localhost
+
+
+development:
+ <<: *defaults
+
+test:
+ <<: *defaults
+
+production:
+ <<: *defaults
diff --git a/plugins/article_import/spec/fixtures/bnn_file01.bnn b/plugins/article_import/spec/fixtures/bnn_file01.bnn
new file mode 100644
index 00000000..b75b63cf
--- /dev/null
+++ b/plugins/article_import/spec/fixtures/bnn_file01.bnn
@@ -0,0 +1,5 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+29932;;;;4280001958081;4280001958203;Walnoten (ongeroosterd);bio;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;;1;1 kg;1;N;930190;99260;;1,41;;;;1;;;4,49;2,34;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+28391;;;;4280001958081;4280001958203;Pijnboompitten;dem;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;;1;100 g;10;N;930190;99260;;1,41;;;;1;;;5,56;2.89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+1829;;;;4280001958081;4280001958203;Appelsap (verpakt);;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;4x250 ml;10;4x250 ml;10;N;930190;99260;;3,21;;;;1;;;4,49;2.89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;ml;28,571;;
+177813;;;;4280001958081;4280001958203;Tomaten;bio;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;;1;500 g;20;N;930190;99260;;1,20;;;;1;;;4,49;2.89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;g;28,571;;
\ No newline at end of file
diff --git a/plugins/article_import/spec/fixtures/bnn_file_02.bnn b/plugins/article_import/spec/fixtures/bnn_file_02.bnn
new file mode 100644
index 00000000..e3dba5bb
--- /dev/null
+++ b/plugins/article_import/spec/fixtures/bnn_file_02.bnn
@@ -0,0 +1,2 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+1;;;;4280001958081;4280001958203;Tomatoes;organic;;;med;;GR;C%;DE-?KO-001;120;1302;10;55;;1;;20;500 g;1;N;930190;99260;;1,41;;;;1;;;4,49;1,20;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;g;28,571;;
\ No newline at end of file
diff --git a/plugins/article_import/spec/fixtures/odin_file_01.xml b/plugins/article_import/spec/fixtures/odin_file_01.xml
new file mode 100644
index 00000000..3b60e83e
--- /dev/null
+++ b/plugins/article_import/spec/fixtures/odin_file_01.xml
@@ -0,0 +1,273 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+Walnoten (ongeroosterd)
+Nucli rose
+
+0
+0
+0
+1
+kg
+Stuk
+0
+Het warme woud
+bio
+
+
+NL
+
+6
+1017515
+29932
+10
+Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+2.34
+7.95
+
+
+
+8719325207668
+Pijnboompitten
+Nucli rose
+
+0
+0
+0
+100
+g
+Stuk
+0
+NELEMAN
+dem
+
+
+TR
+
+6
+1017515
+28391
+10
+Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+5.56
+7.95
+
+
+
+8719325207668
+Appelsap (verpakt)
+Nucli rose
+
+0
+0
+0
+4x250
+ml
+Stuk
+0.4
+Appelgaarde
+
+
+
+DE
+
+6
+1017515
+1829
+10
+Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+3.21
+7.95
+
+
+
+8719325207668
+Tomaten
+Nucli rose
+
+0
+0
+0
+500
+g
+Stuk
+0
+De röde hof
+bio
+
+
+DE
+
+6
+1017515
+177813
+20
+Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+1.2
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/fixtures/odin_file_02.xml b/plugins/article_import/spec/fixtures/odin_file_02.xml
new file mode 100644
index 00000000..c732b4d5
--- /dev/null
+++ b/plugins/article_import/spec/fixtures/odin_file_02.xml
@@ -0,0 +1,75 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+Tomatoes
+Nucli rose
+
+0
+0
+0
+500
+g
+Stuk
+0
+De röde hof
+organic
+
+
+Somewhere, UK
+
+6
+1017515
+1
+20
+Actief
+druiven*
+0
+0
+2
+2
+0
+0
+0
+2
+2
+0
+2
+0
+2
+0
+2
+2
+2
+2
+1
+0
+2
+0
+2
+2
+
+
+
+0
+0
+0
+0
+1
+
+2
+0
+
+adviesprijs
+2022-08-18
+1.2
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/integration/articles_spec.rb b/plugins/article_import/spec/integration/articles_spec.rb
new file mode 100644
index 00000000..0a547515
--- /dev/null
+++ b/plugins/article_import/spec/integration/articles_spec.rb
@@ -0,0 +1,169 @@
+require_relative '../test_helper'
+require_relative '../../../../spec/spec_helper'
+
+feature ArticlesController do
+
+ let(:user) { create(:user, groups: [create(:workgroup, role_article_meta: true)]) }
+ let(:supplier) { create(:supplier) }
+ let!(:article_category) { create(:article_category) }
+
+ before { login user }
+
+ describe ':index', js: true do
+ before do
+ login user
+ visit supplier_articles_path(supplier_id: supplier.id)
+ end
+
+ it 'can visit supplier articles path' do
+ expect(page).to have_content(supplier.name)
+ expect(page).to have_content(I18n.t('articles.index.edit_all'))
+ end
+
+ it 'can create a new article' do
+ click_on I18n.t('articles.index.new')
+ expect(page).to have_selector('form#new_article')
+ article = build(:article, supplier: supplier, article_category: article_category)
+ within('#new_article') do
+ fill_in 'article_name', :with => article.name
+ fill_in 'article_unit', :with => article.unit
+ select article.article_category.name, :from => 'article_article_category_id'
+ fill_in 'article_price', :with => article.price
+ fill_in 'article_unit_quantity', :with => article.unit_quantity
+ fill_in 'article_tax', :with => article.tax
+ fill_in 'article_deposit', :with => article.deposit
+ # "Element cannot be scrolled into view" error, js as workaround
+ # find('input[type="submit"]').click
+ page.execute_script('$("form#new_article").submit();')
+ end
+ expect(page).to have_content(article.name)
+ end
+ end
+
+ describe ':upload' do
+ let(:filename) { 'foodsoft_file_02.csv' }
+ let(:file) { Rails.root.join("spec/fixtures/#{filename}") }
+
+ before do
+ visit upload_supplier_articles_path(supplier_id: supplier.id)
+ attach_file 'articles_file', file
+ end
+
+ Dir.glob('spec/fixtures/foodsoft_file_01.*') do |test_file|
+ describe "can import articles from #{test_file}" do
+ let(:file) { Rails.root.join(test_file) }
+
+ it do
+ find("#articles_type option[value='foodsoft']").select_option
+ find('input[type="submit"]').click
+ expect(find("tr:nth-child(1) #new_articles__note").value).to eq "bio ◎"
+ expect(find("tr:nth-child(2) #new_articles__name").value).to eq "Pijnboompitten"
+
+ 4.times do |i|
+ all("tr:nth-child(#{i + 1}) select > option")[1].select_option
+ end
+ find('input[type="submit"]').click
+ expect(page).to have_content("Pijnboompitten")
+
+ expect(supplier.articles.count).to eq 4
+ end
+ end
+ end
+
+ Dir.glob('spec/fixtures/bnn_file_01.*') do |test_file|
+ describe "can import articles from #{test_file}" do
+ let(:file) { Rails.root.join(test_file) }
+
+ it do
+ find("#articles_type option[value='bnn']").select_option
+ find('input[type="submit"]').click
+ expect(find("tr:nth-child(1) #new_articles__note").value).to eq "bio"
+ expect(find("tr:nth-child(1) #new_articles__name").value).to eq "Walnoten (ongeroosterd)"
+ # set article category
+ 4.times do |i|
+ all("tr:nth-child(#{i + 1}) select > option")[1].select_option
+ end
+ find('input[type="submit"]').click
+
+ expect(page).to have_content("Pijnboompitten")
+
+ expect(supplier.articles.count).to eq 4
+ end
+ end
+ end
+ end
+
+ describe "updates" do
+ file_paths = ['spec/fixtures/foodsoft_file_02.csv', 'plugins/article_import/spec/fixtures/bnn_file_02.bnn', 'plugins/article_import/spec/fixtures/odin_file_02.xml']
+ let(:filename) { 'foodsoft_file_02.csv' }
+ let(:file) { Rails.root.join("spec/fixtures/#{filename}") }
+ let(:val) { 'foodsoft' }
+ let(:type) { %w[foodsoft bnn odin] }
+
+ before do
+ visit upload_supplier_articles_path(supplier_id: supplier.id)
+ attach_file 'articles_file', file
+ find("#articles_type option[value='#{val}']").select_option
+ end
+
+ file_paths.each_with_index do |test_file, index|
+ describe "updates article for #{test_file}" do
+ let(:article) { create(:article, supplier: supplier, name: 'Foobar', order_number: 1, unit: '250 g') }
+ let(:file) { Rails.root.join(test_file) }
+ let(:val) { type[index] }
+
+ it do
+ article.reload
+ find('input[type="submit"]').click
+ expect(find("#articles_#{article.id}_name").value).to eq 'Tomatoes'
+ find('input[type="submit"]').click
+ article.reload
+ expect(article.name).to eq 'Tomatoes'
+ if type[index] == "odin"
+ expect([article.unit, article.unit_quantity, article.price]).to eq ['500gr', 20, 1.20]
+ else
+ expect([article.unit, article.unit_quantity, article.price]).to eq ['500 g', 20, 1.20]
+ end
+ end
+
+ it "handles missing data" do
+ find('input[type="submit"]').click # to overview
+ find('input[type="submit"]').click # missing category, re-show form
+ expect(find('tr.alert')).to be_present
+ expect(supplier.articles.count).to eq 0
+
+ all("tr select > option")[1].select_option
+ find('input[type="submit"]').click # now it should succeed
+ expect(supplier.articles.count).to eq 1
+ end
+ end
+
+ describe "can remove an existing article" do
+ let!(:article) { create(:article, supplier: supplier, name: 'Foobar', order_number: 99999) }
+
+ it do
+ check('articles_outlist_absent')
+ find('input[type="submit"]').click
+ expect(find("#outlisted_articles_#{article.id}", visible: :all)).to be_present
+
+ all("tr select > option")[1].select_option
+ find('input[type="submit"]').click
+ expect(article.reload.deleted?).to be true
+ end
+ end
+
+ describe "can convert units when updating" do
+ let!(:article) { create(:article, supplier: supplier, order_number: 1, unit: '250 g') }
+
+ it do
+ check('articles_convert_units')
+ find('input[type="submit"]').click
+ expect(find("#articles_#{article.id}_name").value).to eq 'Tomatoes'
+ find('input[type="submit"]').click
+ article.reload
+ expect([article.unit, article.unit_quantity, article.price]).to eq ['250 g', 40, 0.6]
+ end
+ end
+ end
+ end
+end
diff --git a/plugins/article_import/spec/integration/supplier_spec.rb b/plugins/article_import/spec/integration/supplier_spec.rb
new file mode 100644
index 00000000..d80c0d52
--- /dev/null
+++ b/plugins/article_import/spec/integration/supplier_spec.rb
@@ -0,0 +1,21 @@
+require_relative '../test_helper'
+require_relative '../../../../spec/spec_helper'
+
+describe Supplier do
+ let(:supplier) { create :supplier }
+
+ context 'syncs from file' do
+ it 'imports and updates articles' do
+ article1 = create(:article, supplier: supplier, order_number: 177813, unit: '250 g', price: 0.1)
+ article2 = create(:article, supplier: supplier, order_number: 12345)
+ supplier.articles = [article1, article2]
+ options = { filename: 'foodsoft_file_01.csv' }
+ options[:outlist_absent] = true
+ options[:convert_units] = true
+ updated_article_pairs, outlisted_articles, new_articles = supplier.sync_from_file(Rails.root.join('spec/fixtures/foodsoft_file_01.csv'), 'foodsoft', options)
+ expect(new_articles.length).to be > 0
+ expect(updated_article_pairs.first[1][:name]).to eq 'Tomaten'
+ expect(outlisted_articles.first).to eq article2
+ end
+ end
+end
\ No newline at end of file
diff --git a/plugins/article_import/spec/spec_helper.rb b/plugins/article_import/spec/spec_helper.rb
deleted file mode 100644
index d3e43d53..00000000
--- a/plugins/article_import/spec/spec_helper.rb
+++ /dev/null
@@ -1,100 +0,0 @@
-# frozen_string_literal: true
-
-require 'simplecov'
-SimpleCov.start
-# This file was generated by the `rspec --init` command. Conventionally, all
-# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
-# The generated `.rspec` file contains `--require spec_helper` which will cause
-# this file to always be loaded, without a need to explicitly require it in any
-# files.
-#
-# Given that it is always loaded, you are encouraged to keep this file as
-# light-weight as possible. Requiring heavyweight dependencies from this file
-# will add to the boot time of your test suite on EVERY test run, even for an
-# individual file that may not need all of that loaded. Instead, consider making
-# a separate helper file that requires the additional dependencies and performs
-# the additional setup, and require it from the spec files that actually need
-# it.
-#
-# See https://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
-RSpec.configure do |config|
- # rspec-expectations config goes here. You can use an alternate
- # assertion/expectation library such as wrong or the stdlib/minitest
- # assertions if you prefer.
- config.expect_with :rspec do |expectations|
- # This option will default to `true` in RSpec 4. It makes the `description`
- # and `failure_message` of custom matchers include text for helper methods
- # defined using `chain`, e.g.:
- # be_bigger_than(2).and_smaller_than(4).description
- # # => "be bigger than 2 and smaller than 4"
- # ...rather than:
- # # => "be bigger than 2"
- expectations.include_chain_clauses_in_custom_matcher_descriptions = true
- end
-
- # rspec-mocks config goes here. You can use an alternate test double
- # library (such as bogus or mocha) by changing the `mock_with` option here.
- config.mock_with :rspec do |mocks|
- # Prevents you from mocking or stubbing a method that does not exist on
- # a real object. This is generally recommended, and will default to
- # `true` in RSpec 4.
- mocks.verify_partial_doubles = true
- end
-
- # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
- # have no way to turn it off -- the option exists only for backwards
- # compatibility in RSpec 3). It causes shared context metadata to be
- # inherited by the metadata hash of host groups and examples, rather than
- # triggering implicit auto-inclusion in groups with matching metadata.
- config.shared_context_metadata_behavior = :apply_to_host_groups
-
- # The settings below are suggested to provide a good initial experience
- # with RSpec, but feel free to customize to your heart's content.
- # # This allows you to limit a spec run to individual examples or groups
- # # you care about by tagging them with `:focus` metadata. When nothing
- # # is tagged with `:focus`, all examples get run. RSpec also provides
- # # aliases for `it`, `describe`, and `context` that include `:focus`
- # # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
- # config.filter_run_when_matching :focus
- #
- # # Allows RSpec to persist some state between runs in order to support
- # # the `--only-failures` and `--next-failure` CLI options. We recommend
- # # you configure your source control system to ignore this file.
- # config.example_status_persistence_file_path = "spec/examples.txt"
- #
- # # Limits the available syntax to the non-monkey patched syntax that is
- # # recommended. For more details, see:
- # # https://relishapp.com/rspec/rspec-core/docs/configuration/zero-monkey-patching-mode
- # config.disable_monkey_patching!
- #
- # # This setting enables warnings. It's recommended, but in some cases may
- # # be too noisy due to issues in dependencies.
- # config.warnings = true
- #
- # # Many RSpec users commonly either run the entire suite or an individual
- # # file, and it's useful to allow more verbose output when running an
- # # individual spec file.
- # if config.files_to_run.one?
- # # Use the documentation formatter for detailed output,
- # # unless a formatter has already been configured
- # # (e.g. via a command-line flag).
- # config.default_formatter = "doc"
- # end
- #
- # # Print the 10 slowest examples and example groups at the
- # # end of the spec run, to help surface which specs are running
- # # particularly slow.
- # config.profile_examples = 10
- #
- # # Run specs in random order to surface order dependencies. If you find an
- # # order dependency and want to debug it, you can fix the order by providing
- # # the seed, which is printed after each run.
- # # --seed 1234
- # config.order = :random
- #
- # # Seed global randomization in this process using the `--seed` CLI option.
- # # Setting this allows you to use `--seed` to deterministically reproduce
- # # test failures related to randomization by passing the same `--seed` value
- # # as the one that triggered the failure.
- # Kernel.srand config.seed
-end
diff --git a/plugins/article_import/spec/test_helper.rb b/plugins/article_import/spec/test_helper.rb
new file mode 100644
index 00000000..527a16d8
--- /dev/null
+++ b/plugins/article_import/spec/test_helper.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+module TestHelper
+ ENV["FOODSOFT_APP_CONFIG"] = "plugins/article_import/spec/app_config.yml"
+end
+
+RSpec.configure do |config|
+ config.include TestHelper, :type => :feature
+end