@aglyn/plugins-commerce 1.0.0-beta.220 → 1.0.0-beta.222

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.
Files changed (130) hide show
  1. package/README.md +4 -2
  2. package/package.json +14 -13
  3. package/src/lib/components/console/catalog-organization-card.component.js +9 -0
  4. package/src/lib/components/console/catalog-organization-card.component.js.map +1 -1
  5. package/src/lib/components/console/discounts-card.component.js +8 -0
  6. package/src/lib/components/console/discounts-card.component.js.map +1 -1
  7. package/src/lib/components/console/gift-cards-card.component.js +8 -0
  8. package/src/lib/components/console/gift-cards-card.component.js.map +1 -1
  9. package/src/lib/components/console/host-coupons-card.component.js +8 -0
  10. package/src/lib/components/console/host-coupons-card.component.js.map +1 -1
  11. package/src/lib/components/console/host-orders-card.component.js +34 -70
  12. package/src/lib/components/console/host-orders-card.component.js.map +1 -1
  13. package/src/lib/components/console/product-zones.d.ts +7 -6
  14. package/src/lib/components/console/product-zones.js +5 -4
  15. package/src/lib/components/console/product-zones.js.map +1 -1
  16. package/src/lib/components/console/products-hub-card.component.js +86 -284
  17. package/src/lib/components/console/products-hub-card.component.js.map +1 -1
  18. package/src/lib/declarations.console-server.d.ts +38 -0
  19. package/src/lib/declarations.console-server.js +67 -0
  20. package/src/lib/declarations.console-server.js.map +1 -0
  21. package/src/lib/model/commerce-gift-cards.d.ts +26 -0
  22. package/src/lib/model/commerce-gift-cards.js +42 -0
  23. package/src/lib/model/commerce-gift-cards.js.map +1 -1
  24. package/src/lib/model/commerce-orders.d.ts +0 -57
  25. package/src/lib/model/commerce-orders.js +0 -80
  26. package/src/lib/model/commerce-orders.js.map +1 -1
  27. package/src/lib/model/index.d.ts +0 -1
  28. package/src/lib/model/index.js +0 -1
  29. package/src/lib/model/index.js.map +1 -1
  30. package/src/lib/plugin.js +5 -0
  31. package/src/lib/plugin.js.map +1 -1
  32. package/src/lib/server/api-v1/openapi.d.ts +27 -0
  33. package/src/lib/server/api-v1/openapi.js +219 -0
  34. package/src/lib/server/api-v1/openapi.js.map +1 -0
  35. package/src/lib/server/api-v1/orders-and-products.d.ts +19 -0
  36. package/src/lib/server/api-v1/orders-and-products.js +419 -0
  37. package/src/lib/server/api-v1/orders-and-products.js.map +1 -0
  38. package/src/lib/server/billing-webhook.js +83 -56
  39. package/src/lib/server/billing-webhook.js.map +1 -1
  40. package/src/lib/server/download.js +4 -1
  41. package/src/lib/server/download.js.map +1 -1
  42. package/src/lib/server/fulfill-order.d.ts +52 -11
  43. package/src/lib/server/fulfill-order.js +6 -9
  44. package/src/lib/server/fulfill-order.js.map +1 -1
  45. package/src/lib/server/gift-card-issue.d.ts +57 -0
  46. package/src/lib/server/gift-card-issue.js +155 -0
  47. package/src/lib/server/gift-card-issue.js.map +1 -0
  48. package/src/lib/server/gift-cards.js +25 -84
  49. package/src/lib/server/gift-cards.js.map +1 -1
  50. package/src/lib/server/membership-register.js +9 -8
  51. package/src/lib/server/membership-register.js.map +1 -1
  52. package/src/lib/server/newsletter.js +7 -5
  53. package/src/lib/server/newsletter.js.map +1 -1
  54. package/src/lib/server/paid-media-delivery.d.ts +21 -0
  55. package/src/lib/server/paid-media-delivery.js +23 -5
  56. package/src/lib/server/paid-media-delivery.js.map +1 -1
  57. package/src/lib/server/paid-media-serve-count.d.ts +28 -0
  58. package/src/lib/server/paid-media-serve-count.js +46 -0
  59. package/src/lib/server/paid-media-serve-count.js.map +1 -0
  60. package/src/lib/server/person-eraser.d.ts +45 -0
  61. package/src/lib/server/person-eraser.js +57 -0
  62. package/src/lib/server/person-eraser.js.map +1 -0
  63. package/src/lib/server/pos-order.js +17 -9
  64. package/src/lib/server/pos-order.js.map +1 -1
  65. package/src/lib/server/refund.js +15 -15
  66. package/src/lib/server/refund.js.map +1 -1
  67. package/src/lib/server/restock-flag.d.ts +2 -2
  68. package/src/lib/server/restock-flag.js +1 -1
  69. package/src/lib/server/restock-flag.js.map +1 -1
  70. package/src/lib/server/stream.js +4 -1
  71. package/src/lib/server/stream.js.map +1 -1
  72. package/src/lib/server.js +5 -21
  73. package/src/lib/server.js.map +1 -1
  74. package/src/lib/testing/stand-in-person-records.d.ts +43 -0
  75. package/src/lib/testing/stand-in-person-records.js +63 -0
  76. package/src/lib/testing/stand-in-person-records.js.map +1 -0
  77. package/src/lib/testing/stand-in-record-system.d.ts +11 -2
  78. package/src/lib/testing/stand-in-record-system.js +5 -3
  79. package/src/lib/testing/stand-in-record-system.js.map +1 -1
  80. package/src/lib/transfer/gift-card-confirm-step.component.d.ts +22 -0
  81. package/src/lib/transfer/gift-card-confirm-step.component.js +259 -0
  82. package/src/lib/transfer/gift-card-confirm-step.component.js.map +1 -0
  83. package/src/lib/transfer/gift-cards-plan.d.ts +29 -0
  84. package/src/lib/transfer/gift-cards-plan.js +40 -0
  85. package/src/lib/transfer/gift-cards-plan.js.map +1 -0
  86. package/src/lib/transfer/gift-cards.server.d.ts +34 -0
  87. package/src/lib/transfer/gift-cards.server.js +296 -0
  88. package/src/lib/transfer/gift-cards.server.js.map +1 -0
  89. package/src/lib/transfer/list-filter.d.ts +29 -0
  90. package/src/lib/transfer/list-filter.js +112 -0
  91. package/src/lib/transfer/list-filter.js.map +1 -0
  92. package/src/lib/transfer/product-import-results.d.ts +29 -0
  93. package/src/lib/transfer/product-import-results.js +49 -0
  94. package/src/lib/transfer/product-import-results.js.map +1 -0
  95. package/src/lib/transfer/product-import-step.component.d.ts +30 -0
  96. package/src/lib/transfer/product-import-step.component.js +99 -0
  97. package/src/lib/transfer/product-import-step.component.js.map +1 -0
  98. package/src/lib/transfer/product-transfer.d.ts +211 -0
  99. package/src/lib/transfer/product-transfer.js +1594 -0
  100. package/src/lib/transfer/product-transfer.js.map +1 -0
  101. package/src/lib/transfer/products.server.d.ts +44 -0
  102. package/src/lib/transfer/products.server.js +550 -0
  103. package/src/lib/transfer/products.server.js.map +1 -0
  104. package/src/lib/transfer/records-transfer.d.ts +182 -0
  105. package/src/lib/transfer/records-transfer.js +1153 -0
  106. package/src/lib/transfer/records-transfer.js.map +1 -0
  107. package/src/lib/transfer/records.server.d.ts +68 -0
  108. package/src/lib/transfer/records.server.js +455 -0
  109. package/src/lib/transfer/records.server.js.map +1 -0
  110. package/src/lib/transfer/register-transfer-resources.d.ts +17 -0
  111. package/src/lib/transfer/register-transfer-resources.js +171 -0
  112. package/src/lib/transfer/register-transfer-resources.js.map +1 -0
  113. package/src/lib/transfer/register-transfer-ui.d.ts +23 -0
  114. package/src/lib/transfer/register-transfer-ui.js +78 -0
  115. package/src/lib/transfer/register-transfer-ui.js.map +1 -0
  116. package/src/lib/transfer/server-common.d.ts +76 -0
  117. package/src/lib/transfer/server-common.js +165 -0
  118. package/src/lib/transfer/server-common.js.map +1 -0
  119. package/src/lib/transfer/transfer-header-actions.component.d.ts +39 -0
  120. package/src/lib/transfer/transfer-header-actions.component.js +79 -0
  121. package/src/lib/transfer/transfer-header-actions.component.js.map +1 -0
  122. package/src/lib/transfer/transfer-keys.d.ts +31 -0
  123. package/src/lib/transfer/transfer-keys.js +56 -0
  124. package/src/lib/transfer/transfer-keys.js.map +1 -0
  125. package/src/lib/model/commerce-io.d.ts +0 -36
  126. package/src/lib/model/commerce-io.js +0 -245
  127. package/src/lib/model/commerce-io.js.map +0 -1
  128. package/src/lib/server/contact-refund.d.ts +0 -117
  129. package/src/lib/server/contact-refund.js +0 -196
  130. package/src/lib/server/contact-refund.js.map +0 -1
@@ -0,0 +1,550 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import * as Aglyn from "@aglyn/aglyn/server";
18
+ import { buildMatchLookup } from "@aglyn/aglyn/data-transfer";
19
+ import { firebaseAdmin, getOrgForHost, logHostActivity } from "@aglyn/tenant-data-admin";
20
+ import { PRODUCT_LIST_QUERY } from "../constants/product-list-query.js";
21
+ import { commerceSlug, liftLegacyProduct, productCollectionIds, productSearchFields, productStockFields, validateProduct } from "../model/commerce.js";
22
+ import { PRODUCT_CATEGORY_PICKLIST, PRODUCT_FIELD, PRODUCT_FIXED_PICKLISTS, PRODUCT_LEVEL_FIELDS, PRODUCT_VARIANT_ORDER, applyProductRow, createProductDocument, planProductTransfer, productExportRowCount, productExportRows, productPlanHandles, productRecord, productRowChanges, productVariantKey } from "./product-transfer.js";
23
+ import { NOTHING, ROW_BUDGET_MS, eachSourceDoc, firestoreOf, hostRefOf, readSourcePage, readerSeesHost, revertEntries, withoutUndefined } from "./server-common.js";
24
+ const ID_IN_MAX = 30;
25
+ function productsOf(firestore, ctx) {
26
+ return hostRefOf(firestore, ctx).collection('products');
27
+ }
28
+ function productSource(firestore, ctx) {
29
+ return {
30
+ collection: productsOf(firestore, ctx),
31
+ declaration: PRODUCT_LIST_QUERY,
32
+ base: [
33
+ {
34
+ path: 'deletedAt',
35
+ op: '==',
36
+ value: null
37
+ }
38
+ ]
39
+ };
40
+ }
41
+ const isLive = (data)=>Boolean(data) && ((data == null ? void 0 : data['deletedAt']) === null || (data == null ? void 0 : data['deletedAt']) === undefined);
42
+ /** The site's categories, id → name. */ async function readCategoryNames(firestore, ctx) {
43
+ const snapshot = await hostRefOf(firestore, ctx).collection('productCategories').limit(2000).get();
44
+ return new Map(snapshot.docs.map((doc)=>{
45
+ var _doc_get;
46
+ return [
47
+ doc.id,
48
+ String((_doc_get = doc.get('name')) != null ? _doc_get : doc.id)
49
+ ];
50
+ }));
51
+ }
52
+ /** Names → ids by the site's categories, in any case; a name it lacks is left out. */ function categoryIdsBy(names) {
53
+ const byName = new Map([
54
+ ...names
55
+ ].map(([id, name])=>[
56
+ name.trim().toLowerCase(),
57
+ id
58
+ ]));
59
+ return (wanted)=>{
60
+ const ids = [];
61
+ for (const name of wanted){
62
+ var _byName_get;
63
+ const id = (_byName_get = byName.get(name.trim().toLowerCase())) != null ? _byName_get : names.has(name) ? name : undefined;
64
+ if (id && !ids.includes(id)) ids.push(id);
65
+ }
66
+ return ids;
67
+ };
68
+ }
69
+ /** The smart collections a product's write answers (AGL-3321). */ async function readSmartCollections(firestore, ctx) {
70
+ const snapshot = await hostRefOf(firestore, ctx).collection('collections').where('kind', '==', 'catalog').where('mode', '==', 'smart').get();
71
+ return snapshot.docs.map((doc)=>{
72
+ var _doc_get;
73
+ return {
74
+ id: doc.id,
75
+ rules: (_doc_get = doc.get('rules')) != null ? _doc_get : [],
76
+ matchAll: doc.get('matchAll')
77
+ };
78
+ });
79
+ }
80
+ /** The keys every product write derives, from the product as it will stand. */ function derivedKeys(product, smart) {
81
+ var _ref, _ref1;
82
+ var _product_variants_, _product_mediaUrls;
83
+ return _extends({}, productSearchFields(product), productStockFields(product), {
84
+ collectionIds: productCollectionIds(product, smart),
85
+ priceUsd: (_ref = (_product_variants_ = product.variants[0]) == null ? void 0 : _product_variants_.priceUsd) != null ? _ref : 0,
86
+ imageUrl: (_ref1 = (_product_mediaUrls = product.mediaUrls) == null ? void 0 : _product_mediaUrls[0]) != null ? _ref1 : null
87
+ });
88
+ }
89
+ /** The document fields a product write may set, the product resource's allow-list. */ const PRODUCT_DOC_KEYS = [
90
+ 'name',
91
+ 'description',
92
+ 'type',
93
+ 'status',
94
+ 'tags',
95
+ 'categoryIds',
96
+ 'mediaUrls',
97
+ 'options',
98
+ 'variants',
99
+ 'seo',
100
+ 'taxExempt',
101
+ 'oversellPolicy',
102
+ 'lowStockThreshold'
103
+ ];
104
+ /** The record keys an undo entry keeps: the product's writable fields and its variants. */ function undoKeys(record, only) {
105
+ var _record_PRODUCT_VARIANT_ORDER;
106
+ const keys = only != null ? only : [
107
+ ...PRODUCT_LEVEL_FIELDS.filter((fieldId)=>fieldId !== PRODUCT_FIELD.published),
108
+ ...((_record_PRODUCT_VARIANT_ORDER = record[PRODUCT_VARIANT_ORDER]) != null ? _record_PRODUCT_VARIANT_ORDER : []).map(productVariantKey)
109
+ ];
110
+ return Object.fromEntries(keys.map((key)=>{
111
+ var _record_key;
112
+ return [
113
+ key,
114
+ (_record_key = record[key]) != null ? _record_key : null
115
+ ];
116
+ }));
117
+ }
118
+ /*------------------------------------------
119
+ * Export
120
+ *-----------------------------------------*/ /** The rows of one page of products. A product is as many rows as it has variants (or images). */ export async function readProductsPage(ctx, cursor, fieldIds, options, firestore = firestoreOf()) {
121
+ var _ref;
122
+ if (!readerSeesHost(ctx, options)) return NOTHING;
123
+ const pageSize = Math.max(1, Math.min(200, (_ref = options == null ? void 0 : options.pageSize) != null ? _ref : 200));
124
+ const page = await readSourcePage(firestore, productSource(firestore, ctx), cursor, options, pageSize);
125
+ const names = fieldIds.includes(PRODUCT_FIELD.categories) ? await readCategoryNames(firestore, ctx) : new Map();
126
+ const rows = page.docs.filter((doc)=>isLive(doc.data())).flatMap((doc)=>productExportRows(doc.id, liftLegacyProduct(doc.data()), fieldIds, names));
127
+ return {
128
+ rows,
129
+ next: page.next
130
+ };
131
+ }
132
+ /** How many rows the export writes: every live product's variant (or image) rows. */ export async function countProductRows(ctx, options, firestore = firestoreOf()) {
133
+ if (!readerSeesHost(ctx, options)) return 0;
134
+ let rows = 0;
135
+ await eachSourceDoc(firestore, productSource(firestore, ctx), options, (doc)=>{
136
+ const data = doc.data();
137
+ if (isLive(data)) rows += productExportRowCount(liftLegacyProduct(data));
138
+ });
139
+ return rows;
140
+ }
141
+ /*------------------------------------------
142
+ * Matching
143
+ *-----------------------------------------*/ /** The values a product is found by: its handle, every SKU it carries, its id. */ function matchValues(id, data) {
144
+ const product = liftLegacyProduct(data);
145
+ return {
146
+ [PRODUCT_FIELD.handle]: product.slug,
147
+ [PRODUCT_FIELD.sku]: product.variants.map((variant)=>variant.sku).filter(Boolean),
148
+ id
149
+ };
150
+ }
151
+ /** The live products holding each requested handle, SKU or id, and each one's record. */ export async function lookupProducts(ctx, requests, firestore = firestoreOf()) {
152
+ const products = productsOf(firestore, ctx);
153
+ const found = new Map();
154
+ const keys = [];
155
+ for (const request of requests){
156
+ keys.push({
157
+ fieldId: request.fieldId,
158
+ normalizer: request.normalizer
159
+ });
160
+ for(let at = 0; at < request.values.length; at += ID_IN_MAX){
161
+ const slice = request.values.slice(at, at + ID_IN_MAX);
162
+ if (request.fieldId === 'id') {
163
+ const snapshots = await firestore.getAll(...slice.map((id)=>products.doc(id)));
164
+ for (const doc of snapshots)if (doc.exists && isLive(doc.data())) found.set(doc.id, doc.data());
165
+ continue;
166
+ }
167
+ const query = request.fieldId === PRODUCT_FIELD.handle ? products.where('slug', 'in', slice) : request.fieldId === PRODUCT_FIELD.sku ? products.where('skus', 'array-contains-any', slice) : null;
168
+ if (!query) continue;
169
+ for (const doc of (await query.get()).docs)if (isLive(doc.data())) found.set(doc.id, doc.data());
170
+ }
171
+ }
172
+ const names = found.size ? await readCategoryNames(firestore, ctx) : new Map();
173
+ const records = new Map();
174
+ for (const [id, data] of found)records.set(id, productRecord(liftLegacyProduct(data), names));
175
+ const lookup = buildMatchLookup([
176
+ ...found
177
+ ].map(([id, data])=>({
178
+ id,
179
+ values: matchValues(id, data)
180
+ })), keys);
181
+ return {
182
+ lookup,
183
+ records
184
+ };
185
+ }
186
+ /*------------------------------------------
187
+ * Lists
188
+ *-----------------------------------------*/ /** The fixed lists, and the site's categories as a list a file may add to. */ export async function productPicklists(ctx, picklistIds, firestore = firestoreOf()) {
189
+ const out = {};
190
+ for (const picklistId of picklistIds){
191
+ const fixed = PRODUCT_FIXED_PICKLISTS[picklistId];
192
+ if (fixed) {
193
+ var _fixed_defaultValueId;
194
+ out[picklistId] = {
195
+ spec: fixed,
196
+ set: {
197
+ values: fixed.standardValues.map((value)=>({
198
+ id: value.id,
199
+ label: value.label,
200
+ active: true
201
+ })),
202
+ defaultValueId: (_fixed_defaultValueId = fixed.defaultValueId) != null ? _fixed_defaultValueId : null
203
+ }
204
+ };
205
+ } else if (picklistId === PRODUCT_CATEGORY_PICKLIST) {
206
+ const names = await readCategoryNames(firestore, ctx);
207
+ out[picklistId] = {
208
+ spec: {
209
+ restricted: false,
210
+ standardValues: []
211
+ },
212
+ set: {
213
+ values: [
214
+ ...names
215
+ ].map(([id, label])=>({
216
+ id,
217
+ label,
218
+ active: true
219
+ })),
220
+ defaultValueId: null
221
+ }
222
+ };
223
+ }
224
+ }
225
+ return out;
226
+ }
227
+ /**
228
+ * The categories the person chose to add, created before the first product
229
+ * names them — each under the id the wizard minted, so a retry finds it.
230
+ */ export async function addProductCategories(ctx, picklistId, values, firestore = firestoreOf()) {
231
+ if (picklistId !== PRODUCT_CATEGORY_PICKLIST) return;
232
+ const categories = hostRefOf(firestore, ctx).collection('productCategories');
233
+ const names = await readCategoryNames(firestore, ctx);
234
+ const held = new Set([
235
+ ...names.values()
236
+ ].map((name)=>name.trim().toLowerCase()));
237
+ for (const value of values){
238
+ const name = value.label.trim();
239
+ if (!name || held.has(name.toLowerCase())) continue;
240
+ held.add(name.toLowerCase());
241
+ await categories.doc(value.id).set({
242
+ name,
243
+ slug: commerceSlug(name) || value.id,
244
+ parentId: null
245
+ }, {
246
+ merge: true
247
+ });
248
+ }
249
+ }
250
+ /*------------------------------------------
251
+ * Plan
252
+ *-----------------------------------------*/ const newId = ()=>Aglyn.createResourceUid();
253
+ const newVariantId = ()=>`v${Aglyn.createResourceUid().slice(0, 10)}`;
254
+ /**
255
+ * The dry run: the slugs a new product would collide with (a deleted
256
+ * product's included, which still holds its address) and the plan's room
257
+ * for new products, then the fold of `planProductTransfer`.
258
+ */ export async function planProducts(ctx, input, firestore = firestoreOf()) {
259
+ var _ref;
260
+ const products = productsOf(firestore, ctx);
261
+ const taken = new Set();
262
+ const candidates = productPlanHandles(input.rows).flatMap((base)=>[
263
+ base,
264
+ ...[
265
+ 2,
266
+ 3,
267
+ 4,
268
+ 5
269
+ ].map((n)=>`${base}-${n}`)
270
+ ]);
271
+ for(let at = 0; at < candidates.length; at += ID_IN_MAX){
272
+ const slice = candidates.slice(at, at + ID_IN_MAX);
273
+ for (const doc of (await products.where('slug', 'in', slice).get()).docs)taken.add(String(doc.get('slug')));
274
+ }
275
+ const owner = await getOrgForHost(ctx.hostId);
276
+ const org = (_ref = owner == null ? void 0 : owner.org) != null ? _ref : null;
277
+ const used = Number((await products.count().get()).data().count) || 0;
278
+ const maxCreates = Aglyn.checkEntitlement(org, 'commerce') ? Aglyn.checkQuota(org, 'productsPerHost', used).remaining : 0;
279
+ return planProductTransfer(input, {
280
+ newProductId: newId,
281
+ newVariantId,
282
+ slugTaken: (slug)=>taken.has(slug),
283
+ maxCreates
284
+ });
285
+ }
286
+ /*------------------------------------------
287
+ * Apply
288
+ *-----------------------------------------*/ /** A refusal the apply records for one row, rather than failing the chunk. */ let RowRefusal = class RowRefusal extends Error {
289
+ constructor(reason, message){
290
+ super(message), this.reason = reason;
291
+ }
292
+ };
293
+ /** Writes one chunk of planned product rows through the product write path. */ export async function applyProducts(ctx, chunk, writer, firestore = firestoreOf()) {
294
+ var _ref, _ctx_actorUid;
295
+ const results = [];
296
+ const undo = [];
297
+ const products = productsOf(firestore, ctx);
298
+ const hostId = ctx.hostId;
299
+ const { Timestamp, FieldValue } = firebaseAdmin.firestore;
300
+ const names = await readCategoryNames(firestore, ctx);
301
+ const categoryIdsOf = categoryIdsBy(names);
302
+ const smart = await readSmartCollections(firestore, ctx);
303
+ const owner = await getOrgForHost(hostId);
304
+ const org = (_ref = owner == null ? void 0 : owner.org) != null ? _ref : null;
305
+ const actor = {
306
+ uid: (_ctx_actorUid = ctx.actorUid) != null ? _ctx_actorUid : 'import',
307
+ email: null
308
+ };
309
+ const logged = new Set();
310
+ const adjustments = [];
311
+ const done = async (result, entry)=>{
312
+ results.push(result);
313
+ if (entry) undo.push(entry);
314
+ await writer.markApplied(result, entry);
315
+ };
316
+ for (const row of chunk.rows){
317
+ const earlier = await writer.alreadyApplied(row.index);
318
+ if (earlier) {
319
+ results.push(earlier);
320
+ continue;
321
+ }
322
+ if (writer.timeLeftMs() < ROW_BUDGET_MS) break;
323
+ const plan = row.commerce;
324
+ if (!plan) {
325
+ await done({
326
+ row: row.index,
327
+ outcome: 'failed',
328
+ message: 'The dry run carried nothing to write for this row.'
329
+ });
330
+ continue;
331
+ }
332
+ const ref = products.doc(plan.productId);
333
+ try {
334
+ if (plan.role === 'create') {
335
+ const product = createProductDocument(plan.create, categoryIdsOf);
336
+ const now = Date.now();
337
+ const doc = withoutUndefined(_extends({}, product, derivedKeys(product, smart), {
338
+ createdAtMs: now,
339
+ updatedAtMs: now,
340
+ deletedAt: null,
341
+ createdAt: Timestamp.now(),
342
+ updatedAt: Timestamp.now(),
343
+ createdBy: ctx.actorUid
344
+ }));
345
+ const created = await firestore.runTransaction(async (tx)=>{
346
+ const existing = await tx.get(ref);
347
+ // A retry after the write landed and before its row was recorded.
348
+ if (existing.exists) return false;
349
+ const used = (await tx.get(products.count())).data().count;
350
+ const quota = Aglyn.checkQuota(org, 'productsPerHost', Number(used) || 0);
351
+ if (!quota.allowed) {
352
+ throw new RowRefusal('planLimit', `Your plan includes ${quota.limit} products — upgrade in Billing for more.`);
353
+ }
354
+ tx.create(ref, doc);
355
+ return true;
356
+ });
357
+ if (created && !logged.has(plan.productId)) {
358
+ logged.add(plan.productId);
359
+ await logHostActivity(hostId, actor, 'Imported product', {
360
+ type: 'content',
361
+ id: plan.productId,
362
+ name: product.name
363
+ });
364
+ }
365
+ await done({
366
+ row: row.index,
367
+ outcome: 'created',
368
+ recordId: plan.productId
369
+ }, {
370
+ row: row.index,
371
+ recordId: plan.productId,
372
+ action: 'created',
373
+ written: undoKeys(productRecord(product, names))
374
+ });
375
+ continue;
376
+ }
377
+ if (plan.role === 'part') {
378
+ // Written with its product by the product's first row.
379
+ const snapshot = await ref.get();
380
+ if (!snapshot.exists) {
381
+ await done({
382
+ row: row.index,
383
+ outcome: 'failed',
384
+ reason: 'matchedRecordMissing',
385
+ message: `The product on row ${plan.leader + 1} was not created, so this row's variant was not either.`
386
+ });
387
+ } else {
388
+ await done({
389
+ row: row.index,
390
+ outcome: 'created',
391
+ recordId: plan.productId
392
+ });
393
+ }
394
+ continue;
395
+ }
396
+ // An existing product: this row's changes, read and written in one transaction.
397
+ const changes = productRowChanges(row);
398
+ const outcome = await firestore.runTransaction(async (tx)=>{
399
+ const snapshot = await tx.get(ref);
400
+ const data = snapshot.data();
401
+ if (!snapshot.exists || !isLive(data)) {
402
+ throw new RowRefusal('matchedRecordMissing', 'The product was deleted after the dry run.');
403
+ }
404
+ const current = liftLegacyProduct(data);
405
+ const next = applyProductRow(current, changes, categoryIdsOf);
406
+ const invalid = validateProduct(next);
407
+ if (invalid) throw new RowRefusal('refusedValue', invalid);
408
+ const patch = {};
409
+ for (const key of PRODUCT_DOC_KEYS){
410
+ const value = next[key];
411
+ const was = current[key];
412
+ if (JSON.stringify(value) === JSON.stringify(was)) continue;
413
+ patch[key] = value === undefined ? FieldValue.delete() : withoutUndefined(value);
414
+ }
415
+ Object.assign(patch, withoutUndefined(derivedKeys(next, smart)), {
416
+ updatedAtMs: Date.now(),
417
+ updatedAt: Timestamp.now()
418
+ });
419
+ tx.update(ref, patch);
420
+ return {
421
+ current,
422
+ next
423
+ };
424
+ });
425
+ const before = productRecord(outcome.current, names);
426
+ const afterRecord = productRecord(outcome.next, names);
427
+ const touched = [
428
+ ...Object.keys(changes.product).filter((fieldId)=>fieldId !== PRODUCT_FIELD.published),
429
+ ...changes.variant ? [
430
+ productVariantKey(changes.variant.id)
431
+ ] : []
432
+ ];
433
+ if (changes.variant) {
434
+ const was = outcome.current.variants.find((variant)=>{
435
+ var _changes_variant;
436
+ return variant.id === ((_changes_variant = changes.variant) == null ? void 0 : _changes_variant.id);
437
+ });
438
+ const now = outcome.next.variants.find((variant)=>{
439
+ var _changes_variant;
440
+ return variant.id === ((_changes_variant = changes.variant) == null ? void 0 : _changes_variant.id);
441
+ });
442
+ if (typeof (was == null ? void 0 : was.inventory) === 'number' && typeof (now == null ? void 0 : now.inventory) === 'number' && was.inventory !== now.inventory) {
443
+ adjustments.push({
444
+ productId: plan.productId,
445
+ variantId: now.id,
446
+ delta: now.inventory - was.inventory
447
+ });
448
+ }
449
+ }
450
+ if (!logged.has(plan.productId)) {
451
+ logged.add(plan.productId);
452
+ await logHostActivity(hostId, actor, 'Updated product from an import', {
453
+ type: 'content',
454
+ id: plan.productId,
455
+ name: outcome.next.name
456
+ });
457
+ }
458
+ await done({
459
+ row: row.index,
460
+ outcome: 'updated',
461
+ recordId: plan.productId
462
+ }, {
463
+ row: row.index,
464
+ recordId: plan.productId,
465
+ action: 'updated',
466
+ previous: undoKeys(before, touched),
467
+ written: undoKeys(afterRecord, touched)
468
+ });
469
+ } catch (error) {
470
+ if (!(error instanceof RowRefusal)) throw error;
471
+ await done({
472
+ row: row.index,
473
+ outcome: 'failed',
474
+ reason: error.reason,
475
+ message: error.message
476
+ });
477
+ }
478
+ }
479
+ // The stock history: what the import moved, as a correction (AGL-281).
480
+ const log = hostRefOf(firestore, ctx).collection('inventoryAdjustments');
481
+ for (const adjustment of adjustments){
482
+ await log.doc(Aglyn.createResourceUid()).set(_extends({}, adjustment, {
483
+ reason: 'correction',
484
+ source: 'import',
485
+ atMs: Date.now()
486
+ }));
487
+ }
488
+ return {
489
+ results,
490
+ undo
491
+ };
492
+ }
493
+ /*------------------------------------------
494
+ * Undo
495
+ *-----------------------------------------*/ /** Reverses one chunk: a created product deleted, an updated one's fields and variants put back. */ export async function revertProducts(ctx, snapshot, decisions, firestore = firestoreOf()) {
496
+ const products = productsOf(firestore, ctx);
497
+ const names = await readCategoryNames(firestore, ctx);
498
+ const categoryIdsOf = categoryIdsBy(names);
499
+ const smart = await readSmartCollections(firestore, ctx);
500
+ const { Timestamp, FieldValue } = firebaseAdmin.firestore;
501
+ return revertEntries(snapshot, decisions, {
502
+ async current (entry) {
503
+ const doc = await products.doc(entry.recordId).get();
504
+ return doc.exists && isLive(doc.data()) ? productRecord(liftLegacyProduct(doc.data()), names) : null;
505
+ },
506
+ async remove (entry) {
507
+ await products.doc(entry.recordId).delete();
508
+ },
509
+ async restore (entry, values) {
510
+ const ref = products.doc(entry.recordId);
511
+ await firestore.runTransaction(async (tx)=>{
512
+ const doc = await tx.get(ref);
513
+ if (!doc.exists) return;
514
+ let next = liftLegacyProduct(doc.data());
515
+ const productValues = {};
516
+ for (const [key, value] of Object.entries(values)){
517
+ if (!key.startsWith('$variant:')) {
518
+ productValues[key] = value;
519
+ continue;
520
+ }
521
+ const variantId = key.slice('$variant:'.length);
522
+ if (value === null) next = _extends({}, next, {
523
+ variants: next.variants.filter((variant)=>variant.id !== variantId)
524
+ });
525
+ else next = applyProductRow(next, {
526
+ variant: {
527
+ id: variantId,
528
+ isNew: false,
529
+ values: value
530
+ }
531
+ }, categoryIdsOf);
532
+ }
533
+ next = applyProductRow(next, {
534
+ product: productValues
535
+ }, categoryIdsOf);
536
+ const patch = {};
537
+ for (const key of PRODUCT_DOC_KEYS){
538
+ const value = next[key];
539
+ patch[key] = value === undefined ? FieldValue.delete() : withoutUndefined(value);
540
+ }
541
+ tx.update(ref, _extends({}, patch, withoutUndefined(derivedKeys(next, smart)), {
542
+ updatedAtMs: Date.now(),
543
+ updatedAt: Timestamp.now()
544
+ }));
545
+ });
546
+ }
547
+ });
548
+ }
549
+
550
+ //# sourceMappingURL=products.server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/transfer/products.server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport * as Aglyn from '@aglyn/aglyn/server'\nimport {\n buildMatchLookup,\n type BuildTransferPlanInput,\n type MatchKeySpec,\n type MatchLookupRequest,\n type TransferChunk,\n type TransferPlan,\n type TransferRowResult,\n type TransferUndoEntry,\n type TransferUndoSnapshot,\n} from '@aglyn/aglyn/data-transfer'\nimport type { PicklistValue } from '@aglyn/aglyn/app-utils/picklists'\nimport type {\n TransferApplyResult,\n TransferApplyWriter,\n TransferLookupResult,\n TransferPicklistList,\n TransferReadOptions,\n TransferReadPage,\n TransferResourceContext,\n TransferRevertDecisions,\n TransferRevertResult,\n} from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { firebaseAdmin, getOrgForHost, logHostActivity } from '@aglyn/tenant-data-admin'\nimport { PRODUCT_LIST_QUERY } from '../constants/product-list-query'\nimport {\n commerceSlug,\n liftLegacyProduct,\n productCollectionIds,\n productSearchFields,\n productStockFields,\n validateProduct,\n type HostProduct,\n type SmartCollectionRules,\n} from '../model/commerce'\nimport {\n PRODUCT_CATEGORY_PICKLIST,\n PRODUCT_FIELD,\n PRODUCT_FIXED_PICKLISTS,\n PRODUCT_LEVEL_FIELDS,\n PRODUCT_VARIANT_ORDER,\n applyProductRow,\n createProductDocument,\n planProductTransfer,\n productExportRowCount,\n productExportRows,\n productPlanHandles,\n productRecord,\n productRowChanges,\n productVariantKey,\n type ProductPlannedRow,\n} from './product-transfer'\nimport {\n NOTHING,\n ROW_BUDGET_MS,\n eachSourceDoc,\n firestoreOf,\n hostRefOf,\n readSourcePage,\n readerSeesHost,\n revertEntries,\n withoutUndefined,\n type CommerceListSource,\n type Firestore,\n} from './server-common'\n\n/*\n * PRODUCTS: THE SERVER HALF (AGL-3531). The pure half — the fields, the\n * Shopify layout, the plan that folds rows into products — is\n * `product-transfer.ts`; this is where it meets the store.\n *\n * Writes keep the product write path's rules: a create is counted against\n * the plan's product allowance in the same transaction that creates it (the\n * `api/hosts/resources` rule, AGL-2231), is born live (`deletedAt: null`)\n * with its creator and stamps, and every write carries the search, stock\n * and smart-collection keys every writer derives (AGL-3321) and leaves an\n * entry in the site's activity. A stock count a file changes is logged as a\n * correction, the same log a manual adjustment writes.\n */\n\ntype ProductDoc = Partial<HostProduct> & Record<string, unknown>\n\nconst ID_IN_MAX = 30\n\nfunction productsOf(firestore: Firestore, ctx: TransferResourceContext) {\n return hostRefOf(firestore, ctx).collection('products')\n}\n\nfunction productSource(firestore: Firestore, ctx: TransferResourceContext): CommerceListSource {\n return {\n collection: productsOf(firestore, ctx),\n declaration: PRODUCT_LIST_QUERY,\n base: [{ path: 'deletedAt', op: '==', value: null }],\n }\n}\n\nconst isLive = (data: FirebaseFirestore.DocumentData | undefined): boolean =>\n Boolean(data) && (data?.['deletedAt'] === null || data?.['deletedAt'] === undefined)\n\n/** The site's categories, id → name. */\nasync function readCategoryNames(firestore: Firestore, ctx: TransferResourceContext): Promise<Map<string, string>> {\n const snapshot = await hostRefOf(firestore, ctx).collection('productCategories').limit(2000).get()\n return new Map(snapshot.docs.map((doc) => [doc.id, String(doc.get('name') ?? doc.id)]))\n}\n\n/** Names → ids by the site's categories, in any case; a name it lacks is left out. */\nfunction categoryIdsBy(names: ReadonlyMap<string, string>) {\n const byName = new Map([...names].map(([id, name]) => [name.trim().toLowerCase(), id]))\n return (wanted: readonly string[]) => {\n const ids: string[] = []\n for (const name of wanted) {\n const id = byName.get(name.trim().toLowerCase()) ?? (names.has(name) ? name : undefined)\n if (id && !ids.includes(id)) ids.push(id)\n }\n return ids\n }\n}\n\n/** The smart collections a product's write answers (AGL-3321). */\nasync function readSmartCollections(firestore: Firestore, ctx: TransferResourceContext): Promise<SmartCollectionRules[]> {\n const snapshot = await hostRefOf(firestore, ctx)\n .collection('collections')\n .where('kind', '==', 'catalog')\n .where('mode', '==', 'smart')\n .get()\n return snapshot.docs.map((doc) => ({ id: doc.id, rules: doc.get('rules') ?? [], matchAll: doc.get('matchAll') }))\n}\n\n/** The keys every product write derives, from the product as it will stand. */\nfunction derivedKeys(product: HostProduct, smart: readonly SmartCollectionRules[]): Record<string, unknown> {\n return {\n ...productSearchFields(product),\n ...productStockFields(product),\n collectionIds: productCollectionIds(product, smart),\n priceUsd: product.variants[0]?.priceUsd ?? 0,\n imageUrl: product.mediaUrls?.[0] ?? null,\n }\n}\n\n/** The document fields a product write may set, the product resource's allow-list. */\nconst PRODUCT_DOC_KEYS = [\n 'name',\n 'description',\n 'type',\n 'status',\n 'tags',\n 'categoryIds',\n 'mediaUrls',\n 'options',\n 'variants',\n 'seo',\n 'taxExempt',\n 'oversellPolicy',\n 'lowStockThreshold',\n] as const\n\n/** The record keys an undo entry keeps: the product's writable fields and its variants. */\nfunction undoKeys(record: Readonly<Record<string, unknown>>, only?: readonly string[]): Record<string, unknown> {\n const keys =\n only ??\n [\n ...PRODUCT_LEVEL_FIELDS.filter((fieldId) => fieldId !== PRODUCT_FIELD.published),\n ...((record[PRODUCT_VARIANT_ORDER] as string[] | undefined) ?? []).map(productVariantKey),\n ]\n return Object.fromEntries(keys.map((key) => [key, record[key] ?? null]))\n}\n\n/*------------------------------------------\n * Export\n *-----------------------------------------*/\n\n/** The rows of one page of products. A product is as many rows as it has variants (or images). */\nexport async function readProductsPage(\n ctx: TransferResourceContext,\n cursor: string | null,\n fieldIds: readonly string[],\n options?: TransferReadOptions,\n firestore: Firestore = firestoreOf(),\n): Promise<TransferReadPage> {\n if (!readerSeesHost(ctx, options)) return NOTHING\n const pageSize = Math.max(1, Math.min(200, options?.pageSize ?? 200))\n const page = await readSourcePage(firestore, productSource(firestore, ctx), cursor, options, pageSize)\n const names = fieldIds.includes(PRODUCT_FIELD.categories) ? await readCategoryNames(firestore, ctx) : new Map()\n const rows = page.docs\n .filter((doc) => isLive(doc.data()))\n .flatMap((doc) => productExportRows(doc.id, liftLegacyProduct(doc.data() as ProductDoc), fieldIds, names))\n return { rows, next: page.next }\n}\n\n/** How many rows the export writes: every live product's variant (or image) rows. */\nexport async function countProductRows(\n ctx: TransferResourceContext,\n options: TransferReadOptions,\n firestore: Firestore = firestoreOf(),\n): Promise<number> {\n if (!readerSeesHost(ctx, options)) return 0\n let rows = 0\n await eachSourceDoc(firestore, productSource(firestore, ctx), options, (doc) => {\n const data = doc.data() as ProductDoc | undefined\n if (isLive(data)) rows += productExportRowCount(liftLegacyProduct(data as ProductDoc))\n })\n return rows\n}\n\n/*------------------------------------------\n * Matching\n *-----------------------------------------*/\n\n/** The values a product is found by: its handle, every SKU it carries, its id. */\nfunction matchValues(id: string, data: ProductDoc): Record<string, unknown> {\n const product = liftLegacyProduct(data)\n return {\n [PRODUCT_FIELD.handle]: product.slug,\n [PRODUCT_FIELD.sku]: product.variants.map((variant) => variant.sku).filter(Boolean),\n id,\n }\n}\n\n/** The live products holding each requested handle, SKU or id, and each one's record. */\nexport async function lookupProducts(\n ctx: TransferResourceContext,\n requests: readonly MatchLookupRequest[],\n firestore: Firestore = firestoreOf(),\n): Promise<TransferLookupResult> {\n const products = productsOf(firestore, ctx)\n const found = new Map<string, ProductDoc>()\n const keys: MatchKeySpec[] = []\n for (const request of requests) {\n keys.push({ fieldId: request.fieldId, normalizer: request.normalizer })\n for (let at = 0; at < request.values.length; at += ID_IN_MAX) {\n const slice = request.values.slice(at, at + ID_IN_MAX)\n if (request.fieldId === 'id') {\n const snapshots = await firestore.getAll(...slice.map((id) => products.doc(id)))\n for (const doc of snapshots) if (doc.exists && isLive(doc.data())) found.set(doc.id, doc.data() as ProductDoc)\n continue\n }\n const query =\n request.fieldId === PRODUCT_FIELD.handle\n ? products.where('slug', 'in', slice)\n : request.fieldId === PRODUCT_FIELD.sku\n ? products.where('skus', 'array-contains-any', slice)\n : null\n if (!query) continue\n for (const doc of (await query.get()).docs) if (isLive(doc.data())) found.set(doc.id, doc.data() as ProductDoc)\n }\n }\n const names = found.size ? await readCategoryNames(firestore, ctx) : new Map<string, string>()\n const records = new Map<string, Readonly<Record<string, unknown>>>()\n for (const [id, data] of found) records.set(id, productRecord(liftLegacyProduct(data), names))\n const lookup = buildMatchLookup(\n [...found].map(([id, data]) => ({ id, values: matchValues(id, data) })),\n keys,\n )\n return { lookup, records }\n}\n\n/*------------------------------------------\n * Lists\n *-----------------------------------------*/\n\n/** The fixed lists, and the site's categories as a list a file may add to. */\nexport async function productPicklists(\n ctx: TransferResourceContext,\n picklistIds: readonly string[],\n firestore: Firestore = firestoreOf(),\n): Promise<Record<string, TransferPicklistList>> {\n const out: Record<string, TransferPicklistList> = {}\n for (const picklistId of picklistIds) {\n const fixed = PRODUCT_FIXED_PICKLISTS[picklistId]\n if (fixed) {\n out[picklistId] = {\n spec: fixed,\n set: {\n values: fixed.standardValues.map((value) => ({ id: value.id, label: value.label, active: true })),\n defaultValueId: fixed.defaultValueId ?? null,\n },\n }\n } else if (picklistId === PRODUCT_CATEGORY_PICKLIST) {\n const names = await readCategoryNames(firestore, ctx)\n out[picklistId] = {\n spec: { restricted: false, standardValues: [] },\n set: {\n values: [...names].map(([id, label]) => ({ id, label, active: true })),\n defaultValueId: null,\n },\n }\n }\n }\n return out\n}\n\n/**\n * The categories the person chose to add, created before the first product\n * names them — each under the id the wizard minted, so a retry finds it.\n */\nexport async function addProductCategories(\n ctx: TransferResourceContext,\n picklistId: string,\n values: readonly PicklistValue[],\n firestore: Firestore = firestoreOf(),\n): Promise<void> {\n if (picklistId !== PRODUCT_CATEGORY_PICKLIST) return\n const categories = hostRefOf(firestore, ctx).collection('productCategories')\n const names = await readCategoryNames(firestore, ctx)\n const held = new Set([...names.values()].map((name) => name.trim().toLowerCase()))\n for (const value of values) {\n const name = value.label.trim()\n if (!name || held.has(name.toLowerCase())) continue\n held.add(name.toLowerCase())\n await categories.doc(value.id).set({ name, slug: commerceSlug(name) || value.id, parentId: null }, { merge: true })\n }\n}\n\n/*------------------------------------------\n * Plan\n *-----------------------------------------*/\n\nconst newId = () => Aglyn.createResourceUid()\nconst newVariantId = () => `v${Aglyn.createResourceUid().slice(0, 10)}`\n\n/**\n * The dry run: the slugs a new product would collide with (a deleted\n * product's included, which still holds its address) and the plan's room\n * for new products, then the fold of `planProductTransfer`.\n */\nexport async function planProducts(\n ctx: TransferResourceContext,\n input: BuildTransferPlanInput,\n firestore: Firestore = firestoreOf(),\n): Promise<TransferPlan> {\n const products = productsOf(firestore, ctx)\n const taken = new Set<string>()\n const candidates = productPlanHandles(input.rows).flatMap((base) => [base, ...[2, 3, 4, 5].map((n) => `${base}-${n}`)])\n for (let at = 0; at < candidates.length; at += ID_IN_MAX) {\n const slice = candidates.slice(at, at + ID_IN_MAX)\n for (const doc of (await products.where('slug', 'in', slice).get()).docs) taken.add(String(doc.get('slug')))\n }\n const owner = await getOrgForHost(ctx.hostId as string)\n const org = owner?.org ?? null\n const used = Number((await products.count().get()).data().count) || 0\n const maxCreates = Aglyn.checkEntitlement(org as never, 'commerce')\n ? Aglyn.checkQuota(org as never, 'productsPerHost', used).remaining\n : 0\n return planProductTransfer(input, {\n newProductId: newId,\n newVariantId,\n slugTaken: (slug) => taken.has(slug),\n maxCreates,\n })\n}\n\n/*------------------------------------------\n * Apply\n *-----------------------------------------*/\n\n/** A refusal the apply records for one row, rather than failing the chunk. */\nclass RowRefusal extends Error {\n constructor(\n readonly reason: string,\n message: string,\n ) {\n super(message)\n }\n}\n\n/** Writes one chunk of planned product rows through the product write path. */\nexport async function applyProducts(\n ctx: TransferResourceContext,\n chunk: TransferChunk<ProductPlannedRow>,\n writer: TransferApplyWriter,\n firestore: Firestore = firestoreOf(),\n): Promise<TransferApplyResult> {\n const results: TransferRowResult[] = []\n const undo: TransferUndoEntry[] = []\n const products = productsOf(firestore, ctx)\n const hostId = ctx.hostId as string\n const { Timestamp, FieldValue } = firebaseAdmin.firestore\n const names = await readCategoryNames(firestore, ctx)\n const categoryIdsOf = categoryIdsBy(names)\n const smart = await readSmartCollections(firestore, ctx)\n const owner = await getOrgForHost(hostId)\n const org = owner?.org ?? null\n const actor = { uid: ctx.actorUid ?? 'import', email: null }\n const logged = new Set<string>()\n const adjustments: Array<{ productId: string; variantId: string; delta: number }> = []\n\n const done = async (result: TransferRowResult, entry?: TransferUndoEntry) => {\n results.push(result)\n if (entry) undo.push(entry)\n await writer.markApplied(result, entry)\n }\n\n for (const row of chunk.rows) {\n const earlier = await writer.alreadyApplied(row.index)\n if (earlier) {\n results.push(earlier)\n continue\n }\n if (writer.timeLeftMs() < ROW_BUDGET_MS) break\n const plan = row.commerce\n if (!plan) {\n await done({ row: row.index, outcome: 'failed', message: 'The dry run carried nothing to write for this row.' })\n continue\n }\n const ref = products.doc(plan.productId)\n try {\n if (plan.role === 'create') {\n const product = createProductDocument(plan.create as NonNullable<typeof plan.create>, categoryIdsOf)\n const now = Date.now()\n const doc = withoutUndefined({\n ...product,\n ...derivedKeys(product, smart),\n createdAtMs: now,\n updatedAtMs: now,\n deletedAt: null,\n createdAt: Timestamp.now(),\n updatedAt: Timestamp.now(),\n createdBy: ctx.actorUid,\n })\n const created = await firestore.runTransaction(async (tx) => {\n const existing = await tx.get(ref)\n // A retry after the write landed and before its row was recorded.\n if (existing.exists) return false\n const used = (await tx.get(products.count())).data().count\n const quota = Aglyn.checkQuota(org as never, 'productsPerHost', Number(used) || 0)\n if (!quota.allowed) {\n throw new RowRefusal('planLimit', `Your plan includes ${quota.limit} products — upgrade in Billing for more.`)\n }\n tx.create(ref, doc)\n return true\n })\n if (created && !logged.has(plan.productId)) {\n logged.add(plan.productId)\n await logHostActivity(hostId, actor, 'Imported product', { type: 'content', id: plan.productId, name: product.name })\n }\n await done(\n { row: row.index, outcome: 'created', recordId: plan.productId },\n {\n row: row.index,\n recordId: plan.productId,\n action: 'created',\n written: undoKeys(productRecord(product, names)),\n },\n )\n continue\n }\n\n if (plan.role === 'part') {\n // Written with its product by the product's first row.\n const snapshot = await ref.get()\n if (!snapshot.exists) {\n await done({\n row: row.index,\n outcome: 'failed',\n reason: 'matchedRecordMissing',\n message: `The product on row ${plan.leader + 1} was not created, so this row's variant was not either.`,\n })\n } else {\n await done({ row: row.index, outcome: 'created', recordId: plan.productId })\n }\n continue\n }\n\n // An existing product: this row's changes, read and written in one transaction.\n const changes = productRowChanges(row)\n const outcome = await firestore.runTransaction(async (tx) => {\n const snapshot = await tx.get(ref)\n const data = snapshot.data() as ProductDoc | undefined\n if (!snapshot.exists || !isLive(data)) {\n throw new RowRefusal('matchedRecordMissing', 'The product was deleted after the dry run.')\n }\n const current = liftLegacyProduct(data as ProductDoc)\n const next = applyProductRow(current, changes, categoryIdsOf)\n const invalid = validateProduct(next)\n if (invalid) throw new RowRefusal('refusedValue', invalid)\n const patch: Record<string, unknown> = {}\n for (const key of PRODUCT_DOC_KEYS) {\n const value = (next as unknown as Record<string, unknown>)[key]\n const was = (current as unknown as Record<string, unknown>)[key]\n if (JSON.stringify(value) === JSON.stringify(was)) continue\n patch[key] = value === undefined ? FieldValue.delete() : withoutUndefined(value)\n }\n Object.assign(patch, withoutUndefined(derivedKeys(next, smart)), {\n updatedAtMs: Date.now(),\n updatedAt: Timestamp.now(),\n })\n tx.update(ref, patch)\n return { current, next }\n })\n const before = productRecord(outcome.current, names)\n const afterRecord = productRecord(outcome.next, names)\n const touched = [\n ...Object.keys(changes.product).filter((fieldId) => fieldId !== PRODUCT_FIELD.published),\n ...(changes.variant ? [productVariantKey(changes.variant.id)] : []),\n ]\n if (changes.variant) {\n const was = outcome.current.variants.find((variant) => variant.id === changes.variant?.id)\n const now = outcome.next.variants.find((variant) => variant.id === changes.variant?.id)\n if (typeof was?.inventory === 'number' && typeof now?.inventory === 'number' && was.inventory !== now.inventory) {\n adjustments.push({ productId: plan.productId, variantId: now.id, delta: now.inventory - was.inventory })\n }\n }\n if (!logged.has(plan.productId)) {\n logged.add(plan.productId)\n await logHostActivity(hostId, actor, 'Updated product from an import', {\n type: 'content',\n id: plan.productId,\n name: outcome.next.name,\n })\n }\n await done(\n { row: row.index, outcome: 'updated', recordId: plan.productId },\n {\n row: row.index,\n recordId: plan.productId,\n action: 'updated',\n previous: undoKeys(before, touched),\n written: undoKeys(afterRecord, touched),\n },\n )\n } catch (error) {\n if (!(error instanceof RowRefusal)) throw error\n await done({ row: row.index, outcome: 'failed', reason: error.reason, message: error.message })\n }\n }\n\n // The stock history: what the import moved, as a correction (AGL-281).\n const log = hostRefOf(firestore, ctx).collection('inventoryAdjustments')\n for (const adjustment of adjustments) {\n await log.doc(Aglyn.createResourceUid()).set({ ...adjustment, reason: 'correction', source: 'import', atMs: Date.now() })\n }\n return { results, undo }\n}\n\n/*------------------------------------------\n * Undo\n *-----------------------------------------*/\n\n/** Reverses one chunk: a created product deleted, an updated one's fields and variants put back. */\nexport async function revertProducts(\n ctx: TransferResourceContext,\n snapshot: TransferUndoSnapshot,\n decisions?: TransferRevertDecisions,\n firestore: Firestore = firestoreOf(),\n): Promise<TransferRevertResult> {\n const products = productsOf(firestore, ctx)\n const names = await readCategoryNames(firestore, ctx)\n const categoryIdsOf = categoryIdsBy(names)\n const smart = await readSmartCollections(firestore, ctx)\n const { Timestamp, FieldValue } = firebaseAdmin.firestore\n return revertEntries(snapshot, decisions, {\n async current(entry) {\n const doc = await products.doc(entry.recordId).get()\n return doc.exists && isLive(doc.data()) ? productRecord(liftLegacyProduct(doc.data() as ProductDoc), names) : null\n },\n async remove(entry) {\n await products.doc(entry.recordId).delete()\n },\n async restore(entry, values) {\n const ref = products.doc(entry.recordId)\n await firestore.runTransaction(async (tx) => {\n const doc = await tx.get(ref)\n if (!doc.exists) return\n let next = liftLegacyProduct(doc.data() as ProductDoc)\n const productValues: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(values)) {\n if (!key.startsWith('$variant:')) {\n productValues[key] = value\n continue\n }\n const variantId = key.slice('$variant:'.length)\n if (value === null) next = { ...next, variants: next.variants.filter((variant) => variant.id !== variantId) }\n else next = applyProductRow(next, { variant: { id: variantId, isNew: false, values: value as Record<string, unknown> } }, categoryIdsOf)\n }\n next = applyProductRow(next, { product: productValues }, categoryIdsOf)\n const patch: Record<string, unknown> = {}\n for (const key of PRODUCT_DOC_KEYS) {\n const value = (next as unknown as Record<string, unknown>)[key]\n patch[key] = value === undefined ? FieldValue.delete() : withoutUndefined(value)\n }\n tx.update(ref, {\n ...patch,\n ...withoutUndefined(derivedKeys(next, smart)),\n updatedAtMs: Date.now(),\n updatedAt: Timestamp.now(),\n })\n })\n },\n })\n}\n"],"names":["Aglyn","buildMatchLookup","firebaseAdmin","getOrgForHost","logHostActivity","PRODUCT_LIST_QUERY","commerceSlug","liftLegacyProduct","productCollectionIds","productSearchFields","productStockFields","validateProduct","PRODUCT_CATEGORY_PICKLIST","PRODUCT_FIELD","PRODUCT_FIXED_PICKLISTS","PRODUCT_LEVEL_FIELDS","PRODUCT_VARIANT_ORDER","applyProductRow","createProductDocument","planProductTransfer","productExportRowCount","productExportRows","productPlanHandles","productRecord","productRowChanges","productVariantKey","NOTHING","ROW_BUDGET_MS","eachSourceDoc","firestoreOf","hostRefOf","readSourcePage","readerSeesHost","revertEntries","withoutUndefined","ID_IN_MAX","productsOf","firestore","ctx","collection","productSource","declaration","base","path","op","value","isLive","data","Boolean","undefined","readCategoryNames","snapshot","limit","get","Map","docs","map","doc","id","String","categoryIdsBy","names","byName","name","trim","toLowerCase","wanted","ids","has","includes","push","readSmartCollections","where","rules","matchAll","derivedKeys","product","smart","collectionIds","priceUsd","variants","imageUrl","mediaUrls","PRODUCT_DOC_KEYS","undoKeys","record","only","keys","filter","fieldId","published","Object","fromEntries","key","readProductsPage","cursor","fieldIds","options","pageSize","Math","max","min","page","categories","rows","flatMap","next","countProductRows","matchValues","handle","slug","sku","variant","lookupProducts","requests","products","found","request","normalizer","at","values","length","slice","snapshots","getAll","exists","set","query","size","records","lookup","productPicklists","picklistIds","out","picklistId","fixed","spec","standardValues","label","active","defaultValueId","restricted","addProductCategories","held","Set","add","parentId","merge","newId","createResourceUid","newVariantId","planProducts","input","taken","candidates","n","owner","hostId","org","used","Number","count","maxCreates","checkEntitlement","checkQuota","remaining","newProductId","slugTaken","RowRefusal","Error","reason","message","applyProducts","chunk","writer","results","undo","Timestamp","FieldValue","categoryIdsOf","actor","uid","actorUid","email","logged","adjustments","done","result","entry","markApplied","row","earlier","alreadyApplied","index","timeLeftMs","plan","commerce","outcome","ref","productId","role","create","now","Date","createdAtMs","updatedAtMs","deletedAt","createdAt","updatedAt","createdBy","created","runTransaction","tx","existing","quota","allowed","type","recordId","action","written","leader","changes","current","invalid","patch","was","JSON","stringify","delete","assign","update","before","afterRecord","touched","find","inventory","variantId","delta","previous","error","log","adjustment","source","atMs","revertProducts","decisions","remove","restore","productValues","entries","startsWith","isNew"],"mappings":"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"}