efo_nelfo 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b1766eab8e78076ddd1e00b0bd831975ab51cf86
4
+ data.tar.gz: d0188d51f9e52fd7bd133717261540334d2abf3f
5
+ SHA512:
6
+ metadata.gz: 2f4522a7ac95b3655348404e92b8dab3bd79d8787b9c4128033e7e2f5d8c8b1659730144c8a136bb17f0b33438e9e1af2c9107c26ed8d1a65a17f1b0b594afcb
7
+ data.tar.gz: 6708ebf094bb145c2a30f7a4e7c4d81887fdcdd5d1603b1c470898ba76a466eb86fb4b86f81edb05f6400128a9c1fcf49d55e64d1d33eb739a88e4f4ab7f08a0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in efo_nelfo.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'minitest' do
5
+ watch(%r|^spec/(.*)_spec\.rb|)
6
+ watch(%r|^lib/(.*)([^/]+)\.rb|) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
7
+ watch(%r|^spec/spec_helper\.rb|) { "spec" }
8
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Gudleik Rasch
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # EfoNelfo
2
+
3
+ Gem for parsing and writing EfoNelfo documents.
4
+
5
+ Supported versions:
6
+
7
+ * 4.0
8
+
9
+ Supported formats:
10
+
11
+ * Bestilling
12
+
13
+
14
+ ## Usage
15
+
16
+ Importing a CSV file:
17
+
18
+ # EfoNelfo.parse_csv "B12345678.332.csv" # => EfoNelfo::V40::Order
19
+
20
+ Exporting CSV:
21
+
22
+ # order = EfoNelfo::V40::Order.new
23
+ # order.add EfoNelfo::V40::Order::Line.new item_number: '442', order_number: 'abc'
24
+ # order.to_csv
25
+
26
+
27
+ ## TODO
28
+
29
+ * Export to json
30
+ * Support more filetypes
31
+ * Support more versions
32
+ * Support XML
33
+
34
+
35
+ ## Resources
36
+
37
+ * http://www.efo.no/Portals/5/docs/ImplementasjonsGuide%20EFONELFO%204.0.pdf
38
+
39
+
40
+ ## Contributing
41
+
42
+ 1. Fork it
43
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
44
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
45
+ 4. Push to the branch (`git push origin my-new-feature`)
46
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs << 'spec'
6
+ t.test_files = FileList['spec/*_spec.rb']
7
+ t.verbose = false
8
+ end
data/efo_nelfo.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'efo_nelfo/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "efo_nelfo"
8
+ spec.version = EfoNelfo::VERSION
9
+ spec.authors = ["Gudleik Rasch"]
10
+ spec.email = ["gr@skalar.no"]
11
+ spec.description = %q{Parser for EFONELFO format}
12
+ spec.summary = %q{Parser for EFONELFO format}
13
+ spec.homepage = "http://efo.no"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_development_dependency "bundler" #, "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+ spec.add_development_dependency "awesome_print"
24
+ spec.add_development_dependency "guard-minitest"
25
+ spec.add_development_dependency "rb-fsevent"
26
+ spec.add_development_dependency "terminal-notifier-guard"
27
+ end
@@ -0,0 +1,10 @@
1
+ module EfoNelfo
2
+
3
+ class Array < ::Array
4
+ def to_a
5
+ map(&:to_a).flatten(1)
6
+ end
7
+ end
8
+
9
+ end
10
+
@@ -0,0 +1,13 @@
1
+ module EfoNelfo
2
+
3
+ module AttributeAssignment
4
+ def initialize_attributes(*args)
5
+ if args && args.first.is_a?(Hash)
6
+ args.first.each do |attr, value|
7
+ send "#{attr}=", value
8
+ end
9
+ end
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,4 @@
1
+ module EfoNelfo
2
+ class UnsupportedPostType < StandardError; end
3
+ class DuplicateProperty < StandardError; end
4
+ end
@@ -0,0 +1,26 @@
1
+ class Order
2
+ def initialize
3
+ @lines = [ ['c', 'd'] ]
4
+ @attrs = [ 'a', 'b' ]
5
+ end
6
+
7
+ def to_a
8
+ # [ @attrs ] << lines
9
+ arr = [ @attrs ]
10
+ lines.each do |i|
11
+ arr += i
12
+ end
13
+ arr
14
+ end
15
+
16
+ def lines
17
+ @lines
18
+ end
19
+
20
+ def to_csv
21
+ to_a
22
+ end
23
+ end
24
+
25
+
26
+ puts Order.new.to_csv.inspect
@@ -0,0 +1,51 @@
1
+ module EfoNelfo
2
+
3
+ class PostType
4
+ include EfoNelfo::Property
5
+
6
+ attr_reader :post_type
7
+
8
+ @modules = []
9
+
10
+ class << self
11
+ def inherited(klass)
12
+ @modules << klass
13
+ end
14
+
15
+ def for(type, version=nil)
16
+ @modules.select { |mod| mod.can_parse?(type, version) }.first
17
+ end
18
+
19
+ def can_parse?(post_type, check_version=nil)
20
+ if check_version
21
+ self::POST_TYPES.keys.include?(post_type) && check_version == version
22
+ else
23
+ self::POST_TYPES.keys.include?(post_type)
24
+ end
25
+ end
26
+
27
+ # Extracts version number from class namespace.
28
+ # Example: EfoNelfo::V41::Some::Class.version # => "4.1"
29
+ def version
30
+ (self.to_s.match(/::V(?<version>\d+)::/)[:version].to_f / 10).to_s
31
+ end
32
+
33
+ end
34
+
35
+ def initialize(*args)
36
+ initialize_attributes *args
37
+ @post_type = self.class::POST_TYPES.keys.first
38
+ @version = self.class.version
39
+ end
40
+
41
+ # This is for adding posttypes
42
+ def add(something)
43
+ end
44
+
45
+ def post_type_human
46
+ self.class::POST_TYPES[post_type]
47
+ end
48
+
49
+ end
50
+
51
+ end
@@ -0,0 +1,132 @@
1
+ module EfoNelfo
2
+
3
+ module Property
4
+ include EfoNelfo::AttributeAssignment
5
+
6
+ def self.included(base)
7
+ base.send :extend, ClassMethods
8
+ end
9
+
10
+ def attributes
11
+ @attributes ||= initialize_default_attributes
12
+ end
13
+
14
+ def properties
15
+ self.class.properties
16
+ end
17
+
18
+ def to_a
19
+ properties.keys.map { |prop| formatted_for_csv(prop) }
20
+ end
21
+
22
+ private
23
+
24
+ def initialize_default_attributes
25
+ properties.inject({}) { |h,(name,options)| h[name] = options[:default]; h }
26
+ end
27
+
28
+ def format_value(value, type)
29
+ case type
30
+ when :integer
31
+ value.nil? ? nil : value.to_i
32
+ when :date
33
+ if value.nil?
34
+ nil
35
+ elsif value.kind_of? String
36
+ Date.parse value
37
+ else
38
+ value
39
+ end
40
+ when :boolean
41
+ value.nil? || value == true || value == 'J' || value == '' ? true : false
42
+ else
43
+ value
44
+ end
45
+ end
46
+
47
+ def formatted_for_csv(attr)
48
+ value = send(attr)
49
+
50
+ type = properties[attr][:type]
51
+ case type
52
+ when :date
53
+ value ? value.strftime("%Y%m%d") : nil
54
+ when :boolean
55
+ value == true ? "J" : nil
56
+ else
57
+ value
58
+ end
59
+ end
60
+
61
+ module ClassMethods
62
+ # Creates an attribute with given name.
63
+ #
64
+ # Options
65
+ # - type String, Integer etc. Default is String
66
+ # - required whether attribute is required. Default is false
67
+ # - limit Length the attribute can be. Default is nil
68
+ # - alias Norwegian alias name for the attribute
69
+ #
70
+ def property(name, options={})
71
+ options = {
72
+ type: :string,
73
+ required: false,
74
+ }.update options
75
+
76
+ name = name.to_sym
77
+
78
+ # Store property info in @properties
79
+ raise EfoNelfo::DuplicateProperty if properties.has_key?(name)
80
+ properties[name] = options
81
+
82
+ create_reader_for(name, options)
83
+ create_setter_for(name, options) unless options[:read_only]
84
+ create_question_for(name) if options[:type] == :boolean
85
+ create_alias_for(name, options) if options[:alias]
86
+ end
87
+
88
+ # Returns all properties
89
+ def properties
90
+ @_properties ||= {}
91
+ end
92
+
93
+ private
94
+
95
+ # Creates an attribute accessor for name
96
+ def create_reader_for(name, options)
97
+ define_method name do
98
+ attributes[name]
99
+ end
100
+ end
101
+
102
+ # Creates an attribute setter for name
103
+ def create_setter_for(name, options)
104
+ define_method "#{name}=" do |value|
105
+ attributes[name] = format_value(value, options[:type])
106
+ end
107
+ end
108
+
109
+ # Creates a name? accessor
110
+ def create_question_for(name)
111
+ define_method "#{name}?" do
112
+ attributes[name] == true
113
+ end
114
+ end
115
+
116
+ def create_alias_for(name, options)
117
+ define_method(options[:alias]) do
118
+ send name
119
+ end
120
+
121
+ unless options[:read_only]
122
+ define_method("#{options[:alias]}=") do |val|
123
+ send "#{name}=", val
124
+ end
125
+ end
126
+ end
127
+
128
+ end
129
+
130
+ end
131
+
132
+ end
@@ -0,0 +1,67 @@
1
+ require 'csv'
2
+
3
+ module EfoNelfo
4
+ module Reader
5
+
6
+ class CSV
7
+ CSV_OPTIONS = {
8
+ col_sep: ';',
9
+ headers: false,
10
+ row_sep: "\r\n",
11
+ encoding: "iso-8859-1",
12
+ quote_char: "\x00",
13
+ force_quotes: false,
14
+ skip_blanks: true
15
+ }
16
+
17
+ attr_reader :csv, :data
18
+
19
+ def initialize(options)
20
+ if options[:filename]
21
+ @data = File.read(options[:filename], encoding: CSV_OPTIONS[:encoding])
22
+ else
23
+ @data = options[:data]
24
+ end
25
+
26
+ @csv = ::CSV.new @data, CSV_OPTIONS
27
+ end
28
+
29
+ def parse
30
+ # Create the head object based on the first row
31
+ head = parse_head csv.first
32
+ head.source = @data
33
+
34
+ # Read rest of the file and add them to the head
35
+ csv.each do |row|
36
+ # Find the correct posttype module for given posttype and version
37
+ klass = EfoNelfo::PostType.for row[0]
38
+ next if klass.nil?
39
+
40
+ line = initialize_object_with_properties klass, row
41
+ head.add line
42
+ end
43
+
44
+ head
45
+ end
46
+
47
+ private
48
+
49
+ def parse_head(row)
50
+ klass = EfoNelfo::PostType.for row[0], row[2]
51
+ raise EfoNelfo::UnsupportedPostType.new("Don't know how to handle v#{row[2]} of #{row[0]}") if klass.nil?
52
+
53
+ initialize_object_with_properties klass, row
54
+ end
55
+
56
+ def initialize_object_with_properties(klass, columns)
57
+ object = klass.new
58
+ object.class.properties.each_with_index do |property, i|
59
+ object.send "#{property.first}=", columns[i]
60
+ end
61
+ object
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,38 @@
1
+ # encoding: utf-8
2
+ module EfoNelfo
3
+ module V40
4
+ class Order
5
+ class Line < EfoNelfo::PostType
6
+ POST_TYPES = {
7
+ 'BL' => 'Bestilling vareLinjepost',
8
+ 'IL' => 'Forespørsel vareLinjepost'
9
+ }
10
+
11
+ # It's important to list the property in the same order as specified in the specs
12
+ property :post_type, alias: :PostType, limit: 2, default: 'BL'
13
+ property :index, alias: :LinjeNr, limit: 4, type: :integer
14
+ property :order_number, alias: :BestNr, limit: 10, required: true
15
+ property :item_type, alias: :VareMrk, limit: 1, required: true, type: :integer
16
+ property :item_number, alias: :VareNr, limit: 14, required: true
17
+ property :item_name, alias: :VaBetg, limit: 30, required: true
18
+ property :item_description, alias: :VaBetg2, limit: 30
19
+ property :item_count, alias: :Ant, limit: 9, required: true, type: :integer
20
+ property :price_unit, alias: :PrisEnhet, limit: 3, required: true
21
+ property :buyer_item_number, alias: :KVareNr, limit: 25
22
+ property :delivery_date, alias: :LevDato, type: :date
23
+ property :buyer_ref, alias: :KjøpersRef, limit: 25
24
+ property :splitable, alias: :DelLev, type: :boolean, default: true
25
+ property :replacable, alias: :AltKode, type: :boolean, default: true
26
+
27
+ attr_accessor :text
28
+
29
+ # Returns an array with one or more elements
30
+ def to_a
31
+ [ super, text.to_a ].reject(&:empty?)
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,102 @@
1
+ # encoding: utf-8
2
+ module EfoNelfo
3
+ module V40
4
+ class Order < EfoNelfo::PostType
5
+ POST_TYPES = {
6
+ 'BH' => 'Bestilling Hodepost',
7
+ 'IH' => 'Forespørsel Hodepost'
8
+ }
9
+
10
+ attr_reader :lines
11
+ attr_accessor :source
12
+
13
+ # It's important to list the property in the same order as specified in the specs
14
+ property :post_type, alias: :PostType, limit: 2, default: 'BH'
15
+ property :format, alias: :Format, limit: 8, default: 'EFONELFO'
16
+ property :version, alias: :Versjon, limit: 3, default: version
17
+ property :seller_id, alias: :SelgersId, limit: 14
18
+ property :buyer_id, alias: :KjøpersId, limit: 14, required: true
19
+ property :order_number, alias: :BestNr, limit: 10, required: true
20
+ property :customer_id, alias: :KundeNr, limit: 10, required: true
21
+ property :contract_type, alias: :AvtaleIdMrk, limit: 1, type: :integer
22
+ property :contract_id, alias: :AvtaleId, limit: 10
23
+ property :buyer_order_number, alias: :KOrdNr, limit: 10
24
+ property :buyer_customer_id, alias: :KundAvd, limit: 10
25
+ property :project_id, alias: :ProsjektNr, limit: 10
26
+ property :buyer_warehouse_location, alias: :KLagerMrk, limit: 1
27
+ property :buyer_warehouse, alias: :KLager, limit: 14
28
+ property :seller_warehoure_location, alias: :SLagerMrk, limit: 1
29
+ property :seller_warehouse, alias: :SLager, limit: 14
30
+ property :external_ref, alias: :EksternRef, limit: 36
31
+ property :buyer_ref, alias: :KjøpersRef, limit: 25
32
+ property :label, alias: :Merket, limit: 25
33
+ property :confirmation_type, alias: :ObkrType, limit: 2
34
+ property :transport_type, alias: :TransportMåte, limit: 25
35
+ property :transport_msg, alias: :Melding, limit: 25
36
+ property :delivery_date, alias: :LevDato, type: :date
37
+ property :origin, alias: :BestOpp, limit: 2
38
+
39
+ property :receiver_delivery_location, alias: :LAdrLok, limit: 14
40
+ property :receiver_name, alias: :LFirmaNavn, limit: 35
41
+ property :receiver_address1, alias: :LAdr1, limit: 35
42
+ property :receiver_address2, alias: :LAdr2, limit: 35
43
+ property :receiver_zip, alias: :LPostNr, limit: 9
44
+ property :receiver_office, alias: :LPostSted, limit: 35
45
+ property :receiver_country, alias: :LLandK, limit: 2
46
+
47
+ property :buyer_name, alias: :KFirmaNavn, limit: 35
48
+ property :buyer_address1, alias: :KAdr1, limit: 35
49
+ property :buyer_address2, alias: :KAdr2, limit: 35
50
+ property :buyer_zip, alias: :KPostNr, limit: 9
51
+ property :buyer_office, alias: :KPostSted, limit: 35
52
+ property :buyer_country, alias: :KLandK, limit: 2
53
+ property :buyer_email, alias: :KEPost, limit: 60
54
+ property :buyer_web, alias: :KWebAdr, limit: 40
55
+
56
+ property :seller_name, alias: :SFirmaNavn, limit: 35
57
+ property :seller_address1, alias: :SAdr1, limit: 35
58
+ property :seller_address2, alias: :SAdr2, limit: 35
59
+ property :seller_zip, alias: :SPostNr, limit: 9
60
+ property :seller_office, alias: :SPostSted, limit: 35
61
+ property :seller_country, alias: :SLandK, limit: 2
62
+
63
+ def initialize(*args)
64
+ super
65
+ @lines = EfoNelfo::Array.new
66
+ end
67
+
68
+ def add(post_type)
69
+ case
70
+ when post_type.is_a?(Order::Line) then add_order_line(post_type)
71
+ when post_type.is_a?(Order::Text) then add_text_to_order_line(post_type)
72
+ end
73
+ end
74
+
75
+ def to_a
76
+ [ super ] + lines.to_a
77
+ end
78
+
79
+ def to_csv
80
+ CSV.generate EfoNelfo::Reader::CSV::CSV_OPTIONS do |csv|
81
+ to_a.each do |row|
82
+ csv << row unless row.empty?
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ # Appends a order line to the order
90
+ def add_order_line(line)
91
+ line.index = lines.length + 1
92
+ lines << line
93
+ end
94
+
95
+ # Add text to the last added orderline
96
+ def add_text_to_order_line(text)
97
+ lines.last.text = text
98
+ end
99
+
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,22 @@
1
+ # encoding: utf-8
2
+ module EfoNelfo
3
+ module V40
4
+ class Order
5
+ class Text < EfoNelfo::PostType
6
+ POST_TYPES = {
7
+ 'BT' => 'Bestilling Fritekstlinje',
8
+ 'IT' => 'Forespørsel Fritekstlinje'
9
+ }
10
+
11
+ # It's important to list the property in the same order as specified in the specs
12
+ property :post_type, alias: :PostType, limit: 2, default: 'BT'
13
+ property :text, alias: :FriTekst, limit: 30
14
+
15
+ def to_s
16
+ text
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,3 @@
1
+ module EfoNelfo
2
+ VERSION = "0.0.1"
3
+ end
data/lib/efo_nelfo.rb ADDED
@@ -0,0 +1,35 @@
1
+ # encoding: utf-8
2
+
3
+ # Common stuff
4
+ require 'efo_nelfo/version'
5
+ require 'efo_nelfo/errors'
6
+ require 'efo_nelfo/array'
7
+ require 'efo_nelfo/attribute_assignment'
8
+ require 'efo_nelfo/property'
9
+ require 'efo_nelfo/post_type'
10
+
11
+ # EfoNelfo v4.0 modules
12
+ require 'efo_nelfo/v40/order/order'
13
+ require 'efo_nelfo/v40/order/line'
14
+ require 'efo_nelfo/v40/order/text'
15
+
16
+ # Reader modules (import)
17
+ require 'efo_nelfo/reader/csv'
18
+
19
+ module EfoNelfo
20
+
21
+ class << self
22
+
23
+ def load(filename)
24
+ Reader::CSV.new(filename: filename).parse
25
+ end
26
+
27
+ def parse(data)
28
+ Reader::CSV.new(data: data).parse
29
+ end
30
+
31
+ end
32
+
33
+
34
+ end
35
+