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