From ba0e15d68176343b6b3a36ce9621545fbc5b8bea Mon Sep 17 00:00:00 2001 From: viehlieb Date: Wed, 29 Mar 2023 16:17:50 +0200 Subject: [PATCH 1/3] add plugin specs --- .../lib/foodsoft_article_import.rb | 1 - .../spec/files/bnn/bnn_bad_encoding.BNN | 3 + .../spec/files/bnn/bnn_flawless.BNN | 3 + .../spec/files/bnn/bnn_flawless_category.BNN | 3 + .../spec/files/bnn/bnn_flawless_special.BNN | 3 + .../spec/files/bnn/bnn_missing_entries.BNN | 3 + .../files/bnn/bnn_missing_order_number.BNN | 3 + .../spec/files/bnn/demo_file.BNN | 7 ++ .../spec/files/custom_codes.yml | 8 ++ .../spec/files/foodsoft/foodsoft_flawless.csv | 3 + .../foodsoft_generate_order_number.csv | 3 + .../foodsoft/foodsoft_missing_entries.csv | 2 + .../spec/files/odin/odin_flawless.xml | 75 +++++++++++++ .../odin/odin_flawless_custom_category.xml | 77 ++++++++++++++ .../spec/files/odin/odin_missing_entries.xml | 75 +++++++++++++ .../files/odin/odin_missing_order_number.xml | 75 +++++++++++++ .../bnn/foodsoft_article_import_bnn_spec.rb | 73 +++++++++++++ .../foodsoft_article_import_foodsoft_spec.rb | 62 +++++++++++ .../odin/foodsoft_article_import_odin_spec.rb | 70 ++++++++++++ plugins/article_import/spec/spec_helper.rb | 100 ++++++++++++++++++ 20 files changed, 648 insertions(+), 1 deletion(-) create mode 100644 plugins/article_import/spec/files/bnn/bnn_bad_encoding.BNN create mode 100644 plugins/article_import/spec/files/bnn/bnn_flawless.BNN create mode 100644 plugins/article_import/spec/files/bnn/bnn_flawless_category.BNN create mode 100644 plugins/article_import/spec/files/bnn/bnn_flawless_special.BNN create mode 100644 plugins/article_import/spec/files/bnn/bnn_missing_entries.BNN create mode 100644 plugins/article_import/spec/files/bnn/bnn_missing_order_number.BNN create mode 100644 plugins/article_import/spec/files/bnn/demo_file.BNN create mode 100644 plugins/article_import/spec/files/custom_codes.yml create mode 100644 plugins/article_import/spec/files/foodsoft/foodsoft_flawless.csv create mode 100644 plugins/article_import/spec/files/foodsoft/foodsoft_generate_order_number.csv create mode 100644 plugins/article_import/spec/files/foodsoft/foodsoft_missing_entries.csv create mode 100644 plugins/article_import/spec/files/odin/odin_flawless.xml create mode 100644 plugins/article_import/spec/files/odin/odin_flawless_custom_category.xml create mode 100644 plugins/article_import/spec/files/odin/odin_missing_entries.xml create mode 100644 plugins/article_import/spec/files/odin/odin_missing_order_number.xml create mode 100644 plugins/article_import/spec/lib/bnn/foodsoft_article_import_bnn_spec.rb create mode 100644 plugins/article_import/spec/lib/foodsoft/foodsoft_article_import_foodsoft_spec.rb create mode 100644 plugins/article_import/spec/lib/odin/foodsoft_article_import_odin_spec.rb create mode 100644 plugins/article_import/spec/spec_helper.rb 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/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/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/spec_helper.rb b/plugins/article_import/spec/spec_helper.rb new file mode 100644 index 00000000..d3e43d53 --- /dev/null +++ b/plugins/article_import/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# 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 From 89f85495d0f523df22cc195d5e91a5d7722b9236 Mon Sep 17 00:00:00 2001 From: viehlieb Date: Thu, 30 Mar 2023 13:38:41 +0200 Subject: [PATCH 2/3] add integration/articles_spec --- .../foodsoft_article_import.gemspec | 2 +- .../lib/foodsoft_article_import/engine.rb | 2 +- .../spec/fixtures/bnn_file01.bnn | 5 + .../spec/fixtures/bnn_file_02.bnn | 2 + .../spec/fixtures/odin_file_01.xml | 273 ++++++++++++++++++ .../spec/fixtures/odin_file_02.xml | 75 +++++ .../spec/integration/articles_spec.rb | 182 ++++++++++++ 7 files changed, 539 insertions(+), 2 deletions(-) create mode 100644 plugins/article_import/spec/fixtures/bnn_file01.bnn create mode 100644 plugins/article_import/spec/fixtures/bnn_file_02.bnn create mode 100644 plugins/article_import/spec/fixtures/odin_file_01.xml create mode 100644 plugins/article_import/spec/fixtures/odin_file_02.xml create mode 100644 plugins/article_import/spec/integration/articles_spec.rb diff --git a/plugins/article_import/foodsoft_article_import.gemspec b/plugins/article_import/foodsoft_article_import.gemspec index b030005f..0d2d5842 100644 --- a/plugins/article_import/foodsoft_article_import.gemspec +++ b/plugins/article_import/foodsoft_article_import.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.email = ["foodsoft@local-it.org"] s.summary = "Manages manual article import from file. File Formats supported are: foodsoft file(csv), bnn files (.bnn) and odin files (xml)" - s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] + s.files = Dir["{app,config,db,lib,spec}/**/*"] + ["Rakefile", "README.md"] s.add_dependency "rails" s.add_dependency "deface", "~> 1.0" 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/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..d70fcae6 --- /dev/null +++ b/plugins/article_import/spec/integration/articles_spec.rb @@ -0,0 +1,182 @@ +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 do + FoodsoftConfig[:use_article_import] = true + login user + end + after do + FoodsoftConfig[:use_article_import] = false + end + + describe ':index', js: true do + puts " + " + "______________" + " + " + "______________" + " + " + "______________" + " + " + "Plugin" + " + " + "______________"+ " + " + "______________"+ " + " + "______________" + 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 From 7e5ca2ec513fca3248189e9082347336b957a6ab Mon Sep 17 00:00:00 2001 From: viehlieb Date: Thu, 30 Mar 2023 14:55:24 +0200 Subject: [PATCH 3/3] finish plugin for bnn, foodsoft, odin uploads --- .../articles_controller_override.rb | 32 +++--- .../app/overrides/models/article_override.rb | 70 ++++++------ .../app/overrides/models/supplier_override.rb | 88 +++++++-------- .../foodsoft_article_import.gemspec | 2 +- plugins/article_import/spec/app_config.yml | 34 ++++++ .../spec/integration/articles_spec.rb | 21 +--- .../spec/integration/supplier_spec.rb | 21 ++++ plugins/article_import/spec/spec_helper.rb | 100 ------------------ plugins/article_import/spec/test_helper.rb | 9 ++ 9 files changed, 167 insertions(+), 210 deletions(-) create mode 100644 plugins/article_import/spec/app_config.yml create mode 100644 plugins/article_import/spec/integration/supplier_spec.rb delete mode 100644 plugins/article_import/spec/spec_helper.rb create mode 100644 plugins/article_import/spec/test_helper.rb 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/foodsoft_article_import.gemspec b/plugins/article_import/foodsoft_article_import.gemspec index 0d2d5842..b030005f 100644 --- a/plugins/article_import/foodsoft_article_import.gemspec +++ b/plugins/article_import/foodsoft_article_import.gemspec @@ -11,7 +11,7 @@ Gem::Specification.new do |s| s.email = ["foodsoft@local-it.org"] s.summary = "Manages manual article import from file. File Formats supported are: foodsoft file(csv), bnn files (.bnn) and odin files (xml)" - s.files = Dir["{app,config,db,lib,spec}/**/*"] + ["Rakefile", "README.md"] + s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"] s.add_dependency "rails" s.add_dependency "deface", "~> 1.0" 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/integration/articles_spec.rb b/plugins/article_import/spec/integration/articles_spec.rb index d70fcae6..0a547515 100644 --- a/plugins/article_import/spec/integration/articles_spec.rb +++ b/plugins/article_import/spec/integration/articles_spec.rb @@ -1,28 +1,15 @@ +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 do - FoodsoftConfig[:use_article_import] = true - login user - end - after do - FoodsoftConfig[:use_article_import] = false - end + + before { login user } describe ':index', js: true do - puts " - " + "______________" + " - " + "______________" + " - " + "______________" + " - " + "Plugin" + " - " + "______________"+ " - " + "______________"+ " - " + "______________" before do login user visit supplier_articles_path(supplier_id: supplier.id) 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