hcp 1.2.4 → 1.4.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.
data/lib/hcp/lead.rb CHANGED
@@ -1,6 +1,9 @@
1
1
  module Hcp
2
- class Lead < Resource
3
- attr_reader :customer_id
2
+ # Somebody who asked a Housecall Pro user for work, before there is a job.
3
+ class Lead
4
+ include Keyed
5
+
6
+ attr_reader :id, :customer_id
4
7
 
5
8
  def initialize(id: nil, customer_id: nil, key:, company_id:)
6
9
  @id = id
@@ -9,6 +12,8 @@ module Hcp
9
12
  @customer_id = customer_id
10
13
  end
11
14
 
15
+ # Opens the lead on Housecall Pro, and keeps what it filed the lead and customer under.
16
+ # @param params [Hash] :name, :email, :phone, :address, :note and :source.
12
17
  def create(params = {})
13
18
  response = Net::HTTP.post uri, lead_for(params).to_json, headers
14
19
  raise Error, response.body unless response.is_a? Net::HTTPSuccess
@@ -18,10 +23,12 @@ module Hcp
18
23
  raise Error, error
19
24
  end
20
25
 
26
+ private
27
+
21
28
  def lead_for(params = {})
22
29
  {
23
30
  customer: customer_for(params), address: params[:address],
24
- lead_source: params[:source], note: params[:note]
31
+ lead_source: params[:source], note: params[:note],
25
32
  }.compact_blank
26
33
  end
27
34
 
@@ -0,0 +1,76 @@
1
+ module Hcp
2
+ # Every record of one kind a Housecall Pro account holds, walked a page at a time.
3
+ class Relation
4
+ include Chainable, Enumerable
5
+
6
+ # Records a page: the most Housecall Pro answers with, and more than it refuses.
7
+ PAGE = 200
8
+
9
+ # @param type [Class] what each record is read as.
10
+ # @param path [String] where Housecall Pro keeps them, under the host.
11
+ # @param company_id [String, nil] the location to read them as.
12
+ def initialize(type:, path: nil, company_id: nil, conditions: {}, sorts: {}, limit: nil,
13
+ expands: [])
14
+ @type = type
15
+ @path = path || type.path
16
+ @company_id = company_id
17
+ @conditions = conditions
18
+ @sorts = sorts
19
+ @limit = limit
20
+ @expands = expands
21
+ end
22
+
23
+ # Nothing is read until the walk starts, and a page only once the one before it runs out.
24
+ # @return [Enumerator, Relation] every record the list holds, or the list once walked.
25
+ def each(&block)
26
+ return walk unless block
27
+
28
+ walk.each(&block)
29
+ self
30
+ end
31
+
32
+ # Shadows Enumerable#find the way Active Record does: a record is reached by the ID
33
+ # Housecall Pro files it under, not by asking every record whether it is the one.
34
+ # @param id [String] the Housecall Pro ID.
35
+ # @return [Resource] the record, raising Hcp::NotFound where there is none.
36
+ def find(id) = record_for read("#{@path}/#{id}")
37
+
38
+ # A page of one carries the total beside it, so this is one small read however long the
39
+ # list is.
40
+ # @return [Integer] how many records the list holds.
41
+ def count = @count ||= [ read(@path, page_size: 1)['total_items'], @limit ].compact.min
42
+
43
+ private
44
+
45
+ def walk
46
+ Enumerator.new do |yielder|
47
+ seen = 0
48
+ (1..).each do |page|
49
+ body = read @path, page: page, page_size: [ @limit, PAGE ].compact.min
50
+ body.fetch(@type.key).each do |node|
51
+ yielder << record_for(node)
52
+ break if (seen += 1) == @limit
53
+ end
54
+ break if seen == @limit || page >= body.fetch('total_pages', page)
55
+ end
56
+ end
57
+ end
58
+
59
+ def read(path, paging = {})
60
+ Request.new(path: path, params: params.merge(paging), company_id: @company_id).body
61
+ end
62
+
63
+ def params
64
+ @conditions.flat_map { |name, value| filter(name, value).to_a }.to_h.
65
+ merge sort_by: @sorts.keys.first, sort_direction: @sorts.values.first,
66
+ expand: @expands.presence
67
+ end
68
+
69
+ def filter(name, value)
70
+ bounds = @type.filters.fetch name
71
+ Filter.new(bounds: bounds, value: @type.many.include?(name) ? Array(value) : value).params
72
+ end
73
+
74
+ def record_for(node) = @type.new node: node, company_id: @company_id
75
+ end
76
+ end
@@ -0,0 +1,31 @@
1
+ module Hcp
2
+ # One read from Housecall Pro.
3
+ class Request
4
+ # Where Housecall Pro answers.
5
+ HOST = 'https://api.housecallpro.com'
6
+
7
+ # @param path [String] what to read, under the host.
8
+ # @param params [Hash] the query to read it with.
9
+ # @param company_id [String, nil] the location to read it as.
10
+ def initialize(path:, params: {}, company_id: nil)
11
+ @path = path
12
+ @params = params
13
+ @company_id = company_id
14
+ end
15
+
16
+ # @return [Hash] what Housecall Pro answered.
17
+ def body = Answer.new(Net::HTTP.get_response(uri, headers)).body
18
+
19
+ private
20
+
21
+ def uri = URI [ "#{HOST}/#{@path}", @params.compact.to_query ].compact_blank.join '?'
22
+
23
+ def headers
24
+ {
25
+ 'Authorization' => "Token #{Hcp.key}",
26
+ 'Content-Type' => 'application/json',
27
+ 'X-Company-Id' => @company_id,
28
+ }.compact
29
+ end
30
+ end
31
+ end
data/lib/hcp/resource.rb CHANGED
@@ -1,15 +1,58 @@
1
1
  module Hcp
2
+ # A record as Housecall Pro answered it.
2
3
  class Resource
3
- attr_reader :id
4
+ class << self
5
+ # Declares a reader for what Housecall Pro answers under a name.
6
+ # @!macro [attach] attribute
7
+ # @!method $1
8
+ # @return [String, nil] the $1, as Housecall Pro answers it.
9
+ def attribute(name, key = name) = define_method(name) { @node[key.to_s] }
10
+
11
+ # Declares a reader for a moment Housecall Pro stamps.
12
+ # @!macro [attach] timestamp
13
+ # @!method $1
14
+ # @return [Time, nil] the moment Housecall Pro answers as $1.
15
+ def timestamp(name, key = name) = define_method(name) { time key.to_s }
16
+
17
+ # Declares a reader for a sum Housecall Pro counts in cents.
18
+ # @!macro [attach] amount
19
+ # @!method $1
20
+ # @return [BigDecimal, nil] the $1, which Housecall Pro counts in cents.
21
+ def amount(name, key = name) = define_method(name) { money key.to_s }
22
+ end
23
+
24
+ # @param node [Hash] the record as Housecall Pro answered it.
25
+ # @param company_id [String, nil] the location it was read as.
26
+ def initialize(node: {}, company_id: nil)
27
+ @node = node
28
+ @company_id = company_id
29
+ end
30
+
31
+ # @return [String] the ID Housecall Pro files the record under.
32
+ def id = @node['id']
4
33
 
5
34
  private
6
35
 
7
- def headers
8
- {
9
- 'Authorization' => "Token #{@key}",
10
- 'Content-Type' => 'application/json',
11
- 'X-Company-Id' => @company_id,
12
- }.compact
36
+ # Built from the record's own path rather than its class, so a nested collection sits
37
+ # under its parent however deep Housecall Pro put it.
38
+ def nested(type, segment)
39
+ Relation.new type: type, path: "#{path}/#{segment}", company_id: @company_id
40
+ end
41
+
42
+ def path = "#{self.class.path}/#{id}"
43
+
44
+ def time(*keys)
45
+ value = @node.dig(*keys)
46
+ Time.iso8601 value if value.present?
47
+ end
48
+
49
+ # Housecall Pro counts money in cents, and a caller reads it in dollars.
50
+ def money(key) = (BigDecimal(@node[key].to_s) / 100 if @node[key])
51
+
52
+ def record(type, key) = (type.new node: @node[key], company_id: @company_id if @node[key])
53
+
54
+ def records(type, key)
55
+ Array(@node[key]).map { |node| type.new node: node, company_id: @company_id }
13
56
  end
14
57
  end
15
58
  end
@@ -0,0 +1,24 @@
1
+ module Hcp
2
+ # Where a customer is billed, or where the work happens.
3
+ class Address < Resource
4
+ # What Housecall Pro calls a page of a customer's addresses.
5
+ def self.key = 'addresses'
6
+
7
+ attribute :street
8
+ attribute :street_line_2
9
+ attribute :city
10
+ attribute :state
11
+ attribute :zip
12
+ attribute :country
13
+
14
+ # Housecall Pro answers these as numbers under a customer and as strings under the company.
15
+ # @return [Float, nil] how far north the address is, where Housecall Pro placed it.
16
+ def latitude = @node['latitude']&.to_f
17
+
18
+ # @return [Float, nil] how far east the address is, where Housecall Pro placed it.
19
+ def longitude = @node['longitude']&.to_f
20
+
21
+ # @return [Symbol, nil] :billing or :service.
22
+ def type = @node['type']&.to_sym
23
+ end
24
+ end
@@ -0,0 +1,52 @@
1
+ module Hcp
2
+ # A slot the account's online booking settings leave open for work to be booked into.
3
+ class BookingWindow < Resource
4
+ # How Housecall Pro writes the moment a range is looked at from.
5
+ STAMP = '%Y-%m-%dT%H:%M:%S'
6
+
7
+ class << self
8
+ # Where Housecall Pro keeps them.
9
+ def path = 'company/schedule_availability/booking_windows'
10
+
11
+ # What Housecall Pro calls a list of them.
12
+ def key = 'booking_windows'
13
+
14
+ # Housecall Pro answers these whole rather than a page at a time, so they are read in one
15
+ # request rather than walked, and there is no list left to narrow, order or cut.
16
+ # @param starts_at [Date, Time, nil] where to look from; the next day holding a free
17
+ # window where left out.
18
+ # @param days [Integer, nil] how many days of the schedule to look at; seven where left out.
19
+ # @param minutes [Integer, nil] how wide to cut each window; the service's own duration,
20
+ # or thirty minutes, where left out.
21
+ # @param service_id [String, nil] the service whose assigned pros to look at.
22
+ # @param price_form_id [String, nil] the price form whose assigned pros to look at.
23
+ # @param employee_ids [Array<String>, nil] the pros to look at, rather than all of them.
24
+ # @param company_id [String, nil] the location to read as, where the account has several.
25
+ # @return [Array<BookingWindow>] every window in the range, free and taken alike.
26
+ def all(starts_at: nil, days: nil, minutes: nil, service_id: nil, price_form_id: nil,
27
+ employee_ids: nil, company_id: nil)
28
+ params = { start_date: stamp(starts_at), show_for_days: days, service_id: service_id,
29
+ service_duration: minutes, price_form_id: price_form_id, employee_ids: employee_ids, }
30
+ read(params, company_id).fetch(key).
31
+ map { |node| new node: node, company_id: company_id }
32
+ end
33
+
34
+ private
35
+
36
+ def stamp(value) = value&.strftime(STAMP)
37
+
38
+ def read(params, company_id)
39
+ Request.new(path: path, params: params, company_id: company_id).body
40
+ end
41
+ end
42
+
43
+ # @return [Time, nil] when the window opens.
44
+ timestamp :starts_at, :start_time
45
+
46
+ # @return [Time, nil] when the window closes.
47
+ timestamp :ends_at, :end_time
48
+
49
+ # @return [Boolean] whether the account is free to take work in the window.
50
+ def available? = @node['available']
51
+ end
52
+ end
@@ -0,0 +1,42 @@
1
+ module Hcp
2
+ # The account a key belongs to, and the settings every job it holds is booked under.
3
+ class Company < Resource
4
+ # Where Housecall Pro keeps it.
5
+ def self.path = 'company'
6
+
7
+ # The only company a key can read is its own, so there is no list and no ID to find one by.
8
+ # @param company_id [String, nil] the location to read as, where the account has several.
9
+ # @return [Company] the account, as the location it was read as.
10
+ def self.current(company_id: nil)
11
+ node = Request.new(path: path, company_id: company_id).body
12
+ new node: node, company_id: company_id
13
+ end
14
+
15
+ attribute :name
16
+ attribute :website
17
+ attribute :time_zone
18
+
19
+ # @return [String, nil] where the account's logo is served from.
20
+ attribute :logo_url
21
+
22
+ # @return [String, nil] the number the account is reached on.
23
+ attribute :phone, :phone_number
24
+
25
+ # @return [String, nil] the address a customer's reply goes to.
26
+ attribute :support_email
27
+
28
+ # @return [Integer, nil] how many minutes wide the window a customer is given is, by default.
29
+ attribute :arrival_window, :default_arrival_window
30
+
31
+ # @return [Address, nil] where the account is run from.
32
+ def address = record Address, 'address'
33
+
34
+ # @return [Array<String>] the ZIP codes the account will travel to.
35
+ def zip_codes = Array(@node.dig('service_areas_data', 'zip_codes'))
36
+
37
+ # Itself first, then every location under it at any depth, in the order Housecall Pro
38
+ # lists them: a single company is its own one location, and a franchise reads flat.
39
+ # @return [Array<Company>] the locations whose IDs `company_id:` takes, this one included.
40
+ def locations = [ self, *records(Company, 'locations').flat_map(&:locations) ]
41
+ end
42
+ end
@@ -0,0 +1,52 @@
1
+ module Hcp
2
+ # Whoever the work is for.
3
+ class Customer < Resource
4
+ extend Queryable
5
+ include Named, Timestamped
6
+
7
+ # A customer is narrowed by one free-text search rather than by named fields.
8
+ FILTERS = { q: %i[q], location_ids: %i[location_ids] }
9
+
10
+ # The conditions Housecall Pro takes more than one of, and refuses one of.
11
+ MANY = %i[location_ids]
12
+
13
+ # What Housecall Pro will put a list of customers in order of.
14
+ SORTS = %i[created_at updated_at]
15
+
16
+ # What a customer brings back beside themselves where they are asked to.
17
+ EXPANDS = %i[attachments do_not_service]
18
+
19
+ # Where Housecall Pro keeps them.
20
+ def self.path = 'customers'
21
+
22
+ # What Housecall Pro calls a page of them.
23
+ def self.key = 'customers'
24
+
25
+ attribute :first_name
26
+ attribute :last_name
27
+ attribute :email
28
+
29
+ # @return [String, nil] the business the customer is, where they are one.
30
+ attribute :company
31
+
32
+ # @return [String, nil] where the customer came from.
33
+ attribute :lead_source
34
+
35
+
36
+ # Housecall Pro holds three numbers, and a caller wants whichever one there is.
37
+ # @return [String, nil] the number the customer is reached on first.
38
+ def phone = @node['mobile_number'] || @node['home_number'] || @node['work_number']
39
+
40
+ # @return [Symbol, nil] :homeowner, or whatever else Housecall Pro takes the customer for.
41
+ def kind = @node['kind']&.to_sym
42
+
43
+ # @return [Boolean] whether the customer agreed to hear from the pro.
44
+ def notifications_enabled? = @node['notifications_enabled']
45
+
46
+ # @return [Array<String>] what the customer is tagged with.
47
+ def tags = Array(@node['tags'])
48
+
49
+ # @return [Relation] every address the customer is billed or served at.
50
+ def addresses = nested Address, 'addresses'
51
+ end
52
+ end
@@ -0,0 +1,12 @@
1
+ module Hcp
2
+ # A pro on a Housecall Pro account.
3
+ class Employee < Resource
4
+ include Named, Timestamped
5
+
6
+ attribute :email
7
+ attribute :role
8
+
9
+ # @return [String, nil] the number the pro is reached on.
10
+ attribute :phone, :mobile_number
11
+ end
12
+ end
@@ -0,0 +1,26 @@
1
+ module Hcp
2
+ # One of the things a customer was offered on an estimate, priced on its own.
3
+ class Estimate::Option < Resource
4
+ include Timestamped
5
+
6
+ attribute :name
7
+ attribute :option_number
8
+
9
+ # @return [String, nil] whether the customer has answered, and how.
10
+ attribute :approval_status
11
+
12
+ # @return [String, nil] where Housecall Pro files the option in its own workflow.
13
+ attribute :status
14
+
15
+ # @return [String, nil] what the pro said when they sent it.
16
+ attribute :message_from_pro
17
+
18
+ amount :total_amount
19
+
20
+ # @return [Array<String>] what the option is tagged with.
21
+ def tags = Array(@node['tags'])
22
+
23
+ # @return [Array<Note>] what the pros wrote on the option.
24
+ def notes = records Note, 'notes'
25
+ end
26
+ end
@@ -0,0 +1,49 @@
1
+ module Hcp
2
+ # Work a Housecall Pro user offered to do, priced as one option or several.
3
+ class Estimate < Resource
4
+ extend Queryable
5
+ include Scheduled, Statused, Timestamped
6
+
7
+ # What a list of estimates may be narrowed by, against the parameters Housecall Pro takes.
8
+ FILTERS = {
9
+ scheduled_at: %i[scheduled_start_min scheduled_start_max],
10
+ ends_at: %i[scheduled_end_min scheduled_end_max],
11
+ customer_id: %i[customer_id],
12
+ employee_ids: %i[employee_ids],
13
+ work_status: %i[work_status],
14
+ location_ids: %i[location_ids],
15
+ }
16
+
17
+ # The conditions Housecall Pro takes more than one of, and refuses one of.
18
+ MANY = %i[employee_ids work_status location_ids]
19
+
20
+ # What Housecall Pro will put a list of estimates in order of.
21
+ SORTS = %i[created_at updated_at id]
22
+
23
+ # What an estimate brings back beside itself where it is asked to.
24
+ EXPANDS = %i[attachments]
25
+
26
+ # Where Housecall Pro keeps them.
27
+ def self.path = 'estimates'
28
+
29
+ # What Housecall Pro calls a page of them.
30
+ def self.key = 'estimates'
31
+
32
+ attribute :estimate_number
33
+
34
+ # @return [String, nil] where the work came from.
35
+ attribute :lead_source
36
+
37
+ # @return [Customer, nil] whose estimate it is.
38
+ def customer = record Customer, 'customer'
39
+
40
+ # @return [Address, nil] where the work would happen.
41
+ def address = record Address, 'address'
42
+
43
+ # @return [Array<Employee>] the pros the estimate is assigned to.
44
+ def assigned_employees = records Employee, 'assigned_employees'
45
+
46
+ # @return [Array<Estimate::Option>] what the customer was offered, priced.
47
+ def options = records Estimate::Option, 'options'
48
+ end
49
+ end
@@ -0,0 +1,22 @@
1
+ module Hcp
2
+ # One visit a job is booked for, where the work takes more than a single trip.
3
+ class Job::Appointment < Resource
4
+ # What Housecall Pro calls a list of them.
5
+ def self.key = 'appointments'
6
+
7
+ # @return [Time, nil] when the visit is booked to start.
8
+ timestamp :starts_at, :start_time
9
+
10
+ # @return [Time, nil] when the visit is booked to end.
11
+ timestamp :ends_at, :end_time
12
+
13
+ # @return [Integer, nil] how many minutes wide the arrival window is.
14
+ attribute :arrival_window, :arrival_window_minutes
15
+
16
+ # @return [Boolean] whether the visit is booked for a day rather than for a time.
17
+ def anytime? = @node['anytime']
18
+
19
+ # @return [Array<String>] the IDs of the pros sent on the visit.
20
+ def dispatched_employee_ids = Array(@node['dispatched_employees_ids'])
21
+ end
22
+ end
@@ -0,0 +1,21 @@
1
+ module Hcp
2
+ # A bill raised for a job.
3
+ class Job::Invoice < Resource
4
+ # What Housecall Pro calls a list of them.
5
+ def self.key = 'invoices'
6
+
7
+ attribute :invoice_number
8
+
9
+ # @return [String, nil] where Housecall Pro files the invoice in its own workflow.
10
+ attribute :status
11
+
12
+ amount :amount
13
+ amount :due_amount
14
+
15
+ # @return [Time, nil] when the invoice was sent.
16
+ timestamp :sent_at
17
+
18
+ # @return [Time, nil] when the invoice was paid.
19
+ timestamp :paid_at
20
+ end
21
+ end
@@ -0,0 +1,73 @@
1
+ module Hcp
2
+ # Work a Housecall Pro user accepted: what it is, who it is for, and what it comes to.
3
+ class Job < Resource
4
+ extend Queryable
5
+ include Scheduled, Statused, Timestamped
6
+
7
+ # What a list of jobs may be narrowed by, against the parameters Housecall Pro takes.
8
+ FILTERS = {
9
+ scheduled_at: %i[scheduled_start_min scheduled_start_max],
10
+ ends_at: %i[scheduled_end_min scheduled_end_max],
11
+ customer_id: %i[customer_id],
12
+ employee_ids: %i[employee_ids],
13
+ work_status: %i[work_status],
14
+ location_ids: %i[location_ids],
15
+ }
16
+
17
+ # The conditions Housecall Pro takes more than one of, and refuses one of.
18
+ MANY = %i[employee_ids work_status location_ids]
19
+
20
+ # What Housecall Pro will put a list of jobs in order of.
21
+ SORTS = %i[created_at updated_at invoice_number id description work_status]
22
+
23
+ # What a job brings back beside itself where it is asked to.
24
+ EXPANDS = %i[attachments appointments]
25
+
26
+ # Where Housecall Pro keeps them.
27
+ def self.path = 'jobs'
28
+
29
+ # What Housecall Pro calls a page of them.
30
+ def self.key = 'jobs'
31
+
32
+ attribute :invoice_number
33
+ attribute :description
34
+ attribute :lead_source
35
+
36
+ # @return [String, nil] the ID of the estimate the job was won with.
37
+ attribute :original_estimate_id
38
+
39
+ amount :subtotal
40
+ amount :total_amount
41
+ amount :outstanding_balance
42
+
43
+ # @return [Time, nil] when the job was locked against further changes.
44
+ timestamp :locked_at
45
+
46
+ # @return [Time, nil] when the customer canceled the job.
47
+ timestamp :canceled_at
48
+
49
+ # @return [Array<String>] what the job is tagged with.
50
+ def tags = Array(@node['tags'])
51
+
52
+ # @return [Customer, nil] whose job it is.
53
+ def customer = record Customer, 'customer'
54
+
55
+ # @return [Address, nil] where the work happens.
56
+ def address = record Address, 'address'
57
+
58
+ # @return [Array<Note>] what the pros wrote on the job.
59
+ def notes = records Note, 'notes'
60
+
61
+ # @return [Array<Employee>] the pros the job is assigned to.
62
+ def assigned_employees = records Employee, 'assigned_employees'
63
+
64
+ # @return [Relation] the visits the job is booked for.
65
+ def appointments = nested Job::Appointment, 'appointments'
66
+
67
+ # @return [Relation] what the job is billed as.
68
+ def line_items = nested LineItem, 'line_items'
69
+
70
+ # @return [Relation] the invoices raised for the job.
71
+ def invoices = nested Job::Invoice, 'invoices'
72
+ end
73
+ end
@@ -0,0 +1,20 @@
1
+ module Hcp
2
+ # One line of what a job or an estimate option comes to.
3
+ class LineItem < Resource
4
+ # Housecall Pro answers a job's line items under `data` rather than under their own name.
5
+ def self.key = 'data'
6
+
7
+ attribute :name
8
+ attribute :description
9
+
10
+ # @return [Float, nil] how many of it, to two decimal places.
11
+ attribute :quantity
12
+
13
+ amount :unit_price
14
+ amount :unit_cost
15
+ amount :amount
16
+
17
+ # @return [Symbol, nil] :materials, :labor, or one of the gratuities and discounts.
18
+ def kind = @node['kind']&.tr(' ', '_')&.to_sym
19
+ end
20
+ end
@@ -0,0 +1,6 @@
1
+ module Hcp
2
+ # Something a pro wrote on a job or on an estimate option.
3
+ class Note < Resource
4
+ attribute :content
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ module Hcp
2
+ # When work is booked for, and how long a window the customer was given.
3
+ class Schedule < Resource
4
+ # @return [Time, nil] when the work is booked to start.
5
+ timestamp :starts_at, :scheduled_start
6
+
7
+ # @return [Time, nil] when the work is booked to end.
8
+ timestamp :ends_at, :scheduled_end
9
+
10
+ # @return [String, nil] the IANA zone to show a customer a time in.
11
+ attribute :time_zone
12
+
13
+ # @return [Integer, nil] how many minutes wide the arrival window is.
14
+ attribute :arrival_window
15
+
16
+ # Housecall Pro leaves these out unless a list was asked to bring them back, so a job read
17
+ # without `includes(:appointments)` has none of them rather than none at all.
18
+ # @return [Array<Job::Appointment>] the visits the work is booked for.
19
+ def appointments = records Job::Appointment, 'appointments'
20
+ end
21
+ end
data/lib/hcp/version.rb CHANGED
@@ -1,7 +1,5 @@
1
- # frozen_string_literal: true
2
-
3
1
  # Ruby client for the Housecall Pro API.
4
2
  module Hcp
5
3
  # Current version of the gem.
6
- VERSION = '1.2.4'
4
+ VERSION = '1.4.0'
7
5
  end