booker_api 0.0.1

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 (62) hide show
  1. checksums.yaml +7 -0
  2. data/lib/booker/booker.rb +5 -0
  3. data/lib/booker/business_client.rb +22 -0
  4. data/lib/booker/business_rest.rb +130 -0
  5. data/lib/booker/client.rb +212 -0
  6. data/lib/booker/common_rest.rb +49 -0
  7. data/lib/booker/config/booker_country_ids_to_iso_codes.yml +269 -0
  8. data/lib/booker/customer_client.rb +17 -0
  9. data/lib/booker/customer_rest.rb +169 -0
  10. data/lib/booker/errors.rb +24 -0
  11. data/lib/booker/generic_token_store.rb +25 -0
  12. data/lib/booker/helpers/active_support_helper.rb +102 -0
  13. data/lib/booker/helpers/logging_helper.rb +11 -0
  14. data/lib/booker/models/address.rb +12 -0
  15. data/lib/booker/models/appointment.rb +77 -0
  16. data/lib/booker/models/appointment_treatment.rb +51 -0
  17. data/lib/booker/models/available_time.rb +13 -0
  18. data/lib/booker/models/business_type.rb +5 -0
  19. data/lib/booker/models/category.rb +5 -0
  20. data/lib/booker/models/class_instance.rb +25 -0
  21. data/lib/booker/models/country.rb +19 -0
  22. data/lib/booker/models/current_price.rb +5 -0
  23. data/lib/booker/models/customer.rb +52 -0
  24. data/lib/booker/models/customer_2.rb +5 -0
  25. data/lib/booker/models/customer_record_type.rb +5 -0
  26. data/lib/booker/models/discount.rb +5 -0
  27. data/lib/booker/models/dynamic_price.rb +11 -0
  28. data/lib/booker/models/employee.rb +10 -0
  29. data/lib/booker/models/feature_settings.rb +7 -0
  30. data/lib/booker/models/final_total.rb +5 -0
  31. data/lib/booker/models/gender.rb +5 -0
  32. data/lib/booker/models/itinerary_time_slot.rb +7 -0
  33. data/lib/booker/models/itinerary_time_slots_list.rb +7 -0
  34. data/lib/booker/models/location.rb +23 -0
  35. data/lib/booker/models/location_day_schedule.rb +18 -0
  36. data/lib/booker/models/model.rb +150 -0
  37. data/lib/booker/models/multi_service_availability_result.rb +7 -0
  38. data/lib/booker/models/notification_settings.rb +12 -0
  39. data/lib/booker/models/online_booking_settings.rb +23 -0
  40. data/lib/booker/models/original_price.rb +5 -0
  41. data/lib/booker/models/payment_method.rb +5 -0
  42. data/lib/booker/models/preferred_staff_gender.rb +5 -0
  43. data/lib/booker/models/price.rb +8 -0
  44. data/lib/booker/models/receipt_display_price.rb +5 -0
  45. data/lib/booker/models/room.rb +12 -0
  46. data/lib/booker/models/shipping_address.rb +5 -0
  47. data/lib/booker/models/source.rb +5 -0
  48. data/lib/booker/models/spa.rb +5 -0
  49. data/lib/booker/models/spa_employee_availability_search_item.rb +11 -0
  50. data/lib/booker/models/status.rb +5 -0
  51. data/lib/booker/models/sub_category.rb +5 -0
  52. data/lib/booker/models/tag_price.rb +5 -0
  53. data/lib/booker/models/teacher.rb +5 -0
  54. data/lib/booker/models/teacher_2.rb +5 -0
  55. data/lib/booker/models/time_zone.rb +8 -0
  56. data/lib/booker/models/treatment.rb +19 -0
  57. data/lib/booker/models/treatment_time_slot.rb +5 -0
  58. data/lib/booker/models/type.rb +8 -0
  59. data/lib/booker/models/user.rb +73 -0
  60. data/lib/booker/version.rb +3 -0
  61. data/lib/booker_api.rb +97 -0
  62. metadata +216 -0
@@ -0,0 +1,13 @@
1
+ module Booker
2
+ module Models
3
+ class AvailableTime < Model
4
+ attr_accessor 'CurrentPrice',
5
+ 'Duration',
6
+ 'EmployeeID',
7
+ 'StartDateTime',
8
+ 'TreatmentID',
9
+ 'RoomID',
10
+ 'Employee2ID'
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ module Models
3
+ class BusinessType < Type; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ module Models
3
+ class Category < Type; end
4
+ end
5
+ end
@@ -0,0 +1,25 @@
1
+ module Booker
2
+ module Models
3
+ class ClassInstance < Model
4
+ attr_accessor 'ID',
5
+ 'EndDateTime',
6
+ 'HasClassFilled',
7
+ 'IsForMembersOnly',
8
+ 'IsRecurring',
9
+ 'IsSpecialEvent',
10
+ 'IsWorkshop',
11
+ 'NumReserved',
12
+ 'Price',
13
+ 'RoomName',
14
+ 'StartDateTime',
15
+ 'Teacher',
16
+ 'Teacher2',
17
+ 'TotalCapacity',
18
+ 'TreatmentDuration',
19
+ 'ListAsSubstitute',
20
+ 'IsEnrollable',
21
+ 'SeriesID',
22
+ 'Treatment'
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,19 @@
1
+ module Booker
2
+ module Models
3
+ class Country < Type
4
+ # ISO 3166-1 alpha-2 Codes
5
+ IDS_TO_ISO_CODES = YAML::load_file(File.join(__dir__, '..', 'config', 'booker_country_ids_to_iso_codes.yml')).freeze
6
+
7
+ def country_code; IDS_TO_ISO_CODES[self.ID]; end
8
+
9
+ def self.from_country_code(code)
10
+ country = self.new ID: IDS_TO_ISO_CODES.detect{|k, v| v == code}.try(:[], 0)
11
+ if country.ID.nil?
12
+ raise ArgumentError, 'Country code not recognized'
13
+ else
14
+ country
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ module Models
3
+ class CurrentPrice < Price; end
4
+ end
5
+ end
@@ -0,0 +1,52 @@
1
+ module Booker
2
+ module Models
3
+ class Customer < Model
4
+ attr_accessor 'CustomerID',
5
+ 'GUID',
6
+ 'Address',
7
+ 'AllowReceiveEmails',
8
+ 'AllowReceiveSMS',
9
+ 'SendEmail',
10
+ 'CellPhone', # Returned when using FindCustomers
11
+ 'CreditCard',
12
+ 'Email',
13
+ 'FirstName',
14
+ 'HasActiveMembership',
15
+ 'HomePhone',
16
+ 'MobilePhone', # Set this when making an appointment
17
+ 'MobilePhoneCarrierID',
18
+ 'LastName',
19
+ 'WorkPhone',
20
+ 'WorkPhoneExt',
21
+ 'ShippingAddress',
22
+ 'CustomerRecordType',
23
+ 'DateOfBirth',
24
+ 'Gender',
25
+ 'GenderID',
26
+ 'HasMembership',
27
+ 'HasPastMembership',
28
+ 'IsNewCustomer',
29
+ 'LoyaltyPoints',
30
+ 'LocationID',
31
+ 'LocationName',
32
+ 'NumberOfReferrals',
33
+ 'PreferredStaffGender',
34
+ 'EmergencyContactName',
35
+ 'EmergencyContactPhone',
36
+ 'EmergencyContactRelationship',
37
+ 'IsActive',
38
+ 'Occupation',
39
+ 'PreferredStaffMemberID',
40
+ 'ReferredByCustomerID'
41
+
42
+ def self.from_list(array)
43
+ if array.any? && array.first['Customer']
44
+ flattened = array.map{|a| a['Customer'].merge('CustomerID' => a['CustomerID'])}
45
+ super(flattened)
46
+ else
47
+ super
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ module Models
3
+ class Customer2 < Customer; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ module Models
3
+ class CustomerRecordType < Type; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ module Models
3
+ class Discount < Price; end
4
+ end
5
+ end
@@ -0,0 +1,11 @@
1
+ module Booker
2
+ module Models
3
+ class DynamicPrice < Model
4
+ attr_accessor 'Discount',
5
+ 'FinalPrice',
6
+ 'OriginalPrice',
7
+ 'ReceiptDisplayPrice',
8
+ 'OriginalTagPrice'
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ module Booker
2
+ module Models
3
+ class Employee < Model
4
+ attr_accessor 'ID',
5
+ 'FirstName',
6
+ 'LastName',
7
+ 'Gender'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,7 @@
1
+ module Booker
2
+ module Models
3
+ class FeatureSettings < Model
4
+ attr_accessor 'UseFrederick', 'UsePromote'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ module Models
3
+ class FinalTotal < Price; end
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ module Booker
2
+ module Models
3
+ class Gender < Type; end
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ module Booker
2
+ module Models
3
+ class ItineraryTimeSlot < Model
4
+ attr_accessor 'TreatmentTimeSlots'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ module Booker
2
+ module Models
3
+ class ItineraryTimeSlotsList < Model
4
+ attr_accessor 'ItineraryTimeSlots'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,23 @@
1
+ module Booker
2
+ module Models
3
+ class Location < Model
4
+ attr_accessor 'ID',
5
+ 'AccountName',
6
+ 'BusinessName',
7
+ 'BusinessType',
8
+ 'EmailAddress',
9
+ 'Address',
10
+ 'Phone',
11
+ 'TimeZone',
12
+ 'WebSite',
13
+ 'IsDistributionPartner',
14
+ 'EncryptedLocationID',
15
+ 'BrandAccountName',
16
+ 'LogoUrl',
17
+ 'BusinessType',
18
+ 'FirstName',
19
+ 'LastName',
20
+ 'CurrencyCode'
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module Booker
2
+ module Models
3
+ class LocationDaySchedule < Model
4
+ attr_accessor 'Weekday',
5
+ 'StartTime',
6
+ 'EndTime'
7
+
8
+ def self.from_hash(hash)
9
+ model = super
10
+ model.Weekday = to_wday(model.Weekday)
11
+ strftime_format = '%T'
12
+ model.StartTime = model.StartTime.try(:strftime, strftime_format)
13
+ model.EndTime = model.EndTime.try(:strftime, strftime_format)
14
+ model
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,150 @@
1
+ module Booker
2
+ module Models
3
+ class Model
4
+ def initialize(options = {})
5
+ options.each { |key, value| send(:"#{key}=", value) }
6
+ end
7
+
8
+ def to_hash
9
+ hash = {}
10
+ self.instance_variables.each do |var|
11
+ value = self.instance_variable_get var
12
+ if value.is_a? Array
13
+ new_value = hash_list(value)
14
+ elsif value.is_a? Booker::Models::Model
15
+ new_value = value.to_hash
16
+ elsif value.is_a? Time
17
+ new_value = self.class.time_to_booker_datetime(value)
18
+ elsif value.is_a? Date
19
+ time = value.in_time_zone
20
+ new_value = self.class.time_to_booker_datetime(time)
21
+ else
22
+ new_value = value
23
+ end
24
+ hash[var[1..-1]] = new_value
25
+ end
26
+ hash
27
+ end
28
+
29
+ def to_json; Oj.dump(to_hash, mode: :compat); end
30
+
31
+ def self.from_hash(hash)
32
+ model = self.new
33
+ hash.each do |key, value|
34
+ if model.respond_to?(:"#{key}")
35
+ constantized = self.constantize(key)
36
+ if constantized
37
+ if value.is_a?(Array) && value.first.is_a?(Hash)
38
+ model.send(:"#{key}=", constantized.from_list(value))
39
+ next
40
+ elsif value.is_a? Hash
41
+ model.send(:"#{key}=", constantized.from_hash(value))
42
+ next
43
+ end
44
+ end
45
+
46
+ if value.is_a?(String) && value.start_with?('/Date(')
47
+ model.send(:"#{key}=", time_from_booker_datetime(value))
48
+ elsif !value.nil?
49
+ model.send(:"#{key}=", value)
50
+ end
51
+ end
52
+ end
53
+ model
54
+ end
55
+
56
+ def self.from_list(array); array.map { |item| self.from_hash(item) }; end
57
+
58
+ def self.constantize(key)
59
+ begin
60
+ Booker::Models.const_get("Booker::Models::#{key.singularize}")
61
+ rescue NameError
62
+ nil
63
+ end
64
+ end
65
+
66
+ # Booker's API hands you back all time as if the business is in server time.
67
+ # First load the time in server time, then return the same hours and minutes in current time zone.
68
+ # Booker will hopefully fix this in a future API version. Sorry.
69
+ def self.time_from_booker_datetime(booker_datetime)
70
+ timestamp = booker_datetime[/\/Date\((.\d+)[\-\+]/, 1].to_i / 1000.to_f
71
+
72
+ original_tz = Time.zone
73
+ begin
74
+ # Booker's server is always EST
75
+ Time.zone = Booker::Client::TimeZone
76
+
77
+ booker_time = Time.zone.at(timestamp)
78
+ ensure
79
+ Time.zone = original_tz
80
+ end
81
+
82
+ # Convert it back to location time without changing hours and minutes
83
+ Time.zone.parse(booker_time.strftime('%Y-%m-%d %H:%M:%S'))
84
+ end
85
+
86
+ # Booker's API requires times to be sent in as if the business is in Eastern Time!
87
+ def self.time_to_booker_datetime(time)
88
+ original_tz = Time.zone
89
+
90
+ begin
91
+ # Booker's server is always EST
92
+ Time.zone = Booker::Client::TimeZone
93
+ timestamp = (Time.zone.parse(time.strftime("%Y-%m-%dT%H:%M:%S")).to_f * 1000).to_i
94
+ ensure
95
+ Time.zone = original_tz
96
+ end
97
+
98
+ "/Date(#{timestamp})/"
99
+ end
100
+
101
+ def self.timezone_from_booker_timezone(booker_timezone_name)
102
+ normalized_booker_timezone_name = Booker::Helpers::ActiveSupport.to_active_support(booker_timezone_name)
103
+ return normalized_booker_timezone_name if normalized_booker_timezone_name.present?
104
+
105
+ begin
106
+ Booker::Helpers::LoggingHelper.log_issue(
107
+ 'Unable to find time zone name using Booker::Helpers::ActiveSupport.to_active_support',
108
+ booker_timezone_name: booker_timezone_name
109
+ )
110
+ rescue
111
+ end
112
+
113
+ timezone_from_booker_offset!(booker_timezone_name)
114
+ end
115
+
116
+ def self.timezone_from_booker_offset!(booker_timezone_name)
117
+ booker_offset_match = booker_timezone_name.scan(/(\A)(.*)(?=\))/).first
118
+
119
+ if booker_offset_match.present?
120
+ booker_offset = booker_offset_match.delete_if { |match| match.blank? }.first
121
+
122
+ if booker_offset
123
+ booker_timezone_map_key = Booker::Helpers::ActiveSupport.booker_timezone_names.find do |key|
124
+ key.start_with?(booker_offset)
125
+ end
126
+
127
+ return Booker::Helpers::ActiveSupport.to_active_support(booker_timezone_map_key) if booker_timezone_map_key
128
+ end
129
+ end
130
+
131
+ raise Booker::Error
132
+ end
133
+
134
+ def self.to_wday(booker_wday); Date.parse(booker_wday).wday; end
135
+
136
+ private
137
+ def hash_list(array)
138
+ array.map do |item|
139
+ if item.is_a? Array
140
+ hash_list(item)
141
+ elsif item.is_a? Booker::Models::Model
142
+ item.to_hash
143
+ else
144
+ item
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,7 @@
1
+ module Booker
2
+ module Models
3
+ class MultiServiceAvailabilityResult < Model
4
+ attr_accessor 'ItineraryTimeSlotsLists'
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,12 @@
1
+ module Booker
2
+ module Models
3
+ class NotificationSettings < Model
4
+ attr_accessor 'NotificationsEmail',
5
+ 'NotifyAdminOnAppointmentOtherEvent',
6
+ 'SendAppointmentReminders',
7
+ 'SendConfirmationAfterBooking',
8
+ 'SendNoticeHoursBeforeAppointment',
9
+ 'NotifyServiceProviderOnAppointmentOtherEvent'
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module Booker
2
+ module Models
3
+ class OnlineBookingSettings < Model
4
+ attr_accessor 'NewAppointmentCutOffInterval',
5
+ 'PaymentMethods',
6
+ 'PreAuthorizeCreditCard',
7
+ 'PreferredAppointmentTimes',
8
+ 'RequirePaymentInformation',
9
+ 'AppointmentStartTimeInterval',
10
+ 'SelectTechnician',
11
+ 'SelectTechnicianGender',
12
+ 'AllowCancelAppointments',
13
+ 'MaximumResultsPerDay',
14
+ 'UsePreferredAppointmentTimes',
15
+ 'RequireCustomerGender',
16
+ 'RequireStreetAddress',
17
+ 'GoogleAnalyticsCode',
18
+ 'BillingAddressRequired',
19
+ 'BackgroundImageUrl',
20
+ 'BusinessLogoUrl'
21
+ end
22
+ end
23
+ end