lightspeed_pos 0.1.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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