pennylane 0.1.0 → 0.2.0.pre.alpha

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da1842f4d3dee5f1bd7c9bccd793eb1c185cb5fece2178e967da0cbec3b752be
4
- data.tar.gz: 1f050f189dae434bdf4febe2c7715858b1f422273a129aa6654677c95ba94921
3
+ metadata.gz: 9a60c1035dbad731707f86c3e97610c740d286cb8e6f70b7ac30709bbe29a0f0
4
+ data.tar.gz: b3fd35e53857c0d8df547bb361d4ae69bd1b73a482b7552232ab4cfcce5e8926
5
5
  SHA512:
6
- metadata.gz: c245562b9cdcfc4754348d1a43d2029d5b6d025cd61191c0ceeb3e7b814ed81bae299cc7e86b76799a11275fc296a4e5fd8080ab133f262981ccf9091fad665f
7
- data.tar.gz: 0404a595eedc597bc04c2dafa9db4796e1ab9d6a12a0bfc9f28132195b50f0ebe8516552c5de04326ed1d79cfef97a2d07de0e7b30ce6fd188abc991e07a221c
6
+ metadata.gz: 903f576a32e59f954c68c255ce0aeacf6a820315f233f9825b2d35036cff3ab0928c6ac9fb0e84daa059da40dfde4f1829c5dff61ad58a57a406a3483e69b9e8
7
+ data.tar.gz: 72ed99e03c1d31ff1a871ebf0d335110c9a0a38a9acc9d8e14ecaf27a50731afef55ce6d1c77d89cbb0f95558eba121938ba8da7b87827e6c2416be10def3c5d
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0-alpha] - 2024-04-20
4
+
5
+ - Support resources
6
+ - `CategoryGroup`
7
+ - `Category`
8
+ - `Product`
9
+ - Added missing `#create` to `Supplier`
10
+ - Adding `#update` to `Customer` and `Supplier`
11
+
12
+
3
13
  ## [0.1.0] - 2024-04-04
4
14
 
5
- - Initial release
15
+ - Initial release
data/Gemfile.lock CHANGED
@@ -1,15 +1,33 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- pennylane (0.1.0)
4
+ pennylane (0.2.0.pre.alpha)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
+ activesupport (7.1.3.2)
10
+ base64
11
+ bigdecimal
12
+ concurrent-ruby (~> 1.0, >= 1.0.2)
13
+ connection_pool (>= 2.2.5)
14
+ drb
15
+ i18n (>= 1.6, < 2)
16
+ minitest (>= 5.1)
17
+ mutex_m
18
+ tzinfo (~> 2.0)
9
19
  ast (2.4.2)
20
+ base64 (0.2.0)
21
+ bigdecimal (3.1.7)
22
+ concurrent-ruby (1.2.3)
23
+ connection_pool (2.4.1)
24
+ drb (2.2.1)
25
+ i18n (1.14.4)
26
+ concurrent-ruby (~> 1.0)
10
27
  json (2.7.2)
11
28
  language_server-protocol (3.17.0.3)
12
29
  minitest (5.22.3)
30
+ mutex_m (0.2.0)
13
31
  parallel (1.24.0)
14
32
  parser (3.3.0.5)
15
33
  ast (~> 2.4.1)
@@ -36,6 +54,8 @@ GEM
36
54
  ruby-progressbar (1.13.0)
37
55
  test-unit (3.6.2)
38
56
  power_assert
57
+ tzinfo (2.0.6)
58
+ concurrent-ruby (~> 1.0)
39
59
  unicode-display_width (2.5.0)
40
60
  vcr (6.2.0)
41
61
 
@@ -43,6 +63,7 @@ PLATFORMS
43
63
  arm64-darwin-22
44
64
 
45
65
  DEPENDENCIES
66
+ activesupport (~> 7.1)
46
67
  minitest (~> 5.22)
47
68
  pennylane!
48
69
  rake (~> 13.0)
data/README.md CHANGED
@@ -38,6 +38,32 @@ Pennylane::Customer.list(filter: [{field: 'name', operator: 'eq', value: 'Apple'
38
38
 
39
39
  # Retrieve single customer
40
40
  Pennylane::Customer.retrieve('38a1f19a-256d-4692-a8fe-0a16403f59ff')
41
+
42
+ # Update a customer
43
+ cus = Pennylane::Customer.retrieve('38a1f19a-256d-4692-a8fe-0a16403f59ff')
44
+ cus.update(name: 'Apple Inc')
45
+
46
+ ```
47
+
48
+ ### Per-request api key [TODO]
49
+ For apps that need to use multiple keys during the lifetime of a process. it's also possible to set a per-request key:
50
+ ```ruby
51
+ require "pennylane"
52
+
53
+ Pennylane::Customer.list(
54
+ {},
55
+ {
56
+ api_key: 'x1fa....'
57
+ }
58
+ )
59
+
60
+ Pennylane::Customer.retrieve(
61
+ '38a1f19a-256d-4692-a8fe-0a16403f59ff',
62
+ {
63
+ api_key: 'x1fa....'
64
+ }
65
+ )
66
+
41
67
  ```
42
68
 
43
69
  ## Test mode
@@ -50,6 +76,26 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
50
76
 
51
77
  To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
52
78
 
79
+ Resources implemented so far :
80
+ ### CUSTOMER INVOICING
81
+
82
+ - Customer Invoices 🚧
83
+ - Estimates 🚧
84
+ - Billing Subscriptions 🚧
85
+
86
+ ### REFERENTIALS
87
+
88
+ - Customers ✅
89
+ - Suppliers ✅
90
+ - Categories ✅
91
+ - CategoryGroups ✅
92
+ - Products ✅
93
+ - Plan Items 🚧
94
+ - Enums 🚧
95
+
96
+ ### SUPPLIER INVOICING
97
+ - Supplier Invoices 🚧
98
+
53
99
  ## Contributing
54
100
 
55
101
  Bug reports and pull requests are welcome on GitHub at https://github.com/sbounmy/pennylane. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/pennylane/blob/main/CODE_OF_CONDUCT.md).
@@ -36,8 +36,7 @@ module Pennylane
36
36
 
37
37
  def request method, path, params:, opts: {}
38
38
  req = initialize_request(method, path, params[:query]).tap do |req|
39
- req.body = params[:body].to_json if params[:body] && method != :get
40
- # req.query = params if !params.empty? && method == :get
39
+ req.body = params[:body].to_json if params[:body]
41
40
  end
42
41
 
43
42
  http.request(req).tap do |resp|
@@ -1,10 +1,7 @@
1
1
  module Pennylane
2
2
  class Object
3
3
  include Enumerable
4
-
5
- def initialize(id = nil)
6
- @id = id
7
- end
4
+ attr_reader :id
8
5
 
9
6
  def initialize_from_response(response, params = {}, opts = {})
10
7
  values = Util.symbolize_names(response)
@@ -19,7 +16,7 @@ module Pennylane
19
16
  end
20
17
 
21
18
  def self.build_from(response, params = {}, opts = {})
22
- new(response['id']).initialize_from_response(response, params, opts)
19
+ new.initialize_from_response(response, params, opts)
23
20
  end
24
21
 
25
22
  def self.objects
@@ -11,9 +11,9 @@ module Pennylane
11
11
  "#{object_name}s"
12
12
  end
13
13
 
14
- def request_pennylane_object(method:, path:, params: {}, opts: {}, usage: [])
14
+ def request_pennylane_object(method:, path:, params: {}, opts: {}, usage: [], with: {})
15
15
  resp, opts = execute_resource_request(method, path, params, opts, usage)
16
- Util.convert_to_pennylane_object(Util.normalize_response(resp), params, opts)
16
+ Util.convert_to_pennylane_object(Util.normalize_response(resp, with), params, opts)
17
17
  end
18
18
 
19
19
  def execute_resource_request(method, path, params = {}, opts = {}, usage = [])
@@ -33,18 +33,46 @@ module Pennylane
33
33
  def client
34
34
  @client ||= Pennylane::Client.new(Pennylane.api_key)
35
35
  end
36
+
37
+ def normalize_filters(filters)
38
+ filters[:filter] = filters[:filter].to_json if filters[:filter]
39
+ filters
40
+ end
41
+ end
42
+
43
+ # object happens to be nil when the object is in a list
44
+ def id
45
+ object.source_id
46
+ rescue
47
+ super
48
+ end
49
+
50
+ #
51
+ def object
52
+ @values[self.class.object_name.to_sym]
53
+ end
54
+
55
+ # def inspect
56
+ # id_string = respond_to?(:id) && !id.nil? ? " id=#{id}" : ""
57
+ # "#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " +
58
+ # JSON.pretty_generate(object.instance_variable_get(:@values) || @values)
59
+ # end
60
+
61
+ def update(attributes)
62
+ resp, opts = self.class.request_pennylane_object(method: :put, path: "/#{self.class.object_name_plural}/#{id}", params: { body: { self.class.object_name => attributes } })
63
+ @values = resp.instance_variable_get :@values
64
+ self
36
65
  end
37
66
 
38
67
  # So we can call directly method on the object rather than going through his key
39
68
  # Pennylane::Customer.retrieve('any-id').name == Pennylane::Customer.retrieve('any-id').customer.name
40
69
  def method_missing(method_name, *args, &block)
41
- obj = @values[self.class.object_name.to_sym]
42
- raise NoMethodError, "undefined method `#{method_name}` for #{self.class}" unless obj
43
- obj.send(method_name, *args, &block)
70
+ raise NoMethodError, "undefined method `#{method_name}` for #{self.class}" unless object
71
+ object.send(method_name, *args, &block)
44
72
  end
45
73
 
46
74
  def respond_to_missing?(method_name, include_private = false)
47
- @values[self.class.object_name.to_sym].respond_to?(method_name) || super
75
+ object.respond_to?(method_name) || super
48
76
  end
49
77
 
50
78
  end
@@ -0,0 +1,27 @@
1
+ module Pennylane
2
+ class Category < Resources::Base
3
+
4
+ class << self
5
+
6
+ # override the object_name_plural method otherwise it will return 'categorys'
7
+ def object_name_plural
8
+ 'categories'
9
+ end
10
+
11
+ def list filters = {}, opts = {}
12
+ normalize_filters(filters)
13
+ request_pennylane_object(method: :get, path: "/categories", params: { query: filters }, opts: opts)
14
+ end
15
+
16
+ def retrieve id, opts = {}
17
+ request_pennylane_object(method: :get, path: "/categories/#{id}", params: {}, opts: opts)
18
+ end
19
+
20
+ def create params, opts = {}
21
+ request_pennylane_object(method: :post, path: "/categories", params: { body: { object_name => params } }, opts: opts)
22
+ end
23
+
24
+ end
25
+
26
+ end
27
+ end
@@ -0,0 +1,16 @@
1
+ module Pennylane
2
+ class CategoryGroup < Resources::Base
3
+
4
+ class << self
5
+
6
+ def object_name
7
+ 'category_group'
8
+ end
9
+
10
+ def list filters = {}, opts = {}
11
+ request_pennylane_object(method: :get, path: "/category_groups", params: { query: filters }, opts: opts)
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -4,11 +4,12 @@ module Pennylane
4
4
  class << self
5
5
 
6
6
  def list filters = {}, opts = {}
7
+ normalize_filters(filters)
7
8
  request_pennylane_object(method: :get, path: "/customers", params: { query: filters }, opts: opts)
8
9
  end
9
10
 
10
- def retrieve customer_id, opts = {}
11
- request_pennylane_object(method: :get, path: "/customers/#{customer_id}", params: {}, opts: opts)
11
+ def retrieve id, opts = {}
12
+ request_pennylane_object(method: :get, path: "/customers/#{id}", params: {}, opts: opts)
12
13
  end
13
14
 
14
15
  def create params, opts = {}
@@ -0,0 +1,45 @@
1
+ module Pennylane
2
+ class CustomerInvoice < Resources::Base
3
+
4
+ class << self
5
+
6
+ def object_name
7
+ 'customer_invoice'
8
+ end
9
+
10
+ def list filters = {}, opts = {}
11
+ normalize_filters(filters)
12
+ request_pennylane_object(method: :get, path: "/customer_invoices", params: { query: filters }, opts: opts, with: { invoice: 'customer_invoice' })
13
+ end
14
+
15
+ def retrieve id, opts = {}
16
+ request_pennylane_object(method: :get, path: "/customer_invoices/#{id}", params: {}, opts: opts, with: { invoice: 'customer_invoice' })
17
+ end
18
+
19
+ def create params, opts = {}
20
+ request_pennylane_object(method: :post, path: "/customer_invoices", params: { body: params }, opts: opts, with: { invoice: 'customer_invoice' })
21
+ end
22
+
23
+ end
24
+
25
+ # since object name is different from the class name, we need to override the method
26
+ def update(attributes)
27
+ resp, opts = self.class.request_pennylane_object(method: :put, path: "/#{self.class.object_name_plural}/#{id}",
28
+ params: { body: { 'invoice' => attributes } },
29
+ opts: {}, with: { invoice: 'customer_invoice' })
30
+ @values = resp.instance_variable_get :@values
31
+ self
32
+ end
33
+
34
+ # since object name is different from the class name, we need to override the object_name method
35
+ def object
36
+ @values[:invoice]
37
+ end
38
+
39
+ # doesnt have a `source_id` so we override it
40
+ def id
41
+ object.id
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,22 @@
1
+ module Pennylane
2
+ class Product < Resources::Base
3
+
4
+ class << self
5
+
6
+ def list filters = {}, opts = {}
7
+ normalize_filters(filters)
8
+ request_pennylane_object(method: :get, path: "/products", params: { query: filters }, opts: opts)
9
+ end
10
+
11
+ def retrieve id, opts = {}
12
+ request_pennylane_object(method: :get, path: "/products/#{id}", params: {}, opts: opts)
13
+ end
14
+
15
+ def create params, opts = {}
16
+ request_pennylane_object(method: :post, path: "/products", params: { body: { product: params } }, opts: opts)
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
@@ -0,0 +1,20 @@
1
+ module Pennylane
2
+ class Supplier < Resources::Base
3
+ class << self
4
+
5
+ def list filters = {}, opts = {}
6
+ normalize_filters(filters)
7
+ request_pennylane_object(method: :get, path: "/suppliers", params: { query: filters }, opts: opts)
8
+ end
9
+
10
+ def retrieve supplier_id, opts = {}
11
+ request_pennylane_object(method: :get, path: "/suppliers/#{supplier_id}", params: {}, opts: opts)
12
+ end
13
+
14
+ def create params, opts = {}
15
+ request_pennylane_object(method: :post, path: "/suppliers", params: { body: { object_name => params } }, opts: opts)
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -20,28 +20,38 @@ module Pennylane
20
20
  # It will add an _object key to each hash in the response
21
21
  # This key will contain the name of the object
22
22
  # It will also add an _object key to the root of the response
23
- def normalize_response(object)
23
+ # with: is used to map the object name to a different class
24
+ # Example : GET /customer_invoices will return a list of invoices
25
+ # {
26
+ # "total_pages": 5,
27
+ # "current_page": 1,
28
+ # "total_invoices": 12,
29
+ # "invoices": []
30
+ # }
31
+ # `invoices` should be `customer_invoice` so we can cast it to the right class CustomerInvoice
32
+ # Since we don't have the ability to change the API response.
33
+ # We can achieve this by calling normalize_response(response, with: {invoice: 'customer_invoice'})
34
+ def normalize_response(object, with={})
24
35
  # puts object.inspect
25
36
  case object
26
37
  when Hash
27
38
  new_hash = {}
28
- new_hash['_object'] = object.has_key?('total_pages') ? 'list' : singularize(object.keys.first)
29
-
39
+ new_hash['_object'] = object.has_key?('total_pages') ? 'list' : (with[singularize(object.keys.first).to_sym] || singularize(object.keys.first))
30
40
  object.each do |key, value|
31
41
  if value.is_a? Array
32
42
  new_hash[key] = value.map do |h|
33
- h['_object'] = singularize(key) if h.is_a? Hash
34
- normalize_response(h)
43
+ h['_object'] = with[singularize(key).to_sym] || singularize(key) if h.is_a? Hash
44
+ normalize_response(h, with)
35
45
  end
36
46
  elsif value.is_a? Hash
37
- value['_object'] = singularize(key)
47
+ value['_object'] = with[singularize(key).to_sym] || singularize(key)
38
48
  end
39
- new_hash[key] = normalize_response(value)
49
+ new_hash[key] = normalize_response(value, with)
40
50
  end
41
51
  new_hash
42
52
  when Array
43
53
  object.map do |value|
44
- normalize_response(value)
54
+ normalize_response(value, with)
45
55
  end
46
56
  else
47
57
  object
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Pennylane
4
- VERSION = "0.1.0"
5
- end
4
+ VERSION = "0.2.0-alpha"
5
+ end
data/lib/pennylane.rb CHANGED
@@ -22,7 +22,12 @@ module Pennylane
22
22
 
23
23
  API_RESOURCES = {
24
24
  ListObject.object_name => ListObject,
25
- Customer.object_name => Customer
25
+ Category.object_name => Category,
26
+ CategoryGroup.object_name => CategoryGroup,
27
+ Customer.object_name => Customer,
28
+ CustomerInvoice.object_name => CustomerInvoice,
29
+ Product.object_name => Product,
30
+ Supplier.object_name => Supplier
26
31
  }.freeze
27
32
 
28
33
  @config = Pennylane::Configuration.new
data/pennylane.gemspec CHANGED
@@ -34,5 +34,6 @@ Gem::Specification.new do |spec|
34
34
  spec.add_development_dependency "vcr", "~> 6.2"
35
35
  spec.add_development_dependency "test-unit", "~> 3.6"
36
36
  spec.add_development_dependency "minitest", "~> 5.22"
37
+ spec.add_development_dependency "activesupport", "~> 7.1"
37
38
  # guide at: https://bundler.io/guides/creating_gem.html
38
39
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pennylane
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0.pre.alpha
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stephane Bounmy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2024-04-14 00:00:00.000000000 Z
11
+ date: 2024-04-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: vcr
@@ -52,6 +52,20 @@ dependencies:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
54
  version: '5.22'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '7.1'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '7.1'
55
69
  description: Pennylane offers integrated financial management and accounting tools
56
70
  for businesses. See https://pennylane.com for details.
57
71
  email:
@@ -74,7 +88,12 @@ files:
74
88
  - lib/pennylane/list_object.rb
75
89
  - lib/pennylane/object.rb
76
90
  - lib/pennylane/resources/base.rb
91
+ - lib/pennylane/resources/category.rb
92
+ - lib/pennylane/resources/category_group.rb
77
93
  - lib/pennylane/resources/customer.rb
94
+ - lib/pennylane/resources/customer_invoice.rb
95
+ - lib/pennylane/resources/product.rb
96
+ - lib/pennylane/resources/supplier.rb
78
97
  - lib/pennylane/util.rb
79
98
  - lib/pennylane/version.rb
80
99
  - pennylane.gemspec
@@ -97,9 +116,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
97
116
  version: 2.6.0
98
117
  required_rubygems_version: !ruby/object:Gem::Requirement
99
118
  requirements:
100
- - - ">="
119
+ - - ">"
101
120
  - !ruby/object:Gem::Version
102
- version: '0'
121
+ version: 1.3.1
103
122
  requirements: []
104
123
  rubygems_version: 3.4.10
105
124
  signing_key: