lightspeed_pos 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/README.markdown +57 -29
  3. data/bin/console +15 -7
  4. data/lib/lightspeed/account.rb +44 -43
  5. data/lib/lightspeed/accounts.rb +22 -0
  6. data/lib/lightspeed/categories.rb +5 -5
  7. data/lib/lightspeed/category.rb +16 -7
  8. data/lib/lightspeed/client.rb +37 -26
  9. data/lib/lightspeed/collection.rb +218 -0
  10. data/lib/lightspeed/employee.rb +25 -0
  11. data/lib/lightspeed/employees.rb +8 -0
  12. data/lib/lightspeed/{errors.rb → error.rb} +2 -1
  13. data/lib/lightspeed/image.rb +35 -0
  14. data/lib/lightspeed/images.rb +16 -0
  15. data/lib/lightspeed/inventories.rb +8 -0
  16. data/lib/lightspeed/inventory.rb +12 -0
  17. data/lib/lightspeed/item.rb +54 -18
  18. data/lib/lightspeed/item_attribute_set.rb +13 -0
  19. data/lib/lightspeed/item_attribute_sets.rb +8 -0
  20. data/lib/lightspeed/item_matrices.rb +4 -3
  21. data/lib/lightspeed/item_matrix.rb +48 -10
  22. data/lib/lightspeed/items.rb +4 -7
  23. data/lib/lightspeed/order.rb +34 -0
  24. data/lib/lightspeed/orders.rb +10 -0
  25. data/lib/lightspeed/prices.rb +43 -0
  26. data/lib/lightspeed/request.rb +74 -28
  27. data/lib/lightspeed/request_throttler.rb +31 -0
  28. data/lib/lightspeed/resource.rb +221 -0
  29. data/lib/lightspeed/sale.rb +57 -0
  30. data/lib/lightspeed/sale_line.rb +52 -0
  31. data/lib/lightspeed/sale_lines.rb +9 -0
  32. data/lib/lightspeed/sales.rb +10 -0
  33. data/lib/lightspeed/shop.rb +30 -0
  34. data/lib/lightspeed/shops.rb +8 -0
  35. data/lib/lightspeed/special_order.rb +22 -0
  36. data/lib/lightspeed/special_orders.rb +10 -0
  37. data/lib/lightspeed/vendor.rb +23 -0
  38. data/lib/lightspeed/vendors.rb +9 -0
  39. data/lib/lightspeed/version.rb +1 -1
  40. data/lib/lightspeed_pos.rb +2 -4
  41. data/lightspeed_pos.gemspec +3 -3
  42. data/script/buildkite +11 -0
  43. data/script/docker_tests +29 -0
  44. metadata +63 -24
  45. data/lib/lightspeed/account_resources.rb +0 -103
  46. data/lib/lightspeed/base.rb +0 -17
@@ -0,0 +1,25 @@
1
+ require_relative 'resource'
2
+
3
+ module Lightspeed
4
+ class Employee < Lightspeed::Resource
5
+ fields(
6
+ employeeID: :id,
7
+ firstName: :string,
8
+ lastName: :string,
9
+ accessPin: :string,
10
+ lockOut: :boolean,
11
+ archived: :boolean,
12
+ contactID: :integer,
13
+ clockInEmployeeHoursID: :integer,
14
+ employeeRoleID: :integer,
15
+ limitToShopID: :integer,
16
+ lastShopID: :integer,
17
+ lastSaleID: :integer,
18
+ lastRegisterID: :integer,
19
+ timeStamp: :datetime,
20
+ Contact: :hash,
21
+ EmployeeRole: :hash,
22
+ EmployeeRights: :hash
23
+ )
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'collection'
2
+
3
+ require_relative 'employee'
4
+
5
+ module Lightspeed
6
+ class Employees < Lightspeed::Collection
7
+ end
8
+ end
@@ -1,8 +1,9 @@
1
1
  module Lightspeed
2
- module Errors
2
+ class Error < Exception
3
3
  class BadRequest < Exception; end # 400
4
4
  class Unauthorized < Exception; end # 401
5
5
  class NotFound < Exception; end # 404
6
6
  class InternalServerError < Exception; end # 500
7
+ class Throttled < Exception; end # 503
7
8
  end
8
9
  end
@@ -0,0 +1,35 @@
1
+ require_relative 'resource'
2
+
3
+ require_relative 'item'
4
+ require_relative 'item_matrix'
5
+
6
+ module Lightspeed
7
+ class Image < Lightspeed::Resource
8
+ fields(
9
+ imageID: :id,
10
+ description: :string,
11
+ filename: :string,
12
+ baseImageURL: :string,
13
+ publicID: :string, # part of the file path; not a Lightspeed ID
14
+ itemID: :id,
15
+ itemMatrixID: :id,
16
+ Item: :hash,
17
+ ItemMatrix: :hash
18
+ )
19
+
20
+ relationships :Item, :ItemMatrix
21
+
22
+ def url
23
+ "#{baseImageURL}#{publicID}"
24
+ end
25
+
26
+ def base_path
27
+ if context.is_a?(Lightspeed::Item) ||
28
+ context.is_a?(Lightspeed::ItemMatrix)
29
+ "#{context.base_path}/#{resource_name}/#{id}"
30
+ else
31
+ super
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,16 @@
1
+ require_relative 'collection'
2
+
3
+ require_relative 'image'
4
+
5
+ module Lightspeed
6
+ class Images < Lightspeed::Collection
7
+ def base_path
8
+ if context.is_a?(Lightspeed::Item) ||
9
+ context.is_a?(Lightspeed::ItemMatrix)
10
+ "#{context.base_path}/#{resource_name}"
11
+ else
12
+ super
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'collection'
2
+
3
+ require_relative 'inventory'
4
+
5
+ module Lightspeed
6
+ class Inventories < Lightspeed::Collection
7
+ end
8
+ end
@@ -0,0 +1,12 @@
1
+ require_relative 'resource'
2
+
3
+ require_relative 'item'
4
+
5
+ module Lightspeed
6
+ class Inventory < Lightspeed::Resource
7
+ fields(
8
+ inventoryID: :integer
9
+ )
10
+ relationships :Item
11
+ end
12
+ end
@@ -1,26 +1,62 @@
1
- require 'lightspeed/base'
2
1
 
3
- module Lightspeed
4
- class Item < Lightspeed::Base
5
- attr_accessor :systemSku, :defaultCost, :avgCost, :discountable, :tax,
6
- :archived, :itemType, :description, :modelYear, :upc, :ean, :customSku,
7
- :manufacturerSku, :createTime, :timeStamp,
2
+ require_relative 'resource'
3
+
4
+ require_relative 'item_matrix'
5
+ require_relative 'category'
6
+ require_relative 'images'
7
+ require_relative 'prices'
8
8
 
9
- # Association keys
10
- :categoryID, :taxClassID, :departmentID, :itemMatrixID, :manufacturerID, :seasonID,
11
- :defaultVendorID, :itemECommerceID,
9
+ module Lightspeed
10
+ class Item < Lightspeed::Resource
11
+ alias_method :archive, :destroy
12
12
 
13
- # Embedded
14
- :ItemMatrix, :ItemAttributes, :ItemShops, :Prices, :Note
13
+ fields(
14
+ itemID: :id,
15
+ systemSku: :string,
16
+ defaultCost: :decimal,
17
+ avgCost: :decimal,
18
+ discountable: :boolean,
19
+ tax: :boolean,
20
+ archived: :boolean,
21
+ itemType: :string,
22
+ description: :string,
23
+ modelYear: :integer,
24
+ upc: :string,
25
+ ean: :string,
26
+ customSku: :string,
27
+ manufacturerSku: :string,
28
+ createTime: :datetime,
29
+ timeStamp: :datetime,
30
+ categoryID: :id,
31
+ taxClassID: :id,
32
+ departmentID: :id,
33
+ itemMatrixID: :id,
34
+ manufacturerID: :id,
35
+ seasonID: :id,
36
+ defaultVendorID: :id,
37
+ itemECommerceID: :id,
38
+ # Category: :hash,
39
+ TaxClass: :hash,
40
+ Department: :hash,
41
+ ItemAttributes: :hash,
42
+ # ItemMatrix: :hash,
43
+ # Images: :hash,
44
+ Manufacturer: :hash,
45
+ Note: :hash,
46
+ ItemECommerce: :hash,
47
+ ItemShops: :hash,
48
+ ItemComponents: :hash,
49
+ ItemShelfLocations: :hash,
50
+ ItemVendorNums: :hash,
51
+ CustomFieldValues: :hash,
52
+ Prices: :hash,
53
+ Tags: :hash
54
+ )
15
55
 
16
- def self.id_field
17
- "itemID"
18
- end
56
+ relationships :ItemMatrix, :Category, :Images, DefaultVendor: :Vendor
19
57
 
20
- # FUNFACT: ItemMatrix data is returned during an `update` request,
21
- # but not during a `find` request.
22
- def item_matrix
23
- @ItemMatrix ||= owner.item_matrices.find(itemMatrixID) # rubocop:disable VariableName
58
+ def prices
59
+ @prices ||= Lightspeed::Prices.new(self.Prices)
24
60
  end
25
61
  end
26
62
  end
@@ -0,0 +1,13 @@
1
+ require_relative 'resource'
2
+
3
+ module Lightspeed
4
+ class ItemAttributeSet < Lightspeed::Resource
5
+ fields(
6
+ itemAttributeSetID: :id,
7
+ name: :string,
8
+ attributeName1: :string,
9
+ attributeName2: :string,
10
+ attributeName3: :string
11
+ )
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ require_relative 'collection'
2
+
3
+ require_relative 'item_attribute_set'
4
+
5
+ module Lightspeed
6
+ class ItemAttributeSets < Lightspeed::Collection
7
+ end
8
+ end
@@ -1,8 +1,9 @@
1
- require 'lightspeed/item_matrix'
2
- require 'lightspeed/account_resources'
1
+ require_relative 'collection'
2
+
3
+ require_relative 'item_matrix'
3
4
 
4
5
  module Lightspeed
5
- class ItemMatrices < AccountResources
6
+ class ItemMatrices < Lightspeed::Collection
6
7
  def self.resource_name
7
8
  "ItemMatrix"
8
9
  end
@@ -1,17 +1,55 @@
1
+ require_relative 'resource'
2
+
3
+ require_relative 'item_attribute_set'
4
+ require_relative 'category'
5
+ require_relative 'items'
6
+
1
7
  module Lightspeed
2
- class ItemMatrix < Lightspeed::Base
3
- attr_accessor :description, :tax, :defaultCost, :itemType, :modelYear, :archived,
4
- :timeStamp
8
+ class ItemMatrix < Lightspeed::Resource
9
+ fields(
10
+ itemMatrixID: :id,
11
+ description: :string,
12
+ tax: :boolean,
13
+ defaultCost: :decimal,
14
+ itemType: :string,
15
+ modelYear: :integer,
16
+ archived: :boolean,
17
+ timeStamp: :datetime,
18
+ itemAttributeSetID: :id,
19
+ manufacturerID: :id,
20
+ categoryID: :id,
21
+ defaultVendorID: :id,
22
+ taxClassID: :id,
23
+ seasonID: :id,
24
+ departmentID: :id,
25
+ itemECommerceID: :id,
26
+ itemAttributeSet: :hash,
27
+ Manufacturer: :hash,
28
+ Category: :hash,
29
+ TaxClass: :hash,
30
+ Season: :hash,
31
+ Department: :hash,
32
+ ItemECommerce: :hash,
33
+ Images: :hash,
34
+ Items: :hash,
35
+ CustomFieldValues: :hash,
36
+ Prices: :hash
37
+ )
38
+
39
+ relationships :ItemAttributeSet, :Category, :Items
5
40
 
6
- # Association keys
7
- attr_accessor :itemAttributeSetID, :manufacturerID, :categoryID, :defaultVendorID,
8
- :taxClassID, :seasonID, :departmentID, :itemECommerceID
41
+ # overrides
9
42
 
10
- # Embedded
11
- attr_accessor :Prices
43
+ def self.collection_name
44
+ 'ItemMatrices'
45
+ end
46
+
47
+ def singular_path_parent
48
+ account
49
+ end
12
50
 
13
- def self.id_field
14
- "itemMatrixID"
51
+ def prices
52
+ @prices ||= Lightspeed::Prices.new(self.Prices)
15
53
  end
16
54
  end
17
55
  end
@@ -1,12 +1,9 @@
1
- require 'lightspeed/item'
2
- require 'lightspeed/account_resources'
1
+ require_relative 'collection'
2
+
3
+ require_relative 'item'
3
4
 
4
5
  module Lightspeed
5
- class Items < Lightspeed::AccountResources
6
+ class Items < Lightspeed::Collection
6
7
  alias_method :archive, :destroy
7
-
8
- def self.resource_name
9
- "Item"
10
- end
11
8
  end
12
9
  end
@@ -0,0 +1,34 @@
1
+ require_relative 'resource'
2
+
3
+ module Lightspeed
4
+ class Order < Lightspeed::Resource
5
+ alias_method :archive, :destroy
6
+
7
+ fields(
8
+ orderID: :id,
9
+ orderedDate: :timestamp,
10
+ receivedDate: :timestamp,
11
+ arrivalDate: :timestamp,
12
+ shipInstructions: :integer,
13
+ stockInstructions: :integer,
14
+ shipCost: :decimal,
15
+ otherCost: :decimal,
16
+ complete: :boolean,
17
+ archived: :boolean,
18
+ discount: :boolean,
19
+ totalDiscount: :decimal,
20
+ totalQuantity: :decimal,
21
+ vendorID: :id,
22
+ noteID: :id,
23
+ shopID: :id,
24
+ Vendor: :hash,
25
+ Note: :hash,
26
+ Shop: :hash,
27
+ OrderLines: :hash,
28
+ CustomFieldValues: :hash,
29
+ timeStamp: :timestamp
30
+ )
31
+
32
+ end
33
+ end
34
+
@@ -0,0 +1,10 @@
1
+ require_relative 'collection'
2
+
3
+ require_relative 'order'
4
+
5
+ module Lightspeed
6
+ class Orders < Lightspeed::Collection
7
+ alias_method :archive, :destroy
8
+ end
9
+ end
10
+
@@ -0,0 +1,43 @@
1
+ require 'active_support/core_ext/string'
2
+ require 'bigdecimal'
3
+
4
+ module Lightspeed
5
+ class Prices
6
+ def initialize(attributes)
7
+ @attributes = attributes
8
+ end
9
+
10
+ def prices
11
+ @prices ||= @attributes["ItemPrice"].map { |v| [v["useType"].parameterize.underscore.to_sym, BigDecimal.new(v["amount"])] }.to_h
12
+ end
13
+
14
+ def as_json
15
+ attributes
16
+ end
17
+ alias_method :to_h, :as_json
18
+
19
+ def to_json
20
+ as_json.to_json
21
+ end
22
+
23
+ def [](key)
24
+ prices[key]
25
+ end
26
+
27
+ def inspect
28
+ prices.inspect
29
+ end
30
+
31
+ def respond_to?(method, private_method)
32
+ prices.keys.include?(method) || super
33
+ end
34
+
35
+ def method_missing(method, *args, &block)
36
+ if prices.keys.include?(method)
37
+ prices[method]
38
+ else
39
+ super
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,55 +1,101 @@
1
+ require 'pp'
2
+ require 'net/http'
3
+
1
4
  module Lightspeed
2
5
  class Request
3
- attr_accessor :raw_request
6
+ attr_accessor :raw_request, :bucket_max, :bucket_level
7
+
8
+ SECONDS_TO_WAIT_WHEN_THROTTLED = 60 # API requirements.
4
9
 
5
- def self.base_url
6
- "https://api.merchantos.com/API"
10
+ class << self
11
+ attr_writer :verbose
7
12
  end
8
13
 
9
- def initialize(client, method:, path:, params: nil, body: nil)
10
- @raw_request = Typhoeus::Request.new(
11
- self.class.base_url + path,
12
- method: method,
13
- body: body,
14
- params: params
15
- )
16
-
17
- if client.oauth_token
18
- @raw_request.options[:headers].merge!(
19
- "Authorization" => "OAuth #{client.oauth_token}"
20
- )
21
- end
14
+ def self.verbose?
15
+ !! @verbose
16
+ end
22
17
 
23
- @raw_request.options[:userpwd] = "#{client.api_key}:apikey" if client.api_key
18
+ def self.base_host
19
+ "api.merchantos.com"
20
+ end
21
+
22
+ def self.base_path
23
+ "/API"
24
+ end
25
+
26
+ def initialize(client, method:, path:, params: nil, body: nil)
27
+ @method = method
28
+ @params = params
29
+ @path = path
30
+ @bucket_max = Float::INFINITY
31
+ @bucket_level = 0
32
+ @http = Net::HTTP.new(self.class.base_host, 443)
33
+ @http.use_ssl = true
34
+ @raw_request = request_class.new(uri)
35
+ @raw_request.body = body if body
36
+ @raw_request.set_form_data(@params) if @params && @method != :get
37
+ @raw_request["Authorization"] = "OAuth #{client.oauth_token}" if client.oauth_token
24
38
  end
25
39
 
26
40
  def perform
27
- response = raw_request.run
28
- if response.code == 200
41
+ response = @http.request(raw_request)
42
+ extract_rate_limits(response)
43
+ if response.code == "200"
29
44
  handle_success(response)
30
45
  else
31
46
  handle_error(response)
32
47
  end
48
+ rescue Lightspeed::Error::Throttled
49
+ retry_throttled_request
33
50
  end
34
51
 
35
52
  private
36
53
 
37
54
  def handle_success(response)
38
- JSON.parse(response.body)
55
+ json = JSON.parse(response.body)
56
+ pp json if self.class.verbose?
57
+ json
58
+ end
59
+
60
+ def retry_throttled_request
61
+ puts 'retrying throttled request after 60s.' if self.class.verbose?
62
+ sleep SECONDS_TO_WAIT_WHEN_THROTTLED
63
+ perform
39
64
  end
40
65
 
41
66
  def handle_error(response)
42
67
  data = JSON.parse(response.body)
43
- error = case response.code
44
- when 400
45
- Lightspeed::Errors::BadRequest
46
- when 401
47
- Lightspeed::Errors::Unauthorized
48
- when 500
49
- Lightspeed::Errors::InternalServerError
68
+ error = case response.code.to_s
69
+ when '400' then Lightspeed::Error::BadRequest
70
+ when '401' then Lightspeed::Error::Unauthorized
71
+ when '404' then Lightspeed::Error::NotFound
72
+ when '429' then Lightspeed::Error::Throttled
73
+ when /5../ then Lightspeed::Error::InternalServerError
74
+ else Lightspeed::Error
75
+ end
76
+ raise error, data["message"]
77
+ end
78
+
79
+ def extract_rate_limits(response)
80
+ if bucket_headers = response["X-LS-API-Bucket-Level"]
81
+ @bucket_level, @bucket_max = bucket_headers.split("/").map(&:to_f)
50
82
  end
83
+ end
51
84
 
52
- raise error.new(data["message"]) if error # rubocop:disable RaiseArgs
85
+ def uri
86
+ uri = self.class.base_path + @path
87
+ uri += "?" + URI.encode_www_form(@params) if @params && @method == :get
88
+ uri
53
89
  end
90
+
91
+ def request_class
92
+ case @method
93
+ when :get then Net::HTTP::Get
94
+ when :put then Net::HTTP::Put
95
+ when :post then Net::HTTP::Post
96
+ when :delete then Net::HTTP::Delete
97
+ end
98
+ end
99
+
54
100
  end
55
101
  end