rconomic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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