commerce_tools 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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f6387743307e316b919e263c931016c71e50c16555a99c5a0328018a14ae6ebf
4
+ data.tar.gz: 99717e4bbb24977bf3b344ba86473670f18c9d499c617431907c51d60d5de7d6
5
+ SHA512:
6
+ metadata.gz: d473c5497fbcec35b51b6bd1cd367b374fa6d36f558795527b044af97edf74675d35fc794b55cc1386d7f1619be742df7d7a87ac2b4bd5854354270ef3880f3b
7
+ data.tar.gz: 8a02ab332968b7ece9a4c20f3144900ec0d2bae8f19f8bbea7efae762f332960817d4b1439681dfcf001bf2f32b28eed031af7f1ec9915ca4b60ef121852c3ac
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.1.0] - 2025-01-01
4
+
5
+ - Initial release
data/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # CommerceTools
2
+
3
+ TODO: Delete this and the text below, and describe your gem
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/commerce_tools`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ ## Installation
8
+
9
+ TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
10
+
11
+ Install the gem and add to the application's Gemfile by executing:
12
+
13
+ ```bash
14
+ bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
15
+ ```
16
+
17
+ If bundler is not being used to manage dependencies, install the gem by executing:
18
+
19
+ ```bash
20
+ gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
21
+ ```
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ 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.
30
+
31
+ 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 the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/commerce_tools.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,247 @@
1
+ # Copyright (c) 2025 Thnk: Labs
2
+ # Author: J
3
+ #
4
+ # All rights reserved. This software and associated documentation files (the "Software")
5
+ # may not be used, copied, modified, merged, published, distributed, sublicensed, and/or
6
+ # sold, except with the express written permission of Thnk: Labs. Unauthorized copying of this
7
+ # file, via any medium is strictly prohibited.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
11
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
12
+ # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
13
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
14
+ # DEALINGS IN THE SOFTWARE.
15
+
16
+ require 'shopify_api'
17
+
18
+ module CommerceTools
19
+ class InventoryManager
20
+ VALID_REASONS = %w[
21
+ correction cycle_count_available damaged movement_created
22
+ movement_updated movement_received movement_canceled other
23
+ promotion quality_control received reservation_created
24
+ reservation_deleted reservation_updated restock safety_stock shrinkage
25
+ ].freeze
26
+
27
+ VALID_STATES = %w[
28
+ available on_hand incoming committed reserved
29
+ damaged safety_stock quality_control
30
+ ].freeze
31
+
32
+ def initialize(shop_url:, access_token:)
33
+ @session = ShopifyAPI::Auth::Session.new(
34
+ shop: shop_url,
35
+ access_token: access_token
36
+ )
37
+ @client = ShopifyAPI::Clients::Graphql::Admin.new(session: @session)
38
+ end
39
+
40
+ def adjust_inventory_level(inventory_item_id:, location_id:, delta:, state: 'available', reason: 'correction', reference_uri: nil)
41
+ validate_inputs!(state: state, reason: reason)
42
+
43
+ variables = build_adjustment_variables(
44
+ inventory_item_id: inventory_item_id,
45
+ location_id: location_id,
46
+ delta: delta,
47
+ state: state,
48
+ reason: reason,
49
+ reference_uri: reference_uri
50
+ )
51
+
52
+ response = execute_mutation(adjustment_mutation, variables)
53
+ handle_response(response)
54
+ end
55
+
56
+ def move_inventory(
57
+ inventory_item_id:,
58
+ location_id:,
59
+ quantity:,
60
+ from_state:,
61
+ to_state:,
62
+ reason: 'correction',
63
+ reference_uri: nil,
64
+ ledger_uri: nil
65
+ )
66
+ validate_inputs!(state: from_state, reason: reason)
67
+ validate_inputs!(state: to_state)
68
+
69
+ variables = build_move_variables(
70
+ inventory_item_id: inventory_item_id,
71
+ location_id: location_id,
72
+ quantity: quantity,
73
+ from_state: from_state,
74
+ to_state: to_state,
75
+ reason: reason,
76
+ reference_uri: reference_uri,
77
+ ledger_uri: ledger_uri
78
+ )
79
+
80
+ response = execute_mutation(move_mutation, variables)
81
+ handle_response(response)
82
+ end
83
+
84
+ def get_inventory_level(inventory_item_id:, location_id:, states: ['available'])
85
+ validate_states!(states)
86
+
87
+ variables = {
88
+ "id": "gid://shopify/InventoryLevel/#{location_id}?inventory_item_id=#{inventory_item_id}"
89
+ }
90
+
91
+ response = execute_query(inventory_level_query, variables)
92
+ handle_response(response)
93
+ end
94
+
95
+ private
96
+
97
+ def validate_inputs!(state:, reason: nil)
98
+ unless VALID_STATES.include?(state)
99
+ raise ArgumentError, "Invalid state: #{state}. Must be one of: #{VALID_STATES.join(', ')}"
100
+ end
101
+
102
+ if reason && !VALID_REASONS.include?(reason)
103
+ raise ArgumentError, "Invalid reason: #{reason}. Must be one of: #{VALID_REASONS.join(', ')}"
104
+ end
105
+ end
106
+
107
+ def validate_states!(states)
108
+ invalid_states = states - VALID_STATES
109
+ unless invalid_states.empty?
110
+ raise ArgumentError, "Invalid states: #{invalid_states.join(', ')}"
111
+ end
112
+ end
113
+
114
+ def build_adjustment_variables(inventory_item_id:, location_id:, delta:, state:, reason:, reference_uri:)
115
+ {
116
+ "input": {
117
+ "name": state,
118
+ "reason": reason,
119
+ "referenceDocumentUri": reference_uri,
120
+ "changes": [
121
+ {
122
+ "delta": delta,
123
+ "inventoryItemId": inventory_item_id,
124
+ "locationId": location_id
125
+ }
126
+ ]
127
+ }
128
+ }
129
+ end
130
+
131
+ def build_move_variables(inventory_item_id:, location_id:, quantity:, from_state:, to_state:, reason:, reference_uri:, ledger_uri:)
132
+ {
133
+ "input": {
134
+ "reason": reason,
135
+ "referenceDocumentUri": reference_uri,
136
+ "changes": [
137
+ {
138
+ "inventoryItemId": inventory_item_id,
139
+ "quantity": quantity,
140
+ "from": {
141
+ "name": from_state,
142
+ "locationId": location_id
143
+ },
144
+ "to": {
145
+ "name": to_state,
146
+ "locationId": location_id,
147
+ "ledgerDocumentUri": ledger_uri
148
+ }
149
+ }
150
+ ]
151
+ }
152
+ }
153
+ end
154
+
155
+ def adjustment_mutation
156
+ <<~GRAPHQL
157
+ mutation inventoryAdjustQuantities($input: InventoryAdjustQuantitiesInput!) {
158
+ inventoryAdjustQuantities(input: $input) {
159
+ userErrors {
160
+ field
161
+ message
162
+ }
163
+ inventoryAdjustmentGroup {
164
+ createdAt
165
+ reason
166
+ changes {
167
+ name
168
+ delta
169
+ quantityAfterChange
170
+ }
171
+ }
172
+ }
173
+ }
174
+ GRAPHQL
175
+ end
176
+
177
+ def move_mutation
178
+ <<~GRAPHQL
179
+ mutation inventoryMoveQuantities($input: InventoryMoveQuantitiesInput!) {
180
+ inventoryMoveQuantities(input: $input) {
181
+ userErrors {
182
+ field
183
+ message
184
+ }
185
+ inventoryAdjustmentGroup {
186
+ createdAt
187
+ reason
188
+ changes {
189
+ name
190
+ delta
191
+ quantityAfterChange
192
+ }
193
+ }
194
+ }
195
+ }
196
+ GRAPHQL
197
+ end
198
+
199
+ def inventory_level_query
200
+ <<~GRAPHQL
201
+ query($id: ID!) {
202
+ inventoryLevel(id: $id) {
203
+ quantities(names: $states) {
204
+ name
205
+ quantity
206
+ }
207
+ item {
208
+ id
209
+ }
210
+ location {
211
+ id
212
+ }
213
+ }
214
+ }
215
+ GRAPHQL
216
+ end
217
+
218
+ def execute_mutation(mutation, variables)
219
+ @client.query(query: mutation, variables: variables)
220
+ rescue StandardError => e
221
+ raise InventoryError, "Failed to execute mutation: #{e.message}"
222
+ end
223
+
224
+ def execute_query(query, variables)
225
+ @client.query(query: query, variables: variables)
226
+ rescue StandardError => e
227
+ raise InventoryError, "Failed to execute query: #{e.message}"
228
+ end
229
+
230
+ def handle_response(response)
231
+ if response.errors.any?
232
+ raise InventoryError, "GraphQL errors: #{response.errors.messages.join(', ')}"
233
+ end
234
+
235
+ data = response.body['data']
236
+ user_errors = data.values.first['userErrors']
237
+
238
+ if user_errors.any?
239
+ raise InventoryError, "User errors: #{user_errors.map { |e| e['message'] }.join(', ')}"
240
+ end
241
+
242
+ data
243
+ end
244
+ end
245
+
246
+ class InventoryError < StandardError; end
247
+ end
@@ -0,0 +1,21 @@
1
+ # Copyright (c) 2025 Thnk: Labs
2
+ # Author: J
3
+ #
4
+ # All rights reserved. This software and associated documentation files (the "Software")
5
+ # may not be used, copied, modified, merged, published, distributed, sublicensed, and/or
6
+ # sold, except with the express written permission of Thnk: Labs. Unauthorized copying of this
7
+ # file, via any medium is strictly prohibited.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
11
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
12
+ # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
13
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
14
+ # DEALINGS IN THE SOFTWARE.
15
+
16
+
17
+ # frozen_string_literal: true
18
+
19
+ module CommerceTools
20
+ VERSION = "0.1.0"
21
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright (c) 2025 Thnk: Labs
2
+ # Author: J
3
+ #
4
+ # All rights reserved. This software and associated documentation files (the "Software")
5
+ # may not be used, copied, modified, merged, published, distributed, sublicensed, and/or
6
+ # sold, except with the express written permission of Thnk: Labs. Unauthorized copying of this
7
+ # file, via any medium is strictly prohibited.
8
+ #
9
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
10
+ # INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
11
+ # PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
12
+ # FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
13
+ # OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
14
+ # DEALINGS IN THE SOFTWARE.
15
+
16
+
17
+ # frozen_string_literal: true
18
+
19
+ require_relative "commerce_tools/version"
20
+ require_relative 'commerce_tools/inventory_manager'
21
+
22
+ module CommerceTools
23
+ class Error < StandardError; end
24
+ # Your code goes here...
25
+ end
@@ -0,0 +1,4 @@
1
+ module CommerceTools
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: commerce_tools
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - thnkr-one
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2025-02-03 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Write a longer description or delete this line.
14
+ email:
15
+ - jacob@thnk.com
16
+ executables: []
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - ".rspec"
21
+ - CHANGELOG.md
22
+ - README.md
23
+ - Rakefile
24
+ - lib/commerce_tools.rb
25
+ - lib/commerce_tools/inventory_manager.rb
26
+ - lib/commerce_tools/version.rb
27
+ - sig/commerce_tools.rbs
28
+ homepage:
29
+ licenses: []
30
+ metadata: {}
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 3.0.0
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ requirements: []
46
+ rubygems_version: 3.5.22
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: Write a short summary, because RubyGems requires one.
50
+ test_files: []