servicetrade 0.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 +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE.md +0 -0
- data/README.md +143 -0
- data/lib/servicetrade/api_operations/create.rb +12 -0
- data/lib/servicetrade/api_operations/delete.rb +12 -0
- data/lib/servicetrade/api_operations/list.rb +67 -0
- data/lib/servicetrade/api_operations/update.rb +12 -0
- data/lib/servicetrade/auth.rb +42 -0
- data/lib/servicetrade/client.rb +72 -0
- data/lib/servicetrade/configuration.rb +11 -0
- data/lib/servicetrade/errors.rb +7 -0
- data/lib/servicetrade/resources/appointment.rb +14 -0
- data/lib/servicetrade/resources/base_resource.rb +30 -0
- data/lib/servicetrade/resources/job.rb +149 -0
- data/lib/servicetrade/resources/location.rb +218 -0
- data/lib/servicetrade/util.rb +0 -0
- data/lib/servicetrade/version.rb +5 -0
- data/lib/servicetrade.rb +52 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 82dc5b71eb88760c25b84d5341b0db78a94245eca3e78e3ecec623b263eb8bb2
|
4
|
+
data.tar.gz: 589dd883ba165436c59fe3b1cb562e4ed2263bd36061977c290a12743b2e76b5
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ba667f7a59253c9bc20ba7bc6f97350d2e0b444eed0e39a9825e9b63a4bae78f1fb0b540dcc681455e5c177c9550dc09707d702672fc253d6e480ca172e4e4c8
|
7
|
+
data.tar.gz: 1de099c1400de275c0a0bd15a4de7a72bc1c6629d72649782bfbc4bf8c6af33f0a8d7045148fd55c03466cec1f67d7adbad97c9366464aaebea9e493fc0c385f
|
data/CHANGELOG.md
ADDED
File without changes
|
data/LICENSE.md
ADDED
File without changes
|
data/README.md
ADDED
@@ -0,0 +1,143 @@
|
|
1
|
+
# ServiceTrade Ruby
|
2
|
+
|
3
|
+
A Ruby client library for the ServiceTrade API.
|
4
|
+
|
5
|
+
[](https://badge.fury.io/rb/servicetrade)
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'servicetrade'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install servicetrade
|
22
|
+
|
23
|
+
## Configuration
|
24
|
+
|
25
|
+
Configure the client with your ServiceTrade credentials:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
ServiceTrade.configure do |config|
|
29
|
+
config.username = 'your_username'
|
30
|
+
config.password = 'your_password'
|
31
|
+
config.api_version = '1' # Optional, defaults to '1'
|
32
|
+
config.timeout = 30 # Optional, defaults to 30 seconds
|
33
|
+
config.open_timeout = 10 # Optional, defaults to 10 seconds
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
## Authentication
|
38
|
+
|
39
|
+
The gem automatically handles authentication with the ServiceTrade API using your username and password. Session management is handled internally.
|
40
|
+
|
41
|
+
## Usage
|
42
|
+
|
43
|
+
### Jobs
|
44
|
+
|
45
|
+
```ruby
|
46
|
+
# List all jobs
|
47
|
+
jobs = ServiceTrade::Job.list
|
48
|
+
|
49
|
+
# Create a new job
|
50
|
+
job = ServiceTrade::Job.create(
|
51
|
+
name: 'Service Call',
|
52
|
+
customer_id: 123,
|
53
|
+
location_id: 456,
|
54
|
+
scheduled_date: '2023-12-01'
|
55
|
+
)
|
56
|
+
|
57
|
+
# Update a job
|
58
|
+
ServiceTrade::Job.update(job_id, {
|
59
|
+
status: 'completed',
|
60
|
+
completed_date: Time.now.iso8601
|
61
|
+
})
|
62
|
+
|
63
|
+
# Delete a job
|
64
|
+
ServiceTrade::Job.delete(job_id)
|
65
|
+
```
|
66
|
+
|
67
|
+
### Appointments
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
# List appointments
|
71
|
+
appointments = ServiceTrade::Appointment.list
|
72
|
+
|
73
|
+
# Create an appointment
|
74
|
+
appointment = ServiceTrade::Appointment.create(
|
75
|
+
job_id: 123,
|
76
|
+
start_time: '2023-12-01T09:00:00Z',
|
77
|
+
end_time: '2023-12-01T10:00:00Z'
|
78
|
+
)
|
79
|
+
```
|
80
|
+
|
81
|
+
### Locations
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
# List locations
|
85
|
+
locations = ServiceTrade::Location.list
|
86
|
+
|
87
|
+
# Create a location
|
88
|
+
location = ServiceTrade::Location.create(
|
89
|
+
name: 'Customer Site',
|
90
|
+
address: '123 Main St',
|
91
|
+
city: 'Anytown',
|
92
|
+
state: 'CA',
|
93
|
+
zip: '12345'
|
94
|
+
)
|
95
|
+
```
|
96
|
+
|
97
|
+
## Error Handling
|
98
|
+
|
99
|
+
The gem provides specific error classes for different types of API errors:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
begin
|
103
|
+
job = ServiceTrade::Job.create(invalid_data)
|
104
|
+
rescue ServiceTrade::AuthenticationError => e
|
105
|
+
# Handle authentication errors (401)
|
106
|
+
puts "Authentication failed: #{e.message}"
|
107
|
+
rescue ServiceTrade::AuthorizationError => e
|
108
|
+
# Handle authorization errors (403)
|
109
|
+
puts "Not authorized: #{e.message}"
|
110
|
+
rescue ServiceTrade::NotFoundError => e
|
111
|
+
# Handle not found errors (404)
|
112
|
+
puts "Resource not found: #{e.message}"
|
113
|
+
rescue ServiceTrade::ApiError => e
|
114
|
+
# Handle other API errors
|
115
|
+
puts "API error: #{e.message}"
|
116
|
+
end
|
117
|
+
```
|
118
|
+
|
119
|
+
## Available Resources
|
120
|
+
|
121
|
+
- **Job** - Create, read, update, and delete jobs
|
122
|
+
- **Appointment** - Manage appointments associated with jobs
|
123
|
+
- **Location** - Manage customer locations
|
124
|
+
|
125
|
+
Each resource supports standard CRUD operations where applicable:
|
126
|
+
- `list` - Retrieve a list of resources
|
127
|
+
- `create(params)` - Create a new resource
|
128
|
+
- `update(id, params)` - Update an existing resource
|
129
|
+
- `delete(id)` - Delete a resource
|
130
|
+
|
131
|
+
## Development
|
132
|
+
|
133
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
134
|
+
|
135
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
136
|
+
|
137
|
+
## Contributing
|
138
|
+
|
139
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/destilado/servicetrade-ruby.
|
140
|
+
|
141
|
+
## License
|
142
|
+
|
143
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# lib/servicetrade/api_operations/list.rb
|
2
|
+
module ServiceTrade
|
3
|
+
module ApiOperations
|
4
|
+
module List
|
5
|
+
def list(filters = {}, page: 1, per_page: 100)
|
6
|
+
params = filters.merge({
|
7
|
+
page: page,
|
8
|
+
per_page: per_page
|
9
|
+
})
|
10
|
+
|
11
|
+
response = ServiceTrade.client.request(:get, resource_url, params)
|
12
|
+
ListResponse.new(response, self)
|
13
|
+
end
|
14
|
+
|
15
|
+
# Iterator method for automatically handling pagination
|
16
|
+
def all(filters = {}, per_page: 100, &block)
|
17
|
+
if block_given?
|
18
|
+
page = 1
|
19
|
+
loop do
|
20
|
+
response = list(filters, page: page, per_page: per_page)
|
21
|
+
response.data.each(&block)
|
22
|
+
break unless response.has_more?
|
23
|
+
page += 1
|
24
|
+
end
|
25
|
+
else
|
26
|
+
Enumerator.new do |yielder|
|
27
|
+
page = 1
|
28
|
+
loop do
|
29
|
+
response = list(filters, page: page, per_page: per_page)
|
30
|
+
response.data.each { |item| yielder << item }
|
31
|
+
break unless response.has_more?
|
32
|
+
page += 1
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class ListResponse
|
41
|
+
attr_reader :data, :total_count, :page, :per_page
|
42
|
+
|
43
|
+
def initialize(response, resource_class)
|
44
|
+
# Handle ServiceTrade API response format
|
45
|
+
data_key = case resource_class.name.split('::').last.downcase
|
46
|
+
when 'job'
|
47
|
+
'jobs'
|
48
|
+
when 'appointment'
|
49
|
+
'appointments'
|
50
|
+
when 'location'
|
51
|
+
'locations'
|
52
|
+
else
|
53
|
+
'data'
|
54
|
+
end
|
55
|
+
|
56
|
+
items = response.dig('data', data_key) || response['data'] || []
|
57
|
+
@data = items.map { |item| resource_class.new(item) }
|
58
|
+
@total_count = response.dig('data', 'total') || response['total'] || items.length
|
59
|
+
@page = response.dig('data', 'page') || response['page'] || 1
|
60
|
+
@per_page = response.dig('data', 'per_page') || response['per_page'] || items.length
|
61
|
+
end
|
62
|
+
|
63
|
+
def has_more?
|
64
|
+
(@page * @per_page) < @total_count
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module ServiceTrade
|
2
|
+
class Auth
|
3
|
+
attr_reader :session_id
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@session_id = nil
|
7
|
+
@last_auth_time = nil
|
8
|
+
end
|
9
|
+
|
10
|
+
def authenticate
|
11
|
+
response = Client.new.request(
|
12
|
+
:post,
|
13
|
+
'auth',
|
14
|
+
{
|
15
|
+
username: ServiceTrade.configuration.username,
|
16
|
+
password: ServiceTrade.configuration.password
|
17
|
+
},
|
18
|
+
skip_auth: true
|
19
|
+
)
|
20
|
+
|
21
|
+
@session_id = response['sessionId']
|
22
|
+
@last_auth_time = Time.now
|
23
|
+
|
24
|
+
@session_id
|
25
|
+
end
|
26
|
+
|
27
|
+
def session_id
|
28
|
+
if @session_id.nil? || session_expired?
|
29
|
+
authenticate
|
30
|
+
end
|
31
|
+
@session_id
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def session_expired?
|
37
|
+
return true if @last_auth_time.nil?
|
38
|
+
# ServiceTrade sessions typically expire after 24 hours
|
39
|
+
Time.now - @last_auth_time > 86400 # 24 hours in seconds
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module ServiceTrade
|
2
|
+
class Client
|
3
|
+
attr_reader :api_base
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@api_base = ServiceTrade.api_base
|
7
|
+
end
|
8
|
+
|
9
|
+
def request(method, path, params = {}, headers = {}, skip_auth: false)
|
10
|
+
uri = URI.parse("#{api_base}/#{path}")
|
11
|
+
|
12
|
+
# Set up the request
|
13
|
+
klass = case method
|
14
|
+
when :get
|
15
|
+
Net::HTTP::Get
|
16
|
+
when :post
|
17
|
+
Net::HTTP::Post
|
18
|
+
when :put
|
19
|
+
Net::HTTP::Put
|
20
|
+
when :delete
|
21
|
+
Net::HTTP::Delete
|
22
|
+
else
|
23
|
+
raise ArgumentError, "Unknown HTTP method: #{method}"
|
24
|
+
end
|
25
|
+
|
26
|
+
request = klass.new(uri)
|
27
|
+
|
28
|
+
# Add headers
|
29
|
+
request['Content-Type'] = 'application/json'
|
30
|
+
request['Accept'] = 'application/json'
|
31
|
+
unless skip_auth
|
32
|
+
request['X-Session-Id'] = ServiceTrade.auth.session_id
|
33
|
+
end
|
34
|
+
headers.each { |key, value| request[key] = value }
|
35
|
+
|
36
|
+
# Add parameters
|
37
|
+
if [:post, :put].include?(method)
|
38
|
+
request.body = params.to_json
|
39
|
+
elsif method == :get && !params.empty?
|
40
|
+
uri.query = URI.encode_www_form(params)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Make the request
|
44
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: uri.scheme == 'https') do |http|
|
45
|
+
http.open_timeout = ServiceTrade.configuration.open_timeout
|
46
|
+
http.read_timeout = ServiceTrade.configuration.timeout
|
47
|
+
http.request(request)
|
48
|
+
end
|
49
|
+
|
50
|
+
handle_response(response)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
def handle_response(response)
|
56
|
+
case response.code.to_i
|
57
|
+
when 200..299
|
58
|
+
# Handle 204 No Content responses
|
59
|
+
return {} if response.body.nil? || response.body.strip.empty?
|
60
|
+
JSON.parse(response.body)
|
61
|
+
when 401
|
62
|
+
raise ServiceTrade::AuthenticationError, "Invalid credentials or expired session"
|
63
|
+
when 403
|
64
|
+
raise ServiceTrade::AuthorizationError, "Not authorized to perform this action"
|
65
|
+
when 404
|
66
|
+
raise ServiceTrade::NotFoundError, "Resource not found"
|
67
|
+
else
|
68
|
+
raise ServiceTrade::ApiError, "API error (#{response.code}): #{response.body}"
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module ServiceTrade
|
2
|
+
class Appointment
|
3
|
+
extend ServiceTrade::ApiOperations::Create
|
4
|
+
extend ServiceTrade::ApiOperations::List
|
5
|
+
extend ServiceTrade::ApiOperations::Update
|
6
|
+
extend ServiceTrade::ApiOperations::Delete
|
7
|
+
|
8
|
+
OBJECT_NAME = 'appointments'
|
9
|
+
|
10
|
+
def self.resource_url
|
11
|
+
OBJECT_NAME
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module ServiceTrade
|
2
|
+
class BaseResource
|
3
|
+
attr_reader :attributes
|
4
|
+
|
5
|
+
def initialize(attributes = {})
|
6
|
+
@attributes = attributes
|
7
|
+
set_attributes(attributes)
|
8
|
+
end
|
9
|
+
|
10
|
+
private
|
11
|
+
|
12
|
+
def set_attributes(attributes)
|
13
|
+
attributes.each do |key, value|
|
14
|
+
# Convert camelCase to snake_case for Ruby conventions
|
15
|
+
snake_key = camel_to_snake(key.to_s)
|
16
|
+
|
17
|
+
# Only set attributes that have corresponding reader methods
|
18
|
+
if self.respond_to?(snake_key)
|
19
|
+
instance_variable_set("@#{snake_key}", value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def camel_to_snake(str)
|
25
|
+
str.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
26
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
27
|
+
.downcase
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
module ServiceTrade
|
2
|
+
class Job < BaseResource
|
3
|
+
extend ServiceTrade::ApiOperations::Create
|
4
|
+
extend ServiceTrade::ApiOperations::List
|
5
|
+
extend ServiceTrade::ApiOperations::Update
|
6
|
+
extend ServiceTrade::ApiOperations::Delete
|
7
|
+
|
8
|
+
OBJECT_NAME = 'job'.freeze
|
9
|
+
|
10
|
+
# Core job attributes
|
11
|
+
attr_reader :id, :uri, :name, :custom_name, :type, :job_type_weight,
|
12
|
+
:status, :display_status, :substatus, :display_substatus,
|
13
|
+
:number, :ref_number, :customer_po, :visibility, :section_visibilities,
|
14
|
+
:description, :scheduled_date, :estimated_price, :latest_clock_in,
|
15
|
+
:ivr_open, :ivr_activity, :service_line, :due_by, :due_after,
|
16
|
+
:completed_on, :percent_complete, :is_project, :budgeted,
|
17
|
+
:created, :updated
|
18
|
+
|
19
|
+
# Related objects
|
20
|
+
attr_reader :vendor, :customer, :location, :owner, :sales, :primary_contact,
|
21
|
+
:current_appointment, :assigned_office, :offices, :tags,
|
22
|
+
:external_ids, :terms, :contract, :project, :notes,
|
23
|
+
:service_requests, :scheduling_comments
|
24
|
+
|
25
|
+
# Service link visibility
|
26
|
+
attr_reader :service_link_attachment_visibility, :service_link_comment_visibility,
|
27
|
+
:service_link_attachment_category_visibility
|
28
|
+
|
29
|
+
# Deprecated fields (kept for backwards compatibility)
|
30
|
+
attr_reader :deficiencies_found, :other_trade_deficiencies_found, :red_tags_found
|
31
|
+
|
32
|
+
def self.resource_url
|
33
|
+
OBJECT_NAME
|
34
|
+
end
|
35
|
+
|
36
|
+
# Find a specific job by ID
|
37
|
+
def self.find(id)
|
38
|
+
response = ServiceTrade.client.request(:get, "#{resource_url}/#{id}")
|
39
|
+
new(response['data'])
|
40
|
+
end
|
41
|
+
|
42
|
+
# Enhanced list method with comprehensive filtering
|
43
|
+
def self.list(filters = {})
|
44
|
+
# Set default status to 'scheduled' if not provided and no job number is specified
|
45
|
+
unless filters.key?(:status) || filters.key?('status') || filters.key?(:number) || filters.key?('number')
|
46
|
+
filters[:status] = 'scheduled'
|
47
|
+
end
|
48
|
+
|
49
|
+
response = ServiceTrade.client.request(:get, resource_url, filters)
|
50
|
+
|
51
|
+
# Handle the nested response structure from ServiceTrade API
|
52
|
+
jobs_data = response.dig('data', 'jobs') || response['data'] || []
|
53
|
+
jobs_data.map { |job_data| new(job_data) }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Create a new job
|
57
|
+
def self.create(params = {})
|
58
|
+
response = ServiceTrade.client.request(:post, resource_url, params)
|
59
|
+
new(response['data'])
|
60
|
+
end
|
61
|
+
|
62
|
+
# Update an existing job
|
63
|
+
def self.update(id, params = {})
|
64
|
+
response = ServiceTrade.client.request(:put, "#{resource_url}/#{id}", params)
|
65
|
+
new(response['data'])
|
66
|
+
end
|
67
|
+
|
68
|
+
# Update this job instance
|
69
|
+
def update(params = {})
|
70
|
+
self.class.update(id, params)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Delete a job
|
74
|
+
def self.delete(id)
|
75
|
+
ServiceTrade.client.request(:delete, "#{resource_url}/#{id}")
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
# Delete this job instance
|
80
|
+
def delete
|
81
|
+
self.class.delete(id)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Add task responses to a job
|
85
|
+
def add_task_responses(task_responses)
|
86
|
+
params = { task_responses: task_responses }
|
87
|
+
response = ServiceTrade.client.request(:post, "#{self.class.resource_url}/#{id}/taskresponses", params)
|
88
|
+
response['data']
|
89
|
+
end
|
90
|
+
|
91
|
+
# Convenience methods for common job filtering
|
92
|
+
def self.by_status(status)
|
93
|
+
list(status: status)
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.by_customer(customer_id)
|
97
|
+
list(customer_id: customer_id)
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.by_vendor(vendor_id)
|
101
|
+
list(vendor_id: vendor_id)
|
102
|
+
end
|
103
|
+
|
104
|
+
def self.by_location(location_id)
|
105
|
+
list(location_id: location_id)
|
106
|
+
end
|
107
|
+
|
108
|
+
def self.by_owner(owner_id)
|
109
|
+
list(owner_id: owner_id)
|
110
|
+
end
|
111
|
+
|
112
|
+
def self.around_location(lat, lon, radius)
|
113
|
+
list(lat: lat, lon: lon, radius: radius)
|
114
|
+
end
|
115
|
+
|
116
|
+
def self.due_between(start_timestamp, end_timestamp)
|
117
|
+
list(due_by_begin: start_timestamp, due_by_end: end_timestamp)
|
118
|
+
end
|
119
|
+
|
120
|
+
def self.completed_between(start_timestamp, end_timestamp)
|
121
|
+
list(completed_on_begin: start_timestamp, completed_on_end: end_timestamp)
|
122
|
+
end
|
123
|
+
|
124
|
+
# Check if job is completed
|
125
|
+
def completed?
|
126
|
+
status == 'completed'
|
127
|
+
end
|
128
|
+
|
129
|
+
# Check if job is canceled
|
130
|
+
def canceled?
|
131
|
+
status == 'canceled'
|
132
|
+
end
|
133
|
+
|
134
|
+
# Check if job is scheduled
|
135
|
+
def scheduled?
|
136
|
+
status == 'scheduled'
|
137
|
+
end
|
138
|
+
|
139
|
+
# Check if job is invoiced
|
140
|
+
def invoiced?
|
141
|
+
status == 'invoiced'
|
142
|
+
end
|
143
|
+
|
144
|
+
# Check if job has IVR activity open
|
145
|
+
def ivr_open?
|
146
|
+
ivr_open == true
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
module ServiceTrade
|
2
|
+
class Location < BaseResource
|
3
|
+
extend ServiceTrade::ApiOperations::Create
|
4
|
+
extend ServiceTrade::ApiOperations::List
|
5
|
+
extend ServiceTrade::ApiOperations::Update
|
6
|
+
extend ServiceTrade::ApiOperations::Delete
|
7
|
+
|
8
|
+
OBJECT_NAME = 'location'.freeze
|
9
|
+
|
10
|
+
# Core location attributes
|
11
|
+
attr_reader :id, :uri, :name, :ref_number, :lat, :lon, :geocode_quality,
|
12
|
+
:distance, :phone_number, :email, :general_manager, :status,
|
13
|
+
:taxable, :store_number, :created, :updated
|
14
|
+
|
15
|
+
# Address components
|
16
|
+
attr_reader :address, :address_street, :address_city, :address_state, :address_postal_code
|
17
|
+
|
18
|
+
# Related objects
|
19
|
+
attr_reader :company, :brand, :primary_contact, :offices, :tags, :tax_group,
|
20
|
+
:external_ids, :remit_to_address, :remit_to_source
|
21
|
+
|
22
|
+
# Deprecated fields (kept for backwards compatibility)
|
23
|
+
attr_reader :legacy_id
|
24
|
+
|
25
|
+
def self.resource_url
|
26
|
+
OBJECT_NAME
|
27
|
+
end
|
28
|
+
|
29
|
+
# Find a specific location by ID
|
30
|
+
def self.find(id)
|
31
|
+
response = ServiceTrade.client.request(:get, "#{resource_url}/#{id}")
|
32
|
+
new(response['data'])
|
33
|
+
end
|
34
|
+
|
35
|
+
# Enhanced list method with comprehensive filtering
|
36
|
+
def self.list(filters = {})
|
37
|
+
# Set default isCustomer to true if not specified
|
38
|
+
unless filters.key?(:is_customer) || filters.key?('isCustomer')
|
39
|
+
filters[:is_customer] = true
|
40
|
+
end
|
41
|
+
|
42
|
+
response = ServiceTrade.client.request(:get, resource_url, filters)
|
43
|
+
|
44
|
+
# Handle the nested response structure from ServiceTrade API
|
45
|
+
locations_data = response.dig('data', 'locations') || response['data'] || []
|
46
|
+
locations_data.map { |location_data| new(location_data) }
|
47
|
+
end
|
48
|
+
|
49
|
+
# Create a new location
|
50
|
+
def self.create(params = {})
|
51
|
+
response = ServiceTrade.client.request(:post, resource_url, params)
|
52
|
+
new(response['data'])
|
53
|
+
end
|
54
|
+
|
55
|
+
# Update an existing location
|
56
|
+
def self.update(id, params = {})
|
57
|
+
response = ServiceTrade.client.request(:put, "#{resource_url}/#{id}", params)
|
58
|
+
new(response['data'])
|
59
|
+
end
|
60
|
+
|
61
|
+
# Update this location instance
|
62
|
+
def update(params = {})
|
63
|
+
self.class.update(id, params)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Delete a location
|
67
|
+
def self.delete(id)
|
68
|
+
ServiceTrade.client.request(:delete, "#{resource_url}/#{id}")
|
69
|
+
true
|
70
|
+
end
|
71
|
+
|
72
|
+
# Delete this location instance
|
73
|
+
def delete
|
74
|
+
self.class.delete(id)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Merge this location into another location
|
78
|
+
def merge(replacement_id)
|
79
|
+
params = { replacement_id: replacement_id }
|
80
|
+
response = ServiceTrade.client.request(:post, "#{self.class.resource_url}/#{id}/merge", params)
|
81
|
+
self.class.new(response['data'])
|
82
|
+
end
|
83
|
+
|
84
|
+
# Get assets at this location
|
85
|
+
def assets
|
86
|
+
response = ServiceTrade.client.request(:get, "#{self.class.resource_url}/#{id}/asset")
|
87
|
+
assets_data = response.dig('data', 'assets') || []
|
88
|
+
# For now, return raw data - could create Asset resource class later
|
89
|
+
assets_data
|
90
|
+
end
|
91
|
+
|
92
|
+
# Get comments for this location
|
93
|
+
def comments
|
94
|
+
response = ServiceTrade.client.request(:get, "#{self.class.resource_url}/#{id}/comment")
|
95
|
+
comments_data = response.dig('data', 'comments') || []
|
96
|
+
# For now, return raw data - could create Comment resource class later
|
97
|
+
comments_data
|
98
|
+
end
|
99
|
+
|
100
|
+
# Create a comment for this location
|
101
|
+
def create_comment(params = {})
|
102
|
+
response = ServiceTrade.client.request(:post, "#{self.class.resource_url}/#{id}/comment", params)
|
103
|
+
response['data']
|
104
|
+
end
|
105
|
+
|
106
|
+
# Convenience methods for common location filtering
|
107
|
+
def self.by_name(name)
|
108
|
+
list(name: name)
|
109
|
+
end
|
110
|
+
|
111
|
+
def self.by_company(company_id)
|
112
|
+
list(company_id: company_id)
|
113
|
+
end
|
114
|
+
|
115
|
+
def self.by_ref_number(ref_number)
|
116
|
+
list(ref_number: ref_number)
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.by_status(status)
|
120
|
+
list(status: status)
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.by_region(region_ids)
|
124
|
+
region_ids = Array(region_ids).join(',') if region_ids.is_a?(Array)
|
125
|
+
list(region_ids: region_ids)
|
126
|
+
end
|
127
|
+
|
128
|
+
def self.by_office(office_ids)
|
129
|
+
office_ids = Array(office_ids).join(',') if office_ids.is_a?(Array)
|
130
|
+
list(office_ids: office_ids)
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.customers_only
|
134
|
+
list(is_customer: true)
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.vendors_only
|
138
|
+
list(is_vendor: true)
|
139
|
+
end
|
140
|
+
|
141
|
+
def self.active_locations
|
142
|
+
list(status: 'active')
|
143
|
+
end
|
144
|
+
|
145
|
+
def self.inactive_locations
|
146
|
+
list(status: 'inactive')
|
147
|
+
end
|
148
|
+
|
149
|
+
def self.updated_after(timestamp)
|
150
|
+
list(updated_after: timestamp)
|
151
|
+
end
|
152
|
+
|
153
|
+
def self.updated_before(timestamp)
|
154
|
+
list(updated_before: timestamp)
|
155
|
+
end
|
156
|
+
|
157
|
+
def self.created_after(timestamp)
|
158
|
+
list(created_after: timestamp)
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.created_before(timestamp)
|
162
|
+
list(created_before: timestamp)
|
163
|
+
end
|
164
|
+
|
165
|
+
def self.with_tags(tags)
|
166
|
+
tags = Array(tags).join(',') if tags.is_a?(Array)
|
167
|
+
list(tag: tags)
|
168
|
+
end
|
169
|
+
|
170
|
+
# Status check methods
|
171
|
+
def active?
|
172
|
+
status == 'active'
|
173
|
+
end
|
174
|
+
|
175
|
+
def inactive?
|
176
|
+
status == 'inactive'
|
177
|
+
end
|
178
|
+
|
179
|
+
def pending?
|
180
|
+
status == 'pending'
|
181
|
+
end
|
182
|
+
|
183
|
+
def on_hold?
|
184
|
+
status == 'on_hold'
|
185
|
+
end
|
186
|
+
|
187
|
+
# Check if location is taxable
|
188
|
+
def taxable?
|
189
|
+
taxable == true
|
190
|
+
end
|
191
|
+
|
192
|
+
# Get the full address as a string
|
193
|
+
def full_address
|
194
|
+
parts = []
|
195
|
+
|
196
|
+
if address
|
197
|
+
parts = [
|
198
|
+
address['street'],
|
199
|
+
address['city'],
|
200
|
+
address['state'],
|
201
|
+
address['postalCode']
|
202
|
+
]
|
203
|
+
else
|
204
|
+
parts = [
|
205
|
+
address_street,
|
206
|
+
address_city,
|
207
|
+
address_state,
|
208
|
+
address_postal_code
|
209
|
+
]
|
210
|
+
end
|
211
|
+
|
212
|
+
parts = parts.compact
|
213
|
+
return nil if parts.empty?
|
214
|
+
|
215
|
+
parts.join(', ')
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
File without changes
|
data/lib/servicetrade.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "servicetrade/version"
|
4
|
+
require_relative "servicetrade/configuration"
|
5
|
+
require_relative "servicetrade/client"
|
6
|
+
require_relative "servicetrade/auth"
|
7
|
+
require_relative "servicetrade/errors"
|
8
|
+
|
9
|
+
# API Operations
|
10
|
+
require_relative "servicetrade/api_operations/create"
|
11
|
+
require_relative "servicetrade/api_operations/list"
|
12
|
+
require_relative "servicetrade/api_operations/update"
|
13
|
+
require_relative "servicetrade/api_operations/delete"
|
14
|
+
|
15
|
+
# Resources
|
16
|
+
require_relative "servicetrade/resources/base_resource"
|
17
|
+
require_relative "servicetrade/resources/job"
|
18
|
+
require_relative "servicetrade/resources/appointment"
|
19
|
+
require_relative "servicetrade/resources/location"
|
20
|
+
|
21
|
+
require "net/http"
|
22
|
+
require "json"
|
23
|
+
|
24
|
+
module ServiceTrade
|
25
|
+
class << self
|
26
|
+
attr_accessor :configuration, :auth_instance, :client_instance
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.configure
|
30
|
+
self.configuration ||= Configuration.new
|
31
|
+
yield(configuration) if block_given?
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.auth
|
35
|
+
@auth_instance ||= Auth.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.client
|
39
|
+
@client_instance ||= Client.new
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.api_base
|
43
|
+
"https://api.servicetrade.com/api"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Reset all instances (useful for testing)
|
47
|
+
def self.reset!
|
48
|
+
@configuration = nil
|
49
|
+
@auth_instance = nil
|
50
|
+
@client_instance = nil
|
51
|
+
end
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: servicetrade
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bryce Holcomb
|
8
|
+
bindir: bin
|
9
|
+
cert_chain: []
|
10
|
+
date: 2025-09-02 00:00:00.000000000 Z
|
11
|
+
dependencies:
|
12
|
+
- !ruby/object:Gem::Dependency
|
13
|
+
name: rake
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
15
|
+
requirements:
|
16
|
+
- - "~>"
|
17
|
+
- !ruby/object:Gem::Version
|
18
|
+
version: '13.0'
|
19
|
+
type: :development
|
20
|
+
prerelease: false
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
requirements:
|
23
|
+
- - "~>"
|
24
|
+
- !ruby/object:Gem::Version
|
25
|
+
version: '13.0'
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
29
|
+
requirements:
|
30
|
+
- - "~>"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '3.0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '3.0'
|
40
|
+
- !ruby/object:Gem::Dependency
|
41
|
+
name: stringio
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: 3.1.2
|
47
|
+
type: :development
|
48
|
+
prerelease: false
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 3.1.2
|
54
|
+
description: A Ruby library for interacting with the ServiceTrade API
|
55
|
+
email:
|
56
|
+
- bryce@destilado.tech
|
57
|
+
executables: []
|
58
|
+
extensions: []
|
59
|
+
extra_rdoc_files: []
|
60
|
+
files:
|
61
|
+
- CHANGELOG.md
|
62
|
+
- LICENSE.md
|
63
|
+
- README.md
|
64
|
+
- lib/servicetrade.rb
|
65
|
+
- lib/servicetrade/api_operations/create.rb
|
66
|
+
- lib/servicetrade/api_operations/delete.rb
|
67
|
+
- lib/servicetrade/api_operations/list.rb
|
68
|
+
- lib/servicetrade/api_operations/update.rb
|
69
|
+
- lib/servicetrade/auth.rb
|
70
|
+
- lib/servicetrade/client.rb
|
71
|
+
- lib/servicetrade/configuration.rb
|
72
|
+
- lib/servicetrade/errors.rb
|
73
|
+
- lib/servicetrade/resources/appointment.rb
|
74
|
+
- lib/servicetrade/resources/base_resource.rb
|
75
|
+
- lib/servicetrade/resources/job.rb
|
76
|
+
- lib/servicetrade/resources/location.rb
|
77
|
+
- lib/servicetrade/util.rb
|
78
|
+
- lib/servicetrade/version.rb
|
79
|
+
homepage: https://github.com/destilado/servicetrade-ruby
|
80
|
+
licenses:
|
81
|
+
- MIT
|
82
|
+
metadata: {}
|
83
|
+
rdoc_options: []
|
84
|
+
require_paths:
|
85
|
+
- lib
|
86
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 2.6.0
|
91
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
96
|
+
requirements: []
|
97
|
+
rubygems_version: 3.6.2
|
98
|
+
specification_version: 4
|
99
|
+
summary: Ruby client for the ServiceTrade API
|
100
|
+
test_files: []
|