@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.
- 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 +83 -56
- 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/pos-order.js +17 -9
- package/src/lib/server/pos-order.js.map +1 -1
- 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/testing/stand-in-record-system.d.ts +11 -2
- package/src/lib/testing/stand-in-record-system.js +5 -3
- package/src/lib/testing/stand-in-record-system.js.map +1 -1
- 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,296 @@
|
|
|
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 { planTransferUndo } from "@aglyn/aglyn/data-transfer";
|
|
18
|
+
import { firebaseAdmin, getOrgForHost, logHostActivity, resolveOrgMembership } from "@aglyn/tenant-data-admin";
|
|
19
|
+
import { TransferEngineError, TransferPlanRefusedError } from "@aglyn/tenant-data-admin/server/transfer-jobs";
|
|
20
|
+
import { hostRoleFor } from "@aglyn/aglyn/app-utils/organizations";
|
|
21
|
+
import { transferImportRoleAllowed, transferImportRoleRefusal } from "@aglyn/aglyn/data-transfer";
|
|
22
|
+
import { declaredTransferResource } from "@aglyn/aglyn/plugin-manager/plugin-transfer-resources";
|
|
23
|
+
import { COMMERCE_GIFT_CARDS_TRANSFER } from "./transfer-keys.js";
|
|
24
|
+
import { giftCardsPlanRefusal } from "./gift-cards-plan.js";
|
|
25
|
+
import { giftCardCodeOf, giftCardRedeemed } from "../model/commerce-gift-cards.js";
|
|
26
|
+
import { GIFT_CARD_ACTIVITY_TYPE, GiftCardIssueRefusal, giftCardActivityName, issueGiftCard, mintGiftCardCode } from "../server/gift-card-issue.js";
|
|
27
|
+
import { GIFT_CARD_MATCH_KEYS, GIFT_CARD_UNDO_FIELDS, giftCardRecord, planGiftCardRows, planWithCreateRequired, readGiftCardConfirmation } from "./records-transfer.js";
|
|
28
|
+
import { readableRecords } from "./records.server.js";
|
|
29
|
+
import { ROW_BUDGET_MS, firestoreOf, hostRefOf } from "./server-common.js";
|
|
30
|
+
/** A code that could be a card's: the document id `in` query refuses anything with a slash. */ const CARD_ID = /^[A-Z0-9][A-Z0-9_-]*$/;
|
|
31
|
+
const GIFT_CARDS = {
|
|
32
|
+
collection: (firestore, ctx)=>hostRefOf(firestore, ctx).collection('giftCards'),
|
|
33
|
+
record: (id, data)=>giftCardRecord(id, data),
|
|
34
|
+
matchValues: (id)=>({
|
|
35
|
+
id,
|
|
36
|
+
code: id
|
|
37
|
+
}),
|
|
38
|
+
// A card's code is its document id: a code is looked up by id. A value
|
|
39
|
+
// that cannot be a code still asks one id no card holds, rather than an
|
|
40
|
+
// empty `in`, which the store refuses.
|
|
41
|
+
find: {
|
|
42
|
+
code: (collection, values)=>{
|
|
43
|
+
const codes = values.map((value)=>giftCardCodeOf(value)).filter((code)=>CARD_ID.test(code));
|
|
44
|
+
return collection.where(firebaseAdmin.firestore.FieldPath.documentId(), 'in', codes.length ? codes : [
|
|
45
|
+
'-'
|
|
46
|
+
]);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
/** The member importing, checked for the role and the plan, or the refusal. */ async function issuerOf(ctx) {
|
|
51
|
+
var _ref;
|
|
52
|
+
var _membership_member;
|
|
53
|
+
const hostId = ctx.hostId;
|
|
54
|
+
if (!hostId) throw new TransferEngineError('invalid', 400, 'Gift cards belong to a site; name the site.');
|
|
55
|
+
const uid = ctx.actorUid;
|
|
56
|
+
const membership = uid ? await resolveOrgMembership(uid, ctx.orgId) : null;
|
|
57
|
+
// The declaration's `importRoles` (AGL-3554), which the transfer gate
|
|
58
|
+
// asked already; asked again here for the sweep that resumes an import.
|
|
59
|
+
const declared = declaredTransferResource(COMMERCE_GIFT_CARDS_TRANSFER);
|
|
60
|
+
const role = (membership == null ? void 0 : membership.member) ? hostRoleFor(membership.member, hostId) : null;
|
|
61
|
+
if (!uid || !declared || !transferImportRoleAllowed('import', declared, role)) {
|
|
62
|
+
throw new TransferEngineError('forbidden', 403, declared ? transferImportRoleRefusal(declared) : 'Gift cards can’t be imported here.');
|
|
63
|
+
}
|
|
64
|
+
const owner = await getOrgForHost(hostId).catch(()=>null);
|
|
65
|
+
// The transfer gate asked already (the resource's `planGate`); asked again
|
|
66
|
+
// here, in the same words, for the sweep that resumes an import.
|
|
67
|
+
const refusal = giftCardsPlanRefusal(owner == null ? void 0 : owner.org);
|
|
68
|
+
if (refusal) throw new TransferPlanRefusedError(refusal);
|
|
69
|
+
const email = (_ref = membership == null ? void 0 : (_membership_member = membership.member) == null ? void 0 : _membership_member.email) != null ? _ref : null;
|
|
70
|
+
return {
|
|
71
|
+
hostId,
|
|
72
|
+
uid,
|
|
73
|
+
email,
|
|
74
|
+
ownerOrg: owner == null ? void 0 : owner.org
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** The codes among `codes` that a card on the site already holds. */ async function takenCodes(firestore, ctx, codes) {
|
|
78
|
+
const collection = GIFT_CARDS.collection(firestore, ctx);
|
|
79
|
+
const taken = new Set();
|
|
80
|
+
const ids = [
|
|
81
|
+
...new Set(codes.filter((code)=>CARD_ID.test(code)))
|
|
82
|
+
];
|
|
83
|
+
for(let at = 0; at < ids.length; at += 100){
|
|
84
|
+
for (const doc of (await firestore.getAll(...ids.slice(at, at + 100).map((id)=>collection.doc(id))))){
|
|
85
|
+
if (doc.exists) taken.add(doc.id);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return taken;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The dry run: the core's plan with a Balance required of every card, made
|
|
92
|
+
* into the cards it issues. The codes the file names are read once, so a
|
|
93
|
+
* code another card holds — however the file spaced or cased it — is never
|
|
94
|
+
* planned as a new card.
|
|
95
|
+
*/ export async function planGiftCards(ctx, input) {
|
|
96
|
+
await issuerOf(ctx);
|
|
97
|
+
const firestore = firestoreOf();
|
|
98
|
+
const rows = input.rows.map((row)=>'code' in row.values ? _extends({}, row, {
|
|
99
|
+
values: _extends({}, row.values, {
|
|
100
|
+
code: giftCardCodeOf(row.values['code']) || null
|
|
101
|
+
})
|
|
102
|
+
}) : row);
|
|
103
|
+
const taken = await takenCodes(firestore, ctx, rows.map((row)=>{
|
|
104
|
+
var _row_values_code;
|
|
105
|
+
return String((_row_values_code = row.values['code']) != null ? _row_values_code : '');
|
|
106
|
+
}));
|
|
107
|
+
return planGiftCardRows(planWithCreateRequired(_extends({}, input, {
|
|
108
|
+
rows
|
|
109
|
+
}), [
|
|
110
|
+
'balance'
|
|
111
|
+
]), {
|
|
112
|
+
confirmation: readGiftCardConfirmation(ctx.extras),
|
|
113
|
+
mintCode: mintGiftCardCode,
|
|
114
|
+
taken
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
/** Whether the card under `code` is the one this job's `row` issued: a retried chunk finds its own card. */ async function issuedByRow(firestore, ctx, code, row) {
|
|
118
|
+
const doc = await GIFT_CARDS.collection(firestore, ctx).doc(code).get();
|
|
119
|
+
return doc.exists && doc.get('importJobId') === ctx.jobId && doc.get('importRow') === row;
|
|
120
|
+
}
|
|
121
|
+
/** Issues one chunk's cards, each through the Gift cards card's own issue path. */ export async function applyGiftCards(ctx, chunk, writer) {
|
|
122
|
+
const issuer = await issuerOf(ctx);
|
|
123
|
+
const confirmation = readGiftCardConfirmation(ctx.extras);
|
|
124
|
+
const firestore = firestoreOf();
|
|
125
|
+
const results = [];
|
|
126
|
+
const undo = [];
|
|
127
|
+
const refuse = async (row, message)=>{
|
|
128
|
+
const result = {
|
|
129
|
+
row,
|
|
130
|
+
outcome: 'failed',
|
|
131
|
+
reason: 'refusedValue',
|
|
132
|
+
message
|
|
133
|
+
};
|
|
134
|
+
results.push(result);
|
|
135
|
+
await writer.markApplied(result);
|
|
136
|
+
};
|
|
137
|
+
for (const row of chunk.rows){
|
|
138
|
+
var _issued_data;
|
|
139
|
+
const earlier = await writer.alreadyApplied(row.index);
|
|
140
|
+
if (earlier) {
|
|
141
|
+
results.push(earlier);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
if (writer.timeLeftMs() < ROW_BUDGET_MS) break;
|
|
145
|
+
const card = row.giftCard;
|
|
146
|
+
if (row.verdict !== 'create' || !card) {
|
|
147
|
+
await refuse(row.index, 'A gift card import only issues new cards; it never changes one.');
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (card.problem) {
|
|
151
|
+
await refuse(row.index, card.problem);
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
// The typed total, read again here: the plan's verdict is not enough.
|
|
155
|
+
if (!card.confirmed || !confirmation || confirmation.totalCents !== card.totalCents) {
|
|
156
|
+
await refuse(row.index, 'The total value was not confirmed, so no card was issued. Confirm the total and import again.');
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
var _ctx_jobId;
|
|
161
|
+
await issueGiftCard({
|
|
162
|
+
firestore,
|
|
163
|
+
hostId: issuer.hostId,
|
|
164
|
+
amountCents: card.amountCents,
|
|
165
|
+
recipientEmail: card.recipientEmail,
|
|
166
|
+
note: card.note,
|
|
167
|
+
code: card.code,
|
|
168
|
+
issuer: {
|
|
169
|
+
uid: issuer.uid,
|
|
170
|
+
email: issuer.email
|
|
171
|
+
},
|
|
172
|
+
email: confirmation.email,
|
|
173
|
+
ownerOrg: issuer.ownerOrg,
|
|
174
|
+
source: {
|
|
175
|
+
importJobId: (_ctx_jobId = ctx.jobId) != null ? _ctx_jobId : '',
|
|
176
|
+
importRow: row.index
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
if (!(error instanceof GiftCardIssueRefusal)) throw error;
|
|
181
|
+
// A retry after the card landed and before its row was recorded.
|
|
182
|
+
if (!(error.reason === 'taken' && await issuedByRow(firestore, ctx, card.code, row.index))) {
|
|
183
|
+
await refuse(row.index, error.message);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
const issued = await GIFT_CARDS.collection(firestore, ctx).doc(card.code).get();
|
|
188
|
+
const record = giftCardRecord(card.code, (_issued_data = issued.data()) != null ? _issued_data : {});
|
|
189
|
+
const result = {
|
|
190
|
+
row: row.index,
|
|
191
|
+
outcome: 'created',
|
|
192
|
+
recordId: card.code
|
|
193
|
+
};
|
|
194
|
+
const entry = {
|
|
195
|
+
row: row.index,
|
|
196
|
+
recordId: card.code,
|
|
197
|
+
action: 'created',
|
|
198
|
+
written: Object.fromEntries(GIFT_CARD_UNDO_FIELDS.map((fieldId)=>{
|
|
199
|
+
var _record_fieldId;
|
|
200
|
+
return [
|
|
201
|
+
fieldId,
|
|
202
|
+
(_record_fieldId = record[fieldId]) != null ? _record_fieldId : null
|
|
203
|
+
];
|
|
204
|
+
}))
|
|
205
|
+
};
|
|
206
|
+
results.push(result);
|
|
207
|
+
undo.push(entry);
|
|
208
|
+
await writer.markApplied(result, entry);
|
|
209
|
+
}
|
|
210
|
+
return {
|
|
211
|
+
results,
|
|
212
|
+
undo
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Undoes one chunk: each card voided — zeroed, never deleted — when nobody
|
|
217
|
+
* has spent from it; a spent or held card is a conflict that stays as it is,
|
|
218
|
+
* whatever the person decided, because a shopper holds that money.
|
|
219
|
+
*/ export async function revertGiftCards(ctx, snapshot) {
|
|
220
|
+
const issuer = await issuerOf(ctx);
|
|
221
|
+
const firestore = firestoreOf();
|
|
222
|
+
const collection = GIFT_CARDS.collection(firestore, ctx);
|
|
223
|
+
const done = [];
|
|
224
|
+
const conflicts = [];
|
|
225
|
+
for (const entry of [
|
|
226
|
+
...snapshot.entries
|
|
227
|
+
].reverse()){
|
|
228
|
+
const ref = collection.doc(entry.recordId);
|
|
229
|
+
const step = await firestore.runTransaction(async (tx)=>{
|
|
230
|
+
var _doc_data, _ctx_jobId;
|
|
231
|
+
const doc = await tx.get(ref);
|
|
232
|
+
if (!doc.exists) return {
|
|
233
|
+
action: 'nothing',
|
|
234
|
+
recordId: entry.recordId,
|
|
235
|
+
why: 'gone'
|
|
236
|
+
};
|
|
237
|
+
const card = (_doc_data = doc.data()) != null ? _doc_data : {};
|
|
238
|
+
if (Number(card.voidedAtMs) > 0) return {
|
|
239
|
+
action: 'nothing',
|
|
240
|
+
recordId: entry.recordId,
|
|
241
|
+
why: 'alreadyReverted'
|
|
242
|
+
};
|
|
243
|
+
if (giftCardRedeemed(card)) {
|
|
244
|
+
const planned = planTransferUndo(entry, giftCardRecord(entry.recordId, card));
|
|
245
|
+
return {
|
|
246
|
+
action: 'conflict',
|
|
247
|
+
recordId: entry.recordId,
|
|
248
|
+
fields: planned.action === 'conflict' ? planned.fields : [
|
|
249
|
+
'balance'
|
|
250
|
+
],
|
|
251
|
+
values: {}
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
tx.set(ref, {
|
|
255
|
+
balanceCents: 0,
|
|
256
|
+
voidedAtMs: Date.now(),
|
|
257
|
+
voidedBy: issuer.uid,
|
|
258
|
+
voidedByImportUndo: (_ctx_jobId = ctx.jobId) != null ? _ctx_jobId : null
|
|
259
|
+
}, {
|
|
260
|
+
merge: true
|
|
261
|
+
});
|
|
262
|
+
return {
|
|
263
|
+
action: 'delete',
|
|
264
|
+
recordId: entry.recordId
|
|
265
|
+
};
|
|
266
|
+
});
|
|
267
|
+
if (step.action === 'conflict') {
|
|
268
|
+
conflicts.push(step);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (step.action === 'delete') {
|
|
272
|
+
await logHostActivity(issuer.hostId, {
|
|
273
|
+
uid: issuer.uid,
|
|
274
|
+
email: issuer.email
|
|
275
|
+
}, 'Voided gift card: import undone', {
|
|
276
|
+
type: GIFT_CARD_ACTIVITY_TYPE,
|
|
277
|
+
id: entry.recordId,
|
|
278
|
+
name: giftCardActivityName(entry.recordId)
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
done.push(step);
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
done,
|
|
285
|
+
conflicts
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
export const giftCardsTransfer = _extends({
|
|
289
|
+
matchKeys: GIFT_CARD_MATCH_KEYS
|
|
290
|
+
}, readableRecords(GIFT_CARDS), {
|
|
291
|
+
plan: (ctx, input)=>planGiftCards(ctx, input),
|
|
292
|
+
apply: (ctx, chunk, writer)=>applyGiftCards(ctx, chunk, writer),
|
|
293
|
+
revert: (ctx, snapshot)=>revertGiftCards(ctx, snapshot)
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
//# sourceMappingURL=gift-cards.server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/transfer/gift-cards.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 {\n planTransferUndo,\n type BuildTransferPlanInput,\n type TransferChunk,\n type PlannedTransferRow,\n type TransferPlan,\n type TransferRowResult,\n type TransferUndoEntry,\n type TransferUndoSnapshot,\n type TransferUndoStep,\n} from '@aglyn/aglyn/data-transfer'\nimport type {\n PluginTransferResource,\n TransferApplyResult,\n TransferApplyWriter,\n TransferResourceContext,\n TransferRevertResult,\n} from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { firebaseAdmin, getOrgForHost, logHostActivity, resolveOrgMembership } from '@aglyn/tenant-data-admin'\nimport { TransferEngineError, TransferPlanRefusedError } from '@aglyn/tenant-data-admin/server/transfer-jobs'\nimport { hostRoleFor } from '@aglyn/aglyn/app-utils/organizations'\nimport { transferImportRoleAllowed, transferImportRoleRefusal } from '@aglyn/aglyn/data-transfer'\nimport { declaredTransferResource } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources'\nimport { COMMERCE_GIFT_CARDS_TRANSFER } from './transfer-keys'\nimport { giftCardsPlanRefusal } from './gift-cards-plan'\nimport { giftCardCodeOf, giftCardRedeemed } from '../model/commerce-gift-cards'\nimport {\n GIFT_CARD_ACTIVITY_TYPE,\n GiftCardIssueRefusal,\n giftCardActivityName,\n issueGiftCard,\n mintGiftCardCode,\n} from '../server/gift-card-issue'\nimport {\n GIFT_CARD_MATCH_KEYS,\n GIFT_CARD_UNDO_FIELDS,\n giftCardRecord,\n planGiftCardRows,\n planWithCreateRequired,\n readGiftCardConfirmation,\n type GiftCardPlannedRow,\n type StoredGiftCard,\n} from './records-transfer'\nimport { readableRecords, type RecordsSpec } from './records.server'\nimport { ROW_BUDGET_MS, firestoreOf, hostRefOf, type Firestore } from './server-common'\n\n/*\n * THE GIFT CARD IMPORT'S SERVER HALF (AGL-3551). A file issues cards; it\n * never writes a balance. The plan (`planGiftCardRows`) decides each card and\n * the total; this module makes the three promises the dry run states:\n *\n * - WHO: the workspace's owners and admins, and a site's admins (the\n * declaration's `importRoles: [\"admin\"]`, AGL-3554), on a plan that\n * includes gift cards. The plan,\n * the apply and the undo each ask again, server-side.\n * - HOW: each card through `issueGiftCard`, the Gift cards card's own issue\n * path — so its amount is bounded, its code is created and never\n * overwritten, its activity line is written — and only when the total the\n * person typed on the Confirm step is the total the plan counted.\n * - UNDO: a card nobody has spent from is voided (zeroed, never deleted,\n * AGL-1767). A card with a redemption, or a checkout holding part of it,\n * is reported as a conflict and left as it is, whatever the decision says:\n * that money has been spent, or is being spent, by a shopper.\n */\n\ntype Doc = Record<string, unknown>\n\n/** A code that could be a card's: the document id `in` query refuses anything with a slash. */\nconst CARD_ID = /^[A-Z0-9][A-Z0-9_-]*$/\n\nconst GIFT_CARDS: RecordsSpec = {\n collection: (firestore, ctx) => hostRefOf(firestore, ctx).collection('giftCards'),\n record: (id, data) => giftCardRecord(id, data as StoredGiftCard),\n matchValues: (id) => ({ id, code: id }),\n // A card's code is its document id: a code is looked up by id. A value\n // that cannot be a code still asks one id no card holds, rather than an\n // empty `in`, which the store refuses.\n find: {\n code: (collection, values) => {\n const codes = values.map((value) => giftCardCodeOf(value)).filter((code) => CARD_ID.test(code))\n return collection.where(firebaseAdmin.firestore.FieldPath.documentId(), 'in', codes.length ? codes : ['-'])\n },\n },\n}\n\n\ninterface GiftCardIssuer {\n hostId: string\n uid: string\n email: string | null\n ownerOrg: unknown\n}\n\n/** The member importing, checked for the role and the plan, or the refusal. */\nasync function issuerOf(ctx: TransferResourceContext): Promise<GiftCardIssuer> {\n const hostId = ctx.hostId\n if (!hostId) throw new TransferEngineError('invalid', 400, 'Gift cards belong to a site; name the site.')\n const uid = ctx.actorUid\n const membership = uid ? await resolveOrgMembership(uid, ctx.orgId) : null\n // The declaration's `importRoles` (AGL-3554), which the transfer gate\n // asked already; asked again here for the sweep that resumes an import.\n const declared = declaredTransferResource(COMMERCE_GIFT_CARDS_TRANSFER)\n const role = membership?.member ? hostRoleFor(membership.member, hostId as never) : null\n if (!uid || !declared || !transferImportRoleAllowed('import', declared, role)) {\n throw new TransferEngineError(\n 'forbidden',\n 403,\n declared ? transferImportRoleRefusal(declared) : 'Gift cards can’t be imported here.',\n )\n }\n const owner = await getOrgForHost(hostId).catch(() => null)\n // The transfer gate asked already (the resource's `planGate`); asked again\n // here, in the same words, for the sweep that resumes an import.\n const refusal = giftCardsPlanRefusal(owner?.org)\n if (refusal) throw new TransferPlanRefusedError(refusal)\n const email = (membership?.member as { email?: string } | undefined)?.email ?? null\n return { hostId, uid, email, ownerOrg: owner?.org }\n}\n\n/** The codes among `codes` that a card on the site already holds. */\nasync function takenCodes(firestore: Firestore, ctx: TransferResourceContext, codes: readonly string[]): Promise<Set<string>> {\n const collection = GIFT_CARDS.collection(firestore, ctx)\n const taken = new Set<string>()\n const ids = [...new Set(codes.filter((code) => CARD_ID.test(code)))]\n for (let at = 0; at < ids.length; at += 100) {\n for (const doc of await firestore.getAll(...ids.slice(at, at + 100).map((id) => collection.doc(id)))) {\n if (doc.exists) taken.add(doc.id)\n }\n }\n return taken\n}\n\n/**\n * The dry run: the core's plan with a Balance required of every card, made\n * into the cards it issues. The codes the file names are read once, so a\n * code another card holds — however the file spaced or cased it — is never\n * planned as a new card.\n */\nexport async function planGiftCards(ctx: TransferResourceContext, input: BuildTransferPlanInput): Promise<TransferPlan> {\n await issuerOf(ctx)\n const firestore = firestoreOf()\n const rows = input.rows.map((row) =>\n 'code' in row.values ? { ...row, values: { ...row.values, code: giftCardCodeOf(row.values['code']) || null } } : row,\n )\n const taken = await takenCodes(\n firestore,\n ctx,\n rows.map((row) => String(row.values['code'] ?? '')),\n )\n return planGiftCardRows(planWithCreateRequired({ ...input, rows }, ['balance']), {\n confirmation: readGiftCardConfirmation(ctx.extras),\n mintCode: mintGiftCardCode,\n taken,\n })\n}\n\n/** Whether the card under `code` is the one this job's `row` issued: a retried chunk finds its own card. */\nasync function issuedByRow(firestore: Firestore, ctx: TransferResourceContext, code: string, row: number): Promise<boolean> {\n const doc = await GIFT_CARDS.collection(firestore, ctx).doc(code).get()\n return doc.exists && doc.get('importJobId') === ctx.jobId && doc.get('importRow') === row\n}\n\n/** Issues one chunk's cards, each through the Gift cards card's own issue path. */\nexport async function applyGiftCards(\n ctx: TransferResourceContext,\n chunk: TransferChunk<PlannedTransferRow>,\n writer: TransferApplyWriter,\n): Promise<TransferApplyResult> {\n const issuer = await issuerOf(ctx)\n const confirmation = readGiftCardConfirmation(ctx.extras)\n const firestore = firestoreOf()\n const results: TransferRowResult[] = []\n const undo: TransferUndoEntry[] = []\n const refuse = async (row: number, message: string) => {\n const result: TransferRowResult = { row, outcome: 'failed', reason: 'refusedValue', message }\n results.push(result)\n await writer.markApplied(result)\n }\n for (const row of chunk.rows as GiftCardPlannedRow[]) {\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 card = row.giftCard\n if (row.verdict !== 'create' || !card) {\n await refuse(row.index, 'A gift card import only issues new cards; it never changes one.')\n continue\n }\n if (card.problem) {\n await refuse(row.index, card.problem)\n continue\n }\n // The typed total, read again here: the plan's verdict is not enough.\n if (!card.confirmed || !confirmation || confirmation.totalCents !== card.totalCents) {\n await refuse(row.index, 'The total value was not confirmed, so no card was issued. Confirm the total and import again.')\n continue\n }\n try {\n await issueGiftCard({\n firestore,\n hostId: issuer.hostId,\n amountCents: card.amountCents,\n recipientEmail: card.recipientEmail,\n note: card.note,\n code: card.code,\n issuer: { uid: issuer.uid, email: issuer.email },\n email: confirmation.email,\n ownerOrg: issuer.ownerOrg,\n source: { importJobId: ctx.jobId ?? '', importRow: row.index },\n })\n } catch (error) {\n if (!(error instanceof GiftCardIssueRefusal)) throw error\n // A retry after the card landed and before its row was recorded.\n if (!(error.reason === 'taken' && (await issuedByRow(firestore, ctx, card.code, row.index)))) {\n await refuse(row.index, error.message)\n continue\n }\n }\n const issued = await GIFT_CARDS.collection(firestore, ctx).doc(card.code).get()\n const record = giftCardRecord(card.code, (issued.data() ?? {}) as StoredGiftCard)\n const result: TransferRowResult = { row: row.index, outcome: 'created', recordId: card.code }\n const entry: TransferUndoEntry = {\n row: row.index,\n recordId: card.code,\n action: 'created',\n written: Object.fromEntries(GIFT_CARD_UNDO_FIELDS.map((fieldId) => [fieldId, record[fieldId] ?? null])),\n }\n results.push(result)\n undo.push(entry)\n await writer.markApplied(result, entry)\n }\n return { results, undo }\n}\n\n/**\n * Undoes one chunk: each card voided — zeroed, never deleted — when nobody\n * has spent from it; a spent or held card is a conflict that stays as it is,\n * whatever the person decided, because a shopper holds that money.\n */\nexport async function revertGiftCards(ctx: TransferResourceContext, snapshot: TransferUndoSnapshot): Promise<TransferRevertResult> {\n const issuer = await issuerOf(ctx)\n const firestore = firestoreOf()\n const collection = GIFT_CARDS.collection(firestore, ctx)\n const done: TransferUndoStep[] = []\n const conflicts: TransferUndoStep[] = []\n for (const entry of [...snapshot.entries].reverse()) {\n const ref = collection.doc(entry.recordId)\n const step = await firestore.runTransaction(async (tx): Promise<TransferUndoStep> => {\n const doc = await tx.get(ref)\n if (!doc.exists) return { action: 'nothing', recordId: entry.recordId, why: 'gone' }\n const card = (doc.data() ?? {}) as StoredGiftCard & Doc\n if (Number(card.voidedAtMs) > 0) return { action: 'nothing', recordId: entry.recordId, why: 'alreadyReverted' }\n if (giftCardRedeemed(card)) {\n const planned = planTransferUndo(entry, giftCardRecord(entry.recordId, card))\n return {\n action: 'conflict',\n recordId: entry.recordId,\n fields: planned.action === 'conflict' ? planned.fields : ['balance'],\n values: {},\n }\n }\n tx.set(\n ref,\n { balanceCents: 0, voidedAtMs: Date.now(), voidedBy: issuer.uid, voidedByImportUndo: ctx.jobId ?? null },\n { merge: true },\n )\n return { action: 'delete', recordId: entry.recordId }\n })\n if (step.action === 'conflict') {\n conflicts.push(step)\n continue\n }\n if (step.action === 'delete') {\n await logHostActivity(\n issuer.hostId,\n { uid: issuer.uid, email: issuer.email },\n 'Voided gift card: import undone',\n { type: GIFT_CARD_ACTIVITY_TYPE, id: entry.recordId, name: giftCardActivityName(entry.recordId) },\n )\n }\n done.push(step)\n }\n return { done, conflicts }\n}\n\nexport const giftCardsTransfer: PluginTransferResource = {\n matchKeys: GIFT_CARD_MATCH_KEYS,\n ...readableRecords(GIFT_CARDS),\n plan: (ctx, input) => planGiftCards(ctx, input),\n apply: (ctx, chunk, writer) => applyGiftCards(ctx, chunk, writer),\n revert: (ctx, snapshot) => revertGiftCards(ctx, snapshot),\n}\n"],"names":["planTransferUndo","firebaseAdmin","getOrgForHost","logHostActivity","resolveOrgMembership","TransferEngineError","TransferPlanRefusedError","hostRoleFor","transferImportRoleAllowed","transferImportRoleRefusal","declaredTransferResource","COMMERCE_GIFT_CARDS_TRANSFER","giftCardsPlanRefusal","giftCardCodeOf","giftCardRedeemed","GIFT_CARD_ACTIVITY_TYPE","GiftCardIssueRefusal","giftCardActivityName","issueGiftCard","mintGiftCardCode","GIFT_CARD_MATCH_KEYS","GIFT_CARD_UNDO_FIELDS","giftCardRecord","planGiftCardRows","planWithCreateRequired","readGiftCardConfirmation","readableRecords","ROW_BUDGET_MS","firestoreOf","hostRefOf","CARD_ID","GIFT_CARDS","collection","firestore","ctx","record","id","data","matchValues","code","find","values","codes","map","value","filter","test","where","FieldPath","documentId","length","issuerOf","membership","hostId","uid","actorUid","orgId","declared","role","member","owner","catch","refusal","org","email","ownerOrg","takenCodes","taken","Set","ids","at","doc","getAll","slice","exists","add","planGiftCards","input","rows","row","String","confirmation","extras","mintCode","issuedByRow","get","jobId","applyGiftCards","chunk","writer","issuer","results","undo","refuse","message","result","outcome","reason","push","markApplied","issued","earlier","alreadyApplied","index","timeLeftMs","card","giftCard","verdict","problem","confirmed","totalCents","amountCents","recipientEmail","note","source","importJobId","importRow","error","recordId","entry","action","written","Object","fromEntries","fieldId","revertGiftCards","snapshot","done","conflicts","entries","reverse","ref","step","runTransaction","tx","why","Number","voidedAtMs","planned","fields","set","balanceCents","Date","now","voidedBy","voidedByImportUndo","merge","type","name","giftCardsTransfer","matchKeys","plan","apply","revert"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { ListQueryDeclaration, ListQueryFilter, ListQueryPlan, ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan';
|
|
18
|
+
/** What a list sends as its export filter. */
|
|
19
|
+
export interface CommerceListFilter {
|
|
20
|
+
filters: ListQueryFilter[];
|
|
21
|
+
orderBy: ListQuerySort;
|
|
22
|
+
}
|
|
23
|
+
/** The filter a list hands the export dialog: its plan's predicates and order, as plain data. */
|
|
24
|
+
export declare function commerceListFilter(plan: Pick<ListQueryPlan, 'filters' | 'orderBy'>): CommerceListFilter;
|
|
25
|
+
/**
|
|
26
|
+
* The filter a request carried, checked against the list's declaration.
|
|
27
|
+
* Throws a sentence naming what was refused.
|
|
28
|
+
*/
|
|
29
|
+
export declare function readCommerceListFilter(value: unknown, declaration: ListQueryDeclaration, base?: readonly string[]): CommerceListFilter;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ const OPS = [
|
|
17
|
+
'==',
|
|
18
|
+
'!=',
|
|
19
|
+
'<',
|
|
20
|
+
'<=',
|
|
21
|
+
'>',
|
|
22
|
+
'>=',
|
|
23
|
+
'in',
|
|
24
|
+
'array-contains',
|
|
25
|
+
'array-contains-any'
|
|
26
|
+
];
|
|
27
|
+
/** The id path a plan names the document id by. */ const ID_PATH = '__name__';
|
|
28
|
+
/** The filter a list hands the export dialog: its plan's predicates and order, as plain data. */ export function commerceListFilter(plan) {
|
|
29
|
+
return {
|
|
30
|
+
filters: plan.filters.map((filter)=>({
|
|
31
|
+
path: filter.path,
|
|
32
|
+
op: filter.op,
|
|
33
|
+
value: filter.value instanceof Date ? filter.value.getTime() : Array.isArray(filter.value) ? [
|
|
34
|
+
...filter.value
|
|
35
|
+
] : filter.value
|
|
36
|
+
})),
|
|
37
|
+
orderBy: {
|
|
38
|
+
path: plan.orderBy.path,
|
|
39
|
+
direction: plan.orderBy.direction
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
/** Every path the declaration lets a query name. */ function allowedPaths(declaration, base) {
|
|
44
|
+
const paths = new Set([
|
|
45
|
+
ID_PATH,
|
|
46
|
+
...base
|
|
47
|
+
]);
|
|
48
|
+
for (const field of declaration.fields){
|
|
49
|
+
for (const path of [
|
|
50
|
+
field.path,
|
|
51
|
+
field.lowerPath,
|
|
52
|
+
field.tokensPath,
|
|
53
|
+
field.containsOrderBy
|
|
54
|
+
]){
|
|
55
|
+
if (path) paths.add(path);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (declaration.search) paths.add(declaration.search.tokensPath);
|
|
59
|
+
for (const sort of declaration.sorts)paths.add(sort.path);
|
|
60
|
+
return paths;
|
|
61
|
+
}
|
|
62
|
+
const plain = (value)=>value === null || [
|
|
63
|
+
'string',
|
|
64
|
+
'number',
|
|
65
|
+
'boolean'
|
|
66
|
+
].includes(typeof value);
|
|
67
|
+
/**
|
|
68
|
+
* The filter a request carried, checked against the list's declaration.
|
|
69
|
+
* Throws a sentence naming what was refused.
|
|
70
|
+
*/ export function readCommerceListFilter(value, declaration, base = []) {
|
|
71
|
+
var _raw_orderBy, _ref;
|
|
72
|
+
var _declaration_sorts_;
|
|
73
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
74
|
+
throw new Error('The filter could not be read.');
|
|
75
|
+
}
|
|
76
|
+
const raw = value;
|
|
77
|
+
const paths = allowedPaths(declaration, base);
|
|
78
|
+
const filters = [];
|
|
79
|
+
for (const entry of Array.isArray(raw.filters) ? raw.filters : []){
|
|
80
|
+
const { path, op, value: operand } = entry != null ? entry : {};
|
|
81
|
+
if (typeof path !== 'string' || !paths.has(path)) {
|
|
82
|
+
throw new Error(`The filter names "${String(path)}", which this list does not filter by.`);
|
|
83
|
+
}
|
|
84
|
+
if (!OPS.includes(op)) throw new Error(`The filter uses an unknown operator.`);
|
|
85
|
+
const list = op === 'in' || op === 'array-contains-any';
|
|
86
|
+
if (list) {
|
|
87
|
+
if (!Array.isArray(operand) || !operand.length || operand.length > 30 || !operand.every(plain)) {
|
|
88
|
+
throw new Error('The filter holds a list it cannot ask for.');
|
|
89
|
+
}
|
|
90
|
+
} else if (!plain(operand)) {
|
|
91
|
+
throw new Error('The filter holds a value it cannot ask for.');
|
|
92
|
+
}
|
|
93
|
+
filters.push({
|
|
94
|
+
path,
|
|
95
|
+
op: op,
|
|
96
|
+
value: operand
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
const order = (_raw_orderBy = raw.orderBy) != null ? _raw_orderBy : {};
|
|
100
|
+
const direction = order.direction === 'desc' ? 'desc' : 'asc';
|
|
101
|
+
const orderPath = typeof order.path === 'string' ? order.path : (_ref = (_declaration_sorts_ = declaration.sorts[0]) == null ? void 0 : _declaration_sorts_.path) != null ? _ref : ID_PATH;
|
|
102
|
+
if (!paths.has(orderPath)) throw new Error(`The filter orders by "${orderPath}", which this list does not sort by.`);
|
|
103
|
+
return {
|
|
104
|
+
filters,
|
|
105
|
+
orderBy: {
|
|
106
|
+
path: orderPath,
|
|
107
|
+
direction
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
//# sourceMappingURL=list-filter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/transfer/list-filter.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 type {\n ListQueryDeclaration,\n ListQueryFilter,\n ListQueryOp,\n ListQueryPlan,\n ListQuerySort,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\n\n/*\n * \"THE CURRENT FILTER\" OF A COMMERCE LIST, CARRIED TO AN EXPORT (AGL-3531).\n *\n * A list's filter is its Firestore query (AGL-3321): the predicates and the\n * one order `planListQuery` made of the chips and the search. The export\n * reads the same query on the server, so the list hands over exactly that —\n * the planned predicates and order — rather than the chips, whose dates the\n * browser resolved in the person's own timezone.\n *\n * The server reads through the Admin SDK, so it never takes a predicate on\n * faith: every path must be one the list's declaration names (a field's\n * path, lower-cased key or tokens, the search tokens, the list's base), the\n * operator one Firestore has, and the value plain data. Anything else is\n * refused, never widened to \"everything\".\n */\n\n/** What a list sends as its export filter. */\nexport interface CommerceListFilter {\n filters: ListQueryFilter[]\n orderBy: ListQuerySort\n}\n\nconst OPS: readonly ListQueryOp[] = [\n '==',\n '!=',\n '<',\n '<=',\n '>',\n '>=',\n 'in',\n 'array-contains',\n 'array-contains-any',\n]\n\n/** The id path a plan names the document id by. */\nconst ID_PATH = '__name__'\n\n/** The filter a list hands the export dialog: its plan's predicates and order, as plain data. */\nexport function commerceListFilter(plan: Pick<ListQueryPlan, 'filters' | 'orderBy'>): CommerceListFilter {\n return {\n filters: plan.filters.map((filter) => ({\n path: filter.path,\n op: filter.op,\n value:\n filter.value instanceof Date\n ? filter.value.getTime()\n : Array.isArray(filter.value)\n ? [...filter.value]\n : filter.value,\n })),\n orderBy: { path: plan.orderBy.path, direction: plan.orderBy.direction },\n }\n}\n\n/** Every path the declaration lets a query name. */\nfunction allowedPaths(declaration: ListQueryDeclaration, base: readonly string[]): Set<string> {\n const paths = new Set<string>([ID_PATH, ...base])\n for (const field of declaration.fields) {\n for (const path of [field.path, field.lowerPath, field.tokensPath, field.containsOrderBy]) {\n if (path) paths.add(path)\n }\n }\n if (declaration.search) paths.add(declaration.search.tokensPath)\n for (const sort of declaration.sorts) paths.add(sort.path)\n return paths\n}\n\nconst plain = (value: unknown): boolean =>\n value === null || ['string', 'number', 'boolean'].includes(typeof value)\n\n/**\n * The filter a request carried, checked against the list's declaration.\n * Throws a sentence naming what was refused.\n */\nexport function readCommerceListFilter(\n value: unknown,\n declaration: ListQueryDeclaration,\n base: readonly string[] = [],\n): CommerceListFilter {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n throw new Error('The filter could not be read.')\n }\n const raw = value as { filters?: unknown; orderBy?: unknown }\n const paths = allowedPaths(declaration, base)\n const filters: ListQueryFilter[] = []\n for (const entry of Array.isArray(raw.filters) ? raw.filters : []) {\n const { path, op, value: operand } = (entry ?? {}) as Record<string, unknown>\n if (typeof path !== 'string' || !paths.has(path)) {\n throw new Error(`The filter names \"${String(path)}\", which this list does not filter by.`)\n }\n if (!OPS.includes(op as ListQueryOp)) throw new Error(`The filter uses an unknown operator.`)\n const list = op === 'in' || op === 'array-contains-any'\n if (list) {\n if (!Array.isArray(operand) || !operand.length || operand.length > 30 || !operand.every(plain)) {\n throw new Error('The filter holds a list it cannot ask for.')\n }\n } else if (!plain(operand)) {\n throw new Error('The filter holds a value it cannot ask for.')\n }\n filters.push({ path, op: op as ListQueryOp, value: operand as ListQueryFilter['value'] })\n }\n const order = (raw.orderBy ?? {}) as Record<string, unknown>\n const direction = order.direction === 'desc' ? 'desc' : 'asc'\n const orderPath = typeof order.path === 'string' ? order.path : (declaration.sorts[0]?.path ?? ID_PATH)\n if (!paths.has(orderPath)) throw new Error(`The filter orders by \"${orderPath}\", which this list does not sort by.`)\n return { filters, orderBy: { path: orderPath, direction } }\n}\n"],"names":["OPS","ID_PATH","commerceListFilter","plan","filters","map","filter","path","op","value","Date","getTime","Array","isArray","orderBy","direction","allowedPaths","declaration","base","paths","Set","field","fields","lowerPath","tokensPath","containsOrderBy","add","search","sort","sorts","plain","includes","readCommerceListFilter","raw","Error","entry","operand","has","String","list","length","every","push","order","orderPath"],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import { type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token';
|
|
18
|
+
export interface PendingProductImport {
|
|
19
|
+
orgId: string;
|
|
20
|
+
jobId: string;
|
|
21
|
+
/** The options the `productImport` zone set, by key. */
|
|
22
|
+
options: Record<string, boolean>;
|
|
23
|
+
}
|
|
24
|
+
/** Files the import a site's wizard is running, with its options. */
|
|
25
|
+
export declare function rememberProductImport(hostId: string, entry: PendingProductImport): void;
|
|
26
|
+
/** The import the site's wizard last ran, once: the hub takes it when the wizard closes. */
|
|
27
|
+
export declare function takeProductImport(hostId: string): PendingProductImport | null;
|
|
28
|
+
/** The products an applied import created, from the job's results (`api/transfer/status`). */
|
|
29
|
+
export declare function createdProductIds(user: MaybeTokenSource, entry: PendingProductImport): Promise<string[]>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ import { authorizedFetch } from "@aglyn/shared-util-http/authorized-token";
|
|
17
|
+
const pending = new Map();
|
|
18
|
+
/** Files the import a site's wizard is running, with its options. */ export function rememberProductImport(hostId, entry) {
|
|
19
|
+
pending.set(hostId, entry);
|
|
20
|
+
}
|
|
21
|
+
/** The import the site's wizard last ran, once: the hub takes it when the wizard closes. */ export function takeProductImport(hostId) {
|
|
22
|
+
var _pending_get;
|
|
23
|
+
const entry = (_pending_get = pending.get(hostId)) != null ? _pending_get : null;
|
|
24
|
+
pending.delete(hostId);
|
|
25
|
+
return entry;
|
|
26
|
+
}
|
|
27
|
+
/** The products an applied import created, from the job's results (`api/transfer/status`). */ export async function createdProductIds(user, entry) {
|
|
28
|
+
var _ref;
|
|
29
|
+
const response = await authorizedFetch(user, '/api/transfer/status', {
|
|
30
|
+
method: 'POST',
|
|
31
|
+
headers: {
|
|
32
|
+
'Content-Type': 'application/json'
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({
|
|
35
|
+
orgId: entry.orgId,
|
|
36
|
+
jobId: entry.jobId,
|
|
37
|
+
include: 'results'
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
if (!response.ok) return [];
|
|
41
|
+
const status = await response.json().catch(()=>null);
|
|
42
|
+
const ids = [];
|
|
43
|
+
for (const row of (_ref = status == null ? void 0 : status.rows) != null ? _ref : []){
|
|
44
|
+
if (row.outcome === 'created' && row.recordId && !ids.includes(row.recordId)) ids.push(row.recordId);
|
|
45
|
+
}
|
|
46
|
+
return ids;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//# sourceMappingURL=product-import-results.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/commerce/src/lib/transfer/product-import-results.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 { authorizedFetch, type MaybeTokenSource } from '@aglyn/shared-util-http/authorized-token'\nimport type { TransferStatusResponse } from '@aglyn/aglyn/data-transfer'\n\n/*\n * WHAT A PRODUCT IMPORT LEFT FOR THE PRODUCTS HUB (AGL-3531).\n *\n * The `productImport` zone's options are set in the wizard's own step\n * (`ProductImportOptionsStep`) and the hub hands them to the `productsHub`\n * zone with the products the import CREATED — the AI copy option starts a\n * copy job for exactly those. The step files the job here; the hub takes it\n * when the wizard closes on its results and reads the created ids from the\n * job's results. Module state is enough: the step and the hub share one tab.\n */\n\nexport interface PendingProductImport {\n orgId: string\n jobId: string\n /** The options the `productImport` zone set, by key. */\n options: Record<string, boolean>\n}\n\nconst pending = new Map<string, PendingProductImport>()\n\n/** Files the import a site's wizard is running, with its options. */\nexport function rememberProductImport(hostId: string, entry: PendingProductImport): void {\n pending.set(hostId, entry)\n}\n\n/** The import the site's wizard last ran, once: the hub takes it when the wizard closes. */\nexport function takeProductImport(hostId: string): PendingProductImport | null {\n const entry = pending.get(hostId) ?? null\n pending.delete(hostId)\n return entry\n}\n\n/** The products an applied import created, from the job's results (`api/transfer/status`). */\nexport async function createdProductIds(user: MaybeTokenSource, entry: PendingProductImport): Promise<string[]> {\n const response = await authorizedFetch(user, '/api/transfer/status', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId: entry.orgId, jobId: entry.jobId, include: 'results' }),\n })\n if (!response.ok) return []\n const status = (await response.json().catch(() => null)) as TransferStatusResponse | null\n const ids: string[] = []\n for (const row of status?.rows ?? []) {\n if (row.outcome === 'created' && row.recordId && !ids.includes(row.recordId)) ids.push(row.recordId)\n }\n return ids\n}\n"],"names":["authorizedFetch","pending","Map","rememberProductImport","hostId","entry","set","takeProductImport","get","delete","createdProductIds","user","response","method","headers","body","JSON","stringify","orgId","jobId","include","ok","status","json","catch","ids","row","rows","outcome","recordId","includes","push"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,SAASA,eAAe,QAA+B,2CAA0C;AAqBjG,MAAMC,UAAU,IAAIC;AAEpB,mEAAmE,GACnE,OAAO,SAASC,sBAAsBC,MAAc,EAAEC,KAA2B;IAC/EJ,QAAQK,GAAG,CAACF,QAAQC;AACtB;AAEA,0FAA0F,GAC1F,OAAO,SAASE,kBAAkBH,MAAc;QAChCH;IAAd,MAAMI,SAAQJ,eAAAA,QAAQO,GAAG,CAACJ,mBAAZH,eAAuB;IACrCA,QAAQQ,MAAM,CAACL;IACf,OAAOC;AACT;AAEA,4FAA4F,GAC5F,OAAO,eAAeK,kBAAkBC,IAAsB,EAAEN,KAA2B;;IACzF,MAAMO,WAAW,MAAMZ,gBAAgBW,MAAM,wBAAwB;QACnEE,QAAQ;QACRC,SAAS;YAAE,gBAAgB;QAAmB;QAC9CC,MAAMC,KAAKC,SAAS,CAAC;YAAEC,OAAOb,MAAMa,KAAK;YAAEC,OAAOd,MAAMc,KAAK;YAAEC,SAAS;QAAU;IACpF;IACA,IAAI,CAACR,SAASS,EAAE,EAAE,OAAO,EAAE;IAC3B,MAAMC,SAAU,MAAMV,SAASW,IAAI,GAAGC,KAAK,CAAC,IAAM;IAClD,MAAMC,MAAgB,EAAE;IACxB,KAAK,MAAMC,eAAOJ,0BAAAA,OAAQK,IAAI,mBAAI,EAAE,CAAE;QACpC,IAAID,IAAIE,OAAO,KAAK,aAAaF,IAAIG,QAAQ,IAAI,CAACJ,IAAIK,QAAQ,CAACJ,IAAIG,QAAQ,GAAGJ,IAAIM,IAAI,CAACL,IAAIG,QAAQ;IACrG;IACA,OAAOJ;AACT"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { TransferWizardStepProps } from '@aglyn/aglyn/plugin-manager/plugin-transfer-resources';
|
|
18
|
+
/**
|
|
19
|
+
* The product import wizard's After import step (AGL-3531): the
|
|
20
|
+
* `productImport` zone, where another plugin offers what happens to the
|
|
21
|
+
* products once they land — the AI plugin's "write descriptions as they
|
|
22
|
+
* land". The options are filed with the job for the products hub, which
|
|
23
|
+
* hands them to `productsHub` with the ids the import created. Nothing here
|
|
24
|
+
* blocks the import: with no widget the step says so and lets it go on.
|
|
25
|
+
*/
|
|
26
|
+
export declare function ProductImportOptionsStep(props: TransferWizardStepProps): import("react").JSX.Element;
|
|
27
|
+
export declare namespace ProductImportOptionsStep {
|
|
28
|
+
var displayName: string;
|
|
29
|
+
}
|
|
30
|
+
export default ProductImportOptionsStep;
|