class Oddb2xml::Builder

Constants

DAT_LEN

— see oddb2tdat

Data_dir

Attributes

actions[RW]
companies[RW]
ean14[RW]
firstbase[RW]
flags[RW]
infos[RW]
infos_zur_rose[RW]
items[RW]
lppvs[RW]
migel[RW]
orphan[RW]
packs[RW]
people[RW]
refdata[RW]
subject[RW]
tag_suffix[RW]
xsd[RW]

Public Class Methods

articlestamm_v5_info_lines() click to toggle source
# File lib/oddb2xml/builder.rb, line 1918
def self.articlestamm_v5_info_lines
  @@articlestamm_v5_info_lines
end
new(args = {}) { |self| ... } click to toggle source
# File lib/oddb2xml/builder.rb, line 49
def initialize(args = {})
  @options = args
  @subject = nil
  @refdata = {}
  @items = {} # Spezailitäteniste: SL-Items from Preparations.xml in BAG, using GTINS as key
  @flags = {}
  @lppvs = {}
  @infos = {}
  @packs = {}
  @migel = {}
  @infos_zur_rose ||= {}
  @firstbase ||= {}
  @actions = []
  @orphan = []
  @ean14 = false
  @companies = []
  @people = []
  @tag_suffix = nil
  @pharmacode = {} # index pharmacode => item
  if block_given?
    yield self
  end
end

Public Instance Methods

to_dat(subject = nil) click to toggle source
# File lib/oddb2xml/builder.rb, line 82
def to_dat(subject = nil)
  Oddb2xml.log "to_dat subject #{subject ? subject.to_s : @subject.to_s} for #{self.class}"
  if subject
    send("build_" + subject.to_s)
  elsif @subject
    send("build_" + @subject.to_s)
  end
end
to_xml(subject = nil) click to toggle source
# File lib/oddb2xml/builder.rb, line 73
def to_xml(subject = nil)
  Oddb2xml.log "to_xml subject #{subject || @subject}"
  if subject
    send("build_" + subject.to_s)
  elsif @subject
    send("build_" + @subject.to_s)
  end
end

Private Instance Methods

add_missing_products_from_swissmedic(add_to_products = false) click to toggle source
# File lib/oddb2xml/builder.rb, line 506
def add_missing_products_from_swissmedic(add_to_products = false)
  Oddb2xml.log "build_product add_missing_products_from_swissmedic. Starting with #{@products.size} products and #{@packs.size} @packs"
  ean13_to_product = {}
  @products.each { |ean13, obj|
    ean13_to_product[ean13] = obj
    obj[:pharmacode] ||= @refdata[ean13][:pharmacode] if @refdata[ean13]
  }
  ausgabe = File.open(File.join(WORK_DIR, "missing_in_refdata.txt"), "w+")
  size_old = ean13_to_product.size
  @missing = []
  Oddb2xml.log "build_product add_missing_products_from_swissmedic. Imported #{size_old} ean13_to_product from @products. Checking #{@packs.size} @packs"
  @packs.each_with_index do |de_idx, index|
    ean = de_idx[1][:ean13]
    next if @refdata[ean]
    list_code = de_idx[1][:list_code]
    next if list_code && /Tierarzneimittel/.match(list_code)
    if add_to_products
      @products[ean] = {seq: {name_de: de_idx.last[:sequence_name],
                              desc_de: "",
                              name_fr: "",
                              desc_fr: "",
                              atc_code: de_idx.last[:atc_code]},
                        pac: nil,
                        sequence_name: de_idx.last[:sequence_name],
                        no8: de_idx.last[:prodno],
                        ean13: ean,
                        atc: de_idx.last[:atc_code],
                        ith: de_idx.last[:ith_swissmedic],
                        siz: de_idx.last[:package_size],
                        eht: de_idx.last[:einheit_swissmedic],
                        sub: de_idx.last[:substance_swissmedic],
                        comp: de_idx.last[:composition_swissmedic],
                        drug_index: de_idx.last[:drug_index]}
    end
    ean13_to_product[ean] = de_idx[1]
    @missing << de_idx[1]
    ausgabe.puts "#{ean},#{de_idx[1][:sequence_name]}"
  end
  corrected_size = ean13_to_product.size
  Oddb2xml.log "build_product add_missing_products_from_swissmedic. Added #{corrected_size - size_old} corrected_size #{corrected_size} size_old #{size_old} ean13_to_product."
end
add_products_from_bag_preparations(add_to_products = false) click to toggle source
# File lib/oddb2xml/builder.rb, line 548
def add_products_from_bag_preparations(add_to_products = false)
  Oddb2xml.log "build_product add_products_from_bag_preparations. Starting with #{@products.size} products and #{@items.size} @items"
  ean13_to_product = {}
  @products.each { |ean13, obj|
    ean13_to_product[ean13] = obj
    obj[:pharmacode] ||= @refdata[ean13][:pharmacode] if @refdata[ean13]
  }
  size_old = ean13_to_product.size
  Oddb2xml.log "build_product add_products_from_bag_preparations. Imported #{size_old} ean13_to_product from @products. Checking #{@items.size} @items"
  @items.each do |ean13, item|
    next if @products[ean13]
    if add_to_products
      @products[ean13] = {
        seq: item,
        pac: nil,
        sequence_name: "",
        desc_de: "#{item[:name_de]}, #{item[:desc_de]}, #{item[:packages][ean13][:desc_de]}",
        desc_fr: "#{item[:name_fr]}, #{item[:desc_fr]}, #{item[:packages][ean13][:desc_fr]}",
        no8: "",
        ean13: ean13,
        atc: item[:atc_code],
        ith: "",
        siz: "",
        eht: "",
        sub: "",
        comp: "",
        drug_index: ""
      }
    end
    ean13_to_product[ean13] = item
  end
  corrected_size = ean13_to_product.size
  Oddb2xml.log "build_product add_products_from_bag_preparations. Added #{corrected_size - size_old} corrected_size #{corrected_size} size_old #{size_old} ean13_to_product."
end
build_article() click to toggle source
# File lib/oddb2xml/builder.rb, line 921
def build_article
  @overriden_salecd = []
  prepare_limitations
  prepare_articles
  idx = 0
  nbr_records = 0
  Oddb2xml.log "build_article #{idx} of #{@articles.size} articles"
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    eans_from_refdata = @articles.collect { |refdata| refdata[:ean13] }
    eans_from_preparations = @items.keys
    missing_eans = []
    eans_from_preparations.each do |ean|
      next if ean.to_i == 0
      unless @articles.collect { |x| x[:ean13] }.index(ean)
        item = @items[ean].clone
        item[:pharmacode] ||= "123456" if defined?(RSpec)
        item[:ean13] = ean
        item[:_type] = :preparations_xml
        item[:desc_de] = item[:name_de] + " " + item[:desc_de]
        item[:desc_fr] = item[:name_fr] + " " + item[:desc_fr]
        @articles << item
      end
      unless eans_from_refdata.index(ean)
        missing_eans << ean
      end
    end
    xml.ARTICLE(XML_OPTIONS) {
      @articles.sort! { |a, b| a[:ean13] <=> b[:ean13] }
      @articles.each do |obj|
        idx += 1
        Oddb2xml.log "build_article #{obj[:ean13]}: #{idx} of #{@articles.size} articles" if idx % 500 == 0
        pac, no8 = nil, obj[:ean13].to_s[4..11] # BAG-XML(SL/LS)
        pack_info = nil
        pack_info = @packs[no8] if no8 # info from Packungen.xlsx from swissmedic_info
        ppac = nil # Packungen
        ean = obj[:ean13]
        next if pack_info && /Tierarzneimittel/.match(pack_info[:list_code])
        next if obj[:desc_de] && /ad us vet/i.match(obj[:desc_de])
        pharma_code = obj[:pharmacode]
        # ean = 0 if sprintf('%013d', ean).match(/^000000/)
        if obj[:seq]
          pac = obj[:seq][:packages][obj[:pharmacode]]
          pac ||= obj[:seq][:packages][ean]
        elsif @items && ean && @items[ean] && @items[ean][:packages]
          pac = @items[ean][:packages][ean]
        end
        if no8
          ppac = ((a_ppac = pack_info) && !a_ppac[:is_tier] ? a_ppac : nil)
        end
        zur_rose = nil
        if !@infos_zur_rose.empty? && ean && @infos_zur_rose[ean]
          zur_rose = @infos_zur_rose[ean] # zurrose
        end
        xml.ART("DT" => obj[:last_change] || "") do
          nbr_records += 1
          # Ignore Lint/RequireParentheses warning on next line!
          xml.REF_DATA obj[:refdata] || @migel[pharma_code] ? "1" : "0"
          if obj[:pharmacode] && obj[:pharmacode].length > 0
            xml.PHAR obj[:pharmacode]
          elsif zur_rose
            puts "Adding #{zur_rose[:pharmacode]} to article GTIN #{ean}"
            xml.PHAR zur_rose[:pharmacode]
          end
          # xml.GRPCD
          # xml.CDS01
          # xml.CDS02
          if ppac
            xml.SMCAT ppac[:swissmedic_category][0] unless ppac[:swissmedic_category].empty?
            xml.GEN_PRODUCTION ppac[:gen_production] unless ppac[:gen_production].empty?
            xml.INSULIN_CATEGORY ppac[:insulin_category] unless ppac[:insulin_category].empty?
            xml.DRUG_INDEX ppac[:drug_index] unless ppac[:drug_index].empty?
          end
          if no8 && !no8.to_s.empty?
            if ean && (ean.to_s[0..3] == "7680")
              xml.SMNO no8.to_s
            end
          end
          if ppac
            xml.PRODNO ppac[:prodno] if ppac[:prodno] && !ppac[:prodno].empty?
          end
          # xml.HOSPCD
          # xml.CLINCD
          # xml.ARTTYP
          if zur_rose
            xml.VAT zur_rose[:vat]
          end
          emit_salecd(xml, ean, obj)
          if pac && pac[:limitation_points]
            # xml.INSLIM
            xml.LIMPTS pac[:limitation_points] unless pac[:limitation_points].empty?
          end
          # xml.GRDFR
          xml.COOL 1 if ppac && /Blutprodukte|impfstoffe/.match(ppac[:list_code])
          # xml.TEMP
          if ean
            flag = ppac && !ppac[:drug_index].empty? ? true : false
            # as same flag
            xml.CDBG(flag ? "Y" : "N")
            xml.BG(flag ? "Y" : "N")
          end
          xml.DSCRD obj[:desc_de] if obj[:desc_de] && !obj[:desc_de].empty?
          xml.DSCRF obj[:desc_fr] if obj[:desc_fr] && !obj[:desc_fr].empty?
          xml.DSCRF obj[:desc_de] if !obj[:desc_fr] || obj[:desc_fr].empty?
          xml.SORTD obj[:desc_de].upcase if obj[:desc_de] && !obj[:desc_de].empty?
          xml.SORTF obj[:desc_fr].upcase if obj[:desc_fr] && !obj[:desc_fr].empty?
          xml.SORTF obj[:desc_de].upcase if !obj[:desc_fr] || obj[:desc_fr].empty?
          # xml.QTYUD
          # xml.QTYUF
          # xml.IMG
          # xml.IMG2
          # xml.PCKTYPD
          # xml.PCKTYPF
          # xml.MULT
          if obj[:seq]
            xml.SYN1D obj[:seq][:name_de] unless obj[:seq][:name_de].empty?
            xml.SYN1F obj[:seq][:name_fr] unless obj[:seq][:name_fr].empty?
          end
          if obj[:seq]
            if obj[:seq][:deductible] == "Y" || obj[:seq][:deductible20] == "Y"
              xml.SLOPLUS 1; # 40%
            elsif obj[:seq][:deductible] == "N" || obj[:seq][:deductible20] == "N"
              xml.SLOPLUS 2; # 10%
            else
              xml.SLOPLUS "" # k.A.
            end
          end
          # xml.NOPCS
          # xml.HSCD
          # xml.MINI
          # xml.DEPCD
          # xml.DEPOT
          # xml.BAGSL
          # xml.BAGSLC
          # xml.LOACD
          if obj[:stat_date]
            xml.OUTSAL obj[:stat_date] if obj[:stat_date] && !obj[:stat_date].empty?
          end
          # xml.STTOX
          # xml.NOTI
          # xml.GGL
          # xml.CE
          # xml.SMDAT
          # xml.SMCDAT
          # xml.SIST
          # xml.ESIST
          # xml.BIOCID
          # xml.BAGNO
          # xml.LIGHT
          # xml.DEL
          xml.ARTCOMP {
            # use ean13(gln) as COMPNO
            xml.COMPNO obj[:company_ean] if obj[:company_ean] && (obj[:company_ean].to_s.length > 1)
            # xml.ROLE
            # xml.ARTNO1
            # xml.ARTNO2
            # xml.ARTNO3
          }
          if ean
            xml.ARTBAR {
              xml.CDTYP "E13"
              xml.BC ean #  /^9999|^0000|^0$/.match(ean.to_s) ? 0 : sprintf('%013d', ean)
              xml.BCSTAT "A" # P is alternative
            }
          end
          if pac && pac[:prices]
            pac[:prices].each_pair do |key, price|
              xml.ARTPRI {
                xml.VDAT price[:valid_date] unless price[:valid_date].empty?
                xml.PTYP price[:price_code] unless price[:price_code].empty?
                xml.PRICE price[:price] unless price[:price].empty?
              }
            end
          end
          if zur_rose
            price = zur_rose[:price]
            xml.ARTPRI {
              xml.PTYP "ZURROSE"
              xml.PRICE price
            }
            xml.ARTPRI {
              xml.PTYP "ZURROSEPUB"
              xml.PRICE zur_rose[:pub_price]
            }
            unless @options[:percent].nil?
              xml.ARTPRI {
                xml.PTYP "RESELLERPUB"
                xml.PRICE (price.to_f * (1 + (@options[:percent].to_f / 100))).round_by(0.05).round(2)
              }
            end
          end
          nincd = detect_nincd(obj)
          if nincd
            xml.ARTINS {
              xml.NINCD nincd
            }
          end
        end
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.log "build_article. Done #{idx} of #{@articles.size} articles. Overrode #{@overriden_salecd.size} SALECD"
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_artikelstamm() click to toggle source
# File lib/oddb2xml/builder.rb, line 1490
def build_artikelstamm
  @@emitted_v5_gtins = []
  @csv_file = CSV.open(File.join(WORK_DIR, "artikelstamm_#{Date.today.strftime("%d%m%Y")}_v5.csv"), "w+")
  @csv_file << ["gtin", "name", "pkg_size", "galenic_form", "price_ex_factory", "price_public", "prodno", "atc_code", "active_substance", "original", "it-code", "sl-liste"]
  @csv_file.sync = true
  variant = "build_artikelstamm"
  # @infos_zur_rose.delete_if { |key, val| val[:cmut].eql?('3') } # collect only active zur rose item
  # No. Marco did not filter it, eg. 8804121 in rtikelstamm_oddb2xml_051217_v5.xm
  self.class.class_eval do
    def check_article_name(obj, lang = :de)
      ean = obj[:ean13]
      refdata = @refdata[ean]
      if lang == :de
        name = refdata && refdata[:desc_de] ? refdata[:desc_de] : obj[:sequence_name]
      elsif lang == :fr
        name = refdata && refdata[:desc_fr] ? refdata[:desc_fr] : obj[:sequence_name]
      else
        return "--missing--"
      end
      return "--missing--" if !name || name.empty? || name.length < 3
      name[0..119] # limit to maximal 120 chars as specified in the XSD
    end
  end
  self.class.class_eval do
    def override(xml, id, field, default_value)
      has_overrides = /\d{13}/.match?(id.to_s) ? @@article_overrides[id.to_i] : @@product_overrides[id.to_i]
      if has_overrides && has_overrides[field.to_s]
        new_value = has_overrides[field.to_s]
        if new_value.to_s.eql?(default_value.to_s)
          xml.comment("obsolete override")
        else
          xml.comment("override #{default_value} with")
        end
        cmd = "xml.#{field} \"#{new_value}\""
      else
        cmd = "xml.#{field} \"#{default_value.to_s.delete('"')}\""
      end
      eval cmd if default_value
    end
  end
  self.class.class_eval do
    def emit_items(xml)
      nr_items = 0
      gtins_to_article = {}
      @articles.each { |article| gtins_to_article[article[:ean13]] = article }
      sl_gtins = @items.values.collect { |x| x[:packages].keys }.flatten.uniq
      gtins = gtins_to_article.keys + @infos_zur_rose.keys + @packs.values.collect { |x| x[:ean13] } + sl_gtins
      gtins = (gtins - @@gtin2ignore)
      gtins.sort!.uniq!
      gtins.each do |ean13|
        no8 = ean13.to_s[4..11] # BAG-XML(SL/LS)
        next if ean13 == 0
        obj = gtins_to_article[ean13] || @items.values.find { |x| x[:packages].keys.index(ean13) } || @infos_zur_rose[ean13]
        if obj
          obj = @packs[no8].merge(obj) if @packs[no8]
        else
          obj = @packs[no8] # obj not yet in refdata. Use data from swissmedic_package.xlsx
        end
        nr_items += 1
        Oddb2xml.log "build_artikelstamm #{ean13}: #{nr_items} of #{gtins.size} articles" if nr_items % 5000 == 0
        item = @items[ean13]
        pack_info = nil
        pack_info = @packs[no8] if no8 && /#{ean13}/.match(@packs[no8].to_s) # info from Packungen.xlsx from swissmedic_info
        next if pack_info && /Tierarzneimittel/.match(pack_info[:list_code])
        next if obj[:desc_de] && /ad us vet/i.match(obj[:desc_de])
        sequence = obj[:seq]
        if sequence.nil? && @packs[no8] && /#{ean13}/.match(@packs[no8].to_s)
          sequence = {packages: {ean13 => @packs[no8]}}
          obj[:seq] = sequence.clone
        end
        if sequence.nil? && @items[ean13] && @items[ean13][:packages][ean13]
          sequence = @items[ean13]
        end
        if sequence
          if obj[:seq] && !obj[:seq][:packages].keys.index(ean13)
            # puts "unable to find  #{ean13} in #{obj[:seq][:packages].keys}"
            next
          end
          sequence[:packages].each do |gtin, package|
            pkg_gtin = package[:ean13].clone
            if package[:no8] && (new_ean_13 = Oddb2xml.getEan13forNo8(package[:no8]))
              if !new_ean_13.eql?(pkg_gtin)
                puts "Setting #{new_ean_13} for #{pkg_gtin}"
                pkg_gtin = new_ean_13
              end
            end
            info = @calc_items[pkg_gtin]
            if @@emitted_v5_gtins.index(pkg_gtin)
              next
            else
              @@emitted_v5_gtins << pkg_gtin.clone
            end
            options = {"PHARMATYPE" => "P"}
            xml.ITEM(options) do
              name = item[:name_de] + " " + item[:desc_de].strip + " " + package[:desc_de] if package && package[:desc_de]
              name ||= @refdata[pkg_gtin] ? @refdata[pkg_gtin][:desc_de] : nil
              name ||= @infos_zur_rose[ean13][:description] if @infos_zur_rose[ean13]
              name ||= obj[:name_de] + ", " + obj[:desc_de].strip if obj[:name_de]
              name ||= (item[:desc_de] + item[:name_de]) if item
              name ||= obj[:sequence_name]
              xml.GTIN pkg_gtin.to_s.rjust(13, "0")
              xml.SALECD("A")
              # maxLength for DSCR is 50 for Artikelstamm v3
              xml.DSCR(name) # for description for zur_rose
              name_fr = item[:name_fr] + " " + item[:desc_fr].strip + " " + package[:desc_fr] if package && package[:desc_fr]
              name_fr ||= @refdata[pkg_gtin] ? @refdata[pkg_gtin][:desc_fr] : nil
              # Zugelassenen Packungen has only german names
              name_fr ||= (obj[:name_fr] + ", " + obj[:desc_fr]).strip if obj[:name_fr]
              # ZuRorse has only german names
              name_fr ||= (item[:name_fr] + ", " + item[:desc_fr]) if item
              xml.DSCRF(name_fr)
              name_it = item[:name_it] + " " + item[:desc_it].strip + " " + package[:desc_it] if package && package[:desc_it]
              name_it ||= @refdata[pkg_gtin] ? @refdata[pkg_gtin][:desc_it] : nil
              # Zugelassenen Packungen has only german names
              name_it ||= (obj[:name_it] + ", " + obj[:desc_it]).strip if obj[:name_it]
              # ZuRorse has only german names
              name_it ||= (item[:name_it] + ", " + item[:desc_it]) if item
              xml.DSCRI(name_it)
              if obj[:company_name] || obj[:company_ean]
                xml.COMP do # Manufacturer
                  xml.NAME obj[:company_name][0..99] # limit to 100 chars as in XSD
                  xml.GLN obj[:company_ean]
                end
              end
              pexf = ppub = nil
              if package[:prices]
                pexf ||= package[:prices][:exf_price][:price]
                ppub ||= package[:prices][:pub_price][:price]
              elsif @items[ean13] && @items[ean13][:packages] && @items[ean13][:packages][ean13] && (bag_prices = @items[ean13][:packages][ean13][:prices])
                pexf ||= bag_prices[:exf_price][:price]
                ppub ||= bag_prices[:pub_price][:price]
              else
                pexf ||= obj[:price]
                ppub ||= obj[:pub_price]
              end
              ppub = nil if ppub && ppub.size == 0
              pexf = nil if pexf && pexf.size == 0
              if !(obj[:price] && !obj[:price].empty?) || !(obj[:pub_price] && !obj[:pub_price].empty?)
                zur_rose_detail = @infos_zur_rose.values.find { |x| x[:ean13].to_i == ean13.to_i }
                if zur_rose_detail
                  pexf ||= zur_rose_detail[:price]
                  ppub ||= zur_rose_detail[:pub_price]
                end
              end
              xml.PEXF pexf if pexf
              xml.PPUB ppub if ppub
              measure = ""
              if info
                # MEASSURE Measurement Unit,e.g. Pills or milliliters
                #             <DSCR>HIRUDOID Creme 3 mg/g 40 g</DSCR>
                xml.PKG_SIZE info.pkg_size.to_i if info.pkg_size
                if info.measure
                  measure = info.measure
                elsif info.pkg_size && info.unit
                  measure = info.pkg_size + " " + info.unit
                elsif info.pkg_size
                  measure = info.pkg_size
                end
                xml.MEASURE measure
                # Die Darreichungsform dieses Items. zB Tablette(n) oder Spritze(n)
                xml.DOSAGE_FORM info.galenic_form.descriptions["de"] if info.galenic_form.descriptions["de"]
                xml.DOSAGE_FORMF info.galenic_form.descriptions["fr"] if info.galenic_form.descriptions["fr"]
                xml.DOSAGE_FORMI info.galenic_form.descriptions["it"] if info.galenic_form.descriptions["it"]
              end
              xml.SL_ENTRY "true" if sl_gtins.index(pkg_gtin)
              xml.IKSCAT package[:swissmedic_category][0] if package[:swissmedic_category] && package[:swissmedic_category].length > 0
              xml.GENERIC_TYPE sequence[:org_gen_code] if sequence[:org_gen_code] && !sequence[:org_gen_code].empty?
              xml.LPPV "true" if @lppvs[pkg_gtin.to_s] # detect_nincd
              if item && item[:deductible]
                case item[:deductible]
                  when "Y" then xml.DEDUCTIBLE 40; # 40%
                  when "N" then xml.DEDUCTIBLE 10; # 10%
                end
              elsif item && item[:deductible20]
                # TODO: Delete deductible20 after 2024/03/01
                # https://github.com/zdavatz/oddb2xml/issues/81
                case item[:deductible20]
                  when "Y" then xml.DEDUCTIBLE 40; # 40%
                  when "N" then xml.DEDUCTIBLE 10; # 10%
                end
              end
              prodno = Oddb2xml.getProdnoForEan13(pkg_gtin)
              atc = package[:atc_code]
              refdata_atc = @refdata[pkg_gtin][:atc_code] if @refdata && @refdata[pkg_gtin] && @refdata[pkg_gtin]
              if refdata_atc && atc.nil?
                puts "WARNING: #{pkg_gtin} ATC-code from refdata #{refdata_atc} as Swissmedic ATC is nil #{name}"
                atc = refdata_atc
              end
              unless prodno # find a prodno from packages for vaccinations
                if atc && /^J07/.match(atc) && !/^J07AX/.match(atc)
                  pack = @packs.values.find { |v| v && v[:atc_code].eql?(atc) }
                  if pack
                    prodno = pack[:prodno]
                    Oddb2xml.log "Patching vaccination for #{pkg_gtin} #{atc} #{name} via prodno #{prodno}"
                  else
                    Oddb2xml.log "unable to find a pack/prodno for  vaccination for #{pkg_gtin} #{atc} #{name}"
                  end
                end
              end
              xml.PRODNO prodno if prodno
              @csv_file << [pkg_gtin, name, package[:unit], measure,
                pexf || "",
                ppub || "",
                prodno, atc, package[:substance_swissmedic],
                sequence[:org_gen_code], package[:ith_swissmedic],
                @items[pkg_gtin] ? "SL" : ""]
            end
          end
        else # non pharma
          if @@emitted_v5_gtins.index(ean13)
            next
          else
            @@emitted_v5_gtins << ean13.clone
          end
          # Set the pharmatype to 'Y' for outdated products, which are no longer found
          # in refdata/packungen
          chap70 = nil
          if @chapter70items.values.find { |x| x[:pharmacode]&.eql?(obj[:pharmacode]) }
            Oddb2xml.log "found chapter #{obj[:pharmacode]}" if $VERBOSE
            chap70 = true
          end
          patched_pharma_type = (/^7680/.match(ean13.to_s.rjust(13, "0")) || chap70 ? "P" : "N")
          next if /^#{Oddb2xml::FAKE_GTIN_START}/o.match?(ean13.to_s)
          next if obj[:data_origin].eql?("zur_rose") && /^7680/.match(ean13) # must skip inactiv items
          xml.ITEM({"PHARMATYPE" => patched_pharma_type}) do
            xml.GTIN ean13.to_s.rjust(13, "0")
            if obj[:pharmacode] && obj[:pharmacode].length > 0
              xml.PHAR obj[:pharmacode]
            elsif (zur_rose = @infos_zur_rose[ean13])
              puts "Artikelstamm: Adding #{zur_rose[:pharmacode]} to article GTIN #{ean13}"
              xml.PHAR zur_rose[:pharmacode]
            elsif /^7680/.match?(ean13)
              puts "Artikelstamm: No pharmacode for article GTIN #{ean13} via ZurRose"
            end
            emit_salecd(xml, ean13, obj)
            description = obj[:desc_de] || obj[:description] # for description for zur_rose
            xml.DSCR(description)
            xml.DSCRF(obj[:desc_fr])
            xml.DSCRI(obj[:desc_it]) if obj[:desc_it] && !obj[:desc_it].empty?
            if obj[:company_ean] && !obj[:company_ean].empty?
              xml.COMP do
                xml.GLN obj[:company_ean]
              end
            end
            if !(obj[:price] && !obj[:price].empty?) || !(obj[:pub_price] && !obj[:pub_price].empty?)
              zur_rose_detail = @infos_zur_rose.values.find { |x| x[:ean13].to_i == ean13.to_i }
            end
            ppub = nil
            pexf = nil
            if obj[:price] && !obj[:price].empty?
              xml.PEXF(pexf = obj[:price])
            elsif zur_rose_detail
              if zur_rose_detail[:price] && !zur_rose_detail[:price].empty? && !zur_rose_detail[:price].eql?("0.00")
                # Oddb2xml.log "NonPharma: #{ean13} adding PEXF #{zur_rose_detail[:price]} #{description}"
                xml.PEXF(pexf = zur_rose_detail[:price])
              end
            end
            if obj[:pub_price] && !obj[:pub_price].empty?
              xml.PPUB(ppub = obj[:pub_price])
            elsif zur_rose_detail
              if zur_rose_detail[:pub_price] && !zur_rose_detail[:pub_price].empty? && !zur_rose_detail[:pub_price].eql?("0.00")
                # Oddb2xml.log "NonPharma: #{ean13} adding PPUB #{zur_rose_detail[:pub_price]} #{description}"
                xml.PPUB(ppub = zur_rose_detail[:pub_price])
              end
            end
            @csv_file << [ean13, description, "", "", pexf, ppub, "", "", "", "", "", ""]
            if chap70
              xml.comment "Chapter70 hack #{ean13.to_s.rjust(13, "0")} #{description.encode(xml: :text).gsub("--", "-")}"
              xml.SL_ENTRY "true"
              xml.PRODNO obj[:pharmacode]
            end
          end
        end
      end
      @csv_file.close if @csv_file && !@csv_file.closed?
      nr_items
    end
  end
  unless @prepared
    require "oddb2xml/chapter_70_hack"
    Oddb2xml::Chapter70xtractor.parse
    @chapter70items = Oddb2xml::Chapter70xtractor.items
    prepare_limitations
    prepare_articles
    prepare_products
    add_missing_products_from_swissmedic(true)
    add_products_from_bag_preparations(true)
    prepare_calc_items(suppress_composition_parsing: true)
    @prepared = true
    @old_rose_size = @infos_zur_rose.size
  end
  nr_products = 0
  nr_articles = 0
  @nr_articles = 0
  used_limitations = []
  # Oddb2xml.log "#{variant} #{nr_products} of #{@products.size} articles and ignore #{@@gtin2ignore.size} gtins specified via #{@@ignore_file}"
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    elexis_strftime_format = "%FT%T\.%L%:z"
    @@cumul_ver = (Date.today.year - 2013) * 12 + Date.today.month
    options_xml = {
      "xmlns" => "http://elexis.ch/Elexis_Artikelstamm_v5",
      "CREATION_DATETIME" => Time.new.strftime(elexis_strftime_format),
      "BUILD_DATETIME" => Time.new.strftime(elexis_strftime_format),
      "DATA_SOURCE" => "oddb2xml"
    }
    emitted_prodno = []
    no8_to_prodno = {}
    @packs.collect { |key, val| no8_to_prodno[key] = val [:prodno] }
    xml.comment("Produced by #{__FILE__} version #{VERSION} at #{Time.now}")
    xml.ARTIKELSTAMM(options_xml) do
      xml.PRODUCTS do
        products = @products.sort_by { |ean13, obj| ean13 }
        products.each do |product|
          ean13 = product[0]
          obj = product[1]
          next if /^Q/i.match?(obj[:atc])
          ean = obj[:ean13]
          sequence = obj[:seq]
          sequence ||= @products[ean][:seq] if @products[ean]
          next unless check_article_name(obj, :de)
          ppac = ((a_ppac = @packs[ean.to_s[4..11]]) && !a_ppac[:is_tier] ? a_ppac : {})
          prodno = ppac[:prodno] if ppac[:prodno] && !ppac[:prodno].empty?
          prodno = obj[:pharmacode] if obj[:chapter70]
          my_pack = @packs.values.find { |x| x[:iksnr].to_i == obj[:seq][:swissmedic_number5].to_i } if obj[:seq]
          if my_pack && !prodno
            prodno ||= my_pack[:prodno]
            puts "Setting prodno #{prodno} for #{ean13} #{my_pack[:sequence_name]}"
          end
          next unless prodno
          next if emitted_prodno.index(prodno)
          sequence ||= @articles.find { |x| x[:ean13].eql?(ean) }
          unless obj[:chapter70]
            next unless sequence && (sequence[:name_de] || sequence[:desc_de])
            if Oddb2xml.getEan13forProdno(prodno).size == 0 && !obj[:no8].eql?(Oddb2xml.getNo8ForEan13(ean))
              puts "No item found for prodno #{prodno} no8 #{obj[:no8]} #{sequence[:name_de]}"
              next
            end
          end
          emitted_prodno << prodno
          nr_products += 1
          xml.PRODUCT do
            xml.PRODNO prodno
            if sequence
              xml.SALECD("A") # these products are always active!
              name_de = "#{sequence[:name_de]} #{sequence[:desc_de]}".strip if sequence[:name_de]
              name_de ||= if ppac && /stk/i.match(sequence[:desc_de])
                ppac[:sequence_name]
              else
                sequence[:desc_de]
              end
              name_fr ||= (ppac && ppac[:sequence_name])
              name_fr = "#{sequence[:name_fr]} #{sequence[:desc_fr]}".strip if sequence[:name_fr]
              name_it = "#{sequence[:name_it]} #{sequence[:desc_it]}".strip if sequence[:name_it]
              override(xml, prodno, :DSCR, name_de.strip)
              override(xml, prodno, :DSCRF, name_fr.strip)
              override(xml, prodno, :DSCRI, name_it.strip) if name_it && !name_it.empty?
              # use overriden ATC if possibel
              atc = sequence[:atc] || sequence[:atc_code]
              xml.ATC atc if atc && !atc.empty?
            end
            if sequence && sequence[:packages] && (first_package = sequence[:packages].values.first) &&
                (first_limitation = first_package[:limitations].first)
              lim_code = first_limitation[:code]
              used_limitations << lim_code unless used_limitations.index(lim_code)
              xml.LIMNAMEBAG lim_code
            elsif obj[:chapter70]
              xml.comment "Chapter70 hack prodno #{prodno} #{obj[:description].encode(xml: :text).gsub("--", "-")}"
              xml.SALECD("A") # these products are always active!
              xml.DSCR obj[:description]
              xml.DSCRF ""
              if @limitations.index(obj[:code])
                xml.LIMNAMEBAG obj[:code]
                used_limitations << obj[:code]
              end
            end
            if sequence && sequence[:substances]
              value = if sequence[:substances].size > 1
                "Verschiedene Kombinationen"
              elsif sequence[:substances].first
                sequence[:substances].first[:name]
              else
                obj[:sub]
              end
              override(xml, prodno, :SUBSTANCE, value) if value
            end
          end
        end
      end
      emitted_lim_code = []
      xml.LIMITATIONS do
        @limitations.sort! { |left, right| left[:code] <=> right[:code] }
        @limitations.each do |lim|
          unless lim[:chap70]
            next unless used_limitations.index(lim[:code])
            next if emitted_lim_code.index(lim[:code])
          end
          emitted_lim_code << lim[:code]
          xml.LIMITATION do
            xml.comment "Chapter70 2 hack" if lim[:chap70]
            xml.LIMNAMEBAG lim[:code] # original LIMCD
            xml.DSCR Oddb2xml.html_decode(lim[:desc_de])
            xml.DSCRF Oddb2xml.html_decode(lim[:desc_fr]) if lim[:desc_fr]
            xml.DSCRI Oddb2xml.html_decode(lim[:desc_it]) if lim[:desc_it]
            xml.LIMITATION_PTS(lim[:value].to_s.length > 1 ? lim[:value] : 1)
          end
        end
      end
      xml.ITEMS do
        nr_articles = emit_items(xml)
      end
    end
  end
  Oddb2xml.log "#{variant}. Done #{nr_products} of #{@products.size} products, #{@limitations.size} limitations and #{nr_articles}/#{@nr_articles} articles. @@emitted_v5_gtins #{@@emitted_v5_gtins.size}"
  # we don't add a SHA256 hash for each element in the article
  # Oddb2xml.add_hash(a_builder.to_xml)
  # doc = REXML::Document.new( source, { :raw => :all })
  # doc.write( $stdout, 0 )
  lines = []
  lines << "  - #{sprintf("%5d", @products.size)} products"
  lines << "  - #{sprintf("%5d", @limitations.size)} limitations"
  lines << "  - #{sprintf("%5d", @nr_articles)} articles"
  lines << "  - #{sprintf("%5d", @@gtin2ignore.size)} ignored GTINS"
  @@articlestamm_v5_info_lines = lines
  a_builder.to_xml({indent: 4, encoding: "UTF-8"})
end
build_calc() click to toggle source
# File lib/oddb2xml/builder.rb, line 865
def build_calc
  prepare_calc_items
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.ARTICLES(XML_OPTIONS) do
      @calc_items.each do |ean13, info|
        if info&.compositions
          xml.ARTICLE do
            xml.GTIN ean13
            xml.NAME info.column_c
            xml.PKG_SIZE info.pkg_size
            xml.SELLING_UNITS info.selling_units
            xml.MEASURE info.measure # Nur wenn Lösung wen Spalte M ml, Spritze

            if info.galenic_form.is_a?(String)
              xml.GALENIC_FORM info.galenic_form
              xml.GALENIC_GROUP "Unknown"
            else
              xml.GALENIC_FORM info.galenic_form.description
              xml.GALENIC_GROUP info.galenic_group ? info.galenic_group.description : "Unknown"
            end
            xml.COMPOSITIONS do
              info.compositions.each do |composition|
                xml.COMPOSITION do
                  xml.EXCIPIENS { emit_substance(xml, composition.excipiens) } if composition.excipiens
                  xml.LABEL composition.label if composition.label
                  xml.LABEL_DESCRIPTION composition.label_description if composition.label_description
                  xml.CORRESP composition.corresp if composition.corresp
                  if composition.substances && (composition.substances.size > 0)
                    xml.SUBSTANCES do
                      composition.substances.each { |substance| xml.SUBSTANCE { emit_substance(xml, substance, true) } }
                    end
                  end
                end
              end
            end
          end
        end
      end
    end
  end

  csv_name = File.join(WORK_DIR, "oddb_calc.csv")
  CSV.open(csv_name, "w+", col_sep: ";") do |csv|
    csv << ["gtin"] + @calc_items.values.first.headers
    @calc_items.each do |key, value|
      if value&.to_array
        csv << [key] + value.to_array
      else
        puts "key #{key.inspect} WITHOUT #{value.inspect}"
      end
    end
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_code() click to toggle source
# File lib/oddb2xml/builder.rb, line 474
def build_code
  prepare_codes
  nbr_records = 0
  Oddb2xml.log "build_code #{@codes.size} codes"
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.CODE(XML_OPTIONS) {
      @codes.each_pair do |val, definition|
        xml.CD("DT" => "") {
          xml.CDTYP definition[:int]
          xml.CDVAL val
          xml.DSCRSD definition[:txt]
          # xml.DSCRSF
          # xml.DSCRMD
          # xml.DSCRMF
          # xml.DSCRD
          # xml.DSCRF
          xml.DEL false
          nbr_records += 1
        }
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_company() click to toggle source
# File lib/oddb2xml/builder.rb, line 1212
def build_company
  nbr_records = 0
  Oddb2xml.log "build_company #{@companies.size} companies"
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.Betriebe(XML_OPTIONS) {
      @companies.each do |c|
        xml.Betrieb("DT" => "") {
          xml.GLN_Betrieb c[:gln] unless c[:gln].empty?
          xml.Betriebsname_1 c[:name_1] unless c[:name_1].empty?
          xml.Betriebsname_2 c[:name_2] unless c[:name_2].empty?
          xml.Strasse c[:address] unless c[:address].empty?
          xml.Nummer c[:number] unless c[:number].empty?
          xml.PLZ c[:post] unless c[:post].empty?
          xml.Ort c[:place] unless c[:place].empty?
          xml.Bewilligungskanton c[:region] unless c[:region].empty?
          xml.Land c[:country] unless c[:country].empty?
          xml.Betriebstyp c[:type] unless c[:type].empty?
          xml.BTM_Berechtigung c[:authorization] unless c[:authorization].empty?
          nbr_records += 1
        }
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_dat() click to toggle source
# File lib/oddb2xml/builder.rb, line 1341
def build_dat
  prepare_articles
  rows = []
  @articles.each do |obj|
    ean = obj[:ean13]
    next if (ean.to_s.length != 13) && !ean14
    next if obj[:type] == :nonpharma
    row = ""
    pack_info = nil
    if (x = @packs.find { |k, v| v[:ean13].eql?(ean) })
      pack_info = x[1]
    end
    # Oddb2tdat.parse
    pac, no8 = nil, nil
    if obj[:seq] && obj[:seq][:packages]
      pac = obj[:seq][:packages][obj[:pharmacode]]
      pac ||= obj[:seq][:packages][ean]
    elsif @items && @items[ean] && @items[ean][:packages]
      pac = @items[ean][:packages][ean]
    end
    # :swissmedic_numbers
    if pac
      no8 = pac[:swissmedic_number8]
    end
    if pac && pac[:prices].nil? && no8
      ppac = ((ppac = pack_info) && ppac[:is_tier] ? ppac : nil)
      pac = ppac if ppac
    end
    row << "%#{DAT_LEN[:RECA]}s" % "11"
    zur_rose = @infos_zur_rose[ean] # zurrose
    row << if zur_rose && zur_rose[:cmut]
      zur_rose[:cmut]
    else
      "1"
    end
    row << "%0#{DAT_LEN[:PHAR]}d" % obj[:pharmacode].to_i
    abez = ( # de name
      obj[:desc_de].to_s + " " +
      (pac ? pac[:name_de].to_s : "") +
      (obj[:quantity] || "")
    ).delete('"')
    if @infos_zur_rose[ean]
      price_exf = sprintf("%06i", (@infos_zur_rose[ean][:price].to_f * 100).to_i)
      price_public = sprintf("%06i", (@infos_zur_rose[ean][:pub_price].to_f * 100).to_i)
      if !@options[:percent].nil?
        price_public = sprintf("%06i", (price_exf.to_f * (1 + (@options[:percent].to_f / 100))).round_by(0.05).round(2))
      end
    elsif pac && pac[:prices]
      price_exf = sprintf("%06i", (pac[:prices][:exf_price][:price].to_f * 100).to_i) if pac[:prices][:exf_price] && pac[:prices][:exf_price][:price]
      price_public = sprintf("%06i", (pac[:prices][:pub_price][:price].to_f * 100).to_i) if pac[:prices][:pub_price] && pac[:prices][:pub_price][:price]
    end
    row << format_name(Oddb2xml.patch_some_utf8(abez), DAT_LEN[:ABEZ])
    if price_exf.to_s.length > DAT_LEN[:PRMO] ||
        price_public.to_s.length > DAT_LEN[:PRPU]
      puts "Price exfactory #{price_exf} or public #{price_public} is too high to be added into transfer.dat"
      break
    end
    row << "%#{DAT_LEN[:PRMO]}s" % (price_exf ? price_exf.to_s : ("0" * DAT_LEN[:PRMO]))
    row << "%#{DAT_LEN[:PRPU]}s" % (price_public ? price_public.to_s : ("0" * DAT_LEN[:PRPU]))
    row << "%#{DAT_LEN[:CKZL]}s" % if @lppvs[ean]
             "2"
           elsif pac # sl_entry
             "1"
           else
             "3"
           end
    row << "%#{DAT_LEN[:CLAG]}s" % if ppac && /Blutproduct|impfstoffe/.match(ppac[:list_code]) # COOL
             "1"
           else
             "0"
           end
    row << "%#{DAT_LEN[:CBGG]}s" % if pack_info && pack_info[:drug_index]
             "3"
           else
             "0"
           end
    row << "%#{DAT_LEN[:CIKS]}s" % if no8 && pack_info && !pack_info[:is_tier] # Packungen.xls
             pack_info[:swissmedic_category]
           else
             "0"
           end.gsub(/(\+|\s)/, "")
    row << "%0#{DAT_LEN[:ITHE]}d" % if no8 && pack_info && !pack_info[:is_tier]
             format_date(pack_info[:ith_swissmedic])
           else
             ("0" * DAT_LEN[:ITHE])
           end.to_i
    row << "%0#{DAT_LEN[:CEAN]}d" % (/^000000/.match?(sprintf("%013d", ean.to_i)) ? 0 : ean.to_i)
    row << "%#{DAT_LEN[:CMWS]}s" % "2" # pharma
    rows << row
  end
  rows.join("\n")
end
build_fi() click to toggle source
# File lib/oddb2xml/builder.rb, line 1132
def build_fi
  nbr_records = 0
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.KOMPENDIUM(XML_OPTIONS) {
      %w[de fr].each do |lang|
        infos = @infos[lang].uniq { |i| i[:monid] }
        infos.each do |info|
          xml.KMP(
            "MONTYPE" => "fi", # only
            "LANG" => lang.upcase,
            "DT" => ""
          ) {
            unless info[:name].empty?
              xml.name info[:name]
            end
            unless info[:owner].empty?
              xml.owner info[:owner]
            end
            xml.monid info[:monid] unless info[:monid].empty?
            xml.style { xml.cdata(info[:style]) } if info[:style]
            xml.paragraph { xml.cdata(Nokogiri::HTML.fragment(info[:paragraph].to_html).to_html(encoding: "UTF-8")) } if info[:paragraph]
            nbr_records += 1
          }
        end
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_fi_product() click to toggle source
# File lib/oddb2xml/builder.rb, line 1169
def build_fi_product
  prepare_products
  nbr_records = 0
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.KOMPENDIUM_PRODUCT(XML_OPTIONS) {
      info_index = {}
      %w[de fr].each do |lang|
        @infos[lang].each_with_index do |info, i|
          info_index[info[:monid]] = i
        end
        # prod
        @products.each do |ean13, prod|
          next unless prod[:seq] && prod[:seq][:packages]
          seq = prod[:seq]
          prod[:seq][:packages].each { |phar, package|
            next unless package[:swissmedic_number8]
            m = /(\d{5})(\d{3})/.match(package[:swissmedic_number8])
            next unless m
            number = m[1].to_s
            idx = info_index[number]
            next unless idx
            xml.KP("DT" => "") {
              xml.MONID @infos[lang][idx][:monid]
              xml.PRDNO seq[:product_key] unless seq[:product_key].empty?
              # as orphan ?
              xml.DEL @orphan.include?(number) ? true : false
              nbr_records += 1
            }
          }
        end
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_interaction() click to toggle source
# File lib/oddb2xml/builder.rb, line 414
def build_interaction
  prepare_interactions
  prepare_codes
  nbr_records = 0
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.INTERACTION(XML_OPTIONS) {
      Oddb2xml.log "build_interaction #{@interactions.size} interactions"
      @interactions.sort_by { |ix| ix[:ixno] }.each do |ix|
        xml.IX("DT" => "") {
          xml.IXNO ix[:ixno]
          xml.TITD ix[:title]
          # xml.TITF
          xml.GRP1D ix[:atc1]
          # xml.GRP1F
          xml.GRP2D ix[:atc2]
          # xml.GRP2F
          xml.EFFD ix[:effect]
          # xml.EFFF
          if @codes && ix[:grad]
            if (dict = @codes[ix[:grad].upcase])
              xml.RLV dict[:int]
              xml.RLVD dict[:txt]
              # xml.RLVF
            end
          end
          # xml.EFFTXTD
          # xml.EFFTXTF
          xml.MECHD ix[:mechanism]
          # xml.MECHF
          xml.MEASD ix[:measures]
          # xml.MEASF
          # xml.REMD
          # xml.REMF
          # xml.LIT
          xml.DEL false
          # xml.IXMCH {
          #  xml.TYP
          #  xml.TYPD
          #  xml.TYPF
          #  xml.CD
          #  xml.CDD
          #  xml.CDF
          #  xml.TXTD
          #  xml.TXTF
          # }
          nbr_records += 1
        }
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_limitation() click to toggle source
# File lib/oddb2xml/builder.rb, line 371
def build_limitation
  prepare_limitations
  Oddb2xml.log "build_limitation #{@limitations.size} limitations"
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.LIMITATION(XML_OPTIONS) do
      nbr_records = 0
      @limitations.each do |lim|
        if lim[:id].empty?
          puts "Skipping empty id of #{lim}"
          next
        end
        xml.LIM("DT" => "") do
          case lim[:key]
          when :swissmedic_number8
            xml.SwissmedicNo8 lim[:id]
          when :swissmedic_number5
            xml.SwissmedicNo5 lim[:id]
          when :pharmacode
            xml.Pharmacode lim[:id]
          end
          xml.IT lim[:it]
          xml.LIMTYP lim[:type]
          xml.LIMVAL lim[:value]
          xml.LIMNAMEBAG lim[:code] # original LIMCD
          xml.LIMNIV lim[:niv]
          xml.DSCRD lim[:desc_de]
          xml.DSCRF lim[:desc_fr]
          xml.VDAT lim[:vdate]
          nbr_records += 1
        end
      end
      xml.RESULT do
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      end
    end
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_person() click to toggle source
# File lib/oddb2xml/builder.rb, line 1245
def build_person
  nbr_records = 0
  Oddb2xml.log "build_person #{@people.size} persons"
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.Personen(XML_OPTIONS) {
      @people.each do |p|
        xml.Person("DT" => "") {
          xml.GLN_Person p[:gln] unless p[:gln].empty?
          xml.Name p[:last_name] unless p[:last_name].empty?
          xml.Vorname p[:first_name] unless p[:first_name].empty?
          xml.PLZ p[:post] unless p[:post].empty?
          xml.Ort p[:place] unless p[:place].empty?
          xml.Bewilligungskanton p[:region] unless p[:region].empty?
          xml.Land p[:country] unless p[:country].empty?
          xml.Bewilligung_Selbstdispensation p[:license] unless p[:license].empty?
          xml.Diplom p[:certificate] unless p[:certificate].empty?
          xml.BTM_Berechtigung p[:authorization] unless p[:authorization].empty?
          nbr_records += 1
        }
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_product() click to toggle source
# File lib/oddb2xml/builder.rb, line 583
def build_product
  self.class.class_eval do
    def check_name(obj, lang = :de)
      ean = obj[:ean13]
      refdata = @refdata[ean]
      if lang == :de
        name = refdata && refdata[:desc_de] ? refdata[:desc_de] : (obj[:desc_de] || obj[:sequence_name])
      elsif lang == :fr
        name = refdata && refdata[:desc_fr] ? refdata[:desc_fr] : (obj[:desc_fr] || obj[:sequence_name])
      elsif lang == :it
        name =  refdata[:desc_it] if refdata && refdata[:desc_it]
      else
        return false
      end
      return false if !name || name.empty? || name.length < 3
      name[0..119] # limit to maximal 120 chars as specified in the XSD
    end
  end
  prepare_substances
  prepare_products
  prepare_interactions
  prepare_codes
  add_missing_products_from_swissmedic
  add_products_from_bag_preparations(true)
  nbr_products = 0
  added_gtin = Set.new
  Oddb2xml.log "build_product #{@products.size + @missing.size} products"
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    emitted = []
    xml.PRODUCT(XML_OPTIONS) {
      @missing.each do |obj|
        ean = obj[:ean13]
        next unless check_name(obj, :de)
        next unless check_name(obj, :fr)
        next if added_gtin.include?(ean)
        next if /^Q/i.match?(obj[:atc])
        if obj[:prodno]
          next if emitted.index(obj[:prodno])
          emitted << obj[:prodno]
        end
        xml.PRD("DT" => obj[:last_change]) {
          nbr_products += 1
          xml.GTIN ean
          xml.PRODNO obj[:prodno] if obj[:prodno]
          xml.DSCRD check_name(obj, :de)
          xml.DSCRF check_name(obj, :fr) if check_name(obj, :fr)
          xml.ATC obj[:atc_code] unless obj[:atc_code].empty?
          xml.IT obj[:ith_swissmedic] if obj[:ith_swissmedic]
          xml.CPT
          xml.PackGrSwissmedic obj[:package_size] if obj[:package_size]
          xml.EinheitSwissmedic obj[:einheit_swissmedic] if obj[:einheit_swissmedic]
          xml.SubstanceSwissmedic obj[:substance_swissmedic] if obj[:substance_swissmedic]
          xml.CompositionSwissmedic obj[:composition_swissmedic] if obj[:composition_swissmedic]
        }
      end
      @products.sort.to_h.each do |ean13, obj|
        next if /^Q/i.match?(obj[:atc])
        seq = obj[:seq]
        ean = obj[:ean13]
        next unless check_name(obj, :de)
        next unless check_name(obj, :fr)
        next if added_gtin.include?(ean)
        added_gtin.add(ean)
        xml.PRD("DT" => obj[:last_change]) do
          nbr_products += 1
          xml.GTIN ean
          ppac = ((a_ppac = @packs[ean.to_s[4..11]]) && !a_ppac[:is_tier] ? a_ppac : {})
          ppac ||= @packs.find { |pac| pac.ean == ean }.first
          xml.PRODNO ppac[:prodno] if ppac[:prodno] && !ppac[:prodno].empty?
          xml.DSCRD check_name(obj, :de)
          xml.DSCRF check_name(obj, :fr)
          # xml.BNAMD
          # xml.BNAMF
          # xml.ADNAMD
          # xml.ADNAMF
          # xml.SIZE
          if seq
            xml.ADINFD seq[:comment_de] unless seq[:comment_de] && seq[:comment_de].empty?
            xml.ADINFF seq[:comment_fr] unless seq[:comment_fr] && seq[:comment_fr].empty?
            xml.GENCD seq[:org_gen_code] unless seq[:org_gen_code] && seq[:org_gen_code].empty?
          end
          # xml.GENGRP
          xml.ATC obj[:atc] unless obj[:atc].empty?
          xml.IT obj[:ith] unless obj[:ith].empty?
          # xml.ITBAG
          # xml.KONO
          # xml.TRADE
          # xml.PRTNO
          # xml.MONO
          # xml.CDGALD
          # xml.CDGALF
          # xml.FORMD
          # xml.FORMF
          # xml.DOSE
          # xml.DOSEU
          # xml.DRGFD
          # xml.DRGFF
          obj[:no8] =~ /(\d{5})(\d{3})/
          if @orphan.include?($1.to_s)
            xml.ORPH true
          end
          # xml.BIOPHA
          # xml.BIOSIM
          # xml.BFS
          # xml.BLOOD
          # xml.MSCD # always empty
          # xml.DEL
          xml.CPT {
            # xml.CPTLNO
            # xml.CNAMED
            # xml.CNAMEF
            # xml.IDXIND
            # xml.DDDD
            # xml.DDDU
            # xml.DDDA
            # xml.IDXIA
            # xml.IXREL
            # xml.GALF
            # xml.DRGGRPCD
            # xml.PRBSUIT
            # xml.CSOLV
            # xml.CSOLVQ
            # xml.CSOLVQU
            # xml.PHVAL
            # xml.LSPNSOL
            # xml.APDURSOL
            # xml.EXCIP
            # xml.EXCIPQ
            # xml.EXCIPCD
            # xml.EXCIPCF
            # xml.PQTY
            # xml.PQTYU
            # xml.SIZEMM
            # xml.WEIGHT
            # xml.LOOKD
            # xml.LOOKF
            # xml.IMG2
            if seq
              seq[:substances].each do |sub|
                xml.CPTCMP {
                  xml.LINE sub[:index] unless sub[:index].empty?
                  xml.SUBNO(@substances.index(sub[:name]) + 1) if @substances.include?(sub[:name])
                  xml.QTY sub[:quantity] unless sub[:quantity].empty?
                  xml.QTYU sub[:unit] unless sub[:unit].empty?
                  # xml.WHK
                }
              end
              @interactions.each do |ix|
                if [ix[:act1], ix[:act2]].include?(seq[:atc_code])
                  xml.CPTIX {
                    xml.IXNO ix[:ixno]
                    # xml.GRP
                    xml.RLV @codes[ix[:grad]]
                  }
                end
              end
            end
            # xml.CPTROA {
            # xml.SYSLOC
            # xml.ROA
            # }
          }
          # xml.PRDICD { # currently empty
          # xml.ICD
          # xml.RTYP
          # xml.RSIG
          # xml.REMD
          # xml.REMF
          # }
          xml.PackGrSwissmedic obj[:siz] unless obj[:siz].empty?
          xml.EinheitSwissmedic obj[:eht] unless obj[:eht].empty?
          xml.SubstanceSwissmedic obj[:sub] unless obj[:sub].empty?
          xml.CompositionSwissmedic obj[:comp] unless obj[:comp].empty?
        end
      end
      @firstbase.each do |ean13, obj|
        description = obj[:trade_item_description_de]
        if description.empty?
          description = obj[:trade_item_description_en]
        end
        next if added_gtin.include?(obj[:gtin])
        if !description.empty? || !obj[:trade_item_description_fr].empty?
          added_gtin.add(obj[:gtin])
          xml.PRD("DT" => "") {
            xml.GTIN obj[:gtin]
            xml.CPT
            xml.DSCRD description
            xml.DSCRF obj[:trade_item_description_fr]
          }
        end
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_products
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_substance() click to toggle source
# File lib/oddb2xml/builder.rb, line 340
def build_substance
  prepare_substances
  a_builder = Nokogiri::XML::Builder.new(encoding: "utf-8") do |xml|
    xml.doc.tag_suffix = @tag_suffix
    xml.SUBSTANCE(
      XML_OPTIONS
    ) {
      Oddb2xml.log "build_substance #{@substances.size} substances"
      exit 2 if (@options[:extended] || @options[:artikelstamm]) && (@substances.size == 0)
      nbr_records = 0
      @substances.each_with_index do |sub_name, i|
        xml.SB("DT" => "") do
          xml.SUBNO((i + 1).to_i)
          # xml.NAMD
          # xml.ANAMD
          # xml.NAMF
          xml.NAML sub_name
          nbr_records += 1
        end
      end
      xml.RESULT {
        xml.OK_ERROR "OK"
        xml.NBR_RECORD nbr_records
        xml.ERROR_CODE ""
        xml.MESSAGE ""
      }
    }
  end
  Oddb2xml.add_hash(a_builder.to_xml)
end
build_with_migel_dat() click to toggle source
# File lib/oddb2xml/builder.rb, line 1434
def build_with_migel_dat
  reset = true
  prepare_articles(reset)
  rows = []
  @articles.each do |obj|
    row = ""
    next if (obj[:ean13].to_s.length != 13) && !ean14
    # Oddb2tdat.parse_migel
    row << "%#{DAT_LEN[:RECA]}s" % "11"
    row << "%#{DAT_LEN[:CMUT]}s" % if (phar = obj[:pharmacode]) && (phar.size > 3)
             "1"
           else
             "3"
           end
    row << "%0#{DAT_LEN[:PHAR]}d" % obj[:pharmacode].to_i
    abez = ( # de name
      obj[:desc_de].to_s + " " +
    (obj[:quantity] || "")
    ).delete('"')
    row << format_name(Oddb2xml.patch_some_utf8(abez), DAT_LEN[:ABEZ])
    row << "0" * DAT_LEN[:PRMO]
    row << "0" * DAT_LEN[:PRPU]
    row << "%#{DAT_LEN[:CKZL]}s" % "3" # sl_entry and lppv
    row << "%#{DAT_LEN[:CLAG]}s" % "0"
    row << "%#{DAT_LEN[:CBGG]}s" % "0"
    row << "%#{DAT_LEN[:CIKS]}s" % " " # no category
    row << "%0#{DAT_LEN[:ITHE]}d" % 0
    row << obj[:ean13].to_s.rjust(DAT_LEN[:CEAN], "0")
    row << "%#{DAT_LEN[:CMWS]}s" % "1" # nonpharma
    rows << row
  end
  rows.join("\n")
end
check_article_name(obj, lang = :de) click to toggle source
# File lib/oddb2xml/builder.rb, line 1499
def check_article_name(obj, lang = :de)
  ean = obj[:ean13]
  refdata = @refdata[ean]
  if lang == :de
    name = refdata && refdata[:desc_de] ? refdata[:desc_de] : obj[:sequence_name]
  elsif lang == :fr
    name = refdata && refdata[:desc_fr] ? refdata[:desc_fr] : obj[:sequence_name]
  else
    return "--missing--"
  end
  return "--missing--" if !name || name.empty? || name.length < 3
  name[0..119] # limit to maximal 120 chars as specified in the XSD
end
check_name(obj, lang = :de) click to toggle source
# File lib/oddb2xml/builder.rb, line 585
def check_name(obj, lang = :de)
  ean = obj[:ean13]
  refdata = @refdata[ean]
  if lang == :de
    name = refdata && refdata[:desc_de] ? refdata[:desc_de] : (obj[:desc_de] || obj[:sequence_name])
  elsif lang == :fr
    name = refdata && refdata[:desc_fr] ? refdata[:desc_fr] : (obj[:desc_fr] || obj[:sequence_name])
  elsif lang == :it
    name =  refdata[:desc_it] if refdata && refdata[:desc_it]
  else
    return false
  end
  return false if !name || name.empty? || name.length < 3
  name[0..119] # limit to maximal 120 chars as specified in the XSD
end
detect_nincd(de_idx) click to toggle source
# File lib/oddb2xml/builder.rb, line 1277
def detect_nincd(de_idx)
  if @lppvs[de_idx[:ean13].to_s] # LPPV
    20
  elsif @items[de_idx[:pharmacode]] # BAG-XML (SL/LS)
    10
  elsif de_idx[:migel] || # MiGel (xls)
      (de_idx[:_type] == :nonpharma) # MiGel (swissindex)
    13
  else
    # fallback via EAN
    bag_entry_via_ean = @items.values.select do |item|
      next unless item[:packages]
      item[:packages].values.count { |a_pac| a_pac[:ean13].to_s.eql?(de_idx[:ean13].to_s) } != 0
    end
    if bag_entry_via_ean.length > 0
      10
    end
  end
end
emit_items(xml) click to toggle source
# File lib/oddb2xml/builder.rb, line 1531
def emit_items(xml)
  nr_items = 0
  gtins_to_article = {}
  @articles.each { |article| gtins_to_article[article[:ean13]] = article }
  sl_gtins = @items.values.collect { |x| x[:packages].keys }.flatten.uniq
  gtins = gtins_to_article.keys + @infos_zur_rose.keys + @packs.values.collect { |x| x[:ean13] } + sl_gtins
  gtins = (gtins - @@gtin2ignore)
  gtins.sort!.uniq!
  gtins.each do |ean13|
    no8 = ean13.to_s[4..11] # BAG-XML(SL/LS)
    next if ean13 == 0
    obj = gtins_to_article[ean13] || @items.values.find { |x| x[:packages].keys.index(ean13) } || @infos_zur_rose[ean13]
    if obj
      obj = @packs[no8].merge(obj) if @packs[no8]
    else
      obj = @packs[no8] # obj not yet in refdata. Use data from swissmedic_package.xlsx
    end
    nr_items += 1
    Oddb2xml.log "build_artikelstamm #{ean13}: #{nr_items} of #{gtins.size} articles" if nr_items % 5000 == 0
    item = @items[ean13]
    pack_info = nil
    pack_info = @packs[no8] if no8 && /#{ean13}/.match(@packs[no8].to_s) # info from Packungen.xlsx from swissmedic_info
    next if pack_info && /Tierarzneimittel/.match(pack_info[:list_code])
    next if obj[:desc_de] && /ad us vet/i.match(obj[:desc_de])
    sequence = obj[:seq]
    if sequence.nil? && @packs[no8] && /#{ean13}/.match(@packs[no8].to_s)
      sequence = {packages: {ean13 => @packs[no8]}}
      obj[:seq] = sequence.clone
    end
    if sequence.nil? && @items[ean13] && @items[ean13][:packages][ean13]
      sequence = @items[ean13]
    end
    if sequence
      if obj[:seq] && !obj[:seq][:packages].keys.index(ean13)
        # puts "unable to find  #{ean13} in #{obj[:seq][:packages].keys}"
        next
      end
      sequence[:packages].each do |gtin, package|
        pkg_gtin = package[:ean13].clone
        if package[:no8] && (new_ean_13 = Oddb2xml.getEan13forNo8(package[:no8]))
          if !new_ean_13.eql?(pkg_gtin)
            puts "Setting #{new_ean_13} for #{pkg_gtin}"
            pkg_gtin = new_ean_13
          end
        end
        info = @calc_items[pkg_gtin]
        if @@emitted_v5_gtins.index(pkg_gtin)
          next
        else
          @@emitted_v5_gtins << pkg_gtin.clone
        end
        options = {"PHARMATYPE" => "P"}
        xml.ITEM(options) do
          name = item[:name_de] + " " + item[:desc_de].strip + " " + package[:desc_de] if package && package[:desc_de]
          name ||= @refdata[pkg_gtin] ? @refdata[pkg_gtin][:desc_de] : nil
          name ||= @infos_zur_rose[ean13][:description] if @infos_zur_rose[ean13]
          name ||= obj[:name_de] + ", " + obj[:desc_de].strip if obj[:name_de]
          name ||= (item[:desc_de] + item[:name_de]) if item
          name ||= obj[:sequence_name]
          xml.GTIN pkg_gtin.to_s.rjust(13, "0")
          xml.SALECD("A")
          # maxLength for DSCR is 50 for Artikelstamm v3
          xml.DSCR(name) # for description for zur_rose
          name_fr = item[:name_fr] + " " + item[:desc_fr].strip + " " + package[:desc_fr] if package && package[:desc_fr]
          name_fr ||= @refdata[pkg_gtin] ? @refdata[pkg_gtin][:desc_fr] : nil
          # Zugelassenen Packungen has only german names
          name_fr ||= (obj[:name_fr] + ", " + obj[:desc_fr]).strip if obj[:name_fr]
          # ZuRorse has only german names
          name_fr ||= (item[:name_fr] + ", " + item[:desc_fr]) if item
          xml.DSCRF(name_fr)
          name_it = item[:name_it] + " " + item[:desc_it].strip + " " + package[:desc_it] if package && package[:desc_it]
          name_it ||= @refdata[pkg_gtin] ? @refdata[pkg_gtin][:desc_it] : nil
          # Zugelassenen Packungen has only german names
          name_it ||= (obj[:name_it] + ", " + obj[:desc_it]).strip if obj[:name_it]
          # ZuRorse has only german names
          name_it ||= (item[:name_it] + ", " + item[:desc_it]) if item
          xml.DSCRI(name_it)
          if obj[:company_name] || obj[:company_ean]
            xml.COMP do # Manufacturer
              xml.NAME obj[:company_name][0..99] # limit to 100 chars as in XSD
              xml.GLN obj[:company_ean]
            end
          end
          pexf = ppub = nil
          if package[:prices]
            pexf ||= package[:prices][:exf_price][:price]
            ppub ||= package[:prices][:pub_price][:price]
          elsif @items[ean13] && @items[ean13][:packages] && @items[ean13][:packages][ean13] && (bag_prices = @items[ean13][:packages][ean13][:prices])
            pexf ||= bag_prices[:exf_price][:price]
            ppub ||= bag_prices[:pub_price][:price]
          else
            pexf ||= obj[:price]
            ppub ||= obj[:pub_price]
          end
          ppub = nil if ppub && ppub.size == 0
          pexf = nil if pexf && pexf.size == 0
          if !(obj[:price] && !obj[:price].empty?) || !(obj[:pub_price] && !obj[:pub_price].empty?)
            zur_rose_detail = @infos_zur_rose.values.find { |x| x[:ean13].to_i == ean13.to_i }
            if zur_rose_detail
              pexf ||= zur_rose_detail[:price]
              ppub ||= zur_rose_detail[:pub_price]
            end
          end
          xml.PEXF pexf if pexf
          xml.PPUB ppub if ppub
          measure = ""
          if info
            # MEASSURE Measurement Unit,e.g. Pills or milliliters
            #             <DSCR>HIRUDOID Creme 3 mg/g 40 g</DSCR>
            xml.PKG_SIZE info.pkg_size.to_i if info.pkg_size
            if info.measure
              measure = info.measure
            elsif info.pkg_size && info.unit
              measure = info.pkg_size + " " + info.unit
            elsif info.pkg_size
              measure = info.pkg_size
            end
            xml.MEASURE measure
            # Die Darreichungsform dieses Items. zB Tablette(n) oder Spritze(n)
            xml.DOSAGE_FORM info.galenic_form.descriptions["de"] if info.galenic_form.descriptions["de"]
            xml.DOSAGE_FORMF info.galenic_form.descriptions["fr"] if info.galenic_form.descriptions["fr"]
            xml.DOSAGE_FORMI info.galenic_form.descriptions["it"] if info.galenic_form.descriptions["it"]
          end
          xml.SL_ENTRY "true" if sl_gtins.index(pkg_gtin)
          xml.IKSCAT package[:swissmedic_category][0] if package[:swissmedic_category] && package[:swissmedic_category].length > 0
          xml.GENERIC_TYPE sequence[:org_gen_code] if sequence[:org_gen_code] && !sequence[:org_gen_code].empty?
          xml.LPPV "true" if @lppvs[pkg_gtin.to_s] # detect_nincd
          if item && item[:deductible]
            case item[:deductible]
              when "Y" then xml.DEDUCTIBLE 40; # 40%
              when "N" then xml.DEDUCTIBLE 10; # 10%
            end
          elsif item && item[:deductible20]
            # TODO: Delete deductible20 after 2024/03/01
            # https://github.com/zdavatz/oddb2xml/issues/81
            case item[:deductible20]
              when "Y" then xml.DEDUCTIBLE 40; # 40%
              when "N" then xml.DEDUCTIBLE 10; # 10%
            end
          end
          prodno = Oddb2xml.getProdnoForEan13(pkg_gtin)
          atc = package[:atc_code]
          refdata_atc = @refdata[pkg_gtin][:atc_code] if @refdata && @refdata[pkg_gtin] && @refdata[pkg_gtin]
          if refdata_atc && atc.nil?
            puts "WARNING: #{pkg_gtin} ATC-code from refdata #{refdata_atc} as Swissmedic ATC is nil #{name}"
            atc = refdata_atc
          end
          unless prodno # find a prodno from packages for vaccinations
            if atc && /^J07/.match(atc) && !/^J07AX/.match(atc)
              pack = @packs.values.find { |v| v && v[:atc_code].eql?(atc) }
              if pack
                prodno = pack[:prodno]
                Oddb2xml.log "Patching vaccination for #{pkg_gtin} #{atc} #{name} via prodno #{prodno}"
              else
                Oddb2xml.log "unable to find a pack/prodno for  vaccination for #{pkg_gtin} #{atc} #{name}"
              end
            end
          end
          xml.PRODNO prodno if prodno
          @csv_file << [pkg_gtin, name, package[:unit], measure,
            pexf || "",
            ppub || "",
            prodno, atc, package[:substance_swissmedic],
            sequence[:org_gen_code], package[:ith_swissmedic],
            @items[pkg_gtin] ? "SL" : ""]
        end
      end
    else # non pharma
      if @@emitted_v5_gtins.index(ean13)
        next
      else
        @@emitted_v5_gtins << ean13.clone
      end
      # Set the pharmatype to 'Y' for outdated products, which are no longer found
      # in refdata/packungen
      chap70 = nil
      if @chapter70items.values.find { |x| x[:pharmacode]&.eql?(obj[:pharmacode]) }
        Oddb2xml.log "found chapter #{obj[:pharmacode]}" if $VERBOSE
        chap70 = true
      end
      patched_pharma_type = (/^7680/.match(ean13.to_s.rjust(13, "0")) || chap70 ? "P" : "N")
      next if /^#{Oddb2xml::FAKE_GTIN_START}/o.match?(ean13.to_s)
      next if obj[:data_origin].eql?("zur_rose") && /^7680/.match(ean13) # must skip inactiv items
      xml.ITEM({"PHARMATYPE" => patched_pharma_type}) do
        xml.GTIN ean13.to_s.rjust(13, "0")
        if obj[:pharmacode] && obj[:pharmacode].length > 0
          xml.PHAR obj[:pharmacode]
        elsif (zur_rose = @infos_zur_rose[ean13])
          puts "Artikelstamm: Adding #{zur_rose[:pharmacode]} to article GTIN #{ean13}"
          xml.PHAR zur_rose[:pharmacode]
        elsif /^7680/.match?(ean13)
          puts "Artikelstamm: No pharmacode for article GTIN #{ean13} via ZurRose"
        end
        emit_salecd(xml, ean13, obj)
        description = obj[:desc_de] || obj[:description] # for description for zur_rose
        xml.DSCR(description)
        xml.DSCRF(obj[:desc_fr])
        xml.DSCRI(obj[:desc_it]) if obj[:desc_it] && !obj[:desc_it].empty?
        if obj[:company_ean] && !obj[:company_ean].empty?
          xml.COMP do
            xml.GLN obj[:company_ean]
          end
        end
        if !(obj[:price] && !obj[:price].empty?) || !(obj[:pub_price] && !obj[:pub_price].empty?)
          zur_rose_detail = @infos_zur_rose.values.find { |x| x[:ean13].to_i == ean13.to_i }
        end
        ppub = nil
        pexf = nil
        if obj[:price] && !obj[:price].empty?
          xml.PEXF(pexf = obj[:price])
        elsif zur_rose_detail
          if zur_rose_detail[:price] && !zur_rose_detail[:price].empty? && !zur_rose_detail[:price].eql?("0.00")
            # Oddb2xml.log "NonPharma: #{ean13} adding PEXF #{zur_rose_detail[:price]} #{description}"
            xml.PEXF(pexf = zur_rose_detail[:price])
          end
        end
        if obj[:pub_price] && !obj[:pub_price].empty?
          xml.PPUB(ppub = obj[:pub_price])
        elsif zur_rose_detail
          if zur_rose_detail[:pub_price] && !zur_rose_detail[:pub_price].empty? && !zur_rose_detail[:pub_price].eql?("0.00")
            # Oddb2xml.log "NonPharma: #{ean13} adding PPUB #{zur_rose_detail[:pub_price]} #{description}"
            xml.PPUB(ppub = zur_rose_detail[:pub_price])
          end
        end
        @csv_file << [ean13, description, "", "", pexf, ppub, "", "", "", "", "", ""]
        if chap70
          xml.comment "Chapter70 hack #{ean13.to_s.rjust(13, "0")} #{description.encode(xml: :text).gsub("--", "-")}"
          xml.SL_ENTRY "true"
          xml.PRODNO obj[:pharmacode]
        end
      end
    end
  end
  @csv_file.close if @csv_file && !@csv_file.closed?
  nr_items
end
emit_salecd(xml, ean13, obj) click to toggle source
# File lib/oddb2xml/builder.rb, line 1468
def emit_salecd(xml, ean13, obj)
  zur_rose = nil
  if !@infos_zur_rose.empty? && ean13 && @infos_zur_rose[ean13]
    zur_rose = @infos_zur_rose[ean13] # zurrose
  end
  nincd = detect_nincd(obj)
  in_refdata = !!(obj[:seq] && obj[:seq][:packages] && obj[:seq][:packages][ean13] && obj[:seq][:packages][ean13][:swissmedic_number8])
  status = if nincd && nincd == 13
    "A"
  else
    zur_rose && zur_rose[:cmut] != "3" ? "A" : "I"
  end
  if in_refdata && !"A".eql?(status)
    msg = "Overriding status #{status} nincd #{nincd} for #{ean13} as in refdata_pharma"
    # Oddb2xml.log msg
    @overriden_salecd << ean13
    xml.SALECD("A") { xml.comment(msg) }
  else
    xml.SALECD(status) { xml.comment("expiry_date #{obj[:expiry_date]}") if obj[:expiry_date] }
  end
end
emit_substance(xml, substance, emit_active = false) click to toggle source
# File lib/oddb2xml/builder.rb, line 837
def emit_substance(xml, substance, emit_active = false)
  xml.MORE_INFO substance.more_info if substance.more_info
  xml.SUBSTANCE_NAME substance.name
  xml.IS_ACTIVE_AGENT substance.is_active_agent if emit_active
  if substance.dose
    if substance.qty.is_a?(Float) || substance.qty.is_a?(Integer)
      xml.QTY substance.qty
      xml.UNIT substance.unit
    else
      xml.DOSE_TEXT substance.dose.to_s
    end
  end
  if substance.chemical_substance
    xml.CHEMICAL_SUBSTANCE {
      emit_substance(xml, substance.chemical_substance)
    }
  end
  if substance.salts && (substance.salts.size > 0)
    xml.SALTS do
      substance.salts.each do |salt|
        xml.SALT do
          emit_substance(xml, salt)
        end
      end
    end
  end
end
format_date(date_str, len = 7) click to toggle source
# File lib/oddb2xml/builder.rb, line 1328
def format_date(date_str, len = 7)
  date = date_str.delete(".")
  if date.size < len
    date += "0" * (len - date.size)
  end
  date[0, len]
end
format_name(name, length) click to toggle source

The migel name must be always 50 chars wide and in ISO 8859-1 format

# File lib/oddb2xml/builder.rb, line 1337
def format_name(name, length)
  ("%-#{length}s" % name)[0..length - 1]
end
format_price(price_str, len = 6, int_len = 4, frac_len = 2) click to toggle source
# File lib/oddb2xml/builder.rb, line 1313
def format_price(price_str, len = 6, int_len = 4, frac_len = 2)
  price = price_str.split(".")
  pre = "%0#{int_len}d" % (price[0] || "0")
  las = if price[1]
    if price[1].size < frac_len
      price[1] + "0" * (frac_len - price[2].to_s.size)
    else
      price[1][0, frac_len]
    end
  else
    "0" * frac_len
  end
  (pre.to_s + las.to_s)[0, len]
end
override(xml, id, field, default_value) click to toggle source
# File lib/oddb2xml/builder.rb, line 1514
def override(xml, id, field, default_value)
  has_overrides = /\d{13}/.match?(id.to_s) ? @@article_overrides[id.to_i] : @@product_overrides[id.to_i]
  if has_overrides && has_overrides[field.to_s]
    new_value = has_overrides[field.to_s]
    if new_value.to_s.eql?(default_value.to_s)
      xml.comment("obsolete override")
    else
      xml.comment("override #{default_value} with")
    end
    cmd = "xml.#{field} \"#{new_value}\""
  else
    cmd = "xml.#{field} \"#{default_value.to_s.delete('"')}\""
  end
  eval cmd if default_value
end
prepare_articles(reset = false) click to toggle source
# File lib/oddb2xml/builder.rb, line 93
def prepare_articles(reset = false)
  @articles = nil if reset
  unless @articles
    Oddb2xml.log("prepare_articles starting with #{@articles ? @articles.size : "no"} articles.")
    @articles = []
    @refdata.each do |ean13, obj|
      unless SKIP_MIGEL_DOWNLOADER
        if @migel[ean13]
          # delete duplicates
          @migel[ean13] = nil
        end
      end
      if (seq = @items[obj[:ean13]])
        obj[:seq] = seq.clone
      end
      @articles << obj
      @pharmacode[obj[:pharmacode]] = obj
    end
    # add
    unless SKIP_MIGEL_DOWNLOADER
      @migel.values.compact.each do |migel|
        next unless migel[:pharmacode]
        entry = {
          ean13: migel[:ean13],
          pharmacode: migel[:pharmacode],
          stat_date: "",
          desc_de: migel[:desc_de],
          desc_fr: migel[:desc_fr],
          atc_code: "",
          quantity: migel[:quantity],
          company_ean: migel[:company_ean],
          company_name: migel[:company_name],
          migel: true
        }
        @articles << entry
      end
    end
    nr_added = 0
    if @options[:extended] || @options[:artikelstamm]
      Oddb2xml.log("prepare_articles extended prepare_local_index having already #{@articles.size} articles")
      nr_items = 0
      @infos_zur_rose.each do |ean13, info|
        nr_items += 1
        pharmacode = info[:pharmacode]
        if @pharmacode[pharmacode]
          @pharmacode[pharmacode][:price] = info[:price]
          @pharmacode[pharmacode][:pub_price] = info[:pub_price]
          next
        end
        obj = {}
        found = false
        existing = @refdata[ean13]
        if existing
          found = true
          existing[:price] = info[:price]
          existing[:pub_price] = info[:pub_price]
        else
          entry = {
            desc: info[:description],
            desc_de: info[:description],
            status: info[:status] == "3" ? "I" : "A", # from ZurRose, we got 1,2 or 3 means aktive, aka available in trade
            atc_code: "",
            ean13: ean13,
            pharmacode: pharmacode,
            price: info[:price],
            pub_price: info[:pub_price],
            type: info[:type]
          }
          if pharmacode
            @refdata[pharmacode] = entry
          else
            @refdata[ean13] = entry
          end
          obj = entry
        end
        if !found && (obj.size > 0)
          @articles << obj unless @options[:artikelstamm]
          nr_added += 1
        end
      end
    end
    @firstbase.each do |ean13, obj|
      description = obj[:trade_item_description_de]
      if description.empty?
        description = obj[:trade_item_description_en]
      end
      if (!description.empty? || !obj[:trade_item_description_fr].empty?) && @refdata[obj[:gtin]].nil?
        entry = {
          ean13: obj[:gtin],
          desc_de: description,
          desc_fr: obj[:trade_item_description_fr],
          atc_code: "",
          company_ean: obj[:gln],
          firstbase: true
        }
        @articles << entry
      end
    end
  end
  Oddb2xml.log("prepare_articles done. Added #{nr_added} prices. Total #{@articles.size}")
end
prepare_calc_items(suppress_composition_parsing: false) click to toggle source
# File lib/oddb2xml/builder.rb, line 786
def prepare_calc_items(suppress_composition_parsing: false)
  @calc_items = {}
  packungen_xlsx = File.join(Oddb2xml::DOWNLOADS, "swissmedic_package.xlsx")
  return unless File.exist?(packungen_xlsx)
  workbook = RubyXL::Parser.parse(packungen_xlsx)
  row_nr = 0
  workbook.worksheets[0].each do |row|
    row_nr += 1
    next unless row && row.cells[0] && row.cells[0].value && (row.cells[0].value.to_i > 0)
    iksnr = "%05i" % row.cells[0].value.to_i
    if row_nr % 250 == 0
      puts "#{Time.now}: At row #{row_nr} iksnr #{iksnr}"
      $stdout.flush
    end
    seq_name = COLUMNS_FEBRUARY_2019.keys.index(:name_base)
    i_3 = COLUMNS_FEBRUARY_2019.keys.index(:ikscd)
    siz = COLUMNS_FEBRUARY_2019.keys.index(:size)
    atc = COLUMNS_FEBRUARY_2019.keys.index(:atc_class)
    list_code = COLUMNS_FEBRUARY_2019.keys.index(:production_science)
    unit = COLUMNS_FEBRUARY_2019.keys.index(:unit)
    sub = COLUMNS_FEBRUARY_2019.keys.index(:substances)
    comp = COLUMNS_FEBRUARY_2019.keys.index(:composition)

    no8 = iksnr + sprintf("%03d", row.cells[i_3].value.to_i)
    name = row.cells[seq_name] ? row.cells[seq_name].value : nil
    atc_code = row.cells[atc] ? row.cells[atc].value : nil
    list_code = row.cells[list_code] ? row.cells[list_code].value : nil
    package_size = row.cells[siz] ? row.cells[siz].value : nil
    unit = row.cells[unit] ? row.cells[unit].value : nil
    active_substance = row.cells[sub] ? row.cells[sub].value : nil
    composition = row.cells[comp] ? row.cells[comp].value : nil

    # skip veterinary product
    next if atc_code && /^Q/i.match(atc_code)
    next if list_code && /Tierarzneimittel/.match(list_code)
    info = nil
    begin
      info = if suppress_composition_parsing
        Calc.new(name, package_size, unit)
      else
        Calc.new(name, package_size, unit, active_substance, composition)
      end
    rescue
      puts "#{Time.now}: #{row_nr} iksnr #{iksnr} rescue from Calc.new"
    end
    ean12 = "7680" + no8
    ean13 = (ean12 + Oddb2xml.calc_checksum(ean12))
    @calc_items[ean13] = info
  end
end
prepare_codes() click to toggle source
# File lib/oddb2xml/builder.rb, line 239
def prepare_codes
  @codes ||= {
    "X" => {int: 11, txt: "Kontraindiziert"},
    "D" => {int: 13, txt: "Kombination meiden"},
    "C" => {int: 14, txt: "Monitorisieren"},
    "B" => {int: 15, txt: "Vorsichtsmassnahmen"},
    "A" => {int: 16, txt: "keine Massnahmen"}
  }
end
prepare_interactions() click to toggle source
# File lib/oddb2xml/builder.rb, line 230
def prepare_interactions
  unless @interactions
    @interactions = []
    @actions.each do |act|
      @interactions << act
    end
  end
end
prepare_limitations() click to toggle source
# File lib/oddb2xml/builder.rb, line 212
def prepare_limitations
  unless @limitations
    Oddb2xml.log("prepare_limitations from #{@items.size} items")
    limitations = []
    @items.values.uniq.each do |seq|
      next unless seq[:packages]
      seq[:packages].each_value do |pac|
        limitations += pac[:limitations]
      end
    end
    # ID is no longer fixed TAG (swissmedicNo8, swissmedicNo5, pharmacode)
    # limitation.xml needs all duplicate entries by this keys.
    limitations.uniq! { |lim| lim[:id].to_s + lim[:code] + lim[:type] }
    @limitations = limitations.sort_by { |lim| lim[:code] }
    Oddb2xml.log("prepare_limitations done. Total #{@limitations.size} from #{@items.size} items")
  end
end
prepare_products() click to toggle source
# File lib/oddb2xml/builder.rb, line 249
def prepare_products
  unless @products
    @products = {}
    if @chapter70items
      Chapter70xtractor::LIMITATIONS.each do |key, desc_de|
        puts "Chapter70: Adding lim #{key} #{desc_de}" if $VERBOSE
        @limitations << {code: key,
                         id: (key.eql?("L") ? "70.02" : "70.01"),
                         desc_de: desc_de,
                         desc_fr: "",
                         chap70: true}
      end
      @chapter70items.values.each do |item|
        next unless item[:limitation] && item[:limitation].length > 0
        rose = @infos_zur_rose.values.find { |x| x[:pharmacode]&.eql?(item[:pharmacode]) }
        ean13 = rose[:ean13] if rose
        ean13 ||= "9999" + item[:pharmacode]
        prodno = item[:pharmacode]
        obj = {
          chapter70: true,
          ean13: ean13,
          description: item[:description],
          code: item[:limitation] # LIMNAMEBAG
        }
        @products[prodno] = obj
        puts "Chapter70: Adding product #{ean13} #{obj}" if $VERBOSE
      end
    end
    @refdata.each_pair do |ean13, item|
      next if item&.is_a?(Hash) && item[:atc_code] && /^Q/i.match(item[:atc_code])
      next if item[:prodno] && @products[item[:prodno]]
      refdata_atc = item[:atc_code]
      obj = {
        seq: @items[ean13] || @items[item[:ean13]],
        pac: nil,
        no8: nil,
        ean13: item[:ean13],
        atc: refdata_atc,
        ith: "",
        siz: "",
        eht: "",
        sub: "",
        comp: "",
        data_origin: "refdata-product"
      }
      # obj[:pexf_refdata] = item[:price]
      # obj[:ppub_refdata] = item[:pub_price=]
      # obj[:pharmacode=]  = item[:pharmacode=]
      if obj[:ean13] # via EAN-Code
        obj[:no8] = obj[:ean13].to_s[4..11]
      end
      swissmedic_pack = @packs[item[:no8]]
      if swissmedic_pack
        swissmedic_atc = swissmedic_pack[:atc_code]
        if swissmedic_atc && swissmedic_atc.length >= 3 && (refdata_atc.nil? || !refdata_atc.eql?(swissmedic_atc))
          puts "WARNING: #{ean13} ATC-code #{swissmedic_atc} from swissmedic overrides #{refdata_atc} one from refdata #{item[:desc_de]}"
          item[:data_origin] += "-swissmedic-ATC"
          item[:atc] = swissmedic_atc
        end
      end
      if obj[:no8] && (ppac = @packs[obj[:no8]]) && # Packungen.xls
          !ppac[:is_tier]
        # If RefData does not have EAN
        if obj[:ean13].nil?
          obj[:ean13] = ppac[:ean13]
        end
        # If RefData dose not have ATC-Code
        if obj[:atc].nil? || obj[:atc].empty?
          obj[:atc] = ppac[:atc_code].to_s
        end
        obj[:ith] = ppac[:ith_swissmedic]
        obj[:siz] = ppac[:package_size]
        obj[:eht] = ppac[:einheit_swissmedic]
        obj[:sub] = ppac[:substance_swissmedic]
        obj[:comp] = ppac[:composition_swissmedic]
      end
      obj[:price] = item[:price]
      obj[:pub_price] = item[:pub_price]

      if obj[:ean13].to_s[0..3] == "7680"
        if item[:prodno]
          @products[item[:prodno]] = obj
        else
          @products[ean13] = obj
        end
      end
    end
  end
  @products
end
prepare_substances() click to toggle source
# File lib/oddb2xml/builder.rb, line 195
def prepare_substances
  unless @substances
    Oddb2xml.log("prepare_substances from #{@items.size} items")
    @substances = []
    @items.values.uniq.each do |seq|
      next unless seq[:substances]
      seq[:substances].each do |sub|
        @substances << sub[:name]
      end
    end
    @substances.uniq!
    @substances.sort!
    Oddb2xml.log("prepare_substances done. Total #{@substances.size} from #{@items.size} items")
    exit 2 if (@options[:extended] || @options[:artikelstamm]) && (@substances.size == 0)
  end
end