hcp 3.1.0 → 4.1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3c2323d80d7ca73d0e62b6cef443bd2e40b803495df3ed0ad0d05fac2b5eb186
4
- data.tar.gz: d908b99bcf637800fbc1bf0bc17bb777d06338e1ca51ae999981405ae717a59b
3
+ metadata.gz: aad72b55ce034138806c6d68b993d430e7c299960ee14c92e0c59563b5f5bae3
4
+ data.tar.gz: ba0582ba808d0dbf087eb8a9f6823336e57d6fbc466a1be0f71a9daee38db25b
5
5
  SHA512:
6
- metadata.gz: a93a05b7b4b3b0785661ab9104ac0a0ec88a4d18e43331fd49414c4ad8388e379806a01425250d5e862c2015b76101747c0a4d2e139d67b9ee139a7871d078fe
7
- data.tar.gz: '0759b4af994c2ef83c40abc10b7730b9e49350d3ceb8a74fa7f9be43cf373094b0056bfd35644b69a77bb04328176d9bd3994cadba1dabcc4f01abbf1054ff7f'
6
+ metadata.gz: da42001d35f67f6aa680b8b2b5f3cbc9ee020b537af8ba126ead44d76257d8f8f881afad501ecbce5c8c5fa7ebf65be59ccfb79f3d16ad2d5af5436288dd3c78
7
+ data.tar.gz: 680f9463a9f6540f03281fe4013251f83622811cfaff0a53bb1e9c47081720bc99f624be79ba86b0d9ce8031892c40f38cc140cf128711bc0cb0b800793ff4f8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [4.1.0] - 2026-09-17
4
+
5
+ - [Feature] `account.visits.for_work`, the stops of jobs and of estimates together and the hours
6
+ held around them left unread. `/events` narrows by nothing, so reading blocked-out time costs
7
+ a sweep of every page on top of the two requests the work itself takes -- fifteen of them on
8
+ an account with 2,877 events. A caller that wants the work and not the holds now says so and
9
+ is spared them.
10
+
11
+ - [Note] The vocabulary is `company` 3.1, where `for_work` is named. A platform that answers its
12
+ whole list at once gets the narrowing for free; this one pushes it down to the requests it
13
+ does not then make.
14
+
15
+ ## [4.0.0] - 2026-09-16
16
+
17
+ - [Breaking change] The vocabulary is `company` 3.0: `assigned_to` is `of`, so
18
+ `account.visits.of(technician)` is what `account.visits.assigned_to(technician)` was. The pin
19
+ is `~> 3.0`.
20
+
21
+ - [Feature] `account.windows`, the free time of the location: every stretch nobody is booked
22
+ for, read off `GET /company/schedule_availability/booking_windows`. Housecall Pro works it out
23
+ for itself -- it holds the hours the business keeps, the notice it needs, the padding it
24
+ leaves around a job and the calendar blocked out -- so this asks rather than reckons, and an
25
+ hour held is already taken off.
26
+
27
+ - [Feature] Housecall Pro answers a week as a row of short windows, each open or not. The open
28
+ ones that run together are joined back into the stretch they came from, so a window is as long
29
+ as the pro is free rather than as long as their booking page happens to offer. What to cut it
30
+ into is the caller's, where an offer is being made.
31
+
32
+ - [Note] `start_date`, `show_for_days` and `employee_ids` all narrow this list, so one person's
33
+ week is one request -- unlike `/events`, which accepts the same words and ignores them. An
34
+ account with nothing open answers a bare list rather than the usual object, and both shapes
35
+ are read.
36
+
3
37
  ## [3.1.0] - 2026-09-16
4
38
 
5
39
  - [Feature] `account.visits` covers the time blocked out around the work, which Housecall Pro
data/README.md CHANGED
@@ -118,8 +118,12 @@ for the stops of work alone:
118
118
  ```ruby
119
119
  account.visits.upcoming(2.weeks).for_jobs # => only the appointments, one request
120
120
  account.visits.upcoming(2.weeks).for_leads # => only the estimates' slots, one request
121
+ account.visits.upcoming(2.weeks).for_work # => both kinds of stop, and no sweep
121
122
  ```
122
123
 
124
+ `for_work` is the one to ask for where a caller wants the work and not the hours held around it:
125
+ the jobs and the estimates are still read, and the fifteen requests of the sweep are not spent.
126
+
123
127
  A stop says where it is without being asked what it was booked for: an appointment has no
124
128
  address of its own and takes the job's, and an estimate's slot takes the estimate's. A caller
125
129
  reading a schedule never reaches through `visit.job` for an address.
@@ -147,7 +151,7 @@ One technician's week is the visits in it narrowed to them, which is how a sched
147
151
 
148
152
  ```ruby
149
153
  monday = Date.today.beginning_of_week.in_time_zone
150
- account.visits.between(monday, monday + 1.week).assigned_to(technician).each do |visit|
154
+ account.visits.between(monday, monday + 1.week).of(technician).each do |visit|
151
155
  visit.starts_at, visit.ends_at, visit.job.location.street
152
156
  end
153
157
  ```
@@ -159,6 +163,24 @@ dispatched to some of its crew or to none of it, so a stop dispatched to nobody
159
163
  crew's, and the stops the technician is not on are let go once the jobs arrive. Asking for the
160
164
  week and asking for the technician narrow the same list, in either order.
161
165
 
166
+ The other half of that week is the hours they are *not* out, which Housecall Pro works out for
167
+ itself and answers whole:
168
+
169
+ ```ruby
170
+ account.windows.between(monday, monday + 1.week).of(technician).each do |window|
171
+ window.starts_at, window.ends_at
172
+ end
173
+ ```
174
+
175
+ It holds the hours the business keeps, the notice it needs, the padding it leaves around a job
176
+ and the time blocked out on the calendar, so this asks rather than reckons and the holds are
177
+ already taken off. It answers a week as a row of short windows, each open or not; the open ones
178
+ that run together are one stretch and are joined back into it, so a window is as long as the pro
179
+ is free rather than as long as their booking page happens to offer.
180
+
181
+ `start_date`, `show_for_days` and `employee_ids` all narrow, so a week for one person is the one
182
+ request -- unlike `/events`, which accepts the same words and ignores them.
183
+
162
184
  What Housecall Pro schedules elsewhere is still not here: time blocked out on the calendar is
163
185
  filed under `/events`, which takes no date and no employee to narrow by, so a week of it cannot
164
186
  be asked for -- only paged in full.
data/lib/hcp/account.rb CHANGED
@@ -16,6 +16,9 @@ module Hcp
16
16
  # @return [Visits] visits of the location, read off its jobs.
17
17
  def visits = Visits.new client: @client
18
18
 
19
+ # @return [Windows] free time of the location, as Housecall Pro works it out.
20
+ def windows = Windows.new client: @client
21
+
19
22
  # @return [Technicians] employees of the location, walked a page at a time.
20
23
  def technicians = Technicians.new client: @client
21
24
 
@@ -31,7 +31,7 @@ module Hcp
31
31
  # to it, so the stops they are not on are let go once the work comes back.
32
32
  # @param technician [Company::Technician] whoever the work is booked for.
33
33
  # @return [Company::Selection] the same list, narrowed to the stops they are booked for.
34
- def assigned_to(technician)
34
+ def of(technician)
35
35
  theirs = with technician: technician
36
36
  Company::Selection.new(collection: theirs) do |visit|
37
37
  visit.technicians.any? { |each| each.id == technician.id }
@@ -44,6 +44,10 @@ module Hcp
44
44
  # @return [Visits] the same list, read off the estimates alone: one request rather than many.
45
45
  def for_leads = with(jobs: false, events: false)
46
46
 
47
+ # @return [Visits] the same list, read off the work alone: two requests rather than the
48
+ # fifteen a sweep of the calendar costs on top of them.
49
+ def for_work = with(events: false)
50
+
47
51
  # Work booked across the window carries every stop in it, so each list is read once and
48
52
  # what was called off keeps its stops to itself.
49
53
  # @yield [Company::Visit] each visit in the window, the work's stops before the hours
@@ -0,0 +1,74 @@
1
+ module Hcp
2
+ # The free time of one location, which Housecall Pro works out for itself: it holds the hours
3
+ # the business keeps, the notice it needs and the padding it leaves around a job, and answers
4
+ # which stretches of a week are still open. So this asks rather than reckons, and the hours
5
+ # blocked out on the calendar are already taken off.
6
+ class Windows < Company::Windows
7
+ # How far ahead to ask where nothing named an end. Housecall Pro's own default, and a week
8
+ # is what a schedule is read by.
9
+ DAYS = 7
10
+
11
+ # @param client [Client] how to reach Housecall Pro as the location.
12
+ # @param technician [Company::Technician, nil] whose free time to ask for, or nothing for
13
+ # whether anybody at all is free.
14
+ # @param from [Time, nil] the moment the window opens, or nothing for the first day open.
15
+ # @param to [Time, nil] the moment the window closes, or nothing for a week of it.
16
+ def initialize(client:, technician: nil, from: nil, to: nil)
17
+ @client = client
18
+ @technician = technician
19
+ @from = from
20
+ @to = to
21
+ end
22
+
23
+ # @param from [Time, nil] the moment the window opens, or nothing for the first day open.
24
+ # @param to [Time, nil] the moment the window closes, or nothing for a week of it.
25
+ # @return [Windows] the same list, narrowed to the free time between the two.
26
+ def between(from, to) = with(from: from, to: to)
27
+
28
+ # Housecall Pro narrows free time by who it is free for, so the technician joins the window
29
+ # in the one request and nobody else's hours are answered or paid for.
30
+ # @param technician [Company::Technician] whose free time to answer.
31
+ # @return [Windows] the same list, as that technician's alone.
32
+ def of(technician) = with(technician: technician)
33
+
34
+ # Housecall Pro answers a week as a row of short windows, each open or not. Consecutive open
35
+ # ones are one stretch of free time and are joined back into it, so what comes out is as long
36
+ # as the pro is actually free rather than as long as their booking page happens to offer.
37
+ # @yield [Company::Window] each stretch nobody is booked for, earliest first.
38
+ def each
39
+ joined(open).each do |from, to|
40
+ yield Company::Window.new node: { starts_at: from, ends_at: to }
41
+ end
42
+ end
43
+
44
+ private
45
+
46
+ def open = answered.select { it['available'] }.map { [ it['start_time'], it['end_time'] ] }
47
+
48
+ def joined(windows)
49
+ windows.each_with_object [] do |(from, to), stretches|
50
+ carries_on = stretches.last && stretches.last[1] == from
51
+ carries_on ? stretches.last[1] = to : stretches << [ from, to ]
52
+ end
53
+ end
54
+
55
+ # An account with nothing open answers a bare list rather than the usual object, so the
56
+ # windows are taken out of whichever shape came back.
57
+ def answered
58
+ body = @client.get 'company/schedule_availability/booking_windows', params
59
+ body.is_a?(Array) ? body : body.fetch('booking_windows', [])
60
+ end
61
+
62
+ def params
63
+ { start_date: @from&.utc&.strftime('%Y-%m-%dT%H:%M:%S'), show_for_days: days,
64
+ employee_ids: (@technician && [ @technician.id ]), }.compact
65
+ end
66
+
67
+ def days = (@to && @from) ? ((@to - @from) / 1.day).ceil : DAYS
68
+
69
+ def with(**changed)
70
+ self.class.new(**{ client: @client, technician: @technician, from: @from,
71
+ to: @to, }.merge(changed))
72
+ end
73
+ end
74
+ end
data/lib/hcp/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # The Housecall Pro API, answered in the vocabulary of the company gem.
2
2
  module Hcp
3
3
  # The version of this gem, as RubyGems knows it.
4
- VERSION = '3.1.0'
4
+ VERSION = '4.1.0'
5
5
  end
data/lib/hcp.rb CHANGED
@@ -45,6 +45,7 @@ require 'hcp/collections/jobs'
45
45
  require 'hcp/collections/estimates'
46
46
  require 'hcp/collections/occurrences'
47
47
  require 'hcp/collections/visits'
48
+ require 'hcp/collections/windows'
48
49
  require 'hcp/collections/technicians'
49
50
  require 'hcp/account'
50
51
  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: 3.1.0
4
+ version: 4.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Claudio Baccigalupo
@@ -29,14 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - "~>"
31
31
  - !ruby/object:Gem::Version
32
- version: '2.1'
32
+ version: '3.1'
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
- version: '2.1'
39
+ version: '3.1'
40
40
  - !ruby/object:Gem::Dependency
41
41
  name: ice_cube
42
42
  requirement: !ruby/object:Gem::Requirement
@@ -157,6 +157,7 @@ files:
157
157
  - lib/hcp/collections/occurrences.rb
158
158
  - lib/hcp/collections/technicians.rb
159
159
  - lib/hcp/collections/visits.rb
160
+ - lib/hcp/collections/windows.rb
160
161
  - lib/hcp/error.rb
161
162
  - lib/hcp/errors/throttled.rb
162
163
  - lib/hcp/event.rb