choiceqr 0.1.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 +7 -0
- data/LICENSE.md +21 -0
- data/README.md +243 -0
- data/lib/choiceqr/client.rb +248 -0
- data/lib/choiceqr/configuration.rb +14 -0
- data/lib/choiceqr/errors.rb +38 -0
- data/lib/choiceqr/key_transformer.rb +105 -0
- data/lib/choiceqr/resource.rb +84 -0
- data/lib/choiceqr/resources/areas.rb +37 -0
- data/lib/choiceqr/resources/base.rb +69 -0
- data/lib/choiceqr/resources/bookings.rb +32 -0
- data/lib/choiceqr/resources/categories.rb +39 -0
- data/lib/choiceqr/resources/cutlery.rb +18 -0
- data/lib/choiceqr/resources/dish_labels.rb +12 -0
- data/lib/choiceqr/resources/dish_options.rb +48 -0
- data/lib/choiceqr/resources/dishes.rb +58 -0
- data/lib/choiceqr/resources/feedbacks.rb +30 -0
- data/lib/choiceqr/resources/full_menu.rb +56 -0
- data/lib/choiceqr/resources/location_points.rb +31 -0
- data/lib/choiceqr/resources/orders.rb +59 -0
- data/lib/choiceqr/resources/pack.rb +36 -0
- data/lib/choiceqr/resources/place.rb +13 -0
- data/lib/choiceqr/resources/section_info.rb +12 -0
- data/lib/choiceqr/resources/sections.rb +38 -0
- data/lib/choiceqr/version.rb +3 -0
- data/lib/choiceqr.rb +38 -0
- metadata +172 -0
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
# Generic response object representing any API entity (dish, order, area, …).
|
|
3
|
+
#
|
|
4
|
+
# All keys are snake_case symbols. Attribute access is available via:
|
|
5
|
+
# - Dot notation: resource.total_price
|
|
6
|
+
# - Hash notation: resource[:total_price]
|
|
7
|
+
# - Plain hash: resource.to_h
|
|
8
|
+
#
|
|
9
|
+
# Unlike the top level, ChoiceQR schemas are deeply nested (a dish carries
|
|
10
|
+
# menu options, which carry option list items; an order carries items,
|
|
11
|
+
# which carry modifiers, etc.) and the API has no `include`-style flag to
|
|
12
|
+
# opt in or out of them. So every nested Hash — at any depth — is wrapped
|
|
13
|
+
# as a Resource too, and every nested Array of Hashes becomes an Array of
|
|
14
|
+
# Resource, giving dot access all the way down.
|
|
15
|
+
class Resource
|
|
16
|
+
# +attributes+ is nil for a 200/201 response with an empty body, which
|
|
17
|
+
# the API returns for a handful of endpoints.
|
|
18
|
+
def initialize(attributes)
|
|
19
|
+
# Only this level's keys need converting: #wrap recurses into
|
|
20
|
+
# Resource.new for every nested Hash, which converts its own keys in
|
|
21
|
+
# turn. Pre-converting the whole tree here (e.g. via
|
|
22
|
+
# KeyTransformer.to_snake) would redo that work once per ancestor for
|
|
23
|
+
# every nested node.
|
|
24
|
+
@attributes = (attributes || {}).transform_keys { |k| KeyTransformer.snake_key(k) }
|
|
25
|
+
.transform_values { |v| self.class.wrap(v) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Wraps a single value: Hash → Resource, Array → Array of wrapped values,
|
|
29
|
+
# anything else is returned unchanged.
|
|
30
|
+
def self.wrap(value)
|
|
31
|
+
case value
|
|
32
|
+
when Hash then new(value)
|
|
33
|
+
when Array then value.map { |v| wrap(v) }
|
|
34
|
+
else value
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Hash-style access with either symbol or string key.
|
|
39
|
+
def [](key)
|
|
40
|
+
@attributes[KeyTransformer.snake_key(key)]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns a plain snake_case-keyed hash (deep copy, nested Resources
|
|
44
|
+
# unwrapped back to Hash).
|
|
45
|
+
def to_h
|
|
46
|
+
deep_dup(@attributes)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def inspect
|
|
50
|
+
"#<#{self.class.name} #{@attributes.inspect}>"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def to_s
|
|
54
|
+
inspect
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ==(other)
|
|
58
|
+
other.is_a?(Resource) && other.to_h == to_h
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def respond_to_missing?(name, include_private = false)
|
|
62
|
+
@attributes.key?(name) || super
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def method_missing(name, *args)
|
|
66
|
+
if @attributes.key?(name)
|
|
67
|
+
@attributes[name]
|
|
68
|
+
else
|
|
69
|
+
super
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
private
|
|
74
|
+
|
|
75
|
+
def deep_dup(obj)
|
|
76
|
+
case obj
|
|
77
|
+
when Resource then obj.to_h
|
|
78
|
+
when Hash then obj.transform_values { |v| deep_dup(v) }
|
|
79
|
+
when Array then obj.map { |v| deep_dup(v) }
|
|
80
|
+
else obj
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Ordering areas (bar, hall, terrace, takeaway, delivery, digital menu).
|
|
4
|
+
#
|
|
5
|
+
# client.areas.list
|
|
6
|
+
# client.areas.get_by_type("takeaway")
|
|
7
|
+
# client.areas.create(name: "Terrace", payment_methods: { cash: true, card: true })
|
|
8
|
+
# client.areas.delete(id)
|
|
9
|
+
class Areas < Base
|
|
10
|
+
def list(language: nil)
|
|
11
|
+
fetch_list("location/#{lang(language)}/areas/list")
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def get(id, language: nil)
|
|
15
|
+
fetch_one("location/#{lang(language)}/areas/#{id}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# +type+ is one of: takeaway, delivery, simple, digitalMenu
|
|
19
|
+
def get_by_type(type, language: nil)
|
|
20
|
+
fetch_one("location/#{lang(language)}/areas/by-type/#{type}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def create(language: nil, **attributes)
|
|
24
|
+
post_create("location/#{lang(language)}/areas", attributes)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# PUT — full replace. Returns true on success.
|
|
28
|
+
def update(id, language: nil, **attributes)
|
|
29
|
+
mutate(:put, "location/#{lang(language)}/areas/#{id}", body: attributes)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def delete(id, language: nil)
|
|
33
|
+
destroy("location/#{lang(language)}/areas/#{id}")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Shared plumbing for the per-entity resource wrapper classes reached via
|
|
4
|
+
# the client accessors (client.sections, client.dishes, client.orders, …).
|
|
5
|
+
#
|
|
6
|
+
# ChoiceQR's endpoints are not path-uniform the way Dotypos's are (list
|
|
7
|
+
# scoping, nesting, and available actions differ per resource type), so
|
|
8
|
+
# unlike a single generic ResourceCollection, each entity gets its own
|
|
9
|
+
# small class here that knows its own paths.
|
|
10
|
+
class Base
|
|
11
|
+
def initialize(client)
|
|
12
|
+
@client = client
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :client
|
|
18
|
+
|
|
19
|
+
# Resolves the :language path segment: an explicit override, or the
|
|
20
|
+
# client's configured default_language.
|
|
21
|
+
def lang(language)
|
|
22
|
+
(language || client.default_language).to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def fetch_one(path, params: {})
|
|
26
|
+
response = client.request(:get, path, params: to_query(params))
|
|
27
|
+
Resource.new(response.fetch(:body))
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def fetch_list(path, params: {})
|
|
31
|
+
response = client.request(:get, path, params: to_query(params))
|
|
32
|
+
Array(response.fetch(:body)).map { |item| Resource.new(item) }
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def post_create(path, attributes, params: {})
|
|
36
|
+
response = client.request(:post, path, body: KeyTransformer.to_camel(attributes), params: to_query(params))
|
|
37
|
+
Resource.new(response.fetch(:body))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# PUT/PATCH/POST calls that return 204 No Content on success.
|
|
41
|
+
def mutate(method, path, body: nil, params: {})
|
|
42
|
+
client.request(method, path, body: body && KeyTransformer.to_camel(body), params: to_query(params))
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def destroy(path)
|
|
47
|
+
client.request(:delete, path)
|
|
48
|
+
true
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Query params use camelCase names too (e.g. includeApproved, perPage),
|
|
52
|
+
# same as request bodies.
|
|
53
|
+
def to_query(params)
|
|
54
|
+
KeyTransformer.to_camel(params.compact)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def format_time(value)
|
|
58
|
+
return nil if value.nil?
|
|
59
|
+
return value.to_s unless value.respond_to?(:iso8601)
|
|
60
|
+
|
|
61
|
+
# Time/DateTime#iso8601 take an optional fractional-digits argument;
|
|
62
|
+
# Date#iso8601 (no time component) takes none. Check arity instead of
|
|
63
|
+
# is_a?(Date) so any iso8601-compatible object works, not just those
|
|
64
|
+
# two stdlib classes.
|
|
65
|
+
value.method(:iso8601).arity.zero? ? value.iso8601 : value.iso8601(3)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Table bookings/reservations.
|
|
4
|
+
#
|
|
5
|
+
# client.bookings.list(from: Time.now, till: Time.now + 86_400 * 7)
|
|
6
|
+
# client.bookings.get(id)
|
|
7
|
+
# client.bookings.confirm(id, location_points: [point_id])
|
|
8
|
+
# client.bookings.cancel(id, cancel_reason: "Table no longer available")
|
|
9
|
+
class Bookings < Base
|
|
10
|
+
# Rate limit: 1 request / 5 seconds.
|
|
11
|
+
def list(from: nil, till: nil, period_field: nil, page: nil, per_page: nil)
|
|
12
|
+
fetch_list("bookings/list", params: {
|
|
13
|
+
from: format_time(from), till: format_time(till),
|
|
14
|
+
period_field: period_field, page: page, per_page: per_page
|
|
15
|
+
})
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get(id)
|
|
19
|
+
fetch_one("bookings/#{id}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Only allowed while the booking status is created.
|
|
23
|
+
def confirm(id, location_points: nil)
|
|
24
|
+
mutate(:put, "bookings/#{id}/confirm", body: { location_points: location_points }.compact)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def cancel(id, cancel_reason:)
|
|
28
|
+
mutate(:put, "bookings/#{id}/cancel", body: { cancel_reason: cancel_reason })
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Groupings of dishes within a section.
|
|
4
|
+
#
|
|
5
|
+
# client.categories.list(section_id)
|
|
6
|
+
# client.categories.create(name: "Hot", section: section_id)
|
|
7
|
+
# client.categories.update(id, name: "Cold")
|
|
8
|
+
# client.categories.set_position(section_id, [id1, id2])
|
|
9
|
+
# client.categories.delete(id)
|
|
10
|
+
class Categories < Base
|
|
11
|
+
def list(section_id, language: nil)
|
|
12
|
+
fetch_list("menu/#{lang(language)}/categories/list/#{section_id}")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def get(id, language: nil)
|
|
16
|
+
fetch_one("menu/#{lang(language)}/categories/#{id}")
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create(language: nil, **attributes)
|
|
20
|
+
post_create("menu/#{lang(language)}/categories", attributes)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# PUT — full replace. Returns true on success.
|
|
24
|
+
def update(id, language: nil, **attributes)
|
|
25
|
+
mutate(:put, "menu/#{lang(language)}/categories/#{id}", body: attributes)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Reorders categories within +section_id+. +ids+ is the full, ordered
|
|
29
|
+
# array of category IDs.
|
|
30
|
+
def set_position(section_id, ids, language: nil)
|
|
31
|
+
mutate(:post, "menu/#{lang(language)}/categories/#{section_id}/position/bulk", body: ids)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def delete(id, language: nil)
|
|
35
|
+
destroy("menu/#{lang(language)}/categories/#{id}")
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Cutlery modal configuration (single settings object, no id).
|
|
4
|
+
#
|
|
5
|
+
# client.cutlery.get
|
|
6
|
+
# client.cutlery.update(show: true, required_cutlery: false)
|
|
7
|
+
class Cutlery < Base
|
|
8
|
+
def get(language: nil)
|
|
9
|
+
fetch_one("menu/#{lang(language)}/cutlery")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# PUT — returns true on success.
|
|
13
|
+
def update(language: nil, **attributes)
|
|
14
|
+
mutate(:put, "menu/#{lang(language)}/cutlery", body: attributes)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Personal (custom) dish labels, e.g. "Chef's pick".
|
|
4
|
+
#
|
|
5
|
+
# client.dish_labels.list # => Array<Resource>
|
|
6
|
+
class DishLabels < Base
|
|
7
|
+
def list(language: nil)
|
|
8
|
+
fetch_list("menu/#{lang(language)}/dish-labels/list")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Customizable dish options (single/multiple choice modifiers), shared
|
|
4
|
+
# across the dishes they are attached to.
|
|
5
|
+
#
|
|
6
|
+
# client.dish_options.list(section_id)
|
|
7
|
+
# client.dish_options.create(name: "Size", type: "single", section: section_id)
|
|
8
|
+
# client.dish_options.attach(option_id, dish_id)
|
|
9
|
+
# client.dish_options.detach(option_id, dish_id)
|
|
10
|
+
# client.dish_options.delete(id)
|
|
11
|
+
class DishOptions < Base
|
|
12
|
+
def list(section_id, language: nil)
|
|
13
|
+
fetch_list("menu/#{lang(language)}/options/list/#{section_id}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def get(id, language: nil)
|
|
17
|
+
fetch_one("menu/#{lang(language)}/options/#{id}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def create(language: nil, **attributes)
|
|
21
|
+
post_create("menu/#{lang(language)}/options", attributes)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# PUT — full replace. Returns true on success.
|
|
25
|
+
def update(id, language: nil, **attributes)
|
|
26
|
+
mutate(:put, "menu/#{lang(language)}/options/#{id}", body: attributes)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Reorders options within +section_id+. +ids+ is the full, ordered
|
|
30
|
+
# array of option IDs.
|
|
31
|
+
def set_position(section_id, ids, language: nil)
|
|
32
|
+
mutate(:post, "menu/#{lang(language)}/options/#{section_id}/position/bulk", body: ids)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def delete(id, language: nil)
|
|
36
|
+
destroy("menu/#{lang(language)}/options/#{id}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def attach(id, dish_id, language: nil)
|
|
40
|
+
mutate(:put, "menu/#{lang(language)}/options/#{id}/attach", body: { dish: dish_id })
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def detach(id, dish_id, language: nil)
|
|
44
|
+
mutate(:put, "menu/#{lang(language)}/options/#{id}/detach", body: { dish: dish_id })
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Menu items.
|
|
4
|
+
#
|
|
5
|
+
# client.dishes.list(category_id)
|
|
6
|
+
# client.dishes.find_by_pos_id("b9e8db61")
|
|
7
|
+
# client.dishes.create(name: "Cappuccino", category: category_id, price: 420)
|
|
8
|
+
# client.dishes.update(id, name: "Double Espresso", price: 450)
|
|
9
|
+
# client.dishes.patch(id, active: false)
|
|
10
|
+
# client.dishes.update_areas(id, takeaway: true, delivery: false)
|
|
11
|
+
# client.dishes.set_position(category_id, [id1, id2])
|
|
12
|
+
# client.dishes.delete(id)
|
|
13
|
+
class Dishes < Base
|
|
14
|
+
def list(category_id, language: nil)
|
|
15
|
+
fetch_list("menu/#{lang(language)}/dishes/list/#{category_id}")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Looks a dish up by the posID assigned in your own POS system.
|
|
19
|
+
def find_by_pos_id(pos_id, language: nil)
|
|
20
|
+
fetch_one("menu/#{lang(language)}/dishes", params: { pos_id: pos_id })
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def get(id, language: nil)
|
|
24
|
+
fetch_one("menu/#{lang(language)}/dishes/#{id}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create(language: nil, **attributes)
|
|
28
|
+
post_create("menu/#{lang(language)}/dishes", attributes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# PUT — full replace. Returns true on success.
|
|
32
|
+
def update(id, language: nil, **attributes)
|
|
33
|
+
mutate(:put, "menu/#{lang(language)}/dishes/#{id}", body: attributes)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# PATCH — partial update of only the given fields.
|
|
37
|
+
def patch(id, language: nil, **attributes)
|
|
38
|
+
mutate(:patch, "menu/#{lang(language)}/dishes/#{id}", body: attributes)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Updates which areas (takeaway/delivery/dine-in/digital menu) the dish
|
|
42
|
+
# is available in.
|
|
43
|
+
def update_areas(id, language: nil, **areas)
|
|
44
|
+
mutate(:put, "menu/#{lang(language)}/dishes/#{id}/areas", body: areas)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Reorders dishes within +category_id+. +ids+ is the full, ordered
|
|
48
|
+
# array of dish IDs.
|
|
49
|
+
def set_position(category_id, ids, language: nil)
|
|
50
|
+
mutate(:post, "menu/#{lang(language)}/dishes/#{category_id}/position/bulk", body: ids)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def delete(id, language: nil)
|
|
54
|
+
destroy("menu/#{lang(language)}/dishes/#{id}")
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Customer feedback for orders and the restaurant in general.
|
|
4
|
+
#
|
|
5
|
+
# client.feedbacks.list(type: "ORDER")
|
|
6
|
+
# client.feedbacks.get(id)
|
|
7
|
+
# client.feedbacks.create(
|
|
8
|
+
# ref_id: order_id,
|
|
9
|
+
# feedback: { type: "ORDER", rate_serve: 5, rate_dish: 4, language: "en" },
|
|
10
|
+
# customer: { name: "John Doe", phone: "+380501234567" }
|
|
11
|
+
# )
|
|
12
|
+
class Feedbacks < Base
|
|
13
|
+
def list(from: nil, to: nil, limit: nil, offset: nil, type: nil, sort: nil)
|
|
14
|
+
fetch_list("feedbacks", params: {
|
|
15
|
+
from: format_time(from), to: format_time(to),
|
|
16
|
+
limit: limit, offset: offset, type: type, sort: sort
|
|
17
|
+
})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def get(id)
|
|
21
|
+
fetch_one("feedbacks/#{id}")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Rate limit: 1 request / 10 seconds.
|
|
25
|
+
def create(**attributes)
|
|
26
|
+
post_create("feedbacks", attributes)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Whole-menu operations: fetching the full client menu in one call,
|
|
4
|
+
# bulk import/replace, availability sync, and marketplace data sync.
|
|
5
|
+
#
|
|
6
|
+
# client.full_menu.list
|
|
7
|
+
# client.full_menu.import(sections: [...], categories: [...], dishes: [...])
|
|
8
|
+
# client.full_menu.patch_dishes(dishes: [...])
|
|
9
|
+
# client.full_menu.sync_availability(dishes: [{ pos_id: "525", active: false }])
|
|
10
|
+
# client.full_menu.sync_chain_availability(sections: [...])
|
|
11
|
+
# client.full_menu.sync_marketplace_data(dishes: [{ pos_id: "1", data: { WOLT: { price: 1000 } } }])
|
|
12
|
+
# client.full_menu.marketplace_sync_status(sync_id)
|
|
13
|
+
class FullMenu < Base
|
|
14
|
+
def list(language: nil)
|
|
15
|
+
fetch_one("menu/#{lang(language)}/full/list")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Full replace of the menu (sections:, categories:, dishes:, dish_options:).
|
|
19
|
+
# By default, entities missing from the payload are deleted; pass
|
|
20
|
+
# preserve_missing_items: true to keep them (marked inactive) instead.
|
|
21
|
+
def import(language: nil, preserve_missing_items: nil, **payload)
|
|
22
|
+
mutate(:post, "menu/#{lang(language)}/full", body: payload,
|
|
23
|
+
params: { preserve_missing_items: preserve_missing_items })
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Partial update of only dish data (dishes:), matched by posID.
|
|
27
|
+
def patch_dishes(language: nil, **payload)
|
|
28
|
+
mutate(:patch, "menu/#{lang(language)}/full/dishes", body: payload)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Bulk-updates active/attribute state for sections:, categories:,
|
|
32
|
+
# dishes:, and dish_options:, matched by posID. At least one of these
|
|
33
|
+
# keys must be present.
|
|
34
|
+
def sync_availability(language: nil, skip_missing: nil, **payload)
|
|
35
|
+
mutate(:post, "menu/#{lang(language)}/full/availability", body: payload,
|
|
36
|
+
params: { skip_missing: skip_missing })
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Same as #sync_availability, but per chain branch.
|
|
40
|
+
def sync_chain_availability(language: nil, **payload)
|
|
41
|
+
mutate(:post, "menu/#{lang(language)}/full/chain/availability", body: payload)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Kicks off an async sync of marketplace-specific price/name overrides.
|
|
45
|
+
# Returns a Resource with the sync job +id+; poll it via
|
|
46
|
+
# #marketplace_sync_status.
|
|
47
|
+
def sync_marketplace_data(dishes:, language: nil)
|
|
48
|
+
post_create("menu/#{lang(language)}/full/marketplace/data", { dishes: dishes })
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def marketplace_sync_status(sync_id, language: nil)
|
|
52
|
+
fetch_one("menu/#{lang(language)}/full/marketplace/data/status/#{sync_id}")
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Individual points within an area (tables, kiosks, rooms).
|
|
4
|
+
#
|
|
5
|
+
# client.location_points.list(area_id)
|
|
6
|
+
# client.location_points.create(name: "Table 4", area: area_id)
|
|
7
|
+
# client.location_points.delete(id)
|
|
8
|
+
class LocationPoints < Base
|
|
9
|
+
def list(area_id, language: nil)
|
|
10
|
+
fetch_list("location/#{lang(language)}/points/list/#{area_id}")
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def get(id, language: nil)
|
|
14
|
+
fetch_one("location/#{lang(language)}/points/#{id}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def create(language: nil, **attributes)
|
|
18
|
+
post_create("location/#{lang(language)}/points", attributes)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# PUT — full replace. Returns true on success.
|
|
22
|
+
def update(id, language: nil, **attributes)
|
|
23
|
+
mutate(:put, "location/#{lang(language)}/points/#{id}", body: attributes)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def delete(id, language: nil)
|
|
27
|
+
destroy("location/#{lang(language)}/points/#{id}")
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Customer orders (delivery, takeaway, or table).
|
|
4
|
+
#
|
|
5
|
+
# client.orders.list(since: Time.now - 3600)
|
|
6
|
+
# client.orders.list_archive(from: Time.now - 86_400 * 30, till: Time.now)
|
|
7
|
+
# client.orders.get(id)
|
|
8
|
+
# client.orders.get_by_guid(guid)
|
|
9
|
+
# client.orders.update_delivery(id, delivery_status: "processing")
|
|
10
|
+
# client.orders.cancel(id, reason: "Out of stock")
|
|
11
|
+
# client.orders.close(id)
|
|
12
|
+
class Orders < Base
|
|
13
|
+
def list(since: nil, include_approved: nil, branches: nil, page: nil, per_page: nil)
|
|
14
|
+
fetch_list("orders/list", params: {
|
|
15
|
+
since: format_time(since), include_approved: include_approved,
|
|
16
|
+
branches: format_branches(branches), page: page, per_page: per_page
|
|
17
|
+
})
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Rate limit: 1 request / 5 seconds.
|
|
21
|
+
def list_archive(from:, till:, branches: nil, page: nil, per_page: nil)
|
|
22
|
+
fetch_list("orders/list/archive", params: {
|
|
23
|
+
from: format_time(from), till: format_time(till),
|
|
24
|
+
branches: format_branches(branches), page: page, per_page: per_page
|
|
25
|
+
})
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get(id)
|
|
29
|
+
fetch_one("orders/#{id}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def get_by_guid(guid)
|
|
33
|
+
fetch_one("orders/guid/#{guid}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Only allowed while the order status is approved or delivery.
|
|
37
|
+
def update_delivery(id, **attributes)
|
|
38
|
+
mutate(:put, "orders/#{id}/delivery", body: attributes)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Only allowed while the order status is waiting_for_approve, approved,
|
|
42
|
+
# or delivery.
|
|
43
|
+
def cancel(id, reason:)
|
|
44
|
+
mutate(:put, "orders/#{id}/cancel", body: { reason: reason })
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Only allowed while the order status is approved or delivery.
|
|
48
|
+
def close(id)
|
|
49
|
+
mutate(:put, "orders/#{id}/close")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def format_branches(branches)
|
|
55
|
+
Array(branches).join(",") if branches
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# Menu packages (bundles of categories sold together).
|
|
4
|
+
#
|
|
5
|
+
# client.pack.list
|
|
6
|
+
# client.pack.create(name: "Lunch set", price: 1000, categories: [{ _id: category_id }])
|
|
7
|
+
# client.pack.delete(id)
|
|
8
|
+
#
|
|
9
|
+
# Note: entries in the +categories+ payload reference an existing
|
|
10
|
+
# category by its Mongo id. Use the literal key +:_id+ (or the string
|
|
11
|
+
# +"_id"+) — the leading underscore is preserved by the key transformer,
|
|
12
|
+
# unlike the top-level +id+ read from responses.
|
|
13
|
+
class Pack < Base
|
|
14
|
+
def list(language: nil)
|
|
15
|
+
fetch_list("menu/#{lang(language)}/pack/list")
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def get(id, language: nil)
|
|
19
|
+
fetch_one("menu/#{lang(language)}/pack/#{id}")
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create(language: nil, **attributes)
|
|
23
|
+
post_create("menu/#{lang(language)}/pack", attributes)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# PUT — full replace. Returns true on success.
|
|
27
|
+
def update(id, language: nil, **attributes)
|
|
28
|
+
mutate(:put, "menu/#{lang(language)}/pack/#{id}", body: attributes)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def delete(id, language: nil)
|
|
32
|
+
destroy("menu/#{lang(language)}/pack/#{id}")
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module ChoiceQR
|
|
2
|
+
module Resources
|
|
3
|
+
# The customizable message shown at the top of a menu section.
|
|
4
|
+
#
|
|
5
|
+
# client.section_info.get(section_id) # => Resource
|
|
6
|
+
class SectionInfo < Base
|
|
7
|
+
def get(section_id, language: nil)
|
|
8
|
+
fetch_one("menu/#{lang(language)}/section-info/#{section_id}")
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|