hcp 3.0.1 → 4.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/CHANGELOG.md +51 -0
- data/README.md +33 -4
- data/lib/hcp/account.rb +3 -0
- data/lib/hcp/collections/occurrences.rb +72 -0
- data/lib/hcp/collections/visits.rb +15 -8
- data/lib/hcp/collections/windows.rb +74 -0
- data/lib/hcp/resources/estimate.rb +1 -1
- data/lib/hcp/resources/occurrence.rb +21 -0
- data/lib/hcp/version.rb +1 -1
- data/lib/hcp.rb +10 -0
- metadata +20 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 6452e662bc00ffd31b8fa21a935de8c1b4ef9d4779a87668ce4f6b4c4585b3b0
|
|
4
|
+
data.tar.gz: e9e8e77e53671ef7e822f2116d32a231ab6f71901de418bdd8643acd46232de5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b03d7602db5e603bd0b0ba03c9031fb93f29bece4d5984a460f0fb4f79b5d7a740de189c4c0d281919650f39f5bf84e4019d8cc5988630114aaec5a59fdab45
|
|
7
|
+
data.tar.gz: 38068b949e79d8c8c26bbfe07f5c75546f4f11964a4ec9259eaab168df458423be6eac7575a070ed7cb91b0a529d3a11d22b6f2d2605decf7ea39d25847ad0c4
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,56 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [4.0.0] - 2026-09-16
|
|
4
|
+
|
|
5
|
+
- [Breaking change] The vocabulary is `company` 3.0: `assigned_to` is `of`, so
|
|
6
|
+
`account.visits.of(technician)` is what `account.visits.assigned_to(technician)` was. The pin
|
|
7
|
+
is `~> 3.0`.
|
|
8
|
+
|
|
9
|
+
- [Feature] `account.windows`, the free time of the location: every stretch nobody is booked
|
|
10
|
+
for, read off `GET /company/schedule_availability/booking_windows`. Housecall Pro works it out
|
|
11
|
+
for itself -- it holds the hours the business keeps, the notice it needs, the padding it
|
|
12
|
+
leaves around a job and the calendar blocked out -- so this asks rather than reckons, and an
|
|
13
|
+
hour held is already taken off.
|
|
14
|
+
|
|
15
|
+
- [Feature] Housecall Pro answers a week as a row of short windows, each open or not. The open
|
|
16
|
+
ones that run together are joined back into the stretch they came from, so a window is as long
|
|
17
|
+
as the pro is free rather than as long as their booking page happens to offer. What to cut it
|
|
18
|
+
into is the caller's, where an offer is being made.
|
|
19
|
+
|
|
20
|
+
- [Note] `start_date`, `show_for_days` and `employee_ids` all narrow this list, so one person's
|
|
21
|
+
week is one request -- unlike `/events`, which accepts the same words and ignores them. An
|
|
22
|
+
account with nothing open answers a bare list rather than the usual object, and both shapes
|
|
23
|
+
are read.
|
|
24
|
+
|
|
25
|
+
## [3.1.0] - 2026-09-16
|
|
26
|
+
|
|
27
|
+
- [Feature] `account.visits` covers the time blocked out around the work, which Housecall Pro
|
|
28
|
+
files as events: a hold, a day off, an hour that is simply not free. It occupies a pro
|
|
29
|
+
exactly as a stop does, and Jobber has answered with it all along, so a schedule read through
|
|
30
|
+
this gem now says the same thing as one read through `jbr`. Each reads as a `Hcp::Occurrence`
|
|
31
|
+
standing on no job and no lead, and a caller that wants what it had before asks `for_jobs`.
|
|
32
|
+
|
|
33
|
+
- [Feature] An event that repeats is stored by Housecall Pro once, as the hour it first takes
|
|
34
|
+
and the iCalendar rule it repeats by, so the hours after that are worked out here -- in the
|
|
35
|
+
time zone the event is kept in, so an hour that holds at ten in the morning still holds at
|
|
36
|
+
ten once the clocks have gone back rather than sliding to nine. `ice_cube` walks the rule;
|
|
37
|
+
it is a new runtime dependency, and the reason not to hand-roll one is that rule, DST and
|
|
38
|
+
month-end clamping together are where a hand-rolled walk quietly goes wrong.
|
|
39
|
+
|
|
40
|
+
- [Feature] An hour answers to the event's ID and the moment it starts -- `evt_1@2026-09-18T14:15:00Z`
|
|
41
|
+
-- there being one ID for the whole rule and nothing else to tell two of its hours apart.
|
|
42
|
+
|
|
43
|
+
- [Note] Housecall Pro narrows events by nothing. `/events` accepts `scheduled_start_min`,
|
|
44
|
+
`scheduled_start_max` and `employee_ids[]` and ignores all three, answering with the same
|
|
45
|
+
total every time, so a week is read by sweeping every page at 200 a page and keeping what the
|
|
46
|
+
week holds. On an account with 2,877 events that is 15 requests, which a nightly walk can
|
|
47
|
+
afford and a caller that only wants the stops of work should not pay: `for_jobs` and
|
|
48
|
+
`for_leads` now leave the sweep unasked.
|
|
49
|
+
|
|
50
|
+
- [Note] An event carries an address but no ID for it, and leaves it empty on all but a
|
|
51
|
+
handful, so `visit.location` is nil on blocked-out time -- as it is in Jobber, where an event
|
|
52
|
+
carries no property either.
|
|
53
|
+
|
|
3
54
|
## [3.0.1] - 2026-09-15
|
|
4
55
|
|
|
5
56
|
- [Fix] `visit.anytime?` answers false on an estimate's slot rather than nil. Housecall Pro
|
data/README.md
CHANGED
|
@@ -96,13 +96,24 @@ account.visits.upcoming(2.weeks).each do |visit|
|
|
|
96
96
|
visit.id, visit.starts_at, visit.ends_at, visit.anytime?
|
|
97
97
|
visit.description # => what the job is called, or nil: an estimate has no words of its own
|
|
98
98
|
visit.location # => where the stop is, whatever it was booked for
|
|
99
|
-
visit.job # => the Hcp::Job the stop belongs to,
|
|
100
|
-
visit.lead # => the Hcp::Estimate it belongs to,
|
|
99
|
+
visit.job # => the Hcp::Job the stop belongs to, nil on an estimate or an event
|
|
100
|
+
visit.lead # => the Hcp::Estimate it belongs to, nil on a job or an event
|
|
101
101
|
visit.technicians # => the Hcp::Technicians the stop is booked for
|
|
102
102
|
end
|
|
103
103
|
```
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
Time blocked out around the work counts as booked too, and Housecall Pro files it as an event:
|
|
106
|
+
a hold, a day off, an hour that is simply not free. An event that repeats is stored once, as the
|
|
107
|
+
hour it first takes and the iCalendar rule it repeats by, so the hours after that are worked out
|
|
108
|
+
here rather than read -- in the time zone the event is kept in, so an hour that holds at ten in
|
|
109
|
+
the morning still holds at ten once the clocks have gone back. Each one reads as a visit
|
|
110
|
+
standing on no job and no lead, and answers to the event's ID and the moment it starts, there
|
|
111
|
+
being one ID for the whole rule.
|
|
112
|
+
|
|
113
|
+
Housecall Pro narrows events by nothing -- it accepts a window and a crew on `/events` and
|
|
114
|
+
ignores both -- so a week of them is read by sweeping every page and keeping what the week
|
|
115
|
+
holds. That is the one expensive part of the list, and the one way to be spared it is to ask
|
|
116
|
+
for the stops of work alone:
|
|
106
117
|
|
|
107
118
|
```ruby
|
|
108
119
|
account.visits.upcoming(2.weeks).for_jobs # => only the appointments, one request
|
|
@@ -136,7 +147,7 @@ One technician's week is the visits in it narrowed to them, which is how a sched
|
|
|
136
147
|
|
|
137
148
|
```ruby
|
|
138
149
|
monday = Date.today.beginning_of_week.in_time_zone
|
|
139
|
-
account.visits.between(monday, monday + 1.week).
|
|
150
|
+
account.visits.between(monday, monday + 1.week).of(technician).each do |visit|
|
|
140
151
|
visit.starts_at, visit.ends_at, visit.job.location.street
|
|
141
152
|
end
|
|
142
153
|
```
|
|
@@ -148,6 +159,24 @@ dispatched to some of its crew or to none of it, so a stop dispatched to nobody
|
|
|
148
159
|
crew's, and the stops the technician is not on are let go once the jobs arrive. Asking for the
|
|
149
160
|
week and asking for the technician narrow the same list, in either order.
|
|
150
161
|
|
|
162
|
+
The other half of that week is the hours they are *not* out, which Housecall Pro works out for
|
|
163
|
+
itself and answers whole:
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
account.windows.between(monday, monday + 1.week).of(technician).each do |window|
|
|
167
|
+
window.starts_at, window.ends_at
|
|
168
|
+
end
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
It holds the hours the business keeps, the notice it needs, the padding it leaves around a job
|
|
172
|
+
and the time blocked out on the calendar, so this asks rather than reckons and the holds are
|
|
173
|
+
already taken off. It answers a week as a row of short windows, each open or not; the open ones
|
|
174
|
+
that run together are one stretch and are joined back into it, so a window is as long as the pro
|
|
175
|
+
is free rather than as long as their booking page happens to offer.
|
|
176
|
+
|
|
177
|
+
`start_date`, `show_for_days` and `employee_ids` all narrow, so a week for one person is the one
|
|
178
|
+
request -- unlike `/events`, which accepts the same words and ignores them.
|
|
179
|
+
|
|
151
180
|
What Housecall Pro schedules elsewhere is still not here: time blocked out on the calendar is
|
|
152
181
|
filed under `/events`, which takes no date and no employee to narrow by, so a week of it cannot
|
|
153
182
|
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
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module Hcp
|
|
2
|
+
# The blocked-out time of one location: the hours Housecall Pro files as events rather than as
|
|
3
|
+
# work. It narrows events by nothing -- neither a window nor a crew, both of which it accepts
|
|
4
|
+
# and ignores -- so the only way to read a week of them is to sweep every page and keep what
|
|
5
|
+
# the week holds. An event that repeats is stored once, as the hour it first takes and the
|
|
6
|
+
# iCalendar rule it repeats by, so every hour after that is worked out here rather than read.
|
|
7
|
+
class Occurrences < Company::Collection
|
|
8
|
+
# How far an open end reaches. A rule that repeats forever has no last hour to walk to, so a
|
|
9
|
+
# caller who named no end gets a year of them, which is a schedule rather than an eternity.
|
|
10
|
+
HORIZON = 1.year
|
|
11
|
+
|
|
12
|
+
# @param client [Client] how to reach Housecall Pro as the location.
|
|
13
|
+
# @param from [Time, nil] the moment the window opens, or nothing for from now on.
|
|
14
|
+
# @param to [Time, nil] the moment the window closes, or nothing for a year of it.
|
|
15
|
+
def initialize(client:, from: nil, to: nil)
|
|
16
|
+
@client = client
|
|
17
|
+
@from = from
|
|
18
|
+
@to = to
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @param from [Time, nil] the moment the window opens, or nothing for from now on.
|
|
22
|
+
# @param to [Time, nil] the moment the window closes, or nothing for a year of it.
|
|
23
|
+
# @return [Occurrences] the same list, narrowed to the hours taken between the two.
|
|
24
|
+
def between(from, to) = self.class.new(client: @client, from: from, to: to)
|
|
25
|
+
|
|
26
|
+
# Nothing is read until the walk starts, and a page only once the one before it runs out.
|
|
27
|
+
# @yield [Occurrence] each stretch blocked out in the window, event by event.
|
|
28
|
+
def each
|
|
29
|
+
events { |event| hours_of(event).each { |node| yield Occurrence.new node: node } }
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def events
|
|
35
|
+
(1..).each do |page|
|
|
36
|
+
body = @client.get 'events', page: page, page_size: Jobs::PAGE
|
|
37
|
+
body.fetch('events').each { |node| yield node }
|
|
38
|
+
break if page >= body.fetch('total_pages')
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def hours_of(event)
|
|
43
|
+
schedule = event.fetch 'schedule'
|
|
44
|
+
first = Time.iso8601 schedule.fetch('start_time')
|
|
45
|
+
length = Time.iso8601(schedule.fetch('end_time')) - first
|
|
46
|
+
starts(event['recurrence_rule'], first, schedule['time_zone']).map do |moment|
|
|
47
|
+
event.merge 'id' => "#{event['id']}@#{moment.utc.iso8601}",
|
|
48
|
+
'start_time' => moment.utc.iso8601, 'end_time' => (moment + length).utc.iso8601
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# An hour is repeated in the time zone the event is kept in rather than in UTC, so a rule
|
|
53
|
+
# that says ten in the morning still says ten once the clocks have gone back.
|
|
54
|
+
def starts(rule, first, zone)
|
|
55
|
+
return [ first ].select { |moment| window.cover? moment } if rule.blank?
|
|
56
|
+
|
|
57
|
+
repeated rule, first.in_time_zone(zone || 'UTC')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def repeated(rule, first)
|
|
61
|
+
schedule = IceCube::Schedule.new first
|
|
62
|
+
schedule.add_recurrence_rule IceCube::Rule.from_ical(rule)
|
|
63
|
+
schedule.occurrences_between opens, closes
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def window = opens..closes
|
|
67
|
+
|
|
68
|
+
def opens = @from || Time.now
|
|
69
|
+
|
|
70
|
+
def closes = @to || opens + HORIZON
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -9,13 +9,16 @@ module Hcp
|
|
|
9
9
|
# @param technician [Company::Technician, nil] whose work to ask for, or nothing for all.
|
|
10
10
|
# @param jobs [Boolean] whether to read the stops of jobs.
|
|
11
11
|
# @param leads [Boolean] whether to read the stops of estimates.
|
|
12
|
-
|
|
12
|
+
# @param events [Boolean] whether to read the hours blocked out around them.
|
|
13
|
+
def initialize(client:, from: nil, to: nil, technician: nil, jobs: true, leads: true,
|
|
14
|
+
events: true)
|
|
13
15
|
@client = client
|
|
14
16
|
@from = from
|
|
15
17
|
@to = to
|
|
16
18
|
@technician = technician
|
|
17
19
|
@jobs = jobs
|
|
18
20
|
@leads = leads
|
|
21
|
+
@events = events
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
# @param from [Time, nil] the moment the window opens, or nothing for every visit there was.
|
|
@@ -28,34 +31,38 @@ module Hcp
|
|
|
28
31
|
# to it, so the stops they are not on are let go once the work comes back.
|
|
29
32
|
# @param technician [Company::Technician] whoever the work is booked for.
|
|
30
33
|
# @return [Company::Selection] the same list, narrowed to the stops they are booked for.
|
|
31
|
-
def
|
|
34
|
+
def of(technician)
|
|
32
35
|
theirs = with technician: technician
|
|
33
36
|
Company::Selection.new(collection: theirs) do |visit|
|
|
34
37
|
visit.technicians.any? { |each| each.id == technician.id }
|
|
35
38
|
end
|
|
36
39
|
end
|
|
37
40
|
|
|
38
|
-
# @return [Visits] the same list, read off the jobs alone: one request rather than
|
|
39
|
-
def for_jobs = with
|
|
41
|
+
# @return [Visits] the same list, read off the jobs alone: one request rather than many.
|
|
42
|
+
def for_jobs = with(leads: false, events: false)
|
|
40
43
|
|
|
41
|
-
# @return [Visits] the same list, read off the estimates alone: one request rather than
|
|
42
|
-
def for_leads = with
|
|
44
|
+
# @return [Visits] the same list, read off the estimates alone: one request rather than many.
|
|
45
|
+
def for_leads = with(jobs: false, events: false)
|
|
43
46
|
|
|
44
47
|
# Work booked across the window carries every stop in it, so each list is read once and
|
|
45
48
|
# what was called off keeps its stops to itself.
|
|
46
|
-
# @yield [Visit] each visit in the window, the
|
|
49
|
+
# @yield [Company::Visit] each visit in the window, the work's stops before the hours
|
|
50
|
+
# blocked out around them.
|
|
47
51
|
def each(&)
|
|
48
52
|
jobs.each { |job| stops job, & } if @jobs
|
|
49
53
|
estimates.each { |estimate| stops estimate, & } if @leads
|
|
54
|
+
occurrences.each(&) if @events
|
|
50
55
|
end
|
|
51
56
|
|
|
52
57
|
private
|
|
53
58
|
|
|
54
59
|
def with(**changed)
|
|
55
60
|
self.class.new(**{ client: @client, from: @from, to: @to, technician: @technician,
|
|
56
|
-
jobs: @jobs, leads: @leads }.merge(changed))
|
|
61
|
+
jobs: @jobs, leads: @leads, events: @events, }.merge(changed))
|
|
57
62
|
end
|
|
58
63
|
|
|
64
|
+
def occurrences = Occurrences.new(client: @client, from: @from, to: @to)
|
|
65
|
+
|
|
59
66
|
def stops(work)
|
|
60
67
|
return if work.canceled?
|
|
61
68
|
|
|
@@ -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
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Hcp
|
|
2
|
+
# One stretch of blocked-out time: what a Housecall Pro event takes, once where the event
|
|
3
|
+
# happens once and once for every hour its rule lands on where it repeats. Housecall Pro files
|
|
4
|
+
# one ID for the whole rule rather than one per hour, so an hour answers to the event's ID and
|
|
5
|
+
# the moment it starts, which is the one thing telling two hours of the same event apart.
|
|
6
|
+
class Occurrence < Company::Visit
|
|
7
|
+
# The node keys Housecall Pro spells otherwise than the vocabulary.
|
|
8
|
+
def self.keys
|
|
9
|
+
{ description: :name, anytime: :all_day, starts_at: :start_time, ends_at: :end_time }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Housecall Pro files an address on an event but no ID for it, and leaves it empty on all
|
|
13
|
+
# but a handful, so there is nothing to file a place under: blocked-out time stands nowhere,
|
|
14
|
+
# as it does in Jobber, where an event carries no property either.
|
|
15
|
+
# @return [nil] nothing, so an hour blocked out is booked at no address.
|
|
16
|
+
def location = nil
|
|
17
|
+
|
|
18
|
+
# @return [Array<Technician>] whoever the hour is blocked out for.
|
|
19
|
+
def technicians = records Technician, :assigned_employees
|
|
20
|
+
end
|
|
21
|
+
end
|
data/lib/hcp/version.rb
CHANGED
data/lib/hcp.rb
CHANGED
|
@@ -7,8 +7,15 @@ require 'time'
|
|
|
7
7
|
# Housecall Pro holds nothing for arrives as readily empty as null, and a query is written the
|
|
8
8
|
# way `to_query` writes one.
|
|
9
9
|
require 'active_support/core_ext/enumerable'
|
|
10
|
+
require 'active_support/core_ext/integer/time'
|
|
11
|
+
require 'active_support/core_ext/numeric/time'
|
|
10
12
|
require 'active_support/core_ext/object/blank'
|
|
11
13
|
require 'active_support/core_ext/object/to_query'
|
|
14
|
+
require 'active_support/core_ext/time/zones'
|
|
15
|
+
|
|
16
|
+
# An event repeats by an iCalendar rule, which is walked rather than parsed here: an hour that
|
|
17
|
+
# holds at ten in the morning either side of the clocks going back is not worth rewriting.
|
|
18
|
+
require 'ice_cube'
|
|
12
19
|
|
|
13
20
|
# The vocabulary the account answers in.
|
|
14
21
|
require 'company'
|
|
@@ -31,11 +38,14 @@ require 'hcp/resources/location'
|
|
|
31
38
|
require 'hcp/resources/line'
|
|
32
39
|
require 'hcp/resources/quote'
|
|
33
40
|
require 'hcp/resources/visit'
|
|
41
|
+
require 'hcp/resources/occurrence'
|
|
34
42
|
require 'hcp/resources/job'
|
|
35
43
|
require 'hcp/resources/estimate'
|
|
36
44
|
require 'hcp/collections/jobs'
|
|
37
45
|
require 'hcp/collections/estimates'
|
|
46
|
+
require 'hcp/collections/occurrences'
|
|
38
47
|
require 'hcp/collections/visits'
|
|
48
|
+
require 'hcp/collections/windows'
|
|
39
49
|
require 'hcp/collections/technicians'
|
|
40
50
|
require 'hcp/account'
|
|
41
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.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Claudio Baccigalupo
|
|
@@ -29,14 +29,28 @@ dependencies:
|
|
|
29
29
|
requirements:
|
|
30
30
|
- - "~>"
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
|
-
version: '
|
|
32
|
+
version: '3.0'
|
|
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.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: ice_cube
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: minitest
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -140,8 +154,10 @@ files:
|
|
|
140
154
|
- lib/hcp/collections/estimates.rb
|
|
141
155
|
- lib/hcp/collections/jobs.rb
|
|
142
156
|
- lib/hcp/collections/leads.rb
|
|
157
|
+
- lib/hcp/collections/occurrences.rb
|
|
143
158
|
- lib/hcp/collections/technicians.rb
|
|
144
159
|
- lib/hcp/collections/visits.rb
|
|
160
|
+
- lib/hcp/collections/windows.rb
|
|
145
161
|
- lib/hcp/error.rb
|
|
146
162
|
- lib/hcp/errors/throttled.rb
|
|
147
163
|
- lib/hcp/event.rb
|
|
@@ -152,6 +168,7 @@ files:
|
|
|
152
168
|
- lib/hcp/resources/lead.rb
|
|
153
169
|
- lib/hcp/resources/line.rb
|
|
154
170
|
- lib/hcp/resources/location.rb
|
|
171
|
+
- lib/hcp/resources/occurrence.rb
|
|
155
172
|
- lib/hcp/resources/quote.rb
|
|
156
173
|
- lib/hcp/resources/technician.rb
|
|
157
174
|
- lib/hcp/resources/visit.rb
|