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.
Files changed (56) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +0 -1
  3. data/CHANGELOG.md +75 -0
  4. data/CLAUDE.md +34 -9
  5. data/README.md +99 -125
  6. data/lib/hcp/account.rb +25 -0
  7. data/lib/hcp/answer.rb +3 -11
  8. data/lib/hcp/client.rb +41 -0
  9. data/lib/hcp/collections/estimates.rb +22 -0
  10. data/lib/hcp/collections/jobs.rb +32 -0
  11. data/lib/hcp/collections/leads.rb +25 -0
  12. data/lib/hcp/collections/technicians.rb +23 -0
  13. data/lib/hcp/collections/visits.rb +80 -0
  14. data/lib/hcp/error.rb +1 -1
  15. data/lib/hcp/errors/throttled.rb +4 -0
  16. data/lib/hcp/event.rb +1 -6
  17. data/lib/hcp/resources/business.rb +11 -0
  18. data/lib/hcp/resources/customer.rb +9 -45
  19. data/lib/hcp/resources/estimate.rb +40 -46
  20. data/lib/hcp/resources/job.rb +58 -69
  21. data/lib/hcp/resources/lead.rb +26 -0
  22. data/lib/hcp/resources/line.rb +8 -0
  23. data/lib/hcp/resources/location.rb +7 -0
  24. data/lib/hcp/resources/quote.rb +28 -0
  25. data/lib/hcp/resources/technician.rb +7 -0
  26. data/lib/hcp/resources/visit.rb +45 -0
  27. data/lib/hcp/version.rb +3 -3
  28. data/lib/hcp.rb +22 -41
  29. metadata +30 -28
  30. data/lib/hcp/access.rb +0 -12
  31. data/lib/hcp/concerns/chainable.rb +0 -39
  32. data/lib/hcp/concerns/keyed.rb +0 -14
  33. data/lib/hcp/concerns/named.rb +0 -7
  34. data/lib/hcp/concerns/queryable.rb +0 -41
  35. data/lib/hcp/concerns/scheduled.rb +0 -16
  36. data/lib/hcp/concerns/statused.rb +0 -23
  37. data/lib/hcp/concerns/timestamped.rb +0 -10
  38. data/lib/hcp/errors/not_found.rb +0 -4
  39. data/lib/hcp/errors/too_many_requests.rb +0 -16
  40. data/lib/hcp/filter.rb +0 -36
  41. data/lib/hcp/key.rb +0 -24
  42. data/lib/hcp/lead/pipeline.rb +0 -36
  43. data/lib/hcp/lead.rb +0 -44
  44. data/lib/hcp/relation.rb +0 -76
  45. data/lib/hcp/request.rb +0 -31
  46. data/lib/hcp/resource.rb +0 -58
  47. data/lib/hcp/resources/address.rb +0 -24
  48. data/lib/hcp/resources/booking_window.rb +0 -52
  49. data/lib/hcp/resources/company.rb +0 -42
  50. data/lib/hcp/resources/employee.rb +0 -12
  51. data/lib/hcp/resources/estimate/option.rb +0 -26
  52. data/lib/hcp/resources/job/appointment.rb +0 -22
  53. data/lib/hcp/resources/job/invoice.rb +0 -21
  54. data/lib/hcp/resources/line_item.rb +0 -20
  55. data/lib/hcp/resources/note.rb +0 -6
  56. 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, its queries carry arrays
9
- # in the bracketed form `to_query` writes, and a key handed to one thread stays on it.
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
- require 'active_support/isolated_execution_state'
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/not_found'
18
- require 'hcp/errors/too_many_requests'
19
- require 'hcp/key'
18
+ require 'hcp/errors/throttled'
20
19
 
21
- # Answer before Request, which reads one, and Filter before Relation, which writes them.
20
+ # Answer before Client, which reads one, and Client before everything that talks through it.
22
21
  require 'hcp/answer'
23
- require 'hcp/request'
24
- require 'hcp/filter'
25
- require 'hcp/concerns/chainable'
26
- require 'hcp/relation'
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
- require 'hcp/resources/address'
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
- # A job before the collections named under it, which its constant has to exist for.
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/resources/estimate/option'
52
-
53
- # After Company, which is what a key opens.
54
- require 'hcp/access'
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: 1.4.0
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/access.rb
137
+ - lib/hcp/account.rb
124
138
  - lib/hcp/answer.rb
125
- - lib/hcp/concerns/chainable.rb
126
- - lib/hcp/concerns/keyed.rb
127
- - lib/hcp/concerns/named.rb
128
- - lib/hcp/concerns/queryable.rb
129
- - lib/hcp/concerns/scheduled.rb
130
- - lib/hcp/concerns/statused.rb
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/not_found.rb
134
- - lib/hcp/errors/too_many_requests.rb
146
+ - lib/hcp/errors/throttled.rb
135
147
  - lib/hcp/event.rb
136
- - lib/hcp/filter.rb
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/job/appointment.rb
152
- - lib/hcp/resources/job/invoice.rb
153
- - lib/hcp/resources/line_item.rb
154
- - lib/hcp/resources/note.rb
155
- - lib/hcp/resources/schedule.rb
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
@@ -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
@@ -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
@@ -1,4 +0,0 @@
1
- module Hcp
2
- # Raised where Housecall Pro has no record under the ID it was asked for.
3
- NotFound = Class.new Error
4
- end
@@ -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
@@ -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