fortnox-api 0.8.2 → 0.9.0
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/.env.template +7 -0
- data/.env.test +11 -3
- data/.gitignore +7 -1
- data/.rubocop.yml +17 -1
- data/.travis.yml +10 -9
- data/CHANGELOG.md +22 -8
- data/CONTRIBUTE.md +21 -9
- data/DEVELOPER_README.md +72 -0
- data/Guardfile +13 -4
- data/README.md +226 -64
- data/Rakefile +128 -0
- data/bin/get_tokens +79 -0
- data/bin/renew_tokens +28 -0
- data/fortnox-api.gemspec +10 -9
- data/lib/fortnox/api/mappers/base/from_json.rb +4 -3
- data/lib/fortnox/api/mappers/base/to_json.rb +2 -3
- data/lib/fortnox/api/models/base.rb +12 -10
- data/lib/fortnox/api/models/customer.rb +55 -55
- data/lib/fortnox/api/models/label.rb +2 -2
- 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 +21 -35
- data/lib/fortnox/api/repositories.rb +1 -0
- data/lib/fortnox/api/request_handling.rb +30 -18
- data/lib/fortnox/api/types/document_row.rb +3 -3
- data/lib/fortnox/api/types/enums.rb +27 -11
- data/lib/fortnox/api/types/model.rb +1 -4
- data/lib/fortnox/api/types/sized.rb +2 -2
- data/lib/fortnox/api/types.rb +14 -1
- data/lib/fortnox/api/version.rb +1 -1
- data/lib/fortnox/api.rb +12 -32
- data/spec/fortnox/api/mappers/base/canonical_name_sym_spec.rb +4 -4
- 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/unit_spec.rb +3 -4
- data/spec/fortnox/api/models/base_spec.rb +27 -16
- 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 +106 -319
- 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 +64 -15
- 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/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 +54 -61
- data/spec/fortnox/api/types/model_spec.rb +3 -27
- data/spec/fortnox/api/types/order_row_spec.rb +2 -2
- data/spec/fortnox/api/types/required_spec.rb +6 -11
- 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/have_nullable_date_matcher.rb +6 -4
- 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 +16 -43
- data/spec/vcr_cassettes/articles/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/articles/find_failure.yml +10 -12
- data/spec/vcr_cassettes/articles/find_id_1.yml +13 -14
- data/spec/vcr_cassettes/articles/find_new.yml +14 -16
- data/spec/vcr_cassettes/articles/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/articles/save_new.yml +13 -15
- data/spec/vcr_cassettes/articles/save_old.yml +14 -16
- data/spec/vcr_cassettes/articles/save_with_specially_named_attribute.yml +13 -15
- data/spec/vcr_cassettes/articles/search_by_name.yml +16 -15
- data/spec/vcr_cassettes/articles/search_miss.yml +10 -12
- data/spec/vcr_cassettes/articles/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/articles/single_param_find_by_hash.yml +13 -27
- 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 +20 -127
- data/spec/vcr_cassettes/customers/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/customers/find_failure.yml +10 -12
- data/spec/vcr_cassettes/customers/find_id_1.yml +14 -15
- data/spec/vcr_cassettes/customers/find_new.yml +13 -15
- data/spec/vcr_cassettes/customers/find_with_sales_account.yml +63 -0
- data/spec/vcr_cassettes/customers/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/customers/save_new.yml +12 -14
- data/spec/vcr_cassettes/customers/save_new_with_country_code_SE.yml +12 -14
- data/spec/vcr_cassettes/customers/save_new_with_sales_account.yml +63 -0
- data/spec/vcr_cassettes/customers/save_old.yml +13 -15
- data/spec/vcr_cassettes/customers/save_with_specially_named_attribute.yml +12 -14
- data/spec/vcr_cassettes/customers/search_by_name.yml +13 -45
- data/spec/vcr_cassettes/customers/search_miss.yml +10 -12
- data/spec/vcr_cassettes/customers/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/customers/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/invoices/all.yml +47 -112
- data/spec/vcr_cassettes/invoices/filter_hit.yml +14 -18
- data/spec/vcr_cassettes/invoices/filter_invalid.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_failure.yml +10 -12
- data/spec/vcr_cassettes/invoices/find_id_1.yml +15 -16
- data/spec/vcr_cassettes/invoices/find_new.yml +16 -18
- data/spec/vcr_cassettes/invoices/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/invoices/row_description_limit.yml +65 -0
- data/spec/vcr_cassettes/invoices/save_new.yml +14 -16
- data/spec/vcr_cassettes/invoices/save_new_with_comments.yml +14 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_GB.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_Norge.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_Norway.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_Sverige.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_VA.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_VI.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_new_with_country_empty_string.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_country_nil.yml +14 -15
- data/spec/vcr_cassettes/invoices/save_new_with_unsaved_parent.yml +65 -0
- data/spec/vcr_cassettes/invoices/save_old.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_empty_comments.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_empty_country.yml +16 -17
- data/spec/vcr_cassettes/invoices/save_old_with_nil_comments.yml +16 -18
- data/spec/vcr_cassettes/invoices/save_old_with_nil_country.yml +16 -17
- data/spec/vcr_cassettes/invoices/save_with_nested_model.yml +15 -16
- data/spec/vcr_cassettes/invoices/save_with_specially_named_attribute.yml +14 -15
- data/spec/vcr_cassettes/invoices/search_by_name.yml +13 -21
- data/spec/vcr_cassettes/invoices/search_miss.yml +10 -12
- data/spec/vcr_cassettes/invoices/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/invoices/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/orders/all.yml +19 -113
- data/spec/vcr_cassettes/orders/filter_hit.yml +14 -20
- data/spec/vcr_cassettes/orders/filter_invalid.yml +10 -12
- data/spec/vcr_cassettes/orders/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/orders/find_failure.yml +10 -12
- data/spec/vcr_cassettes/orders/find_id_1.yml +17 -17
- data/spec/vcr_cassettes/orders/find_new.yml +16 -18
- data/spec/vcr_cassettes/orders/housework_invalid_tax_reduction_type.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_othercoses_invalid.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_type_babysitting.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_cleaning.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_construction.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_cooking.yml +11 -13
- data/spec/vcr_cassettes/orders/housework_type_electricity.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_gardening.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_glassmetalwork.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_grounddrainagework.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_hvac.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_itservices.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_majorappliancerepair.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_masonry.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_movingservices.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_othercare.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_othercosts.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_paintingwallpapering.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_snowplowing.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_textileclothing.yml +15 -16
- data/spec/vcr_cassettes/orders/housework_type_tutoring.yml +11 -13
- data/spec/vcr_cassettes/orders/multi_param_find_by_hash.yml +13 -15
- data/spec/vcr_cassettes/orders/save_new.yml +16 -18
- data/spec/vcr_cassettes/orders/save_old.yml +16 -18
- data/spec/vcr_cassettes/orders/save_with_nested_model.yml +15 -16
- data/spec/vcr_cassettes/orders/search_by_name.yml +13 -17
- data/spec/vcr_cassettes/orders/search_miss.yml +10 -12
- data/spec/vcr_cassettes/orders/search_with_special_char.yml +10 -12
- data/spec/vcr_cassettes/orders/single_param_find_by_hash.yml +14 -16
- data/spec/vcr_cassettes/projects/all.yml +14 -37
- data/spec/vcr_cassettes/projects/find_by_hash_failure.yml +10 -12
- data/spec/vcr_cassettes/projects/find_failure.yml +10 -12
- data/spec/vcr_cassettes/projects/find_id_1.yml +13 -15
- data/spec/vcr_cassettes/projects/find_new.yml +14 -16
- data/spec/vcr_cassettes/projects/multi_param_find_by_hash.yml +15 -16
- data/spec/vcr_cassettes/projects/save_new.yml +13 -15
- data/spec/vcr_cassettes/projects/save_old.yml +14 -16
- data/spec/vcr_cassettes/projects/single_param_find_by_hash.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/all.yml +16 -23
- data/spec/vcr_cassettes/termsofpayments/find_failure.yml +10 -12
- data/spec/vcr_cassettes/termsofpayments/find_id_1.yml +13 -16
- data/spec/vcr_cassettes/termsofpayments/find_new.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/save_new.yml +12 -14
- data/spec/vcr_cassettes/termsofpayments/save_old.yml +12 -14
- data/spec/vcr_cassettes/units/all.yml +13 -24
- data/spec/vcr_cassettes/units/find_failure.yml +10 -12
- data/spec/vcr_cassettes/units/find_id_1.yml +13 -15
- data/spec/vcr_cassettes/units/find_new.yml +12 -14
- data/spec/vcr_cassettes/units/save_new.yml +12 -14
- data/spec/vcr_cassettes/units/save_old.yml +12 -14
- data/spec/vcr_cassettes/units/save_with_specially_named_attribute.yml +12 -14
- metadata +39 -230
- data/lib/fortnox/api/circular_queue.rb +0 -39
- data/spec/fortnox/api/circular_queue_spec.rb +0 -52
- data/spec/support/helpers/when_performing_helper.rb +0 -7
- data/temp.txt +0 -1
data/fortnox-api.gemspec
CHANGED
@@ -1,7 +1,6 @@
|
|
1
|
-
|
2
1
|
# frozen_string_literal: true
|
3
2
|
|
4
|
-
lib = File.expand_path('
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
5
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
5
|
require_relative 'lib/fortnox/api/version'
|
7
6
|
|
@@ -11,16 +10,18 @@ Gem::Specification.new do |spec|
|
|
11
10
|
spec.authors = ['Jonas Schubert Erlandsson', 'Hannes Elvemyr', 'Felix Holmgren', 'Mike Eirih']
|
12
11
|
spec.email = ['info@accodeing.com']
|
13
12
|
spec.summary = 'Gem to use Fortnox REST API in Ruby.'
|
14
|
-
spec.description = 'This gem uses the HTTParty library to abstract away the REST calls. It gives you access to a '\
|
15
|
-
'number of objects that behave a lot like ActiveRecord instances, giving you access to
|
16
|
-
'like `all`, `find`, `find_by_...` and so on. And each individual instance can be
|
17
|
-
'persistable to Fortnox again using the `save` method.'
|
13
|
+
spec.description = 'This gem uses the HTTParty library to abstract away the REST calls. It gives you access to a ' \
|
14
|
+
'number of objects that behave a lot like ActiveRecord instances, giving you access to ' \
|
15
|
+
'methods like `all`, `find`, `find_by_...` and so on. And each individual instance can be' \
|
16
|
+
'easily persistable to Fortnox again using the `save` method.'
|
18
17
|
spec.homepage = 'http://github.com/accodeing/fortnox-api'
|
19
18
|
spec.license = 'LGPL-3.0'
|
19
|
+
spec.metadata = {
|
20
|
+
'rubygems_mfa_required' => 'true'
|
21
|
+
}
|
20
22
|
|
21
23
|
spec.files = `git ls-files -z`.split("\x0")
|
22
24
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
-
spec.test_files = spec.files.grep(%r{^(spec)/})
|
24
25
|
spec.require_paths = ['lib']
|
25
26
|
|
26
27
|
spec.required_ruby_version = '>= 2.7'
|
@@ -29,13 +30,14 @@ Gem::Specification.new do |spec|
|
|
29
30
|
spec.add_dependency 'dry-container', '~> 0.10'
|
30
31
|
spec.add_dependency 'dry-struct', '~> 1.6'
|
31
32
|
spec.add_dependency 'dry-types', '~> 1.7'
|
32
|
-
spec.add_dependency 'httparty', '~> 0.
|
33
|
+
spec.add_dependency 'httparty', '~> 0.21'
|
33
34
|
spec.add_dependency 'jwt', '~> 2.3'
|
34
35
|
|
35
36
|
spec.add_development_dependency 'bundler', '~> 2.4'
|
36
37
|
spec.add_development_dependency 'dotenv', '~> 2.8'
|
37
38
|
spec.add_development_dependency 'guard', '~> 2.18'
|
38
39
|
spec.add_development_dependency 'guard-rspec', '~> 4.7'
|
40
|
+
spec.add_development_dependency 'guard-rubocop', '~> 1.5'
|
39
41
|
spec.add_development_dependency 'pry', '~> 0'
|
40
42
|
spec.add_development_dependency 'rake', '~> 13.0'
|
41
43
|
spec.add_development_dependency 'rspec', '~> 3.12'
|
@@ -43,7 +45,6 @@ Gem::Specification.new do |spec|
|
|
43
45
|
spec.add_development_dependency 'rubocop', '~> 1.39.0'
|
44
46
|
spec.add_development_dependency 'rubocop-rspec', '~> 2.16.0'
|
45
47
|
spec.add_development_dependency 'simplecov', '~> 0.22'
|
46
|
-
spec.add_development_dependency 'timecop', '~> 0.9.6'
|
47
48
|
spec.add_development_dependency 'vcr', '~> 6.1'
|
48
49
|
spec.add_development_dependency 'webmock', '~> 3.18'
|
49
50
|
end
|
@@ -4,7 +4,7 @@ module Fortnox
|
|
4
4
|
module API
|
5
5
|
module Mapper
|
6
6
|
module FromJSON
|
7
|
-
class MissingModelOrMapperException <
|
7
|
+
class MissingModelOrMapperException < Fortnox::API::Exception
|
8
8
|
end
|
9
9
|
|
10
10
|
def wrapped_json_collection_to_entities_hash(json_collection_hash)
|
@@ -45,10 +45,11 @@ module Fortnox
|
|
45
45
|
# Raise exception during test run if this happens so that we can
|
46
46
|
# add it before a new release.
|
47
47
|
|
48
|
-
message = "for #{key} (#{mapper_name}, #{Fortnox::API::Mapper::DefaultTemplates.canonical_name_sym})
|
49
|
-
" #{collection}"
|
48
|
+
message = "for #{key} (#{mapper_name}, #{Fortnox::API::Mapper::DefaultTemplates.canonical_name_sym}) " \
|
49
|
+
"with #{collection}"
|
50
50
|
|
51
51
|
raise MissingModelOrMapperException, message if ENV['RUBY_ENV']
|
52
|
+
|
52
53
|
Fortnox::API.logger.warn("Missing Model or Mapper implementation for #{key} with attributes: #{collection}")
|
53
54
|
convert_hash_keys_from_json_format(collection, {})
|
54
55
|
end
|
@@ -35,13 +35,12 @@ module Fortnox
|
|
35
35
|
end
|
36
36
|
|
37
37
|
def default_key_to_json_transform(key)
|
38
|
-
key.to_s.split('_').map(&:capitalize).join
|
38
|
+
key.to_s.split('_').map(&:capitalize).join
|
39
39
|
end
|
40
40
|
|
41
41
|
def sanitise(hash, keys_to_filter)
|
42
42
|
hash.reject do |key, value|
|
43
|
-
|
44
|
-
value.nil?
|
43
|
+
keys_to_filter.include?(key) || value.nil?
|
45
44
|
end
|
46
45
|
end
|
47
46
|
end
|
@@ -26,8 +26,8 @@ module Fortnox
|
|
26
26
|
obj = preserve_meta_properties(hash) do
|
27
27
|
super(hash)
|
28
28
|
end
|
29
|
-
rescue Dry::Struct::Error =>
|
30
|
-
raise Fortnox::API::AttributeError,
|
29
|
+
rescue Dry::Struct::Error => exception
|
30
|
+
raise Fortnox::API::AttributeError, exception
|
31
31
|
end
|
32
32
|
|
33
33
|
IceNine.deep_freeze(obj)
|
@@ -52,30 +52,30 @@ module Fortnox
|
|
52
52
|
end
|
53
53
|
end
|
54
54
|
|
55
|
-
def to_hash(recursive = false)
|
55
|
+
def to_hash(recursive = false) # rubocop:disable Style/OptionalBooleanParameter
|
56
56
|
return super() if recursive
|
57
57
|
|
58
58
|
self.class.schema.each_with_object({}) do |key, result|
|
59
|
-
|
60
|
-
|
59
|
+
# Only output attributes that have a value set
|
60
|
+
result[key.name] = self[key.name] if send("#{key.name}?")
|
61
61
|
end
|
62
62
|
end
|
63
63
|
|
64
64
|
def update(hash)
|
65
65
|
old_attributes = to_hash
|
66
|
-
new_attributes = old_attributes.merge(hash)
|
66
|
+
new_attributes = old_attributes.merge(hash).compact
|
67
67
|
|
68
68
|
return self if new_attributes == old_attributes
|
69
69
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
self.class.new(new_hash)
|
70
|
+
new_attributes[:new] = @new
|
71
|
+
new_attributes[:parent] = self
|
72
|
+
self.class.new(new_attributes)
|
74
73
|
end
|
75
74
|
|
76
75
|
# Generic comparison, by value, use .eql? or .equal? for object identity.
|
77
76
|
def ==(other)
|
78
77
|
return false unless other.is_a? self.class
|
78
|
+
|
79
79
|
to_hash == other.to_hash
|
80
80
|
end
|
81
81
|
|
@@ -115,6 +115,8 @@ module Fortnox
|
|
115
115
|
obj
|
116
116
|
end
|
117
117
|
|
118
|
+
private_class_method :preserve_meta_properties
|
119
|
+
|
118
120
|
private
|
119
121
|
|
120
122
|
def private_attributes
|
@@ -30,31 +30,31 @@ module Fortnox
|
|
30
30
|
# Url Direct URL to the record
|
31
31
|
attribute :url, Types::Nullable::String.is(:read_only)
|
32
32
|
|
33
|
-
# Address1
|
33
|
+
# Address1 First address of the customer. 1024 characters
|
34
34
|
attribute :address1, Types::Sized::String[1024]
|
35
35
|
|
36
|
-
# Address2
|
36
|
+
# Address2 Second address of the customer. 1024 characters
|
37
37
|
attribute :address2, Types::Sized::String[1024]
|
38
38
|
|
39
|
-
# City
|
39
|
+
# City City of the customer. 1024 characters
|
40
40
|
attribute :city, Types::Sized::String[1024]
|
41
41
|
|
42
|
-
# Country
|
42
|
+
# Country Country of the customer. Read-only.
|
43
43
|
attribute :country, Types::Nullable::String.is(:read_only)
|
44
44
|
|
45
|
-
# Comments
|
45
|
+
# Comments Comments. 1024 characters.
|
46
46
|
attribute :comments, Types::Sized::String[1024]
|
47
47
|
|
48
|
-
# Currency
|
48
|
+
# Currency Currency of the customer, 3 letters
|
49
49
|
attribute :currency, Types::Currency
|
50
50
|
|
51
|
-
# CostCenter
|
51
|
+
# CostCenter Cost center of the customer, Cost center in Fortnox
|
52
52
|
attribute :cost_center, Types::Nullable::String
|
53
53
|
|
54
|
-
# CountryCode
|
54
|
+
# CountryCode Country code of the customer, 2 letters
|
55
55
|
attribute :country_code, Types::CountryCode
|
56
56
|
|
57
|
-
# CustomerNumber
|
57
|
+
# CustomerNumber Customer number. 1024 characters
|
58
58
|
attribute :customer_number, Types::Sized::String[1024]
|
59
59
|
|
60
60
|
# DefaultDeliveryTypes The properties for this object is listed in the table for "Default Delivery Types".
|
@@ -63,146 +63,146 @@ module Fortnox
|
|
63
63
|
# The properties for this object is listed in the table for "Default Templates".
|
64
64
|
attribute :default_templates, Types::DefaultTemplates
|
65
65
|
|
66
|
-
# DeliveryAddress1
|
66
|
+
# DeliveryAddress1 First delivery address of the customer. 1024 characters
|
67
67
|
attribute :delivery_address1, Types::Sized::String[1024]
|
68
68
|
|
69
|
-
# DeliveryAddress2
|
69
|
+
# DeliveryAddress2 Second delivery address of the customer. 1024 characters
|
70
70
|
attribute :delivery_address2, Types::Sized::String[1024]
|
71
71
|
|
72
|
-
# DeliveryCity
|
72
|
+
# DeliveryCity Delivery city of the customer. 1024 characters
|
73
73
|
attribute :delivery_city, Types::Sized::String[1024]
|
74
74
|
|
75
|
-
# DeliveryCountry
|
75
|
+
# DeliveryCountry Delivery country of the customer. Read-only.
|
76
76
|
attribute :delivery_country, Types::Nullable::String.is(:read_only)
|
77
77
|
|
78
|
-
# DeliveryCountryCode
|
78
|
+
# DeliveryCountryCode Delivery country code of the customer, 2 letters
|
79
79
|
attribute :delivery_country_code, Types::CountryCode
|
80
80
|
|
81
|
-
# DeliveryFax
|
81
|
+
# DeliveryFax Delivery fax number of the customer. 1024 characters
|
82
82
|
attribute :delivery_fax, Types::Sized::String[1024]
|
83
83
|
|
84
|
-
# DeliveryName
|
84
|
+
# DeliveryName Delivery name of the customer. 1024 characters
|
85
85
|
attribute :delivery_name, Types::Sized::String[1024]
|
86
86
|
|
87
|
-
# DeliveryPhone1
|
87
|
+
# DeliveryPhone1 First delivery phone number of the customer. 1024 characters
|
88
88
|
attribute :delivery_phone1, Types::Sized::String[1024]
|
89
89
|
|
90
|
-
# DeliveryPhone2
|
90
|
+
# DeliveryPhone2 Second delivery phone number of the customer. 1024 characters
|
91
91
|
attribute :delivery_phone2, Types::Sized::String[1024]
|
92
92
|
|
93
|
-
# DeliveryZipCode
|
93
|
+
# DeliveryZipCode Delivery zip code of the customer. 1024 characters.
|
94
94
|
attribute :delivery_zip_code, Types::Sized::String[1024]
|
95
95
|
|
96
|
-
# Email
|
96
|
+
# Email Email address of the customer. 1024 characters
|
97
97
|
attribute :email, Types::Email
|
98
98
|
|
99
|
-
# EmailInvoice
|
99
|
+
# EmailInvoice Invoice email address of the customer. 1024 characters
|
100
100
|
attribute :email_invoice, Types::Email
|
101
101
|
|
102
|
-
# EmailInvoiceBCC
|
102
|
+
# EmailInvoiceBCC Invoice BCC email address of the customer. 1024 characters
|
103
103
|
attribute :email_invoice_bcc, Types::Email
|
104
104
|
|
105
|
-
# EmailInvoiceCC
|
105
|
+
# EmailInvoiceCC Invoice CC email address of the customer. 1024 characters
|
106
106
|
attribute :email_invoice_cc, Types::Email
|
107
107
|
|
108
|
-
# EmailOffer
|
108
|
+
# EmailOffer Offer email address of the customer. 1024 characters
|
109
109
|
attribute :email_offer, Types::Email
|
110
110
|
|
111
|
-
# EmailOfferBCC
|
111
|
+
# EmailOfferBCC Offer BCC email address of the customer. 1024 characters
|
112
112
|
attribute :email_offer_bcc, Types::Email
|
113
113
|
|
114
|
-
# EmailOfferCC
|
114
|
+
# EmailOfferCC Offer CC email address of the customer. 1024 characters
|
115
115
|
attribute :email_offer_cc, Types::Email
|
116
116
|
|
117
|
-
# EmailOrder
|
117
|
+
# EmailOrder Order email address of the customer. 1024 characters
|
118
118
|
attribute :email_order, Types::Email
|
119
119
|
|
120
|
-
# EmailOrderBCC
|
120
|
+
# EmailOrderBCC Order BCC email address of the customer. 1024 characters
|
121
121
|
attribute :email_order_bcc, Types::Email
|
122
122
|
|
123
|
-
# EmailOrderCC
|
123
|
+
# EmailOrderCC Order CC email address of the customer. 1024 characters
|
124
124
|
attribute :email_order_cc, Types::Email
|
125
125
|
|
126
|
-
# Fax
|
126
|
+
# Fax Fax number of the customer. 1024 characters
|
127
127
|
attribute :fax, Types::Sized::String[1024]
|
128
128
|
|
129
129
|
# InvoiceAdministrationFee Invoice administration fee of the customer, 12 digits (incl. decimals).
|
130
130
|
attribute :invoice_administration_fee,
|
131
131
|
Types::Sized::Float[0.0, 99_999_999_999.9]
|
132
132
|
|
133
|
-
# InvoiceDiscount
|
133
|
+
# InvoiceDiscount Invoice discount of the customer, 12 digits (incl. decimals)
|
134
134
|
attribute :invoice_discount, Types::Sized::Float[0.0, 99_999_999_999.9]
|
135
135
|
|
136
|
-
# InvoiceFreight
|
136
|
+
# InvoiceFreight Invoice freight fee of the customer, 12 digits (incl. decimals)
|
137
137
|
attribute :invoice_freight, Types::Sized::Float[0.0, 99_999_999_999.9]
|
138
138
|
|
139
|
-
# InvoiceRemark
|
139
|
+
# InvoiceRemark Invoice remark of the customer. 1024 characters
|
140
140
|
attribute :invoice_remark, Types::Sized::String[1024]
|
141
141
|
|
142
|
-
# Name
|
142
|
+
# Name Name of the customer, 1024 characters
|
143
143
|
attribute :name, Types::Sized::String[1024].is(:required)
|
144
144
|
|
145
|
-
# OrganisationNumber
|
145
|
+
# OrganisationNumber Organisation number of the customer. 30 characters
|
146
146
|
attribute :organisation_number, Types::Sized::String[30]
|
147
147
|
|
148
|
-
# OurReference
|
148
|
+
# OurReference Our reference of the customer. 50 characters
|
149
149
|
attribute :our_reference, Types::Sized::String[50]
|
150
150
|
|
151
|
-
# Phone1
|
151
|
+
# Phone1 First phone number of the customer. 1024 characters
|
152
152
|
attribute :phone1, Types::Sized::String[1024]
|
153
153
|
|
154
|
-
# Phone2
|
154
|
+
# Phone2 Second phone number of the customer. 1024 characters
|
155
155
|
attribute :phone2, Types::Sized::String[1024]
|
156
156
|
|
157
|
-
# PriceList
|
157
|
+
# PriceList Price list of the customer, Price list in Fortnox
|
158
158
|
attribute :price_list, Types::Nullable::String
|
159
159
|
|
160
|
-
# Project
|
160
|
+
# Project Project of the customer, Project in Fortnox
|
161
161
|
attribute :project, Types::Nullable::String
|
162
162
|
|
163
|
-
# SalesAccount
|
164
|
-
attribute :sales_account, Types::
|
163
|
+
# SalesAccount Sales account of the customer, 4 characters
|
164
|
+
attribute :sales_account, Types::SalesAccount
|
165
165
|
|
166
|
-
# ShowPriceVATIncluded
|
166
|
+
# ShowPriceVATIncluded Show prices with VAT included or not
|
167
167
|
attribute :show_price_vat_included, Types::Nullable::Boolean
|
168
168
|
|
169
|
-
# TermsOfDeliveryCode
|
169
|
+
# TermsOfDeliveryCode Terms of delivery code of the customer
|
170
170
|
attribute :terms_of_delivery, Types::Nullable::String
|
171
171
|
|
172
|
-
# TermsOfPaymentCode
|
172
|
+
# TermsOfPaymentCode Terms of payment code of the customer
|
173
173
|
attribute :terms_of_payment, Types::Nullable::String
|
174
174
|
|
175
|
-
# Type
|
175
|
+
# Type Customer type, PRIVATE / COMPANY
|
176
176
|
attribute :type, Types::CustomerType
|
177
177
|
|
178
|
-
# VATNumber
|
178
|
+
# VATNumber VAT number of the customer
|
179
179
|
attribute :vat_number, Types::Nullable::String
|
180
180
|
|
181
|
-
# VATType
|
181
|
+
# VATType VAT type of the customer, SEVAT / SEREVERSEDVAT / EUREVERSEDVAT / EUVAT / EXPORT
|
182
182
|
attribute :vat_type, Types::VATType
|
183
183
|
|
184
|
-
# VisitAddress
|
184
|
+
# VisitAddress Visit address of the customer. 128 characters
|
185
185
|
attribute :visiting_address, Types::Sized::String[128]
|
186
186
|
|
187
|
-
# VisitCity
|
187
|
+
# VisitCity Visit city of the customer. 128 characters
|
188
188
|
attribute :visiting_city, Types::Sized::String[128]
|
189
189
|
|
190
|
-
# VisitCountry
|
190
|
+
# VisitCountry Visit country of the customer, read-only
|
191
191
|
attribute :visiting_country, Types::Nullable::String.is(:read_only)
|
192
192
|
|
193
193
|
# VisitingCountryCode Code of the visiting country for the customer, 2 letters
|
194
194
|
attribute :visiting_country_code, Types::CountryCode
|
195
195
|
|
196
|
-
# VisitZipCode
|
196
|
+
# VisitZipCode Visit zip code of the customer. 10 characters
|
197
197
|
attribute :visiting_zip_code, Types::Sized::String[10]
|
198
198
|
|
199
|
-
# WayOfDeliveryCode
|
199
|
+
# WayOfDeliveryCode Way of delivery code of the customer
|
200
200
|
attribute :way_of_delivery, Types::Nullable::String
|
201
201
|
|
202
|
-
# YourReference
|
202
|
+
# YourReference Your reference of the customer. 50 characters
|
203
203
|
attribute :your_reference, Types::Sized::String[50]
|
204
204
|
|
205
|
-
# ZipCode
|
205
|
+
# ZipCode Zip code of the customer. 10 characters
|
206
206
|
attribute :zip_code, Types::Sized::String[10]
|
207
207
|
end
|
208
208
|
end
|
@@ -8,10 +8,10 @@ module Fortnox
|
|
8
8
|
class Label < Model::Base
|
9
9
|
STUB = {}.freeze
|
10
10
|
|
11
|
-
# Id
|
11
|
+
# Id integer, read-only. The ID of the label.
|
12
12
|
attribute :id, Types::Required::Integer.is(:read_only)
|
13
13
|
|
14
|
-
# Description
|
14
|
+
# Description string, 25 characters, required. Description of the label
|
15
15
|
attribute :description, Types::Sized::String[25].is(:read_only)
|
16
16
|
end
|
17
17
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'base'
|
4
|
+
|
5
|
+
module Fortnox
|
6
|
+
module API
|
7
|
+
module Repository
|
8
|
+
class Authentication
|
9
|
+
def renew_tokens(refresh_token:, client_id:, client_secret:)
|
10
|
+
body = {
|
11
|
+
grant_type: 'refresh_token',
|
12
|
+
refresh_token: refresh_token
|
13
|
+
}
|
14
|
+
|
15
|
+
response = HTTParty.post(Fortnox::API.config.token_url,
|
16
|
+
headers: headers(client_id, client_secret),
|
17
|
+
body: body)
|
18
|
+
|
19
|
+
validate_response(response)
|
20
|
+
|
21
|
+
parsed_response_to_hash(response.parsed_response)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def parsed_response_to_hash(parsed_response)
|
27
|
+
{
|
28
|
+
access_token: parsed_response['access_token'],
|
29
|
+
refresh_token: parsed_response['refresh_token'],
|
30
|
+
expires_in: parsed_response['expires_in'],
|
31
|
+
token_type: parsed_response['token_type'],
|
32
|
+
scope: parsed_response['scope']
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
def headers(client_id, client_secret)
|
37
|
+
credentials = Base64.encode64("#{client_id}:#{client_secret}")
|
38
|
+
|
39
|
+
{
|
40
|
+
'Content-type' => 'application/x-www-form-urlencoded',
|
41
|
+
Authorization: "Basic #{credentials}"
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
def validate_response(response)
|
46
|
+
return if response.code == 200
|
47
|
+
|
48
|
+
case response.code
|
49
|
+
when 400
|
50
|
+
raise Fortnox::API::RemoteServerError, "Bad request. Error: \"#{response.body}\""
|
51
|
+
when 401
|
52
|
+
raise Fortnox::API::RemoteServerError, "Unauthorized request. Error: \"#{response.body}\""
|
53
|
+
else
|
54
|
+
raise Exception, "Unable to renew access token. Response code: #{response.code}. " \
|
55
|
+
"Response message: #{response.message}. Response body: #{response.body}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -8,6 +8,7 @@ module Fortnox
|
|
8
8
|
return true if entity.saved?
|
9
9
|
|
10
10
|
return save_new(entity) if entity.new?
|
11
|
+
|
11
12
|
update_existing(entity)
|
12
13
|
end
|
13
14
|
|
@@ -33,8 +34,9 @@ module Fortnox
|
|
33
34
|
|
34
35
|
def get_changes_on(entity)
|
35
36
|
hash = @mapper.entity_to_hash(entity, @keys_filtered_on_save)
|
36
|
-
|
37
|
+
return hash unless entity.parent.saved?
|
37
38
|
|
39
|
+
parent_hash = @mapper.entity_to_hash(entity.parent, @keys_filtered_on_save)
|
38
40
|
@mapper.wrap_entity_json_hash(@mapper.diff(hash, parent_hash))
|
39
41
|
end
|
40
42
|
|
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'httparty'
|
4
|
+
require 'jwt'
|
5
|
+
require 'base64'
|
4
6
|
|
5
7
|
require_relative 'base/loaders'
|
6
8
|
require_relative 'base/savers'
|
@@ -27,53 +29,47 @@ module Fortnox
|
|
27
29
|
attr_accessor :headers
|
28
30
|
attr_reader :mapper, :keys_filtered_on_save
|
29
31
|
|
30
|
-
def self.set_headers(headers = {})
|
32
|
+
def self.set_headers(headers = {})
|
31
33
|
self.headers.merge!(headers)
|
32
34
|
end
|
33
35
|
|
34
36
|
HTTP_METHODS.each do |method|
|
35
37
|
define_method method do |path, options = {}, &block|
|
36
38
|
provided_headers = options[:headers] || {}
|
37
|
-
provided_headers['
|
38
|
-
provided_headers['Access-Token'] = next_access_token
|
39
|
+
provided_headers['Authorization'] = "Bearer #{access_token}"
|
39
40
|
options[:headers] = provided_headers
|
40
41
|
options[:base_uri] ||= base_url
|
42
|
+
|
43
|
+
debug_log_request(path, options)
|
44
|
+
|
41
45
|
execute do |remote|
|
42
46
|
remote.send(method, path, options, &block)
|
43
47
|
end
|
44
48
|
end
|
45
49
|
end
|
46
50
|
|
47
|
-
def initialize(keys_filtered_on_save: [:url]
|
51
|
+
def initialize(keys_filtered_on_save: [:url])
|
48
52
|
@keys_filtered_on_save = keys_filtered_on_save
|
49
|
-
@token_store = token_store
|
50
53
|
@mapper = Registry[Mapper::Base.canonical_name_sym(self.class::MODEL)].new
|
51
|
-
|
54
|
+
return unless access_token.nil?
|
52
55
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
+
raise MissingAccessToken,
|
57
|
+
'No Access Token provided! You need to provide an Access Token: ' \
|
58
|
+
'Fortnox::API.access_token = token'
|
56
59
|
end
|
57
60
|
|
58
|
-
|
59
|
-
tokens_present = !(tokens.nil? || tokens.empty?)
|
60
|
-
return if tokens_present
|
61
|
-
error_message = "You have not provided any access tokens in token store #{@token_store.inspect}."
|
62
|
-
raise MissingConfiguration, error_message
|
63
|
-
end
|
61
|
+
private
|
64
62
|
|
65
|
-
def
|
66
|
-
|
67
|
-
tokens = config.token_store.fetch(@token_store)
|
68
|
-
rescue KeyError
|
69
|
-
token_store_not_found!(@token_store.inspect)
|
70
|
-
end
|
63
|
+
def debug_log_request(path, options)
|
64
|
+
return unless Fortnox::API.debugging
|
71
65
|
|
72
|
-
|
73
|
-
|
66
|
+
Fortnox::API.logger.debug("path: #{path.inspect}")
|
67
|
+
Fortnox::API.logger.debug("options: #{options.inspect}")
|
74
68
|
end
|
75
69
|
|
76
|
-
|
70
|
+
def access_token
|
71
|
+
Fortnox::API.access_token
|
72
|
+
end
|
77
73
|
|
78
74
|
def instantiate(hash)
|
79
75
|
hash[:new] = false
|
@@ -84,23 +80,13 @@ module Fortnox
|
|
84
80
|
def base_url
|
85
81
|
base_url = config.base_url
|
86
82
|
raise MissingConfiguration, 'You have to provide a base url.' unless base_url
|
87
|
-
base_url
|
88
|
-
end
|
89
83
|
|
90
|
-
|
91
|
-
client_secret = config.client_secret
|
92
|
-
raise MissingConfiguration, 'You have to provide your client secret.' unless client_secret
|
93
|
-
client_secret
|
84
|
+
base_url
|
94
85
|
end
|
95
86
|
|
96
87
|
def config
|
97
88
|
Fortnox::API.config
|
98
89
|
end
|
99
|
-
|
100
|
-
def token_store_not_found!(store_name)
|
101
|
-
raise MissingConfiguration,
|
102
|
-
"There is no token store named #{store_name}. Available stores are #{config.token_store.keys}."
|
103
|
-
end
|
104
90
|
end
|
105
91
|
end
|
106
92
|
end
|
@@ -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
|