fortnox-api 0.9.2 → 1.0.0.rc1

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 +78 -100
  3. data/{LICENSE.txt → LICENSE.md} +0 -0
  4. data/README.md +234 -248
  5. data/bin/fortnox-setup +195 -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 +121 -0
  21. data/lib/fortnox/resources/article.rb +159 -0
  22. data/lib/fortnox/resources/customer.rb +208 -0
  23. data/lib/fortnox/resources/document.rb +202 -0
  24. data/lib/fortnox/resources/invoice.rb +91 -0
  25. data/lib/fortnox/resources/label.rb +14 -0
  26. data/lib/fortnox/resources/order.rb +37 -0
  27. data/lib/fortnox/resources/project.rb +40 -0
  28. data/lib/fortnox/resources/terms_of_payment.rb +22 -0
  29. data/lib/fortnox/resources/unit.rb +25 -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 +102 -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,355 +1,341 @@
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.rc1`). 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.
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).
49
35
 
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/)
36
+ ### Immutability
54
37
 
55
- ## Model
38
+ The model instances are immutable. That means:
56
39
 
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.
40
+ ```ruby
41
+ customer.model.name # => "Old Name"
42
+ customer.model.name = 'New Name' # => NoMethodError
43
+ ```
60
44
 
61
- ### Attribute
45
+ Any operation that updates state returns a new instance with the updated
46
+ attributes while leaving the old instance alone:
62
47
 
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.
48
+ ```ruby
49
+ customer.model.name # => "Old Name"
50
+ updated_customer = customer.update(name: 'New Name')
51
+ updated_customer.model.name # => "New Name"
52
+ customer.model.name # => "Old Name"
53
+ ```
67
54
 
68
- ### Immutability
55
+ This is how all resources work, they are all immutable.
69
56
 
70
- The model instances are immutable. That means:
57
+ ### Types
71
58
 
72
- ```ruby
73
- customer.name # => "Old Name"
74
- customer.name = 'New Name' # => "New Name"
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
62
+ still get errors from the server; our implementation is not perfect. Also,
63
+ Fortnox sometimes requires a specific combination of attributes.
75
64
 
76
- customer.name == "New Name" # => false
77
- ```
65
+ #### Exceptions
78
66
 
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.
67
+ The gem raises the following exceptions:
83
68
 
84
- So you might think you should do this instead:
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.
85
81
 
86
- ```ruby
87
- customer = customer.name = 'New Name' # => "New Name"
88
- ```
82
+ ## Requirements
83
+
84
+ Ruby 3.1 or higher.
85
+
86
+ ### Installation
89
87
 
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:
88
+ Add this line to your application's Gemfile:
95
89
 
96
90
  ```ruby
97
- customer.name # => "Old Name"
98
- updated_customer = customer.update( name: 'New Name' ) # => <Fortnox::API::Model::Customer:0x007fdf22949298 ... >
99
- updated_customer.name == "New Name" # => true
91
+ gem 'fortnox-api'
100
92
  ```
101
93
 
102
- And note that:
94
+ And then execute:
103
95
 
104
- ```ruby
105
- customer.name # => "Old Name"
106
- customer.update( name: 'New Name' ) # => <Fortnox::API::Model::Customer:0x007fdf21100b00 ... >
107
- customer.name == "New Name" # => false
96
+ ```shell
97
+ bundle install
108
98
  ```
109
99
 
110
- This is how all the models work, they are all immutable.
100
+ ## Authorization
111
101
 
112
- ### Exceptions
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-).
113
107
 
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.
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.
118
111
 
119
- ## Type
112
+ ### Prerequisites
120
113
 
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
125
- still get errors from the server; our implementation is not perfect. Also,
126
- Fortnox sometimes requires a specific combination of attributes).
114
+ You need:
127
115
 
128
- ## Repositories
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
129
123
 
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.
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.
132
129
 
133
- ### Exceptions
130
+ ### Initial setup
134
131
 
135
- Repositories can throw `Fortnox::API::RemoteServerError` if something went wrong
136
- at Fortnox.
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.
137
135
 
138
- ## Mappers
136
+ This gem includes an executable to help with this:
139
137
 
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.
138
+ ```shell
139
+ fortnox-setup
140
+ ```
144
141
 
145
- # Requirements
142
+ The script will:
146
143
 
147
- This gem is built for Ruby 2.6 or higher (see Travis configuration file for what
148
- versions we are testing against).
144
+ 1. Ask for your client ID, client secret, and scopes
145
+ 2. Offer to use a local server on `http://localhost:4242` to catch the
146
+ authorization response automatically. If you choose this, set your Fortnox
147
+ app's redirect URL to `http://localhost:4242`. Otherwise, enter your existing
148
+ redirect URL and paste the authorization code manually.
149
+ 3. Open your browser to the Fortnox authorization page
150
+ 4. You log in to Fortnox and grant your app access
151
+ 5. The script exchanges the authorization code for an access token and extracts
152
+ the tenant ID from the JWT
153
+ 6. The tenant ID is printed for you to store in your application's configuration
149
154
 
150
- ## Installation
155
+ After this you have a tenant ID and never need to run this script again (unless
156
+ you need to authorize against a different Fortnox account).
151
157
 
152
- Add this line to your application's Gemfile:
158
+ ### Requesting access tokens
159
+
160
+ Once you have a tenant ID, you can request access tokens programmatically.
161
+ Access tokens expire after 1 hour, but you can request a new one at any time:
153
162
 
154
163
  ```ruby
155
- gem 'fortnox-api'
156
- ```
164
+ require 'fortnox'
157
165
 
158
- And then execute:
166
+ token = Fortnox.request_access_token(
167
+ client_id: 'your-client-id',
168
+ client_secret: 'your-client-secret',
169
+ tenant_id: 'your-tenant-id'
170
+ )
159
171
 
160
- ```shell
161
- $ bundle
172
+ Fortnox.access_token = token
162
173
  ```
163
174
 
164
- Or install it yourself as:
175
+ It is up to you to manage the token lifecycle in your application. A common
176
+ approach is to request a new token before each batch of API calls, or to cache
177
+ the token and refresh it when it expires.
178
+
179
+ ### Updating access tokens in env files
180
+
181
+ For development and testing, the gem includes an executable that reads your
182
+ credentials from an env file, requests a new access token, and writes it back:
165
183
 
166
184
  ```shell
167
- $ gem install fortnox-api
185
+ fortnox-update-env # reads/writes .env
186
+ fortnox-update-env .env.local # reads/writes a specific file
168
187
  ```
169
188
 
170
- # Usage
189
+ See [.env.test.local.template](.env.test.local.template) for the required
190
+ variables.
171
191
 
172
- ## Authorization
192
+ ### Multiple Fortnox accounts
173
193
 
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.
194
+ The access token is stored per thread, so concurrent threads Sidekiq
195
+ workers, Puma threads, etc. can use different tokens without leaking to
196
+ each other. Each thread must set its own token before making API calls.
178
197
 
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.
198
+ Within a single thread you can switch tokens between calls. Each call uses
199
+ the token currently set on the calling thread:
184
200
 
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/).
201
+ ```ruby
202
+ Fortnox.access_token = 'account1_token'
203
+ Fortnox::Customer.all
191
204
 
192
- Things you need:
205
+ Fortnox.access_token = 'account2_token'
206
+ Fortnox::Customer.all
207
+ ```
193
208
 
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:
209
+ ## Usage
212
210
 
213
- ```ruby
214
- require 'fortnox/api'
211
+ Set the access token before making any API calls:
215
212
 
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"
220
- )
213
+ ```ruby
214
+ require 'fortnox'
221
215
 
222
- # You probably want to persist your tokens somehow...
223
- store_refresh_token(tokens[:refresh_token])
224
- store_access_token(tokens[:access_token])
216
+ Fortnox.access_token = 'your-access-token'
217
+ ```
225
218
 
226
- # Set the new access token
227
- Fortnox::API.access_token = tokens[:access_token]
219
+ ### Listing all records
228
220
 
229
- # The gem will now use the new access token
230
- Fortnox::API::Repository::Customer.new.all
221
+ ```ruby
222
+ Fortnox::Customer.all
231
223
  ```
232
224
 
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).
236
-
237
- ## Get tokens
225
+ `.all`, `.search`, `.only`, and `.find(hash)` return a `Fortnox::Collection`
226
+ an iterable wrapper that also exposes the pagination metadata Fortnox
227
+ returns alongside collection responses:
238
228
 
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.
229
+ ```ruby
230
+ customers = Fortnox::Customer.all
231
+ customers.first.model.name # => "Acme Corp"
232
+ customers.size # => 50
233
+ customers.total # => 327
234
+ customers.pages # => 7
235
+ customers.current_page # => 1
236
+ ```
242
237
 
243
- ### Configuration
238
+ `Collection` is `Enumerable`, so `.each`, `.map`, `.select`, `.first`, etc.
239
+ all work as expected.
244
240
 
245
- The gem can be configured in a `configure` block, where `setting` is one of the
246
- settings from the table below.
241
+ Fortnox's collection endpoints return fewer attributes per record than
242
+ single-resource endpoints, so instances from a `Collection` are flagged as
243
+ partial. Check `instance.meta.partial?` and re-fetch via `find(id)` if you
244
+ need the full record:
247
245
 
248
246
  ```ruby
249
- Fortnox::API.configure do |config|
250
- config.setting = 'value'
251
- end
247
+ customers = Fortnox::Customer.all
248
+ customers.first.meta.partial? # => true
249
+ Fortnox::Customer.find(1).meta.partial? # => false
252
250
  ```
253
251
 
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`. |
252
+ ### Finding a record
260
253
 
261
- ### Support for multiple Fortnox accounts
254
+ ```ruby
255
+ # By ID
256
+ customer = Fortnox::Customer.find(1)
262
257
 
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.
258
+ # By query parameters (pagination, limits, etc.)
259
+ customers = Fortnox::Customer.find(limit: 10, offset: 0)
260
+ ```
266
261
 
267
- ```ruby
268
- repository = Fortnox::API::Repository::Customer.new
262
+ Note that `find` supports a hash as an argument, which adds the given keys as
263
+ HTTP parameters to the call. This lets you use limits, offsets, and pagination.
264
+ See the
265
+ [Fortnox documentation](https://developer.fortnox.se/general/parameters/)
266
+ for available parameters.
269
267
 
270
- Fortnox::API.access_token = 'account1_access_token'
271
- repository.all # Calls account1
268
+ The returned object wraps the model. Access attributes through `.model`:
272
269
 
273
- Fortnox::API.access_token = 'account2_access_token'
274
- repository.all # Calls account2
270
+ ```ruby
271
+ customer = Fortnox::Customer.find(1)
272
+ customer.model.name # => "Acme Corp"
273
+ customer.model.city # => "Stockholm"
274
+ customer.unique_id # => "1"
275
275
  ```
276
276
 
277
- ### Automatic access tokens rotation (deprecated)
277
+ ### Creating a record
278
278
 
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).
279
+ Use `.stub` to build a new instance and `.save` to persist it:
283
280
 
284
- # Usage
281
+ ```ruby
282
+ customer = Fortnox::Customer.stub(name: 'Acme Corp', city: 'Stockholm')
283
+ result = Fortnox::Customer.save(customer)
284
+ result.model.customer_number # => "1"
285
+ ```
285
286
 
286
- ## Repositories
287
+ ### Updating a record
287
288
 
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.
289
+ Fetch, update, and save. Models are immutable, so `.update` returns a new
290
+ instance:
291
291
 
292
292
  ```ruby
293
- require 'fortnox/api'
293
+ customer = Fortnox::Customer.find(1)
294
+ updated = customer.update(name: 'Acme Inc')
295
+ Fortnox::Customer.save(updated)
296
+ ```
294
297
 
295
- Fortnox::API.access_token = 'valid_access_token'
298
+ ### Searching
296
299
 
297
- # Instanciate a repository
298
- repo = Fortnox::API::Repository::Customer.new
300
+ ```ruby
301
+ Fortnox::Customer.search(name: 'Acme')
302
+ ```
299
303
 
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>, ... ]
304
+ ### Filtering
302
305
 
303
- # Get entity by id
304
- repo.find( 5 ) #=> <Fortnox::API::Model::Customer:0x007fdf21100b00>
306
+ Some resources support server-side filters:
305
307
 
306
- # Get entities by attribute
307
- repo.find_by( customer_number: 5 ) #=> <Fortnox::API::Collection:0x007fdf22994310 @entities: [<Fortnox::API::Customer::Simple:0x007fdf22949298>]
308
+ ```ruby
309
+ Fortnox::Invoice.only('unpaid')
308
310
  ```
309
311
 
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.
312
+ ### Gotchas
315
313
 
316
- ### Additional parameters
314
+ See [docs/gotchas.md](docs/gotchas.md) for known quirks and edge cases in the
315
+ Fortnox API.
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
+ ### Resources
323
318
 
324
- ```ruby
325
- # Get second page of Customers
326
- Fortnox::API::Repository::Customer.new.find { page: 2 }
327
- ```
319
+ Each resource maps to a Fortnox API endpoint. Attributes are typed and
320
+ validated before sending data to the API. Read-only attributes (like `url`)
321
+ cannot be set when creating or updating records.
328
322
 
329
- ## Entities
323
+ Resources that share structure inherit from a common base. For example,
324
+ `Invoice` and `Order` both extend `Document`, which defines shared attributes
325
+ like administration fee, delivery address, and row items.
330
326
 
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.
327
+ ## Changelog
333
328
 
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:
329
+ See the [Changelog](CHANGELOG.md).
337
330
 
338
- ```ruby
339
- require 'fortnox/api'
331
+ ## Development
340
332
 
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"
333
+ See the [Developer readme](DEVELOPER_README.md).
345
334
 
346
- updated_customer = customer.update( name: "Ned Stark" ) #=> <Fortnox::API::Model::Customer:0x0193a456fe3791>
347
- updated_customer.name #=> "Ned Stark"
348
- ```
335
+ ## Contributing
349
336
 
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.
337
+ See the [Contribute readme](CONTRIBUTE.md).
352
338
 
353
- # Contributing
339
+ ## License
354
340
 
355
- See the [CONTRIBUTE](CONTRIBUTE.md) readme.
341
+ [LGPL-3.0](LICENSE.md). Copyright (c) 2015-2026 Accodeing to you KB.