rconomic 0.1.0

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.
Files changed (40) hide show
  1. data/.travis.yml +11 -0
  2. data/Gemfile +8 -0
  3. data/Gemfile.lock +54 -0
  4. data/LICENSE +19 -0
  5. data/README.md +74 -0
  6. data/Rakefile +7 -0
  7. data/lib/economic/current_invoice.rb +78 -0
  8. data/lib/economic/current_invoice_line.rb +47 -0
  9. data/lib/economic/debtor.rb +51 -0
  10. data/lib/economic/economic.wsdl +56858 -0
  11. data/lib/economic/entity.rb +167 -0
  12. data/lib/economic/proxies/current_invoice_line_proxy.rb +28 -0
  13. data/lib/economic/proxies/current_invoice_proxy.rb +38 -0
  14. data/lib/economic/proxies/debtor_proxy.rb +40 -0
  15. data/lib/economic/proxies/entity_proxy.rb +61 -0
  16. data/lib/economic/session.rb +59 -0
  17. data/lib/rconomic/version.rb +3 -0
  18. data/lib/rconomic.rb +26 -0
  19. data/rconomic.gemspec +26 -0
  20. data/spec/economic/current_invoice_line_spec.rb +10 -0
  21. data/spec/economic/current_invoice_spec.rb +59 -0
  22. data/spec/economic/debtor_spec.rb +43 -0
  23. data/spec/economic/entity_spec.rb +179 -0
  24. data/spec/economic/proxies/current_invoice_line_proxy_spec.rb +77 -0
  25. data/spec/economic/proxies/current_invoice_proxy_spec.rb +64 -0
  26. data/spec/economic/proxies/debtor_proxy_spec.rb +74 -0
  27. data/spec/economic/session_spec.rb +64 -0
  28. data/spec/fixtures/connect/success.xml +8 -0
  29. data/spec/fixtures/current_invoice_create_from_data/success.xml +10 -0
  30. data/spec/fixtures/current_invoice_get_data/success.xml +73 -0
  31. data/spec/fixtures/current_invoice_line_get_data/success.xml +39 -0
  32. data/spec/fixtures/debtor_find_by_ci_number/many.xml +15 -0
  33. data/spec/fixtures/debtor_get_data/success.xml +54 -0
  34. data/spec/fixtures/debtor_get_next_available_number/success.xml +8 -0
  35. data/spec/fixtures/spec_entity_create_from_data/success.xml +10 -0
  36. data/spec/fixtures/spec_entity_get_data/success.xml +11 -0
  37. data/spec/fixtures/spec_entity_update_from_data/success.xml +10 -0
  38. data/spec/fixtures/wsdl.xml +56596 -0
  39. data/spec/spec_helper.rb +97 -0
  40. metadata +135 -0
@@ -0,0 +1,167 @@
1
+
2
+
3
+ module Economic
4
+
5
+ class Entity
6
+
7
+ # Internal accessors
8
+ attr_accessor :persisted, :session, :partial
9
+
10
+ class << self
11
+ def properties_not_triggering_full_load
12
+ [:id, :number, :handle]
13
+ end
14
+
15
+ def has_properties(*properties)
16
+ @properties = properties
17
+ properties.each do |property|
18
+ unless properties_not_triggering_full_load.include?(property) || instance_methods.collect(&:to_s).include?(property.to_s)
19
+ # Create property accessors that loads the full Entity from the API if necessary
20
+ define_method "#{property}" do
21
+ value = instance_variable_get("@#{property}")
22
+ if value.nil? && partial? && persisted?
23
+ instance_variable_get("@#{property}")
24
+ else
25
+ value
26
+ end
27
+ end
28
+ end
29
+
30
+ # Just use regular writers
31
+ attr_writer property
32
+ end
33
+ end
34
+
35
+ def properties
36
+ @properties || []
37
+ end
38
+
39
+ # Returns the E-conomic API action name to call
40
+ def soap_action(action)
41
+ class_name = self.name
42
+ class_name_without_modules = class_name.split('::').last
43
+ "#{class_name_without_modules.snakecase}_#{action.to_s.snakecase}".intern
44
+ end
45
+ end
46
+
47
+ def initialize(properties = {})
48
+ initialize_defaults
49
+ update_properties(properties)
50
+ @persisted = false
51
+ @partial = true
52
+ end
53
+
54
+ def initialize_defaults
55
+ nil
56
+ end
57
+
58
+ # Updates Entity with its data from the API
59
+ def get_data
60
+ response = session.request soap_action(:get_data) do
61
+ soap.body = {
62
+ 'entityHandle' => {
63
+ 'Number' => number
64
+ }
65
+ }
66
+ end
67
+ self.update_properties(response)
68
+ self.partial = false
69
+ self.persisted = true
70
+ end
71
+
72
+ # Returns the number of Entity. This does not trigger a load from the API even if Entity is partial
73
+ def number
74
+ @number
75
+ end
76
+
77
+ # Returns the id of Entity. This does not trigger a load from the API even if Entity is partial
78
+ def id
79
+ @id
80
+ end
81
+
82
+ def handle
83
+ handle = {}
84
+ handle[:id] = id unless id.blank?
85
+ handle[:number] = number unless number.blank?
86
+ handle
87
+ end
88
+
89
+ # Returns true if CurrentInvoiceLine has been persisted in e-conomic
90
+ def persisted?
91
+ !!@persisted
92
+ end
93
+
94
+ # Returns true if Entity has not been fully loaded from API yet
95
+ def partial?
96
+ # TODO: Can this be introspected somehow?
97
+ !!@partial
98
+ end
99
+
100
+ def inspect
101
+ props = self.class.properties.collect { |p| "#{p}=#{self.send(p).inspect}" }
102
+ "#<#{self.class}:#{self.object_id} partial=#{partial?}, persisted=#{persisted?}, #{props.join(', ')}>"
103
+ end
104
+
105
+ # Persist the Entity to the API
106
+ def save
107
+ create_or_update
108
+ end
109
+
110
+ # Updates properties of Entity with the values from hash
111
+ def update_properties(hash)
112
+ hash.each do |key, value|
113
+ setter_method = "#{key}="
114
+ if self.respond_to?(setter_method)
115
+ self.send(setter_method, value)
116
+ end
117
+ end
118
+ end
119
+
120
+ protected
121
+
122
+ def create_or_update
123
+ if persisted?
124
+ update
125
+ else
126
+ create
127
+ end
128
+ end
129
+
130
+ def create
131
+ response = session.request soap_action('CreateFromData') do
132
+ soap.body = {'data' => build_soap_data}
133
+ end
134
+
135
+ if response
136
+ @number = response[:number]
137
+ @id = response[:id]
138
+ end
139
+
140
+ @persisted = true
141
+ @partial = false
142
+
143
+ return response
144
+ end
145
+
146
+ def update
147
+ response = session.request soap_action(:update_from_data) do
148
+ soap.body = {'data' => build_soap_data}
149
+ end
150
+
151
+ @persisted = true
152
+ @partial = false
153
+
154
+ return response
155
+ end
156
+
157
+ # Returns OrderedHash with the data structure to send to the API
158
+ def build_soap_data
159
+ end
160
+
161
+ def soap_action(action)
162
+ self.class.soap_action(action)
163
+ end
164
+
165
+ end
166
+
167
+ end
@@ -0,0 +1,28 @@
1
+ require 'economic/proxies/entity_proxy'
2
+
3
+ module Economic
4
+ class CurrentInvoiceLineProxy < EntityProxy
5
+ class << self
6
+ # Returns the class this proxy is a proxy for
7
+ def entity_class
8
+ CurrentInvoiceLine
9
+ end
10
+ end
11
+
12
+ # Returns a new, unpersisted Economic::CurrentInvoiceLine
13
+ def build(properties = {})
14
+ invoice_line = super
15
+ initialize_properties_with_values_from_owner(invoice_line) if owner.is_a?(CurrentInvoice)
16
+ invoice_line
17
+ end
18
+
19
+ private
20
+
21
+ # Initialize properties in invoice_line with values from owner
22
+ def initialize_properties_with_values_from_owner(invoice_line)
23
+ invoice_line.invoice_handle = owner.handle
24
+ invoice_line
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,38 @@
1
+ require 'economic/proxies/entity_proxy'
2
+
3
+ module Economic
4
+ class CurrentInvoiceProxy < EntityProxy
5
+ class << self
6
+ # Returns the class this proxy is a proxy for
7
+ def entity_class
8
+ CurrentInvoice
9
+ end
10
+ end
11
+
12
+ # Returns a new, unpersisted Economic::CurrentInvoice
13
+ def build(properties = {})
14
+ invoice = super
15
+ initialize_properties_with_values_from_owner(invoice) if owner.is_a?(Debtor)
16
+ invoice
17
+ end
18
+
19
+ private
20
+
21
+ # Initialize properties in invoice with values from owner
22
+ def initialize_properties_with_values_from_owner(invoice)
23
+ invoice.debtor_handle = owner.handle
24
+
25
+ invoice.debtor_name ||= owner.name
26
+ invoice.debtor_address ||= owner.address
27
+ invoice.debtor_postal_code ||= owner.postal_code
28
+ invoice.debtor_city ||= owner.city
29
+
30
+ invoice.term_of_payment_handle ||= owner.term_of_payment_handle
31
+ invoice.layout_handle ||= owner.layout_handle
32
+ invoice.currency_handle ||= owner.currency_handle
33
+
34
+ invoice
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,40 @@
1
+ require 'economic/proxies/entity_proxy'
2
+
3
+ module Economic
4
+ class DebtorProxy < EntityProxy
5
+ class << self
6
+ # Returns the class this proxy is a proxy for
7
+ def entity_class
8
+ Debtor
9
+ end
10
+ end
11
+
12
+ # Returns Debtors that have the given ci_number. The Debtor objects will only be partially loaded
13
+ def find_by_ci_number(ci_number)
14
+ # Get a list of DebtorHandles from e-conomic
15
+ response = session.request self.class.entity_class.soap_action('FindByCINumber') do
16
+ soap.body = {
17
+ 'ciNumber' => ci_number
18
+ }
19
+ end
20
+
21
+ # Make sure we always have an array of handles even if the result only contains one
22
+ handles = [response[:debtor_handle]].flatten.reject(&:blank?)
23
+
24
+ # Create partial Debtor entities
25
+ handles.collect do |handle|
26
+ debtor = build
27
+ debtor.partial = true
28
+ debtor.persisted = true
29
+ debtor.handle = handle
30
+ debtor.number = handle[:number]
31
+ debtor
32
+ end
33
+ end
34
+
35
+ # Returns the next available debtor number
36
+ def next_available_number
37
+ session.request Debtor.soap_action(:get_next_available_number)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,61 @@
1
+ module Economic
2
+ class EntityProxy
3
+ class << self
4
+ # Returns the class this Proxy is a proxy for
5
+ def entity_class
6
+ Entity
7
+ end
8
+ end
9
+
10
+ include Enumerable
11
+
12
+ attr_reader :owner, :items
13
+
14
+ def initialize(owner)
15
+ @owner = owner
16
+ @items = []
17
+ end
18
+
19
+ def session
20
+ owner.session
21
+ end
22
+
23
+ # Returns a new, unpersisted Economic::Entity
24
+ def build(properties = {})
25
+ entity = self.class.entity_class.new(:session => session)
26
+
27
+ entity.update_properties(properties)
28
+ entity.partial = false
29
+
30
+ entity
31
+ end
32
+
33
+ # Gets data for Entity from the API
34
+ def find(number)
35
+ entity_hash = session.request self.class.entity_class.soap_action(:get_data) do
36
+ soap.body = {
37
+ 'entityHandle' => {
38
+ 'Number' => number
39
+ }
40
+ }
41
+ end
42
+ entity = build(entity_hash)
43
+ entity.persisted = true
44
+ entity
45
+ end
46
+
47
+ # Add item to proxy
48
+ def <<(item)
49
+ items << item
50
+ end
51
+
52
+ def each
53
+ items.each { |i| yield i }
54
+ end
55
+
56
+ def empty?
57
+ items.empty?
58
+ end
59
+
60
+ end
61
+ end
@@ -0,0 +1,59 @@
1
+ module Economic
2
+ class Session
3
+ attr_accessor :agreement_number, :user_name, :password
4
+
5
+ def initialize(agreement_number, user_name, password)
6
+ self.agreement_number = agreement_number
7
+ self.user_name = user_name
8
+ self.password = password
9
+ end
10
+
11
+ # Returns the Savon::Client used to connect to e-conomic
12
+ def client
13
+ @client ||= Savon::Client.new do
14
+ wsdl.document = File.expand_path(File.join(File.dirname(__FILE__), "economic.wsdl"))
15
+ end
16
+ end
17
+
18
+ # Authenticates with e-conomic
19
+ def connect
20
+ response = client.request :economic, :connect do
21
+ soap.body = {
22
+ :agreementNumber => self.agreement_number,
23
+ :userName => self.user_name,
24
+ :password => self.password,
25
+ :order! => [:agreementNumber, :userName, :password]
26
+ }
27
+ end
28
+ client.http.headers["Cookie"] = response.http.headers["Set-Cookie"]
29
+ end
30
+
31
+ # Provides access to the current invoices - ie invoices that haven't yet been booked
32
+ def current_invoices
33
+ @current_invoices ||= CurrentInvoiceProxy.new(self)
34
+ end
35
+
36
+ # Provides access to the debtors
37
+ def debtors
38
+ @debtors ||= DebtorProxy.new(self)
39
+ end
40
+
41
+ def request(action, &block)
42
+ response = client.request :economic, action, &block
43
+ response_hash = response.to_hash
44
+
45
+ response_key = "#{action}_response".intern
46
+ result_key = "#{action}_result".intern
47
+ if response_hash[response_key] && response_hash[response_key][result_key]
48
+ response_hash[response_key][result_key]
49
+ else
50
+ {}
51
+ end
52
+ end
53
+
54
+ # Returns self - used by proxies to access the session of their owner
55
+ def session
56
+ self
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,3 @@
1
+ module Rconomic
2
+ VERSION = "0.1.0"
3
+ end
data/lib/rconomic.rb ADDED
@@ -0,0 +1,26 @@
1
+ # Dependencies
2
+ require 'time'
3
+ require 'savon'
4
+ require 'active_support/ordered_hash'
5
+
6
+ require 'economic/session'
7
+ require 'economic/debtor'
8
+ require 'economic/current_invoice'
9
+ require 'economic/current_invoice_line'
10
+
11
+ require 'economic/proxies/current_invoice_proxy'
12
+ require 'economic/proxies/current_invoice_line_proxy'
13
+ require 'economic/proxies/debtor_proxy'
14
+
15
+ # http://www.e-conomic.com/apidocs/Documentation/index.html
16
+ # https://www.e-conomic.com/secure/api1/EconomicWebService.asmx
17
+ #
18
+ # TODO
19
+ #
20
+ # * Memoization via ActiveSupport?
21
+ # * Basic validations; ie check for nil values before submitting to API
22
+ # * Better Handle handling
23
+
24
+ module Economic
25
+ end
26
+
data/rconomic.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'rconomic/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'rconomic'
7
+ s.summary = "Wrapper for e-conomic.dk's SOAP API."
8
+ s.description = <<-EOS
9
+ Ruby wrapper for the e-conomic SOAP API, that aims at making working with the API bearable.
10
+
11
+ E-conomic is a web-based accounting system. For their marketing speak, see http://www.e-conomic.co.uk/about/. More details about their API at http://www.e-conomic.co.uk/integration/integration-partner/.
12
+ EOS
13
+ s.authors = ["Jakob Skjerning"]
14
+ s.email = 'jakob@mentalized.net'
15
+ s.homepage = 'https://github.com/lokalebasen/rconomic'
16
+
17
+ s.version = Rconomic::VERSION
18
+ s.platform = Gem::Platform::RUBY
19
+
20
+ s.add_runtime_dependency "savon", "0.9.5"
21
+ s.add_runtime_dependency "activesupport", "~> 3.0"
22
+
23
+ s.files = `git ls-files`.split("\n").reject { |filename| ['.gitignore'].include?(filename) }
24
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
25
+ s.require_paths = ["lib"]
26
+ end
@@ -0,0 +1,10 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe Economic::CurrentInvoiceLine do
4
+ let(:session) { make_session }
5
+ subject { (l = Economic::CurrentInvoiceLine.new).tap { l.session = session } }
6
+
7
+ it "inherits from Economic::Entity" do
8
+ Economic::CurrentInvoiceLine.ancestors.should include(Economic::Entity)
9
+ end
10
+ end
@@ -0,0 +1,59 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe Economic::CurrentInvoice do
4
+ let(:session) { make_session }
5
+ subject { (i = Economic::CurrentInvoice.new).tap { i.session = session } }
6
+
7
+ it "inherits from Economic::Entity" do
8
+ Economic::CurrentInvoice.ancestors.should include(Economic::Entity)
9
+ end
10
+
11
+ describe "new" do
12
+ it "initializes lines as an empty proxy" do
13
+ subject.lines.should be_instance_of(Economic::CurrentInvoiceLineProxy)
14
+ subject.lines.should be_empty
15
+ end
16
+ end
17
+
18
+ describe "save" do
19
+ context "when successful" do
20
+ before :each do
21
+ savon.stubs('CurrentInvoice_CreateFromData').returns(:success)
22
+ end
23
+
24
+ context "when invoice has lines" do
25
+ before :each do
26
+ 2.times do
27
+ line = Economic::CurrentInvoiceLine.new
28
+ line.stubs(:save)
29
+ subject.lines << line
30
+ end
31
+ end
32
+
33
+ it "adds the lines to the invoice" do
34
+ subject.lines.each do |line|
35
+ line.expects(:invoice_handle=).with({:id => '42'})
36
+ end
37
+
38
+ subject.save
39
+ end
40
+
41
+ it "assigns the invoice session to each line" do
42
+ subject.lines.each do |line|
43
+ line.expects(:session=).with(subject.session)
44
+ end
45
+
46
+ subject.save
47
+ end
48
+
49
+ it "saves each line" do
50
+ subject.lines.each do |line|
51
+ line.expects(:save)
52
+ end
53
+
54
+ subject.save
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,43 @@
1
+ require './spec/spec_helper'
2
+
3
+ describe Economic::Debtor do
4
+ let(:session) { make_session }
5
+ subject { Economic::Debtor.new(:session => session) }
6
+
7
+ it "inherits from Economic::Entity" do
8
+ Economic::Debtor.ancestors.should include(Economic::Entity)
9
+ end
10
+
11
+ context "when saving" do
12
+ context "when debtor is new" do
13
+ subject { Economic::Debtor.new(:session => session) }
14
+
15
+ context "when debtor_group_handle is nil" do
16
+ before :each do
17
+ subject.debtor_group_handle = nil
18
+ end
19
+
20
+ it "should send request and let e-conomic return an error" do
21
+ session.expects(:request)
22
+ subject.save
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ describe ".current_invoices" do
29
+ it "returns an CurrentInvoiceProxy" do
30
+ subject.current_invoices.should be_instance_of(Economic::CurrentInvoiceProxy)
31
+ end
32
+
33
+ it "memoizes the proxy" do
34
+ subject.current_invoices.should === subject.current_invoices
35
+ end
36
+
37
+ it "should store the session" do
38
+ subject.session.should_not be_nil
39
+ subject.current_invoices.session.should == subject.session
40
+ end
41
+ end
42
+
43
+ end