jbr 3.13.0 → 5.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 (79) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +5 -0
  3. data/CHANGELOG.md +139 -0
  4. data/README.md +302 -167
  5. data/lib/graphql/client.rb +2 -6
  6. data/lib/graphql/error.rb +10 -0
  7. data/lib/graphql/throttled.rb +0 -9
  8. data/lib/jbr/account.rb +48 -15
  9. data/lib/jbr/authorizing.rb +43 -0
  10. data/lib/jbr/booking.rb +56 -0
  11. data/lib/jbr/collection.rb +7 -0
  12. data/lib/jbr/collections/customers.rb +54 -0
  13. data/lib/jbr/collections/invoices.rb +18 -0
  14. data/lib/jbr/{jobs.rb → collections/jobs.rb} +8 -8
  15. data/lib/jbr/collections/leads.rb +30 -0
  16. data/lib/jbr/collections/locations.rb +47 -0
  17. data/lib/jbr/collections/quotes.rb +18 -0
  18. data/lib/jbr/collections/technicians.rb +17 -0
  19. data/lib/jbr/collections/visits.rb +61 -0
  20. data/lib/jbr/error.rb +1 -1
  21. data/lib/jbr/errors/throttled.rb +6 -0
  22. data/lib/jbr/includable.rb +10 -8
  23. data/lib/jbr/listable.rb +17 -35
  24. data/lib/jbr/logger.rb +13 -0
  25. data/lib/jbr/mock/account.rb +28 -7
  26. data/lib/jbr/mock/invoice.rb +7 -11
  27. data/lib/jbr/mock/invoices.rb +7 -0
  28. data/lib/jbr/mock/jobs.rb +10 -6
  29. data/lib/jbr/mock/leads.rb +7 -0
  30. data/lib/jbr/mock/quote.rb +3 -8
  31. data/lib/jbr/mock/quotes.rb +7 -0
  32. data/lib/jbr/mock/technicians.rb +12 -0
  33. data/lib/jbr/mock/visit.rb +9 -16
  34. data/lib/jbr/mock/visits.rb +39 -6
  35. data/lib/jbr/mock.rb +17 -4
  36. data/lib/jbr/phone.rb +5 -8
  37. data/lib/jbr/querying.rb +68 -0
  38. data/lib/jbr/reader.rb +9 -0
  39. data/lib/jbr/reading.rb +15 -0
  40. data/lib/jbr/refreshing.rb +0 -8
  41. data/lib/jbr/resources/customer.rb +23 -0
  42. data/lib/jbr/resources/invoice.rb +29 -0
  43. data/lib/jbr/resources/job.rb +20 -0
  44. data/lib/jbr/resources/lead.rb +7 -0
  45. data/lib/jbr/resources/line.rb +15 -0
  46. data/lib/jbr/resources/location.rb +31 -0
  47. data/lib/jbr/resources/quote.rb +15 -0
  48. data/lib/jbr/resources/technician.rb +19 -0
  49. data/lib/jbr/resources/visit.rb +30 -0
  50. data/lib/jbr/scheduled.rb +36 -0
  51. data/lib/jbr/version.rb +1 -1
  52. data/lib/jbr.rb +45 -45
  53. metadata +46 -28
  54. data/lib/jbr/client.rb +0 -98
  55. data/lib/jbr/cliental.rb +0 -14
  56. data/lib/jbr/invoice.rb +0 -43
  57. data/lib/jbr/itemized.rb +0 -9
  58. data/lib/jbr/job.rb +0 -40
  59. data/lib/jbr/line_item.rb +0 -44
  60. data/lib/jbr/mock/client.rb +0 -17
  61. data/lib/jbr/mock/job.rb +0 -31
  62. data/lib/jbr/mock/line_item.rb +0 -16
  63. data/lib/jbr/mock/oauth.rb +0 -22
  64. data/lib/jbr/mock/property.rb +0 -19
  65. data/lib/jbr/mock/request.rb +0 -12
  66. data/lib/jbr/mock/url.rb +0 -9
  67. data/lib/jbr/mocking.rb +0 -30
  68. data/lib/jbr/named.rb +0 -9
  69. data/lib/jbr/oauth.rb +0 -94
  70. data/lib/jbr/properted.rb +0 -17
  71. data/lib/jbr/property.rb +0 -93
  72. data/lib/jbr/quote.rb +0 -25
  73. data/lib/jbr/request.rb +0 -37
  74. data/lib/jbr/resource.rb +0 -26
  75. data/lib/jbr/retriable.rb +0 -20
  76. data/lib/jbr/url.rb +0 -16
  77. data/lib/jbr/visit.rb +0 -24
  78. data/lib/jbr/visits.rb +0 -36
  79. /data/lib/jbr/{refused.rb → errors/refused.rb} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: eff06d450e1e4c357e208ca25d49eec46229009623dac5fab0d2316784b10ddc
4
- data.tar.gz: 85cd26058f5eec17b84f143e61c1d366f2ac7702d44d0cd8b6fb80694041f6d3
3
+ metadata.gz: a01ddd16fc41518ff9cc7d18b61bdc2c0d9e419959c01fc7dd822473de08bade
4
+ data.tar.gz: 3d60fe2c06fd056d0e61d83bca1e40ff6f61954994a061e858a60422b6546e12
5
5
  SHA512:
6
- metadata.gz: 8cef0ee9b3d02ec476d890d6d34367d896e77ab374fa75f923d77fb6535415c3958c43aefea6ee7512e09606eb75cf92583c8c0ddf79e2ccf7d111b49a858af4
7
- data.tar.gz: 1bdaf92404d72cf9ad5abaf26711e9cba47fa0054e1ab1e4b53114bfe0d157ed073959783ef8a23cdf101ac6ea95da798ca0e4723ffb06d907600b3857018aef
6
+ metadata.gz: 71b4e3faacce7acb0ea6d5a3eab5f1beea5f4534a416a16ed663d434299551b94375e3522f0152e6627a22340a7c098eaf464fdcec0012688fa07b0def98c9e8
7
+ data.tar.gz: 99828c97615dd6357dd58f167ec05338b6595cbcf5206cbad1088f9e3d3a95665f08698add77b3aeb854d4e10c9a664f8b77467ac064c680ecdd409bc763e96f
data/.rubocop.yml CHANGED
@@ -57,3 +57,8 @@ Layout/LineLength:
57
57
  # lines per site, so the cop is declined and the rule held in review.
58
58
  Style/MethodCallWithArgsParentheses:
59
59
  Enabled: false
60
+
61
+ # The vocabulary names a keyword for every platform, and one a platform has no field for is
62
+ # taken and dropped rather than left out of the signature
63
+ Lint/UnusedMethodArgument:
64
+ AllowUnusedKeywordArguments: true
data/CHANGELOG.md CHANGED
@@ -1,3 +1,142 @@
1
+ ## [Unreleased]
2
+
3
+ ## [5.0.0] - 2026-09-15
4
+
5
+ - [Feature] A scope the app was never granted no longer raises. Jobber answers `An object of
6
+ type User was hidden due to permissions` and hides the object rather than the query, so
7
+ whatever came back beside it is kept: `account.technicians` answers `[]`, and a week asked
8
+ for with `includes(:technicians)` answers the week with nobody on it. `Jbr.logger` takes the
9
+ line saying which type to tick the scope for -- standard error until an app points it
10
+ somewhere better. Jobber codes this refusal not at all, so the words are the only signal, and
11
+ only those words are carried on from: anything else raises, an empty list being a poor place
12
+ to hide a fault.
13
+
14
+ - [Breaking change] `account.visits.find` is gone. A visit arrives from the walk carrying
15
+ everything it reads, so looking one up again was a request spent to learn nothing -- and it
16
+ answered a job's stop alone, quietly finding nothing for an assessment, an event or a task.
17
+ `Enumerable#find` is what the name means on a list of visits now.
18
+
19
+ - [Feature] `visit.location`, where a stop is, through `includes(:location)`. Jobber hangs the
20
+ property off each kind of scheduled item rather than off what they share, so it is selected
21
+ inside the fragments; a caller reading a schedule no longer reaches through `visit.job` for an
22
+ address, which a stop booked against a lead never had.
23
+
24
+ - [Fix] A window asks for unassigned work and not for unscheduled. `schedulingAspects: [ALL]`
25
+ means both, so a windowed list came back carrying requests nobody had booked -- no start, no
26
+ end, in no window. `includeUnassigned: true` with `includeUnscheduled: false` asks for exactly
27
+ the half that is booked, verified against an account holding plenty of each.
28
+
29
+ - [Breaking change] An event or a task is a visit. They occupy a pro exactly as a job's stop does
30
+ and were being read past unread. Jobber's scheduled item has exactly four kinds -- visit,
31
+ assessment, event, task -- so nothing is filtered out now; a reminder is named by the filter's
32
+ enum but is not a scheduled item and cannot come back. `visit.job` and `visit.lead` are nil on
33
+ an event, and `visit.location` may be: Jobber gives an event a property, and not always.
34
+
35
+ - [Breaking change] The vocabulary is `company` 2.1: a `Company::Selection` takes its rule as a
36
+ block, a visit answers a lead as well as a job, and it says where it is. The pin is `~> 2.1`.
37
+
38
+ - [Feature] The README says which Developer Center object each reader needs ticked. Jobber
39
+ files a visit under Scheduled Items -- one object covering visits, assessments, tasks and
40
+ calendar events -- and there is no Visits scope of its own.
41
+
42
+ - [Feature] `account.visits.create` books a stop to go and look at work nobody has priced. It
43
+ takes the words `leads.create` takes plus `starts_at:`, `ends_at:` and `technicians:`, opens
44
+ the client and the property where Jobber has none, and files all of it in the one
45
+ `requestCreate`: Jobber hangs the assessment off the request it opens with it. What comes
46
+ back is the assessment Jobber stored, naming the request as its lead. Jobber has no source
47
+ for a request, so `source:` is dropped, and `ends_at:` may be nil for a stop booked to a day.
48
+
49
+ A mocked booking refuses a bare `Time` the same way, so a suite cannot pass on one Jobber
50
+ would not take. `starts_at:` must know its zone. Jobber takes a date, a local time and the zone they are in,
51
+ not a moment in UTC, and a bare `Time` names an offset rather than a zone -- so one is
52
+ refused, with `Jbr::Error`, before a client is opened.
53
+
54
+ - [Breaking change] `account.visits` is every stop booked, not only a job's. Jobber calls the
55
+ stop booked to look at work before there is a job an assessment and hangs it off the request,
56
+ so `visit.lead` answers that request and `visit.job` is nil there; `for_jobs` and `for_leads`
57
+ narrow to one kind, by Jobber rather than here. The list is read from `scheduledItems` rather
58
+ than `visits`, which has three consequences worth reading twice:
59
+
60
+ - **A schedule is read by the window.** `scheduledItems` takes a required `occursWithin`, so
61
+ `account.visits` with nothing narrowing it raises `Jbr::Error` where it used to walk every
62
+ visit there was.
63
+ - **An open end reaches a year.** `occursWithin` takes two moments and no nil, so
64
+ `upcoming` and `past` with no duration are bounded at a year rather than left open.
65
+ - **Unassigned work is in.** `scheduledItems` answers assigned work only unless told
66
+ otherwise, so `includeUnassigned: true` is always sent. A list that did not send it would
67
+ quietly drop every stop nobody is on yet.
68
+
69
+ `account.visits.find` still answers a stop of a job alone: Jobber files an assessment under a
70
+ lookup of its own and this gem does not reach for it. Events, tasks and reminders share the
71
+ list and are read past, Jobber's filter taking one kind and not two.
72
+
73
+ - [Feature] `account.technicians` walks the account's users a page at a time, each a
74
+ `Jbr::Technician` reading `id`, `name` and `surname` off the `name` node Jobber answers a
75
+ user with. Reading one needs the Users scope, which an app granted before this
76
+ release does not have: Jobber refuses a query selecting a user outright rather than leaving
77
+ the field empty, so an app that asks for a technician re-authorizes first.
78
+
79
+ - [Feature] `visit.technicians` is whoever a visit is booked for, and `includes(:technicians)`
80
+ is what asks for them -- nothing brings them back unbidden, for the scope above and because
81
+ Jobber prices them on every row that carries them.
82
+
83
+ - [Feature] `account.visits.between(from, to).assigned_to(technician)` is one technician's
84
+ week. `VisitFilterAttributes` takes an `assignedTo`, so the technician joins the window in
85
+ the one filter Jobber is sent: nobody else's visits are answered, paged or paid for, and the
86
+ crew is not read unless `includes(:technicians)` asks, which means `assigned_to` needs no
87
+ Users scope. Both narrowings land in the same filter, so either order asks the same
88
+ thing. `Jbr.mock.technicians` mocks the crew, and a mocked visit takes a `technicians:` of
89
+ its own.
90
+
91
+ ## [4.0.0] - 2026-09-09
92
+
93
+ - [Breaking change] `Jbr::Account` is the gateway, and reads in the vocabulary the `company` gem
94
+ names. It replaces `Jbr::OAuth` and the `Jbr.oauth_for`, `Jbr.create_oauth` and
95
+ `Jbr.oauth_url_for` entry points: `Jbr::Account.new credentials`, `Jbr::Account.create code:,
96
+ redirect_uri:`, `Jbr::Account.url_for redirect_uri:, state:`, and `Jbr::Account.client_secret`.
97
+ `#account` is `#business`, answering a `Company::Business` whose `phone` is the ten digits to
98
+ dial rather than the string Jobber holds
99
+ - [Breaking change] `#requests` is `#leads`, and `leads.create` takes its keywords by name
100
+ as the vocabulary names them (`name:`, `surname:`, `phone:`, `email:`, `address:`,
101
+ `description:` for the title, `notes:` for the instructions, and a `source:` Jobber has no
102
+ field for) and
103
+ answers a `Jbr::Lead` with `id` and `customer` rather than the collection that filed it.
104
+ `Quote#request_id` is `#lead`, a `Jbr::Lead`; `Invoice#job_id` is `#job`, a `Jbr::Job`;
105
+ `Invoice#total`, `#issued_at` and `#completed_at` are `#amount`, a `BigDecimal`, and
106
+ `#fulfilled_at`, the moment the work was finished or the bill issued where the work never was
107
+ - [Breaking change] A job reads `#amount` as a `BigDecimal` where it read `#total` as a Float,
108
+ answers the quote it was won with as `#quote` -- a `Jbr::Quote` with `id` and `amount`,
109
+ nil where there was none -- in place of `#quote_id` and `#quote_total`, its title as
110
+ `#description` and its instructions as `#notes`. `#summary` is gone with `#name`: a caller sums a job up from its lines and
111
+ its description. `Job#title`, `#status` and `#client`, `Visit#title`, `#name`, `#job_id`
112
+ and `#client`, are gone, and `Visit#client_confirmed?` is `#confirmed?`, `#all_day?` is `#anytime?`
113
+ - [Breaking change] `includes` takes `:lines`, `:location` and `location: :customer` where it
114
+ took `:line_items`, `:property`, `:client` and `property: :client`, and only on jobs: a visit
115
+ happens where its job does, so it answers `#job` and no `#location` or `includes` of its own. A
116
+ job answers `#lines` and `#location`, and a location `#customer`. `Location#address` and `#state` are gone,
117
+ as are `Customer#first_name` and `#company_name`: `#name` answers the first name, or the
118
+ business's name where a person has none. `Jbr::LineItem`, `Jbr::Property`, `Jbr::Client` and
119
+ `Jbr::Request` are `Jbr::Line`, `Jbr::Location`, `Jbr::Customer` and `Jbr::Lead`
120
+ - [Breaking change] `Jbr.mock` takes `business` and `lead` where it took `account` and `request`,
121
+ and every slot reads by the vocabulary's keys: `description`, `amount`, `quote`,
122
+ `lines`, `location`, `customer`, `confirmed`, `lead`, `job`. A mock collection
123
+ answers the vocabulary's own kinds built from those hashes, so `Jbr::Mock::Job` and its
124
+ siblings are gone; `Jbr.mock = nil` hands the accounts back to Jobber
125
+ - [Breaking change] `Jbr::Retriable` is `Jbr::Throttled`, a `Company::Throttled` rather than a
126
+ `Jbr::Error`, so one rescue retries a refusal for rate from any platform; its `#cost`,
127
+ `#available`, `#maximum` and `#restore_rate` are gone and the numbers stay in the message.
128
+ `Jbr::Error` descends from `Company::Error`
129
+ - [Fix] An `includes` chained after `past` or `upcoming` keeps the window it was asked on. It
130
+ used to rebuild the list without the filter, so `account.jobs.past(1.year).includes(:lines)`
131
+ walked every job the account ever had
132
+ - [Fix] A mutation Jobber takes but will not act on raises `Jbr::Error` with the reasons Jobber
133
+ gave. It answers those with a 200 and the messages under the mutation's own `userErrors`,
134
+ which nothing read, so a client Jobber refused to open came back with a nil ID and the
135
+ request filed against nobody
136
+ - [Change] Every selection Jobber is asked for is generated from the keys the vocabulary reads
137
+ wherever Jobber's shape is flat, so a kind gaining a reader asks for it without a query
138
+ written by hand
139
+
1
140
  ## [3.13.0] - 2026-08-31
2
141
 
3
142
  - [New] `Jbr::LineItem#id`, `#description` and `#amount`, beside the `#quantity` and `#name` a