diff --git a/plugins/article_import/app/overrides/articles/upload/replace_label_with_file_format_option.html.haml.deface b/plugins/article_import/app/overrides/articles/upload/replace_label_with_file_format_option.html.haml.deface new file mode 100644 index 00000000..1e5b8065 --- /dev/null +++ b/plugins/article_import/app/overrides/articles/upload/replace_label_with_file_format_option.html.haml.deface @@ -0,0 +1,5 @@ +/ insert_after 'erb:contains("file_field")' +- if FoodsoftArticleImport.enabled? + %label(for="articles_file") + %strong="select the file type you are about to upload" + =f.collection_select :type, FoodsoftArticleImport::FORMATS , :to_s, :to_s \ No newline at end of file diff --git a/plugins/article_import/app/overrides/controllers/articles_controller_override.rb b/plugins/article_import/app/overrides/controllers/articles_controller_override.rb new file mode 100644 index 00000000..707bbefa --- /dev/null +++ b/plugins/article_import/app/overrides/controllers/articles_controller_override.rb @@ -0,0 +1,16 @@ +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') + @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 +end diff --git a/plugins/article_import/app/overrides/models/supplier_override.rb b/plugins/article_import/app/overrides/models/supplier_override.rb new file mode 100644 index 00000000..ec6838d4 --- /dev/null +++ b/plugins/article_import/app/overrides/models/supplier_override.rb @@ -0,0 +1,45 @@ +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 + new_attrs[:article_category] = ArticleCategory.find_match(new_attrs[:article_category]) + 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)) + 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 + 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 new file mode 100644 index 00000000..b030005f --- /dev/null +++ b/plugins/article_import/foodsoft_article_import.gemspec @@ -0,0 +1,20 @@ +$:.push File.expand_path("../lib", __FILE__) + +# Maintain your gem's version: +require "foodsoft_article_import/version" + +# Describe your gem and declare its dependencies: +Gem::Specification.new do |s| + s.name = "foodsoft_article_import" + s.version = FoodsoftArticleImport::VERSION + s.authors = ["viehlieb"] + 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.add_dependency "rails" + s.add_dependency "deface", "~> 1.0" + s.add_dependency 'roo', '~> 2.9.0' + s.add_development_dependency 'simplecov' +end diff --git a/plugins/article_import/lib/foodsoft_article_import.rb b/plugins/article_import/lib/foodsoft_article_import.rb new file mode 100644 index 00000000..83af9b34 --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import.rb @@ -0,0 +1,77 @@ +# frozen_string_literal: true +require "deface" +require 'foodsoft_article_import/engine' +require 'digest/sha1' +require 'tempfile' +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 + class ConversionFailedException < StandardError; end + + FORMATS = %w(bnn foodsoft odin).freeze + def self.enabled? + FoodsoftConfig[:use_article_import] + end + + def self.file_formats + @@file_formats ||= { + 'bnn' => FoodsoftArticleImport::Bnn, + 'foodsoft' => FoodsoftArticleImport::Foodsoft, + 'odin' => FoodsoftArticleImport::Odin, + }.freeze + end + + # Parse file by type (one of {.file_formats}) + # + # @param file [File, Tempfile] + # @option opts [String] type file format (required) (see {.file_formats}) + # @return [File, Roo::Spreadsheet] file with encoding set if needed + def self.parse(file, custom_file_path: nil, type: nil, **opts, &blk) + @@filename = opts[:filename] if opts[:filename] + custom_file_path ||= nil + type ||= 'bnn' + parser = file_formats[type] + if block_given? + parser.parse(file, custom_file_path: custom_file_path, &blk) + else + data = [] + parser.parse(file, custom_file_path: custom_file_path) { |a| data << a } + data + end + end + + # Helper method to generate an article number for suppliers that do not have one + def self.generate_number(article) + # something unique, but not too unique + s = "#{article[:name]}-#{article[:unit_quantity]}x#{article[:unit]}" + s = s.downcase.gsub(/[^a-z0-9.]/, '') + # prefix abbreviated sha1-hash with colon to indicate that it's a generated number + article[:order_number] = ":#{Digest::SHA1.hexdigest(s)[-7..]}" + article + end + + # Helper method for opening a spreadsheet file + # + # @param file [File] file to open + # @param filename [String, NilClass] optional filename for guessing the file format + # @param encoding [String, NilClass] optional CSV encoding + # @param col_sep [String, NilClass] optional column separator + # @return [Roo::Spreadsheet] + def self.open_spreadsheet(file, encoding: nil, col_sep: nil, liberal_parsing: nil) + opts = { csv_options: {} } + opts[:csv_options][:encoding] = encoding if encoding + opts[:csv_options][:col_sep] = col_sep if col_sep + opts[:csv_options][:liberal_parsing] = true if liberal_parsing + opts[:extension] = File.extname(File.basename(@@filename)) if @@filename + begin + Roo::Spreadsheet.open(file, **opts) + rescue StandardError => e + raise "Failed to parse foodsoft file. make sure file format is correct: #{e.message}" + end + end +end diff --git a/plugins/article_import/lib/foodsoft_article_import/bnn.rb b/plugins/article_import/lib/foodsoft_article_import/bnn.rb new file mode 100644 index 00000000..b07b9e45 --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/bnn.rb @@ -0,0 +1,90 @@ +# frozen_string_literal: true + +# Module for translation and parsing of BNN-files (www.n-bnn.de) +# +module FoodsoftArticleImport + module Bnn + @@codes = {} + @@midgard = {} + # Loads the codes_file config/bnn_codes.yml into the class variable @@codes + def self.load_codes(custom_file_path = nil) + @gem_lib = File.expand_path '..', __dir__ + dir = File.join @gem_lib, 'foodsoft_article_import' + begin + @@codes = YAML.safe_load(File.open(File.join(dir, 'bnn_codes.yml'))).symbolize_keys + if custom_file_path + custom_codes = YAML.safe_load(File.open(custom_file_path)).symbolize_keys + custom_codes.each_key do |key| + custom_codes[key] = custom_codes[key].merge @@codes[key] if @@codes.keys.include?(key) + @@codes = @@codes.merge custom_codes + end + end + @@midgard = YAML.safe_load(File.open(File.join(dir, 'midgard_codes.yml'))).symbolize_keys + rescue StandardError => e + raise "Failed to load bnn_codes: #{dir}/{bnn,midgard}_codes.yml: #{e.message}" + end + end + + $missing_bnn_codes = [] + + # translates codes from BNN to foodsoft-code + def self.translate(key, value) + if @@codes[key][value] + @@codes[key][value] + elsif @@midgard[key] + @@midgard[key][value] + elsif !value.nil? + $missing_bnn_codes << value + nil + end + end + + NAME = 'BNN (CSV)' + OUTLIST = false + OPTIONS = { + encoding: 'IBM850', + col_sep: ';' + }.freeze + + # parses a bnn-file + def self.parse(file, custom_file_path: nil, **opts) + custom_file_path ||= nil + encoding = opts[:encoding] || OPTIONS[:encoding] + col_sep = opts[:col_sep] || OPTIONS[:col_sep] + load_codes(custom_file_path) + CSV.foreach(file, { col_sep: col_sep, encoding: encoding, headers: true }).with_index(1) do |row, i| + # check if the line is empty + unless row[0] == '' || row[0].nil? + article = { + name: UTF8Encoder.clean(row[6]), + order_number: row[0], + note: UTF8Encoder.clean(row[7]), + manufacturer: translate(:manufacturer, row[10]), + origin: row[12], + article_category: translate(:category, row[16]), + unit: row[23], + price: row[37], + tax: translate(:tax, row[33]), + unit_quantity: row[22] + } + # TODO: Complete deposit list.... + article.merge!(deposit: translate(:deposit, row[26])) if translate(:deposit, row[26]) + + if !row[62].nil? + # consider special prices + article[:note] = "Sonderpreis: #{article[:price]} von #{row[62]} bis #{row[63]}" + yield article, :special, i + + # Check now for article status, we only consider outlisted articles right now + # N=neu, A=Änderung, X=ausgelistet, R=Restbestand, + # V=vorübergehend ausgelistet, W=wiedergelistet + elsif row[1] == 'X' || row[1] == 'V' + yield article, :outlisted, i + else + yield article, nil, i + end + end + end + end + end +end diff --git a/plugins/article_import/lib/foodsoft_article_import/bnn_codes.yml b/plugins/article_import/lib/foodsoft_article_import/bnn_codes.yml new file mode 100644 index 00000000..dfb226e7 --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/bnn_codes.yml @@ -0,0 +1,1119 @@ +# BNN Codes +tax: + "1": 7.0 + "2": 19.0 + "3": 10.7 + +deposit: + "930190": 0.08 + "930200": 0.08 + "930205": 0.08 + "930210": 0.08 + "930230": 0.08 + "930260": 0.08 + "930270": 0.08 + "930280": 0.08 + "998010": 0.08 + "998016": 0.08 + "998350": 0.08 + "998360": 0.08 + "998427": 0.08 + "998440": 0.08 + "998460": 0.08 + "998470": 0.08 + "998500": 0.08 + "998510": 0.08 + "998700": 0.08 + "998710": 0.08 + "998725": 0.08 + "998730": 0.08 + "998750": 0.08 + "998760": 0.08 + "998790": 0.08 + "998800": 0.08 + "998810": 0.08 + "998840": 0.08 + "998860": 0.08 + "998880": 0.08 + "998887": 0.08 + "900010": 0.15 + "900020": 0.15 + "900030": 0.15 + "900040": 0.15 + "900050": 0.15 + "900070": 0.15 + "900075": 0.15 + "900085": 0.15 + "900089": 0.15 + "900850": 0.15 + "900860": 0.15 + "900870": 0.15 + "900890": 0.15 + "930010": 0.15 + "930020": 0.15 + "930030": 0.15 + "930035": 0.15 + "930050": 0.15 + "930090": 0.15 + "930110": 0.15 + "930120": 0.15 + "930130": 0.15 + "930320": 0.15 + "930325": 0.15 + "955230": 0.15 + "998000": 0.15 + "999983": 0.15 + "999985": 0.15 + "998040": 0.15 + "998060": 0.15 + "998070": 0.15 + "998080": 0.15 + "998100": 0.15 + "998110": 0.15 + "998300": 0.15 + "998310": 0.15 + "998320": 0.15 + "998330": 0.15 + "998340": 0.15 + "998352": 0.15 + "998370": 0.15 + "998380": 0.15 + "998390": 0.15 + "998405": 0.15 + "998417": 0.15 + "998450": 0.15 + "998480": 0.15 + "998520": 0.15 + "999200": 0.15 + "900892": 0.25 + "930290": 0.25 + "999980": 0.25 + "998020": 0.25 + "998030": 0.25 + "998090": 0.25 + "998366": 0.25 + "998420": 0.25 + "998437": 0.25 + "998740": 0.25 + "998770": 0.25 + "930450": 0.50 + "930440": 1.00 + "930460": 1.00 + "930256": 1.50 + "930257": 1.50 + "930250": 30.00 + "930252": 30.00 + "998720": 30.00 + "998830": 30.00 + "998870": 30.00 + +manufacturer: + bro: Brodowin + AAB: Azienda Agricola Bettili + ABD: Agro Bio Drom, Frankreich + ABM: Maintal GmbH + ABT: Albtal Naturkost GmbH + ACF: Arcada France S.A. + ACL: Achleitner Biohof GmbH + ADC: Coste Vincent & Francoise + ADM: Antersdorfer Mühle GmbH + ADR: Faan Zuidhorn + AFH: Doris Wallbaum & Co + agm: Angermeier Weinimport + AGR: Agrano GmbH & Co KG + AGX: Agrexco Ltd. + akr: Biohof Barnsen + ALB: Alber Pilzkonservenfabrik + alf: Bioland-Hof Altfeld + ALL: Allgäuland Käsereien GmbH + ALM: Allmendinger Metzgerei + ALN: AL Naturkost Handels GmbH + ALO: Allos-Walter Lang GmbH + ALR: Allerleirauh GmbH + ALS: Alsan Werk + ALT: Alberts-Tofuhaus + ALV: Alva + AMA: Amazonas Naturpr.Handels GmbH + AMS: Santa Fe Europe GmbH + AMW: AlmaWin GmbH + AND: Andechser Molkerei GmbH + ANF: Allgäu Natur GmbH + ANG: Angelmaier OHG + ANI: Anis de l' Abbaye + ANJ: Atelier Niedernjesa + ANL: Anderlbauer Frasdorf + ANN: AN, Ne Bienenwachskerzen + ANT: Antico Forno a Legna + APR: Apeiron + ARC: Arche Naturprodukte GmbH + ARG: ARGANDÓR + ARI: Aries Umweltprodukte + ARO: Aromabalance, Silvia Plum + art: Artesia + asc: Asch + ASS: Assindia - GmbH + ATX: Ute Arnswald + AUR: Auro Pflanzenchemie AG + BAB: Baba-Laffa, W. Maguid + BAC: Hof Backensholz + BAE: Biofarm A.E. + bai: Bioland-Hof Gerhard Baiker + bäj: Bärthele, Joachim + BAK: Bauckhof demeter Naturkost + BAL: Ballybrado, Cahir Co. + BAS: Bastiaansen Bio-Kaas B.V. + BAU: BioBauernmarkt Chiemgau eG + bba: Bäckerei Bahde + bbi: Bäckerei Bihn + BBN: Bio Bavaria Naturkost + BBO: Flemming Naturkost + BBR: Mineralbrunnen AG + BBW: Bioland Ba-Wü GmbH + bdh: Butendiek-Hof + bdp: Biodynaminska Produkter + BDW: Bio-Dienst Weiss GmbH + BEL: Bellenature + BER: Bergquell GmbH & Co.KG + BEU: Beutelsbacher GmbH + BFA: Biofarms s.r.l. + BFG: Burkhardt Feinkostwerke GmbH + BFR: Bioforce A.Vogel GbmH + BFS: Bruno Fischer GmbH Naturkost + bgb: Heinz Bursch + BGH: Burgunderhof digestif´s GmbH + BHÄ: Bäck. Härdtner + bhb: Bäckerei Höhenberg + bhc: Bioland-Hof Christiansen´s + bhk: Bioland-Hof Klauser + bhl: Bioland Hof Lesker KG + bhm: Bioland-Hof Hubert Merz + BHO: Barnhouse Naturprodukte GmbH + bhö: Bioland-Hof Hörz + bhr: Bioland-Hof Martin Häring + BIA: Bio Aras, ELAFOS + BIB: Bio Bärchen Vertriebs GmbH + bid: Imkerei Binder + BIF: Biofrisch GmbH + BIH: Markus Bihler GmbH + bil: BioHof Laurer + bim: Bieringer Mühle + BIN: Bingenheimer Saatgut AG + BIO: Bio Akademie + BIP: Bio Plus GmbH + BIR: Käserei H. Birkenstock GmbH + BIS: Bio Service SRL + BIT: Bioturm + BIV: BioVita Naturkost GmbH + BIZ: Biozyklische Produkte AG + BJS: Josef Schäfers + BKB: Bio Korn Biscuits + bkf: Bakenhus Biofleisch GmbH + bkh: Bauckhof OHG + BKM: Biokosma GmbH + bko: Bäckerei Kostner + BKP: Brava cv + BKT: Biokorntakt Vertriebs GmbH + BKW: Liane und Roman Wirth + BLA: biolare ? Pilzanbau + BLB: Blütenland Bienenh. + blg: Bollinger, Bernd + BLI: Holle Baby Food GmbH + BLL: Boller Fruchtsäfte + BLM: Binger Lammbräu GmbH + BLN: Bioland GmbH Nord + BLR: Ökofrost GmbH + BLS: Bioland Südtirol + BMA: BIOMAS + BMK: N.V. Biomilk S.A. + BML: Bio Molkerei Lembach + bms: Bioland-Milchschafhof + bmü: Bannmühle + bmw: Barbara Müller Handels GmbH + BND: Bionade GHmbH + BNE: Bionest + BOA: Bio-Obst Augustin KG + BOB: Bobalis + BOH: Boschenhof GbR + BOI: Bois Naturkost GmbH + BOK: Bodensee Kelterei GmbH + BOL: Bohlsener Mühle + böm: BÖMO + BON: Allos Walter Lang GmbH + bos: Biol-Dyn. Obstbau Seger + BOW: Gandha BV + BPL: F.J. Moog SARL + BPR: Bioprim + bra: Brack Kaffee + brb: Rüdiger Born + BRE: Gewürzmühle Brecht GmbH + BRI: Brio Spa + BRO: Jules Brocherin S.A. + brs: Asgaard + BRT: Candy Factory KG + bru: Biolandhof Brummer-Bange + bsh: Bioland Schleswig-Holstein + BSK: Wiggensbach GmbH + bsp: Bioland-Hof Speidel + bsw: Bioland-Hof Schulte-Walter + BTH: Biothek Handels-GmbH + btm: EZG Bioland-Südgetreide + BTR: Biotropic GmbH + BUC: Bucheckchen + BUF: Bodin & Fils + BÜH: Metzgerei Bühler GmbH + BUK: Borghoff & Kötter Gbr + bup: Braun U. Partner + BUR: Burk's Fränkische Öko-Nudeln + BVE: Bio Vegan GmbH + bvg: Bioland Vermarktungs GmbH + BVI: abacco B.V. + BWH: Bornwiesenhof + BWL: Haya Lebensmittel GmbH + BYO: Byodo Naturkost GmbH + CAA: Coop.Agr. ARABIOS.a.r.l. + CAB: Erich Boden Delikatessen + CAL: Calendula Naturkost Backstube + CAM: Campobelle s.r.l. + can: Anthal Canadi + CAR: Care Naturkost GmbH & Co. + CAV: Cal Valls + CBR: C. Berger + CBU: Weingut Clemens Busch + CDO: Cha Do Teehandel + CES: Il Cesto + CGL: Castiglioni S.P.A. + CHE: Chemviron Carbon gmbH + CHI: Chiemgauer Naturfleisch GmbH + CHR: De Rit Handels GmbH + CLO: Clostermann + CMD: CMD Naturkosmetik + CNH: Chiemgauer Naturkosthandel + COL: Chocolat Schönenberger AG + COR: C&C Fine Foods, Niederlassung + COS: Cosmoveda, G. Eckerle + CTE: Castle Tea + CUM: Cumnatura + CVE: Campina Verde ecosol S.L. + DAG: De Dageraad + DAK: Die andere Konditorei + DAM: Dachswanger Hof + DAN: Danival + DAV: Davert GmbH + DBB: Die Beerenbauern + DBH: CW Öko Ei GmbH + DDC: Domain de Clairac, Frankreich + DDI: Demeter Dienste + DDM: Domaine du Midi GmbH + DEH: Bio-Hofmolkerei Dehlwes + DEN: dennree-Versorgungs GmbH + DET: Detmers Getreide GmbH + DFE: Demeter Felderzeugnisse GmbH + DFR: Daniel Frank + DGU: Dal Gustaio + DIE: Helmut Arendt + DIN: Alfons u. Franz Neumeier GbR + dio: Dionisio de Nova Garcia + dis: Schulze-Schleppinghoff + DIW: Weingut Hans Diwald + DKA: Dr. Klaus Karg + DKG: Geifertshofen GmbH + dko: Disselkoen Organics + dma: Demeterhof Massmann + dmä: Demeterhof Mäck + dnj: Danner, Johann Georg + DOT: Dottenfelder Hof + dpr: Demeterhof Preller + DRM: Dr. Martins da Cunha GmbH + DSE: Deutsche See + dsw: Dirk Schulze-Wethmar + DUN: Dunn's of Dublin + DWE: Dwersteg Destillerie + DWF: Dworschak-Fleischmann GbR + dwp: Dritte Welt Partner + DYF: Dynamis France + DZW: De Zwalm + EBR: Die Regionalen + ECO: Ecomel B.V. + ECP: ecopan-Naturkost GmbH + ecr: Ecoregion + ECV: ECOVER Products nv + EGG: Eggert´s Tiefkühl-Service + EGL: Wilhelm Egle GmbH + EGM: Ökoland GmbH + EIQ: Ei.Q. GmbH + EIS: Eisblümerl Naturkost + eit: Eitzinger Franz + ELK: Naturkost Elkershausen GmbH + EOG: EOS Getränke GmbH + EOS: Eosta International BV (NL) + EPI: epikouros + ERD: ErdmannHauser GmbH + ERH: Frank Erhardt + ERN: ERNTESEGEN Naturkost GmbH + EUH: Eurohealth AG + EUN: Euronat ? Bretagne + EVS: Evers Naturkost GmbH + EWE: Ernst Weber Naturkost + fah: FairHandeln + FAI: Frucht Agentur Iberia + FAL: Breisgaumilch GmbH, Fallers + FDO: Fattoria degli Orsi + fig: Fattoria degli Orsi + FIN: Finck + fis: Brauerei Rupert Fisch + FIT: Fitne GmbH + FKS: Hermann Stiefel + FKW: Fruchsaftkelterei Klaus Weber + FLA: Flamant Vert S.A.R.L. + FLC: Flockenhaus + fle: Fleckenbühler Landprodukte + FLN: Flemming-Naturkost + FLO: Florin, Angelika Trankle + FLP: Fleur Products BV + FON: Fontaine Nahrungsmittel GmbH + FOR: Forte + FÖR: Förster + FPF: Fahrenzhausen GmbH + fps: Franz Baumann + FRC: Francia Mozzarella + FRE: Hofladen Frey + FRH: Freiheithof + FRI: Frisetta GmbH + FRK: Frischkeim Naturkost GmbH + FRO: Fromin GmbH + FRT: Florentin-Mediterranean Food + FVG: FALA Verkaufsgesellschaft mbH + FZI: Feinkäserei Zimmermann + FZS: Sommer & Co.KG + gah: Gärtnerei Amaranth + gäh: Gärtnerei Halmberg + gäk: Gärtnerei Kienast + gal: Gallung´s Ziegenhof + GAR: Gärtnerei Landes + GBA: Gerald Bartke GmbH + gch: Gärtnerei Christian Hiss + gcl: Gut Clarenhof + gdi: Gärtnerei Distel + GDR: Graindrops + GEB: Martina Gebhardt GmbH + GEE: Lupina Handels GmbH + GEH: Georg Gehrsitz GmbH & Co.KG + GEP: GEPA + GFH: Mechthild & Andres Klose + GFR: Gebr. Franz GmbH + ggk: Burkhard Dreckel + gha: Gärtnerei Andreas Hankel + gho: Gärtnerei Horizont + gie: Giegold Hefefabrik + gip: Gilchinger Pilzzucht + gks: Gärtnerei Klein Sigi + GLG: Glafey-Lichte GmbH + gli: Glitz Ehringhausen + GLM: Gläserne Meierei GmbH + GOL: Golden Temple + GÖM: Grünsfelder GmbH & Co.KG. + gom: Gomille, Torsten + GOV: Govinda?s Naturkost GbR + gpb: Gerhard Preuschl Biolandhof + gpe: Gesa Petersen + GPN: Grüner Punkt Naturkost GmbH + GRE: C.F. Grell Nachf. + GRM: Grindsted Mejeri + GRN: Biotropic GmbH + GRU: Gruel Biolandhof + GSD: Gerhard Schürholz GmbH + GSE: GSE Vertrieb + gsi: Gregor Sing + gsl: Gärtnerei Schmälzle + gub: Gärtnerei Ulenburg + GUD: Gude GmbH + GUL: Gesund & Leben + GUR: Gurtmann, Christoph + GUT: Gute Zeiten GmbH + GUZ: Glahn & Zindl + GWF: GWF eG + HAA: Haaner Felsenquelle GmbH + hag: Hartmann Getränke + hal: Haldenhof + HAM: Hamfelder Hof + HAN: Handelskontor Willmann + HÄR: Härle + HAU: Dr. Hauschka ? Wala GmbH + HAW: hawo´s Getreidemühlen GmbH + HBG: Hornberger Lebensquell + hdk: Hof Dinkler + HEC: Heuck Landbäckerei + HEI: Heidelberger Naturfarben + hek: Hecker Naturkost + HER: Herbaria Kräuterparadies GmbH + HES: Weingut Heiner Sauer + HEU: Heuschrecke Naturkost GmbH + hex: Hexerküche + HFA: Hof Farrenau, Deimling GbR + hfm: Hofgemeinschaft Fischermühle + hfn: Hofgemeinschaft Fischermühle + hgo: Imkerei Oswald + hha: Hasso Hasbach + HIE: Hierl- Der Nudelmacher + HIN: Mathias Kloppenborg GmbH + HKH: Hofkäserei Heggelbach + hkk: Hekking + HLE: Holzlehner + HLW: Herrmannsdorfer Werkstätten + HMB: Humbel Brennerei + HMS: Handelsag. Rolf Schekerka + HMÜ: Peter & Martina Linxweiler + HOC: Hoch GmbH Oblatenfabrik + hof: Hofmark Brauerei + hoh: Dorfgem. Hohenroth + hoi: Henri Willig B.V. + HOL: Holle Baby Food GmbH + HÖL: Höllensprudel + hom: Hoffmeier + HOR: Horizon Natuurvoeding B.V. + HÖR: Bäckerei Hörtling + HOV: H2Ovital oHG + HOY: Hoyer GmbH + HPG: Horn Papiergroßhandel + hse: Herbert Seitz + hst: Hof Steinrausch + hum: Huber, Martin + HUN: hanf & natur + HUZ: Huzo + hwi: Henri Willig, Kaasm. + HWL: Hawlik Pilzbrut GmbH + IAB: Imkerei (Reiner) Bienefeld + ibe: Imkerei Berrenrath + ilk: Imkerei Ludger Klinker + ILU: ILUMINA GmbH + imb: Imkerei Betz + IMF: Hain Celestial Europe BVBA + imm: Mohr + Müller Imkerei + irf: Imkerei Feldt + IRM: Imkerei Roland Maier + ISA: ISANA GmbH & Co.KG + ISE: Klaus Wolf + ISK: Isko Vertriebs GmbH + IUM: I&M Inge Stamm GmbH + jäh: Jähnke Naturkost + JAN: BioFleischerei Jansen + JAS: Jasci Donatello + JAT: Jatex Handels-GmbH + jbe: Beck Schafhof + JOP: Jogopur Yogoferm GmbH + JOR: Jordan Cereals Ltd. + KÄB: (Martin) Bauhofer Käserei + KÄL: Inntaler GmbH + KAM: KAMUT Association of Europe + KAN: Kanne Brottrunk GmbH & Co.KG + KAT: Karibu Trade + kbh: Kiebitzhof + KBW: Klosterbrauerei Weißenohe + kei: Keil, Sepp + KER: Keramik & Kerzen + kgg: Kelterei Gregor Greimel + KGÖ: Karl Gröner GmbH + KGV: Klostergut Volkenroda + KHA: Konrad Halder + KIL: Kilian + KIP: Kipepeo BIO & FAIR GmbH + KKL: KKL-Naturwaren + KKV: Vollwertbäckerei König + KLA: AlmaWin GmbH + KLD: Keimland + KLG: Keimling Naturkost GmbH + klk: Käserei Schlierbach + klo: Klotz, Martin + KMF: Käsemanufaktur, Lothar Müller + KNE: Getreidemühle Knecht KG + KNÖ: Robert Knöbel + KON: Engelhard GmbH & Co. KG + KOR: Kornblume, F.+ B.Brinkmann + KPL: Kräutergarten Pommernland + KRA: Kranichhof + KRÄ: KAULFUSS + KRE: Krämers Ernährung + krr: Fachkrankenhaus Ringgenhof + KTO: Kato + KUC: Kolla & Co + KWE: Köhlerei Wengert + LAB: Labroco Agrarconsult + LAF: Hofgut Algertshausen + LAL: La Luna del Rospo + läm: Lämmerhof + LAN: Landkrone GmbH + LAS: Lakhsmi + LAV: laverana GmbH + LBI: Do-it Dutsch + LEB: U. Walter GmbH - Lebensbaum + LHQ: St. Leonhardquelle + LIL: Legend International Ltd. + LIM: Lima NV + LIR: Lily Rose + LIW: Lichtwurzel, Imton + LJL: Johann Langgartner + LMC: Lammersiek & CO. + LNA: Grabower Süßwaren GmbH + LÖC: Löcke Bio-Pilzzucht + LOG: Logona Hans Hansel GmbH + LSP: La Spinosa ( Weingut) + LTA: Lauretana, Wasser Import GmbH + LUB: Lubs GmbH + lüp: Lübcke Papier GmbH & Co KG + LUV: Luvos Heilerde + MAB: Bio-Nahrungsmittel GmbH + mah: Hof Mahlitzsch + MAI: Maisch + map: Maple GmbH + MAR: Marschland NK GmbH + MÄR: Märkisches Landbrot GmbH + MAT: Martinshof GmbH + MÄU: Gebr. Grund GmbH & Co.KG + MAY: Mayka Naturbackwaren GmbH + MAZ: Mazer, Bernd + MCA: Mustiola International SRL + med: Medousa, Griechenland Importe + MEK: Weingut Meinklang + met: Metsä Tissue GmbH + MGN: Naturland-Bauern e.G. + mhb: Meyerhof Belm + mhh: ?Die Meierei? Hansfelder Hof + MID: Midi + mig: Migliore + mil: Miller GmbH & Co.KG Agnes + MKF: merkur frucht Freiburg GmbH + MKL: Makulaku Confectionery Ltd + MLL: Mollis Kinderprodukte GmbH + MMC: MM Cosmetic GmbH + MOA: MolenAartje B.V. Natudis + MOB: Frisetta GmbH & Co.KG + MOC: Wasserprinz; div. Anbieter + MOI: Moin BioTK-Bachwaren + MOK: Mokobella EU GmbH + MOL: Moltex Baby-Hygiene GmbH + MOR: EgeSun GmbH + MÖR: Mörk Naturkostprodukte + MOU: Wertform GmbH & Co + MSV: mesa verde + MTB: Mont´Albano + MUL: Multikost Vertriebs GmbH + MWO: Milchwerke Oberfranken + MZG: Sieben Zwerge GmbH + mzi: Mathias Zipf + NAB: Hubert Tempelmann e.K. + NAC: BodyWise (UK)Ltd. + NAG: Tofumanufaktur Nagel GmbH + NAM: Naturmaelk A.m.b.a. + NAP: Combu Cha + NAT: Naturata e.G. + nbm: Nußbaumer, Roman + NBO: Nürnberger Bio-Originale + NCO: Natur Compagnie GmbH + NEE: Neue Erde GmbH + NEG: Neu´s GmbH & Co. KG + NEU: Gebr. Ehrnsperger e.K. + nhb: Naturlandhof Biberger + NHU: Natur Hurtig (Himalaya Salz) + nlg: Nordland, Lebensgem. + NMA: NaturMarkt GmbH + nmd: Münzner + NNA: Mensch & Natur AG + NOK: Noka-Sojamanufaktur GmbH + NPG: Nette Papier GmbH + NSC: Naturkost Schuchardt + NTM: Natumi GmbH Produkte & Ideen + NTR: Naturian Ökoweine OHG + NTU: Naturion + NUR: IL NURAGHE GmbH + NUT: Nutrifors AG + NWA: Nikolaihof Wachau - Weingut + NWR: Öko-Norm GmbH + OAT: Ceba Foods AB + obb: Obsthof Bruno Brugger + obm: Obsthof Bernd Majer + OBS: Öko-Bauernhöfe Sachsen GmbH + ÖBW: Brodowin Ökodorf + OCB: OCB-Vertriebs-GmbH + ODE: Odenwald EKO Brood en Banket + ODI: ODIN Holland C.V. + ÖER: Öko Ernte GmbH + ÖFA: Öko Feinkost Andechs Gmbh + ÖFR: Ökofrost GmbH + ogh: Demeter Gärtnerei Obergrashof + ohc: Obsthof Cordes + OHE: Obsthof Heinrich + ohh: Obsthof Hermann Helde + OHL: Ohling, Andreas + ÖKB: ÖkoBo + ÖKH: Ökohum Vertriebs GmbH + ÖKL: Ökoland GmbH Nord + ÖKN: Ökonatur + ÖKU: Naturkosthandel Ökollus + ÖLI: Öko-Line + ÖMA: ÖMA- Beer GmbH + OML: Ostermühle Naturkost GmbH + ÖMS: Ölmühle Solling GmbH + ORG: Organix4U GmbH + ORH: Obsthof Robert Hartmann + ORO: Organic Oils S.P.A. + ort: Biofrucht Ortlieb GbR + osn: Osning-Getränke GmbH + ÖWK: Haus am Goldberg GmbH + ÖWR: Weingut Richard Schmidt + ÖWS: Öko-Weinimport Schmid + pab: Bacchini Roberto & C. S.n.c. + PAN: Pasta Nuova GmbH + pau: Bioland Gemüse Paul + PEM: PEMA Heinrich Leupoldt KG + PER: Perger Getränke GmbH + PET: Marcel Petite, Frankreich + PFH: Gabriele Gersdorf GmbH + PFO: Papierf. Oberschmitten GmbH + PID: MW BGL Chiemgau eG + PIM: Pinzgauer Molkerei + PIN: Pinkus Müller GmbH & Co.KG + PLA: Käserei Plangger Ges.m.b.H. + PLG: Peralge, Marciella Callegarie + PLO: div. Anbieter + PMI: Peterstaler Mineralquellen + PMP: Progeo Mangimi Petfood + PNA: Pro Natura S.A. + PÖB: Pötzelberger + pod: Poder GmbH + PÖS: PINGU-Öko-Tiefkühlservice + PRG: Provence Regime S.A. + PRN: Pro Natur GmbH + PRO: Probio Handelsgesellschaft + PRV: Provamel + PVL: Primavera Life GmbH + RAA: Raab + RAC: Rachelli Italia s.r.l. + rad: Radicula GmbH (Avalon) + ran: Randegger Ottilienquell + RAP: Rapunzel Naturkost AG + RBB: Michael Krieger KG + rde: Roman Denis Bioland-Gemüsebau + rds: Rudolf Schramm + RED: Redecker + rei: Reicheneder, Gerhard + rha: Hofgut Rengoldshausen + RHG: Rheinland-Höfe GmbH + RHÖ: Rhöner (Brauerei) + RIE: Peter Riegel Weinimport GmbH + RIN: Ringenwalde Werkhof + RIS: Ristic + RIT: De Rit Handels GmbH + RLG: Metzgerei Rieblinger + ROB: Geflügelhof RoBert?s + rog: Söbbeke GmbH & Co. KG + ROH: Geflügelhof Rothäusle + ROL: C. F. Rolle Mühle GmbH + ROM: Rosmarin Ingo Karrasch GbR + ROS: Hubmann- Rosengarten + RÖS: Georg Rösner Vertriebs GmbH + ROT: Rother Bräu + rsh: Rösslerhof + RTE: Manfred und Christine Rothe + RUH: Riensch & Held GmbH & Co. KG + RUN: Runge Nahrungsmittel GmbH + RUS: Ruschin Makrobiotik GmbH + RZO: Rzollhäusle, H.R. Hauser + SAB: SANBEAM Gesunde Produkte GmbH + SAC: Petersilchen Sanchon GmbH + säh: Karla u. Sebastian Schäfer + SAL: Salomon + SAN: Sante Naturkosmetik GmbH + SAO: Gsund & Schön Sanoll + SAR: Sanatur GmbH + SAS: S´Atra Sardigna Coop A.r.l. + SAV: Santaverde GmbH + sbä: Steinofen Bäcker + SBG: Mol Hohenlohe-Franken e.G. + SBH: Matthias Höfflin + SBM: Saarpfälzische Bio-Höfe GmbH + SCH: Naturkost Schramm GmbH + SCK: Walter Rau GmbH & Co. KG + SDE: Robert Schindele GesmbH + SDL: Siegfried Schedel + SEE: Weingut W. Seeber + SEG: Sennerei Walchsee GmbH + SEK: Sekowa Seibold KG + SEL: La Selva Vertriebs-GmbH + SFE: Hof Mühlenberg E. Schiffers + sfm: Schäfer, Martin (Michaelshof) + SFO: Sinfo Naturkost & Naturwaren + SHE: Weingut Schäfer-Heinrich + shh: Max Fischer + SIN: Singer + SJF: Sojafarm + sjh: Metzgerei Schojohann + sjs: Schaut, Josef + SKA: Schönegger Käse-Alm GmbH + SLC: Svenska LantChips AB + sle: Schilling, Erich + slm: Salm, Elvira (Limberger) + SMA: Sana-Mare + SND: Weingut Sander + SNF: Sanoflore + SNI: Weingut Stortz-Nicolaus + snn: Monika u. Thomas Sannmann + SNT: Sonett OHG + SNZ: Schnitzer Bräu + SOB: SOBO Naturkost + SÖB: Söbbeke GmbH & Co. KG + SOD: Sodasan GmbH + SOE: Salamita Soc. Coop. A.r.l. + SOF: Soto Feinkost, Oskar Schramm + SOJ: Triballat Noyal + SON: Sonne GmbH + SOT: Allgäuland + SPA: Spaichinger Nudelmacher GmbH + spe: Speckhan, Rudolf + SPH: Spreewälder Hirsemühle + SPI: Spielberger KG Naturata e.G. + SPL: Silver Plastic GmbH & Co. KG + SPR: B & G Sprossenparadies GmbH + sqn: St. Nikolaus Quelle + SRH: Scharein, Hubert + SSC: Sural-Sacicc + SSI: Santisi Vollkornnudeln + STB: Seitenbacher GmbH Naturkost + STE: Steck + sth: Scholtenhof + STN: Sonnentor GmbH + STY: Teebaumöl Kosmetik + STZ: Schnitzer OHG + SUN: Sunval Nahrungsmittel GmbH + SVA: Svadesha Naturkost- Vertrieb + SVE: Svenska Lant Chips + svm: Hof von der Mehden + SWE: Schuldt & Weber + swo: Schwollener Sprudel + sww: Karin u. Corney Weimeijer + SWZ: Schweizer GmbH + SYM: Sympakorn + tag: Tagwerk + TAI: Life Food GmbH + TAP: TAPIR Wachswaren GmbH + TAR: Tarpa Naturkost + TAU: Tautropfen GmbH + TDP: Terra di Puglia + TEL: Hakle GmbH + TER: Terrasana Naturvoeding BV + TES: Terra Soleil + TEU: Teutoburger Ölmühle + TFO: Faan Zuidhorn BV. F.Andringa + TIL: Tilouche Fruchtimport GmbH + TLI: CV Ter Linde + TMJ: Thise Mejeri + TOF: Ökofrost GmbH + TOP: ToPas GmbH + tph: T.Port Hamburg GmbH & Co. + TRA: Tradin Organic B.V. + tro: Tropenfruchtimport GmbH + TUC: Tra Terra e Cielo + ubm: Upländer Bauernmolkerei GmbH + uhe: Uta Helberg + ulh: Ulmenhof + una: Uli Natterer + uns: Unseld´s Backstube + unt: Ulrich u. Monika Unterweger + URD: Uni-Vert + URT: Urtekram A/S + VAV: Vallée-Verte Handelsges. mbH + vbr: Vollkornbäckerei Rasche + VEN: eco cosmetics GmbH & Co. KG + VGB: Bioland Schleswig Holstein + VGE: Verlag gesund essen GmbH + VGF: Hansen & Koschmieder GmbH + VIA: Viana Naturkost GmbH + VIB: Fattoria VIB + VIV: S.A. Viver, Frankreich + VIZ: Vino Zero + VLV: VivoLo Vin, Ökoweinhandel + vms: Traitteur Villemin GmbH + VNI: L. Weinrich GmbH & Co.KG + VOE: voelkel GmbH + VOL: Volvic, Frankreich + VUN: Velazquez Universal s.L. + VVE: Viola Verde GmbH + vzw: Hüser van Zwoll GmbH & Co. KG + waa: Gartenbau Waas + wal: Walter, J. + WAT: Walter Thies Zellglas + WBT: WBT SRL + WDL: Milchkoop Wendland GmbH + WDM: Windmill Organics Foods Ltd. + WDN: Großbäckerei Wendeln + web: Weber GmbH + WEG: Weingut O. Gottschalk + WEH: Peter Werth + WEL: Weleda AG + WEN: Wilhelm Weber GmbH + WER: Werz GmbH & Co. KG + WGP: Wagner Tiefkühlprodukte GmbH + who: Westhof GmbH + WHS: Deutsche Parmalat GmbH + WIE: Weingut Stephanshof + WLM: Ecover Belgium n.v. + wmr: Richard Wirthmüller + WOB: Bäckerei Wolfgruber OHG + WOL: Verlag Fred Wollner GbR + WOO: woodshade organics ApS + WPA: Wepa P. Krengel GmbH & Co. KG + WRK: Weingut Friedhelm Rinklin + WSB: Battenfeld-Spanier + wsq: Wittenseer Quelle + WTI: WTI GmbH + WUN: Wunderland e.V. + WUR: Wurzel Fachgroßhandel + WÜR: Prima Käse, Jürgen Würth + wwb: Westerwald Bio GmbH + wwi: Weber, Wilhelm + WYS: KAMO, Peter Wyssling + wzb: Wenzelburger GbR. + YAK: Faan Zuidhorn BV. F. Andringa + YAR: Yarrah Food/Vink Sales BV + ZAN: Zann Bio-Center + ZAP: Zapparoli + ZEL: Zellertaler Kellerei GmbH + ZIM: E. Zimmermann GmbH & Co + ZLN: Pastificio Zanellini spa + ZNL: Zagler´s Naturladen + zsl: Ziegenhof Schlatt + ZWE: Zwergenwiese Naturkost GmbH + ZWI: E. Zwicky (Deutschland) GmbH + ZWÖ: Weingut im Zwölberich + +category: + "01": Brot und Backwaren + "02": Milch, Milchprodukte, Eier, Tofu + "03": Obst, Gemüse, Sprossen, Pilze + "04": Fleisch, Wurst, Snacks + "05": Getreide, Ölsaaten, Nußkerne + "06": Nudeln, Trockenfrüchte, Müsli + "07": Brotaufstriche, Honig, Nußmuse + "08": Würzmittel, Öle, Fette + "09": Süßwaren, Gebäck, Pudding + "10": Spezialsortimente + "11": Tee, Kaffee, Kakao + "12": Getränke + "13": Kräuter, Heilmittel, Ätherische Öle + "14": Körperpflege und Kosmetik + "15": Wasch- und Reinigungsmittel + "16": Haushaltsgeräte + "17": Bücher und Zeitschriften + "18": Papier, Schreibwaren, Spielzeug + "19": Textilien und Schuhe + "20": Farben, Bau- u. Wohnmaterial + "0101": Brot + "0102": Brötchen, Semmeln, Brezen + "0103": Spezialitäten + "0111": Standardgebäck + "0112": Saisongebäck + "0113": Kuchen, Torten + "0121": Pikantes Gebäck + "0131": Sonstiges vom Bäcker + "0201": Milch + "0202": Sauermilchprodukte + "0203": Quark + "0204": Joghurt + "0205": Pudding + "0206": Sahne, Butter, Sonstiges + "0211": Ziegen-/Schafsmilchprodukte + "0221": Frischkäse + "0222": Weichkäse + "0223": Halbfester Schnittkäse + "0224": Schnittkäse + "0225": Hartkäse + "0231": Ziegen-/Schafskäse + "0241": Eier + "0251": Tofu, Tempeh + "0252": Soja-Frischprodukte + "0253": Soja- und Reisgetränke + "0254": Sojapudding + "0301": Obst, heimisch + "0302": Südfrüchte + "0303": Beeren + "0304": Exoten + "0311": Kartoffeln + "0312": Wurzelgemüse + "0313": Salate + "0314": Blatt- und Zwiebelgemüse + "0315": Kohlgemüse + "0316": Fruchtgemüse und Spezialitäten + "0321": Kräuter + "0331": Keime und Sprossen + "0341": Pilze + "0351": Nüsse in Schale + "0399": Div. Frischprodukte + "0401": Fleisch + "0402": Geflügel + "0411": Wurst + "0421": Fisch + "0422": Fischerzeugnisse + "0431": Burger, Kroketten + "0441": Sonstige Snacks + "0501": Getreide + "0502": Hülsenfrüchte + "0511": Ölsaaten + "0521": Nußkerne + "0531": Keimsaaten + "0601": Getreideprodukte + "0602": Flocken + "0603": Nudeln + "0611": Sojaerzeugnisse + "0621": Trockenfrüchte + "0631": Müsli + "0632": Krunchy + "0701": Würzige Aufstriche + "0702": Fruchtaufstriche + "0711": Honig + "0712": Honigprodukte + "0721": Nußmuse + "0801": Salz und Kräutersalz + "0802": Essig + "0803": Senf + "0804": Suppen und Soßen + "0805": Sojasoße und Miso + "0806": Würzmittel + "0811": Gewürze + "0812": Gewürzmischungen + "0813": Gewürzöle + "0821": Speiseöle + "0822": Margarine + "0831": Pikante Konserven + "0832": Süße Konserven + "0841": Fertiggerichte + "0842": Halbfertiggerichte + "0901": Frucht- und Knusperriegel + "0902": Bonbons und Lutscher + "0903": Schokolade + "0904": Pralinen + "0911": Dauergebäck + "0912": Waffeln + "0913": Kekse + "0914": Knabbergebäck + "0921": Süßmittel + "0922": Obstdicksäfte + "0923": Carob + "0931": Pudding + "0932": Back- und Geliermittel + "0933": Kochhilfen, Fermente + "1001": Säuglingsbreie + "1002": Babykost + "1011": Makrobiotische Spezialitäten + "1021": 3. Welt-Solidaritätswaren + "1031": Tiefkühlkost + "1051": Tiernahrung + "1101": Früchtetee + "1102": Kräutertee + "1103": Kräutertee-Mischungen + "1104": Rooibos + "1105": Gewürztee + "1111": Schwarzer Tee + "1112": Grüner Tee + "1113": Aromatisierter Tee + "1121": Bohnenkaffee + "1122": Ersatzkaffee + "1131": Kakao + "1132": Schokoladengetränke + "1201": Wasser + "1211": Fruchtsäfte + "1212": Fruchtnektare, Limonade, Schorle + "1213": Gemüsesäfte + "1215": Kwaszgetränke, Getreidegetränke, Diätgetränke + "1221": Bier + "1231": Rotwein + "1232": Rosé-Wein + "1233": Weißwein + "1241": Cidre + "1242": Schaumwein + "1251": Spirituosen + "1301": Heilkräuter + "1302": Kräutermischungen + "1311": Freiverkäufliche Arzneimittel + "1312": Kur- und Heilmittel + "1321": Ätherische Öle + "1322": Ätherische Ölmischungen + "1331": Duftlampen und Rauchgefäße + "1332": Zubehör für Duftwerk + "1341": Räucherwerk + "1401": Seife + "1402": Gesichtsreinigung und -pflege + "1403": Körperöl und Körperpflege + "1404": Haarpflege + "1405": Zahn- und Mundpflege + "1406": Handcreme + "1407": Fußpflege + "1411": Badezusätze und Duschpräparate + "1412": Deo, Eau de Toilette + "1413": Rasierzubehör + "1414": Sonnenschutz + "1415": Baby- und Kinderpflege + "1421": Dekorativkosmetik + "1422": Parfum + "1423": Sonstige Kosmetik + "1431": Zahnbürsten + "1432": Bürsten und Kämme + "1433": Kosmetikzubehör + "1441": Hygiene + "1451": Tierpflege + "1501": Waschmittel + "1502": Spülmittel + "1503": Reinigungsmittel + "1511": Dosierhilfsmittel + "1521": Schuhcreme + "1531": Insektenschutz, Düngemittel + "1601": Handmühlen + "1602": Elektromühlen + "1603": Kombi-Maschinen + "1604": Zubehör für Kombigeräte + "1611": Sonstige Haushaltsgeräte + "1612": Keimgeräte, Dörrapparate, Gärtöpfe + "1621": Küchenhelfer + "1622": Kaffee- und Teefilter + "1631": Haushaltswaren + "1701": Kochen und Backen + "1702": Ernährung und Gesundheit + "1703": Landwirtschaft und Garten + "1704": Ökologie und Ergänzendes + "1705": Baubiologie + "1706": Esoterisches + "1707": Sonstige Bücher + "1711": Zeitschriften + "1801": Schmuckpapier + "1802": Schulpapier + "1803": Neutrales Papier + "1804": Formdrucke + "1805": Geschenkpapier + "1806": Sonstiges Papier + "1811": Stifte + "1812": Malbedarf + "1813": Knetwachs + "1821": Kerzen + "1831": Spielzeug + "1832": Bastelbedarf + "1841": Edelsteine + "1851": CD's + "1852": MC's + "1901": Windeln + "1902": Baby- und Kinderwäsche + "1903": Erwachsenenwäsche + "1904": Oberbekleidung + "1905": Strümpfe + "1911": Schuhe und Einlegesohlen + "2001": Imprägnierung, Lasur, Balsame + "2002": Lacke + "2003": Wandfarben + "2004": Kleber + "2009": Sonstige Farben, Lösemittel + "2011": Tapeten + "2012": Bodenbeläge + "2013": Dämmstoffe + "2019": Sonstige Baumaterialien + "2021": Mobiliar + "2022": Matratzen + "2023": Heimtextilien + "2029": Sonstige Wohnmaterialien + "2031": Werkzeug, Hilfsmittel \ No newline at end of file diff --git a/plugins/article_import/lib/foodsoft_article_import/dnb_codes.yml b/plugins/article_import/lib/foodsoft_article_import/dnb_codes.yml new file mode 100644 index 00000000..b7cf5b02 --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/dnb_codes.yml @@ -0,0 +1,129 @@ + +# from http://www.nieuweband.nl/producten/groepen/ +indeling: + 1: Verswaren + 50: Kaas + 62: Schapenkaas + + 2: Basisproducten + 850: Noten + 855: Noten grootverbruik + 700: Peulvruchten + 705: Peulvruchten grootverbruik + 340: Rijst + 341: Rijst grootverbruik + 450: Vlokken + 455: Vlokken grootverbruik + 800: Zaden en pitten + 805: Zaden en pitten grootverbruik + 603: Melen grootverbruik + + 3: Ontbijt en lunch + 943: Marmelade + 1272: Muesli en poppies + 1000: Notenpasta + 1276: Ontbijtmelen + 1295: Rijstwafels + 1290: Roggebrood + 1270: Sandwichspread + 940: Vruchtenbeleg + 942: Vruchtenjam + 944: Vruchtenstroop + 1300: Knäckebröd, toast en beschuit + + 4: Warme maaltijd + 1820: Mosterd + 1610: Olijfolie + 1600: Olijven + 1451: Peulvruchtenconserven + 1957: Pindasaus + 1960: Sambal, ketjap en pittige smaakmakers + 2170: Seitan + 2260: Siropen + 2248: Smaakmakers + 1500: Soepen en bouillon + 1515: Soepstengels + 2000: Sojasauzen + 2250: Suiker + 1452: Tafelzuren + 1590: Tamme-kastanje-producten + 1975: Thaise keuken + 1900: Tomatenproducten + 1670: Vetten + 1930: Visconserven + 2175: Vleesvervangers + 1360: Vruchtencompote + 1400: Vruchtenconserven + 1350: Vruchtenmoes en -puree + 2249: Zout en kruidenzout + + 5: Sappen en dranken + 2605: Rode wijn Oostenrijk + 2604: Rode wijn Portugal + 2602: Rode wijn Spanje + 2608: Rode wijn Zuid-Afrika + 2612: Rosé Spanje + 2617: Rosé Zuid-Afrika + 2420: Smoothies + 2455: Sojamelkproducten + 2505: Speciaalbieren + 2400: Vruchtensappen + 2490: Waterijs + 2637: Witte wijn Argentinië + 2630: Witte wijn Frankrijk + 2634: Witte wijn Griekenland + 2631: Witte wijn Italië + 2635: Witte wijn Oostenrijk + 2632: Witte wijn Spanje + 2638: Witte wijn Zuid-Afrika + + 6: Warme dranken en theekruiden + 3102: Kruidenthee builtjes + 3100: Kruidenthee los + 3020: Kruidenthee met geneeskrachtige werking + 3009: Rooibosthee + 3010: Thee grootverpakking + 3052: Theekruiden + 3008: Witte thee + 3011: Yogi spice tea + 3012: Yogi tao tea + 3000: Zwarte thee + + 7: Versnaperingen + 3552: Lollies + 3470: Nougat en fudge + 3570: Raw Food + 3360: Rozijntjes in kinderverpakking + 3410: Snijkoek + 3555: Snoep met suiker + 3550: Snoep zonder suiker + 3405: Stroopwafels + 3350: Tortillachips en salsa + 3358: Zoete chips + 3540: Zoethoutstokjes + 3365: Zoutjes, hartige bites en popcorn + 3530: Laurierdrop + + 8: Persoonlijke verzorging en cosmetica + 5036: Lavera + 5037: Namaste + 5040: Natracare + 5042: Odylique + 5049: Sonett + 5055: Urtekram + 5065: Weleda + + 9: Natuurtherapeutisch + 5455: Kruidentincturen + 5420: Propolis-producten + 5245: Zelfzorgmiddelen + 5280: Huid- en massage-olie + + 10: Non Food + 5517: Luiers en babydoekjes + 5510: Maandverband en tampons + 5520: Toiletpapier e.d. + 5890: Voor kinderen (en volwassenen) + 5650: Was- en schoonmaakmiddelen + 5515: Watten + 5610: Luchtverfrissers diff --git a/plugins/article_import/lib/foodsoft_article_import/engine.rb b/plugins/article_import/lib/foodsoft_article_import/engine.rb new file mode 100644 index 00000000..49c54bab --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/engine.rb @@ -0,0 +1,12 @@ +module FoodsoftArticleImport + class Engine < ::Rails::Engine + config.to_prepare do + Dir.glob(Rails.root + "app/decorators/**/*_decorator*.rb").each do |c| + require_dependency(c) + end + end + def default_foodsoft_config(cfg) + cfg[:use_article_import] = true + end + end +end diff --git a/plugins/article_import/lib/foodsoft_article_import/foodsoft.rb b/plugins/article_import/lib/foodsoft_article_import/foodsoft.rb new file mode 100644 index 00000000..25ff4bad --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/foodsoft.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +# Module for Foodsoft-file import +# The Foodsoft-file is a CSV-file, with semicolon-separated columns, or ODS/XLS/XLSX + +require 'roo' +require 'roo-xls' + +module FoodsoftArticleImport + module Foodsoft + NAME = 'Foodsoft (CSV, ODS, XLS, XLSX)' + OUTLIST = false + OPTIONS = { + encoding: 'UTF-8', + col_sep: ';' + }.freeze + + # Parses Foodsoft file + # the yielded article is a simple hash + def self.parse(file, custom_file_path: nil) + custom_file_path ||= nil + opts = OPTIONS.dup + + ss = FoodsoftArticleImport.open_spreadsheet(file, **opts) + + header_row = true + ss.sheet(0).each.with_index(1) do |row, i| + # skip first header row + if header_row + header_row = false + next + end + # skip empty lines + if row[2].to_s.strip.empty? + # raise no order number given + yield nil, nil, i + next + end + + article = { order_number: row[1], + name: row[2], + note: row[3], + manufacturer: row[4], + origin: row[5], + unit: row[6], + price: row[7], + tax: row[8], + unit_quantity: row[10], + article_category: row[13] } + article.merge!(deposit: row[9]) unless row[9].nil? + FoodsoftArticleImport.generate_number(article) if article[:order_number].to_s.strip.empty? + if row[6].nil? || row[7].nil? || row[8].nil? + yield article, 'Error: unit, price and tax must be entered', i + else + yield article, (row[0] == 'x' ? :outlisted : nil), i + end + end + end + end +end diff --git a/plugins/article_import/lib/foodsoft_article_import/midgard_codes.yml b/plugins/article_import/lib/foodsoft_article_import/midgard_codes.yml new file mode 100644 index 00000000..8777e2e7 --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/midgard_codes.yml @@ -0,0 +1,294 @@ +manufacturer: + "61": Maintal + AB: Agrobioservice + AD: Anita Dehnert + AH: Phyto Treasures e.K. + AO: Arganöl + AR: ARIES + AS: Abraham Schinken + Ad: Molkerei Andechs + An: Frans Andringa + Ap: Apfeltraum + Ar: Provamel über Arche + Ay: Aytem + BA: BauckHof Amelinghausn + BB: Bakenhus Biofleisch GmbH + BC: Bio-Bäckerei Bucco + BD: Biosa + BF: Bruno Fischer + BG: Bauers Garten + BH: Bauck Hof + BHA: Bauck Hof Amelinghausen + BI: Biofarben + BK: Burger Knäcke + BKO: BioKräuterei Oberhavel + BL: Beumer & Lutum + BM: Bohlsener Mühle + BN: Brochenin + BOD: Bode Naturkost + BR: Luchs Bier + BT: Beltane Naturkost GmbH + BU: Baumschule am Butzelberg + BV: BIO VITA + BZ: Biozeit + Ba: Bauck demeter Produkte + Bb: Beutelsbacher + Bd: Biosa Danmark Aps + Be: Behncken + Bf: Backforum + Bg: Butzelberg + Bh: Barnhouse + Bj: Milchschafhof Brünjes + Bk: Blank + Bm: Biomax + Bn: Bentele + Bo: Bobalis + Bt: Bretti's + Bu: Biogärtnerei Bauer + By: Byodo + CA: Care + CF: CLUB Feinkost + CI: CIDRERIE + CV: Cosmoveda + Ca: Campo + Cl: Obsthof Clostermann + Co: Obsthof Cordes (Heinrich) + Cp: Campobello + Cs: Cosmoveda + Ct: cbet GmbH + DA: Danival + DE: DEMETER-Erzeugergemeinschaft + DH: Dieter Hein Wurstwaren + DM: Dr. Martins + DN: Hof Dannwisch + DO: Donath-Mühle + DR: De Rit + DV: Davert + DW: Vovic / Evian + De: Dennree + Dk: Dinkula + EB: Erich Boden + EH: Engemann Handel + EI: Natürlich Eistert + ELM: BIONADE + EN: Provence Regime + EO: Eosta + ER: Euresis + Eb: Eisblümerl + Eh: Erhardt Meerrettichprodukte + Ei: Eiland + El: Kelterei Elm + En: Eichhorn + Er: Erdmannhauser Brezelfabrik + Es: Erntesegen + FB: Flensburger Brauerei + FE: Frucht-Express + FF: Schiffers + FI: Fromi GmbH + FL: Florian Kerzen + FR: I Frutti del Sole + FU: Future 3000 + Fh: Florahof + Fq: Fläming-Quelle + Fr: Frunet + Ft: Fontaine + GA: Bio-Gärtnerei Altglobsow + GG: Naturhof Günter Gaßmann + GH: Gutshöfe + GN: Nesse Gewürze + GO: Der Georgshof + GS: Gut Schmerwitz + GT: Gut Temmen + Gb: Grabower + Gl: Glaciar + Go: Golden Temple + Gr: Grützdorfer + Gw: Gwidon Zastawa + Gä: Gärtnerei am Bauerngut + GÖ: Stadtgut Görlitz + HA: Haaner Felsenquelle + HB: Hof Bockum + HF: Hühnerhof Falkenthal + HK: Heinz Ketchup + HM: Hof Marienhöhe + HO: Hoffmann + HS: Obstbau H. Schalkau + Ha: Hake + Hc: Hoch Oblatenfabrik + He: Hennicke + Hk: Natur Obsthof Hauke + Hl: Heidehof + Ho: Holle + Hu: Humanopolis + Hü: Hütterman + IC: Japan Grüntee + IN: Isola della Natura + IOC: IOC + IS: Isana + Ib: Iberia + Il: Il Nuraghe + Is: ISANA + JH: Beerenobst + JS: Juers Fruchtchips + Je: Jelitta Käse + KD: Kristdyn + KG: Kräuter Gut + KK: 74271 + KN: Öko-Gartenbau + KP: Kräutergarten Pommerland + Ka: Kanne + Kg: Karg Brotgenuß + Kä: Kärrners + Kö: Obsthof König + LB: Lammsbräu + LE: LEEB Schaf- und Ziegenmolkerei + LI: Legend Organics + LM: LeMar + LS: La Selva + La: Lahmann + Lb: Lebensbaum + Le: Leuchtenberg Sauerkrautfabrik + Lh: Lindenhof + Li: Lima Belgien + Lk: Landkrone + Ln: Land in Sicht + Ls: Lubs GmbH + Lu: Luvos Heilerde + Lw: Gärtnerei Löwenzahn + MA: Mack + MB: Mabutake + ME: Martin Evers + MH: Märkische Heide + MI: Martin Ibele + MII: Katal. Olivenöl + ML: Märkisches Landbrot + MM: Bioland Imkerei + MT: Maintal + MV: MegaVega Limited + MY: Mayka Brezel + Ma: Marschland + Mg: MIDGARD + Mh: Melchhof + Mn: Mosna + Mo: Mosaikwerkstätten + My: MAYKA, Brezelfabrik + Mü: Hofmolkerei GmbH Münchehofe + MÖ: Märkischer Ökovertrieb + NE: Natürlich Eistert + NM: + NO: Nürnberger Bio Originale + NQ: Pineo Wasser + Na: NATURATA + Nt: Natumi + OTC: OTC + Od: ODIN Holland + PB: Peter Bentele + PG: Pilzgarten + PH: Biopilzhof + PM: Pinkus Müller + PN: Pro Natura + Pi: Piding + Pt: Port International + QB: Panettoncino + RB: Rother Bräu + RP: Rheinsberger Preussenquelle + RS: rosmarin BIOBACK + RZ: Ranch Zempow + Ra: Raab + Rb: Rabenhorst + Re: Rebgarten + Rg: Rosengarten + Rh: Rotenhäusler + Ro: Geflügelhof Robert + RoL: Robert´s LOSE + Rt: Rottstock + Rö: Römerquelle + SB: Sabines Bauernhof + SBP: Stiftelsen Bananen + SC: Sommer & Co. + SF: Sprossen + SH: Spreewälder Hirse + SI: SINFO + SK: Spargelhof Kreienbaum + SL: St. Leonhardsquelle + SM: Seenlan Müritz + SO: Sonett + SR: Sprossenmanufaktur GbR + STN: Sonnentor + SV: SANTAVERDE ALOE VERA + Sa: Salamita + Sb: Hans Hermann Soetbeer + Sc: Schulz-Deetz + Sch: Hof Schütte + Sd: Savid + Se: Sekem, Ägypten + Sf: Sauerkonservenfabrik Schweizer + Sh: Kombucha + Si: Land in Sicht + Sk: Schock Ludwigsburg + Sm: Schramm + So: Sophienhof + Sp: Spielberger + Sr: Sanmar + St: Steck Senf + StB: Stralsunder Brauerei + Su: Sun,Backwaren aus Norwegen + Sv: Sunval demeter-Produkte + Sw: Szilleweit + Sy: Synanon + Sz: Schrozberg + Sü: Südasien + TB: Team Blue + TF: Terra Frischdienst + TN: Tofu Nagel + TR: Teltower Rübchen + Ta: Tarpa + Te: Teutoburger Ölmühle + Ti: Tiedemann + Tm: Tillmann + Tr: tri d´Aix + Tt: Tautropfen + Tö: Töpfer Rohrzucker + UK: Udo Kolm Bananen + UL: Gärtnerei Ulenburg + UV: Uni-Vert + Ul: Ulenburg Bioland Gemüse + VA: Kleingenossenschaft VENUSTA + VD: V & D + VE: Vega e.K. + VG: Biolog. Vollwertgetränke + VT: Vogt + VV: Vallé Käse + Vi: Viana Tofu + VlV: Vivo Lo Vin + Vo: Voelkel + WB: Weber + WD: Werder Feinkost GmbH + WH: Weide-Hardebek + WK: BioCompany Kaffee + WL: Wendland Storchenmilch + WP: Plosewasser + WR: Speickwerke + WS: Weingut Sander + Wa: Watzkendorf + We: Wendts + Wh: Molkerei Weißenhorn + Wz: Werz Heidenheim + ZA: Bio-Center Zann + ZF: Obsthof zum Felde + ZG: Zwergenwiese + ZI: Biolandhof Zielke + ZK: Ziegenkäserei Karolinenhof + ZP: Bioland Ranch Zempow + ZW: Zellertaler Wein + bF: bio Frische + bi: biosanica + dB: ÖMA-d`Beers, Kisslegg im Algäu + eu: felicia + fa: familia Müsli + ha: Hawlik + vL: v.d.Linden + öG: Öko-Gartenbau + öh: ökohum Blumenerde + ÖL: Öko-Line + ÖS: Ölmühle Solling \ No newline at end of file diff --git a/plugins/article_import/lib/foodsoft_article_import/odin.rb b/plugins/article_import/lib/foodsoft_article_import/odin.rb new file mode 100644 index 00000000..bdfa605d --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/odin.rb @@ -0,0 +1,85 @@ +# frozen_string_literal: true + +# Article import for De Nieuw Band XML file +# +# Always contains full assortment, including recently outlisted articles. +# To make sure we don't keep old articles when a number of updates was missed, +# +OUTLIST+ is set to +true+ to remove articles not present in the file. +# +require 'nokogiri' + +module FoodsoftArticleImport + class Odin + NAME = 'De Nieuwe Band (XML)' + OUTLIST = true + OPTIONS = {}.freeze + + # parses a string or file + def self.parse(file, custom_file_path: nil, **_opts) + custom_file_path ||= nil + xml = File.open(file) + doc = Nokogiri.XML(xml, nil, nil, + Nokogiri::XML::ParseOptions::RECOVER + + Nokogiri::XML::ParseOptions::NONET + + Nokogiri::XML::ParseOptions::COMPACT) # do not modify doc! + load_codes(custom_file_path) + doc.search('product').each.with_index(1) do |row, i| + # create a new article + unit = row.search('eenheid').text + unit = case unit.strip + when '' then 'st' + when 'stuk' then 'st' + when 'g' then 'gr' # need at least 2 chars + when 'l' then 'ltr' + else unit + end + inhoud = row.search('inhoud').text + inhoud.to_s.strip.empty? or (inhoud.to_f - 1).abs > 1e-3 and unit = inhoud.gsub(/\.0+\s*$/, '') + unit + deposit = row.search('statiegeld').text + deposit.to_s.strip.empty? and deposit = 0 + category = [ + @@codes[:indeling][row.search('indeling').text.to_i], + @@codes[:indeling][row.search('subindeling').text.to_i] + ].compact.join(' - ') + + status = row.search('status').text == 'Actief' ? nil : :outlisted + article = {} + unless row.search('bestelnummer').text == '' + article = { order_number: row.search('bestelnummer').text, + # :ean => row.search('eancode').text, + name: row.search('omschrijving').text, + note: row.search('kwaliteit').text, + manufacturer: row.search('merk').text, + origin: row.search('herkomst').text, + unit: unit, + price: row.search('prijs inkoopprijs').text, + unit_quantity: row.search('sve').text, + tax: row.search('btw').text, + deposit: deposit, + article_category: category } + end + yield article, status, i + end + end + + @@codes = {} + + def self.load_codes(custom_file_path = nil) + @gem_lib = File.expand_path '..', __dir__ + dir = File.join @gem_lib, 'foodsoft_article_import' + begin + @@codes = YAML.safe_load(File.open(File.join(dir, 'dnb_codes.yml'))).symbolize_keys + if custom_file_path + custom_codes = YAML.safe_load(File.open(custom_file_path)).symbolize_keys + custom_codes.each_key do |key| + custom_codes[key] = custom_codes[key].merge @@codes[key] if @@codes.keys.include?(key) + @@codes = @@codes.merge custom_codes + end + end + @@codes + rescue StandardError => e + raise "Failed to load dnb_codes: #{dir}/dnb_codes.yml: #{e.message}" + end + end + end +end diff --git a/plugins/article_import/lib/foodsoft_article_import/order_bnn.rb b/plugins/article_import/lib/foodsoft_article_import/order_bnn.rb new file mode 100644 index 00000000..59e690c1 --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/order_bnn.rb @@ -0,0 +1,42 @@ +module FoodsoftArticleImport + class OrderBnn < RenderCsv + def initialize(object, options = {}) + super + @options[:col_sep] = "" + @options[:row_sep] = "\n" + @options[:encoding] = 'IBM850' + end + + def header + customer_id = "000001" + delivery_date = Time.zone.now.strftime("%y%m%d") + pickup = " " + + ["D##{customer_id}#{delivery_date}#{pickup}#{@object.id}"] + end + + def data + @object.order_articles.ordered.includes([:article, :article_price]).all.map do |oa| + yield [ + pad_to_length(oa.article.order_number, 13), + "+", + pad_float_values(oa.units_to_order), + pad_to_length(oa.article.name, 30), + pad_float_values(oa.article.unit_quantity), + pad_to_length(oa.article.unit, 14), + pad_to_length(oa.article.manufacturer, 3), + pad_to_length("", 26) + ] + end + end + + def pad_float_values(number, digits_before=4, digits_after=3) + format_string = "%0#{digits_before + digits_after}d" + formatted_number = sprintf(format_string, (number * 10 ** digits_after).to_i) + end + + def pad_to_length(string, length) + string.to_s.rjust(length, " ")[0, length] + end + end +end \ No newline at end of file diff --git a/plugins/article_import/lib/foodsoft_article_import/utf8_encoder.rb b/plugins/article_import/lib/foodsoft_article_import/utf8_encoder.rb new file mode 100644 index 00000000..1edb6727 --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/utf8_encoder.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module UTF8Encoder + def self.clean(string) + if string.nil? + string + else + string.encode('UTF-8') + end + end +end diff --git a/plugins/article_import/lib/foodsoft_article_import/version.rb b/plugins/article_import/lib/foodsoft_article_import/version.rb new file mode 100644 index 00000000..5be60bf7 --- /dev/null +++ b/plugins/article_import/lib/foodsoft_article_import/version.rb @@ -0,0 +1,3 @@ +module FoodsoftArticleImport + VERSION = "0.0.1" +end