channex 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/CHANGELOG.md +21 -0
- data/LICENSE +21 -0
- data/README.md +281 -0
- data/Rakefile +12 -0
- data/lib/channex/client.rb +283 -0
- data/lib/channex/configuration.rb +52 -0
- data/lib/channex/errors.rb +62 -0
- data/lib/channex/object.rb +201 -0
- data/lib/channex/objects/booking_revision.rb +124 -0
- data/lib/channex/objects/booking_room.rb +68 -0
- data/lib/channex/objects/company.rb +16 -0
- data/lib/channex/objects/customer.rb +43 -0
- data/lib/channex/objects/guarantee.rb +21 -0
- data/lib/channex/objects/guest.rb +21 -0
- data/lib/channex/objects/message.rb +95 -0
- data/lib/channex/objects/message_thread.rb +76 -0
- data/lib/channex/objects/occupancy.rb +29 -0
- data/lib/channex/objects/review.rb +87 -0
- data/lib/channex/objects/service.rb +44 -0
- data/lib/channex/objects/tax.rb +36 -0
- data/lib/channex/objects.rb +22 -0
- data/lib/channex/resource.rb +130 -0
- data/lib/channex/resources/attachments.rb +36 -0
- data/lib/channex/resources/availability.rb +87 -0
- data/lib/channex/resources/availability_rules.rb +52 -0
- data/lib/channex/resources/booking_revisions.rb +112 -0
- data/lib/channex/resources/bookings.rb +72 -0
- data/lib/channex/resources/channels.rb +96 -0
- data/lib/channex/resources/facilities.rb +29 -0
- data/lib/channex/resources/group_users.rb +76 -0
- data/lib/channex/resources/groups.rb +26 -0
- data/lib/channex/resources/hotel_policies.rb +54 -0
- data/lib/channex/resources/message_threads.rb +89 -0
- data/lib/channex/resources/messages.rb +64 -0
- data/lib/channex/resources/photos.rb +117 -0
- data/lib/channex/resources/properties.rb +42 -0
- data/lib/channex/resources/property_users.rb +32 -0
- data/lib/channex/resources/rate_plans.rb +34 -0
- data/lib/channex/resources/restrictions.rb +98 -0
- data/lib/channex/resources/reviews.rb +41 -0
- data/lib/channex/resources/room_types.rb +34 -0
- data/lib/channex/resources/tax_sets.rb +34 -0
- data/lib/channex/resources/taxes.rb +60 -0
- data/lib/channex/resources/webhooks.rb +87 -0
- data/lib/channex/resources.rb +5 -0
- data/lib/channex/response.rb +102 -0
- data/lib/channex/version.rb +3 -0
- data/lib/channex.rb +52 -0
- data/sig/channex.rbs +4 -0
- metadata +124 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# A conversation between a guest and the property, as Channex unifies it
|
|
4
|
+
# across Booking.com, Airbnb and Expedia.
|
|
5
|
+
#
|
|
6
|
+
# Like {Message}, a thread arrives as a JSON:API resource node;
|
|
7
|
+
# {.from_resource} flattens the identifier and relationships into the
|
|
8
|
+
# attributes so the whole thread is one hash.
|
|
9
|
+
#
|
|
10
|
+
# A thread usually belongs to a booking, but not always: an Airbnb inquiry
|
|
11
|
+
# is a conversation about a stay that does not exist yet, so `booking_id`
|
|
12
|
+
# is nil until the guest books.
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# thread.title # => "Maldonado Roxy"
|
|
16
|
+
# thread.provider # => "BookingCom"
|
|
17
|
+
# thread.closed? # => false
|
|
18
|
+
# thread.last_message # => Channex::Objects::Message
|
|
19
|
+
#
|
|
20
|
+
class MessageThread < Channex::Object
|
|
21
|
+
attribute :id
|
|
22
|
+
attribute :title
|
|
23
|
+
attribute :provider
|
|
24
|
+
attribute :message_count
|
|
25
|
+
attribute :last_message, type: Message
|
|
26
|
+
attribute :last_message_received_at
|
|
27
|
+
attribute :inserted_at
|
|
28
|
+
attribute :updated_at
|
|
29
|
+
|
|
30
|
+
# Lifted out of `relationships`
|
|
31
|
+
attribute :booking_id
|
|
32
|
+
attribute :channel_id
|
|
33
|
+
attribute :property_id
|
|
34
|
+
|
|
35
|
+
# Flatten a JSON:API resource node into a thread
|
|
36
|
+
#
|
|
37
|
+
# @param node [Hash, nil] A `{"id", "attributes", "relationships"}` node
|
|
38
|
+
# @return [MessageThread, nil]
|
|
39
|
+
def self.from_resource(node)
|
|
40
|
+
return nil if node.nil?
|
|
41
|
+
|
|
42
|
+
attributes = node["attributes"] || {}
|
|
43
|
+
relationships = node["relationships"] || {}
|
|
44
|
+
|
|
45
|
+
new(
|
|
46
|
+
attributes.merge(
|
|
47
|
+
"id" => node["id"],
|
|
48
|
+
"booking_id" => relationships.dig("booking", "data", "id"),
|
|
49
|
+
"channel_id" => relationships.dig("channel", "data", "id"),
|
|
50
|
+
"property_id" => relationships.dig("property", "data", "id")
|
|
51
|
+
).compact
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Whether the property closed the thread. Closed threads still accept
|
|
56
|
+
# messages; the flag is an inbox state, not a lock.
|
|
57
|
+
#
|
|
58
|
+
# @return [Boolean]
|
|
59
|
+
def closed?
|
|
60
|
+
@data["is_closed"] == true
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# @return [Boolean]
|
|
64
|
+
def open?
|
|
65
|
+
!closed?
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Whether the thread hangs off a booking. Airbnb inquiries do not.
|
|
69
|
+
#
|
|
70
|
+
# @return [Boolean]
|
|
71
|
+
def booking?
|
|
72
|
+
!booking_id.nil?
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Guest occupancy information
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# occupancy.adults # => 2
|
|
7
|
+
# occupancy.children # => 1
|
|
8
|
+
# occupancy.infants # => 0
|
|
9
|
+
# occupancy.total_guests # => 3
|
|
10
|
+
#
|
|
11
|
+
class Occupancy < Channex::Object
|
|
12
|
+
attribute :adults
|
|
13
|
+
attribute :children
|
|
14
|
+
attribute :infants
|
|
15
|
+
|
|
16
|
+
# Child ages array (when provided)
|
|
17
|
+
# @return [Array<Integer>]
|
|
18
|
+
def ages
|
|
19
|
+
@data["ages"] || []
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Total number of guests
|
|
23
|
+
# @return [Integer]
|
|
24
|
+
def total_guests
|
|
25
|
+
(adults || 0) + (children || 0) + (infants || 0)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# A guest's review of a stay, as Channex unifies it across Booking.com,
|
|
4
|
+
# Airbnb and Expedia.
|
|
5
|
+
#
|
|
6
|
+
# Like {MessageThread}, a review arrives as a JSON:API resource node;
|
|
7
|
+
# {.from_resource} flattens the identifier and relationships into the
|
|
8
|
+
# attributes so the whole review is one hash.
|
|
9
|
+
#
|
|
10
|
+
# Scores are out of 10 whatever the OTA rates on; `scores` holds one
|
|
11
|
+
# `{"category", "score"}` pair per category the OTA asks about, named in
|
|
12
|
+
# the OTA's own vocabulary (`clean` on Booking.com, `cleanliness` on Airbnb).
|
|
13
|
+
#
|
|
14
|
+
# @example
|
|
15
|
+
# review.ota # => "BookingCom"
|
|
16
|
+
# review.overall_score # => 9.0
|
|
17
|
+
# review.scores # => [{"category" => "clean", "score" => 10}]
|
|
18
|
+
# review.replied? # => false
|
|
19
|
+
#
|
|
20
|
+
class Review < Channex::Object
|
|
21
|
+
attribute :id
|
|
22
|
+
attribute :content
|
|
23
|
+
attribute :guest_name
|
|
24
|
+
attribute :ota
|
|
25
|
+
attribute :ota_reservation_id
|
|
26
|
+
attribute :overall_score
|
|
27
|
+
attribute :reply
|
|
28
|
+
attribute :received_at
|
|
29
|
+
attribute :inserted_at
|
|
30
|
+
attribute :updated_at
|
|
31
|
+
|
|
32
|
+
# Lifted out of `relationships`
|
|
33
|
+
attribute :booking_id
|
|
34
|
+
attribute :channel_id
|
|
35
|
+
attribute :property_id
|
|
36
|
+
|
|
37
|
+
# Flatten a JSON:API resource node into a review
|
|
38
|
+
#
|
|
39
|
+
# @param node [Hash, nil] A `{"id", "attributes", "relationships"}` node
|
|
40
|
+
# @return [Review, nil]
|
|
41
|
+
def self.from_resource(node)
|
|
42
|
+
return nil if node.nil?
|
|
43
|
+
|
|
44
|
+
attributes = node["attributes"] || {}
|
|
45
|
+
relationships = node["relationships"] || {}
|
|
46
|
+
|
|
47
|
+
new(
|
|
48
|
+
attributes.merge(
|
|
49
|
+
"id" => node["id"],
|
|
50
|
+
"booking_id" => relationships.dig("booking", "data", "id"),
|
|
51
|
+
"channel_id" => relationships.dig("channel", "data", "id"),
|
|
52
|
+
"property_id" => relationships.dig("property", "data", "id")
|
|
53
|
+
).compact
|
|
54
|
+
)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Per-category scores, `{"category", "score"}` pairs
|
|
58
|
+
#
|
|
59
|
+
# @return [Array<Hash>]
|
|
60
|
+
def scores
|
|
61
|
+
@data["scores"] || []
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Review tag codes (Airbnb only)
|
|
65
|
+
#
|
|
66
|
+
# @return [Array<String>]
|
|
67
|
+
def tags
|
|
68
|
+
@data["tags"] || []
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Whether the property has replied
|
|
72
|
+
#
|
|
73
|
+
# @return [Boolean]
|
|
74
|
+
def replied?
|
|
75
|
+
@data["is_replied"] == true
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Whether Airbnb is holding the review back until the host reviews the
|
|
79
|
+
# guest in turn (Airbnb only)
|
|
80
|
+
#
|
|
81
|
+
# @return [Boolean]
|
|
82
|
+
def hidden?
|
|
83
|
+
@data["is_hidden"] == true
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Additional service, fee, or extra sold alongside a booking.
|
|
4
|
+
#
|
|
5
|
+
# Channex represents meals, fees, and extras uniformly in a booking's
|
|
6
|
+
# `services` array (present at both booking and room level), distinguished
|
|
7
|
+
# by `type`. A fee charged on a cancelled booking also arrives here — note
|
|
8
|
+
# that real OTA payloads use a specific `type` (e.g. Booking.com sends
|
|
9
|
+
# "Cancellation Fee"), not the generic "Fee" the API docs imply.
|
|
10
|
+
#
|
|
11
|
+
# `is_inclusive` indicates whether tax is already baked into the amount.
|
|
12
|
+
#
|
|
13
|
+
# @see https://docs.channex.io/api-v.1-documentation/bookings-collection
|
|
14
|
+
#
|
|
15
|
+
# @example
|
|
16
|
+
# service.type # => "Cancellation Fee"
|
|
17
|
+
# service.name # => "Cancellation Fee"
|
|
18
|
+
# service.total_price # => "12240.00" (total amount for the service)
|
|
19
|
+
# service.price_per_unit # => "12240.00"
|
|
20
|
+
# service.price_mode # => "Per stay"
|
|
21
|
+
# service.nights # => 0
|
|
22
|
+
# service.persons # => 0
|
|
23
|
+
# service.inclusive? # => true
|
|
24
|
+
# service.room_index # => 0 (room the service is associated with, if any)
|
|
25
|
+
#
|
|
26
|
+
class Service < Channex::Object
|
|
27
|
+
attribute :type
|
|
28
|
+
attribute :name
|
|
29
|
+
attribute :total_price
|
|
30
|
+
attribute :price_per_unit
|
|
31
|
+
attribute :price_mode
|
|
32
|
+
attribute :persons
|
|
33
|
+
attribute :nights
|
|
34
|
+
attribute :room_index
|
|
35
|
+
attribute :inclusive, key: "is_inclusive"
|
|
36
|
+
|
|
37
|
+
# Whether tax is already included in the amount
|
|
38
|
+
# @return [Boolean]
|
|
39
|
+
def inclusive?
|
|
40
|
+
inclusive == true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Objects
|
|
3
|
+
# Tax applied to a booking room.
|
|
4
|
+
#
|
|
5
|
+
# Channex represents taxes with the same shape as services: an amount
|
|
6
|
+
# (`total_price` / `price_per_unit`), a `price_mode`, and an `is_inclusive`
|
|
7
|
+
# flag indicating whether the tax is already baked into the room price.
|
|
8
|
+
#
|
|
9
|
+
# @see https://docs.channex.io/api-v.1-documentation/taxes-and-tax-sets
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# tax.type # => "Value Added Tax (VAT)"
|
|
13
|
+
# tax.name # => "VAT (12%)"
|
|
14
|
+
# tax.total_price # => "1311.43"
|
|
15
|
+
# tax.price_per_unit # => "1311.43"
|
|
16
|
+
# tax.price_mode # => "Per booking"
|
|
17
|
+
# tax.inclusive? # => true
|
|
18
|
+
#
|
|
19
|
+
class Tax < Channex::Object
|
|
20
|
+
attribute :type
|
|
21
|
+
attribute :name
|
|
22
|
+
attribute :total_price
|
|
23
|
+
attribute :price_per_unit
|
|
24
|
+
attribute :price_mode
|
|
25
|
+
attribute :persons
|
|
26
|
+
attribute :nights
|
|
27
|
+
attribute :inclusive, key: "is_inclusive"
|
|
28
|
+
|
|
29
|
+
# Whether the tax is already included in the price
|
|
30
|
+
# @return [Boolean]
|
|
31
|
+
def inclusive?
|
|
32
|
+
inclusive == true
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
# Namespace for typed API response objects
|
|
3
|
+
#
|
|
4
|
+
# All objects inherit from Channex::Object and provide:
|
|
5
|
+
# - Typed attribute accessors
|
|
6
|
+
# - Hash compatibility via [], dig, to_h
|
|
7
|
+
# - Nested object wrapping
|
|
8
|
+
#
|
|
9
|
+
module Objects
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Load in dependency order (leaf nodes first)
|
|
14
|
+
require_relative "objects/company"
|
|
15
|
+
require_relative "objects/occupancy"
|
|
16
|
+
require_relative "objects/guest"
|
|
17
|
+
require_relative "objects/tax"
|
|
18
|
+
require_relative "objects/service"
|
|
19
|
+
require_relative "objects/guarantee"
|
|
20
|
+
require_relative "objects/customer"
|
|
21
|
+
require_relative "objects/booking_room"
|
|
22
|
+
require_relative "objects/booking_revision"
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
# Base class for API resources
|
|
3
|
+
#
|
|
4
|
+
# Provides common CRUD operations and pagination support.
|
|
5
|
+
# Subclasses should define their resource path and customize
|
|
6
|
+
# behavior as needed.
|
|
7
|
+
#
|
|
8
|
+
class Resource
|
|
9
|
+
attr_reader :client
|
|
10
|
+
|
|
11
|
+
def initialize(client)
|
|
12
|
+
@client = client
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# List all resources with optional filtering and pagination
|
|
16
|
+
#
|
|
17
|
+
# @param page [Integer] Page number (starts at 1)
|
|
18
|
+
# @param limit [Integer] Number of results per page (max 100)
|
|
19
|
+
# @param filter [Hash] Filter parameters
|
|
20
|
+
# @param order [Hash] Ordering parameters (e.g., { inserted_at: :asc })
|
|
21
|
+
# @param params [Hash] Keywords named by #filter_keys join the filter; the rest are sent as top-level params
|
|
22
|
+
# @return [Response]
|
|
23
|
+
def list(page: nil, limit: nil, filter: {}, order: {}, **params)
|
|
24
|
+
named_filters = params.extract!(*filter_keys).compact
|
|
25
|
+
filter = filter.merge(named_filters)
|
|
26
|
+
client.get(resource_path, params: query_params(page:, limit:, filter:, order:, **params))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Retrieve all resources using automatic pagination
|
|
30
|
+
#
|
|
31
|
+
# @yield [data] Yields each page of data
|
|
32
|
+
# @yieldparam data [Array] Array of resource data from the page
|
|
33
|
+
# @return [Array] All resources if no block given
|
|
34
|
+
def list_all(filter: {}, order: {}, **params)
|
|
35
|
+
all_data = []
|
|
36
|
+
page = 1
|
|
37
|
+
limit = 100
|
|
38
|
+
|
|
39
|
+
loop do
|
|
40
|
+
response = list(page: page, limit: limit, filter: filter, order: order, **params)
|
|
41
|
+
data = response.data || []
|
|
42
|
+
|
|
43
|
+
if block_given?
|
|
44
|
+
yield data
|
|
45
|
+
else
|
|
46
|
+
all_data.concat(data)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
break unless response.pagination.has_next?
|
|
50
|
+
|
|
51
|
+
page += 1
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
all_data unless block_given?
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Retrieve a single resource by ID
|
|
58
|
+
#
|
|
59
|
+
# @param id [String] Resource UUID
|
|
60
|
+
# @return [Response]
|
|
61
|
+
def retrieve(id)
|
|
62
|
+
client.get("#{resource_path}/#{id}")
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Create a new resource
|
|
66
|
+
#
|
|
67
|
+
# @param attributes [Hash] Resource attributes
|
|
68
|
+
# @return [Response]
|
|
69
|
+
def create(attributes)
|
|
70
|
+
client.post(resource_path, body: wrap_attributes(attributes))
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Update an existing resource
|
|
74
|
+
#
|
|
75
|
+
# @param id [String] Resource UUID
|
|
76
|
+
# @param attributes [Hash] Resource attributes to update
|
|
77
|
+
# @return [Response]
|
|
78
|
+
def update(id, attributes)
|
|
79
|
+
client.put("#{resource_path}/#{id}", body: wrap_attributes(attributes))
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Delete a resource
|
|
83
|
+
#
|
|
84
|
+
# @param id [String] Resource UUID
|
|
85
|
+
# @return [Response]
|
|
86
|
+
def delete(id)
|
|
87
|
+
client.delete("#{resource_path}/#{id}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
protected
|
|
91
|
+
|
|
92
|
+
# Build the query string Channex expects: pagination, filter and order are
|
|
93
|
+
# each nested one level deep (e.g. `pagination[page]=2`). Shared with
|
|
94
|
+
# resources whose collections hang off another resource's path.
|
|
95
|
+
#
|
|
96
|
+
# @return [Hash]
|
|
97
|
+
def query_params(page: nil, limit: nil, filter: {}, order: {}, **params)
|
|
98
|
+
query = params.dup
|
|
99
|
+
|
|
100
|
+
pagination = {}
|
|
101
|
+
pagination[:page] = page if page
|
|
102
|
+
pagination[:limit] = limit if limit
|
|
103
|
+
query[:pagination] = pagination unless pagination.empty?
|
|
104
|
+
|
|
105
|
+
query[:filter] = filter unless filter.empty?
|
|
106
|
+
query[:order] = order unless order.empty?
|
|
107
|
+
|
|
108
|
+
query
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Override in subclasses to define the API path
|
|
112
|
+
def resource_path
|
|
113
|
+
raise NotImplementedError, "Subclass must implement #resource_path"
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# Override to change how attributes are wrapped for create/update
|
|
117
|
+
def wrap_attributes(attributes)
|
|
118
|
+
{resource_name => attributes}
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def resource_name
|
|
122
|
+
self.class.name.demodulize.underscore.singularize
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# Override to name the keywords #list sends to Channex as filters
|
|
126
|
+
def filter_keys
|
|
127
|
+
[]
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Attachments API resource
|
|
4
|
+
#
|
|
5
|
+
# Sending a file to a guest is two calls: upload it here, then pass the
|
|
6
|
+
# returned id to {MessageThreads#send_message} or {Messages#send_message}.
|
|
7
|
+
#
|
|
8
|
+
# @example Send a photo of the directions
|
|
9
|
+
# attachment = client.attachments.create(
|
|
10
|
+
# file: Base64.strict_encode64(File.binread("directions.jpeg")),
|
|
11
|
+
# file_name: "directions.jpeg",
|
|
12
|
+
# file_type: "image/jpeg"
|
|
13
|
+
# )
|
|
14
|
+
# client.message_threads.send_message("thread-uuid", attachment_id: attachment.data["id"])
|
|
15
|
+
#
|
|
16
|
+
# @see https://docs.channex.io/api-v.1-documentation/messages-collection
|
|
17
|
+
#
|
|
18
|
+
class Attachments < Resource
|
|
19
|
+
# Upload an attachment
|
|
20
|
+
#
|
|
21
|
+
# @param file [String] Base64-encoded file contents
|
|
22
|
+
# @param file_name [String] File name, e.g. "photo.jpeg"
|
|
23
|
+
# @param file_type [String] MIME type, e.g. "image/jpeg"
|
|
24
|
+
# @return [Response]
|
|
25
|
+
def create(file:, file_name:, file_type:)
|
|
26
|
+
client.post(resource_path, body: {attachment: {file:, file_name:, file_type:}})
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
protected
|
|
30
|
+
|
|
31
|
+
def resource_path
|
|
32
|
+
"/attachments"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Availability API resource
|
|
4
|
+
#
|
|
5
|
+
# Availability represents how many rooms your property has to sell.
|
|
6
|
+
# This is the core data for inventory management in Channex.
|
|
7
|
+
#
|
|
8
|
+
# @example Get availability for a property
|
|
9
|
+
# client.availability.list(
|
|
10
|
+
# property_id: "property-uuid",
|
|
11
|
+
# date_from: "2024-01-01",
|
|
12
|
+
# date_to: "2024-01-31"
|
|
13
|
+
# )
|
|
14
|
+
#
|
|
15
|
+
# @example Update availability for specific dates
|
|
16
|
+
# client.availability.update([
|
|
17
|
+
# {
|
|
18
|
+
# property_id: "property-uuid",
|
|
19
|
+
# room_type_id: "room-uuid",
|
|
20
|
+
# date_from: "2024-01-15",
|
|
21
|
+
# date_to: "2024-01-20",
|
|
22
|
+
# availability: 5
|
|
23
|
+
# }
|
|
24
|
+
# ])
|
|
25
|
+
#
|
|
26
|
+
# @see https://docs.channex.io/api-v.1-documentation/ari
|
|
27
|
+
#
|
|
28
|
+
class Availability < Resource
|
|
29
|
+
# Get availability for a property within a date range
|
|
30
|
+
#
|
|
31
|
+
# @param property_id [String] Property UUID (required)
|
|
32
|
+
# @param date [String] Specific date (YYYY-MM-DD)
|
|
33
|
+
# @param date_from [String] Start date (YYYY-MM-DD), uses filter[date][gte]
|
|
34
|
+
# @param date_to [String] End date (YYYY-MM-DD), uses filter[date][lte]
|
|
35
|
+
# @param options [Hash] Additional options
|
|
36
|
+
# @return [Response]
|
|
37
|
+
def list(property_id:, date: nil, date_from: nil, date_to: nil, **options)
|
|
38
|
+
filter = options.delete(:filter) || {}
|
|
39
|
+
filter[:property_id] = property_id
|
|
40
|
+
|
|
41
|
+
if date
|
|
42
|
+
filter[:date] = date
|
|
43
|
+
else
|
|
44
|
+
filter[:date] = {}
|
|
45
|
+
filter[:date][:gte] = date_from if date_from
|
|
46
|
+
filter[:date][:lte] = date_to if date_to
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
client.get(resource_path, params: {filter: filter, **options})
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# Update availability for properties
|
|
53
|
+
#
|
|
54
|
+
# Each value in the array should contain all required fields including property_id.
|
|
55
|
+
# You can update multiple properties and room types in a single request.
|
|
56
|
+
#
|
|
57
|
+
# @param values [Array<Hash>] Array of availability updates
|
|
58
|
+
# Each hash should contain:
|
|
59
|
+
# - property_id [String] Property UUID (required)
|
|
60
|
+
# - room_type_id [String] Room Type UUID (required)
|
|
61
|
+
# - date_from [String] Start date YYYY-MM-DD (required)
|
|
62
|
+
# - date_to [String] End date YYYY-MM-DD (required)
|
|
63
|
+
# - availability [Integer] Number of available rooms (required)
|
|
64
|
+
# @return [Response]
|
|
65
|
+
#
|
|
66
|
+
# @example Update availability for a date range
|
|
67
|
+
# client.availability.update([
|
|
68
|
+
# {
|
|
69
|
+
# property_id: "716305c4-561a-4561-a187-7f5b8aeb5920",
|
|
70
|
+
# room_type_id: "994d1375-dbbd-4072-8724-b2ab32ce781b",
|
|
71
|
+
# date_from: "2024-01-15",
|
|
72
|
+
# date_to: "2024-01-20",
|
|
73
|
+
# availability: 5
|
|
74
|
+
# }
|
|
75
|
+
# ])
|
|
76
|
+
def update(values)
|
|
77
|
+
client.post(resource_path, body: {values: values})
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
protected
|
|
81
|
+
|
|
82
|
+
def resource_path
|
|
83
|
+
"/availability"
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Channex
|
|
2
|
+
module Resources
|
|
3
|
+
# Availability Rules API resource
|
|
4
|
+
#
|
|
5
|
+
# Availability Rules allow you to create dynamic rules that affect
|
|
6
|
+
# availability for specific channels, room types, and date ranges.
|
|
7
|
+
# Rules can close out availability, set offsets, or cap maximum availability.
|
|
8
|
+
#
|
|
9
|
+
# @example List availability rules for a property
|
|
10
|
+
# client.availability_rules.list(property_id: "property-uuid")
|
|
11
|
+
#
|
|
12
|
+
# @example Create a close-out rule for specific dates
|
|
13
|
+
# client.availability_rules.create(
|
|
14
|
+
# property_id: "property-uuid",
|
|
15
|
+
# affected_channels: ["channel-uuid"],
|
|
16
|
+
# affected_room_types: ["room-type-uuid"],
|
|
17
|
+
# type: "close_out",
|
|
18
|
+
# start_date: "2024-12-24",
|
|
19
|
+
# end_date: "2024-12-26"
|
|
20
|
+
# )
|
|
21
|
+
#
|
|
22
|
+
# @example Create an availability offset rule
|
|
23
|
+
# client.availability_rules.create(
|
|
24
|
+
# property_id: "property-uuid",
|
|
25
|
+
# affected_channels: ["channel-uuid"],
|
|
26
|
+
# affected_room_types: ["room-type-uuid"],
|
|
27
|
+
# type: "availability_offset",
|
|
28
|
+
# value: 2, # Reserve 2 rooms from OTA availability
|
|
29
|
+
# start_date: "2024-01-01"
|
|
30
|
+
# )
|
|
31
|
+
#
|
|
32
|
+
# @see https://docs.channex.io/api-v.1-documentation/availability-rules-collection
|
|
33
|
+
#
|
|
34
|
+
class AvailabilityRules < Resource
|
|
35
|
+
# Rule types
|
|
36
|
+
TYPES = %w[close_out availability_offset max_availability].freeze
|
|
37
|
+
|
|
38
|
+
# Days of week
|
|
39
|
+
DAYS = %w[mo tu we th fr sa su].freeze
|
|
40
|
+
|
|
41
|
+
protected
|
|
42
|
+
|
|
43
|
+
def resource_path
|
|
44
|
+
"/availability_rules"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def filter_keys
|
|
48
|
+
%i[property_id]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|