Module: YDIM::Camt

Defined in:
lib/ydim/camt.rb

Overview

Reader for ISO-20022 bank-to-customer statements (camt.052/053/054), as delivered by UBS and the other Swiss banks. This is pure parsing: no DB and no ODBA, so it can run on the client side. The matching of entries against invoices lives in YDIM::Reconciler.

Defined Under Namespace

Classes: Entry, Error, Statement

Class Method Summary collapse

Class Method Details

.dedup(entries) ⇒ Object



141
142
143
144
145
# File 'lib/ydim/camt.rb', line 141

def dedup(entries)
  seen = {}
  entries.select { |entry| !seen.key?(entry.dedup_key) \
    && seen[entry.dedup_key] = true }
end

.entries(path) ⇒ Object

All entries of all statements below path, duplicates included -- the Reconciler drops those, and it can only report how many deliveries were doubled up if it gets to see them.



138
139
140
# File 'lib/ydim/camt.rb', line 138

def entries(path)
  read(path).flat_map { |stmt| stmt.entries }
end

.parse(xml, source = nil) ⇒ Object

Parses one camt document into its Statements.



125
126
127
128
129
130
131
132
133
134
# File 'lib/ydim/camt.rb', line 125

def parse(xml, source = nil)
  doc = REXML::Document.new(xml)
  root = doc.root or raise Error, "#{source}: not an XML document"
  # camt.053.001.02 through .08 differ only in the namespace URI for
  # everything we read, so take whatever the document declares.
  ns = { 'c' => root.namespace }
  REXML::XPath.match(doc, '//c:Stmt | //c:Rpt | //c:Ntfctn', ns).collect { |node|
    parse_statement(node, ns, source)
  }
end

.read(path) ⇒ Object

Reads whatever the bank handed you: a single xml file, a directory of them, or the zip you downloaded from e-banking. Returns Statements.



101
102
103
104
105
106
107
108
109
# File 'lib/ydim/camt.rb', line 101

def read(path)
  if File.directory?(path)
    read_files(Dir[File.join(path, '**', '*.[xX][mM][lL]')].sort)
  elsif path =~ /\.zip\z/i
    read_zip(path)
  else
    read_files([path])
  end
end

.read_files(paths) ⇒ Object



110
111
112
113
114
# File 'lib/ydim/camt.rb', line 110

def read_files(paths)
  paths.flat_map { |path|
    parse(File.read(path), File.basename(path))
  }
end

.read_zip(path) ⇒ Object



115
116
117
118
119
120
121
122
123
# File 'lib/ydim/camt.rb', line 115

def read_zip(path)
  Dir.mktmpdir('ydim-camt') { |dir|
    unless system('unzip', '-q', '-o', path, '-d', dir)
      raise Error,
        "unable to unzip #{path} -- extract it and pass the directory"
    end
    read_files(Dir[File.join(dir, '**', '*.[xX][mM][lL]')].sort)
  }
end