printavo-ruby 0.4.0 → 0.5.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/README.md +52 -1
- data/docs/CHANGELOG.md +16 -0
- data/lib/printavo/resources/customers.rb +85 -0
- data/lib/printavo/resources/inquiries.rb +69 -0
- data/lib/printavo/resources/orders.rb +127 -0
- data/lib/printavo/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c8061b68869c47ca86f6c37dc51d60020c035364ea70c68e68bad1df2f4da6c
|
|
4
|
+
data.tar.gz: 1d9c8f4d5805ab82bca17f28891fdc21899f2b166638c4e100e8531c8275073f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9b39cbc93da67a0b81114fa7209720d944d6ee87cd77763876191ce907a38498cf9c3020548b4f3f23f2a3a675bdd5b7f6ccf80ba937e1c929372e6cfe00761d
|
|
7
|
+
data.tar.gz: ab0e28809466df5269eece7960b2d425c1d4f15db68361c55aa730909bd3662b8ac9f1e256c49218e7cc03febe71c96661d64a2c472bb96bc2aa8c0f8b97ab13
|
data/README.md
CHANGED
|
@@ -136,6 +136,57 @@ end
|
|
|
136
136
|
all_jobs = client.jobs.all_pages(order_id: "99")
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
+
### Mutations
|
|
140
|
+
|
|
141
|
+
#### Customers
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
# Create
|
|
145
|
+
customer = client.customers.create(
|
|
146
|
+
primary_contact: { firstName: "Jane", lastName: "Smith", email: "jane@example.com" },
|
|
147
|
+
company_name: "Acme Shirts"
|
|
148
|
+
)
|
|
149
|
+
puts customer.full_name # => "Jane Smith"
|
|
150
|
+
puts customer.company # => "Acme Shirts"
|
|
151
|
+
|
|
152
|
+
# Update
|
|
153
|
+
customer = client.customers.update("42", company_name: "New Name Inc")
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
#### Orders
|
|
157
|
+
|
|
158
|
+
```ruby
|
|
159
|
+
# Create (Printavo creates orders as quotes first)
|
|
160
|
+
order = client.orders.create(
|
|
161
|
+
contact: { id: "456" },
|
|
162
|
+
due_at: "2026-06-01T09:00:00Z",
|
|
163
|
+
customer_due_at: "2026-06-01",
|
|
164
|
+
nickname: "Summer Rush"
|
|
165
|
+
)
|
|
166
|
+
|
|
167
|
+
# Update
|
|
168
|
+
order = client.orders.update("99", nickname: "Rush Job", production_note: "Ships Friday")
|
|
169
|
+
|
|
170
|
+
# Move to a new status
|
|
171
|
+
registry = client.statuses.registry
|
|
172
|
+
order = client.orders.update_status("99", status_id: registry[:in_production].id)
|
|
173
|
+
puts order.status # => "In Production"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### Inquiries
|
|
177
|
+
|
|
178
|
+
```ruby
|
|
179
|
+
# Create
|
|
180
|
+
inquiry = client.inquiries.create(
|
|
181
|
+
name: "Bob Johnson",
|
|
182
|
+
email: "bob@example.com",
|
|
183
|
+
request: "100 hoodies, front + back print"
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Update
|
|
187
|
+
inquiry = client.inquiries.update("55", nickname: "Hoodies Rush")
|
|
188
|
+
```
|
|
189
|
+
|
|
139
190
|
### Statuses
|
|
140
191
|
|
|
141
192
|
```ruby
|
|
@@ -294,7 +345,7 @@ end
|
|
|
294
345
|
| 0.2.0 | Status registry + Inquiries ✅ |
|
|
295
346
|
| 0.3.0 | Pagination helpers (`each_page`, `all_pages`) + `bin/lint` ✅ |
|
|
296
347
|
| 0.4.0 | Expanded GraphQL DSL (`mutate`, `paginate`) ✅ |
|
|
297
|
-
| 0.5.0 | Mutations (create/update) |
|
|
348
|
+
| 0.5.0 | Mutations (create/update) ✅ |
|
|
298
349
|
| 0.6.0 | Analytics / Reporting queries |
|
|
299
350
|
| 0.7.0 | Community burn-in / API stabilization |
|
|
300
351
|
| 0.8.0 | Retry/backoff + rate limit awareness |
|
data/docs/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [0.5.0] - 2026-03-30
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- `Customers#create(primary_contact:, **input)` — `customerCreate` mutation; normalizes `primaryContact` + `companyName` response fields to match the read-side `Customer` model
|
|
15
|
+
- `Customers#update(id, **input)` — `customerUpdate` mutation
|
|
16
|
+
- `Orders#create(**input)` — `quoteCreate` mutation; normalizes `total` → `totalPrice` in response
|
|
17
|
+
- `Orders#update(id, **input)` — `quoteUpdate` mutation
|
|
18
|
+
- `Orders#update_status(id, status_id:)` — `statusUpdate` mutation; handles `OrderUnion (Quote | Invoice)` via inline fragments
|
|
19
|
+
- `Inquiries#create(**input)` — `inquiryCreate` mutation
|
|
20
|
+
- `Inquiries#update(id, **input)` — `inquiryUpdate` mutation
|
|
21
|
+
- All mutation methods accept snake_case keyword args and camelize them for GraphQL input
|
|
22
|
+
- `CREATE_MUTATION` and `UPDATE_MUTATION` constants on `Customers`, `Orders`, `Inquiries`; `UPDATE_STATUS_MUTATION` on `Orders`
|
|
23
|
+
- Private `build_customer` and `build_order` normalizers centralize mutation response mapping
|
|
24
|
+
- Private `camelize_keys` on `Customers` and `Orders` converts `snake_case` → `camelCase` for input variables
|
|
25
|
+
- Factory helpers `fake_customer_mutation_response` and `fake_order_mutation_response` for testing
|
|
26
|
+
|
|
11
27
|
## [0.4.0] - 2026-03-30
|
|
12
28
|
|
|
13
29
|
### Added
|
|
@@ -34,6 +34,38 @@ module Printavo
|
|
|
34
34
|
}
|
|
35
35
|
GQL
|
|
36
36
|
|
|
37
|
+
CREATE_MUTATION = <<~GQL.freeze
|
|
38
|
+
mutation CustomerCreate($input: CustomerCreateInput!) {
|
|
39
|
+
customerCreate(input: $input) {
|
|
40
|
+
id
|
|
41
|
+
companyName
|
|
42
|
+
primaryContact {
|
|
43
|
+
id
|
|
44
|
+
firstName
|
|
45
|
+
lastName
|
|
46
|
+
email
|
|
47
|
+
phone
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
GQL
|
|
52
|
+
|
|
53
|
+
UPDATE_MUTATION = <<~GQL.freeze
|
|
54
|
+
mutation CustomerUpdate($id: ID!, $input: CustomerInput!) {
|
|
55
|
+
customerUpdate(id: $id, input: $input) {
|
|
56
|
+
id
|
|
57
|
+
companyName
|
|
58
|
+
primaryContact {
|
|
59
|
+
id
|
|
60
|
+
firstName
|
|
61
|
+
lastName
|
|
62
|
+
email
|
|
63
|
+
phone
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
GQL
|
|
68
|
+
|
|
37
69
|
def all(first: 25, after: nil)
|
|
38
70
|
fetch_page(first: first, after: after).records
|
|
39
71
|
end
|
|
@@ -43,6 +75,36 @@ module Printavo
|
|
|
43
75
|
Printavo::Customer.new(data['customer'])
|
|
44
76
|
end
|
|
45
77
|
|
|
78
|
+
# Creates a new customer. Requires a +primary_contact+ hash with at least
|
|
79
|
+
# +firstName+ and +email+. Optional keyword arguments map to CustomerCreateInput.
|
|
80
|
+
#
|
|
81
|
+
# @param primary_contact [Hash] contact fields (firstName, lastName, email, phone)
|
|
82
|
+
# @return [Printavo::Customer]
|
|
83
|
+
#
|
|
84
|
+
# @example
|
|
85
|
+
# client.customers.create(
|
|
86
|
+
# primary_contact: { firstName: "Jane", lastName: "Smith", email: "jane@example.com" },
|
|
87
|
+
# company_name: "Acme Shirts"
|
|
88
|
+
# )
|
|
89
|
+
def create(primary_contact:, **input)
|
|
90
|
+
variables = { input: camelize_keys(input).merge(primaryContact: primary_contact) }
|
|
91
|
+
data = @graphql.mutate(CREATE_MUTATION, variables: variables)
|
|
92
|
+
build_customer(data['customerCreate'])
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Updates an existing customer by ID.
|
|
96
|
+
#
|
|
97
|
+
# @param id [String, Integer]
|
|
98
|
+
# @return [Printavo::Customer]
|
|
99
|
+
#
|
|
100
|
+
# @example
|
|
101
|
+
# client.customers.update("42", company_name: "New Name")
|
|
102
|
+
def update(id, **input)
|
|
103
|
+
data = @graphql.mutate(UPDATE_MUTATION,
|
|
104
|
+
variables: { id: id.to_s, input: camelize_keys(input) })
|
|
105
|
+
build_customer(data['customerUpdate'])
|
|
106
|
+
end
|
|
107
|
+
|
|
46
108
|
private
|
|
47
109
|
|
|
48
110
|
def fetch_page(first: 25, after: nil, **)
|
|
@@ -55,6 +117,29 @@ module Printavo
|
|
|
55
117
|
end_cursor: page_info['endCursor']
|
|
56
118
|
)
|
|
57
119
|
end
|
|
120
|
+
|
|
121
|
+
# Normalizes a mutation response into the shape Customer.new expects.
|
|
122
|
+
# The mutation returns companyName + nested primaryContact; our model
|
|
123
|
+
# reads company, firstName, lastName, email, phone from the top level.
|
|
124
|
+
def build_customer(attrs)
|
|
125
|
+
return nil unless attrs
|
|
126
|
+
|
|
127
|
+
normalized = attrs.dup
|
|
128
|
+
normalized['company'] ||= attrs['companyName']
|
|
129
|
+
if (contact = attrs['primaryContact'])
|
|
130
|
+
%w[firstName lastName email phone].each do |field|
|
|
131
|
+
normalized[field] ||= contact[field]
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
Printavo::Customer.new(normalized)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Converts snake_case Ruby keyword args to camelCase for GraphQL input.
|
|
138
|
+
def camelize_keys(hash)
|
|
139
|
+
hash.transform_keys do |key|
|
|
140
|
+
key.to_s.gsub(/_([a-z])/) { ::Regexp.last_match(1).upcase }
|
|
141
|
+
end
|
|
142
|
+
end
|
|
58
143
|
end
|
|
59
144
|
end
|
|
60
145
|
end
|
|
@@ -52,6 +52,50 @@ module Printavo
|
|
|
52
52
|
}
|
|
53
53
|
GQL
|
|
54
54
|
|
|
55
|
+
CREATE_MUTATION = <<~GQL.freeze
|
|
56
|
+
mutation InquiryCreate($input: InquiryCreateInput!) {
|
|
57
|
+
inquiryCreate(input: $input) {
|
|
58
|
+
id
|
|
59
|
+
nickname
|
|
60
|
+
totalPrice
|
|
61
|
+
status {
|
|
62
|
+
id
|
|
63
|
+
name
|
|
64
|
+
color
|
|
65
|
+
}
|
|
66
|
+
customer {
|
|
67
|
+
id
|
|
68
|
+
firstName
|
|
69
|
+
lastName
|
|
70
|
+
email
|
|
71
|
+
company
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
GQL
|
|
76
|
+
|
|
77
|
+
UPDATE_MUTATION = <<~GQL.freeze
|
|
78
|
+
mutation InquiryUpdate($id: ID!, $input: InquiryInput!) {
|
|
79
|
+
inquiryUpdate(id: $id, input: $input) {
|
|
80
|
+
id
|
|
81
|
+
nickname
|
|
82
|
+
totalPrice
|
|
83
|
+
status {
|
|
84
|
+
id
|
|
85
|
+
name
|
|
86
|
+
color
|
|
87
|
+
}
|
|
88
|
+
customer {
|
|
89
|
+
id
|
|
90
|
+
firstName
|
|
91
|
+
lastName
|
|
92
|
+
email
|
|
93
|
+
company
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
GQL
|
|
98
|
+
|
|
55
99
|
def all(first: 25, after: nil)
|
|
56
100
|
fetch_page(first: first, after: after).records
|
|
57
101
|
end
|
|
@@ -61,6 +105,31 @@ module Printavo
|
|
|
61
105
|
Printavo::Inquiry.new(data['inquiry'])
|
|
62
106
|
end
|
|
63
107
|
|
|
108
|
+
# Creates a new inquiry. Requires +name:+ at minimum.
|
|
109
|
+
#
|
|
110
|
+
# @return [Printavo::Inquiry]
|
|
111
|
+
#
|
|
112
|
+
# @example
|
|
113
|
+
# client.inquiries.create(name: "Jane Smith", email: "jane@example.com",
|
|
114
|
+
# request: "100 hoodies, front + back print")
|
|
115
|
+
def create(**input)
|
|
116
|
+
data = @graphql.mutate(CREATE_MUTATION, variables: { input: input })
|
|
117
|
+
Printavo::Inquiry.new(data['inquiryCreate'])
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Updates an existing inquiry by ID.
|
|
121
|
+
#
|
|
122
|
+
# @param id [String, Integer]
|
|
123
|
+
# @return [Printavo::Inquiry]
|
|
124
|
+
#
|
|
125
|
+
# @example
|
|
126
|
+
# client.inquiries.update("55", nickname: "Hoodies Rush")
|
|
127
|
+
def update(id, **input)
|
|
128
|
+
data = @graphql.mutate(UPDATE_MUTATION,
|
|
129
|
+
variables: { id: id.to_s, input: input })
|
|
130
|
+
Printavo::Inquiry.new(data['inquiryUpdate'])
|
|
131
|
+
end
|
|
132
|
+
|
|
64
133
|
private
|
|
65
134
|
|
|
66
135
|
def fetch_page(first: 25, after: nil, **)
|
|
@@ -52,6 +52,73 @@ module Printavo
|
|
|
52
52
|
}
|
|
53
53
|
GQL
|
|
54
54
|
|
|
55
|
+
# Printavo creates orders as quotes first; the mutation is quoteCreate.
|
|
56
|
+
CREATE_MUTATION = <<~GQL.freeze
|
|
57
|
+
mutation QuoteCreate($input: QuoteCreateInput!) {
|
|
58
|
+
quoteCreate(input: $input) {
|
|
59
|
+
id
|
|
60
|
+
nickname
|
|
61
|
+
total
|
|
62
|
+
status {
|
|
63
|
+
id
|
|
64
|
+
name
|
|
65
|
+
color
|
|
66
|
+
}
|
|
67
|
+
customer {
|
|
68
|
+
id
|
|
69
|
+
firstName
|
|
70
|
+
lastName
|
|
71
|
+
email
|
|
72
|
+
company
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
GQL
|
|
77
|
+
|
|
78
|
+
UPDATE_MUTATION = <<~GQL.freeze
|
|
79
|
+
mutation QuoteUpdate($id: ID!, $input: QuoteInput!) {
|
|
80
|
+
quoteUpdate(id: $id, input: $input) {
|
|
81
|
+
id
|
|
82
|
+
nickname
|
|
83
|
+
total
|
|
84
|
+
status {
|
|
85
|
+
id
|
|
86
|
+
name
|
|
87
|
+
color
|
|
88
|
+
}
|
|
89
|
+
customer {
|
|
90
|
+
id
|
|
91
|
+
firstName
|
|
92
|
+
lastName
|
|
93
|
+
email
|
|
94
|
+
company
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
GQL
|
|
99
|
+
|
|
100
|
+
# statusUpdate returns an OrderUnion (Quote | Invoice) — requires fragments.
|
|
101
|
+
UPDATE_STATUS_MUTATION = <<~GQL.freeze
|
|
102
|
+
mutation StatusUpdate($parentId: ID!, $statusId: ID!) {
|
|
103
|
+
statusUpdate(parentId: $parentId, statusId: $statusId) {
|
|
104
|
+
... on Quote {
|
|
105
|
+
id
|
|
106
|
+
nickname
|
|
107
|
+
total
|
|
108
|
+
status { id name color }
|
|
109
|
+
customer { id firstName lastName email company }
|
|
110
|
+
}
|
|
111
|
+
... on Invoice {
|
|
112
|
+
id
|
|
113
|
+
nickname
|
|
114
|
+
total
|
|
115
|
+
status { id name color }
|
|
116
|
+
customer { id firstName lastName email company }
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
GQL
|
|
121
|
+
|
|
55
122
|
def all(first: 25, after: nil)
|
|
56
123
|
fetch_page(first: first, after: after).records
|
|
57
124
|
end
|
|
@@ -61,6 +128,50 @@ module Printavo
|
|
|
61
128
|
Printavo::Order.new(data['order'])
|
|
62
129
|
end
|
|
63
130
|
|
|
131
|
+
# Creates a new order (quote) in Printavo.
|
|
132
|
+
# Requires at minimum +contact:+ (IDInput), +due_at:+, and +customer_due_at:+.
|
|
133
|
+
#
|
|
134
|
+
# @return [Printavo::Order]
|
|
135
|
+
#
|
|
136
|
+
# @example
|
|
137
|
+
# client.orders.create(
|
|
138
|
+
# contact: { id: "456" },
|
|
139
|
+
# due_at: "2026-06-01T09:00:00Z",
|
|
140
|
+
# customer_due_at: "2026-06-01"
|
|
141
|
+
# )
|
|
142
|
+
def create(**input)
|
|
143
|
+
data = @graphql.mutate(CREATE_MUTATION, variables: { input: camelize_keys(input) })
|
|
144
|
+
build_order(data['quoteCreate'])
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Updates an existing order (quote or invoice) by ID.
|
|
148
|
+
#
|
|
149
|
+
# @param id [String, Integer]
|
|
150
|
+
# @return [Printavo::Order]
|
|
151
|
+
#
|
|
152
|
+
# @example
|
|
153
|
+
# client.orders.update("99", nickname: "Rush Job", production_note: "Ships Friday")
|
|
154
|
+
def update(id, **input)
|
|
155
|
+
data = @graphql.mutate(UPDATE_MUTATION,
|
|
156
|
+
variables: { id: id.to_s, input: camelize_keys(input) })
|
|
157
|
+
build_order(data['quoteUpdate'])
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# Moves an order to a new status.
|
|
161
|
+
#
|
|
162
|
+
# @param id [String, Integer] order (quote/invoice) ID
|
|
163
|
+
# @param status_id [String, Integer] target status ID
|
|
164
|
+
# @return [Printavo::Order]
|
|
165
|
+
#
|
|
166
|
+
# @example
|
|
167
|
+
# registry = client.statuses.registry
|
|
168
|
+
# client.orders.update_status("99", status_id: registry[:in_production].id)
|
|
169
|
+
def update_status(id, status_id:)
|
|
170
|
+
data = @graphql.mutate(UPDATE_STATUS_MUTATION,
|
|
171
|
+
variables: { parentId: id.to_s, statusId: status_id.to_s })
|
|
172
|
+
build_order(data['statusUpdate'])
|
|
173
|
+
end
|
|
174
|
+
|
|
64
175
|
private
|
|
65
176
|
|
|
66
177
|
def fetch_page(first: 25, after: nil, **)
|
|
@@ -73,6 +184,22 @@ module Printavo
|
|
|
73
184
|
end_cursor: page_info['endCursor']
|
|
74
185
|
)
|
|
75
186
|
end
|
|
187
|
+
|
|
188
|
+
# Normalizes mutation response: quoteCreate/quoteUpdate return `total`
|
|
189
|
+
# rather than `totalPrice`. Map it so Order model accessors work unchanged.
|
|
190
|
+
def build_order(attrs)
|
|
191
|
+
return nil unless attrs
|
|
192
|
+
|
|
193
|
+
normalized = attrs.dup
|
|
194
|
+
normalized['totalPrice'] ||= attrs['total']
|
|
195
|
+
Printavo::Order.new(normalized)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def camelize_keys(hash)
|
|
199
|
+
hash.transform_keys do |key|
|
|
200
|
+
key.to_s.gsub(/_([a-z])/) { ::Regexp.last_match(1).upcase }
|
|
201
|
+
end
|
|
202
|
+
end
|
|
76
203
|
end
|
|
77
204
|
end
|
|
78
205
|
end
|
data/lib/printavo/version.rb
CHANGED