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 +4 -4
- data/CHANGELOG.md +34 -0
- data/README.md +23 -1
- data/lib/hcp/account.rb +3 -0
- data/lib/hcp/collections/visits.rb +5 -1
- data/lib/hcp/collections/windows.rb +74 -0
- data/lib/hcp/version.rb +1 -1
- data/lib/hcp.rb +1 -0
- metadata +4 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: aad72b55ce034138806c6d68b993d430e7c299960ee14c92e0c59563b5f5bae3
|
|
4
|
+
data.tar.gz: ba0582ba808d0dbf087eb8a9f6823336e57d6fbc466a1be0f71a9daee38db25b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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).
|
|
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
|
|
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
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:
|
|
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: '
|
|
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: '
|
|
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
|