billomat 0.1.4 → 0.3.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.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.editorconfig +30 -0
  3. data/.github/workflows/documentation.yml +38 -0
  4. data/.github/workflows/test.yml +59 -0
  5. data/.gitignore +5 -0
  6. data/.rubocop.yml +53 -0
  7. data/.simplecov +3 -0
  8. data/.yardopts +6 -0
  9. data/CHANGELOG.md +48 -0
  10. data/Dockerfile +29 -0
  11. data/Envfile +6 -0
  12. data/Gemfile +3 -1
  13. data/Makefile +135 -0
  14. data/README.md +42 -17
  15. data/Rakefile +74 -3
  16. data/billomat.gemspec +28 -22
  17. data/config/docker/.bash_profile +3 -0
  18. data/config/docker/.bashrc +48 -0
  19. data/config/docker/.inputrc +17 -0
  20. data/doc/assets/project.svg +68 -0
  21. data/docker-compose.yml +9 -0
  22. data/lib/billomat/actions/cancel.rb +3 -5
  23. data/lib/billomat/actions/complete.rb +8 -12
  24. data/lib/billomat/actions/email.rb +6 -10
  25. data/lib/billomat/actions/pdf.rb +4 -10
  26. data/lib/billomat/actions/uncancel.rb +3 -6
  27. data/lib/billomat/actions.rb +0 -1
  28. data/lib/billomat/configuration.rb +2 -1
  29. data/lib/billomat/gateway.rb +52 -24
  30. data/lib/billomat/models/base.rb +26 -27
  31. data/lib/billomat/models/client.rb +3 -4
  32. data/lib/billomat/models/contact.rb +18 -0
  33. data/lib/billomat/models/credit_note.rb +18 -0
  34. data/lib/billomat/models/credit_note_item.rb +18 -0
  35. data/lib/billomat/models/invoice.rb +6 -11
  36. data/lib/billomat/models/invoice_item.rb +3 -3
  37. data/lib/billomat/models/invoice_payment.rb +3 -4
  38. data/lib/billomat/models/tag.rb +18 -0
  39. data/lib/billomat/models/template.rb +18 -0
  40. data/lib/billomat/models.rb +6 -2
  41. data/lib/billomat/search.rb +12 -14
  42. data/lib/billomat/version.rb +1 -1
  43. data/lib/billomat.rb +15 -18
  44. metadata +117 -20
  45. data/.travis.yml +0 -18
  46. data/doc/assets/logo.png +0 -0
  47. data/doc/assets/project.png +0 -0
  48. data/doc/assets/project.xcf +0 -0
data/billomat.gemspec CHANGED
@@ -1,40 +1,46 @@
1
- # coding: utf-8
2
- lib = File.expand_path("../lib", __FILE__)
1
+ # frozen_string_literal: true
2
+
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "billomat/version"
5
+ require 'billomat/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "billomat"
8
+ spec.name = 'billomat'
8
9
  spec.version = Billomat::VERSION
9
10
  spec.licenses = ['MIT']
10
- spec.authors = ["Henning Vogt"]
11
- spec.email = ["henning.vogt@hausgold.de"]
11
+ spec.authors = ['Henning Vogt']
12
+ spec.email = ['henning.vogt@hausgold.de']
12
13
 
13
- spec.summary = %q{Wrapper for the Billomat API}
14
- spec.homepage = "https://github.com/hausgold/billomat"
14
+ spec.summary = 'Wrapper for the Billomat API'
15
+ spec.homepage = 'https://github.com/hausgold/billomat'
15
16
 
16
- # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
- # to allow pushing to a single host or delete this section to allow pushing to any host.
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the
18
+ # 'allowed_push_host'
19
+ # to allow pushing to a single host or delete this section to allow pushing
20
+ # to any host.
18
21
  if spec.respond_to?(:metadata)
19
- spec.metadata["allowed_push_host"] = "https://rubygems.org"
22
+ spec.metadata['allowed_push_host'] = 'https://rubygems.org'
20
23
  else
21
- raise "RubyGems 2.0 or newer is required to protect against " \
22
- "public gem pushes."
24
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
25
+ 'public gem pushes.'
23
26
  end
24
27
 
25
- spec.files = `git ls-files -z`.split("\x0").reject do |f|
28
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
26
29
  f.match(%r{^(test|spec|features)/})
27
30
  end
28
31
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
- spec.require_paths = ["lib"]
30
-
31
- spec.required_ruby_version = '>= 2.0'
32
+ spec.require_paths = ['lib']
32
33
 
33
34
  spec.add_dependency 'rest-client', '~> 2.0', '>= 2.0.2'
34
35
 
35
- spec.add_development_dependency "bundler", "~> 1.15"
36
- spec.add_development_dependency "rake", "~> 10.0"
37
- spec.add_development_dependency "rspec", "~> 3.0"
38
- spec.add_development_dependency "simplecov", "~> 0.15"
39
- spec.add_development_dependency "pry", "~> 0.11"
36
+ spec.add_development_dependency 'bundler', '>= 1.15', '< 3'
37
+ spec.add_development_dependency 'pry', '~> 0.11'
38
+ spec.add_development_dependency 'railties', '>= 4.2.0', '< 6.1'
39
+ spec.add_development_dependency 'rake', '~> 10.0'
40
+ spec.add_development_dependency 'rspec', '~> 3.0'
41
+ spec.add_development_dependency 'rubocop', '~> 0.63.1'
42
+ spec.add_development_dependency 'rubocop-rspec', '~> 1.31'
43
+ spec.add_development_dependency 'simplecov', '< 0.18'
44
+ spec.add_development_dependency 'yard', '~> 0.9.18'
45
+ spec.add_development_dependency 'yard-activesupport-concern', '~> 0.0.1'
40
46
  end
@@ -0,0 +1,3 @@
1
+ if [ -f ~/.bashrc ]; then
2
+ . ~/.bashrc
3
+ fi
@@ -0,0 +1,48 @@
1
+ # ~/.bashrc: executed by bash(1) for non-login shells.
2
+ # see /usr/share/doc/bash/examples/startup-files (in the package bash-doc)
3
+ # for examples
4
+
5
+ _GEM_PATHS=$(ls -d1 ${HOME}/.gem/ruby/*/bin 2>/dev/null | paste -sd ':')
6
+ _APP_PATHS=$(ls -d1 /app/vendor/bundle/ruby/*/bin 2>/dev/null | paste -sd ':')
7
+
8
+ export PATH="${_GEM_PATHS}:${_APP_PATHS}:${PATH}"
9
+ export PATH="/app/node_modules/.bin:${HOME}/.bin:/app/bin:${PATH}"
10
+ export MAKE_ENV=baremetal
11
+
12
+ # Disable the autostart of all supervisord units
13
+ sudo sed -i 's/autostart=.*/autostart=false/g' /etc/supervisor/conf.d/*
14
+
15
+ # Start the supervisord (empty, no units)
16
+ sudo supervisord >/dev/null 2>&1 &
17
+
18
+ # Wait for supervisord
19
+ while ! supervisorctl status >/dev/null 2>&1; do sleep 1; done
20
+
21
+ # Boot the mDNS stack
22
+ echo '# Start the mDNS stack'
23
+ sudo supervisorctl start dbus avahi
24
+ echo
25
+
26
+ function watch-make-test()
27
+ {
28
+ while [ 1 ]; do
29
+ inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
30
+ make test
31
+ done
32
+ }
33
+
34
+ function watch-make()
35
+ {
36
+ while [ 1 ]; do
37
+ inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
38
+ make $@
39
+ done
40
+ }
41
+
42
+ function watch-run()
43
+ {
44
+ while [ 1 ]; do
45
+ inotifywait --quiet -r `pwd` -e close_write --format '%e -> %w%f'
46
+ bash -c "$@"
47
+ done
48
+ }
@@ -0,0 +1,17 @@
1
+ # mappings for Ctrl-left-arrow and Ctrl-right-arrow for word moving
2
+ "\e[1;5C": forward-word
3
+ "\e[1;5D": backward-word
4
+ "\e[5C": forward-word
5
+ "\e[5D": backward-word
6
+ "\e\e[C": forward-word
7
+ "\e\e[D": backward-word
8
+
9
+ # handle common Home/End escape codes
10
+ "\e[1~": beginning-of-line
11
+ "\e[4~": end-of-line
12
+ "\e[7~": beginning-of-line
13
+ "\e[8~": end-of-line
14
+ "\eOH": beginning-of-line
15
+ "\eOF": end-of-line
16
+ "\e[H": beginning-of-line
17
+ "\e[F": end-of-line
@@ -0,0 +1,68 @@
1
+ <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
+ <svg
3
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
4
+ xmlns:cc="http://creativecommons.org/ns#"
5
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
+ xmlns:svg="http://www.w3.org/2000/svg"
7
+ xmlns="http://www.w3.org/2000/svg"
8
+ version="1.1"
9
+ id="Ebene_1"
10
+ x="0px"
11
+ y="0px"
12
+ viewBox="0 0 800 200"
13
+ xml:space="preserve"
14
+ width="800"
15
+ height="200"><metadata
16
+ id="metadata33"><rdf:RDF><cc:Work
17
+ rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
18
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
19
+ id="defs31" />
20
+ <style
21
+ type="text/css"
22
+ id="style2">
23
+ .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E73E11;}
24
+ .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0371B9;}
25
+ .st2{fill:#132E48;}
26
+ .st3{font-family:'OpenSans-Bold';}
27
+ .st4{font-size:29.5168px;}
28
+ .st5{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
29
+ .st6{opacity:0.5;fill:#132E48;}
30
+ .st7{font-family:'OpenSans';}
31
+ .st8{font-size:12px;}
32
+ </style>
33
+ <g
34
+ transform="translate(0,1.53584)"
35
+ id="g828"><g
36
+ transform="translate(35.93985,35.66416)"
37
+ id="g8">
38
+ <path
39
+ style="clip-rule:evenodd;fill:#e73e11;fill-rule:evenodd"
40
+ id="path4"
41
+ d="m -0.1,124.4 c 0,0 33.7,-123.2 66.7,-123.2 12.8,0 26.9,21.9 38.8,47.2 -23.6,27.9 -66.6,59.7 -94,76 -7.1,0 -11.5,0 -11.5,0 z"
42
+ class="st0" />
43
+ <path
44
+ style="clip-rule:evenodd;fill:#0371b9;fill-rule:evenodd"
45
+ id="path6"
46
+ d="m 88.1,101.8 c 13.5,-10.4 18.4,-16.2 27.1,-25.4 10,25.7 16.7,48 16.7,48 0,0 -41.4,0 -78,0 14.6,-7.9 18.7,-10.7 34.2,-22.6 z"
47
+ class="st1" />
48
+ </g><text
49
+ y="106.40316"
50
+ x="192.43155"
51
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.51733398px;font-family:'Open Sans', sans-serif;-inkscape-font-specification:'OpenSans-Bold, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#132e48"
52
+ id="text10"
53
+ class="st2 st3 st4">Billomat</text>
54
+ <rect
55
+ style="clip-rule:evenodd;fill:none;fill-rule:evenodd"
56
+ id="rect12"
57
+ height="24"
58
+ width="314.5"
59
+ class="st5"
60
+ y="118.06416"
61
+ x="194.23985" /><text
62
+ y="127.22146"
63
+ x="194.21715"
64
+ style="font-size:12px;font-family:'Open Sans', sans-serif;opacity:0.5;fill:#132e48;-inkscape-font-specification:'Open Sans, sans-serif, Normal';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;"
65
+ id="text14"
66
+ class="st6 st7 st8">API wrapper for billomat.com</text>
67
+ </g>
68
+ </svg>
@@ -0,0 +1,9 @@
1
+ version: "3"
2
+ services:
3
+ test:
4
+ build: .
5
+ env_file: Envfile
6
+ network_mode: bridge
7
+ working_dir: /app
8
+ volumes:
9
+ - .:/app
@@ -2,17 +2,15 @@
2
2
 
3
3
  module Billomat
4
4
  module Actions
5
- ##
6
5
  # This actions cancels an invoice
7
6
  class Cancel
8
- # @param [String] invoice_id The invoice ID
7
+ # @param invoice_id [String] the invoice ID
9
8
  # @return [Billomat::Actions::Cancel]
10
9
  def initialize(invoice_id)
11
10
  @invoice_id = invoice_id
12
11
  end
13
12
 
14
- ##
15
- # Calls the gateway
13
+ # Calls the gateway.
16
14
  #
17
15
  # @return [TrueClass]
18
16
  def call
@@ -21,7 +19,7 @@ module Billomat
21
19
  true
22
20
  end
23
21
 
24
- # @return [String] The cancel path with the invoice_id
22
+ # @return [String] the cancel path with the invoice_id
25
23
  def path
26
24
  "/invoices/#{@invoice_id}/cancel"
27
25
  end
@@ -2,14 +2,12 @@
2
2
 
3
3
  module Billomat
4
4
  module Actions
5
- ##
6
- # Completes an invoice by calling the /complete path on a resource
5
+ # Completes an invoice by calling the /complete path on a resource.
7
6
  class Complete
8
- ##
9
- # Returns a Complete object
7
+ # Returns a Complete object.
10
8
  #
11
- # @param [String] invoice_id The ID of the invoice
12
- # @param [Hash] opts The options for this request
9
+ # @param invoice_id [String] the ID of the invoice
10
+ # @param opts [Hash] the options for this request
13
11
  # @return [Billomat::Actions::Complete]
14
12
  #
15
13
  # @example
@@ -19,8 +17,7 @@ module Billomat
19
17
  @opts = opts
20
18
  end
21
19
 
22
- ##
23
- # Calls the gateway
20
+ # Calls the gateway.
24
21
  #
25
22
  # @return [TrueClass]
26
23
  def call
@@ -29,15 +26,14 @@ module Billomat
29
26
  true
30
27
  end
31
28
 
32
- ##
33
- # The given options have to be wrapped
29
+ # The given options have to be wrapped.
34
30
  #
35
- # @return [Hash] The payload for the complete request
31
+ # @return [Hash] the payload for the complete request
36
32
  def wrapped_data
37
33
  { complete: @opts }
38
34
  end
39
35
 
40
- # @return [String] The complete path with the invoice_id
36
+ # @return [String] the complete path with the invoice_id
41
37
  def path
42
38
  "/invoices/#{@invoice_id}/complete"
43
39
  end
@@ -2,25 +2,22 @@
2
2
 
3
3
  module Billomat
4
4
  module Actions
5
- ##
6
5
  # This actions sends an invoice email.
7
6
  # Recipients must be passed like this:
8
- # { recipients: { to: 'bob@example.org' } }
7
+ # { recipients: { to: 'bob@example.org' } }
9
8
  #
10
9
  # @example
11
10
  # Billomat::Actions::Email.new('1235', { recipiens: { to: 'a@b.org' } })
12
11
  class Email
13
- # @param [String] invoice_id The invoice ID
14
- # @param [Hash] opts The options for this action
15
- #
12
+ # @param invoice_id [String] the invoice ID
13
+ # @param opts [Hash] the options for this action
16
14
  # @return [Billomat::Actions::Email]
17
15
  def initialize(invoice_id, opts = {})
18
16
  @invoice_id = invoice_id
19
17
  @opts = opts
20
18
  end
21
19
 
22
- ##
23
- # Calls the gateway
20
+ # Calls the gateway.
24
21
  #
25
22
  # @return [TrueClass]
26
23
  def call
@@ -29,15 +26,14 @@ module Billomat
29
26
  true
30
27
  end
31
28
 
32
- ##
33
29
  # Wraps the options
34
30
  #
35
- # @return [Hash] The wrapped email options
31
+ # @return [Hash] the wrapped email options
36
32
  def wrapped_data
37
33
  { email: @opts }
38
34
  end
39
35
 
40
- # @return [String] The path for the email action
36
+ # @return [String] the path for the email action
41
37
  def path
42
38
  "/invoices/#{@invoice_id}/email"
43
39
  end
@@ -2,21 +2,18 @@
2
2
 
3
3
  module Billomat
4
4
  module Actions
5
- ##
6
5
  # This class allows to download the invoice as a pdf.
7
6
  # The PDF comes in a base64 encoded string in the response body.
8
7
  class Pdf
9
- # @param [String] invoice_id The invoice ID
10
- # @param [Hash] opts The options for this action
11
- #
8
+ # @param invoice_id [String] the invoice ID
9
+ # @param opts [Hash] the options for this action
12
10
  # @return [Billomat::Actions::Pdf]
13
11
  def initialize(invoice_id, opts = {})
14
12
  @invoice_id = invoice_id
15
13
  @opts = opts
16
14
  end
17
15
 
18
- ##
19
- # Calls the gateway
16
+ # Calls the gateway.
20
17
  #
21
18
  # @return [TrueClass]
22
19
  def call
@@ -24,10 +21,7 @@ module Billomat
24
21
  resp['pdf']
25
22
  end
26
23
 
27
- ##
28
- # Wraps the options
29
- #
30
- # @return [Hash] The wrapped email options
24
+ # @return [String] the path for the PDF action
31
25
  def path
32
26
  "/invoices/#{@invoice_id}/pdf"
33
27
  end
@@ -2,21 +2,18 @@
2
2
 
3
3
  module Billomat
4
4
  module Actions
5
- ##
6
5
  # This actions uncancels an canceld invoice.
7
6
  #
8
7
  # @example
9
8
  # Billomat::Actions::Uncancel.new('1235')
10
9
  class Uncancel
11
- # @param [String] invoice_id The invoice ID
12
- #
10
+ # @param invoice_id [String] the invoice ID
13
11
  # @return [Billomat::Actions::Uncancel]
14
12
  def initialize(invoice_id)
15
13
  @invoice_id = invoice_id
16
14
  end
17
15
 
18
- ##
19
- # Calls the gateway
16
+ # Calls the gateway.
20
17
  #
21
18
  # @return [TrueClass]
22
19
  def call
@@ -25,7 +22,7 @@ module Billomat
25
22
  true
26
23
  end
27
24
 
28
- # @return [String] The path for the uncancel action
25
+ # @return [String] the path for the uncancel action
29
26
  def path
30
27
  "/invoices/#{@invoice_id}/uncancel"
31
28
  end
@@ -7,7 +7,6 @@ require 'billomat/actions/cancel'
7
7
  require 'billomat/actions/uncancel'
8
8
 
9
9
  module Billomat
10
- ##
11
10
  # Actions are API calls that do not directly represent a resource.
12
11
  # They are mostly non-RESTful actions that are called on a resources.
13
12
  module Actions; end
@@ -1,7 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Billomat
4
+ # The +billomat+ gem configuration.
4
5
  class Configuration
5
- attr_accessor :api_key, :subdomain, :timeout
6
+ attr_accessor :api_key, :subdomain, :timeout, :app_id, :app_secret
6
7
  end
7
8
  end
@@ -4,16 +4,32 @@ require 'rest-client'
4
4
  require 'json'
5
5
 
6
6
  module Billomat
7
- ##
8
- # Raised if something goes wrong during an API call
9
- class GatewayError < StandardError; end
7
+ # Raised if something goes wrong during an API call.
8
+ class GatewayError < StandardError
9
+ attr_reader :original_exception
10
10
 
11
- ##
12
- # This class can be used by the gem to communicate with the API
11
+ # Create a new GatewayError from a RestClient exception.
12
+ #
13
+ # @param original_exception [RestClient::Exception] the original exception
14
+ def initialize(original_exception)
15
+ # Extract the error from the response if it is present. If no error
16
+ # could be extracted, use the whole body
17
+ body = original_exception.response&.body
18
+ response_error = JSON.parse(body).dig('errors', 'error') || body if body
19
+
20
+ # Use the default message and append our detailed error
21
+ message = original_exception.default_message
22
+ message += " ('#{response_error}')" if response_error
23
+
24
+ super(message)
25
+ @original_exception = original_exception
26
+ end
27
+ end
28
+
29
+ # This class can be used by the gem to communicate with the API.
13
30
  class Gateway
14
31
  attr_reader :method, :path, :body
15
32
 
16
- ##
17
33
  # Creates a new Gateway
18
34
  #
19
35
  # @param [Symbol] method The HTTP verb
@@ -29,25 +45,33 @@ module Billomat
29
45
  @body = body
30
46
  end
31
47
 
32
- ##
33
- # Executes the API call
34
- # @return [Hash] The response body
48
+ # Executes the API call and parse the response.
49
+ #
50
+ # @return [Hash] the response body
35
51
  def run
36
- resp = RestClient::Request.execute(
37
- method: method,
38
- url: url,
39
- timeout: timeout,
40
- headers: headers,
41
- payload: body.to_json
42
- )
52
+ resp = response
43
53
 
44
- raise GatewayError, resp.body if resp.code > 299
45
54
  return nil if resp.body.empty?
46
55
 
47
56
  JSON.parse(resp.body)
57
+ rescue RestClient::Exception => e
58
+ raise GatewayError, e
59
+ end
60
+
61
+ # Executes the API call and return the response.
62
+ #
63
+ # @return [RestClient::Response] the API response
64
+ def response
65
+ RestClient::Request.execute(
66
+ method: method,
67
+ url: url,
68
+ timeout: timeout,
69
+ headers: headers,
70
+ payload: body.to_json
71
+ )
48
72
  end
49
73
 
50
- # @return [String] The complete URL for the request
74
+ # @return [String] the complete URL for the request
51
75
  def url
52
76
  "https://#{config.subdomain}.billomat.net/api#{path}"
53
77
  end
@@ -57,16 +81,20 @@ module Billomat
57
81
  config.timeout || 5
58
82
  end
59
83
 
60
- # @return [Hash] The headers for the request.
84
+ # @return [Hash] the headers for the request
61
85
  def headers
62
86
  {
63
- 'Accept' => 'application/json',
64
- 'Content-Type' => 'application/json',
65
- 'X-BillomatApiKey' => config.api_key
66
- }
87
+ 'Accept' => 'application/json',
88
+ 'Content-Type' => 'application/json',
89
+ 'X-BillomatApiKey' => config.api_key,
90
+ 'X-AppId' => config.app_id,
91
+ 'X-AppSecret' => config.app_secret
92
+ }.reject { |_, val| val.nil? }
67
93
  end
68
94
 
69
- # @return [Billomat::Configuration] The global gem configuration
95
+ # @return [Billomat::Configuration] the global gem configuration
96
+ #
97
+ # :reek:UtilityFunction because it's a shorthand
70
98
  def config
71
99
  Billomat.configuration
72
100
  end