fortnox-api 0.7.2 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (255) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +1 -0
  3. data/.env.template +7 -0
  4. data/.env.test +11 -3
  5. data/.gitignore +7 -1
  6. data/.rubocop.yml +18 -2
  7. data/.tool-versions +1 -0
  8. data/.travis.yml +15 -19
  9. data/CHANGELOG.md +66 -1
  10. data/CONTRIBUTE.md +21 -9
  11. data/DEVELOPER_README.md +72 -0
  12. data/Guardfile +13 -4
  13. data/README.md +226 -61
  14. data/Rakefile +133 -0
  15. data/bin/get_tokens +79 -0
  16. data/bin/renew_tokens +28 -0
  17. data/fortnox-api.gemspec +31 -25
  18. data/lib/fortnox/api/mappers/article.rb +1 -1
  19. data/lib/fortnox/api/mappers/base/from_json.rb +7 -6
  20. data/lib/fortnox/api/mappers/base/to_json.rb +4 -5
  21. data/lib/fortnox/api/mappers/base.rb +3 -3
  22. data/lib/fortnox/api/mappers/customer.rb +1 -1
  23. data/lib/fortnox/api/mappers/default_delivery_types.rb +1 -1
  24. data/lib/fortnox/api/mappers/default_templates.rb +1 -1
  25. data/lib/fortnox/api/mappers/edi_information.rb +1 -1
  26. data/lib/fortnox/api/mappers/email_information.rb +1 -1
  27. data/lib/fortnox/api/mappers/invoice.rb +4 -4
  28. data/lib/fortnox/api/mappers/invoice_row.rb +1 -1
  29. data/lib/fortnox/api/mappers/order.rb +4 -4
  30. data/lib/fortnox/api/mappers/order_row.rb +1 -1
  31. data/lib/fortnox/api/mappers/project.rb +1 -1
  32. data/lib/fortnox/api/mappers/terms_of_payment.rb +1 -1
  33. data/lib/fortnox/api/mappers/unit.rb +1 -1
  34. data/lib/fortnox/api/mappers/value/country_string.rb +1 -1
  35. data/lib/fortnox/api/mappers.rb +18 -18
  36. data/lib/fortnox/api/models/article.rb +2 -2
  37. data/lib/fortnox/api/models/base.rb +23 -21
  38. data/lib/fortnox/api/models/customer.rb +57 -57
  39. data/lib/fortnox/api/models/document.rb +5 -2
  40. data/lib/fortnox/api/models/invoice.rb +2 -2
  41. data/lib/fortnox/api/models/label.rb +3 -3
  42. data/lib/fortnox/api/models/order.rb +2 -2
  43. data/lib/fortnox/api/models/project.rb +2 -2
  44. data/lib/fortnox/api/models/terms_of_payment.rb +2 -2
  45. data/lib/fortnox/api/models/unit.rb +2 -2
  46. data/lib/fortnox/api/models.rb +7 -7
  47. data/lib/fortnox/api/repositories/article.rb +3 -3
  48. data/lib/fortnox/api/repositories/authentication.rb +61 -0
  49. data/lib/fortnox/api/repositories/base/savers.rb +3 -1
  50. data/lib/fortnox/api/repositories/base.rb +25 -38
  51. data/lib/fortnox/api/repositories/customer.rb +3 -3
  52. data/lib/fortnox/api/repositories/invoice.rb +3 -3
  53. data/lib/fortnox/api/repositories/order.rb +3 -3
  54. data/lib/fortnox/api/repositories/project.rb +3 -3
  55. data/lib/fortnox/api/repositories/terms_of_payment.rb +3 -3
  56. data/lib/fortnox/api/repositories/unit.rb +3 -3
  57. data/lib/fortnox/api/repositories.rb +8 -7
  58. data/lib/fortnox/api/request_handling.rb +30 -18
  59. data/lib/fortnox/api/types/default_delivery_types.rb +0 -2
  60. data/lib/fortnox/api/types/default_templates.rb +0 -2
  61. data/lib/fortnox/api/types/document_row.rb +3 -3
  62. data/lib/fortnox/api/types/edi_information.rb +0 -2
  63. data/lib/fortnox/api/types/email_information.rb +0 -2
  64. data/lib/fortnox/api/types/enums.rb +54 -10
  65. data/lib/fortnox/api/types/invoice_row.rb +1 -1
  66. data/lib/fortnox/api/types/model.rb +5 -9
  67. data/lib/fortnox/api/types/nullable.rb +13 -9
  68. data/lib/fortnox/api/types/order_row.rb +1 -1
  69. data/lib/fortnox/api/types/required.rb +3 -3
  70. data/lib/fortnox/api/types/sized.rb +4 -4
  71. data/lib/fortnox/api/types.rb +37 -24
  72. data/lib/fortnox/api/version.rb +1 -1
  73. data/lib/fortnox/api.rb +21 -39
  74. data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +13 -11
  75. data/spec/fortnox/api/mappers/base/from_json_spec.rb +10 -12
  76. data/spec/fortnox/api/mappers/base/to_json_spec.rb +48 -57
  77. data/spec/fortnox/api/mappers/base_spec.rb +4 -7
  78. data/spec/fortnox/api/mappers/contexts/json_conversion.rb +38 -33
  79. data/spec/fortnox/api/mappers/default_delivery_types_spec.rb +1 -1
  80. data/spec/fortnox/api/mappers/examples/mapper.rb +1 -1
  81. data/spec/fortnox/api/mappers/unit_spec.rb +3 -4
  82. data/spec/fortnox/api/models/base_spec.rb +33 -22
  83. data/spec/fortnox/api/models/unit_spec.rb +5 -3
  84. data/spec/fortnox/api/repositories/article_spec.rb +14 -9
  85. data/spec/fortnox/api/repositories/authentication_spec.rb +103 -0
  86. data/spec/fortnox/api/repositories/base_spec.rb +105 -326
  87. data/spec/fortnox/api/repositories/customer_spec.rb +37 -7
  88. data/spec/fortnox/api/repositories/examples/all.rb +0 -1
  89. data/spec/fortnox/api/repositories/examples/find.rb +5 -8
  90. data/spec/fortnox/api/repositories/examples/only.rb +4 -13
  91. data/spec/fortnox/api/repositories/examples/save.rb +32 -18
  92. data/spec/fortnox/api/repositories/examples/save_with_nested_model.rb +0 -5
  93. data/spec/fortnox/api/repositories/examples/save_with_specially_named_attribute.rb +1 -4
  94. data/spec/fortnox/api/repositories/examples/search.rb +4 -7
  95. data/spec/fortnox/api/repositories/invoice_spec.rb +72 -29
  96. data/spec/fortnox/api/repositories/order_spec.rb +11 -9
  97. data/spec/fortnox/api/repositories/project_spec.rb +7 -6
  98. data/spec/fortnox/api/repositories/terms_of_payment_spec.rb +9 -7
  99. data/spec/fortnox/api/repositories/unit_spec.rb +13 -11
  100. data/spec/fortnox/api/types/country_spec.rb +1 -1
  101. data/spec/fortnox/api/types/email_spec.rb +2 -2
  102. data/spec/fortnox/api/types/enums_spec.rb +1 -0
  103. data/spec/fortnox/api/types/examples/document_row.rb +3 -3
  104. data/spec/fortnox/api/types/examples/enum.rb +4 -4
  105. data/spec/fortnox/api/types/examples/types.rb +1 -3
  106. data/spec/fortnox/api/types/housework_types_spec.rb +124 -43
  107. data/spec/fortnox/api/types/model_spec.rb +13 -23
  108. data/spec/fortnox/api/types/nullable_spec.rb +30 -10
  109. data/spec/fortnox/api/types/order_row_spec.rb +2 -2
  110. data/spec/fortnox/api/types/required_spec.rb +7 -15
  111. data/spec/fortnox/api/types/sales_account_spec.rb +57 -0
  112. data/spec/fortnox/api_spec.rb +19 -124
  113. data/spec/spec_helper.rb +0 -14
  114. data/spec/support/helpers/configuration_helper.rb +30 -3
  115. data/spec/support/helpers.rb +1 -1
  116. data/spec/support/matchers/type/attribute_matcher.rb +2 -2
  117. data/spec/support/matchers/type/enum_matcher.rb +1 -1
  118. data/spec/support/matchers/type/have_account_number_matcher.rb +1 -1
  119. data/spec/support/matchers/type/have_email_matcher.rb +1 -1
  120. data/spec/support/matchers/type/have_nullable_date_matcher.rb +7 -5
  121. data/spec/support/matchers/type/have_nullable_matcher.rb +1 -1
  122. data/spec/support/matchers/type/have_nullable_string_matcher.rb +5 -5
  123. data/spec/support/matchers/type/require_attribute_matcher.rb +5 -5
  124. data/spec/support/matchers/type/type_matcher.rb +1 -1
  125. data/spec/support/vcr_setup.rb +16 -0
  126. data/spec/vcr_cassettes/articles/all.yml +41 -42
  127. data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +36 -19
  128. data/spec/vcr_cassettes/articles/find_failure.yml +36 -19
  129. data/spec/vcr_cassettes/articles/find_id_1.yml +38 -20
  130. data/spec/vcr_cassettes/articles/find_new.yml +39 -22
  131. data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +38 -21
  132. data/spec/vcr_cassettes/articles/save_new.yml +37 -19
  133. data/spec/vcr_cassettes/articles/save_old.yml +39 -22
  134. data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +37 -19
  135. data/spec/vcr_cassettes/articles/search_by_name.yml +41 -21
  136. data/spec/vcr_cassettes/articles/search_miss.yml +36 -19
  137. data/spec/vcr_cassettes/articles/search_with_special_char.yml +36 -19
  138. data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +38 -32
  139. data/spec/vcr_cassettes/authentication/expired_token.yml +54 -0
  140. data/spec/vcr_cassettes/authentication/invalid_authorization.yml +57 -0
  141. data/spec/vcr_cassettes/authentication/invalid_refresh_token.yml +58 -0
  142. data/spec/vcr_cassettes/authentication/valid_request.yml +63 -0
  143. data/spec/vcr_cassettes/customers/all.yml +44 -132
  144. data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +36 -19
  145. data/spec/vcr_cassettes/customers/find_failure.yml +36 -19
  146. data/spec/vcr_cassettes/customers/find_id_1.yml +39 -21
  147. data/spec/vcr_cassettes/customers/find_new.yml +38 -21
  148. data/spec/vcr_cassettes/customers/find_with_sales_account.yml +63 -0
  149. data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +38 -21
  150. data/spec/vcr_cassettes/customers/save_new.yml +36 -18
  151. data/spec/vcr_cassettes/customers/save_new_with_country_code_SE.yml +32 -24
  152. data/spec/vcr_cassettes/customers/save_new_with_sales_account.yml +63 -0
  153. data/spec/vcr_cassettes/customers/save_old.yml +38 -21
  154. data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +36 -18
  155. data/spec/vcr_cassettes/customers/search_by_name.yml +38 -48
  156. data/spec/vcr_cassettes/customers/search_miss.yml +36 -19
  157. data/spec/vcr_cassettes/customers/search_with_special_char.yml +36 -19
  158. data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +39 -22
  159. data/spec/vcr_cassettes/invoices/all.yml +71 -114
  160. data/spec/vcr_cassettes/invoices/filter_hit.yml +39 -24
  161. data/spec/vcr_cassettes/invoices/filter_invalid.yml +35 -17
  162. data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +36 -19
  163. data/spec/vcr_cassettes/invoices/find_failure.yml +36 -19
  164. data/spec/vcr_cassettes/invoices/find_id_1.yml +40 -22
  165. data/spec/vcr_cassettes/invoices/find_new.yml +41 -24
  166. data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +38 -21
  167. data/spec/vcr_cassettes/invoices/row_description_limit.yml +65 -0
  168. data/spec/vcr_cassettes/invoices/save_new.yml +39 -21
  169. data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +39 -21
  170. data/spec/vcr_cassettes/invoices/save_new_with_country.yml +35 -26
  171. data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +36 -27
  172. data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +35 -26
  173. data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +35 -26
  174. data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +35 -26
  175. data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +36 -27
  176. data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +36 -27
  177. data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +35 -26
  178. data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +35 -26
  179. data/spec/vcr_cassettes/invoices/save_new_with_unsaved_parent.yml +65 -0
  180. data/spec/vcr_cassettes/invoices/save_old.yml +41 -24
  181. data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +41 -24
  182. data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +37 -29
  183. data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +41 -24
  184. data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +37 -29
  185. data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +40 -21
  186. data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +39 -20
  187. data/spec/vcr_cassettes/invoices/search_by_name.yml +38 -27
  188. data/spec/vcr_cassettes/invoices/search_miss.yml +36 -19
  189. data/spec/vcr_cassettes/invoices/search_with_special_char.yml +36 -19
  190. data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +39 -22
  191. data/spec/vcr_cassettes/orders/all.yml +44 -119
  192. data/spec/vcr_cassettes/orders/filter_hit.yml +39 -26
  193. data/spec/vcr_cassettes/orders/filter_invalid.yml +35 -17
  194. data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +36 -19
  195. data/spec/vcr_cassettes/orders/find_failure.yml +36 -19
  196. data/spec/vcr_cassettes/orders/find_id_1.yml +42 -23
  197. data/spec/vcr_cassettes/orders/find_new.yml +41 -24
  198. data/spec/vcr_cassettes/orders/housework_invalid_tax_reduction_type.yml +61 -0
  199. data/spec/vcr_cassettes/orders/housework_othercoses_invalid.yml +61 -0
  200. data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +40 -21
  201. data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +40 -21
  202. data/spec/vcr_cassettes/orders/housework_type_construction.yml +40 -21
  203. data/spec/vcr_cassettes/orders/housework_type_cooking.yml +36 -18
  204. data/spec/vcr_cassettes/orders/housework_type_electricity.yml +40 -21
  205. data/spec/vcr_cassettes/orders/housework_type_gardening.yml +40 -21
  206. data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +40 -21
  207. data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +40 -21
  208. data/spec/vcr_cassettes/orders/housework_type_hvac.yml +40 -21
  209. data/spec/vcr_cassettes/orders/housework_type_itservices.yml +65 -0
  210. data/spec/vcr_cassettes/orders/housework_type_majorappliancerepair.yml +65 -0
  211. data/spec/vcr_cassettes/orders/housework_type_masonry.yml +40 -21
  212. data/spec/vcr_cassettes/orders/housework_type_movingservices.yml +65 -0
  213. data/spec/vcr_cassettes/orders/housework_type_othercare.yml +40 -21
  214. data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +40 -21
  215. data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +40 -21
  216. data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +40 -21
  217. data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +40 -21
  218. data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +36 -18
  219. data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +38 -21
  220. data/spec/vcr_cassettes/orders/save_new.yml +40 -22
  221. data/spec/vcr_cassettes/orders/save_old.yml +41 -24
  222. data/spec/vcr_cassettes/orders/save_with_nested_model.yml +40 -21
  223. data/spec/vcr_cassettes/orders/search_by_name.yml +38 -23
  224. data/spec/vcr_cassettes/orders/search_miss.yml +36 -19
  225. data/spec/vcr_cassettes/orders/search_with_special_char.yml +36 -19
  226. data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +39 -22
  227. data/spec/vcr_cassettes/projects/all.yml +39 -37
  228. data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +36 -19
  229. data/spec/vcr_cassettes/projects/find_failure.yml +36 -19
  230. data/spec/vcr_cassettes/projects/find_id_1.yml +38 -21
  231. data/spec/vcr_cassettes/projects/find_new.yml +39 -22
  232. data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +40 -22
  233. data/spec/vcr_cassettes/projects/save_new.yml +37 -19
  234. data/spec/vcr_cassettes/projects/save_old.yml +39 -22
  235. data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +38 -21
  236. data/spec/vcr_cassettes/termsofpayments/all.yml +43 -29
  237. data/spec/vcr_cassettes/termsofpayments/find_failure.yml +36 -19
  238. data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +38 -22
  239. data/spec/vcr_cassettes/termsofpayments/find_new.yml +38 -21
  240. data/spec/vcr_cassettes/termsofpayments/save_new.yml +37 -19
  241. data/spec/vcr_cassettes/termsofpayments/save_old.yml +38 -21
  242. data/spec/vcr_cassettes/units/all.yml +38 -26
  243. data/spec/vcr_cassettes/units/find_failure.yml +36 -19
  244. data/spec/vcr_cassettes/units/find_id_1.yml +38 -21
  245. data/spec/vcr_cassettes/units/find_new.yml +38 -21
  246. data/spec/vcr_cassettes/units/save_new.yml +37 -19
  247. data/spec/vcr_cassettes/units/save_old.yml +38 -21
  248. data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +37 -19
  249. metadata +133 -252
  250. data/lib/fortnox/api/circular_queue.rb +0 -39
  251. data/spec/fortnox/api/circular_queue_spec.rb +0 -52
  252. data/spec/support/helpers/dummy_class_helper.rb +0 -38
  253. data/spec/support/helpers/when_performing_helper.rb +0 -7
  254. data/spec/vcr_cassettes/invoices/save_new_with_country_KR.yml +0 -57
  255. data/temp.txt +0 -1
@@ -1,9 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fortnox/api/repositories/article'
4
- require 'fortnox/api/repositories/customer'
5
- require 'fortnox/api/repositories/invoice'
6
- require 'fortnox/api/repositories/order'
7
- require 'fortnox/api/repositories/project'
8
- require 'fortnox/api/repositories/unit'
9
- require 'fortnox/api/repositories/terms_of_payment'
3
+ require_relative 'repositories/article'
4
+ require_relative 'repositories/customer'
5
+ require_relative 'repositories/invoice'
6
+ require_relative 'repositories/order'
7
+ require_relative 'repositories/project'
8
+ require_relative 'repositories/unit'
9
+ require_relative 'repositories/terms_of_payment'
10
+ require_relative 'repositories/authentication'
@@ -5,30 +5,42 @@ module Fortnox
5
5
  module RequestHandling
6
6
  private
7
7
 
8
- def raise_api_error(error, response)
9
- message = (error['message'] || error['Message'] || 'Okänt fel')
8
+ def raise_api_error(error, response)
9
+ message = (error['message'] || error['Message'] || 'Okänt fel')
10
10
 
11
- message += "\n\n#{response.request.inspect}" if Fortnox::API.debugging
11
+ message += "\n\n#{response.request.inspect}" if Fortnox::API.debugging
12
12
 
13
- raise Fortnox::API::RemoteServerError, message
14
- end
13
+ raise Fortnox::API::RemoteServerError, message
14
+ end
15
15
 
16
- def validate_response(response)
17
- return if response.code == 200
16
+ def validate_response(response)
17
+ return if response.code == 200
18
18
 
19
- api_error = response.parsed_response['ErrorInformation']
20
- raise_api_error(api_error, response) if api_error
21
- end
19
+ raise_content_type_error(response) if response.headers['content-type'].start_with?('text/html')
22
20
 
23
- def validate_and_parse(response)
24
- validate_response(response)
25
- response.parsed_response
26
- end
21
+ raise Fortnox::API::RemoteServerError, "Unauthorized request. Error: #{response.body}" if response.code == 401
22
+ raise Fortnox::API::RemoteServerError, "Forbidden request. Error: #{response.body}" if response.code == 403
27
23
 
28
- def execute
29
- response = yield(self.class)
30
- validate_and_parse response
31
- end
24
+ api_error = response.parsed_response['ErrorInformation']
25
+ raise_api_error(api_error, response) if api_error
26
+ end
27
+
28
+ def raise_content_type_error(response)
29
+ raise Fortnox::API::RemoteServerError,
30
+ "Fortnox API's response has content type \"text/html\" instead of requested \"application/json\"." \
31
+ 'This could be due to invalid endpoint or when the API is down. ' \
32
+ "Body: #{response.body}"
33
+ end
34
+
35
+ def validate_and_parse(response)
36
+ validate_response(response)
37
+ response.parsed_response
38
+ end
39
+
40
+ def execute
41
+ response = yield(self.class)
42
+ validate_and_parse response
43
+ end
32
44
  end
33
45
  end
34
46
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fortnox/api/types'
4
-
5
3
  module Fortnox
6
4
  module API
7
5
  module Types
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fortnox/api/types'
4
-
5
3
  module Fortnox
6
4
  module API
7
5
  module Types
@@ -22,8 +22,8 @@ module Fortnox
22
22
  # DeliveredQuantity Delivered quantity. 14 digits
23
23
  attribute :delivered_quantity, Types::Sized::Float[-9_999_999_999_999.9, 9_999_999_999_999.9]
24
24
 
25
- # Description Description Row description. 50 characters
26
- attribute :description, Types::Sized::String[50]
25
+ # Description Description Row description.
26
+ attribute :description, Types::Sized::String[255]
27
27
 
28
28
  # Discount amount. 12 digits (for amount) / 5 digits (for percent)
29
29
  # TODO(hannes): Verify that we can send in more than 5 digits through
@@ -45,7 +45,7 @@ module Fortnox
45
45
  attribute :housework_type, Types::HouseworkType
46
46
 
47
47
  # Price Price per unit. 12 digits
48
- attribute :price, Types::Sized::Float[0.0, 99_999_999_999.9]
48
+ attribute :price, Types::Sized::Float[-9_999_999_999, 99_999_999_999.9]
49
49
 
50
50
  # Project Code of the project for the row.
51
51
  attribute :project, Types::Nullable::String
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fortnox/api/types'
4
-
5
3
  module Fortnox
6
4
  module API
7
5
  module Types
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fortnox/api/types'
4
-
5
3
  module Fortnox
6
4
  module API
7
5
  module Types
@@ -7,6 +7,7 @@ module Fortnox
7
7
  def self.sized(size)
8
8
  lambda do |value|
9
9
  return nil if value == ''
10
+
10
11
  value.to_s.upcase[0...size] unless value.nil?
11
12
  end
12
13
  end
@@ -14,7 +15,16 @@ module Fortnox
14
15
  def self.default
15
16
  lambda do |value|
16
17
  return nil if value == ''
17
- value.to_s.upcase unless value.nil?
18
+
19
+ value&.to_s&.upcase
20
+ end
21
+ end
22
+
23
+ def self.lower_case
24
+ lambda do |value|
25
+ return nil if value == ''
26
+
27
+ value&.to_s&.downcase
18
28
  end
19
29
  end
20
30
  end
@@ -25,15 +35,44 @@ module Fortnox
25
35
  DiscountTypes = Types::Strict::String.enum(
26
36
  'AMOUNT', 'PERCENT'
27
37
  )
28
- CURRENT_HOUSEWORK_TYPES = %w[
29
- CONSTRUCTION ELECTRICITY GLASSMETALWORK GROUNDDRAINAGEWORK
30
- MASONRY PAINTINGWALLPAPERING HVAC MAJORAPPLIANCEREPAIR
31
- MOVINGSERVICES ITSERVICES CLEANING TEXTILECLOTHING
32
- SNOWPLOWING GARDENING BABYSITTING OTHERCARE OTHERCOSTS
33
- ].freeze
34
- LEGACY_HOUSEWORK_TYPES = %w[COOKING TUTORING].freeze
38
+ HOUSEWORK_TYPES = {
39
+ rot: [
40
+ 'CONSTRUCTION',
41
+ 'ELECTRICITY',
42
+ 'GLASSMETALWORK',
43
+ 'GROUNDDRAINAGEWORK',
44
+ 'HVAC',
45
+ 'MASONRY',
46
+ 'OTHERCOSTS',
47
+ 'PAINTINGWALLPAPERING'
48
+ ],
49
+ rut: [
50
+ 'BABYSITTING',
51
+ 'CLEANING',
52
+ 'GARDENING',
53
+ 'ITSERVICES',
54
+ 'MAJORAPPLIANCEREPAIR',
55
+ 'MOVINGSERVICES',
56
+ 'OTHERCARE',
57
+ 'OTHERCOSTS',
58
+ 'SNOWPLOWING',
59
+ 'TEXTILECLOTHING'
60
+ ],
61
+ legacy_rut: ['COOKING', 'TUTORING']
62
+ }.freeze
63
+
64
+ # TODO: RUT to be added:
65
+ # HOMEMAINTENANCE FURNISHING TRANSPORTATIONSERVICES
66
+ # WASHINGANDCAREOFCLOTHING
67
+ #
68
+ # TODO: GREEN to be added:
69
+ # SOLARCELLS STORAGESELFPRODUCEDELECTRICTY
70
+ # CHARGINGSTATIONELECTRICVEHICLE OTHERCOSTS
71
+
35
72
  HouseworkTypes = Types::Strict::String.enum(
36
- *(CURRENT_HOUSEWORK_TYPES + LEGACY_HOUSEWORK_TYPES)
73
+ *(HOUSEWORK_TYPES[:rot] +
74
+ HOUSEWORK_TYPES[:rut] +
75
+ HOUSEWORK_TYPES[:legacy_rut]).uniq
37
76
  )
38
77
  Currencies = Types::Strict::String.enum(
39
78
  'AED', 'AFN', 'ALL', 'AMD', 'ANG', 'AOA', 'ARS', 'AUD', 'AWG', 'AZN',
@@ -62,11 +101,16 @@ module Fortnox
62
101
  'SEVAT', 'SEREVERSEDVAT', 'EUREVERSEDVAT', 'EUVAT', 'EXPORT'
63
102
  )
64
103
  DefaultDeliveryTypeValues = Types::Strict::String.enum(
65
- 'PRINT', 'EMAIL', 'PRINTSERVICE'
104
+ 'PRINT', 'EMAIL', 'PRINTSERVICE', 'ELECTRONICINVOICE'
66
105
  )
67
106
  ProjectStatusTypes = Types::Strict::String.enum(
68
107
  'NOTSTARTED', 'ONGOING', 'COMPLETED'
69
108
  )
109
+ # NOTE: Yes, these needs to be in lower case...
110
+ # I know, this is stupid... Fortnox: why?!
111
+ TaxReductionTypes = Types::Strict::String.enum(
112
+ 'rot', 'rut', 'green', 'none'
113
+ )
70
114
  end
71
115
  end
72
116
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fortnox/api/types/document_row'
3
+ require_relative 'document_row'
4
4
 
5
5
  module Fortnox
6
6
  module API
@@ -4,7 +4,7 @@ module Fortnox
4
4
  module API
5
5
  module Types
6
6
  class Model < Dry::Struct
7
- constructor_type(:schema)
7
+ transform_types(&:omittable)
8
8
 
9
9
  def initialize(input_attributes)
10
10
  if (missing_key = first_missing_required_key(input_attributes))
@@ -15,23 +15,19 @@ module Fortnox
15
15
  super
16
16
  end
17
17
 
18
- def self.is?(*_args) end
19
-
20
18
  private
21
19
 
22
20
  def missing_keys(attributes)
23
- non_nil_attributes = attributes.reject { |_, value| value.nil? }
24
-
25
- attribute_keys = non_nil_attributes.keys
26
- schema_keys = self.class.schema.keys
21
+ attribute_keys = attributes.compact.keys
22
+ schema_keys = self.class.schema.keys.map(&:name)
27
23
 
28
24
  schema_keys - attribute_keys
29
25
  end
30
26
 
31
27
  def first_missing_required_key(attributes)
32
28
  missing_keys(attributes).find do |name|
33
- attribute = self.class.schema[name]
34
- attribute.respond_to?(:options) && attribute.options[:required]
29
+ attribute = self.class.schema.keys.find { |key| key.name == name }
30
+ attribute.type.respond_to?(:options) && attribute.type.options[:required]
35
31
  end
36
32
  end
37
33
  end
@@ -6,15 +6,19 @@ module Fortnox
6
6
  module API
7
7
  module Types
8
8
  module Nullable
9
- String = Types::Strict::String.optional.constructor { |value| value.to_s unless value.nil? }
10
- Float = Types::Strict::Float.optional.constructor { |value| value.to_f unless value.nil? }
11
- Integer = Types::Strict::Int.optional.constructor { |value| value.to_i unless value.nil? }
12
- Boolean = Types::Bool.optional.constructor { |value| THE_TRUTH.fetch(value) { false } }
13
- # TODO: Improve parsing!
14
- # In case date parsing fails, ArgumentError is thrown. Currently, it is rescued in Repository::Loaders.find.
15
- # That method assumes that the exception is due to invalid argument to the find method, which is not the case
16
- # if it is raised from here!
17
- Date = Types::Date.optional.constructor { |value| ::Date.parse(value.to_s) unless value.nil? || value == '' }
9
+ String = Types::Strict::String.optional.constructor { |value| value&.to_s }
10
+ Float = Types::Strict::Float.optional.constructor { |value| value&.to_f }
11
+ Integer = Types::Strict::Integer.optional.constructor { |value| value&.to_i }
12
+ Boolean = Types::Bool.optional.constructor { |value| THE_TRUTH.fetch(value, false) }
13
+ Date = Types::Date.optional.constructor do |value|
14
+ next if value.nil? || value == ''
15
+
16
+ begin
17
+ ::Date.parse(value.to_s)
18
+ rescue ::Date::Error
19
+ raise Fortnox::API::AttributeError, 'invalid date'
20
+ end
21
+ end
18
22
  end
19
23
  end
20
24
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'fortnox/api/types/document_row'
3
+ require_relative 'document_row'
4
4
 
5
5
  module Fortnox
6
6
  module API
@@ -4,9 +4,9 @@ module Fortnox
4
4
  module API
5
5
  module Types
6
6
  module Required
7
- String = Types::Strict::String.constructor { |value| value.to_s unless value.nil? }.is(:required)
8
- Integer = Types::Strict::Int.constructor { |value| value.to_i unless value.nil? }.is(:required)
9
- Float = Types::Strict::Float.constructor { |value| value.to_f unless value.nil? }.is(:required)
7
+ String = Types::Strict::String.constructor { |value| value&.to_s }.is(:required)
8
+ Integer = Types::Strict::Integer.constructor { |value| value&.to_i }.is(:required)
9
+ Float = Types::Strict::Float.constructor { |value| value&.to_f }.is(:required)
10
10
  end
11
11
  end
12
12
  end
@@ -7,15 +7,15 @@ module Fortnox
7
7
  module String
8
8
  def self.[](size)
9
9
  Types::Strict::String.constrained(max_size: size).optional.constructor do |value|
10
- value.to_s unless value.nil?
10
+ value&.to_s
11
11
  end
12
12
  end
13
13
  end
14
14
 
15
15
  module Integer
16
16
  def self.[](low, high)
17
- Types::Strict::Int.constrained(gteq: low, lteq: high).optional.constructor do |value|
18
- value.to_i unless value.nil?
17
+ Types::Strict::Integer.constrained(gteq: low, lteq: high).optional.constructor do |value|
18
+ value&.to_i
19
19
  end
20
20
  end
21
21
  end
@@ -23,7 +23,7 @@ module Fortnox
23
23
  module Float
24
24
  def self.[](low, high)
25
25
  Types::Strict::Float.constrained(gteq: low, lteq: high).optional.constructor do |value|
26
- value.to_f unless value.nil?
26
+ value&.to_f
27
27
  end
28
28
  end
29
29
  end
@@ -3,7 +3,7 @@
3
3
  require 'dry-struct'
4
4
  require 'dry-types'
5
5
  require 'countries'
6
- require 'fortnox/api/types/shim/country_string'
6
+ require_relative 'types/shim/country_string'
7
7
 
8
8
  module Dry
9
9
  module Types
@@ -12,11 +12,7 @@ module Dry
12
12
  new_options = option_names.each_with_object({}) do |name, hash|
13
13
  hash[name] = true
14
14
  end
15
- with(new_options)
16
- end
17
-
18
- def is?(option_name)
19
- @options[option_name]
15
+ with(**new_options)
20
16
  end
21
17
  end
22
18
  end
@@ -25,24 +21,24 @@ end
25
21
  module Fortnox
26
22
  module API
27
23
  module Types
28
- include Dry::Types.module
24
+ include Dry.Types(default: :nominal)
29
25
  ISO3166.configure { |config| config.locales = %i[en sv] }
30
26
 
31
27
  THE_TRUTH = { true => true, 'true' => true, false => false, 'false' => false }.freeze
32
28
 
33
- require 'fortnox/api/types/required'
34
- require 'fortnox/api/types/defaulted'
35
- require 'fortnox/api/types/nullable'
36
-
37
- require 'fortnox/api/types/enums'
29
+ require_relative 'types/required'
30
+ require_relative 'types/defaulted'
31
+ require_relative 'types/nullable'
38
32
 
39
- require 'fortnox/api/types/sized'
33
+ require_relative 'types/enums'
34
+ require_relative 'types/sized'
40
35
 
41
- AccountNumber = Strict::Int
36
+ AccountNumber = Strict::Integer
42
37
  .constrained(gteq: 0, lteq: 9999)
43
38
  .optional
44
39
  .constructor do |value|
45
40
  next nil if value.nil? || value == ''
41
+
46
42
  value
47
43
  end
48
44
 
@@ -57,10 +53,10 @@ module Fortnox
57
53
  next value if value.nil? || value == ''
58
54
 
59
55
  # Fortnox only supports Swedish translation of Sweden
60
- next CountryString.new('SE') if value =~ /^s(e$|we|ve)/i
56
+ next CountryString.new('SE') if value.match?(/^s(e$|we|ve)/i)
61
57
 
62
58
  country = ::ISO3166::Country[value] ||
63
- ::ISO3166::Country.find_country_by_name(value) ||
59
+ ::ISO3166::Country.find_country_by_any_name(value) ||
64
60
  ::ISO3166::Country.find_country_by_translated_names(value)
65
61
 
66
62
  raise Dry::Types::ConstraintError.new('value violates constraints', value) if country.nil?
@@ -97,7 +93,7 @@ module Fortnox
97
93
  Email = Strict::String
98
94
  .constrained(max_size: 1024, format: /^$|\A[[[:alnum:]]+-_.]+@[\w+-_.]+\.[a-z]+\z/i)
99
95
  .optional
100
- .constructor { |v| v.to_s.downcase unless v.nil? }
96
+ .constructor { |v| v&.to_s&.downcase }
101
97
 
102
98
  HouseworkType = Strict::String
103
99
  .constrained(included_in: HouseworkTypes.values)
@@ -119,13 +115,30 @@ module Fortnox
119
115
  .optional
120
116
  .constructor(EnumConstructors.default)
121
117
 
122
- require 'fortnox/api/types/model'
123
- require 'fortnox/api/types/default_delivery_types'
124
- require 'fortnox/api/types/default_templates'
125
- require 'fortnox/api/types/email_information'
126
- require 'fortnox/api/types/edi_information'
127
- require 'fortnox/api/types/invoice_row'
128
- require 'fortnox/api/types/order_row'
118
+ TaxReductionType = Strict::String
119
+ .constrained(included_in: TaxReductionTypes.values)
120
+ .optional
121
+ .constructor(EnumConstructors.lower_case)
122
+
123
+ # Some Fortnox endpoints returns a String and some returns an Integer...
124
+ # The documentation says it should be a string, so let's keep it as a string.
125
+ SalesAccount = Strict::String
126
+ .constrained(format: /^[0-9]{4}$/)
127
+ .optional
128
+ .constructor do |value|
129
+ next nil if value == '' || value.nil?
130
+ next value.to_s if value.is_a?(::Integer)
131
+
132
+ value
133
+ end
134
+
135
+ require_relative 'types/model'
136
+ require_relative 'types/default_delivery_types'
137
+ require_relative 'types/default_templates'
138
+ require_relative 'types/email_information'
139
+ require_relative 'types/edi_information'
140
+ require_relative 'types/invoice_row'
141
+ require_relative 'types/order_row'
129
142
  end
130
143
  end
131
144
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Fortnox
4
4
  module API
5
- VERSION = '0.7.2'
5
+ VERSION = '0.9.0'
6
6
  end
7
7
  end
data/lib/fortnox/api.rb CHANGED
@@ -1,46 +1,39 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'set'
4
- require 'dry-struct'
5
- require 'fortnox/api/circular_queue'
6
- require 'fortnox/api/version'
4
+ require 'dry-configurable'
5
+ require 'dry-container'
7
6
  require 'logger'
8
7
 
8
+ require_relative 'api/version'
9
+
9
10
  module Fortnox
10
11
  module API
11
12
  extend Dry::Configurable
12
13
 
13
14
  DEFAULT_CONFIGURATION = {
14
15
  base_url: 'https://api.fortnox.se/3/',
15
- client_secret: nil,
16
- token_store: {},
17
- access_token: nil,
18
- access_tokens: nil,
16
+ token_url: 'https://apps.fortnox.se/oauth-v1/token',
19
17
  debugging: false,
20
18
  logger: lambda {
21
- logger = Logger.new(STDOUT)
19
+ logger = Logger.new($stdout)
22
20
  logger.level = Logger::WARN
23
21
  return logger
24
22
  }.call
25
23
  }.freeze
26
24
 
27
- setting :base_url, DEFAULT_CONFIGURATION[:base_url]
28
- setting :client_secret, DEFAULT_CONFIGURATION[:client_secret]
29
- setting :token_store, DEFAULT_CONFIGURATION[:token_store]
30
- setting :access_token, DEFAULT_CONFIGURATION[:access_token] do |value|
31
- next if value.nil? # nil is a valid unassigned value
32
- invalid_access_token_format!(value) unless value.is_a?(String)
33
- config.token_store = { default: value }
34
- value
25
+ setting :base_url, default: DEFAULT_CONFIGURATION[:base_url]
26
+ setting :token_url, default: DEFAULT_CONFIGURATION[:token_url]
27
+ setting :debugging, default: DEFAULT_CONFIGURATION[:debugging], reader: true
28
+ setting :logger, default: DEFAULT_CONFIGURATION[:logger], reader: true
29
+
30
+ def self.access_token=(token)
31
+ Thread.current[:access_token] = token
35
32
  end
36
- setting :access_tokens, DEFAULT_CONFIGURATION[:access_tokens] do |value|
37
- next if value.nil? # nil is a valid unassigned value
38
- invalid_access_tokens_format!(value) unless value.is_a?(Hash) || value.is_a?(Array)
39
- config.token_store = value.is_a?(Hash) ? value : { default: value }
40
- value
33
+
34
+ def self.access_token
35
+ Thread.current[:access_token]
41
36
  end
42
- setting :debugging, DEFAULT_CONFIGURATION[:debugging], reader: true
43
- setting :logger, DEFAULT_CONFIGURATION[:logger], reader: true
44
37
 
45
38
  class Exception < StandardError
46
39
  end
@@ -57,24 +50,13 @@ module Fortnox
57
50
  class MissingConfiguration < Fortnox::API::Exception
58
51
  end
59
52
 
60
- Registry = Dry::Container.new
61
-
62
- def self.invalid_access_token_format!(value)
63
- raise ArgumentError,
64
- 'expected a String, but '\
65
- "#{value.inspect} is a(n) #{value.class}"
53
+ class MissingAccessToken < Fortnox::API::Exception
66
54
  end
67
- private_class_method :invalid_access_token_format!
68
55
 
69
- def self.invalid_access_tokens_format!(value)
70
- raise ArgumentError,
71
- 'expected a Hash or an Array, but '\
72
- "#{value.inspect} is a(n) #{value.class}"
73
- end
74
- private_class_method :invalid_access_tokens_format!
56
+ Registry = Dry::Container.new
75
57
  end
76
58
  end
77
59
 
78
- require 'fortnox/api/models'
79
- require 'fortnox/api/repositories'
80
- require 'fortnox/api/mappers'
60
+ require_relative 'api/models'
61
+ require_relative 'api/repositories'
62
+ require_relative 'api/mappers'
@@ -6,27 +6,29 @@ require 'fortnox/api/mappers/base/canonical_name_sym'
6
6
  describe Fortnox::API::Mapper::CanonicalNameSym do
7
7
  describe '.canonical_name_sym' do
8
8
  context 'with simple class' do
9
- using_test_class do
10
- class TestClass
9
+ subject { TestClass.canonical_name_sym }
10
+
11
+ before do
12
+ test_class = Class.new do
11
13
  extend Fortnox::API::Mapper::CanonicalNameSym
12
14
  end
13
- end
14
15
 
15
- subject { TestClass.canonical_name_sym }
16
+ stub_const('TestClass', test_class)
17
+ end
16
18
 
17
19
  it { is_expected.to eq(:testclass) }
18
20
  end
19
21
 
20
22
  context 'when class included in module' do
21
- using_test_class do
22
- module Something
23
- class Test
24
- extend Fortnox::API::Mapper::CanonicalNameSym
25
- end
23
+ subject { Something::Test.canonical_name_sym }
24
+
25
+ before do
26
+ test_class = Class.new do
27
+ extend Fortnox::API::Mapper::CanonicalNameSym
26
28
  end
27
- end
28
29
 
29
- subject { Something::Test.canonical_name_sym }
30
+ stub_const('Something::Test', test_class)
31
+ end
30
32
 
31
33
  it { is_expected.to eq(:test) }
32
34
  end