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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +120 -95
- data/{LICENSE.txt → LICENSE.md} +0 -0
- data/README.md +245 -248
- data/bin/fortnox-setup +211 -0
- data/bin/fortnox-update-env +38 -0
- data/fortnox.gemspec +29 -0
- data/lib/fortnox/auth/thread_local.rb +28 -0
- data/lib/fortnox/collection.rb +23 -0
- data/lib/fortnox/mappers/country_code.rb +34 -0
- data/lib/fortnox/mappers/date.rb +21 -0
- data/lib/fortnox/mappers/document_row.rb +12 -0
- data/lib/fortnox/mappers/edi_information.rb +15 -0
- data/lib/fortnox/mappers/email_information.rb +11 -0
- data/lib/fortnox/mappers/invoice_row.rb +14 -0
- data/lib/fortnox/mappers/label_references.rb +24 -0
- data/lib/fortnox/mappers/order_row.rb +9 -0
- data/lib/fortnox/mappers/struct.rb +71 -0
- data/lib/fortnox/mappers/struct_array.rb +31 -0
- data/lib/fortnox/resource.rb +131 -0
- data/lib/fortnox/resources/article.rb +160 -0
- data/lib/fortnox/resources/customer.rb +209 -0
- data/lib/fortnox/resources/document.rb +202 -0
- data/lib/fortnox/resources/invoice.rb +92 -0
- data/lib/fortnox/resources/label.rb +15 -0
- data/lib/fortnox/resources/order.rb +38 -0
- data/lib/fortnox/resources/project.rb +41 -0
- data/lib/fortnox/resources/terms_of_payment.rb +23 -0
- data/lib/fortnox/resources/unit.rb +26 -0
- data/lib/fortnox/struct.rb +24 -0
- data/lib/fortnox/structs/default_delivery_types.rb +16 -0
- data/lib/fortnox/structs/default_templates.rb +19 -0
- data/lib/fortnox/structs/document_row.rb +62 -0
- data/lib/fortnox/structs/edi_information.rb +25 -0
- data/lib/fortnox/structs/email_information.rb +22 -0
- data/lib/fortnox/structs/invoice_row.rb +13 -0
- data/lib/fortnox/structs/order_row.rb +10 -0
- data/lib/fortnox/types.rb +129 -0
- data/lib/fortnox/{api/version.rb → version.rb} +1 -3
- data/lib/fortnox.rb +111 -0
- metadata +55 -580
- data/.codeclimate.yml +0 -20
- data/.env.template +0 -7
- data/.env.test +0 -11
- data/.gitignore +0 -20
- data/.rspec +0 -2
- data/.rubocop.yml +0 -41
- data/.tool-versions +0 -1
- data/.travis.yml +0 -32
- data/CLAUDE.md +0 -79
- data/CONTRIBUTE.md +0 -38
- data/DEVELOPER_README.md +0 -69
- data/Gemfile +0 -6
- data/Guardfile +0 -16
- data/Rakefile +0 -140
- data/bin/console +0 -22
- data/bin/fortnox +0 -285
- data/bin/get_tokens +0 -79
- data/bin/renew_tokens +0 -28
- data/docs/gotchas.md +0 -146
- data/fortnox-api.gemspec +0 -50
- data/lib/fortnox/api/mappers/article.rb +0 -23
- data/lib/fortnox/api/mappers/base/canonical_name_sym.rb +0 -21
- data/lib/fortnox/api/mappers/base/from_json.rb +0 -91
- data/lib/fortnox/api/mappers/base/to_json.rb +0 -66
- data/lib/fortnox/api/mappers/base.rb +0 -30
- data/lib/fortnox/api/mappers/customer.rb +0 -27
- data/lib/fortnox/api/mappers/default_delivery_types.rb +0 -15
- data/lib/fortnox/api/mappers/default_templates.rb +0 -16
- data/lib/fortnox/api/mappers/edi_information.rb +0 -23
- data/lib/fortnox/api/mappers/email_information.rb +0 -19
- data/lib/fortnox/api/mappers/invoice.rb +0 -29
- data/lib/fortnox/api/mappers/invoice_row.rb +0 -25
- data/lib/fortnox/api/mappers/order.rb +0 -26
- data/lib/fortnox/api/mappers/order_row.rb +0 -23
- data/lib/fortnox/api/mappers/project.rb +0 -17
- data/lib/fortnox/api/mappers/terms_of_payment.rb +0 -17
- data/lib/fortnox/api/mappers/unit.rb +0 -17
- data/lib/fortnox/api/mappers/value/array.rb +0 -18
- data/lib/fortnox/api/mappers/value/country_string.rb +0 -24
- data/lib/fortnox/api/mappers/value/date.rb +0 -11
- data/lib/fortnox/api/mappers/value/hash.rb +0 -16
- data/lib/fortnox/api/mappers/value/identity.rb +0 -18
- data/lib/fortnox/api/mappers.rb +0 -21
- data/lib/fortnox/api/models/article.rb +0 -134
- data/lib/fortnox/api/models/base.rb +0 -128
- data/lib/fortnox/api/models/customer.rb +0 -210
- data/lib/fortnox/api/models/document.rb +0 -189
- data/lib/fortnox/api/models/invoice.rb +0 -87
- data/lib/fortnox/api/models/label.rb +0 -19
- data/lib/fortnox/api/models/order.rb +0 -27
- data/lib/fortnox/api/models/project.rb +0 -42
- data/lib/fortnox/api/models/terms_of_payment.rb +0 -28
- data/lib/fortnox/api/models/unit.rb +0 -24
- data/lib/fortnox/api/models.rb +0 -9
- data/lib/fortnox/api/repositories/article.rb +0 -17
- data/lib/fortnox/api/repositories/authentication.rb +0 -61
- data/lib/fortnox/api/repositories/base/loaders.rb +0 -64
- data/lib/fortnox/api/repositories/base/savers.rb +0 -57
- data/lib/fortnox/api/repositories/base.rb +0 -93
- data/lib/fortnox/api/repositories/customer.rb +0 -17
- data/lib/fortnox/api/repositories/invoice.rb +0 -17
- data/lib/fortnox/api/repositories/order.rb +0 -17
- data/lib/fortnox/api/repositories/project.rb +0 -17
- data/lib/fortnox/api/repositories/terms_of_payment.rb +0 -17
- data/lib/fortnox/api/repositories/unit.rb +0 -17
- data/lib/fortnox/api/repositories.rb +0 -10
- data/lib/fortnox/api/request_handling.rb +0 -46
- data/lib/fortnox/api/types/default_delivery_types.rb +0 -20
- data/lib/fortnox/api/types/default_templates.rb +0 -23
- data/lib/fortnox/api/types/defaulted.rb +0 -11
- data/lib/fortnox/api/types/document_row.rb +0 -64
- data/lib/fortnox/api/types/edi_information.rb +0 -29
- data/lib/fortnox/api/types/email_information.rb +0 -26
- data/lib/fortnox/api/types/enums.rb +0 -116
- data/lib/fortnox/api/types/invoice_row.rb +0 -19
- data/lib/fortnox/api/types/model.rb +0 -37
- data/lib/fortnox/api/types/nullable.rb +0 -25
- data/lib/fortnox/api/types/order_row.rb +0 -16
- data/lib/fortnox/api/types/required.rb +0 -13
- data/lib/fortnox/api/types/shim/country_string.rb +0 -10
- data/lib/fortnox/api/types/sized.rb +0 -33
- data/lib/fortnox/api/types.rb +0 -144
- data/lib/fortnox/api.rb +0 -62
- data/spec/fortnox/api/mappers/article_spec.rb +0 -17
- data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +0 -36
- data/spec/fortnox/api/mappers/base/from_json_spec.rb +0 -70
- data/spec/fortnox/api/mappers/base/to_json_spec.rb +0 -68
- data/spec/fortnox/api/mappers/base_spec.rb +0 -154
- data/spec/fortnox/api/mappers/contexts/json_conversion.rb +0 -62
- data/spec/fortnox/api/mappers/customer_spec.rb +0 -27
- data/spec/fortnox/api/mappers/default_delivery_types_spec.rb +0 -14
- data/spec/fortnox/api/mappers/edi_information_spec.rb +0 -23
- data/spec/fortnox/api/mappers/email_information_spec.rb +0 -19
- data/spec/fortnox/api/mappers/examples/mapper.rb +0 -34
- data/spec/fortnox/api/mappers/invoice_row_spec.rb +0 -24
- data/spec/fortnox/api/mappers/invoice_spec.rb +0 -27
- data/spec/fortnox/api/mappers/order_row_spec.rb +0 -21
- data/spec/fortnox/api/mappers/order_spec.rb +0 -23
- data/spec/fortnox/api/mappers/project_spec.rb +0 -12
- data/spec/fortnox/api/mappers/terms_of_payment_spec.rb +0 -16
- data/spec/fortnox/api/mappers/unit_spec.rb +0 -56
- data/spec/fortnox/api/models/article_spec.rb +0 -9
- data/spec/fortnox/api/models/base_spec.rb +0 -117
- data/spec/fortnox/api/models/customer_spec.rb +0 -9
- data/spec/fortnox/api/models/examples/document_base.rb +0 -15
- data/spec/fortnox/api/models/examples/model.rb +0 -22
- data/spec/fortnox/api/models/invoice_spec.rb +0 -11
- data/spec/fortnox/api/models/order_spec.rb +0 -12
- data/spec/fortnox/api/models/project_spec.rb +0 -9
- data/spec/fortnox/api/models/terms_of_payment_spec.rb +0 -9
- data/spec/fortnox/api/models/unit_spec.rb +0 -33
- data/spec/fortnox/api/repositories/article_spec.rb +0 -80
- data/spec/fortnox/api/repositories/authentication_spec.rb +0 -103
- data/spec/fortnox/api/repositories/base_spec.rb +0 -168
- data/spec/fortnox/api/repositories/customer_spec.rb +0 -119
- data/spec/fortnox/api/repositories/examples/all.rb +0 -17
- data/spec/fortnox/api/repositories/examples/find.rb +0 -84
- data/spec/fortnox/api/repositories/examples/only.rb +0 -34
- data/spec/fortnox/api/repositories/examples/save.rb +0 -76
- data/spec/fortnox/api/repositories/examples/save_with_nested_model.rb +0 -28
- data/spec/fortnox/api/repositories/examples/save_with_specially_named_attribute.rb +0 -26
- data/spec/fortnox/api/repositories/examples/search.rb +0 -39
- data/spec/fortnox/api/repositories/invoice_spec.rb +0 -297
- data/spec/fortnox/api/repositories/order_spec.rb +0 -53
- data/spec/fortnox/api/repositories/project_spec.rb +0 -36
- data/spec/fortnox/api/repositories/terms_of_payment_spec.rb +0 -34
- data/spec/fortnox/api/repositories/unit_spec.rb +0 -39
- data/spec/fortnox/api/types/account_number_spec.rb +0 -35
- data/spec/fortnox/api/types/country_code_spec.rb +0 -42
- data/spec/fortnox/api/types/country_spec.rb +0 -67
- data/spec/fortnox/api/types/default_delivery_types_spec.rb +0 -12
- data/spec/fortnox/api/types/edi_information_spec.rb +0 -15
- data/spec/fortnox/api/types/email_information_spec.rb +0 -15
- data/spec/fortnox/api/types/email_spec.rb +0 -56
- data/spec/fortnox/api/types/enums_spec.rb +0 -17
- data/spec/fortnox/api/types/examples/document_row.rb +0 -25
- data/spec/fortnox/api/types/examples/enum.rb +0 -55
- data/spec/fortnox/api/types/examples/types.rb +0 -11
- data/spec/fortnox/api/types/housework_types_spec.rb +0 -149
- data/spec/fortnox/api/types/invoice_row_spec.rb +0 -11
- data/spec/fortnox/api/types/model_spec.rb +0 -69
- data/spec/fortnox/api/types/nullable_spec.rb +0 -79
- data/spec/fortnox/api/types/order_row_spec.rb +0 -15
- data/spec/fortnox/api/types/required_spec.rb +0 -36
- data/spec/fortnox/api/types/sales_account_spec.rb +0 -57
- data/spec/fortnox/api/types/sized_spec.rb +0 -76
- data/spec/fortnox/api_spec.rb +0 -66
- data/spec/spec_helper.rb +0 -35
- data/spec/support/helpers/configuration_helper.rb +0 -39
- data/spec/support/helpers/repository_helper.rb +0 -10
- data/spec/support/helpers.rb +0 -3
- data/spec/support/matchers/type/attribute_matcher.rb +0 -40
- data/spec/support/matchers/type/enum_matcher.rb +0 -23
- data/spec/support/matchers/type/have_account_number_matcher.rb +0 -23
- data/spec/support/matchers/type/have_currency_matcher.rb +0 -9
- data/spec/support/matchers/type/have_customer_type_matcher.rb +0 -15
- data/spec/support/matchers/type/have_default_delivery_type_matcher.rb +0 -9
- data/spec/support/matchers/type/have_discount_type_matcher.rb +0 -9
- data/spec/support/matchers/type/have_email_matcher.rb +0 -24
- data/spec/support/matchers/type/have_housework_type_matcher.rb +0 -9
- data/spec/support/matchers/type/have_nullable_date_matcher.rb +0 -60
- data/spec/support/matchers/type/have_nullable_matcher.rb +0 -54
- data/spec/support/matchers/type/have_nullable_string_matcher.rb +0 -47
- data/spec/support/matchers/type/have_sized_float_matcher.rb +0 -10
- data/spec/support/matchers/type/have_sized_integer_matcher.rb +0 -10
- data/spec/support/matchers/type/have_sized_string_matcher.rb +0 -36
- data/spec/support/matchers/type/have_vat_type_matcher.rb +0 -9
- data/spec/support/matchers/type/numeric_matcher.rb +0 -52
- data/spec/support/matchers/type/require_attribute_matcher.rb +0 -68
- data/spec/support/matchers/type/type_matcher.rb +0 -40
- data/spec/support/matchers/type.rb +0 -19
- data/spec/support/matchers.rb +0 -3
- data/spec/support/vcr_setup.rb +0 -25
- data/spec/vcr_cassettes/articles/all.yml +0 -67
- data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +0 -62
- data/spec/vcr_cassettes/articles/find_failure.yml +0 -62
- data/spec/vcr_cassettes/articles/find_id_1.yml +0 -63
- data/spec/vcr_cassettes/articles/find_new.yml +0 -63
- data/spec/vcr_cassettes/articles/limits/quantity_in_stock_min_value.yml +0 -63
- data/spec/vcr_cassettes/articles/limits/quantity_in_stock_rounding_positive_value.yml +0 -63
- data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +0 -62
- data/spec/vcr_cassettes/articles/save_new.yml +0 -63
- data/spec/vcr_cassettes/articles/save_old.yml +0 -63
- data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +0 -63
- data/spec/vcr_cassettes/articles/search_by_name.yml +0 -65
- data/spec/vcr_cassettes/articles/search_miss.yml +0 -62
- data/spec/vcr_cassettes/articles/search_with_special_char.yml +0 -62
- data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +0 -62
- data/spec/vcr_cassettes/authentication/expired_token.yml +0 -54
- data/spec/vcr_cassettes/authentication/invalid_authorization.yml +0 -57
- data/spec/vcr_cassettes/authentication/invalid_refresh_token.yml +0 -58
- data/spec/vcr_cassettes/authentication/valid_request.yml +0 -63
- data/spec/vcr_cassettes/customers/all.yml +0 -69
- data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +0 -62
- data/spec/vcr_cassettes/customers/find_failure.yml +0 -62
- data/spec/vcr_cassettes/customers/find_id_1.yml +0 -64
- data/spec/vcr_cassettes/customers/find_new.yml +0 -63
- data/spec/vcr_cassettes/customers/find_with_sales_account.yml +0 -63
- data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +0 -63
- data/spec/vcr_cassettes/customers/save_new.yml +0 -63
- data/spec/vcr_cassettes/customers/save_new_with_country_code_SE.yml +0 -64
- data/spec/vcr_cassettes/customers/save_new_with_idn_email.yml +0 -67
- data/spec/vcr_cassettes/customers/save_new_with_sales_account.yml +0 -63
- data/spec/vcr_cassettes/customers/save_old.yml +0 -63
- data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +0 -63
- data/spec/vcr_cassettes/customers/search_by_name.yml +0 -64
- data/spec/vcr_cassettes/customers/search_miss.yml +0 -62
- data/spec/vcr_cassettes/customers/search_with_special_char.yml +0 -62
- data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +0 -64
- data/spec/vcr_cassettes/invoices/all.yml +0 -96
- data/spec/vcr_cassettes/invoices/filter_hit.yml +0 -64
- data/spec/vcr_cassettes/invoices/filter_invalid.yml +0 -60
- data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +0 -62
- data/spec/vcr_cassettes/invoices/find_failure.yml +0 -62
- data/spec/vcr_cassettes/invoices/find_id_1.yml +0 -65
- data/spec/vcr_cassettes/invoices/find_new.yml +0 -65
- data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +0 -63
- data/spec/vcr_cassettes/invoices/row_delivered_quantity_decimals.yml +0 -65
- data/spec/vcr_cassettes/invoices/row_delivered_quantity_decimals_round_up.yml +0 -65
- data/spec/vcr_cassettes/invoices/row_description_limit.yml +0 -65
- data/spec/vcr_cassettes/invoices/row_price_limit.yml +0 -65
- data/spec/vcr_cassettes/invoices/row_price_limit_round_up.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new_with_country.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +0 -66
- data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +0 -66
- data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +0 -66
- data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_new_with_unsaved_parent.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_old.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +0 -65
- data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +0 -65
- data/spec/vcr_cassettes/invoices/search_by_name.yml +0 -63
- data/spec/vcr_cassettes/invoices/search_miss.yml +0 -62
- data/spec/vcr_cassettes/invoices/search_with_special_char.yml +0 -62
- data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +0 -64
- data/spec/vcr_cassettes/orders/all.yml +0 -69
- data/spec/vcr_cassettes/orders/filter_hit.yml +0 -64
- data/spec/vcr_cassettes/orders/filter_invalid.yml +0 -60
- data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +0 -62
- data/spec/vcr_cassettes/orders/find_failure.yml +0 -62
- data/spec/vcr_cassettes/orders/find_id_1.yml +0 -67
- data/spec/vcr_cassettes/orders/find_new.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_invalid_tax_reduction_type.yml +0 -61
- data/spec/vcr_cassettes/orders/housework_othercoses_invalid.yml +0 -61
- data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_construction.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_cooking.yml +0 -61
- data/spec/vcr_cassettes/orders/housework_type_electricity.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_gardening.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_hvac.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_itservices.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_majorappliancerepair.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_masonry.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_movingservices.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_othercare.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +0 -65
- data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +0 -61
- data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +0 -63
- data/spec/vcr_cassettes/orders/save_new.yml +0 -65
- data/spec/vcr_cassettes/orders/save_old.yml +0 -65
- data/spec/vcr_cassettes/orders/save_with_nested_model.yml +0 -65
- data/spec/vcr_cassettes/orders/search_by_name.yml +0 -63
- data/spec/vcr_cassettes/orders/search_miss.yml +0 -62
- data/spec/vcr_cassettes/orders/search_with_special_char.yml +0 -62
- data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +0 -64
- data/spec/vcr_cassettes/projects/all.yml +0 -64
- data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +0 -62
- data/spec/vcr_cassettes/projects/find_failure.yml +0 -62
- data/spec/vcr_cassettes/projects/find_id_1.yml +0 -63
- data/spec/vcr_cassettes/projects/find_new.yml +0 -63
- data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +0 -64
- data/spec/vcr_cassettes/projects/save_new.yml +0 -63
- data/spec/vcr_cassettes/projects/save_old.yml +0 -63
- data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +0 -63
- data/spec/vcr_cassettes/termsofpayments/all.yml +0 -68
- data/spec/vcr_cassettes/termsofpayments/find_failure.yml +0 -62
- data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +0 -62
- data/spec/vcr_cassettes/termsofpayments/find_new.yml +0 -63
- data/spec/vcr_cassettes/termsofpayments/save_new.yml +0 -63
- data/spec/vcr_cassettes/termsofpayments/save_old.yml +0 -63
- data/spec/vcr_cassettes/units/all.yml +0 -64
- data/spec/vcr_cassettes/units/find_failure.yml +0 -62
- data/spec/vcr_cassettes/units/find_id_1.yml +0 -63
- data/spec/vcr_cassettes/units/find_new.yml +0 -63
- data/spec/vcr_cassettes/units/save_new.yml +0 -63
- data/spec/vcr_cassettes/units/save_old.yml +0 -63
- data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +0 -63
data/bin/fortnox-setup
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
|
5
|
+
|
|
6
|
+
require 'base64'
|
|
7
|
+
require 'cgi'
|
|
8
|
+
require 'json'
|
|
9
|
+
require 'rbconfig'
|
|
10
|
+
require 'securerandom'
|
|
11
|
+
require 'socket'
|
|
12
|
+
require 'uri'
|
|
13
|
+
require 'faraday'
|
|
14
|
+
require 'fortnox'
|
|
15
|
+
|
|
16
|
+
OAUTH_ENDPOINT = 'https://apps.fortnox.se/oauth-v1'
|
|
17
|
+
LOCAL_PORT = 4242
|
|
18
|
+
LOCAL_REDIRECT_URI = "http://localhost:#{LOCAL_PORT}".freeze
|
|
19
|
+
|
|
20
|
+
def prompt(message, default: nil)
|
|
21
|
+
if default
|
|
22
|
+
print "#{message} [#{default}]: "
|
|
23
|
+
else
|
|
24
|
+
print "#{message}: "
|
|
25
|
+
end
|
|
26
|
+
input = $stdin.gets.chomp
|
|
27
|
+
input.empty? && default ? default : input
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def open_browser(url)
|
|
31
|
+
cmd = case RbConfig::CONFIG['host_os']
|
|
32
|
+
when /mswin|mingw|cygwin/ then 'start'
|
|
33
|
+
when /darwin/ then 'open'
|
|
34
|
+
when /linux|bsd/ then 'xdg-open'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if cmd
|
|
38
|
+
system "#{cmd} \"#{url}\""
|
|
39
|
+
else
|
|
40
|
+
puts 'Could not open browser automatically. Please open this URL manually:'
|
|
41
|
+
puts url
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def start_local_server(port)
|
|
46
|
+
server = TCPServer.new(port)
|
|
47
|
+
client = server.accept
|
|
48
|
+
request = client.gets
|
|
49
|
+
_, path, = request.split
|
|
50
|
+
|
|
51
|
+
client.puts "HTTP/1.1 200\r\n\r\n"
|
|
52
|
+
client.puts '<html><body>'
|
|
53
|
+
client.puts '<h1>Authorization received</h1>'
|
|
54
|
+
client.puts '<p>You can close this tab and return to the terminal.</p>'
|
|
55
|
+
client.puts '</body></html>'
|
|
56
|
+
client.close
|
|
57
|
+
server.close
|
|
58
|
+
|
|
59
|
+
URI.decode_www_form(path[2..]).to_h.transform_keys(&:to_sym)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def get_auth_code_from_server(port, nonce)
|
|
63
|
+
response = start_local_server(port)
|
|
64
|
+
|
|
65
|
+
if response[:error]
|
|
66
|
+
puts "\nFortnox returned an error: #{response[:error]}"
|
|
67
|
+
puts " #{response[:description]}" if response[:description]
|
|
68
|
+
exit 1
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
normalized_state = CGI.unescape(response[:state]).gsub(' ', '+')
|
|
72
|
+
if normalized_state != nonce
|
|
73
|
+
puts "\nSecurity error: the state parameter returned from Fortnox did not match."
|
|
74
|
+
puts 'This could indicate a replay attack. Aborting.'
|
|
75
|
+
exit 1
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
response[:code]
|
|
79
|
+
rescue SocketError, Errno::EADDRINUSE => e
|
|
80
|
+
puts "\nCould not start local server (#{e.message})."
|
|
81
|
+
auth_code_manually
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def auth_code_manually
|
|
85
|
+
puts
|
|
86
|
+
puts "After granting access you will be redirected. Copy the 'code' parameter"
|
|
87
|
+
puts 'from the URL you were redirected to.'
|
|
88
|
+
puts
|
|
89
|
+
prompt('Authorization code')
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def decode_jwt_payload(jwt)
|
|
93
|
+
payload = jwt.split('.')[1]
|
|
94
|
+
# JWTs are base64url-encoded without padding (RFC 7515), but Ruby's
|
|
95
|
+
# urlsafe_decode64 is strict about padding before 3.2 — pad to a
|
|
96
|
+
# multiple of 4 chars so it accepts the input on 3.1.
|
|
97
|
+
payload += '=' * ((4 - (payload.size % 4)) % 4)
|
|
98
|
+
JSON.parse(Base64.urlsafe_decode64(payload))
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# --- Main ---
|
|
102
|
+
|
|
103
|
+
puts 'Fortnox Setup'
|
|
104
|
+
puts '============='
|
|
105
|
+
puts
|
|
106
|
+
puts 'This script will authorize your Fortnox app and retrieve your tenant ID.'
|
|
107
|
+
puts 'You only need to do this once per Fortnox account.'
|
|
108
|
+
puts
|
|
109
|
+
|
|
110
|
+
client_id = prompt('Client ID')
|
|
111
|
+
client_secret = prompt('Client secret')
|
|
112
|
+
|
|
113
|
+
puts
|
|
114
|
+
puts 'Available scopes:'
|
|
115
|
+
Fortnox.scopes.each do |scope, resources|
|
|
116
|
+
resource_names = resources.map { |r| r.name.split('::').last }.join(', ')
|
|
117
|
+
puts " #{scope.ljust(10)} → #{resource_names}"
|
|
118
|
+
end
|
|
119
|
+
puts
|
|
120
|
+
puts 'These must match the scopes configured on your Fortnox app in the developer'
|
|
121
|
+
puts 'portal. Other Fortnox scopes (salary, bookkeeping, etc.) can be added manually.'
|
|
122
|
+
puts
|
|
123
|
+
|
|
124
|
+
scopes = loop do
|
|
125
|
+
input = prompt("Enter scopes (space-separated, or 'all')").strip
|
|
126
|
+
break Fortnox.scopes.keys.join(' ') if input == 'all'
|
|
127
|
+
break input unless input.empty?
|
|
128
|
+
|
|
129
|
+
puts 'Please enter at least one scope.'
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
puts
|
|
133
|
+
puts 'The script can catch the authorization response automatically using a'
|
|
134
|
+
puts "local server. This requires your Fortnox app's redirect URI to be set to"
|
|
135
|
+
puts LOCAL_REDIRECT_URI
|
|
136
|
+
puts
|
|
137
|
+
use_local = prompt('Use local server?', default: 'Y')
|
|
138
|
+
use_local_server = use_local.downcase.start_with?('y')
|
|
139
|
+
|
|
140
|
+
redirect_uri = use_local_server ? LOCAL_REDIRECT_URI : prompt('Redirect URI')
|
|
141
|
+
|
|
142
|
+
nonce = SecureRandom.base64
|
|
143
|
+
|
|
144
|
+
auth_params = URI.encode_www_form(
|
|
145
|
+
client_id: client_id,
|
|
146
|
+
redirect_uri: redirect_uri,
|
|
147
|
+
scope: scopes,
|
|
148
|
+
state: nonce,
|
|
149
|
+
access_type: 'offline',
|
|
150
|
+
response_type: 'code',
|
|
151
|
+
account_type: 'service'
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
authorize_url = "#{OAUTH_ENDPOINT}/auth?#{auth_params}"
|
|
155
|
+
|
|
156
|
+
puts
|
|
157
|
+
puts 'Opening browser for Fortnox authorization...'
|
|
158
|
+
|
|
159
|
+
open_browser(authorize_url)
|
|
160
|
+
|
|
161
|
+
if use_local_server
|
|
162
|
+
puts
|
|
163
|
+
puts 'Waiting for Fortnox to redirect back...'
|
|
164
|
+
authorization_code = get_auth_code_from_server(LOCAL_PORT, nonce)
|
|
165
|
+
else
|
|
166
|
+
authorization_code = auth_code_manually
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
credentials = Base64.strict_encode64("#{client_id}:#{client_secret}")
|
|
170
|
+
|
|
171
|
+
response = Faraday.post("#{OAUTH_ENDPOINT}/token") do |req|
|
|
172
|
+
req.headers['Authorization'] = "Basic #{credentials}"
|
|
173
|
+
req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
174
|
+
req.body = URI.encode_www_form(
|
|
175
|
+
grant_type: 'authorization_code',
|
|
176
|
+
code: authorization_code,
|
|
177
|
+
redirect_uri: redirect_uri
|
|
178
|
+
)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
parsed = JSON.parse(response.body)
|
|
182
|
+
|
|
183
|
+
unless response.success?
|
|
184
|
+
puts "\nSomething went wrong."
|
|
185
|
+
puts "Response code: #{response.status}"
|
|
186
|
+
puts "Error: #{parsed['error_description'] || parsed['error'] || response.body}"
|
|
187
|
+
exit 1
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
access_token = parsed['access_token']
|
|
191
|
+
|
|
192
|
+
tenant_id = decode_jwt_payload(access_token)['tenantId']
|
|
193
|
+
|
|
194
|
+
puts
|
|
195
|
+
puts 'Setup complete!'
|
|
196
|
+
puts
|
|
197
|
+
puts "Tenant ID: #{tenant_id}"
|
|
198
|
+
puts "Access token: #{access_token}"
|
|
199
|
+
puts "Expires in: #{parsed['expires_in']} seconds"
|
|
200
|
+
puts "Scopes: #{parsed['scope']}"
|
|
201
|
+
puts
|
|
202
|
+
puts 'Store your tenant ID somewhere safe. Together with your client ID and'
|
|
203
|
+
puts 'client secret, you can use it to request new access tokens at any time:'
|
|
204
|
+
puts
|
|
205
|
+
puts ' Fortnox.request_access_token('
|
|
206
|
+
puts " client_id: '#{client_id}',"
|
|
207
|
+
puts " client_secret: '<your-secret>',"
|
|
208
|
+
puts " tenant_id: '#{tenant_id}'"
|
|
209
|
+
puts ' )'
|
|
210
|
+
|
|
211
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'bundler/setup'
|
|
5
|
+
require 'dotenv'
|
|
6
|
+
require 'fortnox'
|
|
7
|
+
|
|
8
|
+
env_file = ARGV[0] || '.env'
|
|
9
|
+
|
|
10
|
+
unless File.exist?(env_file)
|
|
11
|
+
puts "#{env_file} not found."
|
|
12
|
+
puts 'Create it with FORTNOX_CLIENT_ID, FORTNOX_CLIENT_SECRET, and FORTNOX_TENANT_ID.'
|
|
13
|
+
exit 1
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
Dotenv.load(env_file)
|
|
17
|
+
|
|
18
|
+
client_id = ENV.fetch('FORTNOX_CLIENT_ID') { abort 'Missing FORTNOX_CLIENT_ID' }
|
|
19
|
+
client_secret = ENV.fetch('FORTNOX_CLIENT_SECRET') { abort 'Missing FORTNOX_CLIENT_SECRET' }
|
|
20
|
+
tenant_id = ENV.fetch('FORTNOX_TENANT_ID') { abort 'Missing FORTNOX_TENANT_ID' }
|
|
21
|
+
|
|
22
|
+
token = Fortnox.request_access_token(
|
|
23
|
+
client_id: client_id,
|
|
24
|
+
client_secret: client_secret,
|
|
25
|
+
tenant_id: tenant_id
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
text = File.read(env_file)
|
|
29
|
+
|
|
30
|
+
if text.match?(/^FORTNOX_ACCESS_TOKEN=/)
|
|
31
|
+
text.gsub!(/^FORTNOX_ACCESS_TOKEN=.*$/, "FORTNOX_ACCESS_TOKEN=#{token}")
|
|
32
|
+
else
|
|
33
|
+
text += "\nFORTNOX_ACCESS_TOKEN=#{token}\n"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
File.write(env_file, text)
|
|
37
|
+
|
|
38
|
+
puts "Access token written to #{env_file}"
|
data/fortnox.gemspec
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
require 'fortnox/version'
|
|
6
|
+
|
|
7
|
+
Gem::Specification.new do |spec|
|
|
8
|
+
spec.name = 'fortnox-api'
|
|
9
|
+
spec.authors = ['Jonas Schubert Erlandsson', 'Hannes Elvemyr', 'Felix Holmgren', 'Mike Eirih']
|
|
10
|
+
spec.email = ['info@accodeing.com']
|
|
11
|
+
spec.license = 'LGPL-3.0'
|
|
12
|
+
spec.version = Fortnox::VERSION.dup
|
|
13
|
+
|
|
14
|
+
spec.summary = 'Fortnox F3 REST API library, based on rest-easy.'
|
|
15
|
+
spec.description = spec.summary
|
|
16
|
+
spec.homepage = 'https://github.com/accodeing/fortnox'
|
|
17
|
+
spec.files = Dir['CHANGELOG.md', 'LICENSE.md', 'README.md', 'fortnox.gemspec', 'lib/**/*']
|
|
18
|
+
spec.bindir = 'bin'
|
|
19
|
+
spec.executables = ['fortnox-setup', 'fortnox-update-env']
|
|
20
|
+
spec.require_paths = ['lib']
|
|
21
|
+
|
|
22
|
+
spec.required_ruby_version = '>= 3.1.0'
|
|
23
|
+
|
|
24
|
+
spec.add_dependency 'countries', '~> 7.1'
|
|
25
|
+
spec.add_dependency 'dry-struct', '~> 1.5'
|
|
26
|
+
spec.add_dependency 'rest-easy', '~> 1.0.0'
|
|
27
|
+
|
|
28
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
|
29
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fortnox
|
|
4
|
+
module Auth
|
|
5
|
+
# Bearer-token authentication isolated between threads, so concurrent
|
|
6
|
+
# contexts (Sidekiq workers, Puma threads) can use different tokens
|
|
7
|
+
# without leaking across them.
|
|
8
|
+
class ThreadLocal
|
|
9
|
+
THREAD_LOCAL_KEY = :fortnox_access_token
|
|
10
|
+
|
|
11
|
+
def apply(request)
|
|
12
|
+
request.headers['Authorization'] = "Bearer #{token!}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def on_rejected(response)
|
|
16
|
+
raise Fortnox::RequestError, response
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def token!
|
|
22
|
+
Thread.current[THREAD_LOCAL_KEY] ||
|
|
23
|
+
raise(Fortnox::MissingAccessToken,
|
|
24
|
+
'No access token set for the current thread. Set one with: Fortnox.access_token = token')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'forwardable'
|
|
4
|
+
|
|
5
|
+
module Fortnox
|
|
6
|
+
# An iterable wrapper around a list of resource instances that also carries
|
|
7
|
+
# the pagination metadata Fortnox returns alongside collection responses.
|
|
8
|
+
class Collection
|
|
9
|
+
include Enumerable
|
|
10
|
+
extend Forwardable
|
|
11
|
+
|
|
12
|
+
def_delegators :@items, :each, :first, :last, :size, :length, :empty?, :[], :to_a
|
|
13
|
+
|
|
14
|
+
attr_reader :total, :pages, :current_page
|
|
15
|
+
|
|
16
|
+
def initialize(items, total: nil, pages: nil, current_page: nil)
|
|
17
|
+
@items = items
|
|
18
|
+
@total = total
|
|
19
|
+
@pages = pages
|
|
20
|
+
@current_page = current_page
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'countries'
|
|
4
|
+
|
|
5
|
+
module Fortnox
|
|
6
|
+
module Mappers
|
|
7
|
+
module CountryCode
|
|
8
|
+
ISO3166.configure { |config| config.locales = [:en, :sv] }
|
|
9
|
+
|
|
10
|
+
def self.parse(country)
|
|
11
|
+
return '' if country.nil? || country == ''
|
|
12
|
+
|
|
13
|
+
# Fortnox only supports Swedish translation of Sweden
|
|
14
|
+
return 'SE' if country =~ /^s(e$|we|ve)/i
|
|
15
|
+
|
|
16
|
+
country = ::ISO3166::Country[country] ||
|
|
17
|
+
::ISO3166::Country.find_country_by_iso_short_name(country) ||
|
|
18
|
+
::ISO3166::Country.find_country_by_translated_names(country)
|
|
19
|
+
|
|
20
|
+
raise Fortnox::AttributeError, '"Country" violates constraints' if country.nil?
|
|
21
|
+
|
|
22
|
+
country.alpha2
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def self.serialise(country_code)
|
|
26
|
+
return '' if country_code.nil? || country_code == ''
|
|
27
|
+
|
|
28
|
+
return 'Sverige' if country_code == 'SE'
|
|
29
|
+
|
|
30
|
+
::ISO3166::Country.new(country_code).translations['en']
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'date'
|
|
4
|
+
|
|
5
|
+
module Fortnox
|
|
6
|
+
module Mappers
|
|
7
|
+
module Date
|
|
8
|
+
def self.parse(date_string)
|
|
9
|
+
return nil if date_string.nil? || (date_string == '')
|
|
10
|
+
|
|
11
|
+
::Date.parse(date_string)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.serialise(date)
|
|
15
|
+
return nil if date.nil?
|
|
16
|
+
|
|
17
|
+
date.strftime('%F')
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fortnox
|
|
4
|
+
module Mappers
|
|
5
|
+
class DocumentRow < Struct
|
|
6
|
+
struct Structs::DocumentRow
|
|
7
|
+
overrides housework: 'HouseWork',
|
|
8
|
+
housework_hours_to_report: 'HouseWorkHoursToReport',
|
|
9
|
+
housework_type: 'HouseWorkType'
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fortnox
|
|
4
|
+
module Mappers
|
|
5
|
+
class EDIInformation < Struct
|
|
6
|
+
struct Structs::EDIInformation
|
|
7
|
+
overrides edi_global_location_number: 'EDIGlobalLocationNumber',
|
|
8
|
+
edi_global_location_number_delivery: 'EDIGlobalLocationNumberDelivery',
|
|
9
|
+
edi_invoice_extra1: 'EDIInvoiceExtra1',
|
|
10
|
+
edi_invoice_extra2: 'EDIInvoiceExtra2',
|
|
11
|
+
edi_our_electronic_reference: 'EDIOurElectronicReference',
|
|
12
|
+
edi_your_electronic_reference: 'EDIYourElectronicReference'
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fortnox
|
|
4
|
+
module Mappers
|
|
5
|
+
class InvoiceRow < DocumentRow
|
|
6
|
+
struct Structs::InvoiceRow
|
|
7
|
+
overrides housework: 'HouseWork',
|
|
8
|
+
housework_hours_to_report: 'HouseWorkHoursToReport',
|
|
9
|
+
housework_type: 'HouseWorkType',
|
|
10
|
+
price_excluding_vat: 'PriceExcludingVAT',
|
|
11
|
+
total_excluding_vat: 'TotalExcludingVAT'
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fortnox
|
|
4
|
+
module Mappers
|
|
5
|
+
module LabelReferences
|
|
6
|
+
def self.parse(labels)
|
|
7
|
+
return [] unless labels.is_a?(Array)
|
|
8
|
+
|
|
9
|
+
labels.map do |label|
|
|
10
|
+
Fortnox::Label.parse({ 'Label' => label })
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.serialise(labels)
|
|
15
|
+
return [] if labels.nil?
|
|
16
|
+
return [] unless labels.is_a?(Array)
|
|
17
|
+
|
|
18
|
+
labels.map do |label|
|
|
19
|
+
{ 'Id' => label.id }
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fortnox
|
|
4
|
+
module Mappers
|
|
5
|
+
# Base class for declarative struct mappers. Subclasses declare the target
|
|
6
|
+
# struct and any API key overrides (acronyms like "EDIInformation" that the
|
|
7
|
+
# PascalCase convention can't derive). Read-only attributes are excluded
|
|
8
|
+
# from serialisation.
|
|
9
|
+
#
|
|
10
|
+
# class EmailInformation < Struct
|
|
11
|
+
# struct Structs::EmailInformation
|
|
12
|
+
# overrides email_address_cc: 'EmailAddressCC'
|
|
13
|
+
# end
|
|
14
|
+
class Struct
|
|
15
|
+
CONVENTION = RestEasy::Conventions::PascalCase.new
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
def for(struct_class)
|
|
19
|
+
Class.new(self) { struct struct_class }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def struct(klass)
|
|
23
|
+
@struct_class = klass
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def overrides(map)
|
|
27
|
+
@overrides = map
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def parse(data)
|
|
31
|
+
return nil if data.nil?
|
|
32
|
+
|
|
33
|
+
attributes = data.transform_keys do |api_key|
|
|
34
|
+
api_to_model_map[api_key] || CONVENTION.parse(api_key)
|
|
35
|
+
end
|
|
36
|
+
struct_class.new(attributes)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def serialise(struct)
|
|
40
|
+
return nil if struct.nil?
|
|
41
|
+
|
|
42
|
+
excluded = if struct.class.respond_to?(:read_only_attributes)
|
|
43
|
+
struct.class.read_only_attributes
|
|
44
|
+
else
|
|
45
|
+
[]
|
|
46
|
+
end
|
|
47
|
+
struct.to_h.except(*excluded).transform_keys do |key|
|
|
48
|
+
model_to_api_map[key] || CONVENTION.serialise(key)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
attr_reader :struct_class
|
|
55
|
+
|
|
56
|
+
def model_to_api_map
|
|
57
|
+
parent = if superclass.respond_to?(:send, true) && superclass != Struct
|
|
58
|
+
superclass.send(:model_to_api_map)
|
|
59
|
+
else
|
|
60
|
+
{}
|
|
61
|
+
end
|
|
62
|
+
@overrides ? parent.merge(@overrides) : parent
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def api_to_model_map
|
|
66
|
+
model_to_api_map.invert
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fortnox
|
|
4
|
+
module Mappers
|
|
5
|
+
module StructArray
|
|
6
|
+
def self.for(mapper)
|
|
7
|
+
Handler.new(mapper)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
class Handler
|
|
11
|
+
def initialize(mapper)
|
|
12
|
+
@mapper = mapper
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def parse(data)
|
|
16
|
+
return [] if data.nil?
|
|
17
|
+
raise Fortnox::AttributeError, "Expected Array, got #{data.class}" unless data.is_a?(Array)
|
|
18
|
+
|
|
19
|
+
data.map { |item| @mapper.parse(item) }
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def serialise(structs)
|
|
23
|
+
return [] if structs.nil?
|
|
24
|
+
raise Fortnox::AttributeError, "Expected Array, got #{structs.class}" unless structs.is_a?(Array)
|
|
25
|
+
|
|
26
|
+
structs.map { |struct| @mapper.serialise(struct) }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|