pennylane 0.1.0 → 1.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: da1842f4d3dee5f1bd7c9bccd793eb1c185cb5fece2178e967da0cbec3b752be
4
- data.tar.gz: 1f050f189dae434bdf4febe2c7715858b1f422273a129aa6654677c95ba94921
3
+ metadata.gz: 1b079bfa7d777a0adb18de2cb8b02c4b5b3f7a20ef14ee3be35ccc56c4fc5714
4
+ data.tar.gz: 417d611044b5329eb04040ffaab0c1e78e609b6dc5613deeeac35994aab88012
5
5
  SHA512:
6
- metadata.gz: c245562b9cdcfc4754348d1a43d2029d5b6d025cd61191c0ceeb3e7b814ed81bae299cc7e86b76799a11275fc296a4e5fd8080ab133f262981ccf9091fad665f
7
- data.tar.gz: 0404a595eedc597bc04c2dafa9db4796e1ab9d6a12a0bfc9f28132195b50f0ebe8516552c5de04326ed1d79cfef97a2d07de0e7b30ce6fd188abc991e07a221c
6
+ metadata.gz: 8d1e5ec1c7aacb11921d8d7490c8bfcfd626fb185a74c6199b1ac9b75e4b4cae03dc8f66be2e70effada0cc21a1d563b1aef205beca5d906bc522637d76b429c
7
+ data.tar.gz: fbec4d19969a69390de3903fcb7a1e9e372fcdc4d6bb9b3f735ac09e3a31b7bbea14963e0e35d0c0815d42140385e168e21f21e764caae06d6fdbea08ebec21c
data/CHANGELOG.md CHANGED
@@ -1,5 +1,27 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [1.0.0] - 2024-04-29
4
+
5
+ - Support CustomerInvoice:
6
+ - `finalize`
7
+ - `mark_as_paid`
8
+ - `send_by_email`
9
+ - `links`
10
+ - `import`
11
+ - Support per request `api_key` e.g `Pennylane::Customer.retrieve('cus_id', {api_key: 'x0fd....'})`
12
+ - Endpoints returning empty response we are doing +1 GET request at the resource. Not the best solution but at least we are sure we have fresh data.
13
+ - Added resource properties access with `#[]` e.g `cus['name']`
14
+
15
+ ## [0.2.0-alpha] - 2024-04-20
16
+
17
+ - Support resources
18
+ - `CategoryGroup`
19
+ - `Category`
20
+ - `Product`
21
+ - Added missing `#create` to `Supplier`
22
+ - Adding `#update` to `Customer` and `Supplier`
23
+
24
+
3
25
  ## [0.1.0] - 2024-04-04
4
26
 
5
- - Initial release
27
+ - 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 (1.0.0)
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
@@ -32,12 +32,177 @@ Pennylane.api_key = 'x0fd....'
32
32
 
33
33
  # list customers
34
34
  Pennylane::Customer.list
35
+ ```
36
+
37
+ ### Customers ([API Doc](https://pennylane.readme.io/reference/customers-post-1))
35
38
 
39
+ ```ruby
36
40
  # filter and paginate customers
37
41
  Pennylane::Customer.list(filter: [{field: 'name', operator: 'eq', value: 'Apple'}], page: 2)
38
42
 
39
43
  # Retrieve single customer
40
44
  Pennylane::Customer.retrieve('38a1f19a-256d-4692-a8fe-0a16403f59ff')
45
+
46
+ # Update a customer
47
+ cus = Pennylane::Customer.retrieve('38a1f19a-256d-4692-a8fe-0a16403f59ff')
48
+ cus.update(name: 'Apple Inc')
49
+
50
+ # Create a customer
51
+ Pennylane::Customer.create customer_type: 'company', name: 'Tesla', address: '4 rue du faubourg', postal_code: '75008', city: 'Paris', country_alpha2: 'FR'
52
+ ```
53
+
54
+ ### CustomerInvoices ([API Doc](https://pennylane.readme.io/reference/customer_invoices-import-1))
55
+
56
+ ```ruby
57
+ # Create a customer invoice
58
+ Pennylane::CustomerInvoice.create(
59
+ create_customer: true,
60
+ create_products: true,
61
+ invoice: {
62
+ date: '2021-01-01',
63
+ deadline: '2021-01-31',
64
+ customer: {
65
+ name: 'Tesla',
66
+ customer_type: 'company',
67
+ address: '4 rue du faubourg',
68
+ postal_code: '75001',
69
+ city: 'Paris',
70
+ country_alpha2: 'FR',
71
+ emails: ['stephane@tesla.com'] },
72
+ line_items: [
73
+ {
74
+ description: 'Consulting',
75
+ quantity: 1,
76
+ unit_price: 1000
77
+ }
78
+ ]
79
+ }
80
+ )
81
+
82
+ # List customer invoices
83
+ Pennylane::CustomerInvoice.list
84
+
85
+ # Retrieve a customer invoice
86
+ invoice = Pennylane::CustomerInvoice.retrieve('38a1f19a-256d-4692-a8fe-0a16403f59ff')
87
+
88
+ # Finalize a customer invoice
89
+ invoice.finalize
90
+
91
+ # Send a customer invoice
92
+ invoice.send_by_email
93
+
94
+ # Mark a customer invoice as paid
95
+ invoice.mark_as_paid
96
+
97
+ # Link an invoice and a credit note
98
+ credit_note = Pennylane::CustomerInvoice.retrieve('some-credit-note-id')
99
+ Pennylane::CustomerInvoice.links(invoice.quote_group_uuid, credit_note.quote_group_uuid)
100
+
101
+ # Import a customer invoice
102
+ Pennylane::CustomerInvoice.import(file: Util.file(File.expand_path('../fixtures/files/invoice.pdf', __FILE__)),
103
+ create_customer: true,
104
+ invoice: { date: Date.today, deadline: Date.today >> 1,
105
+ customer: {
106
+ name: 'Tesla',
107
+ customer_type: 'company',
108
+ address: '4 rue du faubourg',
109
+ postal_code: '75001',
110
+ city: 'Paris',
111
+ country_alpha2: 'FR',
112
+ emails: ['stephane@tesla.com'] },
113
+ line_items: [
114
+ {
115
+ description: 'Consulting',
116
+ quantity: 1,
117
+ unit_price: 1000
118
+ }
119
+ ]
120
+ }
121
+ )
122
+ ```
123
+ ### Suppliers ([API Doc](https://pennylane.readme.io/reference/suppliers-post))
124
+
125
+ ```ruby
126
+ # Create a supplier
127
+ Pennylane::Supplier.create(name: 'Apple Inc', address: '4 rue du faubourg', postal_code: '75008', city: 'Paris', country_alpha2: 'FR')
128
+
129
+ # Retrieve a supplier
130
+ Pennylane::Supplier.retrieve('supplier_id')
131
+
132
+ # List all suppliers
133
+ Pennylane::Supplier.list
134
+ ```
135
+
136
+ ### Products ([API Doc](https://pennylane.readme.io/reference/products-post-1))
137
+
138
+ ```ruby
139
+ # Create a product
140
+ Pennylane::Product.create(label: 'Macbook Pro', unit: 'piece', price: 2_999, vat_rate: 'FR_200', currency: 'EUR')
141
+
142
+ # List all products
143
+ Pennylane::Product.list
144
+
145
+ # Retrieve a product
146
+ product = Pennylane::Product.retrieve('product_id')
147
+
148
+ # Update a product
149
+ product.update(label: 'Macbook Pro 2021')
150
+ ```
151
+
152
+ ### Categories ([API Doc](https://pennylane.readme.io/reference/tags-get))
153
+
154
+ ```ruby
155
+ # Create a category
156
+ Pennylane::Category.create(name: 'IT')
157
+
158
+ # Retrieve a category
159
+ Pennylane::Category.retrieve('category_id')
160
+
161
+ # List all categories
162
+ Pennylane::Category.list
163
+
164
+ # Update a category
165
+ category = Pennylane::Category.retrieve('category_id')
166
+ category.update(name: 'IT Services')
167
+ ```
168
+ ### CategoryGroups ([API Doc](https://pennylane.readme.io/reference/tag-groups-get))
169
+ ```ruby
170
+ # List all category groups
171
+ Pennylane::CategoryGroup.list
172
+ ```
173
+
174
+ ### Per-request api key
175
+ For apps that need to use multiple keys during the lifetime of a process. it's also possible to set a per-request key:
176
+ ```ruby
177
+ require "pennylane"
178
+
179
+ Pennylane::Customer.list(
180
+ {},
181
+ {
182
+ api_key: 'x1fa....'
183
+ }
184
+ )
185
+
186
+ Pennylane::Customer.retrieve(
187
+ '38a1f19a-256d-4692-a8fe-0a16403f59ff',
188
+ {
189
+ api_key: 'x1fa....'
190
+ }
191
+ )
192
+
193
+ ```
194
+ ### Accessing resource properties
195
+
196
+ Both indexer and accessors can be used to retrieve values of resource properties.
197
+
198
+ ```ruby
199
+ customer = Pennylane::Customer.retrieve('customer_id')
200
+ puts customer['name']
201
+ puts customer.name
202
+
203
+ # NOTE: To do this the gem will try to guess the key of the resource.
204
+ # Otherwise we will have to do Pennylane::Customer.retrieve('customer_id').customer.name
205
+ # We rely on `method_missing` to do Pennylane::Customer.retrieve('customer_id').name
41
206
  ```
42
207
 
43
208
  ## Test mode
@@ -46,9 +211,30 @@ Pennylane provide a [test environment](https://help.pennylane.com/fr/articles/18
46
211
 
47
212
  ## Development
48
213
 
49
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test-unit` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
214
+ ```bash
215
+ bundle install
216
+ bundle exec rake test
217
+ ```
218
+
219
+ Resources implemented so far :
220
+ ### CUSTOMER INVOICING
221
+
222
+ - Customer Invoices ✅
223
+ - Estimates 🚧
224
+ - Billing Subscriptions 🚧
225
+
226
+ ### REFERENTIALS
227
+
228
+ - Customers ✅
229
+ - Suppliers ✅
230
+ - Categories ✅
231
+ - CategoryGroups ✅
232
+ - Products ✅
233
+ - Plan Items 🚧
234
+ - Enums 🚧
50
235
 
51
- 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).
236
+ ### SUPPLIER INVOICING
237
+ - Supplier Invoices 🚧
52
238
 
53
239
  ## Contributing
54
240
 
@@ -3,7 +3,7 @@ module Pennylane
3
3
  BASE_URI = 'app.pennylane.com/api/external'.freeze
4
4
  VERSION = 'v1'.freeze
5
5
 
6
- attr_accessor :key, :version
6
+ attr_accessor :version
7
7
 
8
8
  def initialize(key, version: 'v1')
9
9
  @key = key
@@ -21,7 +21,7 @@ module Pennylane
21
21
  end
22
22
 
23
23
 
24
- def authorization
24
+ def authorization(key)
25
25
  "Bearer #{key}"
26
26
  end
27
27
  def http
@@ -34,10 +34,9 @@ module Pennylane
34
34
  Net::HTTP.const_get(method.to_s.capitalize)
35
35
  end
36
36
 
37
- def request method, path, params:, opts: {}
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
37
+ def request method, path, params: {}, opts: {}
38
+ req = initialize_request(method, path, params[:query], opts).tap do |req|
39
+ req.body = params[:body].to_json if params[:body]
41
40
  end
42
41
 
43
42
  http.request(req).tap do |resp|
@@ -62,10 +61,11 @@ module Pennylane
62
61
  def should_handle_as_error?(code)
63
62
  code.to_i >= 400
64
63
  end
65
- def initialize_request method=nil, path=nil, params={}
64
+
65
+ def initialize_request method=nil, path=nil, params={}, opts={}
66
66
  klass(method).new(url(path, params)).tap do |request|
67
67
  request["content-type"] = 'application/json'
68
- request["authorization"] = authorization
68
+ request["authorization"] = authorization(opts.fetch(:api_key, @key))
69
69
  end
70
70
  end
71
71
 
@@ -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
@@ -89,9 +86,7 @@ module Pennylane
89
86
  when Pennylane::Object
90
87
  obj.class.build_from(
91
88
  deep_copy(obj.instance_variable_get(:@values)),
92
- obj.instance_variable_get(:@opts).select do |k, _v|
93
- Util::OPTS_COPYABLE.include?(k)
94
- end
89
+ obj.instance_variable_get(:@opts)
95
90
  )
96
91
  else
97
92
  obj
@@ -1,6 +1,7 @@
1
1
  module Pennylane
2
2
  module Resources
3
3
  class Base < Pennylane::Object
4
+
4
5
  class << self
5
6
 
6
7
  def object_name
@@ -11,40 +12,74 @@ module Pennylane
11
12
  "#{object_name}s"
12
13
  end
13
14
 
14
- def request_pennylane_object(method:, path:, params: {}, opts: {}, usage: [])
15
+ def request_pennylane_object(method:, path:, params: {}, opts: {}, usage: [], with: {})
15
16
  resp, opts = execute_resource_request(method, path, params, opts, usage)
16
- Util.convert_to_pennylane_object(Util.normalize_response(resp), params, opts)
17
+ if resp.empty?
18
+ {}
19
+ else
20
+ Util.convert_to_pennylane_object(Util.normalize_response(resp, with), params, opts)
21
+ end
17
22
  end
18
23
 
19
24
  def execute_resource_request(method, path, params = {}, opts = {}, usage = [])
20
- api_key = opts.delete(:api_key) || Pennylane.api_key
21
-
22
-
23
25
  resp = client.request(
24
26
  method,
25
27
  path,
26
28
  params: params,
27
29
  opts: opts
28
30
  )
29
-
30
- [JSON.parse(resp.read_body), opts]
31
+ [JSON.parse(resp.read_body || "{}"), opts] # in case body is nil ew return an empty hash
31
32
  end
32
33
 
33
34
  def client
34
- @client ||= Pennylane::Client.new(Pennylane.api_key)
35
+ @client ||= {}
36
+ @client[Pennylane.api_key] ||= Pennylane::Client.new(Pennylane.api_key)
35
37
  end
38
+
39
+ def normalize_filters(filters)
40
+ filters[:filter] = filters[:filter].to_json if filters[:filter]
41
+ filters
42
+ end
43
+ end
44
+
45
+ # object happens to be nil when the object is in a list
46
+ def id
47
+ object.source_id
48
+ rescue
49
+ super
50
+ end
51
+
52
+ # object happens to be nil when the object is the nested object
53
+ def [](k)
54
+ (object && object[k.to_sym]) || @values[k.to_sym]
55
+ end
56
+
57
+ #
58
+ def object
59
+ @values[self.class.object_name.to_sym]
60
+ end
61
+
62
+ # def inspect
63
+ # id_string = respond_to?(:id) && !id.nil? ? " id=#{id}" : ""
64
+ # "#<#{self.class}:0x#{object_id.to_s(16)}#{id_string}> JSON: " +
65
+ # JSON.pretty_generate(object.instance_variable_get(:@values) || @values)
66
+ # end
67
+
68
+ def update(attributes)
69
+ resp, opts = self.class.request_pennylane_object(method: :put, path: "/#{self.class.object_name_plural}/#{id}", params: { body: { self.class.object_name => attributes } })
70
+ @values = resp.instance_variable_get :@values
71
+ self
36
72
  end
37
73
 
38
74
  # So we can call directly method on the object rather than going through his key
39
75
  # Pennylane::Customer.retrieve('any-id').name == Pennylane::Customer.retrieve('any-id').customer.name
40
76
  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)
77
+ raise NoMethodError, "undefined method `#{method_name}` for #{self.class}" unless object
78
+ object.send(method_name, *args, &block)
44
79
  end
45
80
 
46
81
  def respond_to_missing?(method_name, include_private = false)
47
- @values[self.class.object_name.to_sym].respond_to?(method_name) || super
82
+ object.respond_to?(method_name) || super
48
83
  end
49
84
 
50
85
  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,90 @@
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
+ def import params, opts={}
24
+ request_pennylane_object(method: :post, path: "/customer_invoices/import", params: { body: params }, opts: opts, with: { invoice: 'customer_invoice' })
25
+ end
26
+
27
+ def links(*quote_group_uuids, opts: {})
28
+ request_pennylane_object(method: :post, path: "/customer_invoices/links", params: { body: { quote_group_uuids: quote_group_uuids } },
29
+ opts: {})
30
+ end
31
+ end
32
+
33
+ # since object name is different from the class name, we need to override the object_name method
34
+ def object
35
+ @values[:invoice]
36
+ end
37
+
38
+ # doesnt have a `source_id` so we override it
39
+ def id
40
+ object.id
41
+ end
42
+
43
+ # API CALLS
44
+
45
+ # since object name is different from the class name, we need to override the method
46
+ def update(attributes)
47
+ resp, opts = self.class.request_pennylane_object(method: :put,
48
+ path: "/customer_invoices/#{id}",
49
+ params: { body: { 'invoice' => attributes } },
50
+ opts: {}, with: { invoice: 'customer_invoice' })
51
+ @values = resp.instance_variable_get :@values
52
+ self
53
+ end
54
+
55
+ def finalize
56
+ request_and_retrieve(method: :put, path: "/customer_invoices/#{id}", action: 'finalize')
57
+ end
58
+
59
+ def mark_as_paid
60
+ resp, opts = self.class.request_pennylane_object(method: :put,
61
+ path: "/customer_invoices/#{id}/mark_as_paid",
62
+ params: {},
63
+ opts: {}, with: { invoice: 'customer_invoice' })
64
+ @values = resp.instance_variable_get :@values
65
+ self
66
+ end
67
+
68
+ def send_by_email
69
+ request_and_retrieve(method: :post, path: "/customer_invoices/#{id}", action: 'send_by_email')
70
+ end
71
+
72
+ private
73
+
74
+ # When API returns an empty body
75
+ # so we need to skip values assignment from the response
76
+ # GET /customer_invoices/:id again to get the updated values
77
+ def request_and_retrieve(method:, path:, action:)
78
+ self.class.request_pennylane_object(method: method,
79
+ path: "#{path}/#{action}",
80
+ params: {},
81
+ opts: {}, with: { invoice: 'customer_invoice' })
82
+ resp, opts = self.class.request_pennylane_object(method: :get,
83
+ path: path,
84
+ params: {},
85
+ opts: {}, with: { invoice: 'customer_invoice' })
86
+ @values = resp.instance_variable_get :@values
87
+ self
88
+ end
89
+ end
90
+ 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,34 +20,49 @@ 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
48
58
  end
49
59
  end
50
60
 
61
+ def file(file_path)
62
+ file_data = File.open(file_path).read
63
+ Base64.strict_encode64(file_data)
64
+ end
65
+
51
66
  # This method is used to convert the keys of a hash from strings to symbols
52
67
  def symbolize_names(object)
53
68
  case 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 = "1.0.0"
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: 1.0.0
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-29 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