[open-ils-commits] r235 - in acq_edi: . trunk trunk/lib trunk/lib/edi trunk/lib/openils trunk/test (mbklein)

svn at svn.open-ils.org svn at svn.open-ils.org
Tue Mar 31 18:56:09 EDT 2009


Author: mbklein
Date: 2009-03-31 18:56:08 -0400 (Tue, 31 Mar 2009)
New Revision: 235

Added:
   acq_edi/trunk/
   acq_edi/trunk/Rakefile
   acq_edi/trunk/lib/
   acq_edi/trunk/lib/edi/
   acq_edi/trunk/lib/edi/mapper.rb
   acq_edi/trunk/lib/openils/
   acq_edi/trunk/lib/openils/mapper.rb
   acq_edi/trunk/test/
   acq_edi/trunk/test/map_spec.rb
   acq_edi/trunk/test/openils_map_spec.rb
   acq_edi/trunk/test/test_po.json
Log:
Initial import

Added: acq_edi/trunk/Rakefile
===================================================================
--- acq_edi/trunk/Rakefile	                        (rev 0)
+++ acq_edi/trunk/Rakefile	2009-03-31 22:56:08 UTC (rev 235)
@@ -0,0 +1,10 @@
+require 'spec/rake/spectask'
+
+Spec::Rake::SpecTask.new do |t|
+  t.ruby_opts = ['-I ./lib','-r rubygems']
+  t.spec_opts = ['-c','-f specdoc']
+  t.spec_files = FileList['test/**/*_spec.rb']
+  t.warning = false
+  t.rcov = true
+  t.rcov_opts = ['--exclude',"json,edi4r,rcov,lib/spec,bin/spec,builder,active_"]
+end
\ No newline at end of file

Added: acq_edi/trunk/lib/edi/mapper.rb
===================================================================
--- acq_edi/trunk/lib/edi/mapper.rb	                        (rev 0)
+++ acq_edi/trunk/lib/edi/mapper.rb	2009-03-31 22:56:08 UTC (rev 235)
@@ -0,0 +1,151 @@
+require 'edi4r'
+require 'edi4r/edifact'
+require 'json'
+
+class String
+  
+  def chunk(len)
+    re = Regexp.new(".{0,#{len.to_i}}")
+    self.scan(re).flatten.reject { |chunk| chunk.nil? or chunk.empty? }
+  end
+  
+end
+
+module EDI
+
+module E
+  
+  class Mapper
+    
+    attr :message
+    attr_accessor :defaults
+    
+    class << self
+      def defaults
+        @defaults || {}
+      end
+      
+      def defaults=(value)
+        unless value.is_a?(Hash)
+          raise TypeError, "Mapper defaults must be in the form of a Hash"
+        end
+        @defaults = value
+      end
+      
+      def map(name,expr = nil,&block)
+        register_mapping(name,expr,block)
+      end
+
+      def register_mapping(name, expr, proc)
+        if segment_handlers.find { |h| h[:name] == name }
+          raise NameError, "A pseudo-segment called '#{name}' is already registered"
+        end
+        if expr.nil?
+          expr = Regexp.new("^#{name}$")
+        end
+        segment_handlers.push({:name => name,:re => expr,:proc => proc})
+      end
+
+      def unregister_mapping(name)
+        segment_handlers.delete_if { |h|
+          h[:name] == name
+        }
+      end
+
+      def find_mapping(name)
+        segment_handlers.find { |h|
+          h[:re].match(name)
+        }
+      end
+      
+      private
+      def segment_handlers
+        if @segment_handlers.nil?
+          @segment_handlers = []
+        end
+        @segment_handlers
+      end
+    end
+    
+    def apply_mapping(name, value)
+      handler = self.class.find_mapping(name)
+      if handler.nil?
+        raise NameError, "Unknown pseudo-segment: '#{name}'"
+      end
+      handler[:proc].call(self, name, value)
+    end
+    
+    @segments = []
+    
+    def self.from_json(msg_type, json, msg_opts = {}, ic_opts = {})
+      result = self.new(msg_type, msg_opts, ic_opts)
+      result.add(JSON.parse(json))
+      result
+    end
+    
+    def initialize(msg_type, msg_opts = {}, ic_opts = {})
+      @ic = EDI::E::Interchange.new(ic_opts || {})
+      @message = @ic.new_message( { :msg_type => msg_type, :version => 'D', :release => '96A', :resp_agency => 'UN' }.merge(msg_opts || {}) )
+      @ic.add(@message,false)
+    end
+    
+    def add(*args)
+      if args[0].is_a?(String)
+        while args.length > 0
+          add_segment(args.shift, args.shift)
+        end
+      elsif args.length == 1 and args[0].is_a?(Array)
+        add(*args[0])
+      else
+        args.each { |arg|
+          add(arg)
+        }
+      end
+    end
+    
+    def to_s
+      @ic.to_s
+    end
+    
+    private
+    def add_segment(seg_name, value)
+      if seg_name =~ /^[A-Z]{3}$/
+        seg = @message.new_segment(seg_name)
+        @message.add(seg)
+        default = self.class.defaults[seg_name]
+        data = default.nil? ? value : default.merge(value)
+        data.each_pair { |de,val|
+          add_element(seg,de,val,default)
+        }
+      else
+        apply_mapping(seg_name, value)
+      end
+    end
+
+    def add_element(parent, de, value, default)
+      default = default[de] unless default.nil?
+      
+      if value.is_a?(Hash)
+        new_parent = parent.send("c#{de}")
+        data = default.nil? ? value : default.merge(value)
+        data.each_pair { |k,v| add_element(new_parent,k,v,default) }
+      elsif value.is_a?(Array)
+        de_array = parent.send("a#{de}")
+        value.each_with_index { |v,i|
+          element = de_array[i]
+          if v.is_a?(Hash)
+            data = default.nil? ? v : default.merge(v)
+            data.each_pair { |k,v1| add_element(element, k, v1, default) }
+          else
+            element.value = v
+          end
+        }
+      else
+        parent.send("d#{de}=",value)
+      end
+    end
+    
+  end
+  
+end
+end
\ No newline at end of file

Added: acq_edi/trunk/lib/openils/mapper.rb
===================================================================
--- acq_edi/trunk/lib/openils/mapper.rb	                        (rev 0)
+++ acq_edi/trunk/lib/openils/mapper.rb	2009-03-31 22:56:08 UTC (rev 235)
@@ -0,0 +1,99 @@
+require 'active_support'
+require 'edi/mapper'
+
+module OpenILS
+  
+  class Mapper < EDI::E::Mapper
+  end
+  
+end
+
+OpenILS::Mapper.defaults = {
+  'BGM' => { 'C002' => { '1001' => 220 }, '1225' => 9 },
+  'DTM' => { 'C507' => { '2005' => 137, '2379' => 102 } },
+  'NAD' => { 'C082' => { '3055' => '31B' } },
+  'CUX' => { 'C504' => { '6347' => 2, '6345' => 'USD', '6343' => 9 } },
+  'LIN' => { 'C212' => { '7143' => 'EN' } },
+  'PIA' => { '4347' => 5, 'C212' => { '7143' => 'IB' } },
+  'IMD' => { '7077' => 'F' },
+  'PRI' => { 'C509' => { '5125' => 'AAB' } },
+  'QTY' => { 'C186' => { '6063' => 21 } },
+  'UNS' => { '0081' => 'S' },
+  'CNT' => { 'C270' => { '6069' => 2 } }
+}
+
+OpenILS::Mapper.map 'order' do |mapper,key,value|
+  mapper.add('BGM', { '1004' => value['po_number'] })
+  mapper.add('DTM', { 'C507' => { '2380' => value['date'] } })
+  value['buyer'].to_a.each { |buyer| mapper.add('buyer',buyer) }
+  value['vendor'].to_a.each { |vendor| mapper.add('vendor',vendor) }
+  mapper.add('currency',value['currency'])
+  value['items'].each_with_index { |item,index|
+    item['line_index'] = index + 1
+    item['line_number'] = "#{value['po_number']}/#{index+1}" if item['line_number'].nil?
+    mapper.add('item', item)
+  }
+  mapper.add("UNS", {})
+  mapper.add("CNT", { 'C270' => { '6066' => value['line_items'] } })
+end
+
+OpenILS::Mapper.map 'item' do |mapper,key,value|
+  mapper.add('LIN', { 'C212' => { '7143' => nil }, '1082' => value['line_index'] })
+  id_groups = value['identifiers'].in_groups_of(5)
+  id_groups.each { |group|
+    ids = group.compact.collect { |data| 
+      id = { '7140' => data['id'] }
+      if data['id-qualifier']
+        id['7143'] = data['id-qualifier']
+      end
+      id
+    }
+    mapper.add('PIA',{ 'C212' => ids })
+  }
+  value['desc'].each { |desc| mapper.add('desc',desc) }
+  mapper.add('QTY', { 'C186' => { '6060' => value['quantity'] } })
+  mapper.add('PRI', { 'C509' => { '5118' => value['price'] } })
+  mapper.add('RFF', { 'C506' => { '1153' => 'LI', '1154' => value['line_number'] } })
+end
+
+OpenILS::Mapper.map('party',/^(buyer|vendor)$/) do |mapper,key,value|
+  codes = { 'buyer' => 'BY', 'supplier' => 'SU', 'vendor' => 'SU' }
+  party_code = codes[key]
+  
+  if value.is_a?(String)
+    value = { 'id' => value }
+  end
+
+  data = { 
+    '3035' => party_code, 
+    'C082' => { 
+      '3039' => value['id']
+    }
+  }
+  data['C082']['3055'] = value['id-qualifier'] unless value['id-qualifier'].nil?
+  mapper.add('NAD', data)
+
+  if value['reference']
+    value['reference'].each_pair { |k,v|
+      mapper.add('RFF', { 'C506' => { '1153' => k, '1154' => v }})
+    }
+  end
+end
+
+OpenILS::Mapper.map 'currency' do |mapper,key,value|
+  mapper.add('CUX', { 'C504' => ['6345' => value]})
+end
+
+OpenILS::Mapper.map 'desc' do |mapper,key,value|
+  values = value.to_a.flatten
+  while values.length > 0
+    code = values.shift
+    text = values.shift.to_s
+    code_qual = code =~ /^[0-9]+$/ ? 'L' : 'F'
+    chunked_text = text.chunk(35)
+    while chunked_text.length > 0
+      data = [chunked_text.shift,chunked_text.shift].compact
+      mapper.add('IMD', { '7077' => code_qual, '7081' => code, 'C273' => { '7008' => data } })
+    end
+  end
+end
\ No newline at end of file

Added: acq_edi/trunk/test/map_spec.rb
===================================================================
--- acq_edi/trunk/test/map_spec.rb	                        (rev 0)
+++ acq_edi/trunk/test/map_spec.rb	2009-03-31 22:56:08 UTC (rev 235)
@@ -0,0 +1,103 @@
+# map_spec.rb
+require 'edi/mapper'
+
+describe EDI::E::Mapper do
+  
+  before(:each) do
+    @map = EDI::E::Mapper.new('ORDERS')
+  end
+  
+  it "should chunk text" do
+    s = 'abcdefghijklmnopqrstuvwxyz'
+    s.chunk(5).should == ['abcde','fghij','klmno','pqrst','uvwxy','z']
+  end
+
+  it "should produce an empty purchase order when initialized" do
+    ic_text = @map.to_s
+    ic_text.should_not be_nil
+    ic_text.should_not be_empty
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'UNT+2+1'"
+  end
+  
+  it "should add a single segment in tuple form" do
+    @map.add("BGM", {"1225" => 9,"C002" => {"1001" => 220},"1004" => "12345678"})
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'BGM+220+12345678+9'UNT+3+1'"
+  end
+
+  it "should properly apply defaults" do
+    old_defaults = EDI::E::Mapper.defaults
+    EDI::E::Mapper.defaults = {
+      'BGM' => { 'C002' => { '1001' => 220 }, '1225' => 9 }
+    }
+    @map.add("BGM", {"1004" => "12345678"})
+    EDI::E::Mapper.defaults = old_defaults
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'BGM+220+12345678+9'UNT+3+1'"
+  end
+
+  it "should raise an exception if defaults don't look right" do
+    lambda {
+      EDI::E::Mapper.defaults = 'This is wrong!'
+    }.should raise_error(TypeError)
+  end
+  
+  it "should add multiple elements in tuple form" do
+    @map.add(
+      'BGM', { 'C002' => { '1001' => 220 }, '1004' => '12345678', '1225' => 9 },
+      'DTM', { 'C507' => { '2005' => 137, '2380' => '20090101', '2379' => 102 }}
+    )
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'BGM+220+12345678+9'DTM+137:20090101:102'UNT+4+1'"
+  end
+  
+  it "should add a single element in array form" do
+    @map.add(["BGM", {"1225" => 9,"C002" => {"1001" => 220},"1004" => "12345678"}])
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'BGM+220+12345678+9'UNT+3+1'"
+  end
+  
+  it "should add multiple elements in array form" do
+    @map.add(
+      ['BGM', { 'C002' => { '1001' => 220 }, '1004' => '12345678', '1225' => 9 }],
+      ['DTM', { 'C507' => { '2005' => 137, '2380' => '20090101', '2379' => 102 }}]
+    )
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'BGM+220+12345678+9'DTM+137:20090101:102'UNT+4+1'"
+  end
+  
+  it "should make use of custom mappings" do
+    EDI::E::Mapper.map 'currency' do |mapper,key,value|
+      mapper.add('CUX', { 'C504' => [{ '6347' => 2, '6345' => value, '6343' => 9 }]})
+    end
+
+    @map.add(
+      'BGM', { 'C002' => { '1001' => 220 }, '1004' => '12345678', '1225' => 9 },
+      'DTM', { 'C507' => { '2005' => 137, '2380' => '20090101', '2379' => 102 }},
+      'currency', 'USD'
+    )
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'BGM+220+12345678+9'DTM+137:20090101:102'CUX+2:USD:9'UNT+5+1'"
+  end
+  
+  it "should raise an exception when an unknown mapping is called" do
+    lambda {
+      @map.add('everything', { 'answer' => 42 })
+    }.should raise_error(NameError)
+  end
+
+  it "should raise an exception when re-registering a named mapping" do
+    lambda {
+      EDI::E::Mapper.map 'currency' do |mapper,key,value|
+        mapper.add('CUX', { 'C504' => [{ '6347' => 2, '6345' => value, '6343' => 9 }]})
+      end
+    }.should raise_error(NameError)
+  end
+  
+  it "should correctly unregister a mapping" do
+    EDI::E::Mapper.unregister_mapping 'currency'
+
+    lambda {
+      @map.add(
+        'BGM', { 'C002' => { '1001' => 220 }, '1004' => '12345678', '1225' => 9 },
+        'DTM', { 'C507' => { '2005' => 137, '2380' => '20090101', '2379' => 102 }},
+        'currency', 'USD'
+      )
+    }.should raise_error(NameError)
+  end
+  
+end
\ No newline at end of file

Added: acq_edi/trunk/test/openils_map_spec.rb
===================================================================
--- acq_edi/trunk/test/openils_map_spec.rb	                        (rev 0)
+++ acq_edi/trunk/test/openils_map_spec.rb	2009-03-31 22:56:08 UTC (rev 235)
@@ -0,0 +1,39 @@
+require 'openils/mapper'
+
+describe OpenILS::Mapper do
+  
+  before(:each) do
+    @map = OpenILS::Mapper.new('ORDERS')
+  end
+  
+  it "should add both qualified and unqualified buyer/vendor fields" do
+    @map.add(
+      ['buyer', { 'id' => '3472205', 'id-qualifier' => '91', 'reference' => { 'API' => '3472205 0001' } }],
+      ['buyer', { 'id' => '3472205', 'reference' => { 'API' => '3472205 0001' }}]
+    )
+    @map.add(
+      'vendor', '1556150', 
+      'vendor', { 'id' => '1556150', 'id-qualifier' => '91', 'reference' => { 'IA' => '1865' }}
+    )
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'NAD+BY+3472205::91'RFF+API:3472205 0001'NAD+BY+3472205::31B'RFF+API:3472205 0001'NAD+SU+1556150::31B'NAD+SU+1556150::91'RFF+IA:1865'UNT+9+1'"
+  end
+  
+  it "should properly chunk and add descriptive fields" do
+    @map.add(
+      'desc', [
+        'BAU', 'Campbell, James',
+        'BTI', "The Ghost Mountain boys : their epic march and the terrifying battle for New Guinea -- the forgotten war of the South Pacific",
+        'BPU', 'Crown Publishers',
+        'BPD', 2007
+      ]
+    )
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'IMD+F+BAU+:::Campbell, James'IMD+F+BTI+:::The Ghost Mountain boys ?: their epi:c march and the terrifying battle f'IMD+F+BTI+:::or New Guinea -- the forgotten war :of the South Pacific'IMD+F+BPU+:::Crown Publishers'IMD+F+BPD+:::2007'UNT+7+1'"
+  end
+
+  it "should create a message from JSON input" do
+    json = File.read(File.join(File.dirname(__FILE__), 'test_po.json'))
+    @map = OpenILS::Mapper.from_json('ORDERS',json)
+    @map.message.to_s.should == "UNH+1+ORDERS:D:96A:UN'BGM+220+2+9'DTM+137:20090331:102'NAD+BY+3472205::91'RFF+API:3472205 0001'NAD+BY+3472205::31B'RFF+API:3472205 0001'NAD+SU+1556150::31B'NAD+SU+1556150::91'RFF+IA:1865'CUX+2:USD:9'LIN+1'PIA+5+03-0010837:SA'IMD+F+BTI+:::Discernment'IMD+F+BPU+:::Concord Records,'IMD+F+BPD+:::1986.'IMD+F+BPH+:::1 sound disc ?:'QTY+21:2'PRI+AAB:35.95'RFF+LI:2/1'LIN+2'PIA+5+03-0010840:SA'IMD+F+BTI+:::The inner source'IMD+F+BAU+:::Duke, George, 1946-'IMD+F+BPU+:::MPS Records,'IMD+F+BPD+:::1973.'IMD+F+BPH+:::2 sound discs ?:'QTY+21:1'PRI+AAB:28.95'RFF+LI:2/2'UNS+S'CNT+2:2'UNT+33+1'"
+  end
+  
+end
\ No newline at end of file

Added: acq_edi/trunk/test/test_po.json
===================================================================
--- acq_edi/trunk/test/test_po.json	                        (rev 0)
+++ acq_edi/trunk/test/test_po.json	2009-03-31 22:56:08 UTC (rev 235)
@@ -0,0 +1,36 @@
+["order", {
+  "po_number":2,
+  "date":"20090331",
+  "buyer":[
+    {"id-qualifier":"91","id":"3472205","reference":{"API":"3472205 0001"}},
+    {"id":"3472205","reference":{"API":"3472205 0001"}}
+  ],
+  "vendor":[
+    "1556150",
+    {"id-qualifier":"91","reference":{"IA":"1865"},"id":"1556150"}
+  ],
+  "currency":"USD",
+  "items":[{
+    "identifiers":[{"id-qualifier":"SA","id":"03-0010837"}],
+    "price":35.95,
+    "desc":[
+      {"BTI":"Discernment"},
+      {"BPU":"Concord Records,"},
+      {"BPD":"1986."},
+      {"BPH":"1 sound disc :"}
+    ],
+    "quantity":2
+  },{
+    "identifiers":[{"id-qualifier":"SA","id":"03-0010840"}],
+    "price":28.95,
+    "desc":[
+      {"BTI":"The inner source"},
+      {"BAU":"Duke, George, 1946-"},
+      {"BPU":"MPS Records,"},
+      {"BPD":"1973."},
+      {"BPH":"2 sound discs :"}
+    ],
+    "quantity":1
+  }],
+  "line_items":2
+}]
\ No newline at end of file



More information about the open-ils-commits mailing list