@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,1153 @@
|
|
|
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 { buildTransferPlan, isBlankTransferValue, TRANSFER_FILE_SAMPLE_ROW, TRANSFER_SYSTEM_GROUP } from "@aglyn/aglyn/data-transfer";
|
|
18
|
+
import { commerceSlug } from "../model/commerce.js";
|
|
19
|
+
import { GIFT_CARD_CONFIRM_STEP_ID } from "./transfer-keys.js";
|
|
20
|
+
import { GIFT_CARD_CURRENCY, giftCardAmountProblem, giftCardCodeOf, giftCardCodeProblem } from "../model/commerce-gift-cards.js";
|
|
21
|
+
import { ORDER_CHANNEL_LABELS, ORDER_STATUS_LABELS, liftLegacyOrder } from "../model/commerce-orders.js";
|
|
22
|
+
/*
|
|
23
|
+
* ORDERS, DISCOUNTS, COUPONS, GIFT CARDS AND CATEGORIES AS FILES (AGL-3531):
|
|
24
|
+
* each resource's fields, and its stored document as the record a file
|
|
25
|
+
* names. Pure — the server half is `records.server.ts`.
|
|
26
|
+
*
|
|
27
|
+
* ## What imports, and why the rest does not
|
|
28
|
+
*
|
|
29
|
+
* - Categories, discounts and coupons import: each is a document a person
|
|
30
|
+
* writes from the console with nothing derived behind it, so a file
|
|
31
|
+
* writes the same fields the console's card does. A discount or coupon
|
|
32
|
+
* created from a file starts SWITCHED OFF unless the file says Active —
|
|
33
|
+
* a promotion going live from a spreadsheet is money leaving the store.
|
|
34
|
+
* How many times one was used is history, exported and never written.
|
|
35
|
+
* - Orders are exported only: an order is the record of a sale, written by
|
|
36
|
+
* checkout, the register or a paid draft.
|
|
37
|
+
* - Gift cards import by ISSUING each card (AGL-3551, below), never by
|
|
38
|
+
* writing a balance: a balance is money a shopper can spend. A gift
|
|
39
|
+
* card's ID is its code, so the file is worth what the cards are.
|
|
40
|
+
*/ const iso = (ms)=>{
|
|
41
|
+
var _ref;
|
|
42
|
+
var _ms_toDate;
|
|
43
|
+
const time = typeof ms === 'number' ? ms : ms && typeof ms.toMillis === 'function' ? ms.toMillis() : ms && typeof ms.toDate === 'function' ? (_ref = (_ms_toDate = ms.toDate()) == null ? void 0 : _ms_toDate.getTime()) != null ? _ref : NaN : NaN;
|
|
44
|
+
return Number.isFinite(time) ? new Date(time).toISOString() : null;
|
|
45
|
+
};
|
|
46
|
+
const dollars = (cents)=>{
|
|
47
|
+
const value = Number(cents);
|
|
48
|
+
return cents === null || cents === undefined || !Number.isFinite(value) ? null : Math.round(value) / 100;
|
|
49
|
+
};
|
|
50
|
+
const text = (value)=>value === null || value === undefined ? '' : String(value).trim();
|
|
51
|
+
/** A planned row's changes, by field id. */ export function rowChanges(row) {
|
|
52
|
+
return Object.fromEntries(row.diff.map((change)=>[
|
|
53
|
+
change.fieldId,
|
|
54
|
+
change.after
|
|
55
|
+
]));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The core's plan with the fields a NEW record cannot be made without
|
|
59
|
+
* marked required — never in the catalog, where a required field must be
|
|
60
|
+
* mapped, and a file that only switches discounts off would be refused.
|
|
61
|
+
*/ export function planWithCreateRequired(input, required) {
|
|
62
|
+
return buildTransferPlan(_extends({}, input, {
|
|
63
|
+
fields: input.fields.map((field)=>required.includes(field.id) ? _extends({}, field, {
|
|
64
|
+
required: true
|
|
65
|
+
}) : field)
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
/*==========================================
|
|
69
|
+
* ORDERS
|
|
70
|
+
*=========================================*/ export const ORDER_TRANSFER_FIELDS = [
|
|
71
|
+
{
|
|
72
|
+
id: 'number',
|
|
73
|
+
label: 'Order number',
|
|
74
|
+
group: 'order',
|
|
75
|
+
type: 'integer',
|
|
76
|
+
readOnly: true
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
id: 'date',
|
|
80
|
+
label: 'Date',
|
|
81
|
+
group: 'order',
|
|
82
|
+
type: 'datetime',
|
|
83
|
+
readOnly: true
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'status',
|
|
87
|
+
label: 'Status',
|
|
88
|
+
group: 'order',
|
|
89
|
+
type: 'text',
|
|
90
|
+
readOnly: true
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'channel',
|
|
94
|
+
label: 'Channel',
|
|
95
|
+
group: 'order',
|
|
96
|
+
type: 'text',
|
|
97
|
+
readOnly: true
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
id: 'customerEmail',
|
|
101
|
+
label: 'Customer email',
|
|
102
|
+
group: 'customer',
|
|
103
|
+
type: 'email',
|
|
104
|
+
readOnly: true
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
id: 'customerName',
|
|
108
|
+
label: 'Customer name',
|
|
109
|
+
group: 'customer',
|
|
110
|
+
type: 'text',
|
|
111
|
+
readOnly: true
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 'product',
|
|
115
|
+
label: 'Product',
|
|
116
|
+
group: 'items',
|
|
117
|
+
type: 'text',
|
|
118
|
+
readOnly: true,
|
|
119
|
+
description: 'The first line’s product, and how many more the order holds.'
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'items',
|
|
123
|
+
label: 'Items',
|
|
124
|
+
group: 'items',
|
|
125
|
+
type: 'longText',
|
|
126
|
+
readOnly: true,
|
|
127
|
+
description: 'Every line: quantity × name (variant), SKU.'
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
id: 'itemCount',
|
|
131
|
+
label: 'Units',
|
|
132
|
+
group: 'items',
|
|
133
|
+
type: 'integer',
|
|
134
|
+
readOnly: true
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
id: 'subtotal',
|
|
138
|
+
label: 'Items subtotal',
|
|
139
|
+
group: 'money',
|
|
140
|
+
type: 'number',
|
|
141
|
+
readOnly: true
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
id: 'shipping',
|
|
145
|
+
label: 'Shipping',
|
|
146
|
+
group: 'money',
|
|
147
|
+
type: 'number',
|
|
148
|
+
readOnly: true
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
id: 'tax',
|
|
152
|
+
label: 'Tax',
|
|
153
|
+
group: 'money',
|
|
154
|
+
type: 'number',
|
|
155
|
+
readOnly: true
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
id: 'discount',
|
|
159
|
+
label: 'Discount',
|
|
160
|
+
group: 'money',
|
|
161
|
+
type: 'number',
|
|
162
|
+
readOnly: true
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: 'total',
|
|
166
|
+
label: 'Total',
|
|
167
|
+
group: 'money',
|
|
168
|
+
type: 'number',
|
|
169
|
+
readOnly: true,
|
|
170
|
+
description: 'What was charged, in dollars.'
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
id: 'fee',
|
|
174
|
+
label: 'Fee',
|
|
175
|
+
group: 'money',
|
|
176
|
+
type: 'number',
|
|
177
|
+
readOnly: true
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
id: 'refunded',
|
|
181
|
+
label: 'Refunded',
|
|
182
|
+
group: 'money',
|
|
183
|
+
type: 'number',
|
|
184
|
+
readOnly: true
|
|
185
|
+
},
|
|
186
|
+
{
|
|
187
|
+
id: 'net',
|
|
188
|
+
label: 'Net',
|
|
189
|
+
group: 'money',
|
|
190
|
+
type: 'number',
|
|
191
|
+
readOnly: true,
|
|
192
|
+
description: 'The total less what was refunded.'
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
id: 'coupon',
|
|
196
|
+
label: 'Coupon',
|
|
197
|
+
group: 'money',
|
|
198
|
+
type: 'text',
|
|
199
|
+
readOnly: true
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
id: 'shippingAddress',
|
|
203
|
+
label: 'Shipping address',
|
|
204
|
+
group: 'customer',
|
|
205
|
+
type: 'text',
|
|
206
|
+
readOnly: true
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: 'billingAddress',
|
|
210
|
+
label: 'Billing address',
|
|
211
|
+
group: 'customer',
|
|
212
|
+
type: 'text',
|
|
213
|
+
readOnly: true
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
id: 'note',
|
|
217
|
+
label: 'Note',
|
|
218
|
+
group: 'order',
|
|
219
|
+
type: 'longText',
|
|
220
|
+
readOnly: true
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
id: 'dispute',
|
|
224
|
+
label: 'Dispute',
|
|
225
|
+
group: 'order',
|
|
226
|
+
type: 'text',
|
|
227
|
+
readOnly: true
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: 'paymentIntentId',
|
|
231
|
+
label: 'Payment ID',
|
|
232
|
+
group: TRANSFER_SYSTEM_GROUP.id,
|
|
233
|
+
type: 'text',
|
|
234
|
+
system: true
|
|
235
|
+
}
|
|
236
|
+
];
|
|
237
|
+
export const ORDER_TRANSFER_GROUPS = [
|
|
238
|
+
{
|
|
239
|
+
id: 'order',
|
|
240
|
+
label: 'Order'
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
id: 'customer',
|
|
244
|
+
label: 'Customer'
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 'items',
|
|
248
|
+
label: 'Items'
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
id: 'money',
|
|
252
|
+
label: 'Money'
|
|
253
|
+
}
|
|
254
|
+
];
|
|
255
|
+
const addressLine = (address)=>{
|
|
256
|
+
if (!address) return null;
|
|
257
|
+
const parts = [
|
|
258
|
+
address.name,
|
|
259
|
+
address.line1,
|
|
260
|
+
address.line2,
|
|
261
|
+
address.city,
|
|
262
|
+
address.state,
|
|
263
|
+
address.postalCode,
|
|
264
|
+
address.country
|
|
265
|
+
].map(text).filter(Boolean);
|
|
266
|
+
return parts.length ? parts.join(', ') : null;
|
|
267
|
+
};
|
|
268
|
+
/**
|
|
269
|
+
* One order as a file names it. The money is what was ACTUALLY charged:
|
|
270
|
+
* the order's `totals`, and the legacy flat fields only where a buy-now order
|
|
271
|
+
* of old carries nothing else (AGL-1747) — a POS or draft order has no flat
|
|
272
|
+
* field, and reading those first exported every one of them at $0.00. A
|
|
273
|
+
* legacy row is named from `productNames`, never as "Product".
|
|
274
|
+
*/ export function orderRecord(id, stored, productNames = {}) {
|
|
275
|
+
var _stored_lineItems, _ref, _ref1, _ref2, _stored_productId, _ref3, _ref4, _stored_refundedCents, _iso, _ORDER_STATUS_LABELS_lifted_status, _ref5, _ORDER_CHANNEL_LABELS_, _lifted_channel, _stored_customerEmail, _stored_customerName, _ref6, _ref7, _stored_couponCode, _stored_note, _stored_paymentIntentId;
|
|
276
|
+
var _lineItems_;
|
|
277
|
+
const lifted = liftLegacyOrder(stored);
|
|
278
|
+
const lineItems = (_stored_lineItems = stored.lineItems) != null ? _stored_lineItems : [];
|
|
279
|
+
const first = (_ref = (_ref1 = (_ref2 = (_lineItems_ = lineItems[0]) == null ? void 0 : _lineItems_.name) != null ? _ref2 : productNames[(_stored_productId = stored.productId) != null ? _stored_productId : '']) != null ? _ref1 : stored.productId) != null ? _ref : '';
|
|
280
|
+
const totals = lifted.totals;
|
|
281
|
+
const totalCents = Number((_ref3 = (_ref4 = totals == null ? void 0 : totals.totalCents) != null ? _ref4 : stored.amountCents) != null ? _ref3 : 0);
|
|
282
|
+
const refundedCents = Number((_stored_refundedCents = stored.refundedCents) != null ? _stored_refundedCents : 0);
|
|
283
|
+
return {
|
|
284
|
+
id,
|
|
285
|
+
number: typeof stored.number === 'number' ? stored.number : null,
|
|
286
|
+
date: (_iso = iso(stored.createdAt)) != null ? _iso : iso(stored.createdAtMs),
|
|
287
|
+
status: (_ORDER_STATUS_LABELS_lifted_status = ORDER_STATUS_LABELS[lifted.status]) != null ? _ORDER_STATUS_LABELS_lifted_status : lifted.status,
|
|
288
|
+
channel: (_ref5 = (_ORDER_CHANNEL_LABELS_ = ORDER_CHANNEL_LABELS[(_lifted_channel = lifted.channel) != null ? _lifted_channel : 'online']) != null ? _ORDER_CHANNEL_LABELS_ : lifted.channel) != null ? _ref5 : 'online',
|
|
289
|
+
customerEmail: (_stored_customerEmail = stored.customerEmail) != null ? _stored_customerEmail : null,
|
|
290
|
+
customerName: (_stored_customerName = stored.customerName) != null ? _stored_customerName : null,
|
|
291
|
+
product: lineItems.length > 1 ? `${first} +${lineItems.length - 1} more` : first || null,
|
|
292
|
+
items: lineItems.length ? lineItems.map((line)=>`${line.quantity} × ${line.name}${line.variantLabel ? ` (${line.variantLabel})` : ''}${line.sku ? `, ${line.sku}` : ''}`).join('; ') : null,
|
|
293
|
+
itemCount: lineItems.reduce((sum, line)=>sum + (Number(line.quantity) || 0), 0),
|
|
294
|
+
subtotal: dollars(totals == null ? void 0 : totals.itemsCents),
|
|
295
|
+
shipping: dollars(totals == null ? void 0 : totals.shippingCents),
|
|
296
|
+
tax: dollars(totals == null ? void 0 : totals.taxCents),
|
|
297
|
+
discount: dollars(totals == null ? void 0 : totals.discountCents),
|
|
298
|
+
total: dollars(totalCents),
|
|
299
|
+
fee: dollars((_ref6 = (_ref7 = totals == null ? void 0 : totals.feeCents) != null ? _ref7 : stored.feeCents) != null ? _ref6 : 0),
|
|
300
|
+
refunded: dollars(refundedCents),
|
|
301
|
+
net: dollars(totalCents - refundedCents),
|
|
302
|
+
coupon: (_stored_couponCode = stored.couponCode) != null ? _stored_couponCode : null,
|
|
303
|
+
shippingAddress: addressLine(stored.shippingAddress),
|
|
304
|
+
billingAddress: addressLine(stored.billingAddress),
|
|
305
|
+
note: (_stored_note = stored.note) != null ? _stored_note : null,
|
|
306
|
+
dispute: stored.dispute ? text(stored.dispute.status) || 'open' : null,
|
|
307
|
+
paymentIntentId: (_stored_paymentIntentId = stored.paymentIntentId) != null ? _stored_paymentIntentId : null
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
/*==========================================
|
|
311
|
+
* DISCOUNTS
|
|
312
|
+
*=========================================*/ export const DISCOUNT_KIND_PICKLIST = 'commerce.discountKind';
|
|
313
|
+
const DISCOUNT_KIND_LABELS = {
|
|
314
|
+
percent: 'Percent off',
|
|
315
|
+
fixed: 'Amount off',
|
|
316
|
+
free_shipping: 'Free shipping'
|
|
317
|
+
};
|
|
318
|
+
export const DISCOUNT_KIND_SPEC = {
|
|
319
|
+
restricted: true,
|
|
320
|
+
standardValues: Object.entries(DISCOUNT_KIND_LABELS).map(([id, label])=>({
|
|
321
|
+
id,
|
|
322
|
+
label
|
|
323
|
+
})),
|
|
324
|
+
defaultValueId: 'percent'
|
|
325
|
+
};
|
|
326
|
+
/** A discount kind's id from a label or id in any case, or `null`. */ export function discountKindOf(value) {
|
|
327
|
+
const key = text(value).toLowerCase();
|
|
328
|
+
const found = Object.entries(DISCOUNT_KIND_LABELS).find(([id, label])=>id === key || label.toLowerCase() === key);
|
|
329
|
+
return found ? found[0] : null;
|
|
330
|
+
}
|
|
331
|
+
export const DISCOUNT_TRANSFER_FIELDS = [
|
|
332
|
+
{
|
|
333
|
+
id: 'code',
|
|
334
|
+
label: 'Code',
|
|
335
|
+
group: 'discount',
|
|
336
|
+
type: 'text',
|
|
337
|
+
aliases: [
|
|
338
|
+
'discount code',
|
|
339
|
+
'promo code',
|
|
340
|
+
'coupon code'
|
|
341
|
+
],
|
|
342
|
+
description: 'What a shopper types; blank for a discount that applies on its own.'
|
|
343
|
+
},
|
|
344
|
+
{
|
|
345
|
+
id: 'name',
|
|
346
|
+
label: 'Name',
|
|
347
|
+
group: 'discount',
|
|
348
|
+
type: 'text',
|
|
349
|
+
aliases: [
|
|
350
|
+
'title',
|
|
351
|
+
'discount name'
|
|
352
|
+
]
|
|
353
|
+
},
|
|
354
|
+
{
|
|
355
|
+
id: 'kind',
|
|
356
|
+
label: 'Kind',
|
|
357
|
+
group: 'discount',
|
|
358
|
+
type: 'picklist',
|
|
359
|
+
picklistId: DISCOUNT_KIND_PICKLIST,
|
|
360
|
+
aliases: [
|
|
361
|
+
'type',
|
|
362
|
+
'discount type',
|
|
363
|
+
'value type'
|
|
364
|
+
],
|
|
365
|
+
description: 'Percent off, Amount off or Free shipping. Needed for a new discount.'
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: 'percentOff',
|
|
369
|
+
label: 'Percent off',
|
|
370
|
+
group: 'discount',
|
|
371
|
+
type: 'integer',
|
|
372
|
+
aliases: [
|
|
373
|
+
'percent',
|
|
374
|
+
'percentage'
|
|
375
|
+
]
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: 'amountOff',
|
|
379
|
+
label: 'Amount off',
|
|
380
|
+
group: 'discount',
|
|
381
|
+
type: 'currency',
|
|
382
|
+
aliases: [
|
|
383
|
+
'amount',
|
|
384
|
+
'value'
|
|
385
|
+
]
|
|
386
|
+
},
|
|
387
|
+
{
|
|
388
|
+
id: 'minimumSubtotal',
|
|
389
|
+
label: 'Minimum subtotal',
|
|
390
|
+
group: 'conditions',
|
|
391
|
+
type: 'currency',
|
|
392
|
+
aliases: [
|
|
393
|
+
'minimum',
|
|
394
|
+
'minimum purchase',
|
|
395
|
+
'minimum subtotal'
|
|
396
|
+
]
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
id: 'productIds',
|
|
400
|
+
label: 'Product IDs',
|
|
401
|
+
group: 'conditions',
|
|
402
|
+
type: 'text',
|
|
403
|
+
description: 'The products it covers, by their IDs separated by commas; blank covers the whole store.'
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: 'maxRedemptions',
|
|
407
|
+
label: 'Use limit',
|
|
408
|
+
group: 'conditions',
|
|
409
|
+
type: 'integer',
|
|
410
|
+
aliases: [
|
|
411
|
+
'usage limit',
|
|
412
|
+
'max uses'
|
|
413
|
+
]
|
|
414
|
+
},
|
|
415
|
+
{
|
|
416
|
+
id: 'startsAt',
|
|
417
|
+
label: 'Starts',
|
|
418
|
+
group: 'conditions',
|
|
419
|
+
type: 'datetime',
|
|
420
|
+
aliases: [
|
|
421
|
+
'start date',
|
|
422
|
+
'starts at'
|
|
423
|
+
]
|
|
424
|
+
},
|
|
425
|
+
{
|
|
426
|
+
id: 'endsAt',
|
|
427
|
+
label: 'Ends',
|
|
428
|
+
group: 'conditions',
|
|
429
|
+
type: 'datetime',
|
|
430
|
+
aliases: [
|
|
431
|
+
'end date',
|
|
432
|
+
'ends at',
|
|
433
|
+
'expires'
|
|
434
|
+
]
|
|
435
|
+
},
|
|
436
|
+
{
|
|
437
|
+
id: 'active',
|
|
438
|
+
label: 'Active',
|
|
439
|
+
group: 'discount',
|
|
440
|
+
type: 'boolean',
|
|
441
|
+
aliases: [
|
|
442
|
+
'enabled',
|
|
443
|
+
'status'
|
|
444
|
+
],
|
|
445
|
+
description: 'A new discount starts switched off unless the file says it is active.'
|
|
446
|
+
},
|
|
447
|
+
{
|
|
448
|
+
id: 'redemptions',
|
|
449
|
+
label: 'Times used',
|
|
450
|
+
group: 'conditions',
|
|
451
|
+
type: 'integer',
|
|
452
|
+
readOnly: true
|
|
453
|
+
}
|
|
454
|
+
];
|
|
455
|
+
export const DISCOUNT_MATCH_KEYS = [
|
|
456
|
+
{
|
|
457
|
+
fieldId: 'code',
|
|
458
|
+
normalizer: 'caseless'
|
|
459
|
+
},
|
|
460
|
+
{
|
|
461
|
+
fieldId: 'name',
|
|
462
|
+
normalizer: 'name'
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
fieldId: 'id',
|
|
466
|
+
normalizer: 'aglynId'
|
|
467
|
+
}
|
|
468
|
+
];
|
|
469
|
+
/** A code as the discounts card stores it: upper case, letters, digits, `_` and `-`. */ export function promotionCode(value, max = 40) {
|
|
470
|
+
return text(value).toUpperCase().replace(/[^A-Z0-9_-]/g, '').slice(0, max);
|
|
471
|
+
}
|
|
472
|
+
const cents = (value)=>{
|
|
473
|
+
if (value === null || value === undefined || value === '') return null;
|
|
474
|
+
if (typeof value === 'object' && 'amountMinor' in value) return Math.round(Number(value.amountMinor));
|
|
475
|
+
const number = Number(value);
|
|
476
|
+
return Number.isFinite(number) ? Math.round(number * 100) : null;
|
|
477
|
+
};
|
|
478
|
+
const msOf = (value)=>{
|
|
479
|
+
if (value === null || value === undefined || value === '') return null;
|
|
480
|
+
const time = typeof value === 'number' ? value : Date.parse(String(value));
|
|
481
|
+
return Number.isFinite(time) ? time : null;
|
|
482
|
+
};
|
|
483
|
+
export function discountRecord(id, stored) {
|
|
484
|
+
var _stored_name, _ref, _DISCOUNT_KIND_LABELS_stored_kind, _stored_valuePct, _stored_maxRedemptions, _stored_redemptions;
|
|
485
|
+
var _stored_productIds;
|
|
486
|
+
return {
|
|
487
|
+
id,
|
|
488
|
+
code: stored.code || null,
|
|
489
|
+
name: (_stored_name = stored.name) != null ? _stored_name : null,
|
|
490
|
+
kind: (_ref = (_DISCOUNT_KIND_LABELS_stored_kind = DISCOUNT_KIND_LABELS[stored.kind]) != null ? _DISCOUNT_KIND_LABELS_stored_kind : stored.kind) != null ? _ref : null,
|
|
491
|
+
percentOff: stored.kind === 'percent' ? (_stored_valuePct = stored.valuePct) != null ? _stored_valuePct : null : null,
|
|
492
|
+
amountOff: stored.kind === 'fixed' ? dollars(stored.valueCents) : null,
|
|
493
|
+
minimumSubtotal: dollars(stored.minSubtotalCents),
|
|
494
|
+
productIds: ((_stored_productIds = stored.productIds) == null ? void 0 : _stored_productIds.length) ? stored.productIds.join(', ') : null,
|
|
495
|
+
maxRedemptions: (_stored_maxRedemptions = stored.maxRedemptions) != null ? _stored_maxRedemptions : null,
|
|
496
|
+
startsAt: iso(stored.startAtMs),
|
|
497
|
+
endsAt: iso(stored.endAtMs),
|
|
498
|
+
active: stored.enabled !== false,
|
|
499
|
+
redemptions: (_stored_redemptions = stored.redemptions) != null ? _stored_redemptions : 0
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* The document a discount's changes make, over what it holds: the stored
|
|
504
|
+
* shape the discounts card writes, or the sentence refusing it.
|
|
505
|
+
*/ export function discountWrite(current, changes) {
|
|
506
|
+
const doc = current ? _extends({}, current) : {
|
|
507
|
+
enabled: false,
|
|
508
|
+
redemptions: 0
|
|
509
|
+
};
|
|
510
|
+
const has = (fieldId)=>fieldId in changes;
|
|
511
|
+
if (has('code')) doc['code'] = promotionCode(changes['code']) || null;
|
|
512
|
+
if (has('name')) doc['name'] = text(changes['name']).slice(0, 80) || null;
|
|
513
|
+
if (has('kind')) doc['kind'] = discountKindOf(changes['kind']);
|
|
514
|
+
if (has('percentOff')) doc['valuePct'] = changes['percentOff'] === null ? null : Math.round(Number(changes['percentOff']));
|
|
515
|
+
if (has('amountOff')) doc['valueCents'] = cents(changes['amountOff']);
|
|
516
|
+
if (has('minimumSubtotal')) doc['minSubtotalCents'] = cents(changes['minimumSubtotal']);
|
|
517
|
+
if (has('productIds')) {
|
|
518
|
+
const ids = text(changes['productIds']).split(/[,;|\s]+/).map(text).filter(Boolean);
|
|
519
|
+
doc['productIds'] = ids.length ? [
|
|
520
|
+
...new Set(ids)
|
|
521
|
+
] : null;
|
|
522
|
+
}
|
|
523
|
+
if (has('maxRedemptions')) {
|
|
524
|
+
const limit = Number(changes['maxRedemptions']);
|
|
525
|
+
doc['maxRedemptions'] = changes['maxRedemptions'] === null || !Number.isFinite(limit) ? null : Math.max(1, Math.round(limit));
|
|
526
|
+
}
|
|
527
|
+
if (has('startsAt')) doc['startAtMs'] = msOf(changes['startsAt']);
|
|
528
|
+
if (has('endsAt')) doc['endAtMs'] = msOf(changes['endsAt']);
|
|
529
|
+
if (has('active')) doc['enabled'] = changes['active'] === true;
|
|
530
|
+
if (!('code' in doc)) doc['code'] = null;
|
|
531
|
+
const kind = doc['kind'];
|
|
532
|
+
if (!kind) return {
|
|
533
|
+
problem: 'Say what kind of discount it is: Percent off, Amount off or Free shipping.'
|
|
534
|
+
};
|
|
535
|
+
if (kind === 'percent') {
|
|
536
|
+
const pct = Number(doc['valuePct']);
|
|
537
|
+
if (!(pct >= 1 && pct <= 100)) return {
|
|
538
|
+
problem: 'A percent-off discount takes 1 to 100 percent.'
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
if (kind === 'fixed' && !(Number(doc['valueCents']) > 0)) return {
|
|
542
|
+
problem: 'An amount-off discount needs an amount.'
|
|
543
|
+
};
|
|
544
|
+
if (!doc['code'] && !text(doc['name'])) return {
|
|
545
|
+
problem: 'A discount with no code needs a name shoppers see.'
|
|
546
|
+
};
|
|
547
|
+
const start = doc['startAtMs'];
|
|
548
|
+
const end = doc['endAtMs'];
|
|
549
|
+
if (start != null && end != null && end <= start) return {
|
|
550
|
+
problem: 'A discount ends after it starts.'
|
|
551
|
+
};
|
|
552
|
+
return {
|
|
553
|
+
doc
|
|
554
|
+
};
|
|
555
|
+
}
|
|
556
|
+
/*==========================================
|
|
557
|
+
* COUPONS
|
|
558
|
+
*=========================================*/ export const COUPON_TRANSFER_FIELDS = [
|
|
559
|
+
{
|
|
560
|
+
id: 'code',
|
|
561
|
+
label: 'Code',
|
|
562
|
+
group: 'coupon',
|
|
563
|
+
type: 'text',
|
|
564
|
+
matchKey: true,
|
|
565
|
+
aliases: [
|
|
566
|
+
'coupon code',
|
|
567
|
+
'promo code',
|
|
568
|
+
'discount code'
|
|
569
|
+
],
|
|
570
|
+
description: 'What a shopper types; a coupon’s code is its ID, so it is never changed.'
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
id: 'percentOff',
|
|
574
|
+
label: 'Percent off',
|
|
575
|
+
group: 'coupon',
|
|
576
|
+
type: 'integer',
|
|
577
|
+
aliases: [
|
|
578
|
+
'percent',
|
|
579
|
+
'percentage',
|
|
580
|
+
'value'
|
|
581
|
+
]
|
|
582
|
+
},
|
|
583
|
+
{
|
|
584
|
+
id: 'maxRedemptions',
|
|
585
|
+
label: 'Use limit',
|
|
586
|
+
group: 'coupon',
|
|
587
|
+
type: 'integer',
|
|
588
|
+
aliases: [
|
|
589
|
+
'usage limit',
|
|
590
|
+
'max uses'
|
|
591
|
+
]
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
id: 'expiresAt',
|
|
595
|
+
label: 'Expires',
|
|
596
|
+
group: 'coupon',
|
|
597
|
+
type: 'datetime',
|
|
598
|
+
aliases: [
|
|
599
|
+
'expiry',
|
|
600
|
+
'expires at',
|
|
601
|
+
'end date'
|
|
602
|
+
]
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
id: 'active',
|
|
606
|
+
label: 'Active',
|
|
607
|
+
group: 'coupon',
|
|
608
|
+
type: 'boolean',
|
|
609
|
+
aliases: [
|
|
610
|
+
'enabled',
|
|
611
|
+
'status'
|
|
612
|
+
],
|
|
613
|
+
description: 'A new coupon starts switched off unless the file says it is active.'
|
|
614
|
+
},
|
|
615
|
+
{
|
|
616
|
+
id: 'redemptions',
|
|
617
|
+
label: 'Times used',
|
|
618
|
+
group: 'coupon',
|
|
619
|
+
type: 'integer',
|
|
620
|
+
readOnly: true
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
id: 'createdAt',
|
|
624
|
+
label: 'Created',
|
|
625
|
+
group: TRANSFER_SYSTEM_GROUP.id,
|
|
626
|
+
type: 'datetime',
|
|
627
|
+
system: true
|
|
628
|
+
}
|
|
629
|
+
];
|
|
630
|
+
export const COUPON_MATCH_KEYS = [
|
|
631
|
+
{
|
|
632
|
+
fieldId: 'code',
|
|
633
|
+
normalizer: 'caseless'
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
fieldId: 'id',
|
|
637
|
+
normalizer: 'aglynId'
|
|
638
|
+
}
|
|
639
|
+
];
|
|
640
|
+
export function couponRecord(id, stored) {
|
|
641
|
+
var _stored_percentOff, _stored_maxRedemptions, _stored_redemptions;
|
|
642
|
+
return {
|
|
643
|
+
id,
|
|
644
|
+
code: id,
|
|
645
|
+
percentOff: (_stored_percentOff = stored.percentOff) != null ? _stored_percentOff : null,
|
|
646
|
+
maxRedemptions: (_stored_maxRedemptions = stored.maxRedemptions) != null ? _stored_maxRedemptions : null,
|
|
647
|
+
expiresAt: iso(stored.expiresAtMs),
|
|
648
|
+
active: stored.enabled !== false,
|
|
649
|
+
redemptions: (_stored_redemptions = stored.redemptions) != null ? _stored_redemptions : 0,
|
|
650
|
+
createdAt: iso(stored.createdAt)
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
/** A coupon's document from its changes, as the coupons card writes one, or the refusal. */ export function couponWrite(current, changes) {
|
|
654
|
+
const doc = current ? _extends({}, current) : {
|
|
655
|
+
enabled: false,
|
|
656
|
+
redemptions: 0
|
|
657
|
+
};
|
|
658
|
+
if ('percentOff' in changes) doc['percentOff'] = Math.round(Number(changes['percentOff']));
|
|
659
|
+
if ('maxRedemptions' in changes) {
|
|
660
|
+
const limit = Number(changes['maxRedemptions']);
|
|
661
|
+
doc['maxRedemptions'] = changes['maxRedemptions'] === null || !Number.isFinite(limit) ? null : Math.max(1, Math.round(limit));
|
|
662
|
+
}
|
|
663
|
+
if ('expiresAt' in changes) doc['expiresAtMs'] = msOf(changes['expiresAt']);
|
|
664
|
+
if ('active' in changes) doc['enabled'] = changes['active'] === true;
|
|
665
|
+
const pct = Number(doc['percentOff']);
|
|
666
|
+
if (!(pct > 0 && pct <= 100)) return {
|
|
667
|
+
problem: 'A coupon takes 1 to 100 percent off.'
|
|
668
|
+
};
|
|
669
|
+
return {
|
|
670
|
+
doc
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
/*==========================================
|
|
674
|
+
* GIFT CARDS
|
|
675
|
+
*=========================================*/ /*
|
|
676
|
+
* A GIFT CARD IMPORT ISSUES CARDS (AGL-3551). A row is never written as a
|
|
677
|
+
* balance: each one the dry run passes is issued through the same path as
|
|
678
|
+
* the Gift cards card's Issue (`issueGiftCard`), so its amount is bounded,
|
|
679
|
+
* its code is never overwritten and its activity line is written.
|
|
680
|
+
*
|
|
681
|
+
* - The plan mints the code a row will carry — the file's own, a new one for
|
|
682
|
+
* a blank cell, or a new one for a row whose code another card holds and
|
|
683
|
+
* whose Conflicts choice is "create a new record" — so the dry run shows
|
|
684
|
+
* every card exactly as it will be issued.
|
|
685
|
+
* - A row whose code another card holds and whose choice is "update" is
|
|
686
|
+
* refused: a file never changes a card. "Skip" leaves the card as it is.
|
|
687
|
+
* - Every card the dry run passes adds to a total the person types back on
|
|
688
|
+
* the wizard's Confirm step (`GIFT_CARD_CONFIRM_STEP_ID`). The server reads
|
|
689
|
+
* the typed total from `extras` and issues nothing unless it is exactly
|
|
690
|
+
* the total of the cards about to be issued.
|
|
691
|
+
*/ export const GIFT_CARD_TRANSFER_FIELDS = [
|
|
692
|
+
{
|
|
693
|
+
id: 'code',
|
|
694
|
+
label: 'Code',
|
|
695
|
+
group: 'card',
|
|
696
|
+
type: 'text',
|
|
697
|
+
matchKey: true,
|
|
698
|
+
aliases: [
|
|
699
|
+
'gift card code',
|
|
700
|
+
'card code',
|
|
701
|
+
'gift card number',
|
|
702
|
+
'card number'
|
|
703
|
+
],
|
|
704
|
+
description: 'The card’s code, which spends its balance: keep the file as safe as the cards. On import, a blank code gets a new one.'
|
|
705
|
+
},
|
|
706
|
+
{
|
|
707
|
+
id: 'balance',
|
|
708
|
+
label: 'Balance',
|
|
709
|
+
group: 'card',
|
|
710
|
+
type: 'currency',
|
|
711
|
+
aliases: [
|
|
712
|
+
'amount',
|
|
713
|
+
'value',
|
|
714
|
+
'current balance',
|
|
715
|
+
'remaining balance',
|
|
716
|
+
'balance remaining'
|
|
717
|
+
],
|
|
718
|
+
description: 'On import, what the card is issued for, in US dollars — the balance it carries over.'
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
id: 'currency',
|
|
722
|
+
label: 'Currency',
|
|
723
|
+
group: 'card',
|
|
724
|
+
type: 'text',
|
|
725
|
+
aliases: [
|
|
726
|
+
'currency code'
|
|
727
|
+
],
|
|
728
|
+
description: 'Optional. Cards are held in US dollars; a row in another currency is refused, never converted.'
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
id: 'initial',
|
|
732
|
+
label: 'Issued for',
|
|
733
|
+
group: 'card',
|
|
734
|
+
type: 'number',
|
|
735
|
+
readOnly: true
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
id: 'status',
|
|
739
|
+
label: 'Status',
|
|
740
|
+
group: 'card',
|
|
741
|
+
type: 'text',
|
|
742
|
+
readOnly: true,
|
|
743
|
+
description: 'Active, Used up, Frozen or Voided.'
|
|
744
|
+
},
|
|
745
|
+
{
|
|
746
|
+
id: 'recipientEmail',
|
|
747
|
+
label: 'Recipient email',
|
|
748
|
+
group: 'card',
|
|
749
|
+
type: 'email',
|
|
750
|
+
aliases: [
|
|
751
|
+
'email',
|
|
752
|
+
'customer email',
|
|
753
|
+
'recipient'
|
|
754
|
+
]
|
|
755
|
+
},
|
|
756
|
+
{
|
|
757
|
+
id: 'orderId',
|
|
758
|
+
label: 'Order ID',
|
|
759
|
+
group: 'card',
|
|
760
|
+
type: 'text',
|
|
761
|
+
readOnly: true,
|
|
762
|
+
description: 'Blank for a card issued by hand.'
|
|
763
|
+
},
|
|
764
|
+
{
|
|
765
|
+
id: 'note',
|
|
766
|
+
label: 'Note',
|
|
767
|
+
group: 'card',
|
|
768
|
+
type: 'text',
|
|
769
|
+
aliases: [
|
|
770
|
+
'notes',
|
|
771
|
+
'message'
|
|
772
|
+
]
|
|
773
|
+
},
|
|
774
|
+
{
|
|
775
|
+
id: 'issuedAt',
|
|
776
|
+
label: 'Issued',
|
|
777
|
+
group: 'card',
|
|
778
|
+
type: 'datetime',
|
|
779
|
+
readOnly: true
|
|
780
|
+
},
|
|
781
|
+
{
|
|
782
|
+
id: 'lastUsedAt',
|
|
783
|
+
label: 'Last used',
|
|
784
|
+
group: 'card',
|
|
785
|
+
type: 'datetime',
|
|
786
|
+
readOnly: true
|
|
787
|
+
}
|
|
788
|
+
];
|
|
789
|
+
/** A card is found by its code, which is its ID. */ export const GIFT_CARD_MATCH_KEYS = [
|
|
790
|
+
{
|
|
791
|
+
fieldId: 'code',
|
|
792
|
+
normalizer: 'caseless'
|
|
793
|
+
},
|
|
794
|
+
{
|
|
795
|
+
fieldId: 'id',
|
|
796
|
+
normalizer: 'aglynId'
|
|
797
|
+
}
|
|
798
|
+
];
|
|
799
|
+
/** The fields an issued card's undo entry holds: any of them moving means the card was spent. */ export const GIFT_CARD_UNDO_FIELDS = [
|
|
800
|
+
'balance',
|
|
801
|
+
'status',
|
|
802
|
+
'lastUsedAt'
|
|
803
|
+
];
|
|
804
|
+
export function giftCardRecord(id, stored) {
|
|
805
|
+
var _stored_balanceCents, _stored_recipientEmail, _stored_orderId, _stored_note;
|
|
806
|
+
const balance = Number((_stored_balanceCents = stored.balanceCents) != null ? _stored_balanceCents : 0);
|
|
807
|
+
return {
|
|
808
|
+
id,
|
|
809
|
+
code: id,
|
|
810
|
+
balance: dollars(balance),
|
|
811
|
+
currency: GIFT_CARD_CURRENCY,
|
|
812
|
+
initial: dollars(stored.initialCents),
|
|
813
|
+
status: stored.voidedAtMs ? 'Voided' : stored.frozenAtMs ? 'Frozen' : balance > 0 ? 'Active' : 'Used up',
|
|
814
|
+
recipientEmail: (_stored_recipientEmail = stored.recipientEmail) != null ? _stored_recipientEmail : null,
|
|
815
|
+
orderId: (_stored_orderId = stored.orderId) != null ? _stored_orderId : null,
|
|
816
|
+
note: (_stored_note = stored.note) != null ? _stored_note : null,
|
|
817
|
+
issuedAt: iso(stored.createdAtMs),
|
|
818
|
+
lastUsedAt: iso(stored.lastUsedAtMs)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
/** Dollars and cents as a person reads them: `$1,250.00`. */ export function giftCardMoney(amountCents) {
|
|
822
|
+
return `$${(amountCents / 100).toLocaleString('en-US', {
|
|
823
|
+
minimumFractionDigits: 2,
|
|
824
|
+
maximumFractionDigits: 2
|
|
825
|
+
})}`;
|
|
826
|
+
}
|
|
827
|
+
/** A total as a person types it (`1250`, `$1,250.00`), in cents; `null` for anything else. */ export function typedDollarsToCents(typed) {
|
|
828
|
+
const body = String(typed != null ? typed : '').trim().replace(/^\$|^usd\s*/i, '').replace(/\s*usd$/i, '').replace(/,/g, '').trim();
|
|
829
|
+
if (!/^\d+(\.\d{1,2})?$/.test(body)) return null;
|
|
830
|
+
return Math.round(Number(body) * 100);
|
|
831
|
+
}
|
|
832
|
+
/** The Confirm step's answer from `extras`, or `null` for none: a total is a whole number of cents. */ export function readGiftCardConfirmation(extras) {
|
|
833
|
+
const value = extras == null ? void 0 : extras[GIFT_CARD_CONFIRM_STEP_ID];
|
|
834
|
+
if (!value || typeof value !== 'object') return null;
|
|
835
|
+
const totalCents = value['totalCents'];
|
|
836
|
+
if (typeof totalCents !== 'number' || !Number.isInteger(totalCents) || totalCents <= 0) return null;
|
|
837
|
+
return {
|
|
838
|
+
totalCents,
|
|
839
|
+
email: value['email'] === true
|
|
840
|
+
};
|
|
841
|
+
}
|
|
842
|
+
/** A money cell in cents and its currency: the derived amount, or a number as dollars. */ function giftCardAmount(value) {
|
|
843
|
+
if (value === null || value === undefined || value === '') return null;
|
|
844
|
+
if (typeof value === 'object' && 'amountMinor' in value) {
|
|
845
|
+
const amount = value;
|
|
846
|
+
return {
|
|
847
|
+
cents: Math.round(Number(amount.amountMinor)),
|
|
848
|
+
currency: text(amount.currency).toUpperCase() || GIFT_CARD_CURRENCY
|
|
849
|
+
};
|
|
850
|
+
}
|
|
851
|
+
const number = Number(value);
|
|
852
|
+
return Number.isFinite(number) ? {
|
|
853
|
+
cents: Math.round(number * 100),
|
|
854
|
+
currency: GIFT_CARD_CURRENCY
|
|
855
|
+
} : null;
|
|
856
|
+
}
|
|
857
|
+
/** The card a row's changes issue, or the sentence refusing it. */ export function giftCardIssueOf(changes, code) {
|
|
858
|
+
const recipientEmail = text(changes['recipientEmail']).toLowerCase().slice(0, 200) || null;
|
|
859
|
+
const note = text(changes['note']).slice(0, 200) || null;
|
|
860
|
+
const base = {
|
|
861
|
+
code: code.value,
|
|
862
|
+
amountCents: 0,
|
|
863
|
+
recipientEmail,
|
|
864
|
+
note
|
|
865
|
+
};
|
|
866
|
+
const codeProblem = code.fromFile ? giftCardCodeProblem(code.value) : null;
|
|
867
|
+
if (codeProblem) return _extends({}, base, {
|
|
868
|
+
problem: codeProblem
|
|
869
|
+
});
|
|
870
|
+
const amount = giftCardAmount(changes['balance']);
|
|
871
|
+
if (!amount) return _extends({}, base, {
|
|
872
|
+
problem: 'Say what the card is issued for in the Balance column.'
|
|
873
|
+
});
|
|
874
|
+
const named = text(changes['currency']).toUpperCase();
|
|
875
|
+
for (const currency of [
|
|
876
|
+
amount.currency,
|
|
877
|
+
named
|
|
878
|
+
].filter(Boolean)){
|
|
879
|
+
if (currency !== GIFT_CARD_CURRENCY) {
|
|
880
|
+
return _extends({}, base, {
|
|
881
|
+
problem: `Gift cards are held in US dollars; ${currency} is not converted.`
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
const amountProblem = giftCardAmountProblem(amount.cents);
|
|
886
|
+
if (amountProblem) return _extends({}, base, {
|
|
887
|
+
problem: amountProblem
|
|
888
|
+
});
|
|
889
|
+
return _extends({}, base, {
|
|
890
|
+
amountCents: amount.cents
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
/**
|
|
894
|
+
* The core's plan for a gift card file, made into the cards it issues: each
|
|
895
|
+
* create carrying the card it issues ({@link GiftCardRowPlan}), each row
|
|
896
|
+
* that would change an existing card refused, the total of every card
|
|
897
|
+
* counted, and a `screening` warning stating that total — and, when the
|
|
898
|
+
* Confirm step's answer is missing or names another total, that nothing is
|
|
899
|
+
* issued until it is confirmed.
|
|
900
|
+
*/ export function planGiftCardRows(plan, input) {
|
|
901
|
+
var _input_confirmation;
|
|
902
|
+
const issuing = new Map();
|
|
903
|
+
const rows = plan.rows.map((row)=>{
|
|
904
|
+
var _input_taken;
|
|
905
|
+
if (row.verdict === 'update' || row.verdict === 'unchanged') {
|
|
906
|
+
var _row_recordId;
|
|
907
|
+
const code = (_row_recordId = row.recordId) != null ? _row_recordId : '';
|
|
908
|
+
return _extends({}, row, {
|
|
909
|
+
verdict: 'update',
|
|
910
|
+
diff: [],
|
|
911
|
+
giftCard: {
|
|
912
|
+
code,
|
|
913
|
+
newCode: false,
|
|
914
|
+
madeCode: false,
|
|
915
|
+
amountCents: 0,
|
|
916
|
+
recipientEmail: null,
|
|
917
|
+
note: null,
|
|
918
|
+
problem: `A gift card with the code ${code} already exists, and a file never changes a card. ` + 'On the Conflicts step, skip this row, or create a new record to issue it under a new code.',
|
|
919
|
+
totalCents: 0,
|
|
920
|
+
confirmed: false
|
|
921
|
+
}
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
if (row.verdict !== 'create') return row;
|
|
925
|
+
const changes = rowChanges(row);
|
|
926
|
+
const fileCode = giftCardCodeOf(changes['code']);
|
|
927
|
+
// A create for a row that matched a card is the person's "create a new
|
|
928
|
+
// record": the card it names keeps its code, and this one gets another.
|
|
929
|
+
const newCode = Boolean(fileCode) && row.match.kind !== 'new';
|
|
930
|
+
const madeCode = !fileCode;
|
|
931
|
+
const code = newCode || madeCode ? input.mintCode() : fileCode;
|
|
932
|
+
const fromFile = !newCode && !madeCode;
|
|
933
|
+
let issue = giftCardIssueOf(changes, {
|
|
934
|
+
value: code,
|
|
935
|
+
fromFile
|
|
936
|
+
});
|
|
937
|
+
const earlier = issuing.get(code);
|
|
938
|
+
if (!issue.problem && fromFile && ((_input_taken = input.taken) == null ? void 0 : _input_taken.has(code))) {
|
|
939
|
+
issue = _extends({}, issue, {
|
|
940
|
+
problem: `A gift card with the code ${code} already exists, and a file never changes a card.`
|
|
941
|
+
});
|
|
942
|
+
} else if (!issue.problem && earlier !== undefined) {
|
|
943
|
+
issue = _extends({}, issue, {
|
|
944
|
+
problem: `Row ${earlier + 1} issues the code ${code} already.`
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
if (!issue.problem) issuing.set(code, row.index);
|
|
948
|
+
const diff = [
|
|
949
|
+
...row.diff.filter((change)=>change.fieldId !== 'code'),
|
|
950
|
+
{
|
|
951
|
+
fieldId: 'code',
|
|
952
|
+
before: null,
|
|
953
|
+
after: code,
|
|
954
|
+
mode: 'overwrite',
|
|
955
|
+
source: 'default',
|
|
956
|
+
rule: 'written'
|
|
957
|
+
}
|
|
958
|
+
];
|
|
959
|
+
return _extends({}, row, {
|
|
960
|
+
diff,
|
|
961
|
+
giftCard: _extends({}, issue, {
|
|
962
|
+
newCode,
|
|
963
|
+
madeCode,
|
|
964
|
+
totalCents: 0,
|
|
965
|
+
confirmed: false
|
|
966
|
+
})
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
const issued = rows.filter((row)=>{
|
|
970
|
+
var _row_giftCard;
|
|
971
|
+
return row.verdict === 'create' && Boolean(row.giftCard) && !((_row_giftCard = row.giftCard) == null ? void 0 : _row_giftCard.problem);
|
|
972
|
+
});
|
|
973
|
+
const totalCents = issued.reduce((sum, row)=>sum + row.giftCard.amountCents, 0);
|
|
974
|
+
const confirmed = ((_input_confirmation = input.confirmation) == null ? void 0 : _input_confirmation.totalCents) === totalCents;
|
|
975
|
+
for (const row of rows){
|
|
976
|
+
if (row.giftCard) row.giftCard = _extends({}, row.giftCard, {
|
|
977
|
+
totalCents,
|
|
978
|
+
confirmed
|
|
979
|
+
});
|
|
980
|
+
}
|
|
981
|
+
const warnings = plan.warnings.filter((warning)=>warning.class !== 'screening');
|
|
982
|
+
if (issued.length) {
|
|
983
|
+
const samples = [
|
|
984
|
+
{
|
|
985
|
+
row: TRANSFER_FILE_SAMPLE_ROW,
|
|
986
|
+
fieldId: 'balance',
|
|
987
|
+
value: giftCardMoney(totalCents),
|
|
988
|
+
detail: `Issues ${issued.length.toLocaleString('en-US')} gift card${issued.length === 1 ? '' : 's'} worth ${giftCardMoney(totalCents)} in total.`
|
|
989
|
+
}
|
|
990
|
+
];
|
|
991
|
+
if (!confirmed) {
|
|
992
|
+
samples.push({
|
|
993
|
+
row: TRANSFER_FILE_SAMPLE_ROW,
|
|
994
|
+
detail: input.confirmation ? `The total confirmed was ${giftCardMoney(input.confirmation.totalCents)}, not ${giftCardMoney(totalCents)}: no card is issued until the total is confirmed again on the Confirm step.` : 'The total has not been confirmed: no card is issued until it is typed on the Confirm step.'
|
|
995
|
+
});
|
|
996
|
+
}
|
|
997
|
+
warnings.push({
|
|
998
|
+
class: 'screening',
|
|
999
|
+
count: issued.length,
|
|
1000
|
+
rows: issued.length,
|
|
1001
|
+
fieldIds: [
|
|
1002
|
+
'balance'
|
|
1003
|
+
],
|
|
1004
|
+
samples,
|
|
1005
|
+
requiresAcknowledgement: true
|
|
1006
|
+
});
|
|
1007
|
+
}
|
|
1008
|
+
const summary = {
|
|
1009
|
+
create: 0,
|
|
1010
|
+
update: 0,
|
|
1011
|
+
unchanged: 0,
|
|
1012
|
+
skip: 0,
|
|
1013
|
+
fail: 0,
|
|
1014
|
+
total: rows.length
|
|
1015
|
+
};
|
|
1016
|
+
for (const row of rows)summary[row.verdict] += 1;
|
|
1017
|
+
return {
|
|
1018
|
+
rows,
|
|
1019
|
+
summary,
|
|
1020
|
+
warnings,
|
|
1021
|
+
acknowledgementsRequired: warnings.filter((warning)=>warning.requiresAcknowledgement).map((warning)=>warning.class)
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
/*==========================================
|
|
1025
|
+
* CATEGORIES
|
|
1026
|
+
*=========================================*/ export const CATEGORY_TRANSFER_FIELDS = [
|
|
1027
|
+
{
|
|
1028
|
+
id: 'name',
|
|
1029
|
+
label: 'Name',
|
|
1030
|
+
group: 'category',
|
|
1031
|
+
type: 'text',
|
|
1032
|
+
aliases: [
|
|
1033
|
+
'category',
|
|
1034
|
+
'category name',
|
|
1035
|
+
'title'
|
|
1036
|
+
]
|
|
1037
|
+
},
|
|
1038
|
+
{
|
|
1039
|
+
id: 'slug',
|
|
1040
|
+
label: 'Slug',
|
|
1041
|
+
group: 'category',
|
|
1042
|
+
type: 'text',
|
|
1043
|
+
readOnly: true,
|
|
1044
|
+
matchKey: true,
|
|
1045
|
+
aliases: [
|
|
1046
|
+
'handle'
|
|
1047
|
+
],
|
|
1048
|
+
description: 'Made from the name, as the console makes it; a file finds a category by it.'
|
|
1049
|
+
},
|
|
1050
|
+
{
|
|
1051
|
+
id: 'parent',
|
|
1052
|
+
label: 'Parent',
|
|
1053
|
+
group: 'category',
|
|
1054
|
+
type: 'text',
|
|
1055
|
+
aliases: [
|
|
1056
|
+
'parent category',
|
|
1057
|
+
'parent slug'
|
|
1058
|
+
],
|
|
1059
|
+
description: 'The parent category’s slug or name; blank for a top-level category.'
|
|
1060
|
+
},
|
|
1061
|
+
{
|
|
1062
|
+
id: 'order',
|
|
1063
|
+
label: 'Position',
|
|
1064
|
+
group: 'category',
|
|
1065
|
+
type: 'integer',
|
|
1066
|
+
aliases: [
|
|
1067
|
+
'sort order',
|
|
1068
|
+
'order'
|
|
1069
|
+
]
|
|
1070
|
+
}
|
|
1071
|
+
];
|
|
1072
|
+
export const CATEGORY_MATCH_KEYS = [
|
|
1073
|
+
{
|
|
1074
|
+
fieldId: 'slug',
|
|
1075
|
+
normalizer: 'slug'
|
|
1076
|
+
},
|
|
1077
|
+
{
|
|
1078
|
+
fieldId: 'name',
|
|
1079
|
+
normalizer: 'name'
|
|
1080
|
+
},
|
|
1081
|
+
{
|
|
1082
|
+
fieldId: 'id',
|
|
1083
|
+
normalizer: 'aglynId'
|
|
1084
|
+
}
|
|
1085
|
+
];
|
|
1086
|
+
export function categoryRecord(id, stored, slugs = new Map()) {
|
|
1087
|
+
var _stored_name, _stored_slug, _slugs_get;
|
|
1088
|
+
return {
|
|
1089
|
+
id,
|
|
1090
|
+
name: (_stored_name = stored.name) != null ? _stored_name : null,
|
|
1091
|
+
slug: (_stored_slug = stored.slug) != null ? _stored_slug : null,
|
|
1092
|
+
parent: stored.parentId ? (_slugs_get = slugs.get(stored.parentId)) != null ? _slugs_get : stored.parentId : null,
|
|
1093
|
+
order: typeof stored.order === 'number' ? stored.order : null
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
/** The category a parent cell names — by id, slug, then name — or `null`. */ export function resolveCategoryParent(index, value) {
|
|
1097
|
+
const wanted = text(value);
|
|
1098
|
+
if (!wanted) return null;
|
|
1099
|
+
if (index.has(wanted)) return wanted;
|
|
1100
|
+
const slug = commerceSlug(wanted);
|
|
1101
|
+
for (const [id, category] of index)if (category.slug === slug) return id;
|
|
1102
|
+
const name = wanted.toLowerCase();
|
|
1103
|
+
for (const [id, category] of index)if (text(category.name).toLowerCase() === name) return id;
|
|
1104
|
+
return null;
|
|
1105
|
+
}
|
|
1106
|
+
/**
|
|
1107
|
+
* A category's document from its changes: the name and the slug made from
|
|
1108
|
+
* it, the parent resolved — never itself or one of its own descendants —
|
|
1109
|
+
* and the position. A parent the site does not have is refused, not
|
|
1110
|
+
* guessed.
|
|
1111
|
+
*/ export function categoryWrite(id, current, changes, index) {
|
|
1112
|
+
const doc = current ? _extends({}, current) : {
|
|
1113
|
+
parentId: null
|
|
1114
|
+
};
|
|
1115
|
+
if ('name' in changes) {
|
|
1116
|
+
const name = text(changes['name']).slice(0, 80);
|
|
1117
|
+
if (!name) return {
|
|
1118
|
+
problem: 'A category needs a name.'
|
|
1119
|
+
};
|
|
1120
|
+
doc['name'] = name;
|
|
1121
|
+
doc['slug'] = commerceSlug(name) || id;
|
|
1122
|
+
}
|
|
1123
|
+
if (!text(doc['name'])) return {
|
|
1124
|
+
problem: 'A category needs a name.'
|
|
1125
|
+
};
|
|
1126
|
+
if ('parent' in changes) {
|
|
1127
|
+
if (isBlankTransferValue(changes['parent'])) doc['parentId'] = null;
|
|
1128
|
+
else {
|
|
1129
|
+
const parentId = resolveCategoryParent(index, changes['parent']);
|
|
1130
|
+
if (!parentId) return {
|
|
1131
|
+
problem: `No category "${text(changes['parent'])}" to put it under.`
|
|
1132
|
+
};
|
|
1133
|
+
for(let at = parentId, steps = 0; at && steps < 100; steps += 1){
|
|
1134
|
+
var _ref;
|
|
1135
|
+
var _index_get;
|
|
1136
|
+
if (at === id) return {
|
|
1137
|
+
problem: 'A category cannot sit under itself or one of its own.'
|
|
1138
|
+
};
|
|
1139
|
+
at = (_ref = (_index_get = index.get(at)) == null ? void 0 : _index_get.parentId) != null ? _ref : null;
|
|
1140
|
+
}
|
|
1141
|
+
doc['parentId'] = parentId;
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
if ('order' in changes) {
|
|
1145
|
+
const order = Number(changes['order']);
|
|
1146
|
+
doc['order'] = changes['order'] === null || !Number.isFinite(order) ? null : Math.round(order);
|
|
1147
|
+
}
|
|
1148
|
+
return {
|
|
1149
|
+
doc
|
|
1150
|
+
};
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
//# sourceMappingURL=records-transfer.js.map
|