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.rb b/plugins/article_import/lib/foodsoft_article_import.rb
index 4472cca5..5bd48dd8 100644
--- a/plugins/article_import/lib/foodsoft_article_import.rb
+++ b/plugins/article_import/lib/foodsoft_article_import.rb
@@ -7,7 +7,6 @@ require 'csv'
require 'yaml'
require 'active_support/core_ext/hash/keys'
require_relative 'foodsoft_article_import/bnn'
-require_relative 'foodsoft_article_import/utf8_encoder'
require_relative 'foodsoft_article_import/odin'
require_relative 'foodsoft_article_import/foodsoft'
module FoodsoftArticleImport
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/files/bnn/bnn_bad_encoding.BNN b/plugins/article_import/spec/files/bnn/bnn_bad_encoding.BNN
new file mode 100644
index 00000000..0b7cb14e
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_bad_encoding.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;A;;;4280001958081;4280001958203;Greek Dressing - Kräuter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-ÖKO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/bnn/bnn_flawless.BNN b/plugins/article_import/spec/files/bnn/bnn_flawless.BNN
new file mode 100644
index 00000000..3229196c
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_flawless.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;X;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/bnn_flawless_category.BNN b/plugins/article_import/spec/files/bnn/bnn_flawless_category.BNN
new file mode 100644
index 00000000..78234d92
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_flawless_category.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;4000;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/bnn_flawless_special.BNN b/plugins/article_import/spec/files/bnn/bnn_flawless_special.BNN
new file mode 100644
index 00000000..0f285f6b
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_flawless_special.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;med;;GR;C%;DE-™KO-001;120;1302;10;55;;1;6 x35g;6;35g;1;N;930190;99260;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;20230101;20230201;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/bnn_missing_entries.BNN b/plugins/article_import/spec/files/bnn/bnn_missing_entries.BNN
new file mode 100644
index 00000000..6c8dafe9
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_missing_entries.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+64721;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;HDE;;GR;C%;DE-™KO-001;120;1100;10;55;;1;6 x35g;6;35g;1;N;;99260;;1,41;;;;1;;;4,49;2,89;J;;;;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/bnn_missing_order_number.BNN b/plugins/article_import/spec/files/bnn/bnn_missing_order_number.BNN
new file mode 100644
index 00000000..aadcb9b6
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/bnn_missing_order_number.BNN
@@ -0,0 +1,3 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+;A;;;4280001958081;4280001958203;Greek Dressing - Kr„uter Mix;Oregano, Basilikum und Minze;;;HDE;;GR;C%;DE-™KO-001;120;1100;10;55;;1;6 x35g;6;35g;1;N;;99260;;1,41;;;;1;;;4,49;2,89;J;;;;;;;;;;;;;;;;;;;;;;A;;;;;Kg;28,571;;
+;;99
diff --git a/plugins/article_import/spec/files/bnn/demo_file.BNN b/plugins/article_import/spec/files/bnn/demo_file.BNN
new file mode 100644
index 00000000..3d9cc23f
--- /dev/null
+++ b/plugins/article_import/spec/files/bnn/demo_file.BNN
@@ -0,0 +1,7 @@
+BNN;3;0;Naturkost Nord, Hamburg;T;Angebot Nr. 0922;EUR;20220905;20221001;20220825;837;1
+5;;;;4280001958081;4280001958203;Žpfel Elstar;erntefrisch und knackig;;;obb;;D;C%;DE-?KO-001;120;0301;10;55;;1;10 x1kg;10;1kg;1;N;;;;1,41;;;;1;;;4,49;2,89;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;1;;
+6;;;;4280001958081;4280001958203;Brokkoli;gesund und lecker;;;fig;;IT;C%;DE-?KO-001;120;03;10;55;;1;6 x400g;6;400g;1;N;;;;1,41;;;;1;;;4,49;2,99;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2,5;;
+7;;;;4280001958081;4280001958203;Tomaten;pomodori italiani, demeter;;;TDP;;IT;C%;DE-?KO-001;120;03;10;55;;1;20 x500g;20;500g;1;N;;;;1,41;;;;1;;;4,49;3,19;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2;;
+8;;;;4280001958081;4280001958203;Reis;Reis im Vorratssack, demeter;;;FIN;;D;C%;DE-?KO-001;120;05;10;55;;1;12 x3k;12;3kg;1;N;;;;1,41;;;;1;;;4,49;3,49;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;0,3;;
+9;;;;4280001958081;4280001958203;Spaghetti;100% italienisches Hartweizengrie?;;;ZLN;;D;C%;DE-?KO-001;120;06;10;55;;1;4 x500g;4;500g;1;N;;;;1,41;;;;1;;;4,49;2,99;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;2;;
+10;;;;4280001958081;4280001958203;Kartoffeln;vorwiegend festkochend;;;rsh;;D;C%;DE-?KO-001;120;0311;10;55;;1;6 x5Kg;6;5Kg;1;N;;;;1,41;;;;1;;;4,49;3,00;J;;2;3;;;;;;;;;;;;;;;;;;;A;;;;;Kg;0.2;;
diff --git a/plugins/article_import/spec/files/custom_codes.yml b/plugins/article_import/spec/files/custom_codes.yml
new file mode 100644
index 00000000..5e9020f3
--- /dev/null
+++ b/plugins/article_import/spec/files/custom_codes.yml
@@ -0,0 +1,8 @@
+# BNN Codes
+category:
+ "4000": "Schuhe"
+additional:
+ "additional": "value"
+indeling:
+ 11: Test Indeling
+ 111: Test Subindeling
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/foodsoft/foodsoft_flawless.csv b/plugins/article_import/spec/files/foodsoft/foodsoft_flawless.csv
new file mode 100644
index 00000000..a9a94c22
--- /dev/null
+++ b/plugins/article_import/spec/files/foodsoft/foodsoft_flawless.csv
@@ -0,0 +1,3 @@
+status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
+;1;product;bio;someone;eu;1 kg;1.23;6;0;10;;;coolstuff
+;12;other product;bio;someone;eu;2 kg;3.45;6;0;10;;;coolstuff
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/foodsoft/foodsoft_generate_order_number.csv b/plugins/article_import/spec/files/foodsoft/foodsoft_generate_order_number.csv
new file mode 100644
index 00000000..a50dde34
--- /dev/null
+++ b/plugins/article_import/spec/files/foodsoft/foodsoft_generate_order_number.csv
@@ -0,0 +1,3 @@
+status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
+;;product;bio;someone;eu;1 kg;1.23;6;0;10;;;coolstuff
+;;other product;bio;someone;eu;2 kg;3.45;6;0;10;;;coolstuff
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/foodsoft/foodsoft_missing_entries.csv b/plugins/article_import/spec/files/foodsoft/foodsoft_missing_entries.csv
new file mode 100644
index 00000000..560c11af
--- /dev/null
+++ b/plugins/article_import/spec/files/foodsoft/foodsoft_missing_entries.csv
@@ -0,0 +1,2 @@
+status;number;name;note;manufacturer;origin;unit ;clear price;tax;deposit;unit quantity;scale quantity;scale price;category
+;12;product;bio;;eu;1 kg;1.23;;0;10;;;coolstuff
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/odin/odin_flawless.xml b/plugins/article_import/spec/files/odin/odin_flawless.xml
new file mode 100644
index 00000000..5b5a28fc
--- /dev/null
+++ b/plugins/article_import/spec/files/odin/odin_flawless.xml
@@ -0,0 +1,75 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+nucli rose
+Nucli rose
+
+0
+0
+0
+750
+g
+Stuk
+0
+NELEMAN
+Biologisch
+
+
+ES
+
+21
+1017515
+0109
+6
+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
+4.52
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/odin/odin_flawless_custom_category.xml b/plugins/article_import/spec/files/odin/odin_flawless_custom_category.xml
new file mode 100644
index 00000000..460da24c
--- /dev/null
+++ b/plugins/article_import/spec/files/odin/odin_flawless_custom_category.xml
@@ -0,0 +1,77 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+nucli rose
+Nucli rose
+
+0
+0
+0
+750
+g
+Stuk
+0
+NELEMAN
+Biologisch
+
+
+ES
+
+21
+1017515
+0109
+11
+111
+6
+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
+4.52
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/odin/odin_missing_entries.xml b/plugins/article_import/spec/files/odin/odin_missing_entries.xml
new file mode 100644
index 00000000..5089b911
--- /dev/null
+++ b/plugins/article_import/spec/files/odin/odin_missing_entries.xml
@@ -0,0 +1,75 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+nucli rose
+Nucli rose
+
+0
+0
+0
+750
+
+Stuk
+0
+
+Biologisch
+
+
+ES
+
+21
+1017515
+0109
+6
+Non 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
+4.52
+7.95
+
+
+
\ No newline at end of file
diff --git a/plugins/article_import/spec/files/odin/odin_missing_order_number.xml b/plugins/article_import/spec/files/odin/odin_missing_order_number.xml
new file mode 100644
index 00000000..d43a9439
--- /dev/null
+++ b/plugins/article_import/spec/files/odin/odin_missing_order_number.xml
@@ -0,0 +1,75 @@
+
+
+
+1039
+1.08
+Estafette Associatie C.V.
+Geldermalsen
+
+
+8719325207668
+nucli rose
+Nucli rose
+
+0
+0
+0
+750
+g
+Stuk
+0
+NELEMAN
+Biologisch
+
+
+ES
+
+21
+1017515
+
+6
+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
+4.52
+7.95
+
+
+
\ No newline at end of file
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/lib/bnn/foodsoft_article_import_bnn_spec.rb b/plugins/article_import/spec/lib/bnn/foodsoft_article_import_bnn_spec.rb
new file mode 100644
index 00000000..d50aa591
--- /dev/null
+++ b/plugins/article_import/spec/lib/bnn/foodsoft_article_import_bnn_spec.rb
@@ -0,0 +1,73 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative '../../../lib/foodsoft_article_import'
+
+describe FoodsoftArticleImport do
+ files_path = File.expand_path '../../files', __dir__
+ bnn_files_path = File.join(files_path, 'bnn')
+
+ dummy_article = { name: 'Greek Dressing - Kräuter Mix', order_number: '64721', note: 'Oregano, Basilikum und Minze',
+ manufacturer: 'Medousa, Griechenland Importe', origin: 'GR', article_category: 'Kräutermischungen', unit: '35g', price: '2,89', tax: 7.0, unit_quantity: '6' }
+
+ article = dummy_article.merge({ deposit: 0.08 })
+ article_special = article.merge(note: 'Sonderpreis: 2,89 von 20230101 bis 20230201')
+
+ article_2 = dummy_article.merge({ manufacturer: nil, article_category: nil })
+
+ article_custom_code = article.merge(article_category: 'Schuhe')
+
+ empty = {}
+
+ context 'bnn' do
+ it 'parses bnn file correctly without type parameter' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless.BNN'))) do |new_attrs, status, _line|
+ expect(new_attrs).to eq article
+ expect(status).to eq :outlisted
+ end
+ end
+ it 'parses file correctly with type parameter' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless.BNN')), type: 'bnn') do |new_attrs, status, _line|
+ expect(new_attrs).to eq article
+ expect(status).to eq :outlisted
+ end
+ end
+ it 'raises error wenn wrong type (except odin) specified' do
+ expect do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless.BNN')), type: 'foodsoft')
+ end.to raise_error(RuntimeError)
+
+ expect(FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless.BNN')), type: 'odin')).to eq []
+ end
+ it 'parses article with special correctly' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless_special.BNN')), type: 'bnn') do |new_attrs, status, _line|
+ expect(new_attrs).to eq article_special
+ expect(status).to eq :special
+ end
+ end
+ it 'parses missing entries correctly' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_missing_entries.BNN')), type: 'bnn') do |new_attrs, status, _line|
+ expect(new_attrs).to eq article_2
+ expect(status).to eq nil
+ end
+ end
+ it 'skips rows without order_number' do
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_missing_order_number.BNN')), type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs).to eq empty
+ end
+ end
+ it 'joins custom_codes file' do
+ custom_file_path = File.join(files_path, 'custom_codes.yml').to_s
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_flawless_category.BNN')), custom_file_path: custom_file_path, type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs).to eq article_custom_code
+ end
+ end
+ it 'parses file with different encoding' do
+ # the bnn file is loaded with encoding ibm850. If file is not ibm850 encoded, some characters might look weird
+ FoodsoftArticleImport.parse(File.open(File.join(bnn_files_path, 'bnn_bad_encoding.BNN')), type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs[:order_number]).to eq('64721')
+ expect(new_attrs[:name]).to eq('Greek Dressing - Kräuter Mix')
+ end
+ end
+ end
+end
diff --git a/plugins/article_import/spec/lib/foodsoft/foodsoft_article_import_foodsoft_spec.rb b/plugins/article_import/spec/lib/foodsoft/foodsoft_article_import_foodsoft_spec.rb
new file mode 100644
index 00000000..ce78b12b
--- /dev/null
+++ b/plugins/article_import/spec/lib/foodsoft/foodsoft_article_import_foodsoft_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require 'foodsoft_article_import'
+
+describe FoodsoftArticleImport do
+ files_path = File.expand_path '../../files', __dir__
+ foodsoft_files_path = File.join(files_path, 'foodsoft')
+
+ dummy_article = { order_number: '1', name: 'product', note: 'bio', manufacturer: 'someone', origin: 'eu',
+ unit: '1 kg', price: '1.23', tax: '6', unit_quantity: '10', article_category: 'coolstuff', deposit: '0' }
+
+ dummy_article_2 = { order_number: '12', name: 'other product', note: 'bio', manufacturer: 'someone',
+ origin: 'eu', unit: '2 kg', price: '3.45', tax: '6', unit_quantity: '10', article_category: 'coolstuff', deposit: '0' }
+
+ articles = [dummy_article, dummy_article_2]
+
+ dummy_article_3 = dummy_article.merge({ order_number: ':d8df298' })
+ dummy_article_4 = dummy_article_2.merge({ order_number: ':1f37e39' })
+ articles_number_generated = [dummy_article_3, dummy_article_4]
+ empty = {}
+
+ context 'foodsoft' do
+ it 'parses file correctly with type parameter foodsoft' do
+ count = 0
+ FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_flawless.csv')), type: 'foodsoft') do |new_attrs, status, _line|
+ expect(new_attrs).to eq articles[count]
+ expect(status).to eq nil
+ count += 1
+ end
+ end
+
+ it 'raises error wenn wrong type specified' do
+ expect(FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_flawless.csv')), type: 'odin')).to eq []
+
+ expect(FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_flawless.csv')), type: 'bnn')).to eq []
+ end
+
+ it 'parses missing entries correctly' do
+ FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_missing_entries.csv')), type: 'foodsoft') do |new_attrs, status, _line|
+ expect(status).to eq 'Error: unit, price and tax must be entered'
+ expect(new_attrs[:unit]).to eq '1 kg'
+ expect(new_attrs[:manufacturer]).to eq nil
+ end
+ end
+
+ it 'generates order numbers for articles without order number' do
+ count = 0
+ FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_generate_order_number.csv')), type: 'foodsoft') do |new_attrs, _status, _line|
+ expect(new_attrs).to eq articles_number_generated[count]
+ count += 1
+ end
+ end
+
+ xit 'joins custom_codes file' do
+ custom_file_path = File.join(files_path, 'custom_codes.yml').to_s
+ FoodsoftArticleImport.parse(File.open(File.join(foodsoft_files_path, 'foodsoft_flawless_custom_category.csv')), custom_file_path: custom_file_path, type: 'foodsoft') do |new_attrs, _status, _line|
+ expect(new_attrs[:article_category]).to eq 'Test Indeling - Test Subindeling'
+ end
+ end
+ end
+end
diff --git a/plugins/article_import/spec/lib/odin/foodsoft_article_import_odin_spec.rb b/plugins/article_import/spec/lib/odin/foodsoft_article_import_odin_spec.rb
new file mode 100644
index 00000000..af3da3f4
--- /dev/null
+++ b/plugins/article_import/spec/lib/odin/foodsoft_article_import_odin_spec.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require_relative '../../../lib/foodsoft_article_import'
+
+describe FoodsoftArticleImport do
+ files_path = File.expand_path '../../files', __dir__
+ odin_files_path = File.join(files_path, 'odin')
+
+ dummy_article = { order_number: '0109', name: 'nucli rose', note: 'Biologisch', manufacturer: 'NELEMAN',
+ origin: 'ES', unit: '750gr', price: '4.52', unit_quantity: '6', tax: '21', deposit: '0', article_category: '' }
+
+ empty = {}
+
+ context 'odin' do
+ it 'parses file correctly with type parameter odin' do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_flawless.xml')), type: 'odin') do |new_attrs, status, _line|
+ expect(new_attrs).to eq dummy_article
+ expect(status).to eq nil
+ end
+ end
+
+ it 'raises error wenn wrong type specified' do
+ expect do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_flawless.xml')), type: 'foodsoft')
+ end.to raise_error(RuntimeError)
+
+ expect do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_flawless.xml')), type: 'bnn')
+ end.to raise_error(CSV::MalformedCSVError)
+ end
+
+ it 'parses missing entries correctly' do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_missing_entries.xml')), type: 'odin') do |new_attrs, status, _line|
+ expect(status).to eq :outlisted
+ expect(new_attrs[:unit]).to eq '750st'
+ expect(new_attrs[:manufacturer]).to eq ''
+ end
+ end
+
+ it 'skips rows without order_number' do
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_missing_order_number.xml')), type: 'odin') do |new_attrs, _status, _line|
+ expect(new_attrs).to eq empty
+ end
+ end
+
+ it 'joins custom_codes file' do
+ custom_file_path = File.join(files_path, 'custom_codes.yml').to_s
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'odin_flawless_custom_category.xml')), custom_file_path: custom_file_path, type: 'odin') do |new_attrs, _status, _line|
+ expect(new_attrs[:article_category]).to eq 'Test Indeling - Test Subindeling'
+ end
+ end
+
+ xit 'parses dummy_article with special correctly' do
+ # TODO: find out whether there are special prices for odin files
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'bnn_flawless_special.BNN')), type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs.manufacturer).to eq nil
+ expect(new_attrs.unit).to eq '750st'
+ end
+ end
+
+ xit 'parses file with different encoding' do
+ # the bnn file is loaded with encoding ibm850. If file is not ibm850 encoded, some characters might look weird
+ FoodsoftArticleImport.parse(File.open(File.join(odin_files_path, 'bnn_bad_encoding.BNN')), type: 'bnn') do |new_attrs, _status, _line|
+ expect(new_attrs[:order_number]).to eq('64721')
+ expect(new_attrs[:name]).to eq('Greek Dressing - Kräuter Mix')
+ end
+ end
+ end
+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