lightspeed_pos 0.1.0 → 0.6.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 (57) hide show
  1. checksums.yaml +5 -5
  2. data/.mailmap +4 -0
  3. data/.rubocop.yml +32 -24
  4. data/.rubocop_todo.yml +284 -0
  5. data/.travis.yml +5 -3
  6. data/Gemfile +2 -0
  7. data/README.markdown +85 -28
  8. data/Rakefile +2 -0
  9. data/bin/console +33 -6
  10. data/lib/lightspeed/account.rb +50 -43
  11. data/lib/lightspeed/accounts.rb +24 -0
  12. data/lib/lightspeed/categories.rb +7 -5
  13. data/lib/lightspeed/category.rb +18 -7
  14. data/lib/lightspeed/client.rb +41 -27
  15. data/lib/lightspeed/collection.rb +214 -0
  16. data/lib/lightspeed/customer.rb +36 -0
  17. data/lib/lightspeed/customers.rb +11 -0
  18. data/lib/lightspeed/employee.rb +27 -0
  19. data/lib/lightspeed/employees.rb +10 -0
  20. data/lib/lightspeed/error.rb +17 -0
  21. data/lib/lightspeed/image.rb +37 -0
  22. data/lib/lightspeed/images.rb +18 -0
  23. data/lib/lightspeed/inventories.rb +10 -0
  24. data/lib/lightspeed/inventory.rb +14 -0
  25. data/lib/lightspeed/item.rb +55 -18
  26. data/lib/lightspeed/item_attribute_set.rb +15 -0
  27. data/lib/lightspeed/item_attribute_sets.rb +10 -0
  28. data/lib/lightspeed/item_matrices.rb +6 -3
  29. data/lib/lightspeed/item_matrix.rb +50 -10
  30. data/lib/lightspeed/items.rb +6 -7
  31. data/lib/lightspeed/order.rb +36 -0
  32. data/lib/lightspeed/orders.rb +12 -0
  33. data/lib/lightspeed/price_level.rb +16 -0
  34. data/lib/lightspeed/price_levels.rb +10 -0
  35. data/lib/lightspeed/prices.rb +45 -0
  36. data/lib/lightspeed/request.rb +98 -29
  37. data/lib/lightspeed/request_throttler.rb +33 -0
  38. data/lib/lightspeed/resource.rb +221 -0
  39. data/lib/lightspeed/sale.rb +59 -0
  40. data/lib/lightspeed/sale_line.rb +54 -0
  41. data/lib/lightspeed/sale_lines.rb +11 -0
  42. data/lib/lightspeed/sales.rb +12 -0
  43. data/lib/lightspeed/shop.rb +32 -0
  44. data/lib/lightspeed/shops.rb +10 -0
  45. data/lib/lightspeed/special_order.rb +24 -0
  46. data/lib/lightspeed/special_orders.rb +12 -0
  47. data/lib/lightspeed/vendor.rb +25 -0
  48. data/lib/lightspeed/vendors.rb +11 -0
  49. data/lib/lightspeed/version.rb +3 -1
  50. data/lib/lightspeed_pos.rb +5 -5
  51. data/lightspeed_pos.gemspec +11 -7
  52. data/script/buildkite +24 -0
  53. data/script/docker_tests +29 -0
  54. metadata +96 -38
  55. data/lib/lightspeed/account_resources.rb +0 -103
  56. data/lib/lightspeed/base.rb +0 -17
  57. data/lib/lightspeed/errors.rb +0 -8
data/Rakefile CHANGED
@@ -1 +1,3 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler/gem_tasks"
data/bin/console CHANGED
@@ -1,14 +1,41 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "lightspeed/pos"
4
+ require 'bundler/setup'
5
+ require 'dotenv'
6
+ require 'lightspeed_pos'
7
+ Lightspeed::Request.verbose = true
5
8
 
6
- # You can add fixtures and/or initialization code here to make experimenting
7
- # with your gem easier. You can also use a different console, if you like.
9
+ class TokenHolder
10
+ def initialize(token: nil, refresh_token: nil)
11
+ @token = token
12
+ @refresh_token = refresh_token
13
+ end
14
+
15
+ def oauth_token
16
+ @token
17
+ end
18
+
19
+ def refresh_oauth_token
20
+ raise "Token expired, refresh not yet implemented"
21
+ end
22
+ end
8
23
 
9
- # (If you use this, don't forget to add pry to your Gemfile!)
10
24
  # require "pry"
11
25
  # Pry.start
26
+ Dotenv.load
27
+ def client
28
+ token = ENV['LIGHTSPEED_OAUTH_TOKEN']
29
+ refresh_token = ENV['LIGHTSPEED_OAUTH_REFRESH_TOKEN']
30
+ raise 'set LIGHTSPEED_OAUTH_TOKEN as an envorinment variable to use this' unless token
31
+ raise 'set LIGHTSPEED_OAUTH_REFRESH_TOKEN as an envorinment variable to use this' unless refresh_token
32
+ token_holder = Tokenholder.new(token: token, refresh_token: refresh_token)
33
+ @client ||= Lightspeed::Client.new(oauth_token_holder: token_holder)
34
+ end
35
+
36
+ def account
37
+ client.accounts.first
38
+ end
12
39
 
13
- require "irb"
40
+ require 'irb'
14
41
  IRB.start
@@ -1,48 +1,55 @@
1
- require 'lightspeed/base'
2
- require 'lightspeed/categories'
3
- require 'lightspeed/items'
4
- require 'lightspeed/item_matrices'
1
+ # frozen_string_literal: true
2
+
3
+ require 'uri'
4
+ require_relative 'categories'
5
+ require_relative 'employees'
6
+ require_relative 'items'
7
+ require_relative 'item_matrices'
8
+ require_relative 'item_attribute_sets'
9
+ require_relative 'images'
10
+ require_relative 'inventories'
11
+ require_relative 'orders'
12
+ require_relative 'price_levels'
13
+ require_relative 'sales'
14
+ require_relative 'shops'
15
+ require_relative 'special_orders'
16
+ require_relative 'vendors'
17
+ require_relative 'customers'
5
18
 
6
19
  module Lightspeed
7
- class Account < Lightspeed::Base
8
- attr_accessor :id, :name, :link
9
-
10
- def client
11
- owner
12
- end
13
-
14
- def items
15
- item_proxy
16
- end
17
-
18
- def categories
19
- category_proxy
20
- end
21
-
22
- def item_matrices
23
- item_matrices_proxy
24
- end
25
-
26
- def instantiate(*args)
27
- client.instantiate(self, *args)
28
- end
29
-
30
- private
31
-
32
- def self.id_field
33
- "accountID"
34
- end
35
-
36
- def item_proxy
37
- @item_proxy ||= Lightspeed::Items.new(self)
38
- end
39
-
40
- def category_proxy
41
- @category_proxy ||= Lightspeed::Categories.new(self)
42
- end
43
-
44
- def item_matrices_proxy
45
- @item_matrices_proxy ||= Lightspeed::ItemMatrices.new(self)
20
+ class Account < Lightspeed::Resource
21
+ fields(
22
+ accountID: :id,
23
+ name: :string,
24
+ link: :hash
25
+ )
26
+ relationships(
27
+ :Categories,
28
+ :Employees,
29
+ :Images,
30
+ :Inventories,
31
+ :ItemMatrices,
32
+ :ItemAttributeSets,
33
+ :Items,
34
+ :Orders,
35
+ :PriceLevels,
36
+ :Sales,
37
+ :Shops,
38
+ :SpecialOrders,
39
+ :Vendors,
40
+ :Customers
41
+ )
42
+
43
+ def account
44
+ self
45
+ end
46
+
47
+ def link
48
+ if @link.is_a?(Hash)
49
+ @link['@attributes']['href']
50
+ else
51
+ @link
52
+ end
46
53
  end
47
54
  end
48
55
  end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'collection'
4
+ require_relative 'account'
5
+
6
+ module Lightspeed
7
+ class Accounts < Lightspeed::Collection
8
+ def base_path
9
+ '/Account'
10
+ end
11
+
12
+ def account
13
+ first_loaded || first
14
+ end
15
+
16
+ def page(n, **args)
17
+ # turns out lightspeed doesn't respect pagination for accounts.
18
+ # so page(1) is identical to page(0).
19
+ # they should be different, thus.
20
+ # if someone has more than 100 store accounts, well, good for them.
21
+ n.zero? ? super : []
22
+ end
23
+ end
24
+ end
@@ -1,10 +1,12 @@
1
- require 'lightspeed/category'
2
- require 'lightspeed/account_resources'
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'category'
4
+ require_relative 'collection'
3
5
 
4
6
  module Lightspeed
5
- class Categories < Lightspeed::AccountResources
6
- def self.resource_name
7
- "Category"
7
+ class Categories < Lightspeed::Collection
8
+ def load_relations_default
9
+ nil
8
10
  end
9
11
  end
10
12
  end
@@ -1,10 +1,21 @@
1
- module Lightspeed
2
- class Category < Base
3
- attr_accessor :name, :nodeDepth, :fullPathName, :leftNode, :rightNode, :timeStamp, :parentID,
4
- :createTime
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'resource'
5
4
 
6
- def self.id_field
7
- "categoryID"
8
- end
5
+ module Lightspeed
6
+ class Category < Lightspeed::Resource
7
+ fields(
8
+ categoryID: :id,
9
+ name: :string,
10
+ nodeDepth: :integer,
11
+ fullPathName: :string,
12
+ leftNode: :integer,
13
+ rightNode: :integer,
14
+ createTime: :datetime,
15
+ timeStamp: :datetime,
16
+ parentID: :id,
17
+ Category: :hash
18
+ )
19
+ relationships Parent: :Category
9
20
  end
10
21
  end
@@ -1,45 +1,59 @@
1
- require 'lightspeed/account'
2
- require 'lightspeed/request'
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/array/wrap'
4
+
5
+ require_relative 'accounts'
6
+ require_relative 'request'
7
+ require_relative 'request_throttler'
3
8
 
4
9
  module Lightspeed
5
10
  class Client
6
- attr_accessor :api_key, :oauth_token
11
+ attr_accessor :throttler
7
12
 
8
- def initialize(api_key: nil, oauth_token: nil)
9
- @api_key = api_key
10
- @oauth_token = oauth_token
11
- end
13
+ def initialize(oauth_token_holder: nil, oauth_token: nil)
14
+ @oauth_token_holder = oauth_token_holder
15
+ @throttler = Lightspeed::RequestThrottler.new
12
16
 
13
- def request(**args)
14
- Lightspeed::Request.new(self, **args)
17
+ raise "Passing an oauth token is no longer supported. Pass a token holder instead." if oauth_token
15
18
  end
16
19
 
17
20
  # Returns a list of accounts that you have access to.
18
21
  def accounts
19
- request = request(method: :get, path: "/Account.json")
20
- response = request.perform
21
- instantiate(response["Account"], Lightspeed::Account)
22
+ @accounts ||= Lightspeed::Accounts.new(context: self)
22
23
  end
23
24
 
24
- # Instantiates a bunch of records from Lightspeed into their proper classes.
25
- def instantiate(owner = self, records, klass)
26
- records = splat(records)
27
- records.map do |record|
28
- klass.new(owner, record)
29
- end
25
+ def get(**args)
26
+ perform_request(**args, method: :get)
27
+ end
28
+
29
+ def post(**args)
30
+ perform_request(**args, method: :post)
31
+ end
32
+
33
+ def put(**args)
34
+ perform_request(**args, method: :put)
35
+ end
36
+
37
+ def delete(**args)
38
+ perform_request(**args, method: :delete)
39
+ end
40
+
41
+ def oauth_token
42
+ @oauth_token_holder.oauth_token
43
+ end
44
+
45
+ def refresh_oauth_token
46
+ @oauth_token_holder.refresh_oauth_token
30
47
  end
31
48
 
32
49
  private
33
50
 
34
- # Converts a thing to an Array unless it is already.
35
- # Unfortunately necessary because Lightspeed's API may return an object,
36
- # or an array of objects.
37
- #
38
- # The compact is becuase it may return nothing at all.
39
- # In the example of fetching categories resource where there are no categories,
40
- # response["Category"] will not be present.
41
- def splat(thing)
42
- (thing.is_a?(Array) ? thing : [thing]).compact
51
+ def perform_request(**args)
52
+ @throttler.perform_request request(**args)
53
+ end
54
+
55
+ def request **args
56
+ Lightspeed::Request.new(self, **args)
43
57
  end
44
58
  end
45
59
  end
@@ -0,0 +1,214 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string'
4
+ require 'active_support/core_ext/array/wrap'
5
+
6
+ module Lightspeed
7
+ class Collection
8
+ PER_PAGE = 100 # the max page of records returned in a request
9
+
10
+ attr_accessor :context, :resources
11
+
12
+ def initialize(context:, attributes: nil)
13
+ self.context = context
14
+ instantiate(attributes)
15
+ end
16
+
17
+ def account
18
+ context.account
19
+ end
20
+
21
+ def unload
22
+ @resources = {}
23
+ end
24
+
25
+ def client
26
+ return context if context.is_a?(Lightspeed::Client)
27
+ account.client
28
+ end
29
+
30
+ def first(params: {})
31
+ params = params.merge(limit: 1)
32
+ instantiate(get(params: params)).first
33
+ end
34
+
35
+ def size(params: {})
36
+ params = params.merge(limit: 1, load_relations: nil)
37
+ get(params: params)['@attributes']['count'].to_i
38
+ end
39
+ alias_method :length, :size
40
+
41
+ def each_loaded
42
+ @resources ||= {}
43
+ @resources.each_value
44
+ end
45
+
46
+ def all_loaded
47
+ each_loaded.to_a
48
+ end
49
+
50
+ def first_loaded
51
+ each_loaded.first
52
+ end
53
+
54
+ def size_loaded
55
+ @resources.size
56
+ end
57
+
58
+ def all(params: {})
59
+ enum_page(params: params).to_a.flatten(1)
60
+ end
61
+
62
+ def each_page(per_page: PER_PAGE, params: {}, &block)
63
+ enum_page(per_page: per_page, params: params).each(&block)
64
+ end
65
+
66
+ def enum_page(per_page: PER_PAGE, params: {})
67
+ Enumerator.new do |yielder|
68
+ loop.with_index do |_, n|
69
+ resources = page(n, per_page: per_page, params: params)
70
+ yielder << resources
71
+ raise StopIteration if resources.length < per_page
72
+ end
73
+ end
74
+ end
75
+
76
+ def enum(per_page: PER_PAGE, params: {})
77
+ Enumerator.new do |yielder|
78
+ each_page(per_page: per_page, params: params) do |page|
79
+ page.each { |resource| yielder << resource }
80
+ end
81
+ end
82
+ end
83
+
84
+ def each(per_page: PER_PAGE, params: {}, &block)
85
+ enum(per_page: per_page, params: params).each(&block)
86
+ end
87
+
88
+ def find(id)
89
+ first(params: { resource_class.id_field => id }) || handle_not_found(id)
90
+ end
91
+
92
+ def create(attributes = {})
93
+ instantiate(post(body: Yajl::Encoder.encode(attributes))).first
94
+ end
95
+
96
+ def update(id, attributes = {})
97
+ instantiate(put(id, body: Yajl::Encoder.encode(attributes))).first
98
+ end
99
+
100
+ def destroy(id)
101
+ instantiate(delete(id)).first
102
+ end
103
+
104
+ def self.collection_name
105
+ name.demodulize
106
+ end
107
+
108
+ def self.resource_name
109
+ collection_name.singularize
110
+ end
111
+
112
+ def self.resource_class
113
+ "Lightspeed::#{resource_name}".constantize
114
+ end
115
+
116
+ def base_path
117
+ "#{account.base_path}/#{resource_name}"
118
+ end
119
+
120
+ def inspect
121
+ "#<#{self.class.name} API#{base_path}>"
122
+ end
123
+
124
+ def as_json
125
+ return if all_loaded.empty?
126
+ { resource_name => all_loaded.as_json }
127
+ end
128
+ alias_method :to_h, :as_json
129
+
130
+ def to_json
131
+ Yajl::Encoder.encode(as_json)
132
+ end
133
+
134
+ def page(n, per_page: PER_PAGE, params: {})
135
+ params = params.merge(limit: per_page, offset: per_page * n)
136
+ instantiate(get(params: params))
137
+ end
138
+
139
+ def load_relations_default
140
+ 'all'
141
+ end
142
+
143
+ private
144
+
145
+ def handle_not_found(id)
146
+ raise Lightspeed::Error::NotFound, "Could not find a #{resource_name} with #{resource_class.id_field}=#{id}"
147
+ end
148
+
149
+ def context_params
150
+ if context.class.respond_to?(:id_field) &&
151
+ resource_class.method_defined?(context.class.id_field.to_sym)
152
+ { context.class.id_field => context.id }
153
+ else
154
+ {}
155
+ end
156
+ end
157
+
158
+ def instantiate(response)
159
+ return [] unless response.is_a?(Hash)
160
+ @resources ||= {}
161
+ Array.wrap(response[resource_name]).map do |resource|
162
+ resource = resource_class.new(context: self, attributes: resource)
163
+ @resources[resource.id] = resource
164
+ end
165
+ end
166
+
167
+ def resource_class
168
+ self.class.resource_class
169
+ end
170
+
171
+ def resource_name
172
+ self.class.resource_name
173
+ end
174
+
175
+ def get(params: {})
176
+ params = { load_relations: load_relations_default }
177
+ .merge(context_params)
178
+ .merge(params)
179
+ .compact
180
+ client.get(
181
+ path: collection_path,
182
+ params: params
183
+ )
184
+ end
185
+
186
+ def post(body:)
187
+ client.post(
188
+ path: collection_path,
189
+ body: body
190
+ )
191
+ end
192
+
193
+ def put(id, body:)
194
+ client.put(
195
+ path: resource_path(id),
196
+ body: body
197
+ )
198
+ end
199
+
200
+ def delete(id)
201
+ client.delete(
202
+ path: resource_path(id)
203
+ )
204
+ end
205
+
206
+ def collection_path
207
+ "#{base_path}.json"
208
+ end
209
+
210
+ def resource_path(id)
211
+ "#{base_path}/#{id}.json"
212
+ end
213
+ end
214
+ end