fastbound 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/README.md +366 -0
- data/lib/fastbound/client.rb +154 -0
- data/lib/fastbound/error.rb +31 -0
- data/lib/fastbound/resources/account.rb +13 -0
- data/lib/fastbound/resources/acquisitions.rb +97 -0
- data/lib/fastbound/resources/attachments.rb +13 -0
- data/lib/fastbound/resources/contacts.rb +71 -0
- data/lib/fastbound/resources/dispositions.rb +129 -0
- data/lib/fastbound/resources/downloads.rb +13 -0
- data/lib/fastbound/resources/form4473s.rb +13 -0
- data/lib/fastbound/resources/inventory.rb +20 -0
- data/lib/fastbound/resources/items.rb +84 -0
- data/lib/fastbound/resources/multiple_sale_reports.rb +15 -0
- data/lib/fastbound/resources/smart_lists.rb +71 -0
- data/lib/fastbound/resources/users.rb +13 -0
- data/lib/fastbound/resources/webhooks.rb +39 -0
- data/lib/fastbound/version.rb +3 -0
- data/lib/fastbound.rb +19 -0
- metadata +95 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: a8219bcb38d03ea269bb7d9c4a16a7e062b6beb001f88f6763eecac18cec9a66
|
|
4
|
+
data.tar.gz: 01d7d0f70d77527d5e9d5fc12445493780ba10f539471360c84522820c164ddc
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3fe98da320c0330c51c7e51b36e6e98feb395174988033d1c067f60ddcff8a4f6f8da4d1b9024fe6c52f80ce4e6fafa520cf3722e75ed5b9b5b82b8f60af59a8
|
|
7
|
+
data.tar.gz: f8db9b2ef652c56350a71929751b1d06cfd69c259467fe54755c8ad52c1cf7f9f182aa8035eea73d749003b94b6f2a0b8303fe447d943602bb6a51af429f635a
|
data/README.md
ADDED
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
# Fastbound
|
|
2
|
+
|
|
3
|
+
A Ruby gem for the [FastBound](https://fastbound.com) firearms compliance API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Add to your Gemfile:
|
|
8
|
+
|
|
9
|
+
```ruby
|
|
10
|
+
gem "fastbound"
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Or install directly:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gem install fastbound
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Configuration
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
client = Fastbound::Client.new(
|
|
23
|
+
api_key: "your_api_key",
|
|
24
|
+
account_number: "A001234",
|
|
25
|
+
audit_user: "user@example.com" # required for create/update/delete operations
|
|
26
|
+
)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
| Option | Required | Description |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `api_key` | Yes | Your FastBound API key (used as HTTP Basic auth username) |
|
|
32
|
+
| `account_number` | Yes | Your FastBound account number |
|
|
33
|
+
| `audit_user` | No* | Email address recorded on write operations. Required by the API for POST/PUT/DELETE. |
|
|
34
|
+
| `base_url` | No | Override the API base URL (default: `https://cloud.fastbound.com`) |
|
|
35
|
+
|
|
36
|
+
## Resources
|
|
37
|
+
|
|
38
|
+
### Account
|
|
39
|
+
|
|
40
|
+
```ruby
|
|
41
|
+
client.account.get
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Acquisitions
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
# List acquisitions
|
|
48
|
+
client.acquisitions.list(take: 25, skip: 0)
|
|
49
|
+
client.acquisitions.list(type: "Purchase", acquired_from_contact_id: "uuid")
|
|
50
|
+
|
|
51
|
+
# Find by ID or external ID
|
|
52
|
+
client.acquisitions.find("uuid")
|
|
53
|
+
client.acquisitions.find_by_external_id("my-ext-id")
|
|
54
|
+
|
|
55
|
+
# Create
|
|
56
|
+
client.acquisitions.create(
|
|
57
|
+
date: "2024-01-15",
|
|
58
|
+
type: "Purchase",
|
|
59
|
+
invoice_number: "INV-001",
|
|
60
|
+
external_id: "my-acq-001"
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Update
|
|
64
|
+
client.acquisitions.update("uuid", invoice_number: "INV-002")
|
|
65
|
+
|
|
66
|
+
# Delete
|
|
67
|
+
client.acquisitions.destroy("uuid")
|
|
68
|
+
|
|
69
|
+
# Attach a contact
|
|
70
|
+
client.acquisitions.attach_contact("acquisition-uuid", "contact-uuid")
|
|
71
|
+
|
|
72
|
+
# Commit
|
|
73
|
+
client.acquisitions.commit("uuid")
|
|
74
|
+
client.acquisitions.commit("uuid", list_acquired_items: true)
|
|
75
|
+
|
|
76
|
+
# Create and commit in one request
|
|
77
|
+
client.acquisitions.create_and_commit(
|
|
78
|
+
date: "2024-01-15",
|
|
79
|
+
type: "Purchase",
|
|
80
|
+
contact_id: "contact-uuid",
|
|
81
|
+
items: [{ manufacturer: "Glock", model: "19", serial: "ABC123", type: "Pistol", caliber: "9mm" }]
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Create as pending (does not commit)
|
|
85
|
+
client.acquisitions.create_as_pending(
|
|
86
|
+
date: "2024-01-15",
|
|
87
|
+
type: "Purchase",
|
|
88
|
+
contact_id: "contact-uuid",
|
|
89
|
+
items: [{ manufacturer: "Glock", model: "19", serial: "ABC123", type: "Pistol", caliber: "9mm" }]
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Items
|
|
93
|
+
client.acquisitions.get_item("acquisition-uuid", "item-uuid")
|
|
94
|
+
client.acquisitions.add_item("acquisition-uuid", manufacturer: "Glock", model: "19", serial: "ABC123", type: "Pistol", caliber: "9mm")
|
|
95
|
+
client.acquisitions.add_items("acquisition-uuid", [
|
|
96
|
+
{ manufacturer: "Glock", model: "19", serial: "ABC123", type: "Pistol", caliber: "9mm" },
|
|
97
|
+
{ manufacturer: "Glock", model: "17", serial: "DEF456", type: "Pistol", caliber: "9mm" }
|
|
98
|
+
])
|
|
99
|
+
client.acquisitions.update_item("acquisition-uuid", "item-uuid", location: "Safe A")
|
|
100
|
+
client.acquisitions.delete_item("acquisition-uuid", "item-uuid")
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Attachments
|
|
104
|
+
|
|
105
|
+
```ruby
|
|
106
|
+
# Returns raw binary data
|
|
107
|
+
pdf_bytes = client.attachments.download("attachment-uuid")
|
|
108
|
+
File.binwrite("attachment.pdf", pdf_bytes)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Contacts
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
# List contacts
|
|
115
|
+
client.contacts.list(take: 25, skip: 0)
|
|
116
|
+
client.contacts.list(last_name: "Smith", ffl_number: "1-23-456-07-8A-12345")
|
|
117
|
+
|
|
118
|
+
# Find by ID or external ID
|
|
119
|
+
client.contacts.find("uuid")
|
|
120
|
+
client.contacts.find_by_external_id("my-ext-id")
|
|
121
|
+
|
|
122
|
+
# Create
|
|
123
|
+
client.contacts.create(
|
|
124
|
+
first_name: "John",
|
|
125
|
+
last_name: "Smith",
|
|
126
|
+
ffl_number: "1-23-456-07-8A-12345",
|
|
127
|
+
ffl_expires: "2026-12-31",
|
|
128
|
+
premise_address1: "123 Main St",
|
|
129
|
+
premise_city: "Anytown",
|
|
130
|
+
premise_state: "TX",
|
|
131
|
+
premise_zip_code: "75001"
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Update
|
|
135
|
+
client.contacts.update("uuid", email_address: "john@example.com")
|
|
136
|
+
|
|
137
|
+
# Delete
|
|
138
|
+
client.contacts.destroy("uuid")
|
|
139
|
+
|
|
140
|
+
# Merge two contacts (winning contact receives all data from losing contact)
|
|
141
|
+
client.contacts.merge(winning_contact_id: "uuid-1", losing_contact_id: "uuid-2")
|
|
142
|
+
|
|
143
|
+
# Licenses
|
|
144
|
+
client.contacts.get_license("contact-uuid", "license-uuid")
|
|
145
|
+
client.contacts.create_license("contact-uuid", type: "FFL", number: "1-23-456-07-8A-12345", expiration: "2026-12-31")
|
|
146
|
+
client.contacts.update_license("contact-uuid", "license-uuid", copy_on_file: true)
|
|
147
|
+
client.contacts.delete_license("contact-uuid", "license-uuid")
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Dispositions
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# List dispositions
|
|
154
|
+
client.dispositions.list(take: 25, skip: 0)
|
|
155
|
+
client.dispositions.list(type: "SaleTo", disposed_to_contact_id: "uuid")
|
|
156
|
+
client.dispositions.list(include_4473: true)
|
|
157
|
+
|
|
158
|
+
# List Form 4473 dispositions
|
|
159
|
+
client.dispositions.list_4473s(take: 25)
|
|
160
|
+
client.dispositions.list_4473s(include_awaiting_completion: true)
|
|
161
|
+
|
|
162
|
+
# Find by ID or external ID
|
|
163
|
+
client.dispositions.find("uuid")
|
|
164
|
+
client.dispositions.find_by_external_id("my-ext-id")
|
|
165
|
+
|
|
166
|
+
# Create standard disposition
|
|
167
|
+
client.dispositions.create(type: "SaleTo", date: "2024-01-15", generate_ttsn: true)
|
|
168
|
+
|
|
169
|
+
# Create special disposition types
|
|
170
|
+
client.dispositions.create_nfa(date: "2024-01-15", type: "SaleTo", submission_date: "2024-01-10")
|
|
171
|
+
client.dispositions.create_theft_loss(date: "2024-01-15", theft_loss__type: "Theft")
|
|
172
|
+
client.dispositions.create_destroyed(date: "2024-01-15", destroyed__description: "Destroyed per ATF")
|
|
173
|
+
|
|
174
|
+
# Update
|
|
175
|
+
client.dispositions.update("uuid", note: "Updated note")
|
|
176
|
+
|
|
177
|
+
# Delete
|
|
178
|
+
client.dispositions.destroy("uuid")
|
|
179
|
+
|
|
180
|
+
# Attach a contact
|
|
181
|
+
client.dispositions.attach_contact("disposition-uuid", "contact-uuid")
|
|
182
|
+
|
|
183
|
+
# Lock
|
|
184
|
+
client.dispositions.lock("uuid")
|
|
185
|
+
client.dispositions.lock_by_external_id("my-ext-id")
|
|
186
|
+
|
|
187
|
+
# Commit
|
|
188
|
+
client.dispositions.commit("uuid")
|
|
189
|
+
client.dispositions.commit("uuid", list_disposed_items: true)
|
|
190
|
+
|
|
191
|
+
# Create and commit in one request
|
|
192
|
+
client.dispositions.create_and_commit(
|
|
193
|
+
type: "SaleTo",
|
|
194
|
+
date: "2024-01-15",
|
|
195
|
+
contact_id: "contact-uuid",
|
|
196
|
+
items: [{ id: "item-uuid", price: 500.00 }]
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Create as pending (does not commit)
|
|
200
|
+
client.dispositions.create_as_pending(
|
|
201
|
+
type: "SaleTo",
|
|
202
|
+
date: "2024-01-15",
|
|
203
|
+
contact_id: "contact-uuid",
|
|
204
|
+
items: [{ id: "item-uuid", price: 500.00 }]
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
# Items
|
|
208
|
+
client.dispositions.list_items("disposition-uuid")
|
|
209
|
+
client.dispositions.add_items("disposition-uuid", [{ id: "item-uuid", price: 500.00 }])
|
|
210
|
+
client.dispositions.add_items_by_external_id("disp-ext-id", [{ external_id: "item-ext-id", price: 500.00 }])
|
|
211
|
+
client.dispositions.add_items_by_search("disp-ext-id", manufacturer: "Glock", model: "19", caliber: "9mm")
|
|
212
|
+
client.dispositions.edit_item_price("disposition-uuid", "item-uuid", price: 550.00)
|
|
213
|
+
client.dispositions.remove_item("disposition-uuid", "item-uuid")
|
|
214
|
+
client.dispositions.remove_item_by_external_id("disposition-uuid", "item-ext-id")
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Downloads
|
|
218
|
+
|
|
219
|
+
```ruby
|
|
220
|
+
# Download bound book as binary (PDF or CSV depending on API response)
|
|
221
|
+
data = client.downloads.bound_book
|
|
222
|
+
File.binwrite("bound_book.pdf", data)
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Form 4473s
|
|
226
|
+
|
|
227
|
+
```ruby
|
|
228
|
+
# Returns raw binary PDF data
|
|
229
|
+
pdf_bytes = client.form4473s.download("form4473-uuid")
|
|
230
|
+
File.binwrite("form4473.pdf", pdf_bytes)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Inventory
|
|
234
|
+
|
|
235
|
+
```ruby
|
|
236
|
+
# Bulk verify inventory by serial number
|
|
237
|
+
result = client.inventory.bulk_verify(
|
|
238
|
+
serials: ["ABC123", "DEF456", "GHI789"],
|
|
239
|
+
rollback_partial: true,
|
|
240
|
+
update_location: true,
|
|
241
|
+
location: "Safe A",
|
|
242
|
+
verified_utc: "2024-01-15T10:00:00Z"
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### Items
|
|
247
|
+
|
|
248
|
+
```ruby
|
|
249
|
+
# List items (extensive filtering available)
|
|
250
|
+
client.items.list(take: 25, skip: 0)
|
|
251
|
+
client.items.list(manufacturer: "Glock", caliber: "9mm", status: 1)
|
|
252
|
+
client.items.list(serial: "ABC123")
|
|
253
|
+
client.items.list(acquired_on_or_after: "2024-01-01", acquired_on_or_before: "2024-12-31")
|
|
254
|
+
client.items.list(is_theft_loss: false, is_destroyed: false)
|
|
255
|
+
|
|
256
|
+
# Find by ID or external ID
|
|
257
|
+
client.items.find("uuid")
|
|
258
|
+
client.items.find_by_external_id("my-ext-id")
|
|
259
|
+
|
|
260
|
+
# Update
|
|
261
|
+
client.items.update("uuid", location: "Safe B", note: "Cleaned and inspected")
|
|
262
|
+
|
|
263
|
+
# Delete (logical delete with type)
|
|
264
|
+
client.items.delete("uuid", delete_type: "Destroyed", delete_note: "Per ATF guidelines")
|
|
265
|
+
|
|
266
|
+
# Set acquisition contact
|
|
267
|
+
client.items.set_acquisition_contact("item-uuid", "contact-uuid")
|
|
268
|
+
|
|
269
|
+
# Undispose
|
|
270
|
+
client.items.undispose("uuid")
|
|
271
|
+
client.items.undispose("uuid", note: "Returned by customer")
|
|
272
|
+
|
|
273
|
+
# Set external IDs
|
|
274
|
+
client.items.set_external_id("uuid", external_id: "my-item-001")
|
|
275
|
+
client.items.set_external_ids([
|
|
276
|
+
{ id: "uuid-1", external_id: "my-item-001" },
|
|
277
|
+
{ id: "uuid-2", external_id: "my-item-002" }
|
|
278
|
+
])
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Multiple Sale Reports
|
|
282
|
+
|
|
283
|
+
```ruby
|
|
284
|
+
# Returns raw binary PDF data
|
|
285
|
+
pdf_bytes = client.multiple_sale_reports.download("report-uuid", "attachment-uuid")
|
|
286
|
+
File.binwrite("multiple_sale_report.pdf", pdf_bytes)
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Smart Lists
|
|
290
|
+
|
|
291
|
+
Smart lists return the allowed values for various fields in your account.
|
|
292
|
+
|
|
293
|
+
```ruby
|
|
294
|
+
client.smart_lists.acquire_types
|
|
295
|
+
client.smart_lists.calibers
|
|
296
|
+
client.smart_lists.conditions
|
|
297
|
+
client.smart_lists.countries_of_manufacture
|
|
298
|
+
client.smart_lists.delete_types
|
|
299
|
+
client.smart_lists.dispose_types
|
|
300
|
+
client.smart_lists.importers
|
|
301
|
+
client.smart_lists.item_types
|
|
302
|
+
client.smart_lists.license_types
|
|
303
|
+
client.smart_lists.locations
|
|
304
|
+
client.smart_lists.manufacturers
|
|
305
|
+
client.smart_lists.theft_loss_types
|
|
306
|
+
client.smart_lists.manufacturing_dispose_types
|
|
307
|
+
client.smart_lists.manufacturing_acquire_types
|
|
308
|
+
```
|
|
309
|
+
|
|
310
|
+
### Users
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
client.users.list
|
|
314
|
+
client.users.list(include_disabled: true)
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### Webhooks
|
|
318
|
+
|
|
319
|
+
```ruby
|
|
320
|
+
# List available webhook events
|
|
321
|
+
client.webhooks.list_events
|
|
322
|
+
|
|
323
|
+
# Find a webhook by name
|
|
324
|
+
client.webhooks.find("my-webhook")
|
|
325
|
+
|
|
326
|
+
# Create a webhook
|
|
327
|
+
client.webhooks.create(
|
|
328
|
+
name: "my-webhook",
|
|
329
|
+
url: "https://example.com/webhooks/fastbound",
|
|
330
|
+
description: "Acquisition notifications",
|
|
331
|
+
events: ["acquisition.committed", "disposition.committed"]
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
# Update a webhook
|
|
335
|
+
client.webhooks.update("my-webhook", url: "https://example.com/webhooks/v2")
|
|
336
|
+
|
|
337
|
+
# Delete a webhook
|
|
338
|
+
client.webhooks.destroy("my-webhook")
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## Error Handling
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
begin
|
|
345
|
+
client.contacts.find("nonexistent-uuid")
|
|
346
|
+
rescue Fastbound::NotFoundError => e
|
|
347
|
+
puts "Not found: #{e.message}"
|
|
348
|
+
rescue Fastbound::UnprocessableEntityError => e
|
|
349
|
+
puts "Validation failed: #{e.errors.join(", ")}"
|
|
350
|
+
rescue Fastbound::UnauthorizedError => e
|
|
351
|
+
puts "Auth error: #{e.message}"
|
|
352
|
+
rescue Fastbound::ApiError => e
|
|
353
|
+
puts "API error #{e.status}: #{e.message}"
|
|
354
|
+
end
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
| Exception | HTTP Status |
|
|
358
|
+
|---|---|
|
|
359
|
+
| `Fastbound::UnauthorizedError` | 401, 403 |
|
|
360
|
+
| `Fastbound::NotFoundError` | 404 |
|
|
361
|
+
| `Fastbound::UnprocessableEntityError` | 400, 422 |
|
|
362
|
+
| `Fastbound::ApiError` | All other errors |
|
|
363
|
+
|
|
364
|
+
## License
|
|
365
|
+
|
|
366
|
+
MIT
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
require "faraday"
|
|
2
|
+
require "json"
|
|
3
|
+
|
|
4
|
+
module Fastbound
|
|
5
|
+
class Client
|
|
6
|
+
BASE_URL = "https://cloud.fastbound.com"
|
|
7
|
+
|
|
8
|
+
attr_reader :account_number, :audit_user
|
|
9
|
+
|
|
10
|
+
def initialize(api_key:, account_number:, audit_user: nil, base_url: BASE_URL)
|
|
11
|
+
raise ConfigurationError, "api_key is required" if api_key.nil? || api_key.empty?
|
|
12
|
+
raise ConfigurationError, "account_number is required" if account_number.nil? || account_number.empty?
|
|
13
|
+
|
|
14
|
+
@api_key = api_key
|
|
15
|
+
@account_number = account_number
|
|
16
|
+
@audit_user = audit_user
|
|
17
|
+
@base_url = base_url
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def account
|
|
21
|
+
@account ||= Resources::Account.new(self)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def acquisitions
|
|
25
|
+
@acquisitions ||= Resources::Acquisitions.new(self)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def attachments
|
|
29
|
+
@attachments ||= Resources::Attachments.new(self)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def contacts
|
|
33
|
+
@contacts ||= Resources::Contacts.new(self)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def dispositions
|
|
37
|
+
@dispositions ||= Resources::Dispositions.new(self)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def downloads
|
|
41
|
+
@downloads ||= Resources::Downloads.new(self)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def form4473s
|
|
45
|
+
@form4473s ||= Resources::Form4473s.new(self)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def inventory
|
|
49
|
+
@inventory ||= Resources::Inventory.new(self)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def items
|
|
53
|
+
@items ||= Resources::Items.new(self)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def multiple_sale_reports
|
|
57
|
+
@multiple_sale_reports ||= Resources::MultipleSaleReports.new(self)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def smart_lists
|
|
61
|
+
@smart_lists ||= Resources::SmartLists.new(self)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def users
|
|
65
|
+
@users ||= Resources::Users.new(self)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def webhooks
|
|
69
|
+
@webhooks ||= Resources::Webhooks.new(self)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def get(path, params = {})
|
|
73
|
+
request(:get, path, params: compact(params))
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def post(path, body = {}, audit: true)
|
|
77
|
+
request(:post, path, body: body, audit: audit)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def put(path, body = {}, audit: true)
|
|
81
|
+
request(:put, path, body: body, audit: audit)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def delete(path, audit: true)
|
|
85
|
+
request(:delete, path, audit: audit)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def get_binary(path)
|
|
89
|
+
response = connection.get(path)
|
|
90
|
+
handle_response(response, raw: true)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def post_binary(path, body = {}, audit: true)
|
|
94
|
+
response = connection(audit: audit).post(path) do |req|
|
|
95
|
+
req.body = body.to_json
|
|
96
|
+
req.headers["Content-Type"] = "application/json"
|
|
97
|
+
end
|
|
98
|
+
handle_response(response, raw: true)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def base_path
|
|
102
|
+
"/#{account_number}/api"
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
private
|
|
106
|
+
|
|
107
|
+
def request(method, path, params: nil, body: nil, audit: false)
|
|
108
|
+
response = connection(audit: audit).public_send(method, path) do |req|
|
|
109
|
+
req.params = params if params&.any?
|
|
110
|
+
if body
|
|
111
|
+
req.body = body.to_json
|
|
112
|
+
req.headers["Content-Type"] = "application/json"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
handle_response(response)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def handle_response(response, raw: false)
|
|
119
|
+
case response.status
|
|
120
|
+
when 200, 201
|
|
121
|
+
return response.body if raw
|
|
122
|
+
response.body.empty? ? nil : JSON.parse(response.body)
|
|
123
|
+
when 204
|
|
124
|
+
nil
|
|
125
|
+
when 401, 403
|
|
126
|
+
raise UnauthorizedError.new(response.status, safe_parse(response.body))
|
|
127
|
+
when 404
|
|
128
|
+
raise NotFoundError.new(response.status, safe_parse(response.body))
|
|
129
|
+
when 400, 422
|
|
130
|
+
raise UnprocessableEntityError.new(response.status, safe_parse(response.body))
|
|
131
|
+
else
|
|
132
|
+
raise ApiError.new(response.status, safe_parse(response.body))
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def safe_parse(body)
|
|
137
|
+
JSON.parse(body)
|
|
138
|
+
rescue StandardError
|
|
139
|
+
{}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def compact(hash)
|
|
143
|
+
hash.reject { |_, v| v.nil? }
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def connection(audit: false)
|
|
147
|
+
Faraday.new(url: @base_url) do |conn|
|
|
148
|
+
conn.request :authorization, :basic, @api_key, ""
|
|
149
|
+
conn.headers["Accept"] = "application/json"
|
|
150
|
+
conn.headers["X-AuditUser"] = @audit_user if audit && @audit_user
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
class Error < StandardError; end
|
|
3
|
+
class ConfigurationError < Error; end
|
|
4
|
+
|
|
5
|
+
class ApiError < Error
|
|
6
|
+
attr_reader :status, :errors
|
|
7
|
+
|
|
8
|
+
def initialize(status, body)
|
|
9
|
+
@status = status
|
|
10
|
+
@errors = parse_errors(body)
|
|
11
|
+
super(build_message)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def parse_errors(body)
|
|
17
|
+
return [] unless body.is_a?(Hash)
|
|
18
|
+
|
|
19
|
+
Array(body["errors"]).map { |e| e.is_a?(Hash) ? e["message"] : e.to_s }.compact
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build_message
|
|
23
|
+
base = "HTTP #{status}"
|
|
24
|
+
errors.any? ? "#{base}: #{errors.join(", ")}" : base
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class NotFoundError < ApiError; end
|
|
29
|
+
class UnprocessableEntityError < ApiError; end
|
|
30
|
+
class UnauthorizedError < ApiError; end
|
|
31
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
module Resources
|
|
3
|
+
class Acquisitions
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list(take: nil, skip: nil, id: nil, external_id: nil, type: nil,
|
|
9
|
+
purchase_order_number: nil, invoice_number: nil,
|
|
10
|
+
shipment_tracking_number: nil, is_manufacturing_acquisition: nil,
|
|
11
|
+
acquired_from_contact_id: nil, acquired_from_contact_external_id: nil,
|
|
12
|
+
item_id: nil, item_external_id: nil)
|
|
13
|
+
@client.get("#{base}/Acquisitions", {
|
|
14
|
+
take: take, skip: skip, id: id, externalId: external_id, type: type,
|
|
15
|
+
purchaseOrderNumber: purchase_order_number, invoiceNumber: invoice_number,
|
|
16
|
+
shipmentTrackingNumber: shipment_tracking_number,
|
|
17
|
+
isManufacturingAcquisition: is_manufacturing_acquisition,
|
|
18
|
+
acquiredFromContactId: acquired_from_contact_id,
|
|
19
|
+
acquiredFromContactExternalId: acquired_from_contact_external_id,
|
|
20
|
+
itemId: item_id, itemExternalId: item_external_id
|
|
21
|
+
})
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def find(id)
|
|
25
|
+
@client.get("#{base}/Acquisitions/#{id}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def find_by_external_id(external_id)
|
|
29
|
+
@client.get("#{base}/Acquisitions/GetByExternalId/#{external_id}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def create(params = {})
|
|
33
|
+
@client.post("#{base}/Acquisitions", params)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def update(id, params = {})
|
|
37
|
+
@client.put("#{base}/Acquisitions/#{id}", params)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def destroy(id)
|
|
41
|
+
@client.delete("#{base}/Acquisitions/#{id}")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def attach_contact(id, contact_id)
|
|
45
|
+
@client.put("#{base}/Acquisitions/#{id}/AttachContact/#{contact_id}", {})
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def commit(id, params = {}, list_acquired_items: nil)
|
|
49
|
+
path = "#{base}/Acquisitions/#{id}/Commit"
|
|
50
|
+
path += "?listAcquiredItems=true" if list_acquired_items
|
|
51
|
+
@client.post(path, params)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def create_and_commit(params = {}, list_acquired_items: nil)
|
|
55
|
+
path = "#{base}/Acquisitions/CreateAndCommit"
|
|
56
|
+
path += "?listAcquiredItems=true" if list_acquired_items
|
|
57
|
+
@client.post(path, params)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def create_as_pending(params = {})
|
|
61
|
+
@client.post("#{base}/Acquisitions/CreateAsPending", params)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Item operations
|
|
65
|
+
|
|
66
|
+
def get_item(id, acquisition_item_id)
|
|
67
|
+
@client.get("#{base}/Acquisitions/#{id}/Items/#{acquisition_item_id}")
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def get_item_by_external_ids(acquisition_external_id, acquisition_item_external_id)
|
|
71
|
+
@client.get("#{base}/Acquisitions/#{acquisition_external_id}/Items/#{acquisition_item_external_id}")
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def add_item(id, params = {})
|
|
75
|
+
@client.post("#{base}/Acquisitions/#{id}/Items", params)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def add_items(id, items = [])
|
|
79
|
+
@client.post("#{base}/Acquisitions/#{id}/Items/Multiple", { items: items })
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def update_item(id, acquisition_item_id, params = {})
|
|
83
|
+
@client.put("#{base}/Acquisitions/#{id}/Items/#{acquisition_item_id}", params)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def delete_item(id, acquisition_item_id)
|
|
87
|
+
@client.delete("#{base}/Acquisitions/#{id}/Items/#{acquisition_item_id}")
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def base
|
|
93
|
+
@client.base_path
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
module Resources
|
|
3
|
+
class Contacts
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list(license_name: nil, trade_name: nil, ffl_number: nil,
|
|
9
|
+
organization_name: nil, first_name: nil, middle_name: nil,
|
|
10
|
+
last_name: nil, suffix: nil, take: nil, skip: nil)
|
|
11
|
+
@client.get("#{base}/Contacts", {
|
|
12
|
+
licenseName: license_name, tradeName: trade_name, fflNumber: ffl_number,
|
|
13
|
+
organizationName: organization_name, firstName: first_name,
|
|
14
|
+
middleName: middle_name, lastName: last_name, suffix: suffix,
|
|
15
|
+
take: take, skip: skip
|
|
16
|
+
})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def find(id)
|
|
20
|
+
@client.get("#{base}/Contacts/#{id}")
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def find_by_external_id(external_id)
|
|
24
|
+
@client.get("#{base}/Contacts/GetByExternalId/#{external_id}")
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def create(params = {})
|
|
28
|
+
@client.post("#{base}/Contacts", params)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def update(id, params = {})
|
|
32
|
+
@client.put("#{base}/Contacts/#{id}", params)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def destroy(id)
|
|
36
|
+
@client.delete("#{base}/Contacts/#{id}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def merge(winning_contact_id:, losing_contact_id:)
|
|
40
|
+
@client.post("#{base}/Contacts/Merge", {
|
|
41
|
+
winningContactId: winning_contact_id,
|
|
42
|
+
losingContactId: losing_contact_id
|
|
43
|
+
})
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# License operations
|
|
47
|
+
|
|
48
|
+
def get_license(id, license_id)
|
|
49
|
+
@client.get("#{base}/Contacts/#{id}/Licenses/#{license_id}")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def create_license(id, params = {})
|
|
53
|
+
@client.post("#{base}/Contacts/#{id}/Licenses", params)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def update_license(id, license_id, params = {})
|
|
57
|
+
@client.put("#{base}/Contacts/#{id}/Licenses/#{license_id}", params)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete_license(id, license_id)
|
|
61
|
+
@client.delete("#{base}/Contacts/#{id}/Licenses/#{license_id}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def base
|
|
67
|
+
@client.base_path
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
module Resources
|
|
3
|
+
class Dispositions
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list(take: nil, skip: nil, include_4473: nil, id: nil, external_id: nil,
|
|
9
|
+
type: nil, ttsn: nil, otsn: nil, purchase_order_number: nil,
|
|
10
|
+
invoice_number: nil, shipment_tracking_number: nil,
|
|
11
|
+
is_manufacturing_disposition: nil, disposed_to_contact_id: nil,
|
|
12
|
+
disposed_to_contact_external_id: nil, item_id: nil, item_external_id: nil)
|
|
13
|
+
@client.get("#{base}/Dispositions", {
|
|
14
|
+
take: take, skip: skip, include4473: include_4473, id: id,
|
|
15
|
+
externalId: external_id, type: type, TTSN: ttsn, OTSN: otsn,
|
|
16
|
+
purchaseOrderNumber: purchase_order_number, invoiceNumber: invoice_number,
|
|
17
|
+
shipmentTrackingNumber: shipment_tracking_number,
|
|
18
|
+
isManufacturingDisposition: is_manufacturing_disposition,
|
|
19
|
+
disposedToContactId: disposed_to_contact_id,
|
|
20
|
+
disposedToContactExternalId: disposed_to_contact_external_id,
|
|
21
|
+
itemId: item_id, itemExternalId: item_external_id
|
|
22
|
+
})
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def list_4473s(take: nil, skip: nil, include_awaiting_completion: nil)
|
|
26
|
+
@client.get("#{base}/Dispositions/Only4473s", {
|
|
27
|
+
take: take, skip: skip,
|
|
28
|
+
includeAwaiting4473Completion: include_awaiting_completion
|
|
29
|
+
})
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def find(id)
|
|
33
|
+
@client.get("#{base}/Dispositions/#{id}")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def find_by_external_id(external_id)
|
|
37
|
+
@client.get("#{base}/Dispositions/GetByExternalId/#{external_id}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def create(params = {})
|
|
41
|
+
@client.post("#{base}/Dispositions", params)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def create_nfa(params = {})
|
|
45
|
+
@client.post("#{base}/Dispositions/NFA", params)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def create_theft_loss(params = {})
|
|
49
|
+
@client.post("#{base}/Dispositions/TheftLoss", params)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def create_destroyed(params = {})
|
|
53
|
+
@client.post("#{base}/Dispositions/Destroyed", params)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def update(id, params = {})
|
|
57
|
+
@client.put("#{base}/Dispositions/#{id}", params)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def destroy(id)
|
|
61
|
+
@client.delete("#{base}/Dispositions/#{id}")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def attach_contact(id, contact_id)
|
|
65
|
+
@client.put("#{base}/Dispositions/#{id}/AttachContact/#{contact_id}", {})
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def lock(id)
|
|
69
|
+
@client.put("#{base}/Dispositions/Lock/#{id}", {})
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def lock_by_external_id(external_id)
|
|
73
|
+
@client.put("#{base}/Dispositions/LockByExternalId/#{external_id}", {})
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def commit(id, params = {}, list_disposed_items: nil)
|
|
77
|
+
path = "#{base}/Dispositions/#{id}/Commit"
|
|
78
|
+
path += "?listDisposedItems=true" if list_disposed_items
|
|
79
|
+
@client.post(path, params)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def create_and_commit(params = {}, list_disposed_items: nil)
|
|
83
|
+
path = "#{base}/Dispositions/CreateAndCommit"
|
|
84
|
+
path += "?listDisposedItems=true" if list_disposed_items
|
|
85
|
+
@client.post(path, params)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def create_as_pending(params = {})
|
|
89
|
+
@client.post("#{base}/Dispositions/CreateAsPending", params)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Item operations
|
|
93
|
+
|
|
94
|
+
def list_items(id)
|
|
95
|
+
@client.get("#{base}/Dispositions/#{id}/Items")
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def add_items(id, items = [])
|
|
99
|
+
@client.post("#{base}/Dispositions/#{id}/Items", { items: items })
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def add_items_by_external_id(disposition_external_id, items = [])
|
|
103
|
+
@client.post("#{base}/Dispositions/#{disposition_external_id}/Items/AddByExternalId", { items: items })
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def add_items_by_search(disposition_external_id, params = {})
|
|
107
|
+
@client.post("#{base}/Dispositions/#{disposition_external_id}/Items/AddBySearch", params)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def edit_item_price(id, item_id, price:)
|
|
111
|
+
@client.put("#{base}/Dispositions/#{id}/Items/EditPrice/#{item_id}", { price: price })
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def remove_item(id, item_id)
|
|
115
|
+
@client.delete("#{base}/Dispositions/#{id}/Items/Remove/#{item_id}")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def remove_item_by_external_id(id, item_external_id)
|
|
119
|
+
@client.delete("#{base}/Dispositions/#{id}/Items/RemoveByExternalId/#{item_external_id}")
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def base
|
|
125
|
+
@client.base_path
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
module Resources
|
|
3
|
+
class Inventory
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def bulk_verify(serials:, rollback_partial: nil, update_location: nil,
|
|
9
|
+
location: nil, verified_utc: nil)
|
|
10
|
+
@client.put("#{@client.base_path}/Inventory/BulkVerify", {
|
|
11
|
+
serials: serials,
|
|
12
|
+
rollbackPartial: rollback_partial,
|
|
13
|
+
updateLocation: update_location,
|
|
14
|
+
location: location,
|
|
15
|
+
verifiedUtc: verified_utc
|
|
16
|
+
}.reject { |_, v| v.nil? })
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
module Resources
|
|
3
|
+
class Items
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list(search: nil, item_number: nil, serial: nil, manufacturer: nil,
|
|
9
|
+
importer: nil, model: nil, type: nil, caliber: nil, location: nil,
|
|
10
|
+
condition: nil, mpn: nil, upc: nil, sku: nil, is_theft_loss: nil,
|
|
11
|
+
is_destroyed: nil, do_not_dispose: nil, disposition_id: nil,
|
|
12
|
+
status: nil, acquired_on_or_after: nil, acquired_on_or_before: nil,
|
|
13
|
+
acquire_purchase_order_number: nil, acquire_invoice_number: nil,
|
|
14
|
+
acquire_shipment_tracking_number: nil, disposed_on_or_after: nil,
|
|
15
|
+
disposed_on_or_before: nil, dispose_purchase_order_number: nil,
|
|
16
|
+
dispose_invoice_number: nil, dispose_shipment_tracking_number: nil,
|
|
17
|
+
has_external_id: nil, acquisition_type: nil, ttsn: nil, otsn: nil,
|
|
18
|
+
take: nil, skip: nil)
|
|
19
|
+
@client.get("#{base}/Items", {
|
|
20
|
+
search: search, itemNumber: item_number, serial: serial,
|
|
21
|
+
manufacturer: manufacturer, importer: importer, model: model,
|
|
22
|
+
type: type, caliber: caliber, location: location, condition: condition,
|
|
23
|
+
mpn: mpn, upc: upc, sku: sku, isTheftLoss: is_theft_loss,
|
|
24
|
+
isDestroyed: is_destroyed, doNotDispose: do_not_dispose,
|
|
25
|
+
dispositionId: disposition_id, status: status,
|
|
26
|
+
acquiredOnOrAfter: acquired_on_or_after,
|
|
27
|
+
acquiredOnOrBefore: acquired_on_or_before,
|
|
28
|
+
acquirePurchaseOrderNumber: acquire_purchase_order_number,
|
|
29
|
+
acquireInvoiceNumber: acquire_invoice_number,
|
|
30
|
+
acquireShipmentTrackingNumber: acquire_shipment_tracking_number,
|
|
31
|
+
disposedOnOrAfter: disposed_on_or_after,
|
|
32
|
+
disposedOnOrBefore: disposed_on_or_before,
|
|
33
|
+
disposePurchaseOrderNumber: dispose_purchase_order_number,
|
|
34
|
+
disposeInvoiceNumber: dispose_invoice_number,
|
|
35
|
+
disposeShipmentTrackingNumber: dispose_shipment_tracking_number,
|
|
36
|
+
hasExternalId: has_external_id, acquisitionType: acquisition_type,
|
|
37
|
+
ttsn: ttsn, otsn: otsn, take: take, skip: skip
|
|
38
|
+
})
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def find(id)
|
|
42
|
+
@client.get("#{base}/Items/#{id}")
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def find_by_external_id(external_id)
|
|
46
|
+
@client.get("#{base}/Items/GetByExternalId/#{external_id}")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def update(id, params = {})
|
|
50
|
+
@client.put("#{base}/Items/#{id}", params)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def delete(id, delete_type:, delete_note: nil)
|
|
54
|
+
@client.post("#{base}/Items/#{id}/Delete", {
|
|
55
|
+
deleteType: delete_type,
|
|
56
|
+
deleteNote: delete_note
|
|
57
|
+
}.reject { |_, v| v.nil? })
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def set_acquisition_contact(id, contact_id)
|
|
61
|
+
@client.put("#{base}/Items/#{id}/AcquisitionContact/#{contact_id}", {})
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def undispose(id, note: nil)
|
|
65
|
+
body = note ? { note: note } : {}
|
|
66
|
+
@client.put("#{base}/Items/#{id}/Undispose", body)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def set_external_id(id, external_id:)
|
|
70
|
+
@client.put("#{base}/Items/#{id}/SetExternalId", { externalId: external_id })
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def set_external_ids(items = [])
|
|
74
|
+
@client.put("#{base}/Items/SetExternalIds", { items: items })
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def base
|
|
80
|
+
@client.base_path
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
module Resources
|
|
3
|
+
class MultipleSaleReports
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def download(multiple_sale_report_id, attachment_id)
|
|
9
|
+
@client.get_binary(
|
|
10
|
+
"#{@client.base_path}/MultipleSaleReports/Download/#{multiple_sale_report_id}/a/#{attachment_id}"
|
|
11
|
+
)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
module Resources
|
|
3
|
+
class SmartLists
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def acquire_types
|
|
9
|
+
@client.get("#{base}/SmartLists/AcquireType")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def calibers
|
|
13
|
+
@client.get("#{base}/SmartLists/Caliber")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def conditions
|
|
17
|
+
@client.get("#{base}/SmartLists/Condition")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def countries_of_manufacture
|
|
21
|
+
@client.get("#{base}/SmartLists/CountryOfManufacture")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def delete_types
|
|
25
|
+
@client.get("#{base}/SmartLists/DeleteType")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dispose_types
|
|
29
|
+
@client.get("#{base}/SmartLists/DisposeType")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def importers
|
|
33
|
+
@client.get("#{base}/SmartLists/Importer")
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def item_types
|
|
37
|
+
@client.get("#{base}/SmartLists/ItemType")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def license_types
|
|
41
|
+
@client.get("#{base}/SmartLists/LicenseType")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def locations
|
|
45
|
+
@client.get("#{base}/SmartLists/Location")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def manufacturers
|
|
49
|
+
@client.get("#{base}/SmartLists/Manufacturer")
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def theft_loss_types
|
|
53
|
+
@client.get("#{base}/SmartLists/TheftLossType")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def manufacturing_dispose_types
|
|
57
|
+
@client.get("#{base}/SmartLists/ManufacturingDisposeType")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def manufacturing_acquire_types
|
|
61
|
+
@client.get("#{base}/SmartLists/ManufacturingAcquireType")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def base
|
|
67
|
+
@client.base_path
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
module Fastbound
|
|
2
|
+
module Resources
|
|
3
|
+
class Webhooks
|
|
4
|
+
def initialize(client)
|
|
5
|
+
@client = client
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def list_events
|
|
9
|
+
@client.get("#{base}/Webhooks/Events")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def find(name)
|
|
13
|
+
@client.get("#{base}/Webhooks/#{name}")
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def create(name:, url:, description: nil, events: [])
|
|
17
|
+
@client.post("#{base}/Webhooks", {
|
|
18
|
+
name: name, url: url, description: description, events: events
|
|
19
|
+
}.reject { |_, v| v.nil? })
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def update(name, url: nil, description: nil, events: nil)
|
|
23
|
+
@client.put("#{base}/Webhooks/#{name}", {
|
|
24
|
+
name: name, url: url, description: description, events: events
|
|
25
|
+
}.reject { |_, v| v.nil? })
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def destroy(name)
|
|
29
|
+
@client.delete("#{base}/Webhooks/#{name}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def base
|
|
35
|
+
@client.base_path
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
data/lib/fastbound.rb
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require "fastbound/version"
|
|
2
|
+
require "fastbound/error"
|
|
3
|
+
require "fastbound/client"
|
|
4
|
+
require "fastbound/resources/account"
|
|
5
|
+
require "fastbound/resources/acquisitions"
|
|
6
|
+
require "fastbound/resources/attachments"
|
|
7
|
+
require "fastbound/resources/contacts"
|
|
8
|
+
require "fastbound/resources/dispositions"
|
|
9
|
+
require "fastbound/resources/downloads"
|
|
10
|
+
require "fastbound/resources/form4473s"
|
|
11
|
+
require "fastbound/resources/inventory"
|
|
12
|
+
require "fastbound/resources/items"
|
|
13
|
+
require "fastbound/resources/multiple_sale_reports"
|
|
14
|
+
require "fastbound/resources/smart_lists"
|
|
15
|
+
require "fastbound/resources/users"
|
|
16
|
+
require "fastbound/resources/webhooks"
|
|
17
|
+
|
|
18
|
+
module Fastbound
|
|
19
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: fastbound
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- JD Warren
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-06-04 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: faraday
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - ">="
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1.0'
|
|
20
|
+
- - "<"
|
|
21
|
+
- !ruby/object:Gem::Version
|
|
22
|
+
version: '3.0'
|
|
23
|
+
type: :runtime
|
|
24
|
+
prerelease: false
|
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
26
|
+
requirements:
|
|
27
|
+
- - ">="
|
|
28
|
+
- !ruby/object:Gem::Version
|
|
29
|
+
version: '1.0'
|
|
30
|
+
- - "<"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '3.0'
|
|
33
|
+
- !ruby/object:Gem::Dependency
|
|
34
|
+
name: faraday-multipart
|
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.0'
|
|
40
|
+
type: :runtime
|
|
41
|
+
prerelease: false
|
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.0'
|
|
47
|
+
description:
|
|
48
|
+
email:
|
|
49
|
+
- johndavid400@gmail.com
|
|
50
|
+
executables: []
|
|
51
|
+
extensions: []
|
|
52
|
+
extra_rdoc_files: []
|
|
53
|
+
files:
|
|
54
|
+
- README.md
|
|
55
|
+
- lib/fastbound.rb
|
|
56
|
+
- lib/fastbound/client.rb
|
|
57
|
+
- lib/fastbound/error.rb
|
|
58
|
+
- lib/fastbound/resources/account.rb
|
|
59
|
+
- lib/fastbound/resources/acquisitions.rb
|
|
60
|
+
- lib/fastbound/resources/attachments.rb
|
|
61
|
+
- lib/fastbound/resources/contacts.rb
|
|
62
|
+
- lib/fastbound/resources/dispositions.rb
|
|
63
|
+
- lib/fastbound/resources/downloads.rb
|
|
64
|
+
- lib/fastbound/resources/form4473s.rb
|
|
65
|
+
- lib/fastbound/resources/inventory.rb
|
|
66
|
+
- lib/fastbound/resources/items.rb
|
|
67
|
+
- lib/fastbound/resources/multiple_sale_reports.rb
|
|
68
|
+
- lib/fastbound/resources/smart_lists.rb
|
|
69
|
+
- lib/fastbound/resources/users.rb
|
|
70
|
+
- lib/fastbound/resources/webhooks.rb
|
|
71
|
+
- lib/fastbound/version.rb
|
|
72
|
+
homepage: https://github.com/johndavid400/fastbound
|
|
73
|
+
licenses:
|
|
74
|
+
- MIT
|
|
75
|
+
metadata: {}
|
|
76
|
+
post_install_message:
|
|
77
|
+
rdoc_options: []
|
|
78
|
+
require_paths:
|
|
79
|
+
- lib
|
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - ">="
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: 2.7.0
|
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
86
|
+
requirements:
|
|
87
|
+
- - ">="
|
|
88
|
+
- !ruby/object:Gem::Version
|
|
89
|
+
version: '0'
|
|
90
|
+
requirements: []
|
|
91
|
+
rubygems_version: 3.0.9
|
|
92
|
+
signing_key:
|
|
93
|
+
specification_version: 4
|
|
94
|
+
summary: Ruby API wrapper for the FastBound firearms compliance API
|
|
95
|
+
test_files: []
|