hcp 1.4.0 → 2.0.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 +4 -4
- data/.yardopts +0 -1
- data/CHANGELOG.md +40 -0
- data/CLAUDE.md +13 -9
- data/README.md +60 -142
- data/lib/hcp/account.rb +22 -0
- data/lib/hcp/answer.rb +3 -11
- data/lib/hcp/client.rb +41 -0
- data/lib/hcp/collections/jobs.rb +32 -0
- data/lib/hcp/collections/leads.rb +25 -0
- data/lib/hcp/collections/visits.rb +39 -0
- data/lib/hcp/error.rb +1 -1
- data/lib/hcp/errors/throttled.rb +4 -0
- data/lib/hcp/event.rb +1 -6
- data/lib/hcp/resources/business.rb +11 -0
- data/lib/hcp/resources/customer.rb +9 -45
- data/lib/hcp/resources/job.rb +54 -69
- data/lib/hcp/resources/lead.rb +26 -0
- data/lib/hcp/resources/line.rb +8 -0
- data/lib/hcp/resources/location.rb +7 -0
- data/lib/hcp/resources/quote.rb +28 -0
- data/lib/hcp/resources/visit.rb +21 -0
- data/lib/hcp/version.rb +3 -3
- data/lib/hcp.rb +19 -42
- metadata +27 -29
- data/lib/hcp/access.rb +0 -12
- data/lib/hcp/concerns/chainable.rb +0 -39
- data/lib/hcp/concerns/keyed.rb +0 -14
- data/lib/hcp/concerns/named.rb +0 -7
- data/lib/hcp/concerns/queryable.rb +0 -41
- data/lib/hcp/concerns/scheduled.rb +0 -16
- data/lib/hcp/concerns/statused.rb +0 -23
- data/lib/hcp/concerns/timestamped.rb +0 -10
- data/lib/hcp/errors/not_found.rb +0 -4
- data/lib/hcp/errors/too_many_requests.rb +0 -16
- data/lib/hcp/filter.rb +0 -36
- data/lib/hcp/key.rb +0 -24
- data/lib/hcp/lead/pipeline.rb +0 -36
- data/lib/hcp/lead.rb +0 -44
- data/lib/hcp/relation.rb +0 -76
- data/lib/hcp/request.rb +0 -31
- data/lib/hcp/resource.rb +0 -58
- data/lib/hcp/resources/address.rb +0 -24
- data/lib/hcp/resources/booking_window.rb +0 -52
- data/lib/hcp/resources/company.rb +0 -42
- data/lib/hcp/resources/employee.rb +0 -12
- data/lib/hcp/resources/estimate/option.rb +0 -26
- data/lib/hcp/resources/estimate.rb +0 -49
- data/lib/hcp/resources/job/appointment.rb +0 -22
- data/lib/hcp/resources/job/invoice.rb +0 -21
- data/lib/hcp/resources/line_item.rb +0 -20
- data/lib/hcp/resources/note.rb +0 -6
- data/lib/hcp/resources/schedule.rb +0 -21
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Raised where Housecall Pro refuses a request for rate.
|
|
3
|
-
class TooManyRequests < Error
|
|
4
|
-
# @param message [String] what Housecall Pro said.
|
|
5
|
-
# @param reset_at [Time, nil] when it will answer again.
|
|
6
|
-
def initialize(message, reset_at)
|
|
7
|
-
super message
|
|
8
|
-
@reset_at = reset_at
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
# Nothing here sleeps: a caller with a queue can bring the whole job back, which is worth
|
|
12
|
-
# more than a worker asleep holding a connection open.
|
|
13
|
-
# @return [Time, nil] when the limit lifts, where Housecall Pro said.
|
|
14
|
-
attr_reader :reset_at
|
|
15
|
-
end
|
|
16
|
-
end
|
data/lib/hcp/filter.rb
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# One condition a list is narrowed by, as the parameters Housecall Pro takes it as.
|
|
3
|
-
class Filter
|
|
4
|
-
# @param bounds [Array<Symbol>] the parameter for each end of a range, or the single one.
|
|
5
|
-
# @param value [Object] a range, or the one value to match.
|
|
6
|
-
def initialize(bounds:, value:)
|
|
7
|
-
@bounds = bounds
|
|
8
|
-
@value = value
|
|
9
|
-
end
|
|
10
|
-
|
|
11
|
-
# @return [Hash] the parameters to send, without the end the caller left open.
|
|
12
|
-
def params
|
|
13
|
-
return { @bounds.first => @value } unless @value.is_a? Range
|
|
14
|
-
|
|
15
|
-
refuse
|
|
16
|
-
low, high = @bounds
|
|
17
|
-
{ low => stamp(@value.begin), high => stamp(@value.end) }.compact
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
private
|
|
21
|
-
|
|
22
|
-
# Housecall Pro reads both ends inclusively, so an excluded end would come back anyway.
|
|
23
|
-
def refuse
|
|
24
|
-
raise Error, "#{@bounds.first} takes one value, not a range" if @bounds.one?
|
|
25
|
-
raise Error, "#{@bounds.last} cannot exclude its end" if @value.exclude_end? && @value.end
|
|
26
|
-
end
|
|
27
|
-
|
|
28
|
-
def stamp(value)
|
|
29
|
-
case value
|
|
30
|
-
when Time then value.utc.iso8601
|
|
31
|
-
when Date then value.iso8601
|
|
32
|
-
else value
|
|
33
|
-
end
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
end
|
data/lib/hcp/key.rb
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
class << self
|
|
3
|
-
# The Housecall Pro API key every request is read with, where a thread holds none of its own.
|
|
4
|
-
attr_writer :key
|
|
5
|
-
|
|
6
|
-
# @return [String, nil] the key this thread was handed, the one this module was given, or
|
|
7
|
-
# the one the environment carries.
|
|
8
|
-
def key = ActiveSupport::IsolatedExecutionState[:hcp_key] || @key || ENV['HCP_KEY']
|
|
9
|
-
|
|
10
|
-
# Reads as this key for the block, on this thread alone, so a process serving several
|
|
11
|
-
# accounts can hold a key on every thread without any of them seeing another's.
|
|
12
|
-
# @param key [String] the key to read with.
|
|
13
|
-
# @param company_id [String, nil] the location to read as, where the account has several.
|
|
14
|
-
# @yieldparam access [Access] the account the key opens.
|
|
15
|
-
# @return [Object] what the block answered.
|
|
16
|
-
def with_key(key, company_id: nil)
|
|
17
|
-
previous = ActiveSupport::IsolatedExecutionState[:hcp_key]
|
|
18
|
-
ActiveSupport::IsolatedExecutionState[:hcp_key] = key
|
|
19
|
-
yield Access.new(company_id: company_id)
|
|
20
|
-
ensure
|
|
21
|
-
ActiveSupport::IsolatedExecutionState[:hcp_key] = previous
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
end
|
data/lib/hcp/lead/pipeline.rb
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Where a Housecall Pro user files a lead while they are still chasing it.
|
|
3
|
-
class Lead::Pipeline
|
|
4
|
-
include Keyed
|
|
5
|
-
|
|
6
|
-
def initialize(id: nil, key:, company_id:)
|
|
7
|
-
@id = id
|
|
8
|
-
@key = key
|
|
9
|
-
@company_id = company_id
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# Moves the lead to the status going by this name.
|
|
13
|
-
# @param status_name [String] the status as the account names it, such as 'Won'.
|
|
14
|
-
def update(status_name:)
|
|
15
|
-
status = find_status_by name: status_name
|
|
16
|
-
payload = { resource_type: 'lead', resource_id: @id, status_id: status['id'] }.to_json
|
|
17
|
-
response = Net::HTTP.put uri, payload, headers
|
|
18
|
-
raise Error, response.body unless response.is_a? Net::HTTPSuccess
|
|
19
|
-
rescue Errno::ECONNREFUSED => error
|
|
20
|
-
raise Error, error
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
private
|
|
24
|
-
|
|
25
|
-
def find_status_by(name:)
|
|
26
|
-
body = JSON Net::HTTP.get uri.tap { |url| url.query = 'resource_type=lead' }, headers
|
|
27
|
-
body['statuses'].find { |status| status['name'] == name } || unknown_status(name: name)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def unknown_status(name:)
|
|
31
|
-
raise Error, "Status #{name} not found for lead #{@id}"
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def uri = URI 'https://api.housecallpro.com/pipeline/statuses'
|
|
35
|
-
end
|
|
36
|
-
end
|
data/lib/hcp/lead.rb
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
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
|
|
7
|
-
|
|
8
|
-
def initialize(id: nil, customer_id: nil, key:, company_id:)
|
|
9
|
-
@id = id
|
|
10
|
-
@key = key
|
|
11
|
-
@company_id = company_id
|
|
12
|
-
@customer_id = customer_id
|
|
13
|
-
end
|
|
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.
|
|
17
|
-
def create(params = {})
|
|
18
|
-
response = Net::HTTP.post uri, lead_for(params).to_json, headers
|
|
19
|
-
raise Error, response.body unless response.is_a? Net::HTTPSuccess
|
|
20
|
-
body = JSON response.body
|
|
21
|
-
@id, @customer_id = body['id'], body.dig('customer', 'id')
|
|
22
|
-
rescue Errno::ECONNREFUSED => error
|
|
23
|
-
raise Error, error
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
private
|
|
27
|
-
|
|
28
|
-
def lead_for(params = {})
|
|
29
|
-
{
|
|
30
|
-
customer: customer_for(params), address: params[:address],
|
|
31
|
-
lead_source: params[:source], note: params[:note],
|
|
32
|
-
}.compact_blank
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def customer_for(params = {})
|
|
36
|
-
{
|
|
37
|
-
first_name: params[:name], email: params[:email],
|
|
38
|
-
mobile_number: params[:phone], lead_source: params[:source],
|
|
39
|
-
}.compact_blank
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
def uri = URI 'https://api.housecallpro.com/leads'
|
|
43
|
-
end
|
|
44
|
-
end
|
data/lib/hcp/relation.rb
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
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
|
data/lib/hcp/request.rb
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
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
DELETED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# A record as Housecall Pro answered it.
|
|
3
|
-
class Resource
|
|
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']
|
|
33
|
-
|
|
34
|
-
private
|
|
35
|
-
|
|
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 }
|
|
56
|
-
end
|
|
57
|
-
end
|
|
58
|
-
end
|
|
@@ -1,24 +0,0 @@
|
|
|
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
|
|
@@ -1,52 +0,0 @@
|
|
|
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
|
|
@@ -1,42 +0,0 @@
|
|
|
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
|
|
@@ -1,26 +0,0 @@
|
|
|
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
|
|
@@ -1,49 +0,0 @@
|
|
|
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
|
|
@@ -1,22 +0,0 @@
|
|
|
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
|
|
@@ -1,21 +0,0 @@
|
|
|
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
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
data/lib/hcp/resources/note.rb
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
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
|