@aglyn/plugins-commerce 1.0.0-beta.221 → 1.0.0-beta.223
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/README.md +4 -2
- package/package.json +14 -13
- package/src/lib/components/console/catalog-organization-card.component.js +9 -0
- package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
- package/src/lib/components/console/discounts-card.component.js +8 -0
- package/src/lib/components/console/discounts-card.component.js.map +1 -1
- package/src/lib/components/console/gift-cards-card.component.js +8 -0
- package/src/lib/components/console/gift-cards-card.component.js.map +1 -1
- package/src/lib/components/console/host-coupons-card.component.js +8 -0
- package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
- package/src/lib/components/console/host-orders-card.component.js +34 -70
- package/src/lib/components/console/host-orders-card.component.js.map +1 -1
- package/src/lib/components/console/product-zones.d.ts +7 -6
- package/src/lib/components/console/product-zones.js +5 -4
- package/src/lib/components/console/product-zones.js.map +1 -1
- package/src/lib/components/console/products-hub-card.component.js +86 -284
- package/src/lib/components/console/products-hub-card.component.js.map +1 -1
- package/src/lib/declarations.console-server.d.ts +38 -0
- package/src/lib/declarations.console-server.js +67 -0
- package/src/lib/declarations.console-server.js.map +1 -0
- package/src/lib/model/commerce-gift-cards.d.ts +26 -0
- package/src/lib/model/commerce-gift-cards.js +42 -0
- package/src/lib/model/commerce-gift-cards.js.map +1 -1
- package/src/lib/model/commerce-orders.d.ts +0 -57
- package/src/lib/model/commerce-orders.js +0 -80
- package/src/lib/model/commerce-orders.js.map +1 -1
- package/src/lib/model/index.d.ts +0 -1
- package/src/lib/model/index.js +0 -1
- package/src/lib/model/index.js.map +1 -1
- package/src/lib/plugin.js +5 -0
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server/api-v1/openapi.d.ts +27 -0
- package/src/lib/server/api-v1/openapi.js +219 -0
- package/src/lib/server/api-v1/openapi.js.map +1 -0
- package/src/lib/server/api-v1/orders-and-products.d.ts +19 -0
- package/src/lib/server/api-v1/orders-and-products.js +419 -0
- package/src/lib/server/api-v1/orders-and-products.js.map +1 -0
- package/src/lib/server/billing-webhook.js +10 -13
- package/src/lib/server/billing-webhook.js.map +1 -1
- package/src/lib/server/download.js +4 -1
- package/src/lib/server/download.js.map +1 -1
- package/src/lib/server/fulfill-order.d.ts +52 -11
- package/src/lib/server/fulfill-order.js +6 -9
- package/src/lib/server/fulfill-order.js.map +1 -1
- package/src/lib/server/gift-card-issue.d.ts +57 -0
- package/src/lib/server/gift-card-issue.js +155 -0
- package/src/lib/server/gift-card-issue.js.map +1 -0
- package/src/lib/server/gift-cards.js +25 -84
- package/src/lib/server/gift-cards.js.map +1 -1
- package/src/lib/server/membership-register.js +9 -8
- package/src/lib/server/membership-register.js.map +1 -1
- package/src/lib/server/newsletter.js +7 -5
- package/src/lib/server/newsletter.js.map +1 -1
- package/src/lib/server/paid-media-delivery.d.ts +21 -0
- package/src/lib/server/paid-media-delivery.js +23 -5
- package/src/lib/server/paid-media-delivery.js.map +1 -1
- package/src/lib/server/paid-media-serve-count.d.ts +28 -0
- package/src/lib/server/paid-media-serve-count.js +46 -0
- package/src/lib/server/paid-media-serve-count.js.map +1 -0
- package/src/lib/server/person-eraser.d.ts +45 -0
- package/src/lib/server/person-eraser.js +57 -0
- package/src/lib/server/person-eraser.js.map +1 -0
- package/src/lib/server/refund.js +15 -15
- package/src/lib/server/refund.js.map +1 -1
- package/src/lib/server/restock-flag.d.ts +2 -2
- package/src/lib/server/restock-flag.js +1 -1
- package/src/lib/server/restock-flag.js.map +1 -1
- package/src/lib/server/stream.js +4 -1
- package/src/lib/server/stream.js.map +1 -1
- package/src/lib/server.js +5 -21
- package/src/lib/server.js.map +1 -1
- package/src/lib/testing/stand-in-person-records.d.ts +43 -0
- package/src/lib/testing/stand-in-person-records.js +63 -0
- package/src/lib/testing/stand-in-person-records.js.map +1 -0
- package/src/lib/transfer/gift-card-confirm-step.component.d.ts +22 -0
- package/src/lib/transfer/gift-card-confirm-step.component.js +259 -0
- package/src/lib/transfer/gift-card-confirm-step.component.js.map +1 -0
- package/src/lib/transfer/gift-cards-plan.d.ts +29 -0
- package/src/lib/transfer/gift-cards-plan.js +40 -0
- package/src/lib/transfer/gift-cards-plan.js.map +1 -0
- package/src/lib/transfer/gift-cards.server.d.ts +34 -0
- package/src/lib/transfer/gift-cards.server.js +296 -0
- package/src/lib/transfer/gift-cards.server.js.map +1 -0
- package/src/lib/transfer/list-filter.d.ts +29 -0
- package/src/lib/transfer/list-filter.js +112 -0
- package/src/lib/transfer/list-filter.js.map +1 -0
- package/src/lib/transfer/product-import-results.d.ts +29 -0
- package/src/lib/transfer/product-import-results.js +49 -0
- package/src/lib/transfer/product-import-results.js.map +1 -0
- package/src/lib/transfer/product-import-step.component.d.ts +30 -0
- package/src/lib/transfer/product-import-step.component.js +99 -0
- package/src/lib/transfer/product-import-step.component.js.map +1 -0
- package/src/lib/transfer/product-transfer.d.ts +211 -0
- package/src/lib/transfer/product-transfer.js +1594 -0
- package/src/lib/transfer/product-transfer.js.map +1 -0
- package/src/lib/transfer/products.server.d.ts +44 -0
- package/src/lib/transfer/products.server.js +550 -0
- package/src/lib/transfer/products.server.js.map +1 -0
- package/src/lib/transfer/records-transfer.d.ts +182 -0
- package/src/lib/transfer/records-transfer.js +1153 -0
- package/src/lib/transfer/records-transfer.js.map +1 -0
- package/src/lib/transfer/records.server.d.ts +68 -0
- package/src/lib/transfer/records.server.js +455 -0
- package/src/lib/transfer/records.server.js.map +1 -0
- package/src/lib/transfer/register-transfer-resources.d.ts +17 -0
- package/src/lib/transfer/register-transfer-resources.js +171 -0
- package/src/lib/transfer/register-transfer-resources.js.map +1 -0
- package/src/lib/transfer/register-transfer-ui.d.ts +23 -0
- package/src/lib/transfer/register-transfer-ui.js +78 -0
- package/src/lib/transfer/register-transfer-ui.js.map +1 -0
- package/src/lib/transfer/server-common.d.ts +76 -0
- package/src/lib/transfer/server-common.js +165 -0
- package/src/lib/transfer/server-common.js.map +1 -0
- package/src/lib/transfer/transfer-header-actions.component.d.ts +39 -0
- package/src/lib/transfer/transfer-header-actions.component.js +79 -0
- package/src/lib/transfer/transfer-header-actions.component.js.map +1 -0
- package/src/lib/transfer/transfer-keys.d.ts +31 -0
- package/src/lib/transfer/transfer-keys.js +56 -0
- package/src/lib/transfer/transfer-keys.js.map +1 -0
- package/src/lib/model/commerce-io.d.ts +0 -36
- package/src/lib/model/commerce-io.js +0 -245
- package/src/lib/model/commerce-io.js.map +0 -1
- package/src/lib/server/contact-refund.d.ts +0 -117
- package/src/lib/server/contact-refund.js +0 -196
- package/src/lib/server/contact-refund.js.map +0 -1
|
@@ -0,0 +1,1594 @@
|
|
|
1
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
2
|
+
import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
|
|
3
|
+
/**
|
|
4
|
+
* @license
|
|
5
|
+
* Copyright 2026 Aglyn LLC
|
|
6
|
+
*
|
|
7
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
8
|
+
* you may not use this file except in compliance with the License.
|
|
9
|
+
* You may obtain a copy of the License at
|
|
10
|
+
*
|
|
11
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
12
|
+
*
|
|
13
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
14
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
15
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
16
|
+
* See the License for the specific language governing permissions and
|
|
17
|
+
* limitations under the License.
|
|
18
|
+
*/ import { buildTransferPlan, isBlankTransferValue, TRANSFER_SYSTEM_GROUP, TRANSFER_WARNING_CLASSES } from "@aglyn/aglyn/data-transfer";
|
|
19
|
+
import { COMMERCE_MAX_VARIANTS, COMMERCE_SLUG_MAX_LENGTH, commerceSlug, productInventory, productPriceRange, validateProduct } from "../model/commerce.js";
|
|
20
|
+
/*
|
|
21
|
+
* PRODUCTS ON THE TRANSFER FRAMEWORK (AGL-3531).
|
|
22
|
+
*
|
|
23
|
+
* ## One product, one row per variant
|
|
24
|
+
*
|
|
25
|
+
* A product's file is laid out the way storefront platforms lay theirs out:
|
|
26
|
+
* a row per variant, the product's own fields (title, description, tags…)
|
|
27
|
+
* on its first row, and every row carrying the product's handle. Images
|
|
28
|
+
* follow the same convention — the first on the first row, the next on the
|
|
29
|
+
* second, and rows of only a handle and an image when there are more images
|
|
30
|
+
* than variants. So a store moving here from Shopify uploads its own export
|
|
31
|
+
* as it is, and the Shopify preset writes one Shopify reads back.
|
|
32
|
+
*
|
|
33
|
+
* ## Rows fold into products
|
|
34
|
+
*
|
|
35
|
+
* The engine plans rows; a product is several. `planProductTransfer` (this
|
|
36
|
+
* resource's `plan` hook) folds the rows of one handle into one product: the
|
|
37
|
+
* first row decides whether the product is new or which one it is (by
|
|
38
|
+
* handle, then SKU, then ID — the person's order), its product fields are
|
|
39
|
+
* planned against the product, and every row's variant fields are planned
|
|
40
|
+
* against the variant it names — by SKU, then by its option values, then the
|
|
41
|
+
* single variant of a single-variant product. A match is UPDATED; the old
|
|
42
|
+
* importer created a second product under a suffixed slug.
|
|
43
|
+
*
|
|
44
|
+
* Every row keeps its own verdict and diff, so the dry run shows what each
|
|
45
|
+
* line of the file does, and the plan stashes what the apply needs on the
|
|
46
|
+
* row (`commerce`): the product's id — minted here for a new one, so a retry
|
|
47
|
+
* finds what it already wrote — the variant's, and for a new product the
|
|
48
|
+
* whole document.
|
|
49
|
+
*
|
|
50
|
+
* ## What a file never sets
|
|
51
|
+
*
|
|
52
|
+
* Option names already on a product (renaming one moves every variant, which
|
|
53
|
+
* the product editor does), stock kept by location (moved per location), and
|
|
54
|
+
* the search, stock and collection keys every write derives.
|
|
55
|
+
*/ /*==========================================
|
|
56
|
+
* FIELDS
|
|
57
|
+
*=========================================*/ /** The field ids, as the catalog and every file name them. */ export const PRODUCT_FIELD = {
|
|
58
|
+
handle: 'handle',
|
|
59
|
+
title: 'title',
|
|
60
|
+
description: 'description',
|
|
61
|
+
kind: 'type',
|
|
62
|
+
status: 'status',
|
|
63
|
+
published: 'published',
|
|
64
|
+
tags: 'tags',
|
|
65
|
+
categories: 'categories',
|
|
66
|
+
option1Name: 'option1Name',
|
|
67
|
+
option2Name: 'option2Name',
|
|
68
|
+
option3Name: 'option3Name',
|
|
69
|
+
image: 'image',
|
|
70
|
+
seoTitle: 'seoTitle',
|
|
71
|
+
seoDescription: 'seoDescription',
|
|
72
|
+
taxExempt: 'taxExempt',
|
|
73
|
+
oversellPolicy: 'oversellPolicy',
|
|
74
|
+
lowStockThreshold: 'lowStockThreshold',
|
|
75
|
+
option1Value: 'option1Value',
|
|
76
|
+
option2Value: 'option2Value',
|
|
77
|
+
option3Value: 'option3Value',
|
|
78
|
+
sku: 'sku',
|
|
79
|
+
barcode: 'barcode',
|
|
80
|
+
price: 'price',
|
|
81
|
+
compareAtPrice: 'compareAtPrice',
|
|
82
|
+
inventory: 'inventory',
|
|
83
|
+
weightGrams: 'weightGrams',
|
|
84
|
+
variantImage: 'variantImage',
|
|
85
|
+
priceFrom: 'priceFrom',
|
|
86
|
+
totalInventory: 'totalInventory',
|
|
87
|
+
soldOut: 'soldOut',
|
|
88
|
+
variantCount: 'variantCount',
|
|
89
|
+
createdAt: 'createdAt',
|
|
90
|
+
updatedAt: 'updatedAt'
|
|
91
|
+
};
|
|
92
|
+
const F = PRODUCT_FIELD;
|
|
93
|
+
/** The picklists the product fields name. */ export const PRODUCT_KIND_PICKLIST = 'commerce.productKind';
|
|
94
|
+
export const PRODUCT_STATUS_PICKLIST = 'commerce.productStatus';
|
|
95
|
+
export const PRODUCT_OVERSELL_PICKLIST = 'commerce.oversellPolicy';
|
|
96
|
+
export const PRODUCT_CATEGORY_PICKLIST = 'commerce.categories';
|
|
97
|
+
const KIND_LABELS = {
|
|
98
|
+
physical: 'Physical',
|
|
99
|
+
digital: 'Digital',
|
|
100
|
+
service: 'Service'
|
|
101
|
+
};
|
|
102
|
+
const STATUS_LABELS = {
|
|
103
|
+
active: 'Active',
|
|
104
|
+
draft: 'Draft',
|
|
105
|
+
archived: 'Archived'
|
|
106
|
+
};
|
|
107
|
+
const OVERSELL_LABELS = {
|
|
108
|
+
deny: 'Stop selling',
|
|
109
|
+
backorder: 'Keep selling'
|
|
110
|
+
};
|
|
111
|
+
/** A fixed list as the values step matches it: a file adds nothing to it. */ function fixedList(labels, defaultValueId) {
|
|
112
|
+
return {
|
|
113
|
+
restricted: true,
|
|
114
|
+
standardValues: Object.entries(labels).map(([id, label])=>({
|
|
115
|
+
id,
|
|
116
|
+
label
|
|
117
|
+
})),
|
|
118
|
+
defaultValueId
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/** The fixed lists products name, by picklist id. */ export const PRODUCT_FIXED_PICKLISTS = {
|
|
122
|
+
[PRODUCT_KIND_PICKLIST]: fixedList(KIND_LABELS, 'physical'),
|
|
123
|
+
[PRODUCT_STATUS_PICKLIST]: fixedList(STATUS_LABELS, 'active'),
|
|
124
|
+
[PRODUCT_OVERSELL_PICKLIST]: fixedList(OVERSELL_LABELS, 'deny')
|
|
125
|
+
};
|
|
126
|
+
/** The id a label of a fixed list names, by label or id in any case; `null` for none. */ export function fixedListId(picklistId, value) {
|
|
127
|
+
const spec = PRODUCT_FIXED_PICKLISTS[picklistId];
|
|
128
|
+
const key = String(value != null ? value : '').trim().toLowerCase();
|
|
129
|
+
if (!spec || !key) return null;
|
|
130
|
+
const found = spec.standardValues.find((entry)=>entry.id.toLowerCase() === key || entry.label.toLowerCase() === key);
|
|
131
|
+
return found ? found.id : null;
|
|
132
|
+
}
|
|
133
|
+
/** A fixed list's label for a stored id. */ function fixedListLabel(picklistId, id) {
|
|
134
|
+
var _ref;
|
|
135
|
+
var _spec_standardValues_find;
|
|
136
|
+
const spec = PRODUCT_FIXED_PICKLISTS[picklistId];
|
|
137
|
+
return (_ref = spec == null ? void 0 : (_spec_standardValues_find = spec.standardValues.find((entry)=>entry.id === id)) == null ? void 0 : _spec_standardValues_find.label) != null ? _ref : null;
|
|
138
|
+
}
|
|
139
|
+
export const PRODUCT_TRANSFER_GROUPS = [
|
|
140
|
+
{
|
|
141
|
+
id: 'product',
|
|
142
|
+
label: 'Product'
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
id: 'variant',
|
|
146
|
+
label: 'Variant'
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
id: 'options',
|
|
150
|
+
label: 'Options'
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
id: 'media',
|
|
154
|
+
label: 'Images'
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 'selling',
|
|
158
|
+
label: 'Selling'
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
id: 'seo',
|
|
162
|
+
label: 'Search listing'
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'computed',
|
|
166
|
+
label: 'Computed'
|
|
167
|
+
}
|
|
168
|
+
];
|
|
169
|
+
/** The fields a product's first row carries: the product's own. */ export const PRODUCT_LEVEL_FIELDS = [
|
|
170
|
+
F.handle,
|
|
171
|
+
F.title,
|
|
172
|
+
F.description,
|
|
173
|
+
F.kind,
|
|
174
|
+
F.status,
|
|
175
|
+
F.published,
|
|
176
|
+
F.tags,
|
|
177
|
+
F.categories,
|
|
178
|
+
F.option1Name,
|
|
179
|
+
F.option2Name,
|
|
180
|
+
F.option3Name,
|
|
181
|
+
F.image,
|
|
182
|
+
F.seoTitle,
|
|
183
|
+
F.seoDescription,
|
|
184
|
+
F.taxExempt,
|
|
185
|
+
F.oversellPolicy,
|
|
186
|
+
F.lowStockThreshold
|
|
187
|
+
];
|
|
188
|
+
/** The fields every row carries for its own variant. */ export const VARIANT_LEVEL_FIELDS = [
|
|
189
|
+
F.option1Value,
|
|
190
|
+
F.option2Value,
|
|
191
|
+
F.option3Value,
|
|
192
|
+
F.sku,
|
|
193
|
+
F.barcode,
|
|
194
|
+
F.price,
|
|
195
|
+
F.compareAtPrice,
|
|
196
|
+
F.inventory,
|
|
197
|
+
F.weightGrams,
|
|
198
|
+
F.variantImage
|
|
199
|
+
];
|
|
200
|
+
const OPTION_NAME_FIELDS = [
|
|
201
|
+
F.option1Name,
|
|
202
|
+
F.option2Name,
|
|
203
|
+
F.option3Name
|
|
204
|
+
];
|
|
205
|
+
const OPTION_VALUE_FIELDS = [
|
|
206
|
+
F.option1Value,
|
|
207
|
+
F.option2Value,
|
|
208
|
+
F.option3Value
|
|
209
|
+
];
|
|
210
|
+
/** Every writable product field, in picker order. */ export const PRODUCT_STANDARD_FIELDS = [
|
|
211
|
+
{
|
|
212
|
+
id: F.handle,
|
|
213
|
+
label: 'Handle',
|
|
214
|
+
group: 'product',
|
|
215
|
+
type: 'text',
|
|
216
|
+
maxLength: 80,
|
|
217
|
+
aliases: [
|
|
218
|
+
'slug',
|
|
219
|
+
'url handle',
|
|
220
|
+
'product handle'
|
|
221
|
+
],
|
|
222
|
+
description: 'The product’s address, /products/<handle>. Rows with one handle are one product.'
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
id: F.title,
|
|
226
|
+
label: 'Title',
|
|
227
|
+
group: 'product',
|
|
228
|
+
type: 'text',
|
|
229
|
+
aliases: [
|
|
230
|
+
'name',
|
|
231
|
+
'product name',
|
|
232
|
+
'product title'
|
|
233
|
+
],
|
|
234
|
+
description: 'Needed for a new product.'
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: F.description,
|
|
238
|
+
label: 'Description',
|
|
239
|
+
group: 'product',
|
|
240
|
+
type: 'longText',
|
|
241
|
+
aliases: [
|
|
242
|
+
'body',
|
|
243
|
+
'body html',
|
|
244
|
+
'product description',
|
|
245
|
+
'description html'
|
|
246
|
+
]
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
id: F.kind,
|
|
250
|
+
label: 'Kind',
|
|
251
|
+
group: 'product',
|
|
252
|
+
type: 'picklist',
|
|
253
|
+
picklistId: PRODUCT_KIND_PICKLIST,
|
|
254
|
+
aliases: [
|
|
255
|
+
'product kind',
|
|
256
|
+
'type'
|
|
257
|
+
],
|
|
258
|
+
description: 'Physical, Digital or Service.'
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
id: F.status,
|
|
262
|
+
label: 'Status',
|
|
263
|
+
group: 'product',
|
|
264
|
+
type: 'picklist',
|
|
265
|
+
picklistId: PRODUCT_STATUS_PICKLIST,
|
|
266
|
+
aliases: [
|
|
267
|
+
'product status'
|
|
268
|
+
],
|
|
269
|
+
description: 'Active, Draft or Archived.'
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: F.published,
|
|
273
|
+
label: 'Published',
|
|
274
|
+
group: 'product',
|
|
275
|
+
type: 'boolean',
|
|
276
|
+
aliases: [
|
|
277
|
+
'visible',
|
|
278
|
+
'online'
|
|
279
|
+
],
|
|
280
|
+
description: 'Read as the status when the file has no Status: yes is Active, no is Draft.'
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: F.tags,
|
|
284
|
+
label: 'Tags',
|
|
285
|
+
group: 'product',
|
|
286
|
+
type: 'text',
|
|
287
|
+
aliases: [
|
|
288
|
+
'tag',
|
|
289
|
+
'keywords'
|
|
290
|
+
],
|
|
291
|
+
description: 'Separated by commas; their case is kept, because collections match a tag exactly.'
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
id: F.categories,
|
|
295
|
+
label: 'Categories',
|
|
296
|
+
group: 'product',
|
|
297
|
+
type: 'multiPicklist',
|
|
298
|
+
picklistId: PRODUCT_CATEGORY_PICKLIST,
|
|
299
|
+
aliases: [
|
|
300
|
+
'category',
|
|
301
|
+
'product categories'
|
|
302
|
+
],
|
|
303
|
+
description: 'By name. A name the site does not have can be added as a new category.'
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
id: F.option1Name,
|
|
307
|
+
label: 'Option 1 name',
|
|
308
|
+
group: 'options',
|
|
309
|
+
type: 'text',
|
|
310
|
+
maxLength: 60
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
id: F.option1Value,
|
|
314
|
+
label: 'Option 1 value',
|
|
315
|
+
group: 'options',
|
|
316
|
+
type: 'text',
|
|
317
|
+
maxLength: 60
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
id: F.option2Name,
|
|
321
|
+
label: 'Option 2 name',
|
|
322
|
+
group: 'options',
|
|
323
|
+
type: 'text',
|
|
324
|
+
maxLength: 60
|
|
325
|
+
},
|
|
326
|
+
{
|
|
327
|
+
id: F.option2Value,
|
|
328
|
+
label: 'Option 2 value',
|
|
329
|
+
group: 'options',
|
|
330
|
+
type: 'text',
|
|
331
|
+
maxLength: 60
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: F.option3Name,
|
|
335
|
+
label: 'Option 3 name',
|
|
336
|
+
group: 'options',
|
|
337
|
+
type: 'text',
|
|
338
|
+
maxLength: 60
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
id: F.option3Value,
|
|
342
|
+
label: 'Option 3 value',
|
|
343
|
+
group: 'options',
|
|
344
|
+
type: 'text',
|
|
345
|
+
maxLength: 60
|
|
346
|
+
},
|
|
347
|
+
{
|
|
348
|
+
id: F.sku,
|
|
349
|
+
label: 'SKU',
|
|
350
|
+
group: 'variant',
|
|
351
|
+
type: 'text',
|
|
352
|
+
aliases: [
|
|
353
|
+
'variant sku',
|
|
354
|
+
'stock keeping unit',
|
|
355
|
+
'item number'
|
|
356
|
+
]
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
id: F.barcode,
|
|
360
|
+
label: 'Barcode',
|
|
361
|
+
group: 'variant',
|
|
362
|
+
type: 'text',
|
|
363
|
+
aliases: [
|
|
364
|
+
'variant barcode',
|
|
365
|
+
'gtin',
|
|
366
|
+
'upc',
|
|
367
|
+
'ean',
|
|
368
|
+
'isbn'
|
|
369
|
+
]
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
id: F.price,
|
|
373
|
+
label: 'Price',
|
|
374
|
+
group: 'variant',
|
|
375
|
+
type: 'currency',
|
|
376
|
+
aliases: [
|
|
377
|
+
'variant price',
|
|
378
|
+
'price usd',
|
|
379
|
+
'unit price'
|
|
380
|
+
],
|
|
381
|
+
description: 'In US dollars. Needed for a new variant.'
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: F.compareAtPrice,
|
|
385
|
+
label: 'Compare-at price',
|
|
386
|
+
group: 'variant',
|
|
387
|
+
type: 'currency',
|
|
388
|
+
aliases: [
|
|
389
|
+
'variant compare at price',
|
|
390
|
+
'compare at price',
|
|
391
|
+
'was price',
|
|
392
|
+
'original price'
|
|
393
|
+
],
|
|
394
|
+
description: 'The strike-through price; above the price, or a product is refused.'
|
|
395
|
+
},
|
|
396
|
+
{
|
|
397
|
+
id: F.inventory,
|
|
398
|
+
label: 'Inventory',
|
|
399
|
+
group: 'variant',
|
|
400
|
+
type: 'integer',
|
|
401
|
+
aliases: [
|
|
402
|
+
'variant inventory qty',
|
|
403
|
+
'inventory quantity',
|
|
404
|
+
'quantity',
|
|
405
|
+
'stock',
|
|
406
|
+
'on hand'
|
|
407
|
+
],
|
|
408
|
+
description: 'Units in stock; blank is not counted. Stock kept by location is changed on the products page.'
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
id: F.weightGrams,
|
|
412
|
+
label: 'Weight (grams)',
|
|
413
|
+
group: 'variant',
|
|
414
|
+
type: 'number',
|
|
415
|
+
aliases: [
|
|
416
|
+
'variant grams',
|
|
417
|
+
'grams',
|
|
418
|
+
'weight'
|
|
419
|
+
]
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
id: F.variantImage,
|
|
423
|
+
label: 'Variant image',
|
|
424
|
+
group: 'variant',
|
|
425
|
+
type: 'url',
|
|
426
|
+
aliases: [
|
|
427
|
+
'variant image url'
|
|
428
|
+
]
|
|
429
|
+
},
|
|
430
|
+
{
|
|
431
|
+
id: F.image,
|
|
432
|
+
label: 'Image',
|
|
433
|
+
group: 'media',
|
|
434
|
+
type: 'url',
|
|
435
|
+
aliases: [
|
|
436
|
+
'image url',
|
|
437
|
+
'image src',
|
|
438
|
+
'photo',
|
|
439
|
+
'picture'
|
|
440
|
+
],
|
|
441
|
+
description: 'One image per row; the first row’s is the photo the storefront leads with.'
|
|
442
|
+
},
|
|
443
|
+
{
|
|
444
|
+
id: F.seoTitle,
|
|
445
|
+
label: 'SEO title',
|
|
446
|
+
group: 'seo',
|
|
447
|
+
type: 'text',
|
|
448
|
+
aliases: [
|
|
449
|
+
'meta title',
|
|
450
|
+
'page title',
|
|
451
|
+
'search title'
|
|
452
|
+
]
|
|
453
|
+
},
|
|
454
|
+
{
|
|
455
|
+
id: F.seoDescription,
|
|
456
|
+
label: 'SEO description',
|
|
457
|
+
group: 'seo',
|
|
458
|
+
type: 'longText',
|
|
459
|
+
aliases: [
|
|
460
|
+
'meta description',
|
|
461
|
+
'search description'
|
|
462
|
+
]
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: F.taxExempt,
|
|
466
|
+
label: 'Tax exempt',
|
|
467
|
+
group: 'selling',
|
|
468
|
+
type: 'boolean',
|
|
469
|
+
aliases: [
|
|
470
|
+
'tax free',
|
|
471
|
+
'no tax'
|
|
472
|
+
]
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
id: F.oversellPolicy,
|
|
476
|
+
label: 'When sold out',
|
|
477
|
+
group: 'selling',
|
|
478
|
+
type: 'picklist',
|
|
479
|
+
picklistId: PRODUCT_OVERSELL_PICKLIST,
|
|
480
|
+
aliases: [
|
|
481
|
+
'oversell policy',
|
|
482
|
+
'inventory policy',
|
|
483
|
+
'backorders'
|
|
484
|
+
],
|
|
485
|
+
description: 'Stop selling, or Keep selling as a backorder.'
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
id: F.lowStockThreshold,
|
|
489
|
+
label: 'Low stock alert at',
|
|
490
|
+
group: 'selling',
|
|
491
|
+
type: 'integer',
|
|
492
|
+
aliases: [
|
|
493
|
+
'low stock threshold',
|
|
494
|
+
'reorder point'
|
|
495
|
+
]
|
|
496
|
+
}
|
|
497
|
+
];
|
|
498
|
+
export const PRODUCT_DERIVED_FIELDS = [
|
|
499
|
+
{
|
|
500
|
+
id: F.priceFrom,
|
|
501
|
+
label: 'Lowest price',
|
|
502
|
+
group: 'computed',
|
|
503
|
+
type: 'number'
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: F.totalInventory,
|
|
507
|
+
label: 'Total inventory',
|
|
508
|
+
group: 'computed',
|
|
509
|
+
type: 'integer'
|
|
510
|
+
},
|
|
511
|
+
{
|
|
512
|
+
id: F.soldOut,
|
|
513
|
+
label: 'Sold out',
|
|
514
|
+
group: 'computed',
|
|
515
|
+
type: 'boolean'
|
|
516
|
+
},
|
|
517
|
+
{
|
|
518
|
+
id: F.variantCount,
|
|
519
|
+
label: 'Variants',
|
|
520
|
+
group: 'computed',
|
|
521
|
+
type: 'integer'
|
|
522
|
+
}
|
|
523
|
+
];
|
|
524
|
+
export const PRODUCT_SYSTEM_FIELDS = [
|
|
525
|
+
{
|
|
526
|
+
id: F.createdAt,
|
|
527
|
+
label: 'Created',
|
|
528
|
+
group: TRANSFER_SYSTEM_GROUP.id,
|
|
529
|
+
type: 'datetime'
|
|
530
|
+
},
|
|
531
|
+
{
|
|
532
|
+
id: F.updatedAt,
|
|
533
|
+
label: 'Updated',
|
|
534
|
+
group: TRANSFER_SYSTEM_GROUP.id,
|
|
535
|
+
type: 'datetime'
|
|
536
|
+
}
|
|
537
|
+
];
|
|
538
|
+
/** Handle, then SKU, then the Aglyn ID. */ export const PRODUCT_MATCH_KEYS = [
|
|
539
|
+
{
|
|
540
|
+
fieldId: F.handle,
|
|
541
|
+
normalizer: 'slug'
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
fieldId: F.sku,
|
|
545
|
+
normalizer: 'caseless'
|
|
546
|
+
},
|
|
547
|
+
{
|
|
548
|
+
fieldId: 'id',
|
|
549
|
+
normalizer: 'aglynId'
|
|
550
|
+
}
|
|
551
|
+
];
|
|
552
|
+
/**
|
|
553
|
+
* A product's handle and its option names are changed in the product editor,
|
|
554
|
+
* never by a file: the handle is the product's address, and renaming an
|
|
555
|
+
* option moves every variant. A file gives a new product both, and names an
|
|
556
|
+
* option a product does not have yet.
|
|
557
|
+
*/ export const PRODUCT_LOCKED_RULES = [
|
|
558
|
+
{
|
|
559
|
+
fieldId: F.handle,
|
|
560
|
+
reason: 'A product’s handle is its address; a file gives a new product one, and an existing product’s is changed in the product editor.',
|
|
561
|
+
forced: {
|
|
562
|
+
mode: 'fillBlanks',
|
|
563
|
+
blank: 'leave'
|
|
564
|
+
}
|
|
565
|
+
},
|
|
566
|
+
...OPTION_NAME_FIELDS.map((fieldId)=>({
|
|
567
|
+
fieldId,
|
|
568
|
+
reason: 'An option a product already has is renamed in the product editor, where every variant moves with it; a file only names an option the product does not have yet.',
|
|
569
|
+
forced: {
|
|
570
|
+
mode: 'fillBlanks',
|
|
571
|
+
blank: 'leave'
|
|
572
|
+
}
|
|
573
|
+
}))
|
|
574
|
+
];
|
|
575
|
+
/*==========================================
|
|
576
|
+
* THE SHOPIFY LAYOUT
|
|
577
|
+
*=========================================*/ /** Shopify's product CSV columns, by the field each one means. */ export const SHOPIFY_PRODUCT_COLUMNS = {
|
|
578
|
+
[F.handle]: 'Handle',
|
|
579
|
+
[F.title]: 'Title',
|
|
580
|
+
[F.description]: 'Body (HTML)',
|
|
581
|
+
[F.tags]: 'Tags',
|
|
582
|
+
[F.published]: 'Published',
|
|
583
|
+
[F.option1Name]: 'Option1 Name',
|
|
584
|
+
[F.option1Value]: 'Option1 Value',
|
|
585
|
+
[F.option2Name]: 'Option2 Name',
|
|
586
|
+
[F.option2Value]: 'Option2 Value',
|
|
587
|
+
[F.option3Name]: 'Option3 Name',
|
|
588
|
+
[F.option3Value]: 'Option3 Value',
|
|
589
|
+
[F.sku]: 'Variant SKU',
|
|
590
|
+
[F.weightGrams]: 'Variant Grams',
|
|
591
|
+
[F.inventory]: 'Variant Inventory Qty',
|
|
592
|
+
[F.oversellPolicy]: 'Variant Inventory Policy',
|
|
593
|
+
[F.price]: 'Variant Price',
|
|
594
|
+
[F.compareAtPrice]: 'Variant Compare At Price',
|
|
595
|
+
[F.barcode]: 'Variant Barcode',
|
|
596
|
+
[F.image]: 'Image Src',
|
|
597
|
+
[F.seoTitle]: 'SEO Title',
|
|
598
|
+
[F.seoDescription]: 'SEO Description',
|
|
599
|
+
[F.variantImage]: 'Variant Image',
|
|
600
|
+
[F.status]: 'Status'
|
|
601
|
+
};
|
|
602
|
+
/** Shopify's export headers, so its file maps column for column. */ export const PRODUCT_ALIAS_DICTIONARIES = [
|
|
603
|
+
{
|
|
604
|
+
source: 'Shopify',
|
|
605
|
+
aliases: Object.fromEntries(Object.entries(SHOPIFY_PRODUCT_COLUMNS).map(([fieldId, column])=>[
|
|
606
|
+
fieldId,
|
|
607
|
+
[
|
|
608
|
+
column
|
|
609
|
+
]
|
|
610
|
+
]))
|
|
611
|
+
}
|
|
612
|
+
];
|
|
613
|
+
/**
|
|
614
|
+
* The Shopify preset: Shopify's product CSV columns, in Shopify's order and
|
|
615
|
+
* under Shopify's names. Status is left out — Shopify reads it in lowercase
|
|
616
|
+
* and Published says the same thing — and so is the kind, which Shopify's
|
|
617
|
+
* "Type" does not mean.
|
|
618
|
+
*/ export const SHOPIFY_PRODUCT_PRESET = {
|
|
619
|
+
id: 'shopify',
|
|
620
|
+
label: 'Shopify',
|
|
621
|
+
description: 'Shopify’s product CSV: its columns, its order and its names.',
|
|
622
|
+
fieldIds: [
|
|
623
|
+
F.handle,
|
|
624
|
+
F.title,
|
|
625
|
+
F.description,
|
|
626
|
+
F.tags,
|
|
627
|
+
F.published,
|
|
628
|
+
F.option1Name,
|
|
629
|
+
F.option1Value,
|
|
630
|
+
F.option2Name,
|
|
631
|
+
F.option2Value,
|
|
632
|
+
F.option3Name,
|
|
633
|
+
F.option3Value,
|
|
634
|
+
F.sku,
|
|
635
|
+
F.weightGrams,
|
|
636
|
+
F.inventory,
|
|
637
|
+
F.oversellPolicy,
|
|
638
|
+
F.price,
|
|
639
|
+
F.compareAtPrice,
|
|
640
|
+
F.barcode,
|
|
641
|
+
F.image,
|
|
642
|
+
F.seoTitle,
|
|
643
|
+
F.seoDescription,
|
|
644
|
+
F.variantImage
|
|
645
|
+
],
|
|
646
|
+
headers: Object.fromEntries(Object.entries(SHOPIFY_PRODUCT_COLUMNS).filter(([fieldId])=>fieldId !== F.status))
|
|
647
|
+
};
|
|
648
|
+
/*==========================================
|
|
649
|
+
* A PRODUCT AS RECORDS
|
|
650
|
+
*=========================================*/ /** Where a product's record keeps its variants, apart from its fields. */ export const PRODUCT_VARIANT_ORDER = '$variants';
|
|
651
|
+
/** The record key one variant's values live under. */ export function productVariantKey(variantId) {
|
|
652
|
+
return `$variant:${variantId}`;
|
|
653
|
+
}
|
|
654
|
+
const isoOf = (value, ms)=>{
|
|
655
|
+
const time = typeof ms === 'number' ? ms : value && typeof value.toMillis === 'function' ? value.toMillis() : typeof value === 'number' ? value : null;
|
|
656
|
+
return time === null || !Number.isFinite(time) ? null : new Date(time).toISOString();
|
|
657
|
+
};
|
|
658
|
+
/** One variant as a file names it. */ export function variantRecord(variant, options) {
|
|
659
|
+
var _variant_sku, _variant_barcode, _variant_compareAtPriceUsd, _variant_weightGrams, _variant_imageUrl;
|
|
660
|
+
const values = {
|
|
661
|
+
[F.sku]: (_variant_sku = variant.sku) != null ? _variant_sku : null,
|
|
662
|
+
[F.barcode]: (_variant_barcode = variant.barcode) != null ? _variant_barcode : null,
|
|
663
|
+
[F.price]: Number.isFinite(Number(variant.priceUsd)) ? Number(variant.priceUsd) : null,
|
|
664
|
+
[F.compareAtPrice]: (_variant_compareAtPriceUsd = variant.compareAtPriceUsd) != null ? _variant_compareAtPriceUsd : null,
|
|
665
|
+
[F.inventory]: typeof variant.inventory === 'number' ? variant.inventory : null,
|
|
666
|
+
[F.weightGrams]: (_variant_weightGrams = variant.weightGrams) != null ? _variant_weightGrams : null,
|
|
667
|
+
[F.variantImage]: (_variant_imageUrl = variant.imageUrl) != null ? _variant_imageUrl : null
|
|
668
|
+
};
|
|
669
|
+
OPTION_VALUE_FIELDS.forEach((fieldId, axis)=>{
|
|
670
|
+
var _ref;
|
|
671
|
+
var _options_axis, _variant_options;
|
|
672
|
+
const name = (_options_axis = options[axis]) == null ? void 0 : _options_axis.name;
|
|
673
|
+
values[fieldId] = name ? (_ref = (_variant_options = variant.options) == null ? void 0 : _variant_options[name]) != null ? _ref : null : null;
|
|
674
|
+
});
|
|
675
|
+
if (variant.inventoryByLocation && Object.keys(variant.inventoryByLocation).length) values.$byLocation = true;
|
|
676
|
+
return values;
|
|
677
|
+
}
|
|
678
|
+
/**
|
|
679
|
+
* A product as one record: its fields by field id, and its variants under
|
|
680
|
+
* {@link productVariantKey} in the order {@link PRODUCT_VARIANT_ORDER} lists
|
|
681
|
+
* them. The lookup answers this shape, so the dry run's before → after and
|
|
682
|
+
* undo's "edited since" both read a product the way a file names it.
|
|
683
|
+
*/ export function productRecord(stored, categoryNames = new Map()) {
|
|
684
|
+
var _product_options, _product_slug, _product_name, _product_description, _product_type, _product_status, _product_status1, _product_categoryIds, _ref, _ref1, _product_taxExempt, _product_oversellPolicy, _product_lowStockThreshold;
|
|
685
|
+
var _product_tags, _product_mediaUrls, _product_seo, _product_seo1;
|
|
686
|
+
const product = stored;
|
|
687
|
+
const options = (_product_options = product.options) != null ? _product_options : [];
|
|
688
|
+
const variants = Array.isArray(product.variants) ? product.variants : [];
|
|
689
|
+
const [low] = variants.length ? productPriceRange(product) : [
|
|
690
|
+
null
|
|
691
|
+
];
|
|
692
|
+
const total = variants.length ? productInventory(product) : null;
|
|
693
|
+
const record = {
|
|
694
|
+
[F.handle]: (_product_slug = product.slug) != null ? _product_slug : null,
|
|
695
|
+
[F.title]: (_product_name = product.name) != null ? _product_name : null,
|
|
696
|
+
[F.description]: (_product_description = product.description) != null ? _product_description : null,
|
|
697
|
+
[F.kind]: fixedListLabel(PRODUCT_KIND_PICKLIST, (_product_type = product.type) != null ? _product_type : 'physical'),
|
|
698
|
+
[F.status]: fixedListLabel(PRODUCT_STATUS_PICKLIST, (_product_status = product.status) != null ? _product_status : 'active'),
|
|
699
|
+
[F.published]: ((_product_status1 = product.status) != null ? _product_status1 : 'active') === 'active',
|
|
700
|
+
[F.tags]: ((_product_tags = product.tags) == null ? void 0 : _product_tags.length) ? product.tags.join(', ') : null,
|
|
701
|
+
[F.categories]: ((_product_categoryIds = product.categoryIds) != null ? _product_categoryIds : []).map((id)=>{
|
|
702
|
+
var _categoryNames_get;
|
|
703
|
+
return (_categoryNames_get = categoryNames.get(id)) != null ? _categoryNames_get : id;
|
|
704
|
+
}),
|
|
705
|
+
[F.image]: ((_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls.length) ? [
|
|
706
|
+
...product.mediaUrls
|
|
707
|
+
] : product.imageUrl ? [
|
|
708
|
+
product.imageUrl
|
|
709
|
+
] : [],
|
|
710
|
+
[F.seoTitle]: (_ref = (_product_seo = product.seo) == null ? void 0 : _product_seo.title) != null ? _ref : null,
|
|
711
|
+
[F.seoDescription]: (_ref1 = (_product_seo1 = product.seo) == null ? void 0 : _product_seo1.description) != null ? _ref1 : null,
|
|
712
|
+
[F.taxExempt]: (_product_taxExempt = product.taxExempt) != null ? _product_taxExempt : null,
|
|
713
|
+
[F.oversellPolicy]: fixedListLabel(PRODUCT_OVERSELL_PICKLIST, (_product_oversellPolicy = product.oversellPolicy) != null ? _product_oversellPolicy : 'deny'),
|
|
714
|
+
[F.lowStockThreshold]: (_product_lowStockThreshold = product.lowStockThreshold) != null ? _product_lowStockThreshold : null,
|
|
715
|
+
[F.priceFrom]: low,
|
|
716
|
+
[F.totalInventory]: total,
|
|
717
|
+
[F.soldOut]: total !== null && total <= 0 && product.oversellPolicy !== 'backorder',
|
|
718
|
+
[F.variantCount]: variants.length,
|
|
719
|
+
[F.createdAt]: isoOf(stored.createdAt, stored.createdAtMs),
|
|
720
|
+
[F.updatedAt]: isoOf(stored.updatedAt, stored.updatedAtMs),
|
|
721
|
+
[PRODUCT_VARIANT_ORDER]: variants.map((variant)=>variant.id)
|
|
722
|
+
};
|
|
723
|
+
OPTION_NAME_FIELDS.forEach((fieldId, axis)=>{
|
|
724
|
+
var _ref;
|
|
725
|
+
var _options_axis;
|
|
726
|
+
record[fieldId] = (_ref = (_options_axis = options[axis]) == null ? void 0 : _options_axis.name) != null ? _ref : null;
|
|
727
|
+
});
|
|
728
|
+
for (const variant of variants)record[productVariantKey(variant.id)] = variantRecord(variant, options);
|
|
729
|
+
return record;
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* A product's rows in a file, holding only `fieldIds`: one per variant (and
|
|
733
|
+
* per image past the variants), the product's own fields on the first, the
|
|
734
|
+
* handle and the ID on every one.
|
|
735
|
+
*/ export function productExportRows(id, stored, fieldIds, categoryNames = new Map()) {
|
|
736
|
+
const record = productRecord(stored, categoryNames);
|
|
737
|
+
const variantIds = record[PRODUCT_VARIANT_ORDER];
|
|
738
|
+
const images = record[F.image];
|
|
739
|
+
const count = Math.max(1, variantIds.length, images.length);
|
|
740
|
+
const variantFields = new Set(VARIANT_LEVEL_FIELDS);
|
|
741
|
+
const rows = [];
|
|
742
|
+
for(let index = 0; index < count; index += 1){
|
|
743
|
+
const variantId = variantIds[index];
|
|
744
|
+
const variant = variantId ? record[productVariantKey(variantId)] : null;
|
|
745
|
+
const row = {};
|
|
746
|
+
for (const fieldId of fieldIds){
|
|
747
|
+
var _images_index, _variant_fieldId, _record_fieldId;
|
|
748
|
+
if (fieldId === 'id') row[fieldId] = id;
|
|
749
|
+
else if (fieldId === F.handle) row[fieldId] = record[F.handle];
|
|
750
|
+
else if (fieldId === F.image) row[fieldId] = (_images_index = images[index]) != null ? _images_index : null;
|
|
751
|
+
else if (variantFields.has(fieldId)) row[fieldId] = variant ? (_variant_fieldId = variant[fieldId]) != null ? _variant_fieldId : null : null;
|
|
752
|
+
else row[fieldId] = index === 0 ? (_record_fieldId = record[fieldId]) != null ? _record_fieldId : null : null;
|
|
753
|
+
}
|
|
754
|
+
rows.push(row);
|
|
755
|
+
}
|
|
756
|
+
return rows;
|
|
757
|
+
}
|
|
758
|
+
/** How many rows {@link productExportRows} writes for a product. */ export function productExportRowCount(stored) {
|
|
759
|
+
var _ref;
|
|
760
|
+
var _stored_mediaUrls;
|
|
761
|
+
const variants = Array.isArray(stored.variants) ? stored.variants.length : 0;
|
|
762
|
+
const images = (_ref = (_stored_mediaUrls = stored.mediaUrls) == null ? void 0 : _stored_mediaUrls.length) != null ? _ref : stored.imageUrl ? 1 : 0;
|
|
763
|
+
return Math.max(1, variants, images);
|
|
764
|
+
}
|
|
765
|
+
const text = (value)=>value === null || value === undefined ? '' : String(value).trim();
|
|
766
|
+
/** A money cell as US dollars: the derived amount, or the number as typed. */ function dollars(value) {
|
|
767
|
+
if (value === null || value === undefined || value === '') return {
|
|
768
|
+
ok: true,
|
|
769
|
+
value: null
|
|
770
|
+
};
|
|
771
|
+
if (typeof value === 'number') return {
|
|
772
|
+
ok: true,
|
|
773
|
+
value
|
|
774
|
+
};
|
|
775
|
+
if (typeof value === 'object' && 'amountMinor' in value) {
|
|
776
|
+
const amount = value;
|
|
777
|
+
if (String(amount.currency).toUpperCase() !== 'USD') {
|
|
778
|
+
return {
|
|
779
|
+
ok: false,
|
|
780
|
+
message: `Prices are in US dollars; ${amount.currency} is not converted.`
|
|
781
|
+
};
|
|
782
|
+
}
|
|
783
|
+
return {
|
|
784
|
+
ok: true,
|
|
785
|
+
value: Math.round(Number(amount.amountMinor)) / 100
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
const number = Number(value);
|
|
789
|
+
return Number.isFinite(number) ? {
|
|
790
|
+
ok: true,
|
|
791
|
+
value: number
|
|
792
|
+
} : {
|
|
793
|
+
ok: false,
|
|
794
|
+
message: `"${String(value)}" is not a price.`
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
/** The tags a cell names, in their own case. */ export function productTagList(value) {
|
|
798
|
+
const list = Array.isArray(value) ? value : String(value != null ? value : '').split(',');
|
|
799
|
+
const seen = new Set();
|
|
800
|
+
const out = [];
|
|
801
|
+
for (const raw of list){
|
|
802
|
+
const tag = String(raw).trim();
|
|
803
|
+
if (!tag || seen.has(tag)) continue;
|
|
804
|
+
seen.add(tag);
|
|
805
|
+
out.push(tag);
|
|
806
|
+
}
|
|
807
|
+
return out;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* A row's values the way the plan compares them with a record: prices in
|
|
811
|
+
* dollars, a fixed list's label checked, tags as one comma list, Published
|
|
812
|
+
* read as the status. What cannot be read is dropped as a located problem.
|
|
813
|
+
*/ function normalizeRow(row) {
|
|
814
|
+
var _row_problems;
|
|
815
|
+
const values = _extends({}, row.values);
|
|
816
|
+
const problems = [
|
|
817
|
+
...(_row_problems = row.problems) != null ? _row_problems : []
|
|
818
|
+
];
|
|
819
|
+
const drop = (fieldId, code, message)=>{
|
|
820
|
+
problems.push({
|
|
821
|
+
fieldId,
|
|
822
|
+
raw: text(values[fieldId]),
|
|
823
|
+
code,
|
|
824
|
+
message
|
|
825
|
+
});
|
|
826
|
+
delete values[fieldId];
|
|
827
|
+
};
|
|
828
|
+
for (const fieldId of [
|
|
829
|
+
F.price,
|
|
830
|
+
F.compareAtPrice
|
|
831
|
+
]){
|
|
832
|
+
if (!(fieldId in values)) continue;
|
|
833
|
+
const read = dollars(values[fieldId]);
|
|
834
|
+
if ('message' in read) drop(fieldId, 'invalidCurrency', read.message);
|
|
835
|
+
else values[fieldId] = read.value;
|
|
836
|
+
}
|
|
837
|
+
for (const [fieldId, picklistId] of [
|
|
838
|
+
[
|
|
839
|
+
F.kind,
|
|
840
|
+
PRODUCT_KIND_PICKLIST
|
|
841
|
+
],
|
|
842
|
+
[
|
|
843
|
+
F.status,
|
|
844
|
+
PRODUCT_STATUS_PICKLIST
|
|
845
|
+
],
|
|
846
|
+
[
|
|
847
|
+
F.oversellPolicy,
|
|
848
|
+
PRODUCT_OVERSELL_PICKLIST
|
|
849
|
+
]
|
|
850
|
+
]){
|
|
851
|
+
var _PRODUCT_FIXED_PICKLISTS_picklistId;
|
|
852
|
+
if (isBlankTransferValue(values[fieldId])) continue;
|
|
853
|
+
const id = fixedListId(picklistId, values[fieldId]);
|
|
854
|
+
if (id) values[fieldId] = fixedListLabel(picklistId, id);
|
|
855
|
+
else drop(fieldId, 'invalidJson', `"${text(values[fieldId])}" is not one of ${(_PRODUCT_FIXED_PICKLISTS_picklistId = PRODUCT_FIXED_PICKLISTS[picklistId]) == null ? void 0 : _PRODUCT_FIXED_PICKLISTS_picklistId.standardValues.map((entry)=>entry.label).join(', ')}.`);
|
|
856
|
+
}
|
|
857
|
+
if (F.tags in values && !isBlankTransferValue(values[F.tags])) {
|
|
858
|
+
values[F.tags] = productTagList(values[F.tags]).join(', ');
|
|
859
|
+
}
|
|
860
|
+
if (F.handle in values && !isBlankTransferValue(values[F.handle])) {
|
|
861
|
+
const slug = commerceSlug(text(values[F.handle]));
|
|
862
|
+
if (slug) values[F.handle] = slug;
|
|
863
|
+
else drop(F.handle, 'invalidUrl', `"${text(values[F.handle])}" has no letters or numbers to make a handle from.`);
|
|
864
|
+
}
|
|
865
|
+
if (F.published in values) {
|
|
866
|
+
if (isBlankTransferValue(values[F.status]) && typeof values[F.published] === 'boolean') {
|
|
867
|
+
values[F.status] = values[F.published] ? STATUS_LABELS.active : STATUS_LABELS.draft;
|
|
868
|
+
}
|
|
869
|
+
delete values[F.published];
|
|
870
|
+
}
|
|
871
|
+
return _extends({}, row, {
|
|
872
|
+
values
|
|
873
|
+
}, problems.length ? {
|
|
874
|
+
problems
|
|
875
|
+
} : {});
|
|
876
|
+
}
|
|
877
|
+
const pick = (values, fieldIds)=>{
|
|
878
|
+
const out = {};
|
|
879
|
+
for (const fieldId of fieldIds)if (fieldId in values) out[fieldId] = values[fieldId];
|
|
880
|
+
return out;
|
|
881
|
+
};
|
|
882
|
+
const hasAny = (values, fieldIds)=>fieldIds.some((fieldId)=>!isBlankTransferValue(values[fieldId]));
|
|
883
|
+
/** Which product a row belongs to in the file. */ function groupKey(row, position) {
|
|
884
|
+
const handle = text(row.values[F.handle]);
|
|
885
|
+
if (handle) return `h:${handle}`;
|
|
886
|
+
const id = text(row.values['id']);
|
|
887
|
+
if (id) return `i:${id}`;
|
|
888
|
+
return `r:${position}`;
|
|
889
|
+
}
|
|
890
|
+
/** Warnings, tallied the way the core tallies them, for what this plan adds and merges. */ let Tally = class Tally {
|
|
891
|
+
merge(warnings) {
|
|
892
|
+
for (const warning of warnings){
|
|
893
|
+
var _entry;
|
|
894
|
+
const entry = this.entry(warning.class, warning.requiresAcknowledgement);
|
|
895
|
+
entry.count += warning.count;
|
|
896
|
+
for (const sample of warning.samples){
|
|
897
|
+
entry.rowSet.add(sample.row);
|
|
898
|
+
if (entry.samples.length < 5) entry.samples.push(sample);
|
|
899
|
+
}
|
|
900
|
+
for (const fieldId of warning.fieldIds)entry.fieldSet.add(fieldId);
|
|
901
|
+
(_entry = entry).requiresAcknowledgement || (_entry.requiresAcknowledgement = warning.requiresAcknowledgement);
|
|
902
|
+
}
|
|
903
|
+
}
|
|
904
|
+
add(warningClass, sample) {
|
|
905
|
+
const entry = this.entry(warningClass, true);
|
|
906
|
+
entry.count += 1;
|
|
907
|
+
entry.rowSet.add(sample.row);
|
|
908
|
+
if (sample.fieldId) entry.fieldSet.add(sample.fieldId);
|
|
909
|
+
if (entry.samples.length < 5) entry.samples.push(sample);
|
|
910
|
+
}
|
|
911
|
+
entry(warningClass, acknowledge) {
|
|
912
|
+
let entry = this.byClass.get(warningClass);
|
|
913
|
+
if (!entry) {
|
|
914
|
+
entry = {
|
|
915
|
+
class: warningClass,
|
|
916
|
+
count: 0,
|
|
917
|
+
rows: 0,
|
|
918
|
+
fieldIds: [],
|
|
919
|
+
samples: [],
|
|
920
|
+
requiresAcknowledgement: acknowledge,
|
|
921
|
+
rowSet: new Set(),
|
|
922
|
+
fieldSet: new Set()
|
|
923
|
+
};
|
|
924
|
+
this.byClass.set(warningClass, entry);
|
|
925
|
+
}
|
|
926
|
+
return entry;
|
|
927
|
+
}
|
|
928
|
+
list(order) {
|
|
929
|
+
return [
|
|
930
|
+
...this.byClass.values()
|
|
931
|
+
].sort((a, b)=>order.indexOf(a.class) - order.indexOf(b.class)).map((_0)=>{
|
|
932
|
+
let { rowSet, fieldSet } = _0, warning = _object_without_properties_loose(_0, [
|
|
933
|
+
"rowSet",
|
|
934
|
+
"fieldSet"
|
|
935
|
+
]);
|
|
936
|
+
return _extends({}, warning, {
|
|
937
|
+
rows: Math.max(rowSet.size, warning.rows),
|
|
938
|
+
fieldIds: [
|
|
939
|
+
...fieldSet
|
|
940
|
+
]
|
|
941
|
+
});
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
constructor(){
|
|
945
|
+
this.byClass = new Map();
|
|
946
|
+
}
|
|
947
|
+
};
|
|
948
|
+
/** The order the core lists warning classes in. */ const WARNING_ORDER = TRANSFER_WARNING_CLASSES;
|
|
949
|
+
/** A variant line's identity in the product it targets. */ function optionKey(values, names) {
|
|
950
|
+
const parts = [];
|
|
951
|
+
OPTION_VALUE_FIELDS.forEach((fieldId, axis)=>{
|
|
952
|
+
const value = text(values[fieldId]);
|
|
953
|
+
if (value && names[axis]) parts.push(`${names[axis]}=${value.toLowerCase()}`);
|
|
954
|
+
});
|
|
955
|
+
return parts.length ? parts.join('|') : null;
|
|
956
|
+
}
|
|
957
|
+
/** The value a field's diff leaves, or what the record held. */ function after(diff, before, fieldId) {
|
|
958
|
+
const change = diff.find((entry)=>entry.fieldId === fieldId);
|
|
959
|
+
return change ? change.after : before[fieldId];
|
|
960
|
+
}
|
|
961
|
+
/**
|
|
962
|
+
* A product's document from its record values (see {@link productRecord}),
|
|
963
|
+
* for checking what a plan would store with the model's own rules.
|
|
964
|
+
*/ export function productFromRecord(values, variants) {
|
|
965
|
+
var _fixedListId, _fixedListId1;
|
|
966
|
+
const names = OPTION_NAME_FIELDS.map((fieldId)=>text(values[fieldId]));
|
|
967
|
+
const options = [];
|
|
968
|
+
names.forEach((name, axis)=>{
|
|
969
|
+
if (!name) return;
|
|
970
|
+
const seen = [];
|
|
971
|
+
for (const variant of variants){
|
|
972
|
+
const value = text(variant.values[OPTION_VALUE_FIELDS[axis]]);
|
|
973
|
+
if (value && !seen.includes(value)) seen.push(value);
|
|
974
|
+
}
|
|
975
|
+
options.push({
|
|
976
|
+
name,
|
|
977
|
+
values: seen
|
|
978
|
+
});
|
|
979
|
+
});
|
|
980
|
+
return _extends({
|
|
981
|
+
name: text(values[F.title]),
|
|
982
|
+
slug: text(values[F.handle]),
|
|
983
|
+
type: (_fixedListId = fixedListId(PRODUCT_KIND_PICKLIST, values[F.kind])) != null ? _fixedListId : 'physical',
|
|
984
|
+
status: (_fixedListId1 = fixedListId(PRODUCT_STATUS_PICKLIST, values[F.status])) != null ? _fixedListId1 : 'active'
|
|
985
|
+
}, options.length ? {
|
|
986
|
+
options
|
|
987
|
+
} : {}, {
|
|
988
|
+
variants: variants.map((variant)=>variantFromRecord(variant.id, variant.values, names))
|
|
989
|
+
});
|
|
990
|
+
}
|
|
991
|
+
/** One variant of a document from its record values. */ export function variantFromRecord(id, values, optionNames) {
|
|
992
|
+
const options = {};
|
|
993
|
+
OPTION_VALUE_FIELDS.forEach((fieldId, axis)=>{
|
|
994
|
+
const name = text(optionNames[axis]);
|
|
995
|
+
const value = text(values[fieldId]);
|
|
996
|
+
if (name && value) options[name] = value;
|
|
997
|
+
});
|
|
998
|
+
const number = (fieldId)=>{
|
|
999
|
+
const raw = values[fieldId];
|
|
1000
|
+
return raw === null || raw === undefined || raw === '' || !Number.isFinite(Number(raw)) ? undefined : Number(raw);
|
|
1001
|
+
};
|
|
1002
|
+
const inventory = number(F.inventory);
|
|
1003
|
+
return _extends({
|
|
1004
|
+
id
|
|
1005
|
+
}, Object.keys(options).length ? {
|
|
1006
|
+
options
|
|
1007
|
+
} : {}, {
|
|
1008
|
+
priceUsd: number(F.price)
|
|
1009
|
+
}, text(values[F.sku]) ? {
|
|
1010
|
+
sku: text(values[F.sku])
|
|
1011
|
+
} : {}, text(values[F.barcode]) ? {
|
|
1012
|
+
barcode: text(values[F.barcode])
|
|
1013
|
+
} : {}, number(F.compareAtPrice) !== undefined ? {
|
|
1014
|
+
compareAtPriceUsd: number(F.compareAtPrice)
|
|
1015
|
+
} : {}, number(F.weightGrams) !== undefined ? {
|
|
1016
|
+
weightGrams: number(F.weightGrams)
|
|
1017
|
+
} : {}, text(values[F.variantImage]) ? {
|
|
1018
|
+
imageUrl: text(values[F.variantImage])
|
|
1019
|
+
} : {}, {
|
|
1020
|
+
inventory: inventory === undefined ? null : Math.max(0, Math.round(inventory))
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
/** The fields a new product is refused without. */ const PRODUCT_PLAN_FIELDS = (fields)=>fields.filter((field)=>!VARIANT_LEVEL_FIELDS.includes(field.id)).map((field)=>field.id === F.title ? _extends({}, field, {
|
|
1024
|
+
required: true
|
|
1025
|
+
}) : field);
|
|
1026
|
+
const VARIANT_PLAN_FIELDS = (fields)=>fields.filter((field)=>VARIANT_LEVEL_FIELDS.includes(field.id)).map((field)=>field.id === F.price ? _extends({}, field, {
|
|
1027
|
+
required: true
|
|
1028
|
+
}) : field);
|
|
1029
|
+
/**
|
|
1030
|
+
* The products resource's plan: the core's plan per product and per variant,
|
|
1031
|
+
* folded back into one verdict per file row (see the block header).
|
|
1032
|
+
*/ export function planProductTransfer(input, context) {
|
|
1033
|
+
const rows = input.rows.map(normalizeRow);
|
|
1034
|
+
const tally = new Tally();
|
|
1035
|
+
// The groups, in the order their first rows appear.
|
|
1036
|
+
const groups = new Map();
|
|
1037
|
+
rows.forEach((row, position)=>{
|
|
1038
|
+
var _groups_get;
|
|
1039
|
+
const key = groupKey(row, position);
|
|
1040
|
+
const group = (_groups_get = groups.get(key)) != null ? _groups_get : {
|
|
1041
|
+
key,
|
|
1042
|
+
positions: []
|
|
1043
|
+
};
|
|
1044
|
+
group.positions.push(position);
|
|
1045
|
+
groups.set(key, group);
|
|
1046
|
+
});
|
|
1047
|
+
const ordered = [
|
|
1048
|
+
...groups.values()
|
|
1049
|
+
];
|
|
1050
|
+
// The product half: each group's first row, with the group's images and option names.
|
|
1051
|
+
const leaders = ordered.map((group)=>{
|
|
1052
|
+
const first = rows[group.positions[0]];
|
|
1053
|
+
const values = pick(first.values, [
|
|
1054
|
+
...PRODUCT_LEVEL_FIELDS,
|
|
1055
|
+
'id'
|
|
1056
|
+
]);
|
|
1057
|
+
const images = [];
|
|
1058
|
+
for (const position of group.positions){
|
|
1059
|
+
var _rows_position;
|
|
1060
|
+
const url = text((_rows_position = rows[position]) == null ? void 0 : _rows_position.values[F.image]);
|
|
1061
|
+
if (url && !images.includes(url)) images.push(url);
|
|
1062
|
+
}
|
|
1063
|
+
if (images.length) values[F.image] = images;
|
|
1064
|
+
else delete values[F.image];
|
|
1065
|
+
OPTION_NAME_FIELDS.forEach((fieldId)=>{
|
|
1066
|
+
if (!isBlankTransferValue(values[fieldId])) return;
|
|
1067
|
+
for (const position of group.positions){
|
|
1068
|
+
var _rows_position;
|
|
1069
|
+
const name = text((_rows_position = rows[position]) == null ? void 0 : _rows_position.values[fieldId]);
|
|
1070
|
+
if (name) {
|
|
1071
|
+
values[fieldId] = name;
|
|
1072
|
+
break;
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
return _extends({}, first, {
|
|
1077
|
+
values
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
const leaderMatches = ordered.map((group)=>{
|
|
1081
|
+
var _input_matches_group_positions_;
|
|
1082
|
+
return (_input_matches_group_positions_ = input.matches[group.positions[0]]) != null ? _input_matches_group_positions_ : {
|
|
1083
|
+
kind: 'new'
|
|
1084
|
+
};
|
|
1085
|
+
});
|
|
1086
|
+
const productPlan = buildTransferPlan({
|
|
1087
|
+
fields: PRODUCT_PLAN_FIELDS(input.fields),
|
|
1088
|
+
rows: leaders,
|
|
1089
|
+
matches: leaderMatches,
|
|
1090
|
+
existing: input.existing,
|
|
1091
|
+
policy: input.policy,
|
|
1092
|
+
limits: context.maxCreates === undefined ? {} : {
|
|
1093
|
+
maxCreates: context.maxCreates
|
|
1094
|
+
}
|
|
1095
|
+
});
|
|
1096
|
+
tally.merge(productPlan.warnings);
|
|
1097
|
+
const variantFields = VARIANT_PLAN_FIELDS(input.fields);
|
|
1098
|
+
const out = new Array(rows.length);
|
|
1099
|
+
// Slugs this plan gives new products, so two new ones never share one.
|
|
1100
|
+
const handedOut = new Set();
|
|
1101
|
+
ordered.forEach((group, at)=>{
|
|
1102
|
+
var _input_existing_get, _before_PRODUCT_VARIANT_ORDER;
|
|
1103
|
+
const planned = productPlan.rows[at];
|
|
1104
|
+
const leaderPosition = group.positions[0];
|
|
1105
|
+
const leaderIndex = rows[leaderPosition].index;
|
|
1106
|
+
const original = (position)=>{
|
|
1107
|
+
var _input_matches_position;
|
|
1108
|
+
return (_input_matches_position = input.matches[position]) != null ? _input_matches_position : {
|
|
1109
|
+
kind: 'new'
|
|
1110
|
+
};
|
|
1111
|
+
};
|
|
1112
|
+
// A product that writes nothing: every row of it follows its first.
|
|
1113
|
+
if (planned.verdict === 'skip' || planned.verdict === 'fail') {
|
|
1114
|
+
for (const position of group.positions){
|
|
1115
|
+
const row = rows[position];
|
|
1116
|
+
out[position] = position === leaderPosition ? planned : _extends({
|
|
1117
|
+
index: row.index,
|
|
1118
|
+
verdict: planned.verdict,
|
|
1119
|
+
recordId: null
|
|
1120
|
+
}, planned.reason ? {
|
|
1121
|
+
reason: planned.reason
|
|
1122
|
+
} : {}, {
|
|
1123
|
+
diff: [],
|
|
1124
|
+
heldBack: [],
|
|
1125
|
+
warnings: [],
|
|
1126
|
+
match: original(position)
|
|
1127
|
+
});
|
|
1128
|
+
}
|
|
1129
|
+
return;
|
|
1130
|
+
}
|
|
1131
|
+
const creating = planned.verdict === 'create';
|
|
1132
|
+
const productId = creating ? context.newProductId() : planned.recordId;
|
|
1133
|
+
const before = creating ? {} : (_input_existing_get = input.existing.get(productId)) != null ? _input_existing_get : {};
|
|
1134
|
+
const optionNames = OPTION_NAME_FIELDS.map((fieldId)=>text(after(planned.diff, before, fieldId)) || null);
|
|
1135
|
+
// The variants the product has, and which one each line names.
|
|
1136
|
+
const existingVariants = creating ? [] : ((_before_PRODUCT_VARIANT_ORDER = before[PRODUCT_VARIANT_ORDER]) != null ? _before_PRODUCT_VARIANT_ORDER : []).map((id)=>{
|
|
1137
|
+
var _before_productVariantKey;
|
|
1138
|
+
return {
|
|
1139
|
+
id,
|
|
1140
|
+
values: (_before_productVariantKey = before[productVariantKey(id)]) != null ? _before_productVariantKey : {}
|
|
1141
|
+
};
|
|
1142
|
+
});
|
|
1143
|
+
const lines = group.positions.filter((position)=>{
|
|
1144
|
+
const values = rows[position].values;
|
|
1145
|
+
return hasAny(values, VARIANT_LEVEL_FIELDS);
|
|
1146
|
+
});
|
|
1147
|
+
const claimed = new Map();
|
|
1148
|
+
const lineTargets = new Map();
|
|
1149
|
+
const lineMatches = [];
|
|
1150
|
+
const lineRows = [];
|
|
1151
|
+
const linePositions = [];
|
|
1152
|
+
const existingVariantValues = new Map();
|
|
1153
|
+
const duplicates = new Map();
|
|
1154
|
+
for (const position of lines){
|
|
1155
|
+
const row = rows[position];
|
|
1156
|
+
const sku = text(row.values[F.sku]).toLowerCase();
|
|
1157
|
+
const key = optionKey(row.values, optionNames);
|
|
1158
|
+
let target = sku ? existingVariants.find((variant)=>text(variant.values[F.sku]).toLowerCase() === sku) : undefined;
|
|
1159
|
+
let via = sku ? {
|
|
1160
|
+
fieldId: F.sku,
|
|
1161
|
+
value: sku
|
|
1162
|
+
} : undefined;
|
|
1163
|
+
if (!target && key) {
|
|
1164
|
+
target = existingVariants.find((variant)=>optionKey(variant.values, optionNames) === key);
|
|
1165
|
+
via = {
|
|
1166
|
+
fieldId: F.option1Value,
|
|
1167
|
+
value: key
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
if (!target && !key && !sku && existingVariants.length === 1 && lines.length === 1) {
|
|
1171
|
+
var _ref;
|
|
1172
|
+
target = existingVariants[0];
|
|
1173
|
+
via = {
|
|
1174
|
+
fieldId: 'id',
|
|
1175
|
+
value: (_ref = target == null ? void 0 : target.id) != null ? _ref : ''
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
// Two lines naming one variant, or one new variant twice: the first one counts.
|
|
1179
|
+
const identity = target ? `v:${target.id}` : sku ? `s:${sku}` : key ? `o:${key}` : null;
|
|
1180
|
+
const earlier = identity ? claimed.get(identity) : undefined;
|
|
1181
|
+
if (earlier !== undefined) {
|
|
1182
|
+
duplicates.set(position, earlier);
|
|
1183
|
+
continue;
|
|
1184
|
+
}
|
|
1185
|
+
if (identity) claimed.set(identity, position);
|
|
1186
|
+
const id = target ? target.id : context.newVariantId();
|
|
1187
|
+
lineTargets.set(position, _extends({
|
|
1188
|
+
id,
|
|
1189
|
+
isNew: !target
|
|
1190
|
+
}, via ? {
|
|
1191
|
+
via
|
|
1192
|
+
} : {}));
|
|
1193
|
+
lineRows.push(_extends({}, row, {
|
|
1194
|
+
values: pick(row.values, VARIANT_LEVEL_FIELDS)
|
|
1195
|
+
}));
|
|
1196
|
+
linePositions.push(position);
|
|
1197
|
+
if (target) {
|
|
1198
|
+
existingVariantValues.set(`${productId}::${target.id}`, target.values);
|
|
1199
|
+
lineMatches.push({
|
|
1200
|
+
kind: 'matched',
|
|
1201
|
+
recordId: `${productId}::${target.id}`,
|
|
1202
|
+
via: via
|
|
1203
|
+
});
|
|
1204
|
+
} else {
|
|
1205
|
+
lineMatches.push({
|
|
1206
|
+
kind: 'new'
|
|
1207
|
+
});
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
const variantPlan = buildTransferPlan({
|
|
1211
|
+
fields: variantFields,
|
|
1212
|
+
rows: lineRows,
|
|
1213
|
+
matches: lineMatches,
|
|
1214
|
+
existing: existingVariantValues,
|
|
1215
|
+
policy: input.policy
|
|
1216
|
+
});
|
|
1217
|
+
tally.merge(variantPlan.warnings);
|
|
1218
|
+
const lineVerdicts = new Map();
|
|
1219
|
+
lineRows.forEach((row, at)=>{
|
|
1220
|
+
const position = linePositions[at];
|
|
1221
|
+
const verdict = variantPlan.rows[at];
|
|
1222
|
+
const target = lineTargets.get(position);
|
|
1223
|
+
// Stock kept per location moves per location, never as one total.
|
|
1224
|
+
if (target && !target.isNew && verdict.verdict === 'update') {
|
|
1225
|
+
const values = existingVariantValues.get(`${productId}::${target.id}`);
|
|
1226
|
+
if ((values == null ? void 0 : values.$byLocation) && verdict.diff.some((change)=>change.fieldId === F.inventory)) {
|
|
1227
|
+
verdict.diff = verdict.diff.filter((change)=>change.fieldId !== F.inventory);
|
|
1228
|
+
verdict.heldBack = [
|
|
1229
|
+
...verdict.heldBack,
|
|
1230
|
+
F.inventory
|
|
1231
|
+
];
|
|
1232
|
+
verdict.warnings = [
|
|
1233
|
+
...new Set([
|
|
1234
|
+
...verdict.warnings,
|
|
1235
|
+
'lockedRule'
|
|
1236
|
+
])
|
|
1237
|
+
];
|
|
1238
|
+
tally.add('lockedRule', {
|
|
1239
|
+
row: row.index,
|
|
1240
|
+
fieldId: F.inventory,
|
|
1241
|
+
value: text(row.values[F.inventory]),
|
|
1242
|
+
detail: 'This variant’s stock is kept by location; change it per location on the products page.'
|
|
1243
|
+
});
|
|
1244
|
+
if (!verdict.diff.length) verdict.verdict = 'unchanged';
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
lineVerdicts.set(position, verdict);
|
|
1248
|
+
});
|
|
1249
|
+
// A new product needs a variant.
|
|
1250
|
+
const createdLines = [
|
|
1251
|
+
...lineVerdicts.entries()
|
|
1252
|
+
].filter(([, verdict])=>verdict.verdict === 'create');
|
|
1253
|
+
if (creating && !createdLines.length) {
|
|
1254
|
+
for (const position of group.positions){
|
|
1255
|
+
const row = rows[position];
|
|
1256
|
+
out[position] = {
|
|
1257
|
+
index: row.index,
|
|
1258
|
+
verdict: 'fail',
|
|
1259
|
+
recordId: null,
|
|
1260
|
+
reason: 'missingRequired',
|
|
1261
|
+
missing: [
|
|
1262
|
+
F.price
|
|
1263
|
+
],
|
|
1264
|
+
diff: position === leaderPosition ? planned.diff : [],
|
|
1265
|
+
heldBack: [],
|
|
1266
|
+
warnings: position === leaderPosition ? planned.warnings : [],
|
|
1267
|
+
match: original(position)
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
return;
|
|
1271
|
+
}
|
|
1272
|
+
// The handle a new product is stored under: the file's, or one made from its title.
|
|
1273
|
+
let slug = '';
|
|
1274
|
+
let problem;
|
|
1275
|
+
if (creating) {
|
|
1276
|
+
const base = text(after(planned.diff, before, F.handle)) || commerceSlug(text(after(planned.diff, before, F.title))) || 'product';
|
|
1277
|
+
const taken = (candidate)=>handedOut.has(candidate) || Boolean(context.slugTaken == null ? void 0 : context.slugTaken.call(context, candidate));
|
|
1278
|
+
slug = base;
|
|
1279
|
+
for(let suffix = 2; taken(slug); suffix += 1){
|
|
1280
|
+
const tail = `-${suffix}`;
|
|
1281
|
+
slug = `${base.slice(0, COMMERCE_SLUG_MAX_LENGTH - tail.length)}${tail}`;
|
|
1282
|
+
}
|
|
1283
|
+
handedOut.add(slug);
|
|
1284
|
+
if (slug !== base) {
|
|
1285
|
+
tally.add('lockedRule', {
|
|
1286
|
+
row: leaderIndex,
|
|
1287
|
+
fieldId: F.handle,
|
|
1288
|
+
value: base,
|
|
1289
|
+
detail: `Another product holds /products/${base}; this one is created at /products/${slug}.`
|
|
1290
|
+
});
|
|
1291
|
+
planned.warnings = [
|
|
1292
|
+
...new Set([
|
|
1293
|
+
...planned.warnings,
|
|
1294
|
+
'lockedRule'
|
|
1295
|
+
])
|
|
1296
|
+
];
|
|
1297
|
+
planned.heldBack = [
|
|
1298
|
+
...planned.heldBack,
|
|
1299
|
+
F.handle
|
|
1300
|
+
];
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
// What the product would hold once every row of it is written, by the model's own rules.
|
|
1304
|
+
const productValues = _extends({}, before);
|
|
1305
|
+
for (const change of planned.diff)productValues[change.fieldId] = change.after;
|
|
1306
|
+
if (creating) productValues[F.handle] = slug;
|
|
1307
|
+
const finalVariants = existingVariants.map((variant)=>({
|
|
1308
|
+
id: variant.id,
|
|
1309
|
+
values: _extends({}, variant.values)
|
|
1310
|
+
}));
|
|
1311
|
+
for (const [position, verdict] of lineVerdicts){
|
|
1312
|
+
if (verdict.verdict !== 'create' && verdict.verdict !== 'update') continue;
|
|
1313
|
+
const target = lineTargets.get(position);
|
|
1314
|
+
let entry = finalVariants.find((variant)=>variant.id === target.id);
|
|
1315
|
+
if (!entry) {
|
|
1316
|
+
entry = {
|
|
1317
|
+
id: target.id,
|
|
1318
|
+
values: {}
|
|
1319
|
+
};
|
|
1320
|
+
finalVariants.push(entry);
|
|
1321
|
+
}
|
|
1322
|
+
for (const change of verdict.diff)entry.values[change.fieldId] = change.after;
|
|
1323
|
+
}
|
|
1324
|
+
if (finalVariants.length > COMMERCE_MAX_VARIANTS) {
|
|
1325
|
+
problem = `A product holds at most ${COMMERCE_MAX_VARIANTS} variants; this file gives it ${finalVariants.length}.`;
|
|
1326
|
+
} else {
|
|
1327
|
+
const check = validateProduct(productFromRecord(productValues, finalVariants));
|
|
1328
|
+
if (check) problem = check;
|
|
1329
|
+
}
|
|
1330
|
+
const create = creating ? {
|
|
1331
|
+
slug,
|
|
1332
|
+
values: Object.fromEntries(planned.diff.map((change)=>[
|
|
1333
|
+
change.fieldId,
|
|
1334
|
+
change.after
|
|
1335
|
+
])),
|
|
1336
|
+
variants: createdLines.map(([position, verdict])=>({
|
|
1337
|
+
id: lineTargets.get(position).id,
|
|
1338
|
+
values: Object.fromEntries(verdict.diff.map((change)=>[
|
|
1339
|
+
change.fieldId,
|
|
1340
|
+
change.after
|
|
1341
|
+
]))
|
|
1342
|
+
}))
|
|
1343
|
+
} : undefined;
|
|
1344
|
+
for (const position of group.positions){
|
|
1345
|
+
var _ref1, _ref2;
|
|
1346
|
+
const row = rows[position];
|
|
1347
|
+
const isLeader = position === leaderPosition;
|
|
1348
|
+
const line = lineVerdicts.get(position);
|
|
1349
|
+
const target = lineTargets.get(position);
|
|
1350
|
+
const duplicateOf = duplicates.get(position);
|
|
1351
|
+
const base = {
|
|
1352
|
+
index: row.index,
|
|
1353
|
+
match: original(position)
|
|
1354
|
+
};
|
|
1355
|
+
if (duplicateOf !== undefined) {
|
|
1356
|
+
const firstRow = rows[duplicateOf].index;
|
|
1357
|
+
tally.add('duplicateInFile', {
|
|
1358
|
+
row: row.index,
|
|
1359
|
+
fieldId: F.sku,
|
|
1360
|
+
value: text(row.values[F.sku]) || text(row.values[F.option1Value]),
|
|
1361
|
+
detail: `The same variant as row ${firstRow + 1}`
|
|
1362
|
+
});
|
|
1363
|
+
out[position] = _extends({}, base, {
|
|
1364
|
+
verdict: 'skip',
|
|
1365
|
+
recordId: creating ? null : productId,
|
|
1366
|
+
reason: 'duplicateInFile',
|
|
1367
|
+
diff: [],
|
|
1368
|
+
heldBack: [],
|
|
1369
|
+
warnings: [
|
|
1370
|
+
'duplicateInFile'
|
|
1371
|
+
]
|
|
1372
|
+
});
|
|
1373
|
+
continue;
|
|
1374
|
+
}
|
|
1375
|
+
const productDiff = isLeader ? planned.diff : [];
|
|
1376
|
+
const lineDiff = line && (line.verdict === 'create' || line.verdict === 'update') ? line.diff : [];
|
|
1377
|
+
const writes = productDiff.length + lineDiff.length > 0;
|
|
1378
|
+
const lineFailed = line && (line.verdict === 'fail' || line.verdict === 'skip');
|
|
1379
|
+
if (lineFailed && isLeader && (productDiff.length || creating)) {
|
|
1380
|
+
var _line_reason;
|
|
1381
|
+
var _line_missing;
|
|
1382
|
+
// The product's own changes still land; the variant on this row does not, and says so.
|
|
1383
|
+
tally.add('droppedCell', _extends({
|
|
1384
|
+
row: row.index
|
|
1385
|
+
}, ((_line_missing = line.missing) == null ? void 0 : _line_missing[0]) ? {
|
|
1386
|
+
fieldId: line.missing[0]
|
|
1387
|
+
} : {}, {
|
|
1388
|
+
detail: `The variant on this row is not written (${(_line_reason = line.reason) != null ? _line_reason : line.verdict}).`
|
|
1389
|
+
}));
|
|
1390
|
+
}
|
|
1391
|
+
const warnings = [
|
|
1392
|
+
...new Set([
|
|
1393
|
+
...isLeader ? planned.warnings : [],
|
|
1394
|
+
...(_ref1 = line == null ? void 0 : line.warnings) != null ? _ref1 : []
|
|
1395
|
+
])
|
|
1396
|
+
];
|
|
1397
|
+
const heldBack = [
|
|
1398
|
+
...isLeader ? planned.heldBack : [],
|
|
1399
|
+
...(_ref2 = line == null ? void 0 : line.heldBack) != null ? _ref2 : []
|
|
1400
|
+
];
|
|
1401
|
+
const plan = _extends({
|
|
1402
|
+
productId,
|
|
1403
|
+
leader: leaderIndex,
|
|
1404
|
+
role: creating ? isLeader ? 'create' : 'part' : 'update'
|
|
1405
|
+
}, target && lineDiff.length ? {
|
|
1406
|
+
variant: {
|
|
1407
|
+
id: target.id,
|
|
1408
|
+
isNew: target.isNew
|
|
1409
|
+
}
|
|
1410
|
+
} : {}, isLeader && create ? {
|
|
1411
|
+
create
|
|
1412
|
+
} : {}, problem ? {
|
|
1413
|
+
problem
|
|
1414
|
+
} : {});
|
|
1415
|
+
if (lineFailed && (!isLeader || !(productDiff.length || creating))) {
|
|
1416
|
+
out[position] = _extends({}, base, {
|
|
1417
|
+
verdict: line.verdict,
|
|
1418
|
+
recordId: creating ? null : productId
|
|
1419
|
+
}, line.reason ? {
|
|
1420
|
+
reason: line.reason
|
|
1421
|
+
} : {}, line.missing ? {
|
|
1422
|
+
missing: line.missing
|
|
1423
|
+
} : {}, {
|
|
1424
|
+
diff: [],
|
|
1425
|
+
heldBack,
|
|
1426
|
+
warnings
|
|
1427
|
+
});
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
const verdict = creating ? isLeader || writes ? 'create' : 'unchanged' : writes ? 'update' : 'unchanged';
|
|
1431
|
+
out[position] = _extends({}, base, {
|
|
1432
|
+
verdict,
|
|
1433
|
+
recordId: creating ? null : productId,
|
|
1434
|
+
diff: [
|
|
1435
|
+
...productDiff,
|
|
1436
|
+
...lineDiff
|
|
1437
|
+
],
|
|
1438
|
+
heldBack,
|
|
1439
|
+
warnings
|
|
1440
|
+
}, verdict === 'create' || verdict === 'update' ? {
|
|
1441
|
+
commerce: plan
|
|
1442
|
+
} : problem ? {
|
|
1443
|
+
commerce: plan
|
|
1444
|
+
} : {});
|
|
1445
|
+
}
|
|
1446
|
+
});
|
|
1447
|
+
const summary = {
|
|
1448
|
+
create: 0,
|
|
1449
|
+
update: 0,
|
|
1450
|
+
unchanged: 0,
|
|
1451
|
+
skip: 0,
|
|
1452
|
+
fail: 0,
|
|
1453
|
+
total: out.length
|
|
1454
|
+
};
|
|
1455
|
+
for (const row of out)summary[row.verdict] += 1;
|
|
1456
|
+
const warnings = tally.list(WARNING_ORDER);
|
|
1457
|
+
return {
|
|
1458
|
+
rows: out,
|
|
1459
|
+
summary,
|
|
1460
|
+
warnings,
|
|
1461
|
+
acknowledgementsRequired: warnings.filter((entry)=>entry.requiresAcknowledgement).map((entry)=>entry.class)
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1464
|
+
/** The handles a plan may create products under, for the server to ask which are taken. */ export function productPlanHandles(rows) {
|
|
1465
|
+
const out = new Set();
|
|
1466
|
+
for (const row of rows){
|
|
1467
|
+
const handle = commerceSlug(text(row.values[F.handle])) || commerceSlug(text(row.values[F.title]));
|
|
1468
|
+
if (handle) out.add(handle);
|
|
1469
|
+
}
|
|
1470
|
+
return [
|
|
1471
|
+
...out
|
|
1472
|
+
];
|
|
1473
|
+
}
|
|
1474
|
+
/** The product fields a row's changes set, as the document stores them. */ function productFieldPatch(changes, categoryIdsOf) {
|
|
1475
|
+
var _fixedListId, _fixedListId1;
|
|
1476
|
+
const patch = {};
|
|
1477
|
+
const has = (fieldId)=>fieldId in changes;
|
|
1478
|
+
const value = (fieldId)=>changes[fieldId];
|
|
1479
|
+
if (has(F.title)) patch.name = text(value(F.title));
|
|
1480
|
+
if (has(F.description)) patch.description = text(value(F.description)) || undefined;
|
|
1481
|
+
if (has(F.kind)) patch.type = (_fixedListId = fixedListId(PRODUCT_KIND_PICKLIST, value(F.kind))) != null ? _fixedListId : 'physical';
|
|
1482
|
+
if (has(F.status)) patch.status = (_fixedListId1 = fixedListId(PRODUCT_STATUS_PICKLIST, value(F.status))) != null ? _fixedListId1 : 'active';
|
|
1483
|
+
if (has(F.tags)) patch.tags = productTagList(value(F.tags));
|
|
1484
|
+
if (has(F.categories)) {
|
|
1485
|
+
const names = Array.isArray(value(F.categories)) ? value(F.categories).map(text).filter(Boolean) : [];
|
|
1486
|
+
patch.categoryIds = categoryIdsOf(names);
|
|
1487
|
+
}
|
|
1488
|
+
if (has(F.image)) {
|
|
1489
|
+
const urls = Array.isArray(value(F.image)) ? value(F.image).map(text).filter(Boolean) : [];
|
|
1490
|
+
patch.mediaUrls = urls;
|
|
1491
|
+
}
|
|
1492
|
+
if (has(F.taxExempt)) patch.taxExempt = value(F.taxExempt) === true;
|
|
1493
|
+
if (has(F.oversellPolicy)) {
|
|
1494
|
+
var _fixedListId2;
|
|
1495
|
+
patch.oversellPolicy = (_fixedListId2 = fixedListId(PRODUCT_OVERSELL_PICKLIST, value(F.oversellPolicy))) != null ? _fixedListId2 : 'deny';
|
|
1496
|
+
}
|
|
1497
|
+
if (has(F.lowStockThreshold)) {
|
|
1498
|
+
const threshold = Number(value(F.lowStockThreshold));
|
|
1499
|
+
patch.lowStockThreshold = value(F.lowStockThreshold) === null || !Number.isFinite(threshold) ? undefined : Math.max(0, Math.round(threshold));
|
|
1500
|
+
}
|
|
1501
|
+
return patch;
|
|
1502
|
+
}
|
|
1503
|
+
/** A product with one planned row's changes written into it — the product's own on its first row, and the row's variant. */ export function applyProductRow(current, changes, categoryIdsOf) {
|
|
1504
|
+
var _current_options, _changes_product;
|
|
1505
|
+
const next = _extends({}, current, {
|
|
1506
|
+
options: ((_current_options = current.options) != null ? _current_options : []).map((option)=>_extends({}, option, {
|
|
1507
|
+
values: [
|
|
1508
|
+
...option.values
|
|
1509
|
+
]
|
|
1510
|
+
})),
|
|
1511
|
+
variants: current.variants.map((variant)=>_extends({}, variant))
|
|
1512
|
+
});
|
|
1513
|
+
const product = (_changes_product = changes.product) != null ? _changes_product : {};
|
|
1514
|
+
Object.assign(next, productFieldPatch(product, categoryIdsOf));
|
|
1515
|
+
if (F.seoTitle in product || F.seoDescription in product) {
|
|
1516
|
+
var _current_seo;
|
|
1517
|
+
const seo = _extends({}, (_current_seo = current.seo) != null ? _current_seo : {});
|
|
1518
|
+
if (F.seoTitle in product) seo.title = text(product[F.seoTitle]) || undefined;
|
|
1519
|
+
if (F.seoDescription in product) seo.description = text(product[F.seoDescription]) || undefined;
|
|
1520
|
+
next.seo = Object.fromEntries(Object.entries(seo).filter(([, entry])=>entry !== undefined));
|
|
1521
|
+
}
|
|
1522
|
+
// An option the product does not have yet takes its name.
|
|
1523
|
+
const options = next.options;
|
|
1524
|
+
OPTION_NAME_FIELDS.forEach((fieldId, axis)=>{
|
|
1525
|
+
const name = text(product[fieldId]);
|
|
1526
|
+
if (name && !options[axis]) options[axis] = {
|
|
1527
|
+
name,
|
|
1528
|
+
values: []
|
|
1529
|
+
};
|
|
1530
|
+
});
|
|
1531
|
+
const variant = changes.variant;
|
|
1532
|
+
if (variant) {
|
|
1533
|
+
const names = options.map((option)=>{
|
|
1534
|
+
var _ref;
|
|
1535
|
+
return (_ref = option == null ? void 0 : option.name) != null ? _ref : null;
|
|
1536
|
+
});
|
|
1537
|
+
const at = next.variants.findIndex((entry)=>entry.id === variant.id);
|
|
1538
|
+
const existing = at >= 0 ? next.variants[at] : null;
|
|
1539
|
+
const record = existing ? variantRecord(existing, options) : {};
|
|
1540
|
+
const merged = variantFromRecord(variant.id, _extends({}, record, variant.values), names);
|
|
1541
|
+
const written = existing ? _extends({}, existing, merged, existing.inventoryByLocation ? {
|
|
1542
|
+
inventoryByLocation: existing.inventoryByLocation,
|
|
1543
|
+
inventory: existing.inventory
|
|
1544
|
+
} : {}) : merged;
|
|
1545
|
+
for (const key of [
|
|
1546
|
+
'sku',
|
|
1547
|
+
'barcode',
|
|
1548
|
+
'compareAtPriceUsd',
|
|
1549
|
+
'weightGrams',
|
|
1550
|
+
'imageUrl'
|
|
1551
|
+
]){
|
|
1552
|
+
if (merged[key] === undefined) delete written[key];
|
|
1553
|
+
}
|
|
1554
|
+
if (at >= 0) next.variants[at] = written;
|
|
1555
|
+
else next.variants.push(written);
|
|
1556
|
+
// Each option value the variant holds is one its option offers.
|
|
1557
|
+
options.forEach((option, axis)=>{
|
|
1558
|
+
const value = text(variant.values[OPTION_VALUE_FIELDS[axis]]);
|
|
1559
|
+
if (option && value && !option.values.includes(value)) option.values.push(value);
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
next.options = options.filter(Boolean);
|
|
1563
|
+
if (!next.options.length) delete next.options;
|
|
1564
|
+
return next;
|
|
1565
|
+
}
|
|
1566
|
+
/** A new product's document from its leader's plan, before the platform's stamps. */ export function createProductDocument(create, categoryIdsOf) {
|
|
1567
|
+
const base = productFromRecord(_extends({}, create.values, {
|
|
1568
|
+
[F.handle]: create.slug
|
|
1569
|
+
}), create.variants);
|
|
1570
|
+
return applyProductRow(_extends({}, base, {
|
|
1571
|
+
variants: base.variants
|
|
1572
|
+
}), {
|
|
1573
|
+
product: Object.fromEntries(Object.entries(create.values).filter(([fieldId])=>!OPTION_NAME_FIELDS.includes(fieldId)))
|
|
1574
|
+
}, categoryIdsOf);
|
|
1575
|
+
}
|
|
1576
|
+
/** The values one planned row changes, split into the product's and its variant's. */ export function productRowChanges(row) {
|
|
1577
|
+
var _row_commerce;
|
|
1578
|
+
const product = {};
|
|
1579
|
+
const variant = {};
|
|
1580
|
+
for (const change of row.diff){
|
|
1581
|
+
if (VARIANT_LEVEL_FIELDS.includes(change.fieldId)) variant[change.fieldId] = change.after;
|
|
1582
|
+
else if (PRODUCT_LEVEL_FIELDS.includes(change.fieldId)) product[change.fieldId] = change.after;
|
|
1583
|
+
}
|
|
1584
|
+
const target = (_row_commerce = row.commerce) == null ? void 0 : _row_commerce.variant;
|
|
1585
|
+
return _extends({
|
|
1586
|
+
product
|
|
1587
|
+
}, target ? {
|
|
1588
|
+
variant: _extends({}, target, {
|
|
1589
|
+
values: variant
|
|
1590
|
+
})
|
|
1591
|
+
} : {});
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
//# sourceMappingURL=product-transfer.js.map
|