fortnox-api 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +3 -2
  3. data/.env.test +3 -0
  4. data/.rubocop.yml +32 -23
  5. data/.travis.yml +3 -2
  6. data/Guardfile +2 -2
  7. data/LICENSE.txt +165 -22
  8. data/README.md +56 -37
  9. data/fortnox-api.gemspec +9 -6
  10. data/lib/fortnox/api.rb +18 -2
  11. data/lib/fortnox/api/base.rb +5 -3
  12. data/lib/fortnox/api/environment_validation.rb +51 -6
  13. data/lib/fortnox/api/mappers.rb +49 -0
  14. data/lib/fortnox/api/mappers/base.rb +47 -0
  15. data/lib/fortnox/api/mappers/base/from_json.rb +78 -0
  16. data/lib/fortnox/api/mappers/base/to_json.rb +65 -0
  17. data/lib/fortnox/api/mappers/customer.rb +27 -0
  18. data/lib/fortnox/api/mappers/default_delivery_types.rb +13 -0
  19. data/lib/fortnox/api/mappers/default_templates.rb +15 -0
  20. data/lib/fortnox/api/mappers/edi_information.rb +22 -0
  21. data/lib/fortnox/api/mappers/email_information.rb +18 -0
  22. data/lib/fortnox/api/mappers/invoice.rb +27 -0
  23. data/lib/fortnox/api/mappers/invoice_row.rb +20 -0
  24. data/lib/fortnox/api/mappers/order.rb +23 -0
  25. data/lib/fortnox/api/mappers/order_row.rb +16 -0
  26. data/lib/fortnox/api/models.rb +2 -0
  27. data/lib/fortnox/api/models/base.rb +56 -13
  28. data/lib/fortnox/api/models/customer.rb +112 -101
  29. data/lib/fortnox/api/models/document_base.rb +189 -0
  30. data/lib/fortnox/api/models/invoice.rb +29 -195
  31. data/lib/fortnox/api/models/label.rb +17 -0
  32. data/lib/fortnox/api/models/order.rb +27 -0
  33. data/lib/fortnox/api/repositories.rb +2 -0
  34. data/lib/fortnox/api/repositories/base.rb +4 -5
  35. data/lib/fortnox/api/repositories/base/loaders.rb +22 -14
  36. data/lib/fortnox/api/repositories/base/savers.rb +30 -16
  37. data/lib/fortnox/api/repositories/customer.rb +3 -25
  38. data/lib/fortnox/api/repositories/invoice.rb +3 -22
  39. data/lib/fortnox/api/repositories/order.rb +16 -0
  40. data/lib/fortnox/api/request_handling.rb +3 -3
  41. data/lib/fortnox/api/types.rb +44 -0
  42. data/lib/fortnox/api/types/default_delivery_types.rb +20 -0
  43. data/lib/fortnox/api/types/default_templates.rb +23 -0
  44. data/lib/fortnox/api/types/defaulted.rb +11 -0
  45. data/lib/fortnox/api/types/document_row.rb +65 -0
  46. data/lib/fortnox/api/types/edi_information.rb +29 -0
  47. data/lib/fortnox/api/types/email_information.rb +26 -0
  48. data/lib/fortnox/api/types/enums.rb +75 -0
  49. data/lib/fortnox/api/types/invoice_row.rb +19 -0
  50. data/lib/fortnox/api/types/model.rb +40 -0
  51. data/lib/fortnox/api/types/nullable.rb +21 -0
  52. data/lib/fortnox/api/types/order_row.rb +16 -0
  53. data/lib/fortnox/api/types/required.rb +13 -0
  54. data/lib/fortnox/api/types/sized.rb +25 -0
  55. data/lib/fortnox/api/version.rb +1 -1
  56. data/spec/fortnox/api/base_spec.rb +85 -14
  57. data/spec/fortnox/api/mappers/base/from_json_spec.rb +70 -0
  58. data/spec/fortnox/api/mappers/base/to_json_spec.rb +76 -0
  59. data/spec/fortnox/api/mappers/base_spec.rb +156 -0
  60. data/spec/fortnox/api/mappers/contexts/json_conversion.rb +56 -0
  61. data/spec/fortnox/api/mappers/customer_spec.rb +25 -0
  62. data/spec/fortnox/api/mappers/default_delivery_types_spec.rb +12 -0
  63. data/spec/fortnox/api/mappers/edi_information_spec.rb +21 -0
  64. data/spec/fortnox/api/mappers/email_information_spec.rb +17 -0
  65. data/spec/fortnox/api/mappers/examples/mapper.rb +26 -0
  66. data/spec/fortnox/api/mappers/invoice_row_spec.rb +19 -0
  67. data/spec/fortnox/api/mappers/invoice_spec.rb +24 -0
  68. data/spec/fortnox/api/mappers/order_row_spec.rb +14 -0
  69. data/spec/fortnox/api/mappers/order_spec.rb +20 -0
  70. data/spec/fortnox/api/models/base_spec.rb +44 -22
  71. data/spec/fortnox/api/models/customer_spec.rb +9 -0
  72. data/spec/fortnox/api/models/examples/document_base.rb +13 -0
  73. data/spec/fortnox/api/models/examples/model.rb +13 -0
  74. data/spec/fortnox/api/models/invoice_spec.rb +7 -31
  75. data/spec/fortnox/api/models/order_spec.rb +13 -0
  76. data/spec/fortnox/api/repositories/customer_spec.rb +20 -76
  77. data/spec/fortnox/api/repositories/examples/all.rb +17 -0
  78. data/spec/fortnox/api/repositories/examples/find.rb +25 -0
  79. data/spec/fortnox/api/repositories/examples/only.rb +42 -0
  80. data/spec/fortnox/api/repositories/examples/save.rb +69 -0
  81. data/spec/fortnox/api/repositories/examples/save_with_nested_model.rb +32 -0
  82. data/spec/fortnox/api/repositories/examples/save_with_specially_named_attribute.rb +27 -0
  83. data/spec/fortnox/api/repositories/examples/search.rb +31 -0
  84. data/spec/fortnox/api/repositories/invoice_spec.rb +36 -5
  85. data/spec/fortnox/api/repositories/order_spec.rb +35 -0
  86. data/spec/fortnox/api/types/account_number_spec.rb +28 -0
  87. data/spec/fortnox/api/types/default_delivery_types_spec.rb +10 -0
  88. data/spec/fortnox/api/types/edi_information_spec.rb +13 -0
  89. data/spec/fortnox/api/types/email_information_spec.rb +13 -0
  90. data/spec/fortnox/api/types/email_spec.rb +29 -0
  91. data/spec/fortnox/api/types/enums_spec.rb +13 -0
  92. data/spec/fortnox/api/types/examples/document_row.rb +15 -0
  93. data/spec/fortnox/api/types/examples/enum.rb +48 -0
  94. data/spec/fortnox/api/types/examples/types.rb +9 -0
  95. data/spec/fortnox/api/types/house_work_types_spec.rb +60 -0
  96. data/spec/fortnox/api/types/invoice_row_spec.rb +9 -0
  97. data/spec/fortnox/api/types/model_spec.rb +56 -0
  98. data/spec/fortnox/api/types/nullable_spec.rb +57 -0
  99. data/spec/fortnox/api/types/order_row_spec.rb +13 -0
  100. data/spec/fortnox/api/types/required_spec.rb +42 -0
  101. data/spec/fortnox/api/types/sized_spec.rb +74 -0
  102. data/spec/fortnox/api_spec.rb +16 -15
  103. data/spec/spec_helper.rb +19 -9
  104. data/spec/support/helpers/dummy_class_helper.rb +19 -0
  105. data/spec/support/helpers/environment_helper.rb +7 -0
  106. data/spec/support/helpers/repository_helper.rb +8 -0
  107. data/spec/support/helpers/when_performing_helper.rb +5 -0
  108. data/spec/support/matchers.rb +1 -1
  109. data/spec/support/matchers/type.rb +17 -0
  110. data/spec/support/matchers/type/attribute_matcher.rb +39 -0
  111. data/spec/support/matchers/type/enum_matcher.rb +21 -0
  112. data/spec/support/matchers/type/have_account_number_matcher.rb +21 -0
  113. data/spec/support/matchers/type/have_country_code_matcher.rb +13 -0
  114. data/spec/support/matchers/type/have_currency_matcher.rb +7 -0
  115. data/spec/support/matchers/type/have_customer_type_matcher.rb +13 -0
  116. data/spec/support/matchers/type/have_default_delivery_type_matcher.rb +7 -0
  117. data/spec/support/matchers/type/have_discount_type_matcher.rb +7 -0
  118. data/spec/support/matchers/type/have_email_matcher.rb +22 -0
  119. data/spec/support/matchers/type/have_house_work_type_matcher.rb +7 -0
  120. data/spec/support/matchers/type/have_nullable_date_matcher.rb +58 -0
  121. data/spec/support/matchers/type/have_nullable_matcher.rb +52 -0
  122. data/spec/support/matchers/type/have_nullable_string_matcher.rb +49 -0
  123. data/spec/support/matchers/type/have_sized_float_matcher.rb +8 -0
  124. data/spec/support/matchers/type/have_sized_integer_matcher.rb +8 -0
  125. data/spec/support/matchers/type/have_sized_string_matcher.rb +35 -0
  126. data/spec/support/matchers/type/have_vat_type_matcher.rb +7 -0
  127. data/spec/support/matchers/type/numeric_matcher.rb +50 -0
  128. data/spec/support/matchers/type/require_attribute_matcher.rb +69 -0
  129. data/spec/support/matchers/type/type_matcher.rb +38 -0
  130. data/spec/vcr_cassettes/customers/all.yml +119 -9
  131. data/spec/vcr_cassettes/customers/find_id_1.yml +8 -9
  132. data/spec/vcr_cassettes/customers/find_new.yml +46 -0
  133. data/spec/vcr_cassettes/customers/save_new.yml +9 -11
  134. data/spec/vcr_cassettes/customers/save_old.yml +9 -12
  135. data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +45 -0
  136. data/spec/vcr_cassettes/customers/search_by_name.yml +66 -0
  137. data/spec/vcr_cassettes/customers/search_miss.yml +45 -0
  138. data/spec/vcr_cassettes/invoices/all.yml +104 -0
  139. data/spec/vcr_cassettes/invoices/filter_hit.yml +46 -0
  140. data/spec/vcr_cassettes/invoices/filter_invalid.yml +42 -0
  141. data/spec/vcr_cassettes/invoices/find_id_1.yml +47 -0
  142. data/spec/vcr_cassettes/invoices/find_new.yml +49 -0
  143. data/spec/vcr_cassettes/invoices/save_new.yml +48 -0
  144. data/spec/vcr_cassettes/invoices/save_old.yml +49 -0
  145. data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +47 -0
  146. data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +47 -0
  147. data/spec/vcr_cassettes/invoices/search_by_name.yml +48 -0
  148. data/spec/vcr_cassettes/invoices/search_miss.yml +45 -0
  149. data/spec/vcr_cassettes/orders/all.yml +144 -0
  150. data/spec/vcr_cassettes/orders/filter_hit.yml +48 -0
  151. data/spec/vcr_cassettes/orders/filter_invalid.yml +42 -0
  152. data/spec/vcr_cassettes/orders/find_id_1.yml +48 -0
  153. data/spec/vcr_cassettes/orders/find_new.yml +49 -0
  154. data/spec/vcr_cassettes/orders/house_work_type_babysitting.yml +47 -0
  155. data/spec/vcr_cassettes/orders/house_work_type_cleaning.yml +47 -0
  156. data/spec/vcr_cassettes/orders/house_work_type_construction.yml +47 -0
  157. data/spec/vcr_cassettes/orders/house_work_type_cooking.yml +43 -0
  158. data/spec/vcr_cassettes/orders/house_work_type_electricity.yml +47 -0
  159. data/spec/vcr_cassettes/orders/house_work_type_gardening.yml +47 -0
  160. data/spec/vcr_cassettes/orders/house_work_type_glassmetalwork.yml +47 -0
  161. data/spec/vcr_cassettes/orders/house_work_type_grounddrainagework.yml +47 -0
  162. data/spec/vcr_cassettes/orders/house_work_type_hvac.yml +47 -0
  163. data/spec/vcr_cassettes/orders/house_work_type_masonry.yml +47 -0
  164. data/spec/vcr_cassettes/orders/house_work_type_othercare.yml +47 -0
  165. data/spec/vcr_cassettes/orders/house_work_type_othercosts.yml +47 -0
  166. data/spec/vcr_cassettes/orders/house_work_type_paintingwallpapering.yml +47 -0
  167. data/spec/vcr_cassettes/orders/house_work_type_snowplowing.yml +47 -0
  168. data/spec/vcr_cassettes/orders/house_work_type_textileclothing.yml +47 -0
  169. data/spec/vcr_cassettes/orders/house_work_type_tutoring.yml +43 -0
  170. data/spec/vcr_cassettes/orders/save_new.yml +48 -0
  171. data/spec/vcr_cassettes/orders/save_old.yml +49 -0
  172. data/spec/vcr_cassettes/orders/save_with_nested_model.yml +47 -0
  173. data/spec/vcr_cassettes/orders/search_by_name.yml +47 -0
  174. data/spec/vcr_cassettes/orders/search_miss.yml +45 -0
  175. metadata +301 -71
  176. data/lib/fortnox/api/models/attributes/country_code.rb +0 -17
  177. data/lib/fortnox/api/models/attributes/currency.rb +0 -17
  178. data/lib/fortnox/api/models/edi_information.rb +0 -28
  179. data/lib/fortnox/api/models/email_information.rb +0 -25
  180. data/lib/fortnox/api/models/row.rb +0 -82
  181. data/lib/fortnox/api/repositories/base/json_convertion.rb +0 -68
  182. data/lib/fortnox/api/repositories/base/options.rb +0 -33
  183. data/lib/fortnox/api/validators.rb +0 -1
  184. data/lib/fortnox/api/validators/attributes/country_code.rb +0 -42
  185. data/lib/fortnox/api/validators/attributes/currency.rb +0 -38
  186. data/lib/fortnox/api/validators/base.rb +0 -70
  187. data/lib/fortnox/api/validators/constant.rb +0 -21
  188. data/lib/fortnox/api/validators/customer.rb +0 -29
  189. data/lib/fortnox/api/validators/edi_information.rb +0 -11
  190. data/lib/fortnox/api/validators/email_information.rb +0 -19
  191. data/lib/fortnox/api/validators/invoice.rb +0 -33
  192. data/lib/fortnox/api/validators/row.rb +0 -22
  193. data/spec/fortnox/api/models/attributes/country_code_spec.rb +0 -23
  194. data/spec/fortnox/api/models/attributes/currency_spec.rb +0 -23
  195. data/spec/fortnox/api/models/attributes/dummy_model_context.rb +0 -9
  196. data/spec/fortnox/api/models/row_spec.rb +0 -13
  197. data/spec/fortnox/api/repositories/context.rb +0 -10
  198. data/spec/fortnox/api/repositories/examples.rb +0 -16
  199. data/spec/fortnox/api/validators/attributes/country_code_spec.rb +0 -9
  200. data/spec/fortnox/api/validators/attributes/currency_spec.rb +0 -9
  201. data/spec/fortnox/api/validators/attributes/examples_for_validate.rb +0 -29
  202. data/spec/fortnox/api/validators/base_spec.rb +0 -61
  203. data/spec/fortnox/api/validators/constant_spec.rb +0 -12
  204. data/spec/fortnox/api/validators/context.rb +0 -102
  205. data/spec/fortnox/api/validators/customer_spec.rb +0 -31
  206. data/spec/fortnox/api/validators/edi_information_spec.rb +0 -18
  207. data/spec/fortnox/api/validators/email_information_spec.rb +0 -26
  208. data/spec/fortnox/api/validators/invoice_spec.rb +0 -36
  209. data/spec/fortnox/api/validators/row_spec.rb +0 -27
  210. data/spec/fortnox/api/validators/validator_examples.rb +0 -20
  211. data/spec/support/matchers/models.rb +0 -27
  212. data/spec/support/matchers/validators.rb +0 -36
@@ -6,12 +6,12 @@ require 'fortnox/api/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "fortnox-api"
8
8
  spec.version = Fortnox::API::VERSION
9
- spec.authors = ["Jonas Schubert Erlandsson"]
9
+ spec.authors = ["Jonas Schubert Erlandsson", "Hannes Elvemyr"]
10
10
  spec.email = ["jonas.schubert.erlandsson@my-codeworks.com"]
11
11
  spec.summary = "Gem to use Fortnox REST API in Ruby."
12
12
  spec.description = "This gem uses the HTTParty library to abstract away the REST calls. It gives you access to a number of objects that behave a lot like ActiveRecord instances, giving you access to methods like `all`, `find`, `find_by_...` and so on. And each individual instance can be easaly persistable to Fortnox again using the `save` method."
13
13
  spec.homepage = "http://github.com/my-codeworks/fortnox-api"
14
- spec.license = "MIT"
14
+ spec.license = "LGPL-3.0"
15
15
 
16
16
  spec.files = `git ls-files -z`.split("\x0")
17
17
  spec.executables = spec.files.grep(%r{^bin/}){ |f| File.basename(f) }
@@ -20,17 +20,20 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "httparty", "~> 0.13"
22
22
  spec.add_dependency "dotenv", "~> 2.0"
23
- spec.add_dependency "virtus", "~> 1.0"
24
- spec.add_dependency "vanguard", "~> 0.0"
23
+ spec.add_dependency "dry-struct", "~> 0.1"
24
+ spec.add_dependency "dry-types", "~> 0.8"
25
25
 
26
26
  spec.add_development_dependency "bundler", "~> 1.9"
27
27
  spec.add_development_dependency "rake", "~> 10.4"
28
28
  spec.add_development_dependency "rspec", "~> 3.2"
29
+ spec.add_development_dependency "rspec-collection_matchers", "~> 0"
29
30
  spec.add_development_dependency "guard", "~> 2.12"
30
31
  spec.add_development_dependency "guard-rspec", "~> 4.5"
31
32
  spec.add_development_dependency "webmock", "~> 1.21"
32
33
  spec.add_development_dependency "vcr", "~> 3.0"
33
- spec.add_development_dependency "pry"
34
- spec.add_development_dependency "codeclimate-test-reporter"
34
+ spec.add_development_dependency "pry", "~> 0"
35
+ spec.add_development_dependency "codeclimate-test-reporter", "~> 0"
36
+ spec.add_development_dependency "rubocop", "~> 0.41"
37
+ spec.add_development_dependency "rubocop-rspec", "~> 1.7"
35
38
 
36
39
  end
@@ -1,28 +1,44 @@
1
1
  require "set"
2
2
  require "dotenv"
3
+ require "dry-struct"
3
4
  require "fortnox/api/base"
4
5
  require "fortnox/api/version"
6
+ require 'logger'
5
7
 
6
8
  Dotenv.load unless ENV['RUBY_ENV'] == 'test'
7
9
 
8
10
  module Fortnox
9
11
  module API
10
12
 
11
- class RemoteServerError < ::RuntimeError
13
+ class Exception < StandardError
14
+ end
15
+
16
+ class AttributeError < Fortnox::API::Exception
17
+ end
18
+
19
+ class RemoteServerError < Fortnox::API::Exception
20
+ end
21
+
22
+ class MissingAttributeError < Fortnox::API::Exception
12
23
  end
13
24
 
14
25
  class << self
15
26
  @debugging = false
27
+ @logger = Logger.new(STDOUT)
16
28
  attr_accessor :debugging
29
+ attr_accessor :logger
30
+
31
+ @logger.level = Logger::WARN
17
32
  end
18
33
 
19
34
  def self.get_access_token
20
35
  Base.get_access_token
21
36
  end
22
37
 
38
+ Registry = Dry::Container.new
23
39
  end
24
40
  end
25
41
 
26
42
  require "fortnox/api/models"
27
43
  require "fortnox/api/repositories"
28
- require "fortnox/api/validators"
44
+ require "fortnox/api/mappers"
@@ -17,9 +17,9 @@ module Fortnox
17
17
  DEFAULT_HEADERS = {
18
18
  'Content-Type' => 'application/json',
19
19
  'Accept' => 'application/json',
20
- }
20
+ }.freeze
21
21
 
22
- HTTP_METHODS = [ :get, :put, :post, :delete ]
22
+ HTTP_METHODS = [ :get, :put, :post, :delete ].freeze
23
23
 
24
24
  attr_accessor :headers
25
25
 
@@ -28,12 +28,14 @@ module Fortnox
28
28
 
29
29
  self.headers = DEFAULT_HEADERS.merge({
30
30
  'Client-Secret' => get_client_secret,
31
- 'Access-Token' => get_access_token,
32
31
  })
32
+
33
+ check_access_tokens!
33
34
  end
34
35
 
35
36
  HTTP_METHODS.each do |method|
36
37
  define_method method do |*args|
38
+ self.headers['Access-Token'] = get_access_token
37
39
  execute do |remote|
38
40
  remote.send( method, *args )
39
41
  end
@@ -1,33 +1,78 @@
1
+ require 'forwardable'
2
+
1
3
  module Fortnox
2
4
  module API
3
5
  module EnvironmentValidation
4
6
 
7
+ class MissingEnvironmentVariable < ArgumentError
8
+ end
9
+
10
+ class CircularQueue
11
+ extend Forwardable
12
+
13
+ def initialize *items
14
+ @queue = [ *items ]
15
+ @@next_index = random_start_index
16
+ end
17
+
18
+ # support some general Array methods that fit Queues well
19
+ def_delegators :@queue, :new, :[], :size
20
+
21
+ def next
22
+ value = @queue[ @@next_index ]
23
+ if @@next_index == size - 1
24
+ @@next_index = 0
25
+ else
26
+ @@next_index += 1
27
+ end
28
+ return value
29
+ end
30
+
31
+ private
32
+
33
+ def random_start_index
34
+ Random.rand(@queue.size)
35
+ end
36
+ end
37
+
5
38
  private
6
39
 
7
40
  def get_base_url
8
41
  base_url = ENV['FORTNOX_API_BASE_URL']
9
- fail ArgumentError, 'You have to provide a base url.' unless base_url
42
+ fail MissingEnvironmentVariable, 'You have to provide a base url.' unless base_url
10
43
  base_url
11
44
  end
12
45
 
13
46
  def get_client_secret
14
47
  client_secret = ENV['FORTNOX_API_CLIENT_SECRET']
15
- fail ArgumentError, 'You have to provide your client secret.' unless client_secret
48
+ fail MissingEnvironmentVariable, 'You have to provide your client secret.' unless client_secret
16
49
  client_secret
17
50
  end
18
51
 
19
52
  def get_access_token
20
- access_token = ENV['FORTNOX_API_ACCESS_TOKEN']
21
- fail ArgumentError, 'You have to provide your access token.' unless access_token
22
- access_token
53
+ @access_tokens ||= load_access_tokens
54
+ @access_tokens.next
23
55
  end
24
56
 
25
57
  def get_authorization_code
26
58
  authorization_code = ENV['FORTNOX_API_AUTHORIZATION_CODE']
27
- fail ArgumentError, 'You have to provide your access token.' unless authorization_code
59
+ fail MissingEnvironmentVariable, 'You have to provide your authorization code.' unless authorization_code
28
60
  authorization_code
29
61
  end
30
62
 
63
+ def load_access_tokens
64
+ check_access_tokens!
65
+ access_token_string = ENV['FORTNOX_API_ACCESS_TOKEN']
66
+ access_tokens = access_token_string.split(",").map(&:strip)
67
+ CircularQueue.new( *access_tokens )
68
+ end
69
+
70
+ def check_access_tokens!
71
+ unless ENV['FORTNOX_API_ACCESS_TOKEN']
72
+ fail MissingEnvironmentVariable, 'You have to provide your access token.'
73
+ end
74
+ end
75
+
31
76
  end
32
77
  end
33
78
  end
@@ -0,0 +1,49 @@
1
+ require 'fortnox/api/types'
2
+
3
+ module Fortnox
4
+ module API
5
+ module Mapper
6
+ Identity = ->(value){ value }
7
+
8
+ Date = ->(value){ value.to_s }
9
+
10
+ Array = ->(array) do
11
+ array.each_with_object( [] ) do |item, converted_array|
12
+ name = Fortnox::API::Mapper::Base.canonical_name_sym( item )
13
+ converted_array << Fortnox::API::Registry[ name ].call( item )
14
+ end
15
+ end
16
+
17
+ Registry.register( :fixnum, Fortnox::API::Mapper::Identity )
18
+ Registry.register( :int, Fortnox::API::Mapper::Identity )
19
+ Registry.register( :float, Fortnox::API::Mapper::Identity )
20
+ Registry.register( :string, Fortnox::API::Mapper::Identity )
21
+ Registry.register( :boolean, Fortnox::API::Mapper::Identity )
22
+ Registry.register( :falseclass, Fortnox::API::Mapper::Identity )
23
+ Registry.register( :trueclass, Fortnox::API::Mapper::Identity )
24
+ Registry.register( :nilclass, Fortnox::API::Mapper::Identity )
25
+ Registry.register( :array, Fortnox::API::Mapper::Array )
26
+ Registry.register( :date, Fortnox::API::Mapper::Date )
27
+
28
+ Registry.register( :account_number, Fortnox::API::Mapper::Identity )
29
+ Registry.register( :country_code, Fortnox::API::Mapper::Identity )
30
+ Registry.register( :currency, Fortnox::API::Mapper::Identity )
31
+ Registry.register( :customer_type, Fortnox::API::Mapper::Identity )
32
+ Registry.register( :discount_type, Fortnox::API::Mapper::Identity )
33
+ Registry.register( :email, Fortnox::API::Mapper::Identity )
34
+ Registry.register( :house_work_type, Fortnox::API::Mapper::Identity )
35
+ Registry.register( :vat_type, Fortnox::API::Mapper::Identity )
36
+ Registry.register( :labels, Fortnox::API::Mapper::Array )
37
+ end
38
+ end
39
+ end
40
+
41
+ require 'fortnox/api/mappers/customer'
42
+ require 'fortnox/api/mappers/default_delivery_types'
43
+ require 'fortnox/api/mappers/default_templates'
44
+ require 'fortnox/api/mappers/edi_information'
45
+ require 'fortnox/api/mappers/email_information'
46
+ require 'fortnox/api/mappers/invoice_row'
47
+ require 'fortnox/api/mappers/invoice'
48
+ require 'fortnox/api/mappers/order_row'
49
+ require 'fortnox/api/mappers/order'
@@ -0,0 +1,47 @@
1
+ require "fortnox/api/mappers/base/from_json"
2
+ require "fortnox/api/mappers/base/to_json"
3
+
4
+ module Fortnox
5
+ module API
6
+ module Mapper
7
+ class Base
8
+ include FromJSON
9
+ include ToJSON
10
+
11
+ Hash = ->(hash) do
12
+ hash.each do |key, value|
13
+ name = Fortnox::API::Mapper::Base.canonical_name_sym( value )
14
+ hash[key] = Fortnox::API::Registry[ name ].call( value )
15
+ end
16
+ end
17
+
18
+ Registry.register( :hash, Fortnox::API::Mapper::Base::Hash )
19
+
20
+ def self.canonical_name_sym( *values )
21
+ klass = if values.empty?
22
+ self
23
+ elsif values.first.is_a? Class
24
+ values.first
25
+ else
26
+ values.first.class
27
+ end
28
+
29
+ klass.name.split('::').last.downcase.to_sym
30
+ end
31
+
32
+ def diff( entity_hash, parent_hash )
33
+ hash_diff( entity_hash[self.class::JSON_ENTITY_WRAPPER],
34
+ parent_hash[self.class::JSON_ENTITY_WRAPPER] )
35
+ end
36
+
37
+ private
38
+
39
+ def hash_diff( hash1, hash2 )
40
+ hash1.dup.
41
+ delete_if{ |k, v| hash2[k] == v }.
42
+ merge!(hash2.dup.delete_if{ |k, _| hash1.has_key?(k) })
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,78 @@
1
+ module Fortnox
2
+ module API
3
+ module Mapper
4
+ module FromJSON
5
+
6
+ class MissingModelOrMapperException < StandardError
7
+ end
8
+
9
+ def wrapped_json_collection_to_entities_hash( json_collection_hash )
10
+ entities_hash = []
11
+ json_collection_hash[ self.class::JSON_COLLECTION_WRAPPER ].each do |json_hash|
12
+ entities_hash << json_hash_to_entity_hash( json_hash, self.class::KEY_MAP )
13
+ end
14
+
15
+ entities_hash
16
+ end
17
+
18
+ def wrapped_json_hash_to_entity_hash( json_entity_hash )
19
+ json_hash_to_entity_hash( json_entity_hash[self.class::JSON_ENTITY_WRAPPER],
20
+ self.class::KEY_MAP )
21
+ end
22
+
23
+ private
24
+
25
+ def json_hash_to_entity_hash( hash, key_map )
26
+ hash.each_with_object( {} ) do |(key, value), json_hash|
27
+ converted_key = convert_key_from_json( key, key_map )
28
+ json_hash[converted_key] =
29
+ if value.respond_to?(:each)
30
+ convert_collection( key, value )
31
+ else
32
+ value
33
+ end
34
+ end
35
+ end
36
+
37
+ def convert_collection( key, collection )
38
+ mapper_name = key.downcase
39
+ if Registry.key?(mapper_name)
40
+ convert_nested_data( mapper_name, key, collection )
41
+ else
42
+ # NOTE: This probably means this is a nested model that we have
43
+ # not implemented yet, or that is missing a mapper.
44
+ # Raise exception during test run if this happens so that we can
45
+ # add it before a new release.
46
+
47
+ raise MissingModelOrMapperException, "for #{ key } (#{ mapper_name }, #{ Fortnox::API::Mapper::DefaultTemplates.canonical_name_sym }) with #{ collection }" if ENV['RUBY_ENV']
48
+ Fortnox::API.logger.warn( "Missing Model or Mapper implementation for #{ key } with attributes: #{ collection }" )
49
+ return convert_hash_keys_from_json_format( collection, {} )
50
+ end
51
+ end
52
+
53
+ def convert_nested_data( _mapper_name, key, nested_data )
54
+ nested_mapper = Registry[ key.downcase ]
55
+ if nested_data.is_a?(::Array) # Array of nested models
56
+ return nested_data.each_with_object( [] ) do |value, nested_models|
57
+ nested_models << json_hash_to_entity_hash( value, nested_mapper::KEY_MAP )
58
+ end
59
+ else # Assume Hash of nested model
60
+ return json_hash_to_entity_hash( nested_data, nested_mapper::KEY_MAP )
61
+ end
62
+ end
63
+
64
+ def convert_key_from_json( key, key_map )
65
+ key_map.key( key ) || default_key_from_json_transform( key )
66
+ end
67
+
68
+ def default_key_from_json_transform( key )
69
+ key = key.to_s
70
+ unless key =~ /\A[A-Z]+\z/
71
+ key = key.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').gsub(/([a-z])([A-Z])/, '\1_\2')
72
+ end
73
+ key.downcase.to_sym
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,65 @@
1
+ module Fortnox
2
+ module API
3
+ module Mapper
4
+ module ToJSON
5
+ def self.included(base)
6
+ base.instance_eval do
7
+
8
+ # PUBLIC
9
+
10
+ def call( entity, keys_to_filter = {} )
11
+ entity_hash = entity.to_hash
12
+ clean_entity_hash = sanitise( entity_hash, keys_to_filter )
13
+ clean_entity_hash = convert_hash_keys_to_json_format( clean_entity_hash )
14
+ Registry[:hash].call( clean_entity_hash )
15
+ end
16
+
17
+ # PRIVATE
18
+
19
+ def convert_hash_keys_to_json_format( hash )
20
+ hash.each_with_object( {} ) do |(key, value), json_hash|
21
+ json_hash[ convert_key_to_json( key ) ] = value
22
+ end
23
+ end
24
+
25
+ def convert_key_to_json( key )
26
+ self::KEY_MAP.fetch( key ){ default_key_to_json_transform( key ) }
27
+ end
28
+
29
+ def default_key_to_json_transform( key )
30
+ key.to_s.split('_').map(&:capitalize).join('')
31
+ end
32
+
33
+ def sanitise( hash, keys_to_filter )
34
+ hash.select do |key, value|
35
+ next false if keys_to_filter.include?( key )
36
+ value != nil
37
+ end
38
+ end
39
+
40
+ private_class_method :convert_hash_keys_to_json_format,
41
+ :convert_key_to_json,
42
+ :default_key_to_json_transform,
43
+ :sanitise
44
+ end
45
+ end
46
+
47
+ def entity_to_hash( entity, keys_to_filter )
48
+ entity_json_hash = Registry[ mapper_name_for( entity ) ]
49
+ .call( entity, keys_to_filter )
50
+ wrapp_entity_json_hash( entity_json_hash )
51
+ end
52
+
53
+ def wrapp_entity_json_hash( entity_json_hash )
54
+ { self.class::JSON_ENTITY_WRAPPER => entity_json_hash }
55
+ end
56
+
57
+ private
58
+
59
+ def mapper_name_for( value )
60
+ value.class.name.split('::').last.downcase.to_sym
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,27 @@
1
+ require "fortnox/api/mappers/base"
2
+
3
+ module Fortnox
4
+ module API
5
+ module Mapper
6
+ class Customer < Fortnox::API::Mapper::Base
7
+
8
+ KEY_MAP = {
9
+ vat_type: 'VATType',
10
+ vat_number: 'VATNumber',
11
+ email_invoice_bcc: 'EmailInvoiceBCC',
12
+ email_invoice_cc: 'EmailInvoiceCC',
13
+ email_offer_bcc: 'EmailOfferBCC',
14
+ email_offer_cc: 'EmailOfferCC',
15
+ email_order_bcc: 'EmailOrderBCC',
16
+ email_order_cc: 'EmailOrderCC',
17
+ show_price_vat_included: 'ShowPriceVATIncluded',
18
+ }.freeze
19
+ JSON_ENTITY_WRAPPER = 'Customer'.freeze
20
+ JSON_COLLECTION_WRAPPER = 'Customers'.freeze
21
+
22
+ end
23
+
24
+ Registry.register( Customer.canonical_name_sym, Customer )
25
+ end
26
+ end
27
+ end