fortnox-api 0.9.2 → 1.0.0.rc2

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 (344) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +120 -95
  3. data/{LICENSE.txt → LICENSE.md} +0 -0
  4. data/README.md +245 -248
  5. data/bin/fortnox-setup +211 -0
  6. data/bin/fortnox-update-env +38 -0
  7. data/fortnox.gemspec +29 -0
  8. data/lib/fortnox/auth/thread_local.rb +28 -0
  9. data/lib/fortnox/collection.rb +23 -0
  10. data/lib/fortnox/mappers/country_code.rb +34 -0
  11. data/lib/fortnox/mappers/date.rb +21 -0
  12. data/lib/fortnox/mappers/document_row.rb +12 -0
  13. data/lib/fortnox/mappers/edi_information.rb +15 -0
  14. data/lib/fortnox/mappers/email_information.rb +11 -0
  15. data/lib/fortnox/mappers/invoice_row.rb +14 -0
  16. data/lib/fortnox/mappers/label_references.rb +24 -0
  17. data/lib/fortnox/mappers/order_row.rb +9 -0
  18. data/lib/fortnox/mappers/struct.rb +71 -0
  19. data/lib/fortnox/mappers/struct_array.rb +31 -0
  20. data/lib/fortnox/resource.rb +131 -0
  21. data/lib/fortnox/resources/article.rb +160 -0
  22. data/lib/fortnox/resources/customer.rb +209 -0
  23. data/lib/fortnox/resources/document.rb +202 -0
  24. data/lib/fortnox/resources/invoice.rb +92 -0
  25. data/lib/fortnox/resources/label.rb +15 -0
  26. data/lib/fortnox/resources/order.rb +38 -0
  27. data/lib/fortnox/resources/project.rb +41 -0
  28. data/lib/fortnox/resources/terms_of_payment.rb +23 -0
  29. data/lib/fortnox/resources/unit.rb +26 -0
  30. data/lib/fortnox/struct.rb +24 -0
  31. data/lib/fortnox/structs/default_delivery_types.rb +16 -0
  32. data/lib/fortnox/structs/default_templates.rb +19 -0
  33. data/lib/fortnox/structs/document_row.rb +62 -0
  34. data/lib/fortnox/structs/edi_information.rb +25 -0
  35. data/lib/fortnox/structs/email_information.rb +22 -0
  36. data/lib/fortnox/structs/invoice_row.rb +13 -0
  37. data/lib/fortnox/structs/order_row.rb +10 -0
  38. data/lib/fortnox/types.rb +129 -0
  39. data/lib/fortnox/{api/version.rb → version.rb} +1 -3
  40. data/lib/fortnox.rb +111 -0
  41. metadata +55 -580
  42. data/.codeclimate.yml +0 -20
  43. data/.env.template +0 -7
  44. data/.env.test +0 -11
  45. data/.gitignore +0 -20
  46. data/.rspec +0 -2
  47. data/.rubocop.yml +0 -41
  48. data/.tool-versions +0 -1
  49. data/.travis.yml +0 -32
  50. data/CLAUDE.md +0 -79
  51. data/CONTRIBUTE.md +0 -38
  52. data/DEVELOPER_README.md +0 -69
  53. data/Gemfile +0 -6
  54. data/Guardfile +0 -16
  55. data/Rakefile +0 -140
  56. data/bin/console +0 -22
  57. data/bin/fortnox +0 -285
  58. data/bin/get_tokens +0 -79
  59. data/bin/renew_tokens +0 -28
  60. data/docs/gotchas.md +0 -146
  61. data/fortnox-api.gemspec +0 -50
  62. data/lib/fortnox/api/mappers/article.rb +0 -23
  63. data/lib/fortnox/api/mappers/base/canonical_name_sym.rb +0 -21
  64. data/lib/fortnox/api/mappers/base/from_json.rb +0 -91
  65. data/lib/fortnox/api/mappers/base/to_json.rb +0 -66
  66. data/lib/fortnox/api/mappers/base.rb +0 -30
  67. data/lib/fortnox/api/mappers/customer.rb +0 -27
  68. data/lib/fortnox/api/mappers/default_delivery_types.rb +0 -15
  69. data/lib/fortnox/api/mappers/default_templates.rb +0 -16
  70. data/lib/fortnox/api/mappers/edi_information.rb +0 -23
  71. data/lib/fortnox/api/mappers/email_information.rb +0 -19
  72. data/lib/fortnox/api/mappers/invoice.rb +0 -29
  73. data/lib/fortnox/api/mappers/invoice_row.rb +0 -25
  74. data/lib/fortnox/api/mappers/order.rb +0 -26
  75. data/lib/fortnox/api/mappers/order_row.rb +0 -23
  76. data/lib/fortnox/api/mappers/project.rb +0 -17
  77. data/lib/fortnox/api/mappers/terms_of_payment.rb +0 -17
  78. data/lib/fortnox/api/mappers/unit.rb +0 -17
  79. data/lib/fortnox/api/mappers/value/array.rb +0 -18
  80. data/lib/fortnox/api/mappers/value/country_string.rb +0 -24
  81. data/lib/fortnox/api/mappers/value/date.rb +0 -11
  82. data/lib/fortnox/api/mappers/value/hash.rb +0 -16
  83. data/lib/fortnox/api/mappers/value/identity.rb +0 -18
  84. data/lib/fortnox/api/mappers.rb +0 -21
  85. data/lib/fortnox/api/models/article.rb +0 -134
  86. data/lib/fortnox/api/models/base.rb +0 -128
  87. data/lib/fortnox/api/models/customer.rb +0 -210
  88. data/lib/fortnox/api/models/document.rb +0 -189
  89. data/lib/fortnox/api/models/invoice.rb +0 -87
  90. data/lib/fortnox/api/models/label.rb +0 -19
  91. data/lib/fortnox/api/models/order.rb +0 -27
  92. data/lib/fortnox/api/models/project.rb +0 -42
  93. data/lib/fortnox/api/models/terms_of_payment.rb +0 -28
  94. data/lib/fortnox/api/models/unit.rb +0 -24
  95. data/lib/fortnox/api/models.rb +0 -9
  96. data/lib/fortnox/api/repositories/article.rb +0 -17
  97. data/lib/fortnox/api/repositories/authentication.rb +0 -61
  98. data/lib/fortnox/api/repositories/base/loaders.rb +0 -64
  99. data/lib/fortnox/api/repositories/base/savers.rb +0 -57
  100. data/lib/fortnox/api/repositories/base.rb +0 -93
  101. data/lib/fortnox/api/repositories/customer.rb +0 -17
  102. data/lib/fortnox/api/repositories/invoice.rb +0 -17
  103. data/lib/fortnox/api/repositories/order.rb +0 -17
  104. data/lib/fortnox/api/repositories/project.rb +0 -17
  105. data/lib/fortnox/api/repositories/terms_of_payment.rb +0 -17
  106. data/lib/fortnox/api/repositories/unit.rb +0 -17
  107. data/lib/fortnox/api/repositories.rb +0 -10
  108. data/lib/fortnox/api/request_handling.rb +0 -46
  109. data/lib/fortnox/api/types/default_delivery_types.rb +0 -20
  110. data/lib/fortnox/api/types/default_templates.rb +0 -23
  111. data/lib/fortnox/api/types/defaulted.rb +0 -11
  112. data/lib/fortnox/api/types/document_row.rb +0 -64
  113. data/lib/fortnox/api/types/edi_information.rb +0 -29
  114. data/lib/fortnox/api/types/email_information.rb +0 -26
  115. data/lib/fortnox/api/types/enums.rb +0 -116
  116. data/lib/fortnox/api/types/invoice_row.rb +0 -19
  117. data/lib/fortnox/api/types/model.rb +0 -37
  118. data/lib/fortnox/api/types/nullable.rb +0 -25
  119. data/lib/fortnox/api/types/order_row.rb +0 -16
  120. data/lib/fortnox/api/types/required.rb +0 -13
  121. data/lib/fortnox/api/types/shim/country_string.rb +0 -10
  122. data/lib/fortnox/api/types/sized.rb +0 -33
  123. data/lib/fortnox/api/types.rb +0 -144
  124. data/lib/fortnox/api.rb +0 -62
  125. data/spec/fortnox/api/mappers/article_spec.rb +0 -17
  126. data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +0 -36
  127. data/spec/fortnox/api/mappers/base/from_json_spec.rb +0 -70
  128. data/spec/fortnox/api/mappers/base/to_json_spec.rb +0 -68
  129. data/spec/fortnox/api/mappers/base_spec.rb +0 -154
  130. data/spec/fortnox/api/mappers/contexts/json_conversion.rb +0 -62
  131. data/spec/fortnox/api/mappers/customer_spec.rb +0 -27
  132. data/spec/fortnox/api/mappers/default_delivery_types_spec.rb +0 -14
  133. data/spec/fortnox/api/mappers/edi_information_spec.rb +0 -23
  134. data/spec/fortnox/api/mappers/email_information_spec.rb +0 -19
  135. data/spec/fortnox/api/mappers/examples/mapper.rb +0 -34
  136. data/spec/fortnox/api/mappers/invoice_row_spec.rb +0 -24
  137. data/spec/fortnox/api/mappers/invoice_spec.rb +0 -27
  138. data/spec/fortnox/api/mappers/order_row_spec.rb +0 -21
  139. data/spec/fortnox/api/mappers/order_spec.rb +0 -23
  140. data/spec/fortnox/api/mappers/project_spec.rb +0 -12
  141. data/spec/fortnox/api/mappers/terms_of_payment_spec.rb +0 -16
  142. data/spec/fortnox/api/mappers/unit_spec.rb +0 -56
  143. data/spec/fortnox/api/models/article_spec.rb +0 -9
  144. data/spec/fortnox/api/models/base_spec.rb +0 -117
  145. data/spec/fortnox/api/models/customer_spec.rb +0 -9
  146. data/spec/fortnox/api/models/examples/document_base.rb +0 -15
  147. data/spec/fortnox/api/models/examples/model.rb +0 -22
  148. data/spec/fortnox/api/models/invoice_spec.rb +0 -11
  149. data/spec/fortnox/api/models/order_spec.rb +0 -12
  150. data/spec/fortnox/api/models/project_spec.rb +0 -9
  151. data/spec/fortnox/api/models/terms_of_payment_spec.rb +0 -9
  152. data/spec/fortnox/api/models/unit_spec.rb +0 -33
  153. data/spec/fortnox/api/repositories/article_spec.rb +0 -80
  154. data/spec/fortnox/api/repositories/authentication_spec.rb +0 -103
  155. data/spec/fortnox/api/repositories/base_spec.rb +0 -168
  156. data/spec/fortnox/api/repositories/customer_spec.rb +0 -119
  157. data/spec/fortnox/api/repositories/examples/all.rb +0 -17
  158. data/spec/fortnox/api/repositories/examples/find.rb +0 -84
  159. data/spec/fortnox/api/repositories/examples/only.rb +0 -34
  160. data/spec/fortnox/api/repositories/examples/save.rb +0 -76
  161. data/spec/fortnox/api/repositories/examples/save_with_nested_model.rb +0 -28
  162. data/spec/fortnox/api/repositories/examples/save_with_specially_named_attribute.rb +0 -26
  163. data/spec/fortnox/api/repositories/examples/search.rb +0 -39
  164. data/spec/fortnox/api/repositories/invoice_spec.rb +0 -297
  165. data/spec/fortnox/api/repositories/order_spec.rb +0 -53
  166. data/spec/fortnox/api/repositories/project_spec.rb +0 -36
  167. data/spec/fortnox/api/repositories/terms_of_payment_spec.rb +0 -34
  168. data/spec/fortnox/api/repositories/unit_spec.rb +0 -39
  169. data/spec/fortnox/api/types/account_number_spec.rb +0 -35
  170. data/spec/fortnox/api/types/country_code_spec.rb +0 -42
  171. data/spec/fortnox/api/types/country_spec.rb +0 -67
  172. data/spec/fortnox/api/types/default_delivery_types_spec.rb +0 -12
  173. data/spec/fortnox/api/types/edi_information_spec.rb +0 -15
  174. data/spec/fortnox/api/types/email_information_spec.rb +0 -15
  175. data/spec/fortnox/api/types/email_spec.rb +0 -56
  176. data/spec/fortnox/api/types/enums_spec.rb +0 -17
  177. data/spec/fortnox/api/types/examples/document_row.rb +0 -25
  178. data/spec/fortnox/api/types/examples/enum.rb +0 -55
  179. data/spec/fortnox/api/types/examples/types.rb +0 -11
  180. data/spec/fortnox/api/types/housework_types_spec.rb +0 -149
  181. data/spec/fortnox/api/types/invoice_row_spec.rb +0 -11
  182. data/spec/fortnox/api/types/model_spec.rb +0 -69
  183. data/spec/fortnox/api/types/nullable_spec.rb +0 -79
  184. data/spec/fortnox/api/types/order_row_spec.rb +0 -15
  185. data/spec/fortnox/api/types/required_spec.rb +0 -36
  186. data/spec/fortnox/api/types/sales_account_spec.rb +0 -57
  187. data/spec/fortnox/api/types/sized_spec.rb +0 -76
  188. data/spec/fortnox/api_spec.rb +0 -66
  189. data/spec/spec_helper.rb +0 -35
  190. data/spec/support/helpers/configuration_helper.rb +0 -39
  191. data/spec/support/helpers/repository_helper.rb +0 -10
  192. data/spec/support/helpers.rb +0 -3
  193. data/spec/support/matchers/type/attribute_matcher.rb +0 -40
  194. data/spec/support/matchers/type/enum_matcher.rb +0 -23
  195. data/spec/support/matchers/type/have_account_number_matcher.rb +0 -23
  196. data/spec/support/matchers/type/have_currency_matcher.rb +0 -9
  197. data/spec/support/matchers/type/have_customer_type_matcher.rb +0 -15
  198. data/spec/support/matchers/type/have_default_delivery_type_matcher.rb +0 -9
  199. data/spec/support/matchers/type/have_discount_type_matcher.rb +0 -9
  200. data/spec/support/matchers/type/have_email_matcher.rb +0 -24
  201. data/spec/support/matchers/type/have_housework_type_matcher.rb +0 -9
  202. data/spec/support/matchers/type/have_nullable_date_matcher.rb +0 -60
  203. data/spec/support/matchers/type/have_nullable_matcher.rb +0 -54
  204. data/spec/support/matchers/type/have_nullable_string_matcher.rb +0 -47
  205. data/spec/support/matchers/type/have_sized_float_matcher.rb +0 -10
  206. data/spec/support/matchers/type/have_sized_integer_matcher.rb +0 -10
  207. data/spec/support/matchers/type/have_sized_string_matcher.rb +0 -36
  208. data/spec/support/matchers/type/have_vat_type_matcher.rb +0 -9
  209. data/spec/support/matchers/type/numeric_matcher.rb +0 -52
  210. data/spec/support/matchers/type/require_attribute_matcher.rb +0 -68
  211. data/spec/support/matchers/type/type_matcher.rb +0 -40
  212. data/spec/support/matchers/type.rb +0 -19
  213. data/spec/support/matchers.rb +0 -3
  214. data/spec/support/vcr_setup.rb +0 -25
  215. data/spec/vcr_cassettes/articles/all.yml +0 -67
  216. data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +0 -62
  217. data/spec/vcr_cassettes/articles/find_failure.yml +0 -62
  218. data/spec/vcr_cassettes/articles/find_id_1.yml +0 -63
  219. data/spec/vcr_cassettes/articles/find_new.yml +0 -63
  220. data/spec/vcr_cassettes/articles/limits/quantity_in_stock_min_value.yml +0 -63
  221. data/spec/vcr_cassettes/articles/limits/quantity_in_stock_rounding_positive_value.yml +0 -63
  222. data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +0 -62
  223. data/spec/vcr_cassettes/articles/save_new.yml +0 -63
  224. data/spec/vcr_cassettes/articles/save_old.yml +0 -63
  225. data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +0 -63
  226. data/spec/vcr_cassettes/articles/search_by_name.yml +0 -65
  227. data/spec/vcr_cassettes/articles/search_miss.yml +0 -62
  228. data/spec/vcr_cassettes/articles/search_with_special_char.yml +0 -62
  229. data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +0 -62
  230. data/spec/vcr_cassettes/authentication/expired_token.yml +0 -54
  231. data/spec/vcr_cassettes/authentication/invalid_authorization.yml +0 -57
  232. data/spec/vcr_cassettes/authentication/invalid_refresh_token.yml +0 -58
  233. data/spec/vcr_cassettes/authentication/valid_request.yml +0 -63
  234. data/spec/vcr_cassettes/customers/all.yml +0 -69
  235. data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +0 -62
  236. data/spec/vcr_cassettes/customers/find_failure.yml +0 -62
  237. data/spec/vcr_cassettes/customers/find_id_1.yml +0 -64
  238. data/spec/vcr_cassettes/customers/find_new.yml +0 -63
  239. data/spec/vcr_cassettes/customers/find_with_sales_account.yml +0 -63
  240. data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +0 -63
  241. data/spec/vcr_cassettes/customers/save_new.yml +0 -63
  242. data/spec/vcr_cassettes/customers/save_new_with_country_code_SE.yml +0 -64
  243. data/spec/vcr_cassettes/customers/save_new_with_idn_email.yml +0 -67
  244. data/spec/vcr_cassettes/customers/save_new_with_sales_account.yml +0 -63
  245. data/spec/vcr_cassettes/customers/save_old.yml +0 -63
  246. data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +0 -63
  247. data/spec/vcr_cassettes/customers/search_by_name.yml +0 -64
  248. data/spec/vcr_cassettes/customers/search_miss.yml +0 -62
  249. data/spec/vcr_cassettes/customers/search_with_special_char.yml +0 -62
  250. data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +0 -64
  251. data/spec/vcr_cassettes/invoices/all.yml +0 -96
  252. data/spec/vcr_cassettes/invoices/filter_hit.yml +0 -64
  253. data/spec/vcr_cassettes/invoices/filter_invalid.yml +0 -60
  254. data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +0 -62
  255. data/spec/vcr_cassettes/invoices/find_failure.yml +0 -62
  256. data/spec/vcr_cassettes/invoices/find_id_1.yml +0 -65
  257. data/spec/vcr_cassettes/invoices/find_new.yml +0 -65
  258. data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +0 -63
  259. data/spec/vcr_cassettes/invoices/row_delivered_quantity_decimals.yml +0 -65
  260. data/spec/vcr_cassettes/invoices/row_delivered_quantity_decimals_round_up.yml +0 -65
  261. data/spec/vcr_cassettes/invoices/row_description_limit.yml +0 -65
  262. data/spec/vcr_cassettes/invoices/row_price_limit.yml +0 -65
  263. data/spec/vcr_cassettes/invoices/row_price_limit_round_up.yml +0 -65
  264. data/spec/vcr_cassettes/invoices/save_new.yml +0 -65
  265. data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +0 -65
  266. data/spec/vcr_cassettes/invoices/save_new_with_country.yml +0 -65
  267. data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +0 -66
  268. data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +0 -65
  269. data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +0 -65
  270. data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +0 -65
  271. data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +0 -66
  272. data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +0 -66
  273. data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +0 -65
  274. data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +0 -65
  275. data/spec/vcr_cassettes/invoices/save_new_with_unsaved_parent.yml +0 -65
  276. data/spec/vcr_cassettes/invoices/save_old.yml +0 -65
  277. data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +0 -65
  278. data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +0 -65
  279. data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +0 -65
  280. data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +0 -65
  281. data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +0 -65
  282. data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +0 -65
  283. data/spec/vcr_cassettes/invoices/search_by_name.yml +0 -63
  284. data/spec/vcr_cassettes/invoices/search_miss.yml +0 -62
  285. data/spec/vcr_cassettes/invoices/search_with_special_char.yml +0 -62
  286. data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +0 -64
  287. data/spec/vcr_cassettes/orders/all.yml +0 -69
  288. data/spec/vcr_cassettes/orders/filter_hit.yml +0 -64
  289. data/spec/vcr_cassettes/orders/filter_invalid.yml +0 -60
  290. data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +0 -62
  291. data/spec/vcr_cassettes/orders/find_failure.yml +0 -62
  292. data/spec/vcr_cassettes/orders/find_id_1.yml +0 -67
  293. data/spec/vcr_cassettes/orders/find_new.yml +0 -65
  294. data/spec/vcr_cassettes/orders/housework_invalid_tax_reduction_type.yml +0 -61
  295. data/spec/vcr_cassettes/orders/housework_othercoses_invalid.yml +0 -61
  296. data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +0 -65
  297. data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +0 -65
  298. data/spec/vcr_cassettes/orders/housework_type_construction.yml +0 -65
  299. data/spec/vcr_cassettes/orders/housework_type_cooking.yml +0 -61
  300. data/spec/vcr_cassettes/orders/housework_type_electricity.yml +0 -65
  301. data/spec/vcr_cassettes/orders/housework_type_gardening.yml +0 -65
  302. data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +0 -65
  303. data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +0 -65
  304. data/spec/vcr_cassettes/orders/housework_type_hvac.yml +0 -65
  305. data/spec/vcr_cassettes/orders/housework_type_itservices.yml +0 -65
  306. data/spec/vcr_cassettes/orders/housework_type_majorappliancerepair.yml +0 -65
  307. data/spec/vcr_cassettes/orders/housework_type_masonry.yml +0 -65
  308. data/spec/vcr_cassettes/orders/housework_type_movingservices.yml +0 -65
  309. data/spec/vcr_cassettes/orders/housework_type_othercare.yml +0 -65
  310. data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +0 -65
  311. data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +0 -65
  312. data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +0 -65
  313. data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +0 -65
  314. data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +0 -61
  315. data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +0 -63
  316. data/spec/vcr_cassettes/orders/save_new.yml +0 -65
  317. data/spec/vcr_cassettes/orders/save_old.yml +0 -65
  318. data/spec/vcr_cassettes/orders/save_with_nested_model.yml +0 -65
  319. data/spec/vcr_cassettes/orders/search_by_name.yml +0 -63
  320. data/spec/vcr_cassettes/orders/search_miss.yml +0 -62
  321. data/spec/vcr_cassettes/orders/search_with_special_char.yml +0 -62
  322. data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +0 -64
  323. data/spec/vcr_cassettes/projects/all.yml +0 -64
  324. data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +0 -62
  325. data/spec/vcr_cassettes/projects/find_failure.yml +0 -62
  326. data/spec/vcr_cassettes/projects/find_id_1.yml +0 -63
  327. data/spec/vcr_cassettes/projects/find_new.yml +0 -63
  328. data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +0 -64
  329. data/spec/vcr_cassettes/projects/save_new.yml +0 -63
  330. data/spec/vcr_cassettes/projects/save_old.yml +0 -63
  331. data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +0 -63
  332. data/spec/vcr_cassettes/termsofpayments/all.yml +0 -68
  333. data/spec/vcr_cassettes/termsofpayments/find_failure.yml +0 -62
  334. data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +0 -62
  335. data/spec/vcr_cassettes/termsofpayments/find_new.yml +0 -63
  336. data/spec/vcr_cassettes/termsofpayments/save_new.yml +0 -63
  337. data/spec/vcr_cassettes/termsofpayments/save_old.yml +0 -63
  338. data/spec/vcr_cassettes/units/all.yml +0 -64
  339. data/spec/vcr_cassettes/units/find_failure.yml +0 -62
  340. data/spec/vcr_cassettes/units/find_id_1.yml +0 -63
  341. data/spec/vcr_cassettes/units/find_new.yml +0 -63
  342. data/spec/vcr_cassettes/units/save_new.yml +0 -63
  343. data/spec/vcr_cassettes/units/save_old.yml +0 -63
  344. data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +0 -63
data/README.md CHANGED
@@ -1,153 +1,89 @@
1
- # Fortnox API
1
+ # fortnox-api
2
2
 
3
- Wrapper gem for Fortnox AB's version 3 REST(ish) API. If you need to integrate
4
- an existing or new Ruby or Rails app against Fortnox this gem will save you a
5
- lot of time, you are welcome. Feel free to repay the community with some nice
6
- PRs of your own 😃
3
+ Ruby gem for Fortnox's version 3 REST API, built on
4
+ [rest-easy](https://github.com/accodeing/rest-easy). If you need to integrate an existing or new Ruby
5
+ or Rails app against Fortnox this gem will save you a lot of time.
7
6
 
8
- # Status for master
7
+ Feel free to repay the community with some nice PRs of your own.
9
8
 
10
- [![Gem version](https://img.shields.io/gem/v/fortnox-api.svg?style=flat-square)](https://rubygems.org/gems/fortnox-api)
11
- [![Build Status](https://app.travis-ci.com/ehannes/fortnox-api.svg?branch=master)](https://app.travis-ci.com/github/accodeing/fortnox-api)
9
+ ## Supported resources
12
10
 
13
- # Status for development
11
+ Article, Customer, Invoice, Label, Order, Project, TermsOfPayment, Unit
14
12
 
15
- [![Build Status](https://app.travis-ci.com/ehannes/fortnox-api.svg?branch=development)](https://app.travis-ci.com/github/accodeing/fortnox-api)
16
- [![Maintainability](https://api.codeclimate.com/v1/badges/89d30a43fedf210d470b/maintainability)](https://codeclimate.com/github/accodeing/fortnox-api/maintainability)
17
- [![Test Coverage](https://api.codeclimate.com/v1/badges/89d30a43fedf210d470b/test_coverage)](https://codeclimate.com/github/accodeing/fortnox-api/test_coverage)
13
+ Adding more resources is quick and easy, see the
14
+ [Contributing](#contributing) section.
18
15
 
19
- The rough status of this project is as follows (as of spring 2025):
16
+ ## Status
20
17
 
21
- - `master` branch and the released versions is production ready.
22
- - Basic structure complete. Things like getting customers and invoices, updating
23
- and saving etc.
24
- - Some advanced features implemented, for instance support for multiple Fortnox
25
- accounts and filtering entities.
26
- - We have ideas for more advanced features, like sorting entities, pagination of
27
- results but it's not implemented...
28
- - A few models implemented. Right now we pretty good support for `Customer`,
29
- `Invoice`, `Order`, `Article`, `Label` and `Project`. Adding more models in
30
- general is quick and easy (that's the whole point with this gem), see the
31
- developer guide further down.
18
+ Version 1.0 is a complete rewrite, currently in release candidate
19
+ (`1.0.0.rc2`). It is built on
20
+ [rest-easy](https://github.com/accodeing/rest-easy), replacing the old
21
+ HTTParty + Data Mapper architecture with a single resource class per entity.
22
+ Authorization uses the Fortnox client credentials flow.
32
23
 
33
- # Architecture overview
24
+ ## Migrating from 0.x
34
25
 
35
- The gem is structured with distinct models for the tasks of data, JSON mapping
36
- and saving state. These are called: model, type, mapper and repository.
26
+ See the [Migration guide](MIGRATING_TO_1.0.md).
37
27
 
38
- If you come from a Rails background and have not been exposed to other ways of
39
- structuring the solution to the CRUD problem this might seem strange to you
40
- since ActiveRecord merges these roles into the `ActiveRecord::Base` class.
28
+ ## Architecture overview
41
29
 
42
- To keep it simple: The active record pattern (as implemented by Rails) is easier
43
- to work with if you only have one data source, the database, in your
44
- application. The data mapper pattern is easier to work with if you have several
45
- data sources, such as different databases, external APIs and flat files on disk
46
- etc, in your application. It's also easier to compose the data mapper components
47
- into active record like classes than to separate active records parts to get a
48
- data mapper style structure.
49
-
50
- If you are interested in a more detailed description of the difference between
51
- the two architectures you can read this post that explains it well using simple
52
- examples:
53
- [What’s the difference between Active Record and Data Mapper?](http://culttt.com/2014/06/18/whats-difference-active-record-data-mapper/)
54
-
55
- ## Model
56
-
57
- The model role classes serve as dumb data objects. They do have some logic to
58
- coheres values etc, but they do not contain validation logic nor any business
59
- logic at all.
60
-
61
- ### Attribute
62
-
63
- Several of the models share attributes. One example is account, as in a
64
- `Bookkeeping` account number. These attributes have the same definition,
65
- cohesion and validation logic so it makes sense to extract them from the models
66
- and put them in separate classes. For more information, see Types below.
30
+ The gem uses the [rest-easy](https://github.com/accodeing/rest-easy) framework to map between Ruby
31
+ objects and the Fortnox JSON API. Each resource is a class that declares its
32
+ attributes with types and constraints. rest-easy handles the HTTP requests,
33
+ JSON serialisation, and attribute convention mapping (PascalCase in the API,
34
+ snake_case in Ruby).
67
35
 
68
36
  ### Immutability
69
37
 
70
- The model instances are immutable. That means:
38
+ Resource instances are immutable. That means:
71
39
 
72
40
  ```ruby
73
41
  customer.name # => "Old Name"
74
- customer.name = 'New Name' # => "New Name"
75
-
76
- customer.name == "New Name" # => false
42
+ customer.name = 'New Name' # => NoMethodError
77
43
  ```
78
44
 
79
- Normally you would expect an assignment to mutate the instance and update the
80
- `name` field. Immutability explicitly means that you can't mutate state this
81
- way, any operation that attempts to update state needs to return a new instance
82
- with the updated state while leaving the old instance alone.
83
-
84
- So you might think you should do this instead:
85
-
86
- ```ruby
87
- customer = customer.name = 'New Name' # => "New Name"
88
- ```
89
-
90
- But if you are familiar with chaining assignments in Ruby you will see that this
91
- does not work. The result of any assignment, `LHS = RHS`, operation in Ruby is
92
- `RHS`. Even if you implement your own `=` method and explicitly return something
93
- else. This is a feature of the language and not something we can get around. So
94
- instead you have to do:
45
+ Any operation that updates state returns a new instance with the updated
46
+ attributes while leaving the old instance alone:
95
47
 
96
48
  ```ruby
97
49
  customer.name # => "Old Name"
98
- updated_customer = customer.update( name: 'New Name' ) # => <Fortnox::API::Model::Customer:0x007fdf22949298 ... >
99
- updated_customer.name == "New Name" # => true
100
- ```
101
-
102
- And note that:
103
-
104
- ```ruby
50
+ updated_customer = customer.update(name: 'New Name')
51
+ updated_customer.name # => "New Name"
105
52
  customer.name # => "Old Name"
106
- customer.update( name: 'New Name' ) # => <Fortnox::API::Model::Customer:0x007fdf21100b00 ... >
107
- customer.name == "New Name" # => false
108
53
  ```
109
54
 
110
- This is how all the models work, they are all immutable.
111
-
112
- ### Exceptions
113
-
114
- Models can throw `Fortnox::API::AttributeError` if an attribute is invalid in
115
- some way (for instance if you try to assign a too long string to a limited
116
- string attribute) and `Fortnox::API::MissingAttributeError` if a required
117
- attribute is missing.
55
+ This is how all resources work, they are all immutable.
118
56
 
119
- ## Type
57
+ ### Types
120
58
 
121
- The types automatically enforce the constraints on values, lengths and, in some
122
- cases, content of the model attributes. Types forces your models to be correct
123
- before sending data to the API, which saves you a lot of API calls and rescuing
124
- the exception we throw when we get a 4xx/5xx response from the server (you can
59
+ Types automatically enforce constraints on values, lengths and, in some cases,
60
+ content of the resource attributes. Types force your data to be correct before
61
+ sending it to the API, which saves you API calls and time debugging. You can
125
62
  still get errors from the server; our implementation is not perfect. Also,
126
- Fortnox sometimes requires a specific combination of attributes).
63
+ Fortnox sometimes requires a specific combination of attributes.
127
64
 
128
- ## Repositories
65
+ #### Exceptions
129
66
 
130
- Used to load, update, create and delete model instances. These are what is
131
- actually wrapping the HTTP REST API requests against Fortnox's server.
67
+ The gem raises the following exceptions:
132
68
 
133
- ### Exceptions
69
+ - `Fortnox::Error` — base class for everything below. Rescue this to catch
70
+ any error raised by the gem.
71
+ - `Fortnox::RequestError` — 4xx/5xx responses from the Fortnox API. Carries
72
+ the response object as `.response`.
73
+ - `Fortnox::AttributeError` — base for attribute validation failures.
74
+ - `Fortnox::ConstraintError` — an attribute value violates a type
75
+ constraint (max size, format, etc.). Carries `.attribute_name` and
76
+ `.value`.
77
+ - `Fortnox::MissingAttributeError` — a required attribute is missing
78
+ from an API response. Carries `.attribute_name`.
79
+ - `Fortnox::MissingAccessToken` — `Fortnox.access_token=` was not called
80
+ on the current thread before an API call.
134
81
 
135
- Repositories can throw `Fortnox::API::RemoteServerError` if something went wrong
136
- at Fortnox.
82
+ ## Requirements
137
83
 
138
- ## Mappers
84
+ Ruby 3.1 or higher.
139
85
 
140
- These are responsible for the mapping between our plain old Ruby object models
141
- and Fortnox JSON requests. The repositories use the mappers to map models to
142
- JSON requests and JSON to model instances when working with the Fortnox API, you
143
- will not need to use them directly.
144
-
145
- # Requirements
146
-
147
- This gem is built for Ruby 2.6 or higher (see Travis configuration file for what
148
- versions we are testing against).
149
-
150
- ## Installation
86
+ ### Installation
151
87
 
152
88
  Add this line to your application's Gemfile:
153
89
 
@@ -158,198 +94,259 @@ gem 'fortnox-api'
158
94
  And then execute:
159
95
 
160
96
  ```shell
161
- $ bundle
97
+ bundle install
162
98
  ```
163
99
 
164
- Or install it yourself as:
100
+ ## Authorization
101
+
102
+ Fortnox uses OAuth2 for authorization. This gem supports the **client
103
+ credentials** flow, which is the recommended way to authenticate server-to-server
104
+ integrations. The older refresh token flow is no longer supported. Read more
105
+ about this change in the
106
+ [Fortnox blog post](https://www.fortnox.se/developer/blog/say-goodbye-to-refresh-tokens-).
107
+
108
+ With client credentials you can request a new access token at any time using
109
+ three pieces of information: your **client ID**, **client secret**, and the
110
+ **tenant ID** of the Fortnox account you are integrating with.
111
+
112
+ ### Prerequisites
113
+
114
+ You need:
115
+
116
+ - A Fortnox developer account
117
+ ([register here](https://developer.fortnox.se/getting-started/))
118
+ - A Fortnox app in the developer portal with:
119
+ - Service account setting enabled
120
+ - Correct scopes configured
121
+ - A redirect URL
122
+ - A Fortnox test environment for testing your integration
123
+
124
+ Read the
125
+ [Fortnox getting started guide](https://developer.fortnox.se/getting-started/)
126
+ and
127
+ [authorization documentation](https://www.fortnox.se/developer/authorization/get-access-token-using-client-credentials)
128
+ for more details.
129
+
130
+ ### Initial setup
131
+
132
+ Before you can use client credentials you need to perform a one-time
133
+ authorization code exchange. This grants your app access to a specific Fortnox
134
+ account and gives you the **tenant ID** you need for all future token requests.
135
+
136
+ This gem includes an executable to help with this:
165
137
 
166
138
  ```shell
167
- $ gem install fortnox-api
139
+ fortnox-setup
168
140
  ```
169
141
 
170
- # Usage
171
-
172
- ## Authorization
142
+ The script will:
173
143
 
174
- > :warning: Before 2022, Fortnox used a client ID and a fixed access token for
175
- > authorization. This way of is now deprecated. The old access tokens will be
176
- > deprecated April 30, 2025 according to Fortnox. This gem will no longer support
177
- > the old way of authorization from v0.9.0.
144
+ 1. Ask for your client ID and client secret.
145
+ 2. List the OAuth scopes covered by the gem's resources and ask which ones
146
+ you need. Enter a space-separated list, or `all` for everything the gem
147
+ supports. You can also enter scopes the gem doesn't expose directly
148
+ if you plan to call those endpoints manually.
149
+ 3. Offer to use a local server to catch the authorization response automatically.
150
+ If you choose this, set your Fortnox app's redirect URL to `http://localhost:4242`.
151
+ Otherwise, enter your existing redirect URL and paste the authorization code manually.
152
+ 4. Open your browser to the Fortnox authorization page.
153
+ 5. You log in to Fortnox and grant your app access.
154
+ 6. The script exchanges the authorization code for an access token and extracts
155
+ the tenant ID from the JWT.
156
+ 7. The tenant ID is printed for you to store in your application's configuration.
178
157
 
179
- You need to have a Fortnox app and to create such an app, you need to register
180
- as a Fortnox developer. It might feel as if "I just want to create an
181
- integration to Fortnox, not build a public app to in the marketplace". Yeah, we
182
- agree... You don't need to release the app on the Fortnox Marketplace, but you
183
- need that Fortnox app. Also, see further Fortnox app requirements down below.
158
+ After this you have a tenant ID and never need to run this script again (unless
159
+ you need to authorize against a different Fortnox account).
184
160
 
185
- Start your journey at
186
- [Fortnox getting started guide](https://developer.fortnox.se/getting-started/).
187
- Note that there's a script to authorize the Fortnox app to your Fortnox account
188
- bundled with this gem to help you getting started, see
189
- [Initialization](#initialization). Also read
190
- [Authorizing your integration](https://developer.fortnox.se/general/authentication/).
161
+ Note: If you change the integration configuration in Fortnox it takes some time for
162
+ Fortnox to propagate the changes (for instance changing the Redirect URI or the scope).
191
163
 
192
- Things you need:
164
+ ### Requesting access tokens
193
165
 
194
- - A Fortnox developer account
195
- - A Fortnox app with:
196
- - Service account setting enabled (it's used in server to server integrations,
197
- which this is)
198
- - Correct scopes set
199
- - A redirect URL (just use a dummy URL if you want to, you just need the
200
- parameters send to that URL)
201
- - A Fortnox test environment so that you can test your integration.
202
-
203
- When you have authorized your integration you get an access token from Fortnox.
204
- It's a JWT with a expiration time (currently **1 hour**). You also get a long
205
- lived refresh token (currently lasts for **45 days** ). When you need a new
206
- access token you send a renewal request to Fortnox. That request contains the
207
- new access token as well as a new refresh token and some other data. Note that
208
- **the old refresh token is invalidated when new tokens are requested**. As long
209
- as you have a valid refresh token you will be available to request new tokens.
210
-
211
- The gem exposes a specific repository for renewing tokens. You use it like this:
166
+ Once you have a tenant ID, you can request access tokens programmatically.
167
+ Access tokens expire after 1 hour, but you can request a new one at any time:
212
168
 
213
169
  ```ruby
214
- require 'fortnox/api'
170
+ require 'fortnox'
215
171
 
216
- tokens = Fortnox::API::Repository::Authentication.new.renew_tokens(
217
- refresh_token: 'a valid refresh token',
218
- client_id: "the integration's client id",
219
- client_secret: "the integration's client secret"
172
+ token = Fortnox.request_access_token(
173
+ client_id: 'your-client-id',
174
+ client_secret: 'your-client-secret',
175
+ tenant_id: 'your-tenant-id'
220
176
  )
221
177
 
222
- # You probably want to persist your tokens somehow...
223
- store_refresh_token(tokens[:refresh_token])
224
- store_access_token(tokens[:access_token])
178
+ Fortnox.access_token = token
179
+ ```
180
+
181
+ It is up to you to manage the token lifecycle in your application. A common
182
+ approach is to request a new token before each batch of API calls, or to cache
183
+ the token and refresh it when it expires.
184
+
185
+ Note: Fortnox only allows one active access token per integration. Requesting a
186
+ new token invalidates the previous one. If you need multiple active access
187
+ tokens in parallel, you need a separate integration (client ID and secret) for
188
+ each token.
189
+
190
+ ### Updating access tokens in env files
225
191
 
226
- # Set the new access token
227
- Fortnox::API.access_token = tokens[:access_token]
192
+ For development and testing, the gem includes an executable that reads your
193
+ credentials from an env file, requests a new access token, and writes it back:
228
194
 
229
- # The gem will now use the new access token
230
- Fortnox::API::Repository::Customer.new.all
195
+ ```shell
196
+ fortnox-update-env # reads/writes .env
197
+ fortnox-update-env .env.local # reads/writes a specific file
231
198
  ```
232
199
 
233
- It's up to you to provide a valid token to the gem and to renew it regularly,
234
- otherwise you need to start over again with the
235
- [Initialization](#initialization).
200
+ See [.env.test.local.template](.env.test.local.template) for the required
201
+ variables.
202
+
203
+ ### Multiple Fortnox accounts
236
204
 
237
- ## Get tokens
205
+ The access token is stored per thread, so concurrent threads — Sidekiq
206
+ workers, Puma threads, etc. — can use different tokens without leaking to
207
+ each other. Each thread must set its own token before making API calls.
238
208
 
239
- There's a script in `bin/get_tokens` to issue valid access and refresh tokens.
240
- Provide valid credentials in `.env`, see `.env.template` or have a look in the
241
- script itself to see what's needed.
209
+ Within a single thread you can switch tokens between calls. Each call uses
210
+ the token currently set on the calling thread:
242
211
 
243
- ### Configuration
212
+ ```ruby
213
+ Fortnox.access_token = 'account1_token'
214
+ Fortnox::Customer.all
244
215
 
245
- The gem can be configured in a `configure` block, where `setting` is one of the
246
- settings from the table below.
216
+ Fortnox.access_token = 'account2_token'
217
+ Fortnox::Customer.all
218
+ ```
219
+
220
+ ## Usage
221
+
222
+ Set the access token before making any API calls:
247
223
 
248
224
  ```ruby
249
- Fortnox::API.configure do |config|
250
- config.setting = 'value'
251
- end
225
+ require 'fortnox'
226
+
227
+ Fortnox.access_token = 'your-access-token'
252
228
  ```
253
229
 
254
- | Setting | Description | Required | Default |
255
- | ----------- | --------------------------------- | -------- | --------------------------------------------------------------- |
256
- | `base_url` | The base url to Fortnox API | No | `'https://api.fortnox.se/3/'` |
257
- | `token_url` | The url to Fortnox token endpoint | No | `'https://apps.fortnox.se/oauth-v1/token'` |
258
- | `debugging` | For debugging | No | `false` |
259
- | `logger` | The logger to use | No | A simple logger that writes to `$stdout` with log level `WARN`. |
230
+ ### Listing all records
260
231
 
261
- ### Support for multiple Fortnox accounts
232
+ ```ruby
233
+ Fortnox::Customer.all
234
+ ```
262
235
 
263
- Yes, we support working with several accounts at once. Simply switch access
264
- token between calls. The token is stored in the current thread, so it's thread
265
- safe.
236
+ `.all`, `.search`, `.only`, and `.find(hash)` return a `Fortnox::Collection`
237
+ an iterable wrapper that also exposes the pagination metadata Fortnox
238
+ returns alongside collection responses:
266
239
 
267
240
  ```ruby
268
- repository = Fortnox::API::Repository::Customer.new
241
+ customers = Fortnox::Customer.all
242
+ customers.first.name # => "Acme Corp"
243
+ customers.size # => 50
244
+ customers.total # => 327
245
+ customers.pages # => 7
246
+ customers.current_page # => 1
247
+ ```
269
248
 
270
- Fortnox::API.access_token = 'account1_access_token'
271
- repository.all # Calls account1
249
+ `Collection` is `Enumerable`, so `.each`, `.map`, `.select`, `.first`, etc.
250
+ all work as expected.
272
251
 
273
- Fortnox::API.access_token = 'account2_access_token'
274
- repository.all # Calls account2
252
+ Fortnox's collection endpoints return fewer attributes per record than
253
+ single-resource endpoints, so instances from a `Collection` are flagged as
254
+ partial. Check `instance.meta.partial?` and re-fetch via `find(id)` if you
255
+ need the full record:
256
+
257
+ ```ruby
258
+ customers = Fortnox::Customer.all
259
+ customers.first.meta.partial? # => true
260
+ Fortnox::Customer.find(1).meta.partial? # => false
275
261
  ```
276
262
 
277
- ### Automatic access tokens rotation (deprecated)
263
+ ### Finding a record
278
264
 
279
- As of november 2021 and the new OAuth 2 flow, Fortnox has made
280
- [adjustments to the rate limit](https://developer.fortnox.se/blog/adjustments-to-the-rate-limit/)
281
- and it is no longer calculated per access token (if you are not using the old
282
- auth flow, but that flow is deprecated in this gem since v0.9.0).
265
+ ```ruby
266
+ # By ID
267
+ customer = Fortnox::Customer.find(1)
283
268
 
284
- # Usage
269
+ # By query parameters (pagination, limits, etc.)
270
+ customers = Fortnox::Customer.find(limit: 10, offset: 0)
271
+ ```
285
272
 
286
- ## Repositories
273
+ Note that `find` supports a hash as an argument, which adds the given keys as
274
+ HTTP parameters to the call. This lets you use limits, offsets, and pagination.
275
+ See the
276
+ [Fortnox documentation](https://developer.fortnox.se/general/parameters/)
277
+ for available parameters.
287
278
 
288
- Repositories are used to load,save and remove entities from the remote server.
289
- The calls are subject to network latency and are blocking. Do make sure to
290
- rescue appropriate network errors in your code.
279
+ Attributes are exposed directly on the returned instance:
291
280
 
292
281
  ```ruby
293
- require 'fortnox/api'
282
+ customer = Fortnox::Customer.find(1)
283
+ customer.name # => "Acme Corp"
284
+ customer.city # => "Stockholm"
285
+ customer.unique_id # => "1"
286
+ ```
287
+
288
+ ### Creating a record
294
289
 
295
- Fortnox::API.access_token = 'valid_access_token'
290
+ Use `.stub` to build a new instance and `.save` to persist it:
296
291
 
297
- # Instanciate a repository
298
- repo = Fortnox::API::Repository::Customer.new
292
+ ```ruby
293
+ customer = Fortnox::Customer.stub(name: 'Acme Corp', city: 'Stockholm')
294
+ result = Fortnox::Customer.save(customer)
295
+ result.customer_number # => "1"
296
+ ```
299
297
 
300
- # Get a list of all the entities
301
- repo.all #=> <Fortnox::API::Collection:0x007fdf2104575638 @entities: [<Fortnox::API::Customer::Simple:0x007fdf21033ee8>, <Fortnox::API::Customer::Simple:0x007fdf22994310>, ... ]
298
+ ### Updating a record
302
299
 
303
- # Get entity by id
304
- repo.find( 5 ) #=> <Fortnox::API::Model::Customer:0x007fdf21100b00>
300
+ Fetch, update, and save. Models are immutable, so `.update` returns a new
301
+ instance:
305
302
 
306
- # Get entities by attribute
307
- repo.find_by( customer_number: 5 ) #=> <Fortnox::API::Collection:0x007fdf22994310 @entities: [<Fortnox::API::Customer::Simple:0x007fdf22949298>]
303
+ ```ruby
304
+ customer = Fortnox::Customer.find(1)
305
+ updated = customer.update(name: 'Acme Inc')
306
+ Fortnox::Customer.save(updated)
308
307
  ```
309
308
 
310
- If you are eagle eyed you might have spotted the different classes for the
311
- entities returned in a collection vs the one we get from find. The simple
312
- version of a class is used in thouse cases where the API-server doesn't return a
313
- full set of attributes for an entity. For customers the simple version has 10
314
- attributes while the full have over 40.
309
+ ### Searching
310
+
311
+ ```ruby
312
+ Fortnox::Customer.search(name: 'Acme')
313
+ ```
315
314
 
316
- ### Additional parameters
315
+ ### Filtering
317
316
 
318
- Note that `find` also supports a hash as an argument, which simply adds given
319
- hash keys as HTTP parameters to the call. This will let you search, sort, use
320
- limits and offsets as well as do pagination. See
321
- [Fortnox documentation](https://developer.fortnox.se/general/parameters/) for
322
- more information about available parameters.
317
+ Some resources support server-side filters:
323
318
 
324
319
  ```ruby
325
- # Get second page of Customers
326
- Fortnox::API::Repository::Customer.new.find { page: 2 }
320
+ Fortnox::Invoice.only('unpaid')
327
321
  ```
328
322
 
329
- ## Entities
323
+ ### Gotchas
330
324
 
331
- All the repository methods return instances or collections of instances of some
332
- resource class such as customer, invoice, item, voucher and so on.
325
+ See [docs/gotchas.md](docs/gotchas.md) for known quirks and edge cases in the
326
+ Fortnox API.
333
327
 
334
- Instances are immutable and any update returns a new instance with the
335
- appropriate attributes changed (see the Immutable section under Architecture
336
- above for more details). To change the properties of a model works like this:
328
+ ### Resources
337
329
 
338
- ```ruby
339
- require 'fortnox/api'
330
+ Each resource maps to a Fortnox API endpoint. Attributes are typed and
331
+ validated before sending data to the API. Read-only attributes (like `url`)
332
+ cannot be set when creating or updating records.
340
333
 
341
- customer #=> <Fortnox::API::Model::Customer:0x007fdf228db310>
342
- customer.name #=> "Nelly Bloom"
343
- customer.update( name: "Ned Stark" ) #=> <Fortnox::API::Model::Customer:0x0193a456ff0307>
344
- customer.name #=> "Nelly Bloom"
334
+ Resources that share structure inherit from a common base. For example,
335
+ `Invoice` and `Order` both extend `Document`, which defines shared attributes
336
+ like administration fee, delivery address, and row items.
345
337
 
346
- updated_customer = customer.update( name: "Ned Stark" ) #=> <Fortnox::API::Model::Customer:0x0193a456fe3791>
347
- updated_customer.name #=> "Ned Stark"
348
- ```
338
+ ## Changelog
339
+
340
+ See the [Changelog](CHANGELOG.md).
341
+
342
+ ## Development
343
+
344
+ See the [Developer readme](DEVELOPER_README.md).
345
+
346
+ ## Contributing
349
347
 
350
- The update method takes an implicit hash of attributes to update, so you can
351
- update as many as you like in one go.
348
+ See the [Contribute readme](CONTRIBUTE.md).
352
349
 
353
- # Contributing
350
+ ## License
354
351
 
355
- See the [CONTRIBUTE](CONTRIBUTE.md) readme.
352
+ [LGPL-3.0](LICENSE.md). Copyright (c) 2015-2026 Accodeing to you KB.