moloni 0.5.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 (45) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +14 -0
  3. data/.rspec +4 -0
  4. data/.rubocop.yml +76 -0
  5. data/.travis.yml +6 -0
  6. data/AGENTS.md +41 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +140 -0
  9. data/LICENSE.txt +21 -0
  10. data/MOLONI_API_DOC.md +328 -0
  11. data/README.md +184 -0
  12. data/Rakefile +8 -0
  13. data/bin/auth +16 -0
  14. data/bin/console +34 -0
  15. data/bin/setup +8 -0
  16. data/lib/moloni/auth.rb +105 -0
  17. data/lib/moloni/base_model.rb +174 -0
  18. data/lib/moloni/cli/oauth_callback_command.rb +54 -0
  19. data/lib/moloni/cli/oauth_callback_server.rb +24 -0
  20. data/lib/moloni/cli/views/variables.erb +80 -0
  21. data/lib/moloni/configuration.rb +46 -0
  22. data/lib/moloni/errors.rb +21 -0
  23. data/lib/moloni/models/company.rb +18 -0
  24. data/lib/moloni/models/country.rb +13 -0
  25. data/lib/moloni/models/customer.rb +47 -0
  26. data/lib/moloni/models/document.rb +18 -0
  27. data/lib/moloni/models/document_set.rb +9 -0
  28. data/lib/moloni/models/invoice.rb +9 -0
  29. data/lib/moloni/models/invoice_receipt.rb +9 -0
  30. data/lib/moloni/models/language.rb +25 -0
  31. data/lib/moloni/models/maturity_date.rb +13 -0
  32. data/lib/moloni/models/payment_method.rb +13 -0
  33. data/lib/moloni/models/printer.rb +13 -0
  34. data/lib/moloni/models/product.rb +20 -0
  35. data/lib/moloni/models/product_category.rb +9 -0
  36. data/lib/moloni/models/product_stock.rb +9 -0
  37. data/lib/moloni/models/simplified_invoice.rb +9 -0
  38. data/lib/moloni/models/subscription.rb +9 -0
  39. data/lib/moloni/models/supplier.rb +17 -0
  40. data/lib/moloni/models/tax.rb +33 -0
  41. data/lib/moloni/models/user.rb +13 -0
  42. data/lib/moloni/version.rb +5 -0
  43. data/lib/moloni.rb +55 -0
  44. data/moloni.gemspec +45 -0
  45. metadata +271 -0
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moloni
4
+ class Configuration
5
+ REQUIRED_ATTRIBUTES = %i[
6
+ developer_id
7
+ redirect_uri
8
+ client_secret
9
+ refresh_token
10
+ access_token
11
+ ].freeze
12
+
13
+ attr_accessor :developer_id, :redirect_uri, :client_secret, :refresh_token, :access_token,
14
+ :company_id, :access_token_expires_at, :refresh_token_expires_at
15
+
16
+ attr_writer :debug
17
+
18
+ def initialize
19
+ @developer_id = ''
20
+ @redirect_uri = ''
21
+ @client_secret = ''
22
+ @refresh_token = ''
23
+ @access_token = ''
24
+ @company_id = 0
25
+ @debug = false
26
+ @access_token_expires_at = nil
27
+ @refresh_token_expires_at = nil
28
+ end
29
+
30
+ def debug?
31
+ @debug
32
+ end
33
+
34
+ def access_token_expired?(margin_seconds: 300)
35
+ return true if @access_token_expires_at.nil?
36
+
37
+ Time.now >= (@access_token_expires_at - margin_seconds)
38
+ end
39
+
40
+ def refresh_token_expired?
41
+ return true if @refresh_token_expires_at.nil?
42
+
43
+ Time.now >= @refresh_token_expires_at
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moloni
4
+ class APIKeyError < StandardError
5
+ end
6
+
7
+ class GenericError < StandardError
8
+ end
9
+
10
+ class APIError < StandardError
11
+ attr_reader :errors
12
+
13
+ def initialize(message, errors = [])
14
+ super(message)
15
+ @errors = errors
16
+ end
17
+ end
18
+
19
+ class TokenExpiredError < StandardError
20
+ end
21
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Company < BaseModel
7
+ @intermediate_path = 'companies/'
8
+
9
+ # Backward-compatible alias (v0.4 accepted a bare id)
10
+ def self.find(id)
11
+ post('getOne/', { company_id: id })
12
+ end
13
+
14
+ def self.check_slug(slug)
15
+ post('freeSlug/', { slug: })[:valid] == 1
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Country < BaseModel
7
+ @intermediate_path = 'countries/'
8
+
9
+ def self.portugal
10
+ 1
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Customer < BaseModel
7
+ @intermediate_path = 'customers/'
8
+
9
+ def self.find_by_vat(args)
10
+ post('getByVat/', args)
11
+ end
12
+
13
+ def self.find_by_email(args)
14
+ post('getByEmail/', args)
15
+ end
16
+
17
+ def self.find_by_number(args)
18
+ post('getByNumber/', args)
19
+ end
20
+
21
+ def self.create(args)
22
+ default_empty_required_params = {
23
+ salesman_id: '',
24
+ payment_day: '',
25
+ discount: '',
26
+ credit_limit: '',
27
+ payment_method_id: '',
28
+ delivery_method_id: ''
29
+ }
30
+
31
+ post('insert/', args.merge(default_empty_required_params))
32
+ end
33
+
34
+ def self.update(args)
35
+ default_empty_required_params = {
36
+ salesman_id: '',
37
+ payment_day: '',
38
+ discount: '',
39
+ credit_limit: '',
40
+ payment_method_id: '',
41
+ delivery_method_id: ''
42
+ }
43
+
44
+ post('update/', args.merge(default_empty_required_params))
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Document < BaseModel
7
+ @intermediate_path = 'documents/'
8
+
9
+ # Backward-compatible aliases (v0.4 API names)
10
+ def self.pdf_link(args = {})
11
+ post('getPDFLink/', args)
12
+ end
13
+
14
+ def self.all_types(args = {})
15
+ post('getAllDocumentTypes/', args)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class DocumentSet < BaseModel
7
+ @intermediate_path = 'documentSets/'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Invoice < BaseModel
7
+ @intermediate_path = 'invoices/'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class InvoiceReceipt < BaseModel
7
+ @intermediate_path = 'invoiceReceipts/'
8
+ end
9
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Language < BaseModel
7
+ @intermediate_path = 'languages/'
8
+
9
+ def self.pt
10
+ find('pt')
11
+ end
12
+
13
+ def self.en
14
+ find('en')
15
+ end
16
+
17
+ def self.es
18
+ find('es')
19
+ end
20
+
21
+ def self.find(code)
22
+ post('getAll/').select { |el| el[:code].match?(/#{code}/i) }.first
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class MaturityDate < BaseModel
7
+ @intermediate_path = 'maturityDates/'
8
+
9
+ def self.pronto_pagamento
10
+ 970_080
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class PaymentMethod < BaseModel
7
+ @intermediate_path = 'paymentMethods/'
8
+
9
+ def self.numerario
10
+ 1_068_129
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Printer < BaseModel
7
+ @intermediate_path = 'printers/'
8
+
9
+ def self.all(args = {})
10
+ post('getCompanyPrinters/', args)
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Product < BaseModel
7
+ @intermediate_path = 'products/'
8
+
9
+ # Backward-compatible alias (v0.4 API name)
10
+ # rubocop:disable Naming/MethodName
11
+ def self.searchByName(args)
12
+ post('getByName/', args)
13
+ end
14
+ # rubocop:enable Naming/MethodName
15
+
16
+ def self.search_by_name(args)
17
+ post('getByName/', args)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class ProductCategory < BaseModel
7
+ @intermediate_path = 'productCategories/'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class ProductStock < BaseModel
7
+ @intermediate_path = 'productStocks/'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class SimplifiedInvoice < BaseModel
7
+ @intermediate_path = 'simplifiedInvoices/'
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Subscription < BaseModel
7
+ @intermediate_path = 'subscription/'
8
+ end
9
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Supplier < BaseModel
7
+ @intermediate_path = 'suppliers/'
8
+
9
+ def self.find_by_vat(args)
10
+ post('getByVat/', args)
11
+ end
12
+
13
+ def self.find_by_email(args)
14
+ post('getByEmail/', args)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class Tax < BaseModel
7
+ @intermediate_path = 'taxes/'
8
+
9
+ def self.iva_normal
10
+ post('getAll/').select { |el| el[:tax_id] == iva_normal_id }.first
11
+ end
12
+
13
+ def self.iva_intermedio
14
+ post('getAll/').select { |el| el[:tax_id] == iva_intermedio_id }.first
15
+ end
16
+
17
+ def self.iva_reduzido
18
+ post('getAll/').select { |el| el[:tax_id] == iva_reduzido_id }.first
19
+ end
20
+
21
+ def self.iva_normal_id
22
+ 2_072_734
23
+ end
24
+
25
+ def self.iva_intermedio_id
26
+ 2_072_748
27
+ end
28
+
29
+ def self.iva_reduzido_id
30
+ 2_072_741
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'moloni/base_model'
4
+
5
+ module Moloni
6
+ class User < BaseModel
7
+ @intermediate_path = 'users/'
8
+
9
+ def self.me
10
+ post('getMe/')
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Moloni
4
+ VERSION = '0.5.0'
5
+ end
data/lib/moloni.rb ADDED
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'faraday'
4
+
5
+ require 'moloni/version'
6
+ require 'moloni/configuration'
7
+ require 'moloni/errors'
8
+ require 'moloni/auth'
9
+
10
+ module Moloni
11
+ module_function
12
+
13
+ API_PROTOCOL = 'https'
14
+ API_HOST = 'api.moloni.pt'
15
+ API_VERSION = 'v1'
16
+ API_BASE_URL = "#{API_PROTOCOL}://#{API_HOST}/#{API_VERSION}/".freeze
17
+
18
+ def config
19
+ @config ||= Configuration.new
20
+ end
21
+
22
+ def configure
23
+ yield(config)
24
+ end
25
+
26
+ def connection
27
+ Faraday.new(url: API_BASE_URL) do |conn|
28
+ conn.request :json
29
+ conn.response :logger if config.debug?
30
+ conn.response :json, parser_options: { symbolize_names: true }
31
+ end
32
+ end
33
+ end
34
+
35
+ # Load models
36
+ require 'moloni/base_model'
37
+ require 'moloni/models/company'
38
+ require 'moloni/models/country'
39
+ require 'moloni/models/customer'
40
+ require 'moloni/models/document'
41
+ require 'moloni/models/document_set'
42
+ require 'moloni/models/invoice'
43
+ require 'moloni/models/invoice_receipt'
44
+ require 'moloni/models/language'
45
+ require 'moloni/models/maturity_date'
46
+ require 'moloni/models/payment_method'
47
+ require 'moloni/models/printer'
48
+ require 'moloni/models/product'
49
+ require 'moloni/models/product_category'
50
+ require 'moloni/models/product_stock'
51
+ require 'moloni/models/simplified_invoice'
52
+ require 'moloni/models/subscription'
53
+ require 'moloni/models/supplier'
54
+ require 'moloni/models/tax'
55
+ require 'moloni/models/user'
data/moloni.gemspec ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'lib/moloni/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'moloni'
7
+ spec.version = Moloni::VERSION
8
+ spec.authors = ['Tiago Pinto']
9
+ spec.email = ['tp@tiagopinto.pt']
10
+
11
+ spec.summary = 'A RubyGem to consume the Moloni API.'
12
+ spec.description = spec.summary
13
+ # spec.homepage = 'TODO: Put your gem's website or public repo URL here.'
14
+ spec.license = 'MIT'
15
+ spec.required_ruby_version = Gem::Requirement.new('>= 3.2.0')
16
+
17
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com'"
18
+ # spec.metadata["homepage_uri"] = spec.homepage
19
+ # spec.metadata["source_code_uri"] = "TODO: Put your gem's public repo URL here."
20
+ # spec.metadata["changelog_uri"] = "TODO: Put your gem's CHANGELOG.md URL here."
21
+
22
+ # Specify which files should be added to the gem when it is released.
23
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
24
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
25
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
26
+ end
27
+ spec.bindir = 'bin'
28
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
29
+ spec.require_paths = ['lib']
30
+
31
+ spec.add_dependency 'addressable', '~> 2.9'
32
+ spec.add_dependency 'faraday', '~> 2.14'
33
+ spec.add_dependency 'launchy', '~> 3.1'
34
+ spec.add_dependency 'sinatra', '~> 4.2'
35
+
36
+ spec.add_development_dependency 'bundler', '~> 2.4'
37
+ spec.add_development_dependency 'dotenv', '~> 2.8'
38
+ spec.add_development_dependency 'pry-byebug', '~> 3.10'
39
+ spec.add_development_dependency 'rake', '~> 13.0'
40
+ spec.add_development_dependency 'rspec', '~> 3.13'
41
+ spec.add_development_dependency 'rubocop', '~> 1.59'
42
+ spec.add_development_dependency 'rubocop-rspec', '~> 2.26'
43
+ spec.add_development_dependency 'simplecov', '~> 0.22'
44
+ spec.add_development_dependency 'webmock', '~> 3.26'
45
+ end