hcp 1.4.0 → 3.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 +75 -0
- data/CLAUDE.md +34 -9
- data/README.md +99 -125
- data/lib/hcp/account.rb +25 -0
- data/lib/hcp/answer.rb +3 -11
- data/lib/hcp/client.rb +41 -0
- data/lib/hcp/collections/estimates.rb +22 -0
- data/lib/hcp/collections/jobs.rb +32 -0
- data/lib/hcp/collections/leads.rb +25 -0
- data/lib/hcp/collections/technicians.rb +23 -0
- data/lib/hcp/collections/visits.rb +80 -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/estimate.rb +40 -46
- data/lib/hcp/resources/job.rb +58 -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/technician.rb +7 -0
- data/lib/hcp/resources/visit.rb +45 -0
- data/lib/hcp/version.rb +3 -3
- data/lib/hcp.rb +22 -41
- metadata +30 -28
- 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/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
data/lib/hcp.rb
CHANGED
|
@@ -1,60 +1,41 @@
|
|
|
1
1
|
require 'bigdecimal'
|
|
2
|
-
require 'date'
|
|
3
2
|
require 'json'
|
|
4
3
|
require 'net/http'
|
|
5
4
|
require 'time'
|
|
6
5
|
|
|
7
6
|
# Only the Active Support files whose methods are used, rather than the whole of it: a name
|
|
8
|
-
# Housecall Pro holds nothing for arrives as readily empty as null,
|
|
9
|
-
#
|
|
7
|
+
# Housecall Pro holds nothing for arrives as readily empty as null, and a query is written the
|
|
8
|
+
# way `to_query` writes one.
|
|
10
9
|
require 'active_support/core_ext/enumerable'
|
|
11
10
|
require 'active_support/core_ext/object/blank'
|
|
12
11
|
require 'active_support/core_ext/object/to_query'
|
|
13
|
-
|
|
12
|
+
|
|
13
|
+
# The vocabulary the account answers in.
|
|
14
|
+
require 'company'
|
|
14
15
|
|
|
15
16
|
require 'hcp/version'
|
|
16
17
|
require 'hcp/error'
|
|
17
|
-
require 'hcp/errors/
|
|
18
|
-
require 'hcp/errors/too_many_requests'
|
|
19
|
-
require 'hcp/key'
|
|
18
|
+
require 'hcp/errors/throttled'
|
|
20
19
|
|
|
21
|
-
# Answer before
|
|
20
|
+
# Answer before Client, which reads one, and Client before everything that talks through it.
|
|
22
21
|
require 'hcp/answer'
|
|
23
|
-
require 'hcp/
|
|
24
|
-
require 'hcp/
|
|
25
|
-
require 'hcp/
|
|
26
|
-
require 'hcp/
|
|
27
|
-
require 'hcp/concerns/queryable'
|
|
28
|
-
require 'hcp/resource'
|
|
29
|
-
|
|
30
|
-
# Every concern before the records including it, and Schedule before Scheduled, which reads one.
|
|
31
|
-
require 'hcp/concerns/named'
|
|
32
|
-
require 'hcp/concerns/timestamped'
|
|
33
|
-
require 'hcp/concerns/statused'
|
|
34
|
-
require 'hcp/resources/schedule'
|
|
35
|
-
require 'hcp/concerns/scheduled'
|
|
22
|
+
require 'hcp/client'
|
|
23
|
+
require 'hcp/resources/business'
|
|
24
|
+
require 'hcp/resources/lead'
|
|
25
|
+
require 'hcp/collections/leads'
|
|
36
26
|
|
|
37
|
-
|
|
38
|
-
require 'hcp/resources/booking_window'
|
|
39
|
-
require 'hcp/resources/company'
|
|
40
|
-
require 'hcp/resources/employee'
|
|
41
|
-
require 'hcp/resources/line_item'
|
|
42
|
-
require 'hcp/resources/note'
|
|
27
|
+
# Every record before the one that reads it beside itself, and the job before its list.
|
|
43
28
|
require 'hcp/resources/customer'
|
|
44
|
-
|
|
45
|
-
|
|
29
|
+
require 'hcp/resources/technician'
|
|
30
|
+
require 'hcp/resources/location'
|
|
31
|
+
require 'hcp/resources/line'
|
|
32
|
+
require 'hcp/resources/quote'
|
|
33
|
+
require 'hcp/resources/visit'
|
|
46
34
|
require 'hcp/resources/job'
|
|
47
|
-
require 'hcp/resources/job/appointment'
|
|
48
|
-
require 'hcp/resources/job/invoice'
|
|
49
|
-
|
|
50
35
|
require 'hcp/resources/estimate'
|
|
51
|
-
require 'hcp/
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
require 'hcp/
|
|
55
|
-
|
|
56
|
-
require 'hcp/concerns/keyed'
|
|
57
|
-
require 'hcp/lead'
|
|
58
|
-
require 'hcp/lead/pipeline'
|
|
59
|
-
|
|
36
|
+
require 'hcp/collections/jobs'
|
|
37
|
+
require 'hcp/collections/estimates'
|
|
38
|
+
require 'hcp/collections/visits'
|
|
39
|
+
require 'hcp/collections/technicians'
|
|
40
|
+
require 'hcp/account'
|
|
60
41
|
require 'hcp/event'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: hcp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version:
|
|
4
|
+
version: 3.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Claudio Baccigalupo
|
|
@@ -23,6 +23,20 @@ dependencies:
|
|
|
23
23
|
- - ">="
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
25
|
version: '0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: company
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '2.1'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '2.1'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: minitest
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -120,39 +134,27 @@ files:
|
|
|
120
134
|
- LICENSE.txt
|
|
121
135
|
- README.md
|
|
122
136
|
- lib/hcp.rb
|
|
123
|
-
- lib/hcp/
|
|
137
|
+
- lib/hcp/account.rb
|
|
124
138
|
- lib/hcp/answer.rb
|
|
125
|
-
- lib/hcp/
|
|
126
|
-
- lib/hcp/
|
|
127
|
-
- lib/hcp/
|
|
128
|
-
- lib/hcp/
|
|
129
|
-
- lib/hcp/
|
|
130
|
-
- lib/hcp/
|
|
131
|
-
- lib/hcp/concerns/timestamped.rb
|
|
139
|
+
- lib/hcp/client.rb
|
|
140
|
+
- lib/hcp/collections/estimates.rb
|
|
141
|
+
- lib/hcp/collections/jobs.rb
|
|
142
|
+
- lib/hcp/collections/leads.rb
|
|
143
|
+
- lib/hcp/collections/technicians.rb
|
|
144
|
+
- lib/hcp/collections/visits.rb
|
|
132
145
|
- lib/hcp/error.rb
|
|
133
|
-
- lib/hcp/errors/
|
|
134
|
-
- lib/hcp/errors/too_many_requests.rb
|
|
146
|
+
- lib/hcp/errors/throttled.rb
|
|
135
147
|
- lib/hcp/event.rb
|
|
136
|
-
- lib/hcp/
|
|
137
|
-
- lib/hcp/key.rb
|
|
138
|
-
- lib/hcp/lead.rb
|
|
139
|
-
- lib/hcp/lead/pipeline.rb
|
|
140
|
-
- lib/hcp/relation.rb
|
|
141
|
-
- lib/hcp/request.rb
|
|
142
|
-
- lib/hcp/resource.rb
|
|
143
|
-
- lib/hcp/resources/address.rb
|
|
144
|
-
- lib/hcp/resources/booking_window.rb
|
|
145
|
-
- lib/hcp/resources/company.rb
|
|
148
|
+
- lib/hcp/resources/business.rb
|
|
146
149
|
- lib/hcp/resources/customer.rb
|
|
147
|
-
- lib/hcp/resources/employee.rb
|
|
148
150
|
- lib/hcp/resources/estimate.rb
|
|
149
|
-
- lib/hcp/resources/estimate/option.rb
|
|
150
151
|
- lib/hcp/resources/job.rb
|
|
151
|
-
- lib/hcp/resources/
|
|
152
|
-
- lib/hcp/resources/
|
|
153
|
-
- lib/hcp/resources/
|
|
154
|
-
- lib/hcp/resources/
|
|
155
|
-
- lib/hcp/resources/
|
|
152
|
+
- lib/hcp/resources/lead.rb
|
|
153
|
+
- lib/hcp/resources/line.rb
|
|
154
|
+
- lib/hcp/resources/location.rb
|
|
155
|
+
- lib/hcp/resources/quote.rb
|
|
156
|
+
- lib/hcp/resources/technician.rb
|
|
157
|
+
- lib/hcp/resources/visit.rb
|
|
156
158
|
- lib/hcp/version.rb
|
|
157
159
|
homepage: https://github.com/claudiob/hcp
|
|
158
160
|
licenses:
|
data/lib/hcp/access.rb
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# What a key opens: the account it belongs to, read as one of its locations.
|
|
3
|
-
class Access
|
|
4
|
-
# @param company_id [String, nil] the location to read as, where the account has several.
|
|
5
|
-
def initialize(company_id: nil)
|
|
6
|
-
@company_id = company_id
|
|
7
|
-
end
|
|
8
|
-
|
|
9
|
-
# @return [Company] the account the key belongs to, as the location it is read as.
|
|
10
|
-
def account = Company.current company_id: @company_id
|
|
11
|
-
end
|
|
12
|
-
end
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Extends a list with the chaining that narrows, orders and cuts it.
|
|
3
|
-
module Chainable
|
|
4
|
-
# A condition Housecall Pro does not take is one it ignores, answering the whole account
|
|
5
|
-
# rather than a page of it, so an unknown one is refused here where it can still be named.
|
|
6
|
-
# @return [Relation] the same list, narrowed by these conditions.
|
|
7
|
-
def where(**conditions)
|
|
8
|
-
only conditions.keys, @type.filters.keys
|
|
9
|
-
narrowed conditions: @conditions.merge(conditions)
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# @return [Relation] the same list, in this order.
|
|
13
|
-
def order(**sorts)
|
|
14
|
-
only sorts.keys, @type.sorts
|
|
15
|
-
narrowed sorts: sorts
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
# @return [Relation] the same list, stopping after this many records.
|
|
19
|
-
def limit(count) = narrowed limit: count
|
|
20
|
-
|
|
21
|
-
# @return [Relation] the same list, asking Housecall Pro for these beside each record.
|
|
22
|
-
def includes(*names)
|
|
23
|
-
only names, @type.expands
|
|
24
|
-
narrowed expands: @expands | names
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
private
|
|
28
|
-
|
|
29
|
-
def only(names, allowed)
|
|
30
|
-
unknown = names - allowed
|
|
31
|
-
raise Error, "#{unknown.first} is not one of: #{allowed.join ', '}" if unknown.any?
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def narrowed(conditions: @conditions, sorts: @sorts, limit: @limit, expands: @expands)
|
|
35
|
-
Relation.new type: @type, path: @path, company_id: @company_id, conditions: conditions,
|
|
36
|
-
sorts: sorts, limit: limit, expands: expands
|
|
37
|
-
end
|
|
38
|
-
end
|
|
39
|
-
end
|
data/lib/hcp/concerns/keyed.rb
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Extends a class handed its own key, as everything was before the key became global.
|
|
3
|
-
module Keyed
|
|
4
|
-
private
|
|
5
|
-
|
|
6
|
-
def headers
|
|
7
|
-
{
|
|
8
|
-
'Authorization' => "Token #{@key}",
|
|
9
|
-
'Content-Type' => 'application/json',
|
|
10
|
-
'X-Company-Id' => @company_id,
|
|
11
|
-
}.compact
|
|
12
|
-
end
|
|
13
|
-
end
|
|
14
|
-
end
|
data/lib/hcp/concerns/named.rb
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Extends a record Housecall Pro holds a person's names for.
|
|
3
|
-
module Named
|
|
4
|
-
# @return [String] what they are called, by whichever of their names Housecall Pro holds.
|
|
5
|
-
def name = [ @node['first_name'], @node['last_name'] ].compact_blank.join ' '
|
|
6
|
-
end
|
|
7
|
-
end
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Extends a resource with the entry points that read a list of it, or one record.
|
|
3
|
-
module Queryable
|
|
4
|
-
# @param company_id [String, nil] the location to read as, where the account has several.
|
|
5
|
-
# @return [Relation] every record of this kind the location holds.
|
|
6
|
-
def all(company_id: nil) = Relation.new type: self, company_id: company_id
|
|
7
|
-
|
|
8
|
-
# @return [Relation] the records matching these conditions.
|
|
9
|
-
def where(company_id: nil, **conditions) = all(company_id: company_id).where(**conditions)
|
|
10
|
-
|
|
11
|
-
# @return [Relation] the records in this order.
|
|
12
|
-
def order(company_id: nil, **sorts) = all(company_id: company_id).order(**sorts)
|
|
13
|
-
|
|
14
|
-
# @return [Relation] at most this many records.
|
|
15
|
-
def limit(count, company_id: nil) = all(company_id: company_id).limit(count)
|
|
16
|
-
|
|
17
|
-
# @return [Relation] the records, with these brought back beside each of them.
|
|
18
|
-
def includes(*names, company_id: nil) = all(company_id: company_id).includes(*names)
|
|
19
|
-
|
|
20
|
-
# @return [Integer] how many records of this kind the location holds.
|
|
21
|
-
def count(company_id: nil) = all(company_id: company_id).count
|
|
22
|
-
|
|
23
|
-
# @return [Resource, nil] the first record, or nil where the location holds none.
|
|
24
|
-
def first(company_id: nil) = all(company_id: company_id).first
|
|
25
|
-
|
|
26
|
-
# @return [Resource] the record Housecall Pro files under this ID.
|
|
27
|
-
def find(id, company_id: nil) = all(company_id: company_id).find(id)
|
|
28
|
-
|
|
29
|
-
# @return [Hash] what a list of these may be narrowed by.
|
|
30
|
-
def filters = self::FILTERS
|
|
31
|
-
|
|
32
|
-
# @return [Array<Symbol>] the conditions Housecall Pro takes more than one of.
|
|
33
|
-
def many = self::MANY
|
|
34
|
-
|
|
35
|
-
# @return [Array<Symbol>] what Housecall Pro will put a list of these in order of.
|
|
36
|
-
def sorts = self::SORTS
|
|
37
|
-
|
|
38
|
-
# @return [Array<Symbol>] what these bring back beside themselves where asked to.
|
|
39
|
-
def expands = self::EXPANDS
|
|
40
|
-
end
|
|
41
|
-
end
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Extends a record Housecall Pro books work for and stamps as the work happens.
|
|
3
|
-
module Scheduled
|
|
4
|
-
# @return [Schedule, nil] when the work is booked for.
|
|
5
|
-
def schedule = record Schedule, 'schedule'
|
|
6
|
-
|
|
7
|
-
# @return [Time, nil] when the pro said they were on their way.
|
|
8
|
-
def on_my_way_at = time 'work_timestamps', 'on_my_way_at'
|
|
9
|
-
|
|
10
|
-
# @return [Time, nil] when the work started.
|
|
11
|
-
def started_at = time 'work_timestamps', 'started_at'
|
|
12
|
-
|
|
13
|
-
# @return [Time, nil] when the work was finished.
|
|
14
|
-
def completed_at = time 'work_timestamps', 'completed_at'
|
|
15
|
-
end
|
|
16
|
-
end
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Extends a record Housecall Pro moves through a workflow.
|
|
3
|
-
module Statused
|
|
4
|
-
# Housecall Pro narrows a list by one set of words and answers with another, so a caller
|
|
5
|
-
# who filters by :in_progress reads :in_progress back rather than 'in progress'.
|
|
6
|
-
STATUSES = {
|
|
7
|
-
'needs scheduling' => :unscheduled,
|
|
8
|
-
'scheduled' => :scheduled,
|
|
9
|
-
'in progress' => :in_progress,
|
|
10
|
-
'complete rated' => :completed,
|
|
11
|
-
'complete unrated' => :completed,
|
|
12
|
-
'user canceled' => :canceled,
|
|
13
|
-
'pro canceled' => :canceled,
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
# @return [Symbol, nil] where Housecall Pro files the record in its own workflow.
|
|
17
|
-
def work_status = STATUSES[@node['work_status']]
|
|
18
|
-
|
|
19
|
-
# A rating is not a status, so it reads beside one rather than inside it.
|
|
20
|
-
# @return [Boolean] whether the customer rated the work.
|
|
21
|
-
def rated? = @node['work_status'] == 'complete rated'
|
|
22
|
-
end
|
|
23
|
-
end
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
module Hcp
|
|
2
|
-
# Extends a record Housecall Pro stamps with when it was opened and last changed.
|
|
3
|
-
module Timestamped
|
|
4
|
-
# @return [Time, nil] when the record was opened.
|
|
5
|
-
def created_at = time 'created_at'
|
|
6
|
-
|
|
7
|
-
# @return [Time, nil] when the record last changed.
|
|
8
|
-
def updated_at = time 'updated_at'
|
|
9
|
-
end
|
|
10
|
-
end
|
data/lib/hcp/errors/not_found.rb
DELETED
|
@@ -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
|