jbr 3.12.0 → 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/.rubocop.yml +5 -0
- data/CHANGELOG.md +59 -0
- data/README.md +155 -165
- data/lib/graphql/client.rb +2 -6
- data/lib/graphql/throttled.rb +0 -9
- data/lib/jbr/account.rb +44 -15
- data/lib/jbr/authorizing.rb +43 -0
- data/lib/jbr/collection.rb +14 -0
- data/lib/jbr/collections/customers.rb +54 -0
- data/lib/jbr/collections/invoices.rb +18 -0
- data/lib/jbr/{jobs.rb → collections/jobs.rb} +6 -6
- data/lib/jbr/collections/leads.rb +30 -0
- data/lib/jbr/collections/locations.rb +47 -0
- data/lib/jbr/collections/quotes.rb +18 -0
- data/lib/jbr/{visits.rb → collections/visits.rb} +8 -8
- data/lib/jbr/error.rb +1 -1
- data/lib/jbr/errors/throttled.rb +6 -0
- data/lib/jbr/includable.rb +6 -8
- data/lib/jbr/listable.rb +12 -34
- data/lib/jbr/mock/account.rb +25 -7
- data/lib/jbr/mock/invoice.rb +7 -11
- data/lib/jbr/mock/invoices.rb +7 -0
- data/lib/jbr/mock/jobs.rb +10 -6
- data/lib/jbr/mock/leads.rb +7 -0
- data/lib/jbr/mock/quote.rb +3 -8
- data/lib/jbr/mock/quotes.rb +7 -0
- data/lib/jbr/mock/visit.rb +3 -19
- data/lib/jbr/mock/visits.rb +9 -5
- data/lib/jbr/mock.rb +16 -3
- data/lib/jbr/phone.rb +5 -8
- data/lib/jbr/querying.rb +57 -0
- data/lib/jbr/reader.rb +9 -0
- data/lib/jbr/refreshing.rb +0 -8
- data/lib/jbr/resources/customer.rb +23 -0
- data/lib/jbr/resources/invoice.rb +29 -0
- data/lib/jbr/resources/job.rb +20 -0
- data/lib/jbr/resources/lead.rb +7 -0
- data/lib/jbr/resources/line.rb +15 -0
- data/lib/jbr/resources/location.rb +31 -0
- data/lib/jbr/resources/quote.rb +15 -0
- data/lib/jbr/resources/visit.rb +17 -0
- data/lib/jbr/version.rb +1 -1
- data/lib/jbr.rb +37 -46
- metadata +39 -28
- data/lib/jbr/client.rb +0 -98
- data/lib/jbr/cliental.rb +0 -14
- data/lib/jbr/invoice.rb +0 -43
- data/lib/jbr/itemized.rb +0 -9
- data/lib/jbr/job.rb +0 -40
- data/lib/jbr/line_item.rb +0 -36
- data/lib/jbr/mock/client.rb +0 -17
- data/lib/jbr/mock/job.rb +0 -31
- data/lib/jbr/mock/line_item.rb +0 -10
- data/lib/jbr/mock/oauth.rb +0 -22
- data/lib/jbr/mock/property.rb +0 -19
- data/lib/jbr/mock/request.rb +0 -12
- data/lib/jbr/mock/url.rb +0 -9
- data/lib/jbr/mocking.rb +0 -30
- data/lib/jbr/named.rb +0 -9
- data/lib/jbr/oauth.rb +0 -94
- data/lib/jbr/properted.rb +0 -17
- data/lib/jbr/property.rb +0 -93
- data/lib/jbr/quote.rb +0 -25
- data/lib/jbr/request.rb +0 -37
- data/lib/jbr/resource.rb +0 -26
- data/lib/jbr/retriable.rb +0 -20
- data/lib/jbr/url.rb +0 -16
- data/lib/jbr/visit.rb +0 -24
- /data/lib/jbr/{refused.rb → errors/refused.rb} +0 -0
data/lib/jbr/mock/visit.rb
CHANGED
|
@@ -1,23 +1,7 @@
|
|
|
1
1
|
module Jbr
|
|
2
|
-
# A visit
|
|
2
|
+
# A visit an app under test listed, keyed by the vocabulary's names rather than Jobber's.
|
|
3
3
|
class Mock::Visit < Visit
|
|
4
|
-
#
|
|
5
|
-
def
|
|
6
|
-
|
|
7
|
-
def title = @node[:title]
|
|
8
|
-
|
|
9
|
-
def job_id = @node[:job_id]
|
|
10
|
-
|
|
11
|
-
def client = Mock::Client.new(node: @node.fetch(:client, {}))
|
|
12
|
-
|
|
13
|
-
def property = Mock::Property.new(node: @node.fetch(:property, {}))
|
|
14
|
-
|
|
15
|
-
def all_day? = @node[:all_day]
|
|
16
|
-
|
|
17
|
-
def client_confirmed? = @node[:client_confirmed]
|
|
18
|
-
|
|
19
|
-
def starts_at = @node[:starts_at]
|
|
20
|
-
|
|
21
|
-
def ends_at = @node[:ends_at]
|
|
4
|
+
# The mock spells every key as the reader is named.
|
|
5
|
+
def self.keys = {}
|
|
22
6
|
end
|
|
23
7
|
end
|
data/lib/jbr/mock/visits.rb
CHANGED
|
@@ -2,17 +2,21 @@ module Jbr
|
|
|
2
2
|
# The visits an app under test asked {Jbr.mock} to answer with. Only the walk is mocked:
|
|
3
3
|
# narrowing a list, and reading it as records or as IDs, is the same code a real one runs.
|
|
4
4
|
class Mock::Visits < Visits
|
|
5
|
-
# @
|
|
6
|
-
|
|
5
|
+
# @param id [String] ID the app filed the visit under.
|
|
6
|
+
# @return [Mock::Visit, nil] visit the app listed under that ID, nil where it listed none.
|
|
7
|
+
def find(id)
|
|
8
|
+
node = Jbr.mock.visits.to_a.find { |visit| visit[:id] == id }
|
|
9
|
+
Mock::Visit.new node: node if node
|
|
10
|
+
end
|
|
7
11
|
|
|
8
12
|
private
|
|
9
13
|
|
|
10
14
|
def walk(_statement)
|
|
11
|
-
Enumerator.new
|
|
15
|
+
Enumerator.new do |yielder|
|
|
16
|
+
selected.each { |node| yielder << Mock::Visit.new(node: node) }
|
|
17
|
+
end
|
|
12
18
|
end
|
|
13
19
|
|
|
14
20
|
def selected = Jbr.mock.visits.select { |visit| scheduled? visit[:starts_at] }
|
|
15
|
-
|
|
16
|
-
def listed(id) = Jbr.mock.visits.to_a.find { |visit| visit[:id] == id }.to_h
|
|
17
21
|
end
|
|
18
22
|
end
|
data/lib/jbr/mock.rb
CHANGED
|
@@ -1,8 +1,21 @@
|
|
|
1
1
|
module Jbr
|
|
2
|
-
# What an app under test wants Jobber to answer.
|
|
2
|
+
# What an app under test wants Jobber to answer, by the vocabulary's names.
|
|
3
3
|
class Mock
|
|
4
|
-
# The canned answers, each read by the matching
|
|
5
|
-
attr_accessor :
|
|
4
|
+
# The canned answers, each read by the matching mock collection.
|
|
5
|
+
attr_accessor :business, :quote, :job, :invoice, :lead, :jobs, :visits, :oauth_url,
|
|
6
6
|
:oauth_error
|
|
7
7
|
end
|
|
8
|
+
|
|
9
|
+
class << self
|
|
10
|
+
# Touching it once turns every account over to its mock counterpart.
|
|
11
|
+
# @return [Mock] answers this process gives, created on first use.
|
|
12
|
+
def mock = @mock ||= Mock.new
|
|
13
|
+
|
|
14
|
+
# @return [Boolean] whether an app under test has switched Jobber off.
|
|
15
|
+
def mocked? = !@mock.nil?
|
|
16
|
+
|
|
17
|
+
# Set to nil, hands the accounts back to Jobber.
|
|
18
|
+
# @return [Mock, nil] answers this process gives.
|
|
19
|
+
attr_writer :mock
|
|
20
|
+
end
|
|
8
21
|
end
|
data/lib/jbr/phone.rb
CHANGED
|
@@ -4,20 +4,17 @@ module Jbr
|
|
|
4
4
|
# What Jobber is asked for wherever a client is read: the number, and what ranks it.
|
|
5
5
|
SELECTION = 'phones { normalizedPhoneNumber smsAllowed primary }'
|
|
6
6
|
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
#
|
|
11
|
-
# @param phones [Array<Hash>, nil] the numbers as Jobber answered them.
|
|
12
|
-
# @return [String, nil] the ten digits to call, or nil where none can be dialed.
|
|
7
|
+
# The primary number first, then one that takes texts, then the rest as filed; the first
|
|
8
|
+
# of them that can be dialed is the one.
|
|
9
|
+
# @param phones [Array<Hash>, nil] numbers as Jobber answered them.
|
|
10
|
+
# @return [String, nil] ten digits to call, or nil where none can be dialed.
|
|
13
11
|
def self.from(phones)
|
|
14
12
|
# The index breaks a tie because sort_by does not: two equally ranked numbers would
|
|
15
13
|
# otherwise swap between runs, and the client would answer a different phone each time.
|
|
16
14
|
ranked = Array(phones).each_with_index.sort_by do |phone, index|
|
|
17
15
|
[ phone['primary'] ? 0 : 1, phone['smsAllowed'] ? 0 : 1, index ]
|
|
18
16
|
end
|
|
19
|
-
|
|
20
|
-
numbers.filter_map { |number| number[NORTH_AMERICAN, 1] }.first
|
|
17
|
+
ranked.lazy.filter_map { |phone, _| Company::Phone.from phone['normalizedPhoneNumber'] }.first
|
|
21
18
|
end
|
|
22
19
|
end
|
|
23
20
|
end
|
data/lib/jbr/querying.rb
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# What credentials do with a statement: post it to Jobber, refresh a stale token once, and
|
|
3
|
+
# raise every refusal as a {Error} so a caller rescues one class.
|
|
4
|
+
module Querying
|
|
5
|
+
# Where every query and mutation is posted.
|
|
6
|
+
ENDPOINT = 'https://api.getjobber.com/api/graphql'
|
|
7
|
+
|
|
8
|
+
# The version of the schema every statement is written against.
|
|
9
|
+
HEADERS = { 'X-JOBBER-GRAPHQL-VERSION' => '2026-04-22' }
|
|
10
|
+
|
|
11
|
+
# The mutation that revokes the app on the account.
|
|
12
|
+
DISCONNECT = <<~GRAPHQL
|
|
13
|
+
mutation Disconnect {
|
|
14
|
+
appDisconnect {
|
|
15
|
+
app { name author }
|
|
16
|
+
userErrors { message }
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
GRAPHQL
|
|
20
|
+
|
|
21
|
+
# Nothing here ever sleeps: where Jobber holds the app to a limit it says so, and a caller
|
|
22
|
+
# asking from a background job has a queue that will bring the whole job back later.
|
|
23
|
+
# @param statement [String] query or mutation to run.
|
|
24
|
+
# @param variables [Hash] what the statement takes.
|
|
25
|
+
# @return [Hash] data Jobber answered, or empty when the credentials are dead.
|
|
26
|
+
# @raise [Throttled] where Jobber refused the statement for what it costs.
|
|
27
|
+
# @raise [Error] where Jobber refused the statement, or took a mutation and would not act.
|
|
28
|
+
def query(statement, variables: {})
|
|
29
|
+
data = client.query statement, variables: variables
|
|
30
|
+
refusals = user_errors(data).map { |error| error['message'] }
|
|
31
|
+
raise Error, refusals.join('; ') if refusals.any?
|
|
32
|
+
|
|
33
|
+
data
|
|
34
|
+
rescue GraphQL::Unauthorized
|
|
35
|
+
refresh ? retry : {}
|
|
36
|
+
rescue GraphQL::Throttled => error
|
|
37
|
+
raise Throttled, error.message
|
|
38
|
+
rescue GraphQL::Error => error
|
|
39
|
+
raise Error, error.message
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Revoke the credentials on the account. Dead ones have nothing left to revoke.
|
|
43
|
+
def delete
|
|
44
|
+
client.query DISCONNECT
|
|
45
|
+
rescue GraphQL::Unauthorized
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def client = GraphQL::Client.new endpoint: ENDPOINT, token: @access_token, headers: HEADERS
|
|
51
|
+
|
|
52
|
+
def user_errors(data)
|
|
53
|
+
fields = data.each_value.select { |field| field.is_a? Hash }
|
|
54
|
+
fields.flat_map { |field| Array(field['userErrors']) }
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
data/lib/jbr/reader.rb
ADDED
data/lib/jbr/refreshing.rb
CHANGED
|
@@ -5,18 +5,12 @@ module Jbr
|
|
|
5
5
|
module Refreshing
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
-
# With no store there is nobody to compare against: refresh, and leave what we end up
|
|
9
|
-
# holding for the caller to persist.
|
|
10
8
|
def refresh
|
|
11
9
|
return exchange unless @store
|
|
12
10
|
|
|
13
11
|
@store.exclusively { |stored| renew stored }
|
|
14
12
|
end
|
|
15
13
|
|
|
16
|
-
# Under the store's lock, holding what it says right now. A token that is no longer the one
|
|
17
|
-
# we tried is one somebody else has already replaced, and adopting it asks Jobber nothing —
|
|
18
|
-
# which is what keeps a queue of workers from refreshing a hundred times over, each with a
|
|
19
|
-
# refresh token the first of them has already spent.
|
|
20
14
|
def renew(stored)
|
|
21
15
|
return adopt stored if stored[:access_token] != @access_token
|
|
22
16
|
|
|
@@ -37,8 +31,6 @@ module Jbr
|
|
|
37
31
|
true
|
|
38
32
|
end
|
|
39
33
|
|
|
40
|
-
# Refused while holding the freshest refresh token there is, so the grant itself is dead
|
|
41
|
-
# rather than our copy being behind.
|
|
42
34
|
def refused
|
|
43
35
|
@invalid_at = Time.now
|
|
44
36
|
@store&.write self
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# A person the business works for, who Jobber calls a client.
|
|
3
|
+
class Customer < Company::Customer
|
|
4
|
+
# The node keys Jobber spells otherwise than the vocabulary.
|
|
5
|
+
def self.keys = { name: :firstName, surname: :lastName }
|
|
6
|
+
|
|
7
|
+
# What Jobber calls each flat field of a client. The phones nest: see {Phone}.
|
|
8
|
+
FIELDS = 'id firstName lastName companyName email'
|
|
9
|
+
|
|
10
|
+
# What to ask for wherever a record names its client.
|
|
11
|
+
SELECTION = "client { #{FIELDS} #{Phone::SELECTION} }"
|
|
12
|
+
|
|
13
|
+
# Jobber files nobody without one name or the other, so there is always one to call them.
|
|
14
|
+
# @return [String, nil] first name, or the business's name where a person has none.
|
|
15
|
+
def name = super.presence || attribute(:companyName).presence
|
|
16
|
+
|
|
17
|
+
# @return [String, nil] ten digits they are reached on, nil where none can be dialed.
|
|
18
|
+
def phone = Phone.from @node[:phones]
|
|
19
|
+
|
|
20
|
+
# @return [Array<Location>] places on their file, where the query asked for them.
|
|
21
|
+
def locations = @node.dig(:clientProperties, :nodes).to_a.map { |node| Location.new node: node }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# A bill the business issued for finished work, which Jobber alone answers for.
|
|
3
|
+
class Invoice < Company::Resource
|
|
4
|
+
# What every invoice reads.
|
|
5
|
+
def self.attributes = %i[id amount completed_at issued_at]
|
|
6
|
+
|
|
7
|
+
# The node keys Jobber spells otherwise than the readers.
|
|
8
|
+
def self.keys = { amount: :total, issued_at: :issuedDate }
|
|
9
|
+
|
|
10
|
+
# Jobber lists the jobs an invoice bills, and one it bills one.
|
|
11
|
+
# @return [Job, nil] job the invoice bills, where Jobber listed one.
|
|
12
|
+
def job
|
|
13
|
+
node = @node.dig :jobs, :nodes, 0
|
|
14
|
+
Job.new node: node if node
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [BigDecimal, nil] what the invoice comes to, in dollars.
|
|
18
|
+
def amount = decimal :amount
|
|
19
|
+
|
|
20
|
+
# @return [Time, nil] moment the billed work was finished, or the bill issued where undated.
|
|
21
|
+
def fulfilled_at = completed_at || issued_at
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def completed_at = job&.completed_at
|
|
26
|
+
|
|
27
|
+
def issued_at = time :issued_at
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# Work the business accepted and scheduled.
|
|
3
|
+
class Job < Company::Job
|
|
4
|
+
# The node keys Jobber spells otherwise than the vocabulary.
|
|
5
|
+
def self.keys
|
|
6
|
+
{ description: :title, notes: :instructions, created_at: :createdAt,
|
|
7
|
+
scheduled_at: :startAt, completed_at: :completedAt, amount: :total, }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
# @return [Quote, nil] quote the job was won with, where Jobber filed one beside it.
|
|
11
|
+
def quote = record Quote, :quote
|
|
12
|
+
|
|
13
|
+
# @return [Array<Line>] lines the job is made of, empty where the query never asked for
|
|
14
|
+
# them: a page costs what it carries, so nothing nested arrives unasked.
|
|
15
|
+
def lines = @node.dig(:lineItems, :nodes).to_a.map { |node| Line.new node: node }
|
|
16
|
+
|
|
17
|
+
# @return [Location, nil] where the work happens, where it came back beside the job.
|
|
18
|
+
def location = record Location, :property
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# One line of the work a job is made of.
|
|
3
|
+
class Line < Company::Line
|
|
4
|
+
# The node keys Jobber spells otherwise than the vocabulary.
|
|
5
|
+
def self.keys = { amount: :totalPrice }
|
|
6
|
+
|
|
7
|
+
# The most lines to read off one record. Bounded because Jobber prices a connection by the
|
|
8
|
+
# page it is asked for and prices an unbounded one at its own maximum, so the lines of a
|
|
9
|
+
# page of jobs were charged for as though every job had the largest job's worth of them.
|
|
10
|
+
PAGE = 20
|
|
11
|
+
|
|
12
|
+
# What to ask for wherever a record lists the lines it is made of.
|
|
13
|
+
SELECTION = "lineItems(first: #{PAGE}) { nodes { #{node_keys.join ' '} } }"
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# Where work happens, which Jobber calls a property: one address on a client's file.
|
|
3
|
+
class Location < Company::Location
|
|
4
|
+
# The node keys Jobber spells otherwise than the vocabulary.
|
|
5
|
+
def self.keys = { street: :street1, zip: :postalCode }
|
|
6
|
+
|
|
7
|
+
# The address as Jobber answers it, the coordinates tucked inside.
|
|
8
|
+
ADDRESS = 'street1 city postalCode coordinates { latitude longitude }'
|
|
9
|
+
|
|
10
|
+
# What to ask for wherever a record names the place the work happens at.
|
|
11
|
+
# @param customer [Boolean] whether whose place it is comes back beside it, which spares a
|
|
12
|
+
# second query for somebody Jobber already knows the place belongs to.
|
|
13
|
+
# @return [String] selection of a property.
|
|
14
|
+
def self.selection(customer: false)
|
|
15
|
+
[ 'property { id address {', ADDRESS, '}', (Customer::SELECTION if customer), '}' ].
|
|
16
|
+
compact.join ' '
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Jobber answers a field it holds nothing for with an empty string as readily as with
|
|
20
|
+
# null, and the two arrive as the same nothing.
|
|
21
|
+
# @param node [Hash] property as Jobber answered it, the address and its coordinates nested.
|
|
22
|
+
def initialize(node: {})
|
|
23
|
+
node = node.with_indifferent_access
|
|
24
|
+
address, coordinates = node[:address].to_h, node.dig(:address, :coordinates).to_h
|
|
25
|
+
super node: node.merge(address, coordinates).compact_blank
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @return [Customer, nil] whose place it is, where they came back beside the location.
|
|
29
|
+
def customer = record Customer, :client
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# A price the business sent to answer a lead, which Jobber calls a request.
|
|
3
|
+
class Quote < Company::Quote
|
|
4
|
+
# Jobber alone files the request a quote answers beside it; the vocabulary knows no such link.
|
|
5
|
+
# @return [Lead, nil] request the quote answers, where Jobber filed one beside it.
|
|
6
|
+
def lead = record Lead, :request
|
|
7
|
+
|
|
8
|
+
# Jobber files the total among the quote's amounts.
|
|
9
|
+
# @return [BigDecimal, nil] what the quote comes to, in dollars.
|
|
10
|
+
def amount
|
|
11
|
+
total = @node.dig :amounts, :total
|
|
12
|
+
BigDecimal total.to_s if total.present?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module Jbr
|
|
2
|
+
# One stop of a job: when the work is scheduled to happen, where the job does.
|
|
3
|
+
class Visit < Company::Visit
|
|
4
|
+
# What every visit reads: the vocabulary's names, and whether the client confirmed it,
|
|
5
|
+
# which Jobber alone asks.
|
|
6
|
+
def self.attributes = super + %i[confirmed]
|
|
7
|
+
|
|
8
|
+
# The node keys Jobber spells otherwise than the vocabulary.
|
|
9
|
+
def self.keys
|
|
10
|
+
{ description: :title, starts_at: :startAt, ends_at: :endAt, anytime: :allDay,
|
|
11
|
+
confirmed: :clientConfirmed, }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [Boolean, nil] whether the client confirmed the visit.
|
|
15
|
+
def confirmed? = attribute :confirmed
|
|
16
|
+
end
|
|
17
|
+
end
|
data/lib/jbr/version.rb
CHANGED
data/lib/jbr.rb
CHANGED
|
@@ -1,68 +1,59 @@
|
|
|
1
1
|
require 'json'
|
|
2
2
|
require 'net/http'
|
|
3
3
|
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
|
|
7
|
-
# for the sentence a job's lines read as.
|
|
8
|
-
require 'active_support/core_ext/object/blank'
|
|
4
|
+
# The vocabulary every record here answers in, and the Active Support it brings along. One
|
|
5
|
+
# more file of it: an address is sent without the fields a caller left blank.
|
|
6
|
+
require 'company'
|
|
9
7
|
require 'active_support/core_ext/enumerable'
|
|
10
|
-
require 'active_support/core_ext/array/conversions'
|
|
11
8
|
|
|
12
9
|
require 'graphql/error'
|
|
13
10
|
require 'graphql/unauthorized'
|
|
14
11
|
require 'graphql/throttled'
|
|
15
12
|
require 'graphql/client'
|
|
16
13
|
|
|
17
|
-
require 'jbr/
|
|
18
|
-
|
|
19
|
-
require 'jbr/url'
|
|
14
|
+
require 'jbr/version'
|
|
20
15
|
require 'jbr/error'
|
|
21
|
-
require 'jbr/refused'
|
|
22
|
-
require 'jbr/
|
|
16
|
+
require 'jbr/errors/refused'
|
|
17
|
+
require 'jbr/errors/throttled'
|
|
23
18
|
require 'jbr/token'
|
|
24
19
|
require 'jbr/refreshing'
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
require 'jbr/
|
|
28
|
-
require 'jbr/cliental'
|
|
29
|
-
require 'jbr/named'
|
|
30
|
-
require 'jbr/resource'
|
|
31
|
-
require 'jbr/request'
|
|
32
|
-
require 'jbr/oauth'
|
|
20
|
+
require 'jbr/querying'
|
|
21
|
+
require 'jbr/authorizing'
|
|
22
|
+
require 'jbr/mock'
|
|
33
23
|
|
|
34
|
-
|
|
35
|
-
#
|
|
36
|
-
require 'jbr/
|
|
37
|
-
require 'jbr/
|
|
24
|
+
# Phone before Customer, and Location before Customers: what each asks Jobber for is built as
|
|
25
|
+
# it loads. Every record before the collection that reads it, for the same reason.
|
|
26
|
+
require 'jbr/phone'
|
|
27
|
+
require 'jbr/resources/customer'
|
|
28
|
+
require 'jbr/resources/location'
|
|
29
|
+
require 'jbr/resources/line'
|
|
30
|
+
require 'jbr/resources/job'
|
|
31
|
+
require 'jbr/resources/visit'
|
|
32
|
+
require 'jbr/resources/quote'
|
|
33
|
+
require 'jbr/resources/invoice'
|
|
34
|
+
require 'jbr/resources/lead'
|
|
35
|
+
|
|
36
|
+
require 'jbr/reader'
|
|
37
|
+
require 'jbr/collection'
|
|
38
38
|
require 'jbr/includable'
|
|
39
39
|
require 'jbr/listable'
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
require 'jbr/
|
|
43
|
-
require 'jbr/
|
|
44
|
-
require 'jbr/
|
|
45
|
-
require 'jbr/
|
|
46
|
-
require 'jbr/
|
|
47
|
-
require 'jbr/
|
|
48
|
-
require 'jbr/quote'
|
|
49
|
-
require 'jbr/visit'
|
|
50
|
-
require 'jbr/visits'
|
|
40
|
+
require 'jbr/collections/jobs'
|
|
41
|
+
require 'jbr/collections/visits'
|
|
42
|
+
require 'jbr/collections/quotes'
|
|
43
|
+
require 'jbr/collections/invoices'
|
|
44
|
+
require 'jbr/collections/customers'
|
|
45
|
+
require 'jbr/collections/locations'
|
|
46
|
+
require 'jbr/collections/leads'
|
|
47
|
+
require 'jbr/account'
|
|
51
48
|
|
|
52
|
-
require 'jbr/mock/oauth'
|
|
53
|
-
require 'jbr/mock/client'
|
|
54
|
-
require 'jbr/mock/property'
|
|
55
|
-
require 'jbr/mock/quote'
|
|
56
|
-
require 'jbr/mock/line_item'
|
|
57
|
-
require 'jbr/mock/job'
|
|
58
49
|
require 'jbr/mock/jobs'
|
|
59
|
-
require 'jbr/mock/invoice'
|
|
60
|
-
require 'jbr/mock/request'
|
|
61
|
-
require 'jbr/mock/account'
|
|
62
|
-
require 'jbr/mock/url'
|
|
63
50
|
require 'jbr/mock/visit'
|
|
64
51
|
require 'jbr/mock/visits'
|
|
65
|
-
|
|
66
|
-
require 'jbr/
|
|
52
|
+
require 'jbr/mock/quote'
|
|
53
|
+
require 'jbr/mock/quotes'
|
|
54
|
+
require 'jbr/mock/invoice'
|
|
55
|
+
require 'jbr/mock/invoices'
|
|
56
|
+
require 'jbr/mock/leads'
|
|
57
|
+
require 'jbr/mock/account'
|
|
67
58
|
|
|
68
59
|
require 'jbr/event'
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: jbr
|
|
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
|
|
@@ -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: '1.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
26
40
|
- !ruby/object:Gem::Dependency
|
|
27
41
|
name: minitest
|
|
28
42
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -110,48 +124,45 @@ files:
|
|
|
110
124
|
- lib/graphql/unauthorized.rb
|
|
111
125
|
- lib/jbr.rb
|
|
112
126
|
- lib/jbr/account.rb
|
|
113
|
-
- lib/jbr/
|
|
114
|
-
- lib/jbr/
|
|
127
|
+
- lib/jbr/authorizing.rb
|
|
128
|
+
- lib/jbr/collection.rb
|
|
129
|
+
- lib/jbr/collections/customers.rb
|
|
130
|
+
- lib/jbr/collections/invoices.rb
|
|
131
|
+
- lib/jbr/collections/jobs.rb
|
|
132
|
+
- lib/jbr/collections/leads.rb
|
|
133
|
+
- lib/jbr/collections/locations.rb
|
|
134
|
+
- lib/jbr/collections/quotes.rb
|
|
135
|
+
- lib/jbr/collections/visits.rb
|
|
115
136
|
- lib/jbr/error.rb
|
|
137
|
+
- lib/jbr/errors/refused.rb
|
|
138
|
+
- lib/jbr/errors/throttled.rb
|
|
116
139
|
- lib/jbr/event.rb
|
|
117
140
|
- lib/jbr/includable.rb
|
|
118
|
-
- lib/jbr/invoice.rb
|
|
119
|
-
- lib/jbr/itemized.rb
|
|
120
|
-
- lib/jbr/job.rb
|
|
121
|
-
- lib/jbr/jobs.rb
|
|
122
|
-
- lib/jbr/line_item.rb
|
|
123
141
|
- lib/jbr/listable.rb
|
|
124
142
|
- lib/jbr/mock.rb
|
|
125
143
|
- lib/jbr/mock/account.rb
|
|
126
|
-
- lib/jbr/mock/client.rb
|
|
127
144
|
- lib/jbr/mock/invoice.rb
|
|
128
|
-
- lib/jbr/mock/
|
|
145
|
+
- lib/jbr/mock/invoices.rb
|
|
129
146
|
- lib/jbr/mock/jobs.rb
|
|
130
|
-
- lib/jbr/mock/
|
|
131
|
-
- lib/jbr/mock/oauth.rb
|
|
132
|
-
- lib/jbr/mock/property.rb
|
|
147
|
+
- lib/jbr/mock/leads.rb
|
|
133
148
|
- lib/jbr/mock/quote.rb
|
|
134
|
-
- lib/jbr/mock/
|
|
135
|
-
- lib/jbr/mock/url.rb
|
|
149
|
+
- lib/jbr/mock/quotes.rb
|
|
136
150
|
- lib/jbr/mock/visit.rb
|
|
137
151
|
- lib/jbr/mock/visits.rb
|
|
138
|
-
- lib/jbr/mocking.rb
|
|
139
|
-
- lib/jbr/named.rb
|
|
140
|
-
- lib/jbr/oauth.rb
|
|
141
152
|
- lib/jbr/phone.rb
|
|
142
|
-
- lib/jbr/
|
|
143
|
-
- lib/jbr/
|
|
144
|
-
- lib/jbr/quote.rb
|
|
153
|
+
- lib/jbr/querying.rb
|
|
154
|
+
- lib/jbr/reader.rb
|
|
145
155
|
- lib/jbr/refreshing.rb
|
|
146
|
-
- lib/jbr/
|
|
147
|
-
- lib/jbr/
|
|
148
|
-
- lib/jbr/
|
|
149
|
-
- lib/jbr/
|
|
156
|
+
- lib/jbr/resources/customer.rb
|
|
157
|
+
- lib/jbr/resources/invoice.rb
|
|
158
|
+
- lib/jbr/resources/job.rb
|
|
159
|
+
- lib/jbr/resources/lead.rb
|
|
160
|
+
- lib/jbr/resources/line.rb
|
|
161
|
+
- lib/jbr/resources/location.rb
|
|
162
|
+
- lib/jbr/resources/quote.rb
|
|
163
|
+
- lib/jbr/resources/visit.rb
|
|
150
164
|
- lib/jbr/token.rb
|
|
151
|
-
- lib/jbr/url.rb
|
|
152
165
|
- lib/jbr/version.rb
|
|
153
|
-
- lib/jbr/visit.rb
|
|
154
|
-
- lib/jbr/visits.rb
|
|
155
166
|
homepage: https://github.com/HouseAccountEng/jbr
|
|
156
167
|
licenses:
|
|
157
168
|
- MIT
|