ecomcoder-cli 1.3.1 → 1.3.3
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.
- package/dist/cli.js +21 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/discount/create-basic.d.ts +6 -0
- package/dist/commands/discount/create-basic.d.ts.map +1 -0
- package/dist/commands/discount/create-basic.js +156 -0
- package/dist/commands/discount/create-basic.js.map +1 -0
- package/dist/commands/discount/create-bxgy.d.ts +17 -0
- package/dist/commands/discount/create-bxgy.d.ts.map +1 -0
- package/dist/commands/discount/create-bxgy.js +230 -0
- package/dist/commands/discount/create-bxgy.js.map +1 -0
- package/dist/commands/discount/create-shipping.d.ts +17 -0
- package/dist/commands/discount/create-shipping.d.ts.map +1 -0
- package/dist/commands/discount/create-shipping.js +170 -0
- package/dist/commands/discount/create-shipping.js.map +1 -0
- package/dist/commands/discount/delete.d.ts +6 -0
- package/dist/commands/discount/delete.d.ts.map +1 -0
- package/dist/commands/discount/delete.js +45 -0
- package/dist/commands/discount/delete.js.map +1 -0
- package/dist/commands/discount/index.d.ts +10 -0
- package/dist/commands/discount/index.d.ts.map +1 -0
- package/dist/commands/discount/index.js +98 -0
- package/dist/commands/discount/index.js.map +1 -0
- package/dist/commands/discount/list.d.ts +6 -0
- package/dist/commands/discount/list.d.ts.map +1 -0
- package/dist/commands/discount/list.js +84 -0
- package/dist/commands/discount/list.js.map +1 -0
- package/dist/commands/discount/queries.d.ts +14 -0
- package/dist/commands/discount/queries.d.ts.map +1 -0
- package/dist/commands/discount/queries.js +574 -0
- package/dist/commands/discount/queries.js.map +1 -0
- package/dist/commands/discount/service.d.ts +31 -0
- package/dist/commands/discount/service.d.ts.map +1 -0
- package/dist/commands/discount/service.js +684 -0
- package/dist/commands/discount/service.js.map +1 -0
- package/dist/commands/discount/types.d.ts +228 -0
- package/dist/commands/discount/types.d.ts.map +1 -0
- package/dist/commands/discount/types.js +5 -0
- package/dist/commands/discount/types.js.map +1 -0
- package/dist/commands/discount/utils.d.ts +51 -0
- package/dist/commands/discount/utils.d.ts.map +1 -0
- package/dist/commands/discount/utils.js +153 -0
- package/dist/commands/discount/utils.js.map +1 -0
- package/dist/commands/product/__tests__/update-description.test.js +1 -0
- package/dist/commands/product/__tests__/update-description.test.js.map +1 -1
- package/dist/commands/product/create-variants.d.ts +8 -0
- package/dist/commands/product/create-variants.d.ts.map +1 -0
- package/dist/commands/product/create-variants.js +160 -0
- package/dist/commands/product/create-variants.js.map +1 -0
- package/dist/commands/product/delete-variants.d.ts +8 -0
- package/dist/commands/product/delete-variants.d.ts.map +1 -0
- package/dist/commands/product/delete-variants.js +105 -0
- package/dist/commands/product/delete-variants.js.map +1 -0
- package/dist/commands/product/index.d.ts.map +1 -1
- package/dist/commands/product/index.js +30 -0
- package/dist/commands/product/index.js.map +1 -1
- package/dist/commands/product/queries.d.ts +12 -2
- package/dist/commands/product/queries.d.ts.map +1 -1
- package/dist/commands/product/queries.js +62 -0
- package/dist/commands/product/queries.js.map +1 -1
- package/dist/commands/product/service.d.ts +41 -1
- package/dist/commands/product/service.d.ts.map +1 -1
- package/dist/commands/product/service.js +159 -1
- package/dist/commands/product/service.js.map +1 -1
- package/dist/commands/product/types.d.ts +78 -0
- package/dist/commands/product/types.d.ts.map +1 -1
- package/dist/commands/product/update-title.d.ts +8 -0
- package/dist/commands/product/update-title.d.ts.map +1 -0
- package/dist/commands/product/update-title.js +94 -0
- package/dist/commands/product/update-title.js.map +1 -0
- package/dist/commands/product/utils.d.ts.map +1 -1
- package/dist/commands/product/utils.js +1 -0
- package/dist/commands/product/utils.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,684 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discount Service Layer
|
|
3
|
+
* Business logic for discount operations
|
|
4
|
+
*/
|
|
5
|
+
import { shopifyGraphQL } from '../../lib/shopify-client.js';
|
|
6
|
+
import { DISCOUNT_CODE_BASIC_CREATE, DISCOUNT_AUTOMATIC_BASIC_CREATE, DISCOUNT_CODE_BXGY_CREATE, DISCOUNT_AUTOMATIC_BXGY_CREATE, DISCOUNT_CODE_FREE_SHIPPING_CREATE, DISCOUNT_AUTOMATIC_FREE_SHIPPING_CREATE, CODE_DISCOUNT_NODES, AUTOMATIC_DISCOUNT_NODES, DISCOUNT_CODE_DELETE, DISCOUNT_AUTOMATIC_DELETE } from './queries.js';
|
|
7
|
+
import { validateDiscountValue, validateDiscountCode, parseDateTimeInput, formatDiscountCode, formatUserErrors, formatDiscountForDisplay, buildDiscountQuery } from './utils.js';
|
|
8
|
+
/**
|
|
9
|
+
* Create a basic discount (percentage or fixed amount)
|
|
10
|
+
*/
|
|
11
|
+
export async function createBasicDiscount(input, credentials) {
|
|
12
|
+
// Validate discount value
|
|
13
|
+
const valueValidation = validateDiscountValue(input.type, input.value);
|
|
14
|
+
if (!valueValidation.valid) {
|
|
15
|
+
return { success: false, error: valueValidation.error };
|
|
16
|
+
}
|
|
17
|
+
// Validate code (required for code discounts)
|
|
18
|
+
if (!input.automatic) {
|
|
19
|
+
if (!input.code) {
|
|
20
|
+
return { success: false, error: 'Discount code is required for code-based discounts' };
|
|
21
|
+
}
|
|
22
|
+
const codeValidation = validateDiscountCode(input.code);
|
|
23
|
+
if (!codeValidation.valid) {
|
|
24
|
+
return { success: false, error: codeValidation.error };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// Validate dates
|
|
28
|
+
const startsAtValidation = parseDateTimeInput(input.startsAt);
|
|
29
|
+
if (!startsAtValidation.valid) {
|
|
30
|
+
return { success: false, error: `Start date: ${startsAtValidation.error}` };
|
|
31
|
+
}
|
|
32
|
+
let endsAt;
|
|
33
|
+
if (input.endsAt) {
|
|
34
|
+
const endsAtValidation = parseDateTimeInput(input.endsAt);
|
|
35
|
+
if (!endsAtValidation.valid) {
|
|
36
|
+
return { success: false, error: `End date: ${endsAtValidation.error}` };
|
|
37
|
+
}
|
|
38
|
+
endsAt = endsAtValidation.value;
|
|
39
|
+
}
|
|
40
|
+
// Build customerGets input
|
|
41
|
+
const customerGets = {
|
|
42
|
+
items: {},
|
|
43
|
+
value: {}
|
|
44
|
+
};
|
|
45
|
+
// Set items based on target
|
|
46
|
+
if (input.target === 'all') {
|
|
47
|
+
customerGets.items.all = true;
|
|
48
|
+
}
|
|
49
|
+
else if (input.target === 'collections' && input.targetIds) {
|
|
50
|
+
customerGets.items.collections = {
|
|
51
|
+
add: input.targetIds
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
else if (input.target === 'products' && input.targetIds) {
|
|
55
|
+
customerGets.items.products = {
|
|
56
|
+
add: input.targetIds
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
error: `Invalid target: ${input.target}. Must specify targetIds for collections/products`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
// Set value based on type
|
|
66
|
+
if (input.type === 'percentage') {
|
|
67
|
+
customerGets.value.percentage = parseFloat(input.value.toString());
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
customerGets.value.discountAmount = {
|
|
71
|
+
amount: input.value.toString(),
|
|
72
|
+
appliesOnEachItem: false
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
// Build minimum requirement
|
|
76
|
+
let minimumRequirement = undefined;
|
|
77
|
+
if (input.minSubtotal) {
|
|
78
|
+
minimumRequirement = {
|
|
79
|
+
subtotal: {
|
|
80
|
+
greaterThanOrEqualToSubtotal: input.minSubtotal
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
else if (input.minQuantity) {
|
|
85
|
+
minimumRequirement = {
|
|
86
|
+
quantity: {
|
|
87
|
+
greaterThanOrEqualToQuantity: input.minQuantity
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
// Build combinesWith
|
|
92
|
+
const combinesWith = input.combinesWith || {
|
|
93
|
+
orderDiscounts: true,
|
|
94
|
+
productDiscounts: true,
|
|
95
|
+
shippingDiscounts: true
|
|
96
|
+
};
|
|
97
|
+
// Build context (customer targeting)
|
|
98
|
+
// Context is REQUIRED - must specify all customers or specific segments
|
|
99
|
+
const context = input.customerSegments && input.customerSegments.length > 0
|
|
100
|
+
? {
|
|
101
|
+
customerSegments: {
|
|
102
|
+
add: input.customerSegments
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
: undefined; // Will be handled separately for code vs automatic
|
|
106
|
+
// Choose mutation based on automatic flag
|
|
107
|
+
if (input.automatic) {
|
|
108
|
+
// Automatic discount (context not needed for automatic)
|
|
109
|
+
const variables = {
|
|
110
|
+
automaticBasicDiscount: {
|
|
111
|
+
title: input.title,
|
|
112
|
+
startsAt: startsAtValidation.value,
|
|
113
|
+
endsAt,
|
|
114
|
+
customerGets,
|
|
115
|
+
minimumRequirement,
|
|
116
|
+
combinesWith
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const response = await shopifyGraphQL(credentials, DISCOUNT_AUTOMATIC_BASIC_CREATE, variables);
|
|
120
|
+
// Check for GraphQL errors
|
|
121
|
+
if (response.errors) {
|
|
122
|
+
return {
|
|
123
|
+
success: false,
|
|
124
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (!response.data) {
|
|
128
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
129
|
+
}
|
|
130
|
+
if (response.data.discountAutomaticBasicCreate.userErrors.length > 0) {
|
|
131
|
+
return {
|
|
132
|
+
success: false,
|
|
133
|
+
error: formatUserErrors(response.data.discountAutomaticBasicCreate.userErrors)
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const node = response.data.discountAutomaticBasicCreate.automaticDiscountNode;
|
|
137
|
+
if (!node) {
|
|
138
|
+
return { success: false, error: 'Failed to create automatic discount' };
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
success: true,
|
|
142
|
+
data: {
|
|
143
|
+
id: node.id,
|
|
144
|
+
...node.automaticDiscount
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
// Code discount - context is REQUIRED
|
|
150
|
+
const discountContext = context || {
|
|
151
|
+
all: "ALL" // Enum value for "all customers"
|
|
152
|
+
};
|
|
153
|
+
const variables = {
|
|
154
|
+
basicCodeDiscount: {
|
|
155
|
+
title: input.title,
|
|
156
|
+
code: formatDiscountCode(input.code),
|
|
157
|
+
startsAt: startsAtValidation.value,
|
|
158
|
+
endsAt,
|
|
159
|
+
customerGets,
|
|
160
|
+
minimumRequirement,
|
|
161
|
+
combinesWith,
|
|
162
|
+
context: discountContext,
|
|
163
|
+
usageLimit: input.usageLimit,
|
|
164
|
+
appliesOncePerCustomer: input.appliesOncePerCustomer
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
const response = await shopifyGraphQL(credentials, DISCOUNT_CODE_BASIC_CREATE, variables);
|
|
168
|
+
// Check for GraphQL errors
|
|
169
|
+
if (response.errors) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
if (!response.data) {
|
|
176
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
177
|
+
}
|
|
178
|
+
if (response.data.discountCodeBasicCreate.userErrors.length > 0) {
|
|
179
|
+
return {
|
|
180
|
+
success: false,
|
|
181
|
+
error: formatUserErrors(response.data.discountCodeBasicCreate.userErrors)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const node = response.data.discountCodeBasicCreate.codeDiscountNode;
|
|
185
|
+
if (!node) {
|
|
186
|
+
return { success: false, error: 'Failed to create code discount' };
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
success: true,
|
|
190
|
+
data: {
|
|
191
|
+
id: node.id,
|
|
192
|
+
...node.codeDiscount
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* List discounts with filtering
|
|
199
|
+
*/
|
|
200
|
+
export async function listDiscounts(input, credentials) {
|
|
201
|
+
const limit = input.limit || 50;
|
|
202
|
+
const query = buildDiscountQuery(input.status, input.query);
|
|
203
|
+
const results = [];
|
|
204
|
+
try {
|
|
205
|
+
// Fetch code discounts
|
|
206
|
+
if (input.type === 'code' || input.type === 'all' || !input.type) {
|
|
207
|
+
const variables = {
|
|
208
|
+
first: limit,
|
|
209
|
+
query: query || undefined,
|
|
210
|
+
after: input.after
|
|
211
|
+
};
|
|
212
|
+
const response = await shopifyGraphQL(credentials, CODE_DISCOUNT_NODES, variables);
|
|
213
|
+
if (response.errors) {
|
|
214
|
+
return {
|
|
215
|
+
success: false,
|
|
216
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
if (!response.data) {
|
|
220
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
221
|
+
}
|
|
222
|
+
const codeDiscounts = response.data.codeDiscountNodes.edges.map((edge) => formatDiscountForDisplay(edge.node, false));
|
|
223
|
+
results.push(...codeDiscounts);
|
|
224
|
+
}
|
|
225
|
+
// Fetch automatic discounts
|
|
226
|
+
if (input.type === 'automatic' || input.type === 'all' || !input.type) {
|
|
227
|
+
const variables = {
|
|
228
|
+
first: limit,
|
|
229
|
+
query: query || undefined,
|
|
230
|
+
after: input.after
|
|
231
|
+
};
|
|
232
|
+
const response = await shopifyGraphQL(credentials, AUTOMATIC_DISCOUNT_NODES, variables);
|
|
233
|
+
if (response.errors) {
|
|
234
|
+
return {
|
|
235
|
+
success: false,
|
|
236
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
if (!response.data) {
|
|
240
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
241
|
+
}
|
|
242
|
+
const automaticDiscounts = response.data.automaticDiscountNodes.edges.map((edge) => formatDiscountForDisplay(edge.node, true));
|
|
243
|
+
results.push(...automaticDiscounts);
|
|
244
|
+
}
|
|
245
|
+
return {
|
|
246
|
+
success: true,
|
|
247
|
+
data: {
|
|
248
|
+
discounts: results,
|
|
249
|
+
count: results.length
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
return {
|
|
255
|
+
success: false,
|
|
256
|
+
error: error.message || 'Failed to list discounts'
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Delete a discount by ID
|
|
262
|
+
*/
|
|
263
|
+
export async function deleteDiscount(discountId, credentials) {
|
|
264
|
+
// Determine if it's a code or automatic discount based on GID
|
|
265
|
+
const isCodeDiscount = discountId.includes('DiscountCodeNode');
|
|
266
|
+
const isAutomaticDiscount = discountId.includes('DiscountAutomaticNode');
|
|
267
|
+
if (!isCodeDiscount && !isAutomaticDiscount) {
|
|
268
|
+
return {
|
|
269
|
+
success: false,
|
|
270
|
+
error: 'Invalid discount ID: must be a DiscountCodeNode or DiscountAutomaticNode GID'
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
try {
|
|
274
|
+
if (isCodeDiscount) {
|
|
275
|
+
const response = await shopifyGraphQL(credentials, DISCOUNT_CODE_DELETE, { id: discountId });
|
|
276
|
+
if (response.errors) {
|
|
277
|
+
return {
|
|
278
|
+
success: false,
|
|
279
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
if (!response.data) {
|
|
283
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
284
|
+
}
|
|
285
|
+
if (response.data.discountCodeDelete.userErrors.length > 0) {
|
|
286
|
+
return {
|
|
287
|
+
success: false,
|
|
288
|
+
error: formatUserErrors(response.data.discountCodeDelete.userErrors)
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
return {
|
|
292
|
+
success: true,
|
|
293
|
+
data: {
|
|
294
|
+
deletedId: response.data.discountCodeDelete.deletedCodeDiscountId
|
|
295
|
+
}
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
const response = await shopifyGraphQL(credentials, DISCOUNT_AUTOMATIC_DELETE, { id: discountId });
|
|
300
|
+
if (response.errors) {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
if (!response.data) {
|
|
307
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
308
|
+
}
|
|
309
|
+
if (response.data.discountAutomaticDelete.userErrors.length > 0) {
|
|
310
|
+
return {
|
|
311
|
+
success: false,
|
|
312
|
+
error: formatUserErrors(response.data.discountAutomaticDelete.userErrors)
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
return {
|
|
316
|
+
success: true,
|
|
317
|
+
data: {
|
|
318
|
+
deletedId: response.data.discountAutomaticDelete.deletedAutomaticDiscountId
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch (error) {
|
|
324
|
+
return {
|
|
325
|
+
success: false,
|
|
326
|
+
error: error.message || 'Failed to delete discount'
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Create a BXGY (Buy X Get Y) discount
|
|
332
|
+
* Single Responsibility: Handles BXGY discount creation logic
|
|
333
|
+
* Dependency Inversion: Depends on ShopifyCredentials abstraction
|
|
334
|
+
*/
|
|
335
|
+
export async function createBxgyDiscount(input, credentials) {
|
|
336
|
+
// Validate code (required for code discounts)
|
|
337
|
+
if (!input.automatic) {
|
|
338
|
+
if (!input.code) {
|
|
339
|
+
return { success: false, error: 'Discount code is required for code-based BXGY discounts' };
|
|
340
|
+
}
|
|
341
|
+
const codeValidation = validateDiscountCode(input.code);
|
|
342
|
+
if (!codeValidation.valid) {
|
|
343
|
+
return { success: false, error: codeValidation.error };
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
else if (input.code) {
|
|
347
|
+
return { success: false, error: 'Automatic discounts cannot have codes' };
|
|
348
|
+
}
|
|
349
|
+
// Validate dates
|
|
350
|
+
const startsAtValidation = parseDateTimeInput(input.startsAt);
|
|
351
|
+
if (!startsAtValidation.valid) {
|
|
352
|
+
return { success: false, error: `Start date: ${startsAtValidation.error}` };
|
|
353
|
+
}
|
|
354
|
+
let endsAt;
|
|
355
|
+
if (input.endsAt) {
|
|
356
|
+
const endsAtValidation = parseDateTimeInput(input.endsAt);
|
|
357
|
+
if (!endsAtValidation.valid) {
|
|
358
|
+
return { success: false, error: `End date: ${endsAtValidation.error}` };
|
|
359
|
+
}
|
|
360
|
+
endsAt = endsAtValidation.value;
|
|
361
|
+
}
|
|
362
|
+
// Validate customerBuys has either quantity or amount
|
|
363
|
+
const hasQuantity = input.customerBuys.value.quantity !== undefined;
|
|
364
|
+
const hasAmount = input.customerBuys.value.amount !== undefined;
|
|
365
|
+
if (!hasQuantity && !hasAmount) {
|
|
366
|
+
return {
|
|
367
|
+
success: false,
|
|
368
|
+
error: 'customerBuys must specify either quantity or amount requirement'
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
if (hasQuantity && hasAmount) {
|
|
372
|
+
return {
|
|
373
|
+
success: false,
|
|
374
|
+
error: 'customerBuys cannot specify both quantity and amount - choose one'
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
// Validate customerGets has discountOnQuantity
|
|
378
|
+
if (!input.customerGets.value.discountOnQuantity) {
|
|
379
|
+
return {
|
|
380
|
+
success: false,
|
|
381
|
+
error: 'customerGets must specify discountOnQuantity with quantity and effect'
|
|
382
|
+
};
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
if (!input.automatic) {
|
|
386
|
+
// Code-based BXGY discount
|
|
387
|
+
const discountContext = {
|
|
388
|
+
all: "ALL" // Apply to all customers
|
|
389
|
+
};
|
|
390
|
+
const variables = {
|
|
391
|
+
bxgyCodeDiscount: {
|
|
392
|
+
title: input.title,
|
|
393
|
+
code: formatDiscountCode(input.code),
|
|
394
|
+
startsAt: startsAtValidation.value,
|
|
395
|
+
endsAt,
|
|
396
|
+
customerBuys: input.customerBuys,
|
|
397
|
+
customerGets: input.customerGets,
|
|
398
|
+
usesPerOrderLimit: input.usesPerOrderLimit,
|
|
399
|
+
combinesWith: input.combinesWith || {
|
|
400
|
+
orderDiscounts: false,
|
|
401
|
+
productDiscounts: false,
|
|
402
|
+
shippingDiscounts: false
|
|
403
|
+
},
|
|
404
|
+
context: discountContext
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
const response = await shopifyGraphQL(credentials, DISCOUNT_CODE_BXGY_CREATE, variables);
|
|
408
|
+
// Check for GraphQL errors
|
|
409
|
+
if (response.errors) {
|
|
410
|
+
return {
|
|
411
|
+
success: false,
|
|
412
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
if (!response.data) {
|
|
416
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
417
|
+
}
|
|
418
|
+
if (response.data.discountCodeBxgyCreate.userErrors.length > 0) {
|
|
419
|
+
return {
|
|
420
|
+
success: false,
|
|
421
|
+
error: formatUserErrors(response.data.discountCodeBxgyCreate.userErrors)
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
const node = response.data.discountCodeBxgyCreate.codeDiscountNode;
|
|
425
|
+
if (!node) {
|
|
426
|
+
return { success: false, error: 'Failed to create BXGY code discount' };
|
|
427
|
+
}
|
|
428
|
+
return {
|
|
429
|
+
success: true,
|
|
430
|
+
data: {
|
|
431
|
+
id: node.id,
|
|
432
|
+
title: node.codeDiscount.title,
|
|
433
|
+
code: node.codeDiscount.codes?.edges?.[0]?.node.code,
|
|
434
|
+
type: 'bxgy',
|
|
435
|
+
automatic: false,
|
|
436
|
+
status: node.codeDiscount.status,
|
|
437
|
+
startsAt: node.codeDiscount.startsAt,
|
|
438
|
+
endsAt: node.codeDiscount.endsAt,
|
|
439
|
+
summary: node.codeDiscount.summary
|
|
440
|
+
}
|
|
441
|
+
};
|
|
442
|
+
}
|
|
443
|
+
else {
|
|
444
|
+
// Automatic BXGY discount
|
|
445
|
+
const variables = {
|
|
446
|
+
automaticBxgyDiscount: {
|
|
447
|
+
title: input.title,
|
|
448
|
+
startsAt: startsAtValidation.value,
|
|
449
|
+
endsAt,
|
|
450
|
+
customerBuys: input.customerBuys,
|
|
451
|
+
customerGets: input.customerGets,
|
|
452
|
+
usesPerOrderLimit: input.usesPerOrderLimit,
|
|
453
|
+
combinesWith: input.combinesWith || {
|
|
454
|
+
orderDiscounts: false,
|
|
455
|
+
productDiscounts: false,
|
|
456
|
+
shippingDiscounts: false
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
const response = await shopifyGraphQL(credentials, DISCOUNT_AUTOMATIC_BXGY_CREATE, variables);
|
|
461
|
+
// Check for GraphQL errors
|
|
462
|
+
if (response.errors) {
|
|
463
|
+
return {
|
|
464
|
+
success: false,
|
|
465
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
if (!response.data) {
|
|
469
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
470
|
+
}
|
|
471
|
+
if (response.data.discountAutomaticBxgyCreate.userErrors.length > 0) {
|
|
472
|
+
return {
|
|
473
|
+
success: false,
|
|
474
|
+
error: formatUserErrors(response.data.discountAutomaticBxgyCreate.userErrors)
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
const node = response.data.discountAutomaticBxgyCreate.automaticDiscountNode;
|
|
478
|
+
if (!node) {
|
|
479
|
+
return { success: false, error: 'Failed to create automatic BXGY discount' };
|
|
480
|
+
}
|
|
481
|
+
return {
|
|
482
|
+
success: true,
|
|
483
|
+
data: {
|
|
484
|
+
id: node.id,
|
|
485
|
+
title: node.automaticDiscount.title,
|
|
486
|
+
type: 'bxgy',
|
|
487
|
+
automatic: true,
|
|
488
|
+
status: node.automaticDiscount.status,
|
|
489
|
+
startsAt: node.automaticDiscount.startsAt,
|
|
490
|
+
endsAt: node.automaticDiscount.endsAt,
|
|
491
|
+
summary: node.automaticDiscount.summary
|
|
492
|
+
}
|
|
493
|
+
};
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
catch (error) {
|
|
497
|
+
return {
|
|
498
|
+
success: false,
|
|
499
|
+
error: error.message || 'Failed to create BXGY discount'
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
/**
|
|
504
|
+
* Create a free shipping discount
|
|
505
|
+
* Single Responsibility: Handles free shipping discount creation logic
|
|
506
|
+
* Dependency Inversion: Depends on ShopifyCredentials abstraction
|
|
507
|
+
*/
|
|
508
|
+
export async function createFreeShippingDiscount(input, credentials) {
|
|
509
|
+
// Validate code (required for code discounts)
|
|
510
|
+
if (!input.automatic) {
|
|
511
|
+
if (!input.code) {
|
|
512
|
+
return { success: false, error: 'Discount code is required for code-based free shipping discounts' };
|
|
513
|
+
}
|
|
514
|
+
const codeValidation = validateDiscountCode(input.code);
|
|
515
|
+
if (!codeValidation.valid) {
|
|
516
|
+
return { success: false, error: codeValidation.error };
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
else if (input.code) {
|
|
520
|
+
return { success: false, error: 'Automatic discounts cannot have codes' };
|
|
521
|
+
}
|
|
522
|
+
// Validate dates
|
|
523
|
+
const startsAtValidation = parseDateTimeInput(input.startsAt);
|
|
524
|
+
if (!startsAtValidation.valid) {
|
|
525
|
+
return { success: false, error: `Start date: ${startsAtValidation.error}` };
|
|
526
|
+
}
|
|
527
|
+
let endsAt;
|
|
528
|
+
if (input.endsAt) {
|
|
529
|
+
const endsAtValidation = parseDateTimeInput(input.endsAt);
|
|
530
|
+
if (!endsAtValidation.valid) {
|
|
531
|
+
return { success: false, error: `End date: ${endsAtValidation.error}` };
|
|
532
|
+
}
|
|
533
|
+
endsAt = endsAtValidation.value;
|
|
534
|
+
}
|
|
535
|
+
// Build minimum requirement (optional)
|
|
536
|
+
let minimumRequirement = undefined;
|
|
537
|
+
if (input.minimumRequirement) {
|
|
538
|
+
if (input.minimumRequirement.subtotal) {
|
|
539
|
+
minimumRequirement = {
|
|
540
|
+
subtotal: {
|
|
541
|
+
greaterThanOrEqualToSubtotal: input.minimumRequirement.subtotal
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
else if (input.minimumRequirement.quantity) {
|
|
546
|
+
minimumRequirement = {
|
|
547
|
+
quantity: {
|
|
548
|
+
greaterThanOrEqualToQuantity: input.minimumRequirement.quantity
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
// Build destination (default to all countries)
|
|
554
|
+
let destination = { all: true };
|
|
555
|
+
if (input.destinationSelection) {
|
|
556
|
+
if (input.destinationSelection.countries && input.destinationSelection.countries.length > 0) {
|
|
557
|
+
destination = {
|
|
558
|
+
countries: {
|
|
559
|
+
add: input.destinationSelection.countries
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
else if (input.destinationSelection.all) {
|
|
564
|
+
destination = { all: true };
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
try {
|
|
568
|
+
if (!input.automatic) {
|
|
569
|
+
// Code-based free shipping discount
|
|
570
|
+
const discountContext = {
|
|
571
|
+
all: "ALL" // Apply to all customers
|
|
572
|
+
};
|
|
573
|
+
const variables = {
|
|
574
|
+
freeShippingCodeDiscount: {
|
|
575
|
+
title: input.title,
|
|
576
|
+
code: formatDiscountCode(input.code),
|
|
577
|
+
startsAt: startsAtValidation.value,
|
|
578
|
+
endsAt,
|
|
579
|
+
minimumRequirement,
|
|
580
|
+
destination,
|
|
581
|
+
combinesWith: input.combinesWith || {
|
|
582
|
+
orderDiscounts: false,
|
|
583
|
+
productDiscounts: false,
|
|
584
|
+
shippingDiscounts: false
|
|
585
|
+
},
|
|
586
|
+
context: discountContext
|
|
587
|
+
}
|
|
588
|
+
};
|
|
589
|
+
const response = await shopifyGraphQL(credentials, DISCOUNT_CODE_FREE_SHIPPING_CREATE, variables);
|
|
590
|
+
// Check for GraphQL errors
|
|
591
|
+
if (response.errors) {
|
|
592
|
+
return {
|
|
593
|
+
success: false,
|
|
594
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
if (!response.data) {
|
|
598
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
599
|
+
}
|
|
600
|
+
if (response.data.discountCodeFreeShippingCreate.userErrors.length > 0) {
|
|
601
|
+
return {
|
|
602
|
+
success: false,
|
|
603
|
+
error: formatUserErrors(response.data.discountCodeFreeShippingCreate.userErrors)
|
|
604
|
+
};
|
|
605
|
+
}
|
|
606
|
+
const node = response.data.discountCodeFreeShippingCreate.codeDiscountNode;
|
|
607
|
+
if (!node) {
|
|
608
|
+
return { success: false, error: 'Failed to create free shipping code discount' };
|
|
609
|
+
}
|
|
610
|
+
return {
|
|
611
|
+
success: true,
|
|
612
|
+
data: {
|
|
613
|
+
id: node.id,
|
|
614
|
+
title: node.codeDiscount.title,
|
|
615
|
+
code: node.codeDiscount.codes?.edges?.[0]?.node.code,
|
|
616
|
+
type: 'free_shipping',
|
|
617
|
+
automatic: false,
|
|
618
|
+
status: node.codeDiscount.status,
|
|
619
|
+
startsAt: node.codeDiscount.startsAt,
|
|
620
|
+
endsAt: node.codeDiscount.endsAt,
|
|
621
|
+
summary: node.codeDiscount.summary
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
// Automatic free shipping discount
|
|
627
|
+
const variables = {
|
|
628
|
+
freeShippingAutomaticDiscount: {
|
|
629
|
+
title: input.title,
|
|
630
|
+
startsAt: startsAtValidation.value,
|
|
631
|
+
endsAt,
|
|
632
|
+
minimumRequirement,
|
|
633
|
+
destination,
|
|
634
|
+
combinesWith: input.combinesWith || {
|
|
635
|
+
orderDiscounts: false,
|
|
636
|
+
productDiscounts: false,
|
|
637
|
+
shippingDiscounts: false
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
const response = await shopifyGraphQL(credentials, DISCOUNT_AUTOMATIC_FREE_SHIPPING_CREATE, variables);
|
|
642
|
+
// Check for GraphQL errors
|
|
643
|
+
if (response.errors) {
|
|
644
|
+
return {
|
|
645
|
+
success: false,
|
|
646
|
+
error: response.errors.map(e => e.message).join('; ')
|
|
647
|
+
};
|
|
648
|
+
}
|
|
649
|
+
if (!response.data) {
|
|
650
|
+
return { success: false, error: 'No data returned from Shopify API' };
|
|
651
|
+
}
|
|
652
|
+
if (response.data.discountAutomaticFreeShippingCreate.userErrors.length > 0) {
|
|
653
|
+
return {
|
|
654
|
+
success: false,
|
|
655
|
+
error: formatUserErrors(response.data.discountAutomaticFreeShippingCreate.userErrors)
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
const node = response.data.discountAutomaticFreeShippingCreate.automaticDiscountNode;
|
|
659
|
+
if (!node) {
|
|
660
|
+
return { success: false, error: 'Failed to create automatic free shipping discount' };
|
|
661
|
+
}
|
|
662
|
+
return {
|
|
663
|
+
success: true,
|
|
664
|
+
data: {
|
|
665
|
+
id: node.id,
|
|
666
|
+
title: node.automaticDiscount.title,
|
|
667
|
+
type: 'free_shipping',
|
|
668
|
+
automatic: true,
|
|
669
|
+
status: node.automaticDiscount.status,
|
|
670
|
+
startsAt: node.automaticDiscount.startsAt,
|
|
671
|
+
endsAt: node.automaticDiscount.endsAt,
|
|
672
|
+
summary: node.automaticDiscount.summary
|
|
673
|
+
}
|
|
674
|
+
};
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
catch (error) {
|
|
678
|
+
return {
|
|
679
|
+
success: false,
|
|
680
|
+
error: error.message || 'Failed to create free shipping discount'
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
//# sourceMappingURL=service.js.map
|