platform-types 1.10.777 → 1.10.779
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/notification.js +19 -1
- package/dist/cjs/socket.js +11 -0
- package/dist/cjs/store.js +8 -0
- package/dist/cjs/storefrontWebLiteralKeys.js +11 -46
- package/dist/cjs/wholesaleRequest.js +64 -0
- package/dist/customer.d.ts +15 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/notification.d.ts +25 -2
- package/dist/notification.js +19 -1
- package/dist/socket.d.ts +1 -1
- package/dist/socket.js +11 -0
- package/dist/store.d.ts +23 -2
- package/dist/store.js +8 -0
- package/dist/storefrontWebLiteralKeys.d.ts +5 -9
- package/dist/storefrontWebLiteralKeys.js +10 -44
- package/dist/wholesaleRequest.d.ts +183 -0
- package/dist/wholesaleRequest.js +61 -0
- package/package.json +1 -1
package/dist/cjs/index.js
CHANGED
|
@@ -121,6 +121,7 @@ __exportStar(require("./user.js"), exports);
|
|
|
121
121
|
__exportStar(require("./userActivity.js"), exports);
|
|
122
122
|
__exportStar(require("./valuation.js"), exports);
|
|
123
123
|
__exportStar(require("./webhook.js"), exports);
|
|
124
|
+
__exportStar(require("./wholesaleRequest.js"), exports);
|
|
124
125
|
__exportStar(require("./wire.js"), exports);
|
|
125
126
|
__exportStar(require("./workItem.js"), exports);
|
|
126
127
|
__exportStar(require("./zone.js"), exports);
|
package/dist/cjs/notification.js
CHANGED
|
@@ -6,7 +6,7 @@ exports.WEB_NOTIFICATIONS_MAX_LIMIT = exports.CUSTOMER_NOTIFICATION_TYPES = expo
|
|
|
6
6
|
// legacy opt-ins and explicit channel maps remain source data on USER rows.
|
|
7
7
|
// Addressed ERROR / ML_FACTURADOR_COLLISION / ML_SELLER_SWITCHED /
|
|
8
8
|
// ML_CONNECTION_BROKEN / MENTION / CUSTOMER_PENDING_APPROVAL /
|
|
9
|
-
// PAYMENT_ATTEMPT_RESOLUTION do not select a producer subscriber audience;
|
|
9
|
+
// PAYMENT_ATTEMPT_RESOLUTION / CUSTOMER_WHOLESALE_REQUEST do not select a producer subscriber audience;
|
|
10
10
|
// the consumer still reads the live USER row for each delivery channel.
|
|
11
11
|
// AFIP_CERT_EXPIRY is seeded on the founding user and uses the subscriber index.
|
|
12
12
|
// Exported as a real enum consumed by api and app; adding an indexed type
|
|
@@ -199,6 +199,24 @@ var NotificationTypeEnum;
|
|
|
199
199
|
* re-opens rings again.
|
|
200
200
|
*/
|
|
201
201
|
NotificationTypeEnum["PAYMENT_ATTEMPT_RESOLUTION"] = "PAYMENT_ATTEMPT_RESOLUTION";
|
|
202
|
+
/**
|
|
203
|
+
* A signed-in storefront buyer asked for a wholesale account
|
|
204
|
+
* (`POST /user { mode: 'wholesale-request' }`) and the request waits in the
|
|
205
|
+
* operator queue (`GET /customers?mode=wholesale-requests`). Names the buyer
|
|
206
|
+
* by name only — never their email or CUIT.
|
|
207
|
+
*
|
|
208
|
+
* ⚠️ ADMIN-ONLY, and its own preference key. The producer fans out to every
|
|
209
|
+
* non-disabled ADMIN of the store (`listActiveAdmins`) — the tier that reads
|
|
210
|
+
* the queue and decides it — so a client offers the
|
|
211
|
+
* `CUSTOMER_WHOLESALE_REQUEST` toggle to ADMINs only. No legacy opt-in scan:
|
|
212
|
+
* the consumer enforces explicit type/channel preferences
|
|
213
|
+
* (`notificationPreferences.CUSTOMER_WHOLESALE_REQUEST`), default-on when
|
|
214
|
+
* absent. Additive — no backfill or consumer migration owed.
|
|
215
|
+
*
|
|
216
|
+
* ⚠️ Carries `wholesaleRequestRef` and NEVER a top-level `customerId` (an
|
|
217
|
+
* audience — the bell would reach the buyer's own storefront socket).
|
|
218
|
+
*/
|
|
219
|
+
NotificationTypeEnum["CUSTOMER_WHOLESALE_REQUEST"] = "CUSTOMER_WHOLESALE_REQUEST";
|
|
202
220
|
})(NotificationTypeEnum || (exports.NotificationTypeEnum = NotificationTypeEnum = {}));
|
|
203
221
|
/**
|
|
204
222
|
* The notification types a CUSTOMER bell can carry today; each links through
|
package/dist/cjs/socket.js
CHANGED
|
@@ -691,6 +691,17 @@ exports.SOCKET_ACTIONS = [
|
|
|
691
691
|
* where the role and scope gates sit.
|
|
692
692
|
*/
|
|
693
693
|
'workItems',
|
|
694
|
+
/**
|
|
695
|
+
* BE → the store's ADMIN-TIER operator sockets when a buyer's wholesale
|
|
696
|
+
* request is submitted or decided. Carries the `WholesaleRequest` row as it
|
|
697
|
+
* now stands (company data included), so the queue can patch in place.
|
|
698
|
+
*
|
|
699
|
+
* ⚠️ Admin tier only — `wsPostStore(..., { audience: 'operator', lowTierData:
|
|
700
|
+
* null })`: the queue is an admin-tier read, so a USER or PRINTER socket
|
|
701
|
+
* receives nothing. The buyer follows their own request on the `'customers'`
|
|
702
|
+
* action instead (`WholesaleRequestStatusFrame`).
|
|
703
|
+
*/
|
|
704
|
+
'wholesaleRequests',
|
|
694
705
|
];
|
|
695
706
|
/** Runtime guard — narrows an untrusted string to a known action. */
|
|
696
707
|
const isSocketAction = (value) => typeof value === 'string' && exports.SOCKET_ACTIONS.includes(value);
|
package/dist/cjs/store.js
CHANGED
|
@@ -139,6 +139,13 @@ exports.STORE_MODULE_ERROR_CODES = [
|
|
|
139
139
|
* entry naming no list of the store, with `data: { field, value }` where
|
|
140
140
|
* `field` is `<catalog>[<row>].availability.priceLists[<entry>]`.
|
|
141
141
|
*
|
|
142
|
+
* Request refusal, `409`:
|
|
143
|
+
* - `WHOLESALE_LIST_REQUIRED` — an `ecommerce` save turns
|
|
144
|
+
* `config.wholesaleRequests` on while the store holds no
|
|
145
|
+
* `audienceClass: 'wholesale'` list (`store/_ecommercePatch.ts`), with
|
|
146
|
+
* `data: { field: 'ecommerce.config.wholesaleRequests' }`. The platform PUT
|
|
147
|
+
* answers it too.
|
|
148
|
+
*
|
|
142
149
|
* Maintenance mode (`{ mode: 'maintenance', level }`, `store/_patchMaintenance.ts`):
|
|
143
150
|
* - `FORBIDDEN` (403) — the caller is neither an ADMIN nor a MANAGER.
|
|
144
151
|
* - `STORE_NOT_FOUND` (404) — the store row is gone.
|
|
@@ -170,6 +177,7 @@ exports.STORE_PATCH_ERROR_CODES = [
|
|
|
170
177
|
'DISPLAY_CURRENCY_NOT_INVOICEABLE',
|
|
171
178
|
'PRODUCT_CURRENCY_NOT_INVOICEABLE',
|
|
172
179
|
'PRICE_LIST_NOT_FOUND',
|
|
180
|
+
'WHOLESALE_LIST_REQUIRED',
|
|
173
181
|
'FORBIDDEN',
|
|
174
182
|
'STORE_NOT_FOUND',
|
|
175
183
|
];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isAdoptableStorefrontLiteral = exports.
|
|
3
|
+
exports.isAdoptableStorefrontLiteral = exports.isStorefrontLiteralKey = exports.STOREFRONT_WEB_LITERAL_KEYS = void 0;
|
|
4
4
|
const storefrontDesignRegistry_ts_1 = require("./storefrontDesignRegistry.js");
|
|
5
5
|
/**
|
|
6
6
|
* Every storefront `WEB` literal key a shop may override, in code-unit order.
|
|
@@ -17,6 +17,9 @@ const storefrontDesignRegistry_ts_1 = require("./storefrontDesignRegistry.js");
|
|
|
17
17
|
* - keys whose storefront value is not a plain `{name}`-token sentence (it
|
|
18
18
|
* branches, computes, or is not a string), which a shop string cannot
|
|
19
19
|
* express.
|
|
20
|
+
* Route paths (the former `NAVIGATE_*` keys) are not copy at all: they live in
|
|
21
|
+
* the storefront's own config, and the api refuses and drops a `NAVIGATE_*`
|
|
22
|
+
* literal on every storefront surface.
|
|
20
23
|
* An editor that lists any of those offers an edit the server refuses.
|
|
21
24
|
*
|
|
22
25
|
* Interpolated values use `{name}` tokens named after the storefront's own
|
|
@@ -492,6 +495,8 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
492
495
|
'DATA_RIGHTS_COLLECTION_NOTES',
|
|
493
496
|
'DATA_RIGHTS_COLLECTION_ORDERS',
|
|
494
497
|
'DATA_RIGHTS_COLLECTION_PAYMENTS',
|
|
498
|
+
'DATA_RIGHTS_COLLECTION_RECENT_PURCHASES',
|
|
499
|
+
'DATA_RIGHTS_COLLECTION_WHOLESALE_REQUESTS',
|
|
495
500
|
'DATA_RIGHTS_DIALOG_ACK',
|
|
496
501
|
'DATA_RIGHTS_DIALOG_CONFIRM',
|
|
497
502
|
'DATA_RIGHTS_DIALOG_FISCAL',
|
|
@@ -815,27 +820,6 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
815
820
|
'MOBILE_BUTTONS_FAVORITES',
|
|
816
821
|
'MOBILE_BUTTONS_INVOICES',
|
|
817
822
|
'MOBILE_BUTTONS_ORDERS',
|
|
818
|
-
'NAVIGATE_APPOINTMENTS',
|
|
819
|
-
'NAVIGATE_BASKET',
|
|
820
|
-
'NAVIGATE_BRANDS',
|
|
821
|
-
'NAVIGATE_CATEGORIES',
|
|
822
|
-
'NAVIGATE_CHECKOUT',
|
|
823
|
-
'NAVIGATE_FAVORITES',
|
|
824
|
-
'NAVIGATE_HOME',
|
|
825
|
-
'NAVIGATE_INCOMES',
|
|
826
|
-
'NAVIGATE_INVOICES',
|
|
827
|
-
'NAVIGATE_LOGIN',
|
|
828
|
-
'NAVIGATE_LOGOUT',
|
|
829
|
-
'NAVIGATE_MAINTENANCE',
|
|
830
|
-
'NAVIGATE_NEW_ARRIVALS',
|
|
831
|
-
'NAVIGATE_NOTIFICATIONS',
|
|
832
|
-
'NAVIGATE_ORDERS',
|
|
833
|
-
'NAVIGATE_PAYMENTS',
|
|
834
|
-
'NAVIGATE_PROFILE',
|
|
835
|
-
'NAVIGATE_RECOVER',
|
|
836
|
-
'NAVIGATE_REGISTRATION',
|
|
837
|
-
'NAVIGATE_SALE',
|
|
838
|
-
'NAVIGATE_SESSIONS',
|
|
839
823
|
'NETWORK_OFFLINE',
|
|
840
824
|
'NETWORK_REQUEST_FAILED',
|
|
841
825
|
'NETWORK_TIMEOUT',
|
|
@@ -1078,6 +1062,7 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
1078
1062
|
'PREVIEW_LOADING',
|
|
1079
1063
|
'PREVIEW_READ_FAILED',
|
|
1080
1064
|
'PREVIEW_READ_ONLY',
|
|
1065
|
+
'PREVIEW_RECENTLY_BOUGHT',
|
|
1081
1066
|
'PREVIEW_RESTART',
|
|
1082
1067
|
'PREVIEW_RETRY',
|
|
1083
1068
|
'PREVIEW_TITLE',
|
|
@@ -1346,6 +1331,7 @@ exports.STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
1346
1331
|
'STOREFRONT_FOOTER_NAV_ARIA',
|
|
1347
1332
|
'STOREFRONT_MERCHANT_NAV_ARIA',
|
|
1348
1333
|
'STOREFRONT_NAV_GROUP_FALLBACK',
|
|
1334
|
+
'STOREFRONT_PRODUCTS_REFRESH_FAILED',
|
|
1349
1335
|
'STOREFRONT_SEARCH',
|
|
1350
1336
|
'STOREFRONT_SOCIAL_FACEBOOK',
|
|
1351
1337
|
'STOREFRONT_SOCIAL_INSTAGRAM',
|
|
@@ -1374,34 +1360,13 @@ const STOREFRONT_WEB_LITERAL_KEY_SET = new Set(exports.STOREFRONT_WEB_LITERAL_KE
|
|
|
1374
1360
|
/** Whether a shop may override `key` — membership in {@link STOREFRONT_WEB_LITERAL_KEYS}. */
|
|
1375
1361
|
const isStorefrontLiteralKey = (key) => STOREFRONT_WEB_LITERAL_KEY_SET.has(key);
|
|
1376
1362
|
exports.isStorefrontLiteralKey = isStorefrontLiteralKey;
|
|
1377
|
-
/** Keys whose value is a storefront route the storefront renders as a link target. */
|
|
1378
|
-
const STOREFRONT_NAVIGATE_LITERAL_PREFIX = 'NAVIGATE_';
|
|
1379
|
-
/**
|
|
1380
|
-
* A same-origin path: a leading `/`, a second character that is neither `/` nor `\`
|
|
1381
|
-
* (`//host` and `/\host` are protocol-relative to a browser), and no whitespace,
|
|
1382
|
-
* backslash, control character (`\p{Cc}`) or invisible format character (`\p{Cf}`: every
|
|
1383
|
-
* bidi mark including U+061C, zero-width characters, the soft hyphen, tag characters)
|
|
1384
|
-
* anywhere. Browsers strip tab and newline before parsing, so `/\t/host` becomes `//host`;
|
|
1385
|
-
* invisible and directional characters can also disguise the link target. A scheme
|
|
1386
|
-
* cannot start with `/`.
|
|
1387
|
-
*/
|
|
1388
|
-
const STOREFRONT_SAME_ORIGIN_PATH_RE = /^\/(?![/\\])[^\s\\\p{Cc}\p{Cf}]*$/u;
|
|
1389
|
-
/**
|
|
1390
|
-
* Whether `value` may be stored, published or served for `key`. A `NAVIGATE_*` key is
|
|
1391
|
-
* rendered as a link, so it must be a same-origin path — otherwise an override could
|
|
1392
|
-
* send a shop's customers to an off-site look-alike login page. Every other key is
|
|
1393
|
-
* unconstrained here (key membership, well-formedness and length are checked apart).
|
|
1394
|
-
*/
|
|
1395
|
-
const isAllowedStorefrontLiteralValue = (key, value) => !key.startsWith(STOREFRONT_NAVIGATE_LITERAL_PREFIX) || STOREFRONT_SAME_ORIGIN_PATH_RE.test(value);
|
|
1396
|
-
exports.isAllowedStorefrontLiteralValue = isAllowedStorefrontLiteralValue;
|
|
1397
1363
|
/**
|
|
1398
1364
|
* Whether a shop's stored `value` for `key` is one a save keeps, a read serves and the editor
|
|
1399
|
-
* baseline adopts: a shop-overridable key
|
|
1400
|
-
* {@link STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS} code points
|
|
1365
|
+
* baseline adopts: a shop-overridable key and a well-formed string within
|
|
1366
|
+
* {@link STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS} code points.
|
|
1401
1367
|
*/
|
|
1402
1368
|
const isAdoptableStorefrontLiteral = (key, value) => (0, exports.isStorefrontLiteralKey)(key) &&
|
|
1403
1369
|
typeof value === 'string' &&
|
|
1404
1370
|
value.isWellFormed() &&
|
|
1405
|
-
Array.from(value).length <= storefrontDesignRegistry_ts_1.STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS
|
|
1406
|
-
(0, exports.isAllowedStorefrontLiteralValue)(key, value);
|
|
1371
|
+
Array.from(value).length <= storefrontDesignRegistry_ts_1.STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS;
|
|
1407
1372
|
exports.isAdoptableStorefrontLiteral = isAdoptableStorefrontLiteral;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* A signed-in storefront buyer's request for a wholesale account, and the
|
|
4
|
+
* operator queue that decides it.
|
|
5
|
+
*
|
|
6
|
+
* One row per customer (`WHOLESALEREQ#{storeId}` / `{customerId}`). A new
|
|
7
|
+
* request overwrites the previous one only once that one is decided, so a
|
|
8
|
+
* customer holds at most one pending request.
|
|
9
|
+
*
|
|
10
|
+
* The flow is offered only while the store's
|
|
11
|
+
* `ecommerce.config.wholesaleRequests` is `true` (absent = off).
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.WHOLESALE_REQUEST_ERROR_CODES = exports.WHOLESALE_REQUEST_REASON_MAX = exports.WHOLESALE_REQUEST_PHONE_MAX = exports.WHOLESALE_REQUEST_MESSAGE_MAX = exports.WHOLESALE_REQUEST_ACTIVITY_MAX = exports.WHOLESALE_REQUEST_RAZON_SOCIAL_MAX = exports.WHOLESALE_REQUESTS_PAGE_MAX = exports.WHOLESALE_REQUEST_STATES = void 0;
|
|
15
|
+
/** The three states a request moves through. */
|
|
16
|
+
exports.WHOLESALE_REQUEST_STATES = ['pending', 'approved', 'declined'];
|
|
17
|
+
/** `GET /customers?mode=wholesale-requests` page ceiling; the default page is 50. */
|
|
18
|
+
exports.WHOLESALE_REQUESTS_PAGE_MAX = 100;
|
|
19
|
+
/** Character ceilings on the buyer's free text, counted after trimming. */
|
|
20
|
+
exports.WHOLESALE_REQUEST_RAZON_SOCIAL_MAX = 200;
|
|
21
|
+
exports.WHOLESALE_REQUEST_ACTIVITY_MAX = 120;
|
|
22
|
+
exports.WHOLESALE_REQUEST_MESSAGE_MAX = 1000;
|
|
23
|
+
exports.WHOLESALE_REQUEST_PHONE_MAX = 30;
|
|
24
|
+
/** Character ceiling on the operator's decline reason, counted after trimming. */
|
|
25
|
+
exports.WHOLESALE_REQUEST_REASON_MAX = 500;
|
|
26
|
+
/**
|
|
27
|
+
* Every `error` code the wholesale-request flow answers on its own behalf:
|
|
28
|
+
* - web `POST /user { mode: 'wholesale-request' }` (`stacks/web/lambdas/user/_wholesaleRequest.ts`):
|
|
29
|
+
* `WHOLESALE_REQUESTS_DISABLED`, `CUSTOMER_NOT_FOUND`, `CUSTOMER_ANONYMIZED`, `USER_NOT_ENABLED`,
|
|
30
|
+
* `CUSTOMER_ALREADY_WHOLESALE`, `WHOLESALE_REQUEST_CONTENDED`, `WHOLESALE_REQUEST_PENDING`;
|
|
31
|
+
* - app `GET /customers?mode=wholesale-requests` and `POST /customers { mode: 'wholesale-decision' }`
|
|
32
|
+
* (`stacks/lambdas/customers/_wholesaleRequests.ts`): `ROLE_FORBIDDEN`, `ADMIN_REQUIRED`,
|
|
33
|
+
* `INVALID_CURSOR`, `WHOLESALE_REQUEST_NOT_PENDING`, `WHOLESALE_DECISION_CONTENDED`,
|
|
34
|
+
* `CUSTOMER_NOT_FOUND`, `CUSTOMER_ANONYMIZED`, `CUSTOMER_MERGED`, `PRICE_LIST_NOT_FOUND`,
|
|
35
|
+
* `PRICE_LIST_NOT_WHOLESALE`, `WHOLESALE_REQUEST_NOT_FOUND`;
|
|
36
|
+
* - the opt-in refusal both configuration writers answer when `ecommerce.config.wholesaleRequests`
|
|
37
|
+
* turns on with no wholesale-class list: `WHOLESALE_LIST_REQUIRED` (`409`). It is also in
|
|
38
|
+
* `STORE_PATCH_ERROR_CODES`, on purpose: a wholesale client handles it without reading that tuple.
|
|
39
|
+
*
|
|
40
|
+
* The rule: a code is listed when a wholesale handler writes it as its own literal, and each of
|
|
41
|
+
* those literals is typed `satisfies WholesaleRequestErrorCode`, so one this list misses fails
|
|
42
|
+
* typecheck. A code those routes forward from a shared helper is not listed, though a wholesale
|
|
43
|
+
* route can answer it: the customer-access refusals (`refuseCustomerAccess`), `VALIDATION_FAILED`
|
|
44
|
+
* (`validateRequest`), the handler wrapper's own codes and the authorizer's.
|
|
45
|
+
*/
|
|
46
|
+
exports.WHOLESALE_REQUEST_ERROR_CODES = [
|
|
47
|
+
'WHOLESALE_REQUESTS_DISABLED',
|
|
48
|
+
'CUSTOMER_NOT_FOUND',
|
|
49
|
+
'CUSTOMER_ANONYMIZED',
|
|
50
|
+
'USER_NOT_ENABLED',
|
|
51
|
+
'CUSTOMER_ALREADY_WHOLESALE',
|
|
52
|
+
'WHOLESALE_REQUEST_CONTENDED',
|
|
53
|
+
'WHOLESALE_REQUEST_PENDING',
|
|
54
|
+
'ROLE_FORBIDDEN',
|
|
55
|
+
'ADMIN_REQUIRED',
|
|
56
|
+
'INVALID_CURSOR',
|
|
57
|
+
'WHOLESALE_REQUEST_NOT_PENDING',
|
|
58
|
+
'WHOLESALE_DECISION_CONTENDED',
|
|
59
|
+
'CUSTOMER_MERGED',
|
|
60
|
+
'PRICE_LIST_NOT_FOUND',
|
|
61
|
+
'PRICE_LIST_NOT_WHOLESALE',
|
|
62
|
+
'WHOLESALE_REQUEST_NOT_FOUND',
|
|
63
|
+
'WHOLESALE_LIST_REQUIRED',
|
|
64
|
+
];
|
package/dist/customer.d.ts
CHANGED
|
@@ -1238,6 +1238,15 @@ declare global {
|
|
|
1238
1238
|
* On BOTH surfaces; `[]` when the subject has none.
|
|
1239
1239
|
*/
|
|
1240
1240
|
recentPurchases: CustomerRecentPurchaseExportEntry[];
|
|
1241
|
+
/**
|
|
1242
|
+
* The subject's wholesale-account requests, on BOTH surfaces: the one filed
|
|
1243
|
+
* under their own id plus any filed under a merged-away id folded into them
|
|
1244
|
+
* (the same folded ids, and the same cap, as the financial collections) —
|
|
1245
|
+
* the company data submitted and its state, never the decline reason or the
|
|
1246
|
+
* deciding operator. Absent when there is none (and from servers predating
|
|
1247
|
+
* the request flow).
|
|
1248
|
+
*/
|
|
1249
|
+
wholesaleRequests?: WholesaleRequestExportEntry[];
|
|
1241
1250
|
/**
|
|
1242
1251
|
* Per COLLECTION, and honest: `true` means the read stopped before the
|
|
1243
1252
|
* end of a partition, folded financial IDs were omitted at the cap, or whole
|
|
@@ -1261,6 +1270,12 @@ declare global {
|
|
|
1261
1270
|
favorites: boolean;
|
|
1262
1271
|
/** `true` when folded ids past the cap were not read. */
|
|
1263
1272
|
recentPurchases: boolean;
|
|
1273
|
+
/**
|
|
1274
|
+
* Present when `wholesaleRequests` is, or when merged-away ids were cut
|
|
1275
|
+
* at the folded-id cap — then `true`: a request filed under an omitted id
|
|
1276
|
+
* may be missing.
|
|
1277
|
+
*/
|
|
1278
|
+
wholesaleRequests?: boolean;
|
|
1264
1279
|
};
|
|
1265
1280
|
}
|
|
1266
1281
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -97,6 +97,7 @@ export * from './user.ts';
|
|
|
97
97
|
export * from './userActivity.ts';
|
|
98
98
|
export * from './valuation.ts';
|
|
99
99
|
export * from './webhook.ts';
|
|
100
|
+
export * from './wholesaleRequest.ts';
|
|
100
101
|
export * from './wire.ts';
|
|
101
102
|
export * from './workItem.ts';
|
|
102
103
|
export * from './zone.ts';
|
package/dist/index.js
CHANGED
|
@@ -105,6 +105,7 @@ export * from "./user.js";
|
|
|
105
105
|
export * from "./userActivity.js";
|
|
106
106
|
export * from "./valuation.js";
|
|
107
107
|
export * from "./webhook.js";
|
|
108
|
+
export * from "./wholesaleRequest.js";
|
|
108
109
|
export * from "./wire.js";
|
|
109
110
|
export * from "./workItem.js";
|
|
110
111
|
export * from "./zone.js";
|
package/dist/notification.d.ts
CHANGED
|
@@ -177,7 +177,25 @@ export declare enum NotificationTypeEnum {
|
|
|
177
177
|
* nothing. An attempt an operator closed (`resolved`) and a later approval
|
|
178
178
|
* re-opens rings again.
|
|
179
179
|
*/
|
|
180
|
-
PAYMENT_ATTEMPT_RESOLUTION = "PAYMENT_ATTEMPT_RESOLUTION"
|
|
180
|
+
PAYMENT_ATTEMPT_RESOLUTION = "PAYMENT_ATTEMPT_RESOLUTION",
|
|
181
|
+
/**
|
|
182
|
+
* A signed-in storefront buyer asked for a wholesale account
|
|
183
|
+
* (`POST /user { mode: 'wholesale-request' }`) and the request waits in the
|
|
184
|
+
* operator queue (`GET /customers?mode=wholesale-requests`). Names the buyer
|
|
185
|
+
* by name only — never their email or CUIT.
|
|
186
|
+
*
|
|
187
|
+
* ⚠️ ADMIN-ONLY, and its own preference key. The producer fans out to every
|
|
188
|
+
* non-disabled ADMIN of the store (`listActiveAdmins`) — the tier that reads
|
|
189
|
+
* the queue and decides it — so a client offers the
|
|
190
|
+
* `CUSTOMER_WHOLESALE_REQUEST` toggle to ADMINs only. No legacy opt-in scan:
|
|
191
|
+
* the consumer enforces explicit type/channel preferences
|
|
192
|
+
* (`notificationPreferences.CUSTOMER_WHOLESALE_REQUEST`), default-on when
|
|
193
|
+
* absent. Additive — no backfill or consumer migration owed.
|
|
194
|
+
*
|
|
195
|
+
* ⚠️ Carries `wholesaleRequestRef` and NEVER a top-level `customerId` (an
|
|
196
|
+
* audience — the bell would reach the buyer's own storefront socket).
|
|
197
|
+
*/
|
|
198
|
+
CUSTOMER_WHOLESALE_REQUEST = "CUSTOMER_WHOLESALE_REQUEST"
|
|
181
199
|
}
|
|
182
200
|
/**
|
|
183
201
|
* The notification types a CUSTOMER bell can carry today; each links through
|
|
@@ -290,6 +308,11 @@ declare global {
|
|
|
290
308
|
* POINTER, not an audience — see {@link PendingCustomerRef}.
|
|
291
309
|
*/
|
|
292
310
|
pendingCustomerRef?: PendingCustomerRef;
|
|
311
|
+
/**
|
|
312
|
+
* `CUSTOMER_WHOLESALE_REQUEST` only: which request to open. A POINTER, not
|
|
313
|
+
* an audience — see {@link WholesaleRequestRef}.
|
|
314
|
+
*/
|
|
315
|
+
wholesaleRequestRef?: WholesaleRequestRef;
|
|
293
316
|
/** `MENTION` only: the colleague who wrote the mention (a snapshot). */
|
|
294
317
|
actor?: NotificationActor;
|
|
295
318
|
userId?: string;
|
|
@@ -353,7 +376,7 @@ declare global {
|
|
|
353
376
|
* customer socket carry — the stored row minus the operator-only pointers.
|
|
354
377
|
* A union keyed on `type`; `ORDER` is its only member today.
|
|
355
378
|
*/
|
|
356
|
-
type CustomerNotification = Omit<NotificationInterface, 'cartRef' | 'pendingCustomerRef' | 'TableName' | 'type' | 'orderId'> & {
|
|
379
|
+
type CustomerNotification = Omit<NotificationInterface, 'cartRef' | 'pendingCustomerRef' | 'wholesaleRequestRef' | 'TableName' | 'type' | 'orderId'> & {
|
|
357
380
|
type: NotificationTypeEnum.ORDER;
|
|
358
381
|
/** Set on every customer row: the order the bell is about. */
|
|
359
382
|
orderId: string;
|
package/dist/notification.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// legacy opt-ins and explicit channel maps remain source data on USER rows.
|
|
4
4
|
// Addressed ERROR / ML_FACTURADOR_COLLISION / ML_SELLER_SWITCHED /
|
|
5
5
|
// ML_CONNECTION_BROKEN / MENTION / CUSTOMER_PENDING_APPROVAL /
|
|
6
|
-
// PAYMENT_ATTEMPT_RESOLUTION do not select a producer subscriber audience;
|
|
6
|
+
// PAYMENT_ATTEMPT_RESOLUTION / CUSTOMER_WHOLESALE_REQUEST do not select a producer subscriber audience;
|
|
7
7
|
// the consumer still reads the live USER row for each delivery channel.
|
|
8
8
|
// AFIP_CERT_EXPIRY is seeded on the founding user and uses the subscriber index.
|
|
9
9
|
// Exported as a real enum consumed by api and app; adding an indexed type
|
|
@@ -196,6 +196,24 @@ export var NotificationTypeEnum;
|
|
|
196
196
|
* re-opens rings again.
|
|
197
197
|
*/
|
|
198
198
|
NotificationTypeEnum["PAYMENT_ATTEMPT_RESOLUTION"] = "PAYMENT_ATTEMPT_RESOLUTION";
|
|
199
|
+
/**
|
|
200
|
+
* A signed-in storefront buyer asked for a wholesale account
|
|
201
|
+
* (`POST /user { mode: 'wholesale-request' }`) and the request waits in the
|
|
202
|
+
* operator queue (`GET /customers?mode=wholesale-requests`). Names the buyer
|
|
203
|
+
* by name only — never their email or CUIT.
|
|
204
|
+
*
|
|
205
|
+
* ⚠️ ADMIN-ONLY, and its own preference key. The producer fans out to every
|
|
206
|
+
* non-disabled ADMIN of the store (`listActiveAdmins`) — the tier that reads
|
|
207
|
+
* the queue and decides it — so a client offers the
|
|
208
|
+
* `CUSTOMER_WHOLESALE_REQUEST` toggle to ADMINs only. No legacy opt-in scan:
|
|
209
|
+
* the consumer enforces explicit type/channel preferences
|
|
210
|
+
* (`notificationPreferences.CUSTOMER_WHOLESALE_REQUEST`), default-on when
|
|
211
|
+
* absent. Additive — no backfill or consumer migration owed.
|
|
212
|
+
*
|
|
213
|
+
* ⚠️ Carries `wholesaleRequestRef` and NEVER a top-level `customerId` (an
|
|
214
|
+
* audience — the bell would reach the buyer's own storefront socket).
|
|
215
|
+
*/
|
|
216
|
+
NotificationTypeEnum["CUSTOMER_WHOLESALE_REQUEST"] = "CUSTOMER_WHOLESALE_REQUEST";
|
|
199
217
|
})(NotificationTypeEnum || (NotificationTypeEnum = {}));
|
|
200
218
|
/**
|
|
201
219
|
* The notification types a CUSTOMER bell can carry today; each links through
|
package/dist/socket.d.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
* A client must ignore actions it does not own rather than assume it
|
|
27
27
|
* receives all of them.
|
|
28
28
|
*/
|
|
29
|
-
export declare const SOCKET_ACTIONS: readonly ["account", "appointments", "appointmentWaitlist", "attendance", "attributes", "automation-rules", "baskets", "cart", "brands", "cash", "catalogOperation", "categories", "campaigns", "customers", "customerNote", "customerAddress", "customerContact", "deliveries", "globals", "inventory", "invoices", "literals", "loyalty", "orders", "presupuestos", "products", "promotions", "segments", "returns", "rma", "templates", "services", "service-templates", "shifts", "stores", "suppliers", "supplier-invoice", "support", "surveys", "users", "warehouses", "zones", "favorites", "webhooks", "account-delete", "baskets-delete", "cart-delete", "log-delete", "notifications", "support-message", "payment_received", "mp_payment_status_changed", "payment_linked", "payment_unlinked", "mercadopago", "mercadopago_dynamic_qr_created", "mercadopago_static_qr_created", "mp_hook_log_appended", "mp_ipn_log_appended", "orderPaymentAttempt", "orderDelivery", "paymentAttemptChanged", "stripe", "subscription", "caea", "print", "print-order", "print-invoice", "print-product", "print-tag", "print_job_failed", "request_printers", "printers_changed", "agent_status_changed", "print_rules_changed", "printers_active", "agent_command", "maintenance", "currency_auto_updated", "drain_progress", "integration_event_appended", "userActivityRecorded", "mercadolibre_settlement_period", "customDomain", "purchaseOrders", "cycleCounts", "stockTransfers", "workItems"];
|
|
29
|
+
export declare const SOCKET_ACTIONS: readonly ["account", "appointments", "appointmentWaitlist", "attendance", "attributes", "automation-rules", "baskets", "cart", "brands", "cash", "catalogOperation", "categories", "campaigns", "customers", "customerNote", "customerAddress", "customerContact", "deliveries", "globals", "inventory", "invoices", "literals", "loyalty", "orders", "presupuestos", "products", "promotions", "segments", "returns", "rma", "templates", "services", "service-templates", "shifts", "stores", "suppliers", "supplier-invoice", "support", "surveys", "users", "warehouses", "zones", "favorites", "webhooks", "account-delete", "baskets-delete", "cart-delete", "log-delete", "notifications", "support-message", "payment_received", "mp_payment_status_changed", "payment_linked", "payment_unlinked", "mercadopago", "mercadopago_dynamic_qr_created", "mercadopago_static_qr_created", "mp_hook_log_appended", "mp_ipn_log_appended", "orderPaymentAttempt", "orderDelivery", "paymentAttemptChanged", "stripe", "subscription", "caea", "print", "print-order", "print-invoice", "print-product", "print-tag", "print_job_failed", "request_printers", "printers_changed", "agent_status_changed", "print_rules_changed", "printers_active", "agent_command", "maintenance", "currency_auto_updated", "drain_progress", "integration_event_appended", "userActivityRecorded", "mercadolibre_settlement_period", "customDomain", "purchaseOrders", "cycleCounts", "stockTransfers", "workItems", "wholesaleRequests"];
|
|
30
30
|
/** Union of every server→client data-frame action. */
|
|
31
31
|
export type SocketAction = (typeof SOCKET_ACTIONS)[number];
|
|
32
32
|
/**
|
package/dist/socket.js
CHANGED
|
@@ -688,6 +688,17 @@ export const SOCKET_ACTIONS = [
|
|
|
688
688
|
* where the role and scope gates sit.
|
|
689
689
|
*/
|
|
690
690
|
'workItems',
|
|
691
|
+
/**
|
|
692
|
+
* BE → the store's ADMIN-TIER operator sockets when a buyer's wholesale
|
|
693
|
+
* request is submitted or decided. Carries the `WholesaleRequest` row as it
|
|
694
|
+
* now stands (company data included), so the queue can patch in place.
|
|
695
|
+
*
|
|
696
|
+
* ⚠️ Admin tier only — `wsPostStore(..., { audience: 'operator', lowTierData:
|
|
697
|
+
* null })`: the queue is an admin-tier read, so a USER or PRINTER socket
|
|
698
|
+
* receives nothing. The buyer follows their own request on the `'customers'`
|
|
699
|
+
* action instead (`WholesaleRequestStatusFrame`).
|
|
700
|
+
*/
|
|
701
|
+
'wholesaleRequests',
|
|
691
702
|
];
|
|
692
703
|
/** Runtime guard — narrows an untrusted string to a known action. */
|
|
693
704
|
export const isSocketAction = (value) => typeof value === 'string' && SOCKET_ACTIONS.includes(value);
|
package/dist/store.d.ts
CHANGED
|
@@ -146,6 +146,18 @@ declare global {
|
|
|
146
146
|
* frame), so the storefront can say the account needs approval.
|
|
147
147
|
*/
|
|
148
148
|
registrationMode?: 'open' | 'approval';
|
|
149
|
+
/**
|
|
150
|
+
* Whether a signed-in storefront buyer may ask for a wholesale account
|
|
151
|
+
* (`POST /user { mode: 'wholesale-request' }`). Absent or `false` = off:
|
|
152
|
+
* the request route refuses `403 WHOLESALE_REQUESTS_DISABLED`. Explicit on
|
|
153
|
+
* purpose — a store may hold wholesale-class lists for customers it assigns
|
|
154
|
+
* itself without inviting requests. Turning it on requires at least one
|
|
155
|
+
* `audienceClass: 'wholesale'` list (`409 WHOLESALE_LIST_REQUIRED`).
|
|
156
|
+
* Written by `PATCH /store` (ADMIN) and `PUT /platform/stores/{storeId}`
|
|
157
|
+
* (MANAGER). SENT TO SHOPPERS (web `GET /store` and the customer `stores`
|
|
158
|
+
* frame), so the storefront can offer the request.
|
|
159
|
+
*/
|
|
160
|
+
wholesaleRequests?: boolean;
|
|
149
161
|
/**
|
|
150
162
|
* How long a storefront online payment holds the order, in whole
|
|
151
163
|
* minutes: the payment attempt's `expiresAt` and its Checkout Pro
|
|
@@ -198,9 +210,11 @@ declare global {
|
|
|
198
210
|
/** Sparse tenant PATCH shape; live null values reset to registry defaults and are never stored.
|
|
199
211
|
* An unknown newCustomerPriceList refuses 400 PRICE_LIST_NOT_FOUND with
|
|
200
212
|
* data: { field: 'ecommerce.config.newCustomerPriceList', value: number }; nothing written.
|
|
213
|
+
* `wholesaleRequests: true` on a store with no `audienceClass: 'wholesale'` list
|
|
214
|
+
* refuses 409 WHOLESALE_LIST_REQUIRED; nothing written.
|
|
201
215
|
*/
|
|
202
216
|
interface EcommercePatchInput {
|
|
203
|
-
config?: Pick<NonNullable<Ecommerce['config']>, 'showDefaultPriceList' | 'newCustomerPriceList' | 'registrationMode' | 'paymentHoldMinutes'> & StorefrontLiveSettingsPatch;
|
|
217
|
+
config?: Pick<NonNullable<Ecommerce['config']>, 'showDefaultPriceList' | 'newCustomerPriceList' | 'registrationMode' | 'wholesaleRequests' | 'paymentHoldMinutes'> & StorefrontLiveSettingsPatch;
|
|
204
218
|
sidebar?: Ecommerce['sidebar'];
|
|
205
219
|
home?: Omit<NonNullable<Ecommerce['home']>, 'displayOrders' | 'displayProfile'>;
|
|
206
220
|
themeColors?: Ecommerce['themeColors'];
|
|
@@ -2398,6 +2412,13 @@ export type StoreModuleRefusalBody = {
|
|
|
2398
2412
|
* entry naming no list of the store, with `data: { field, value }` where
|
|
2399
2413
|
* `field` is `<catalog>[<row>].availability.priceLists[<entry>]`.
|
|
2400
2414
|
*
|
|
2415
|
+
* Request refusal, `409`:
|
|
2416
|
+
* - `WHOLESALE_LIST_REQUIRED` — an `ecommerce` save turns
|
|
2417
|
+
* `config.wholesaleRequests` on while the store holds no
|
|
2418
|
+
* `audienceClass: 'wholesale'` list (`store/_ecommercePatch.ts`), with
|
|
2419
|
+
* `data: { field: 'ecommerce.config.wholesaleRequests' }`. The platform PUT
|
|
2420
|
+
* answers it too.
|
|
2421
|
+
*
|
|
2401
2422
|
* Maintenance mode (`{ mode: 'maintenance', level }`, `store/_patchMaintenance.ts`):
|
|
2402
2423
|
* - `FORBIDDEN` (403) — the caller is neither an ADMIN nor a MANAGER.
|
|
2403
2424
|
* - `STORE_NOT_FOUND` (404) — the store row is gone.
|
|
@@ -2411,5 +2432,5 @@ export type StoreModuleRefusalBody = {
|
|
|
2411
2432
|
* checks, the display-currency lock, the storefront fence) are not listed
|
|
2412
2433
|
* either. Cross-cutting refusals: check `GATING_ERROR_CODES` as well.
|
|
2413
2434
|
*/
|
|
2414
|
-
export declare const STORE_PATCH_ERROR_CODES: readonly ["STORE_CONFIG_FORBIDDEN", "STORE_ECOMMERCE_FORBIDDEN", "STORE_FISCAL_FIELD_FORBIDDEN", "STORE_JURISDICTION_FORBIDDEN", "AFIP_RELACION_FLAG_FORBIDDEN", "FX_TABLE_FORBIDDEN", "FX_AUTO_UPDATE_FORBIDDEN", "FIELD_NOT_WRITABLE_HERE", "AFIP_POINT_OF_SALE_NOT_CLEARABLE", "CURRENCY_CATALOG_ID_UNKNOWN", "ARS_RATE_MUST_BE_ONE", "CURRENCY_RATE_ABOVE_CEILING", "FX_CATALOG_ID_UNKNOWN", "FX_SOURCE_ID_UNKNOWN", "DISPLAY_CURRENCY_NOT_INVOICEABLE", "PRODUCT_CURRENCY_NOT_INVOICEABLE", "PRICE_LIST_NOT_FOUND", "FORBIDDEN", "STORE_NOT_FOUND"];
|
|
2435
|
+
export declare const STORE_PATCH_ERROR_CODES: readonly ["STORE_CONFIG_FORBIDDEN", "STORE_ECOMMERCE_FORBIDDEN", "STORE_FISCAL_FIELD_FORBIDDEN", "STORE_JURISDICTION_FORBIDDEN", "AFIP_RELACION_FLAG_FORBIDDEN", "FX_TABLE_FORBIDDEN", "FX_AUTO_UPDATE_FORBIDDEN", "FIELD_NOT_WRITABLE_HERE", "AFIP_POINT_OF_SALE_NOT_CLEARABLE", "CURRENCY_CATALOG_ID_UNKNOWN", "ARS_RATE_MUST_BE_ONE", "CURRENCY_RATE_ABOVE_CEILING", "FX_CATALOG_ID_UNKNOWN", "FX_SOURCE_ID_UNKNOWN", "DISPLAY_CURRENCY_NOT_INVOICEABLE", "PRODUCT_CURRENCY_NOT_INVOICEABLE", "PRICE_LIST_NOT_FOUND", "WHOLESALE_LIST_REQUIRED", "FORBIDDEN", "STORE_NOT_FOUND"];
|
|
2415
2436
|
export type StorePatchErrorCode = (typeof STORE_PATCH_ERROR_CODES)[number];
|
package/dist/store.js
CHANGED
|
@@ -134,6 +134,13 @@ export const STORE_MODULE_ERROR_CODES = [
|
|
|
134
134
|
* entry naming no list of the store, with `data: { field, value }` where
|
|
135
135
|
* `field` is `<catalog>[<row>].availability.priceLists[<entry>]`.
|
|
136
136
|
*
|
|
137
|
+
* Request refusal, `409`:
|
|
138
|
+
* - `WHOLESALE_LIST_REQUIRED` — an `ecommerce` save turns
|
|
139
|
+
* `config.wholesaleRequests` on while the store holds no
|
|
140
|
+
* `audienceClass: 'wholesale'` list (`store/_ecommercePatch.ts`), with
|
|
141
|
+
* `data: { field: 'ecommerce.config.wholesaleRequests' }`. The platform PUT
|
|
142
|
+
* answers it too.
|
|
143
|
+
*
|
|
137
144
|
* Maintenance mode (`{ mode: 'maintenance', level }`, `store/_patchMaintenance.ts`):
|
|
138
145
|
* - `FORBIDDEN` (403) — the caller is neither an ADMIN nor a MANAGER.
|
|
139
146
|
* - `STORE_NOT_FOUND` (404) — the store row is gone.
|
|
@@ -165,6 +172,7 @@ export const STORE_PATCH_ERROR_CODES = [
|
|
|
165
172
|
'DISPLAY_CURRENCY_NOT_INVOICEABLE',
|
|
166
173
|
'PRODUCT_CURRENCY_NOT_INVOICEABLE',
|
|
167
174
|
'PRICE_LIST_NOT_FOUND',
|
|
175
|
+
'WHOLESALE_LIST_REQUIRED',
|
|
168
176
|
'FORBIDDEN',
|
|
169
177
|
'STORE_NOT_FOUND',
|
|
170
178
|
];
|
|
@@ -13,6 +13,9 @@
|
|
|
13
13
|
* - keys whose storefront value is not a plain `{name}`-token sentence (it
|
|
14
14
|
* branches, computes, or is not a string), which a shop string cannot
|
|
15
15
|
* express.
|
|
16
|
+
* Route paths (the former `NAVIGATE_*` keys) are not copy at all: they live in
|
|
17
|
+
* the storefront's own config, and the api refuses and drops a `NAVIGATE_*`
|
|
18
|
+
* literal on every storefront surface.
|
|
16
19
|
* An editor that lists any of those offers an edit the server refuses.
|
|
17
20
|
*
|
|
18
21
|
* Interpolated values use `{name}` tokens named after the storefront's own
|
|
@@ -21,16 +24,9 @@
|
|
|
21
24
|
export declare const STOREFRONT_WEB_LITERAL_KEYS: readonly string[];
|
|
22
25
|
/** Whether a shop may override `key` — membership in {@link STOREFRONT_WEB_LITERAL_KEYS}. */
|
|
23
26
|
export declare const isStorefrontLiteralKey: (key: string) => boolean;
|
|
24
|
-
/**
|
|
25
|
-
* Whether `value` may be stored, published or served for `key`. A `NAVIGATE_*` key is
|
|
26
|
-
* rendered as a link, so it must be a same-origin path — otherwise an override could
|
|
27
|
-
* send a shop's customers to an off-site look-alike login page. Every other key is
|
|
28
|
-
* unconstrained here (key membership, well-formedness and length are checked apart).
|
|
29
|
-
*/
|
|
30
|
-
export declare const isAllowedStorefrontLiteralValue: (key: string, value: string) => boolean;
|
|
31
27
|
/**
|
|
32
28
|
* Whether a shop's stored `value` for `key` is one a save keeps, a read serves and the editor
|
|
33
|
-
* baseline adopts: a shop-overridable key
|
|
34
|
-
* {@link STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS} code points
|
|
29
|
+
* baseline adopts: a shop-overridable key and a well-formed string within
|
|
30
|
+
* {@link STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS} code points.
|
|
35
31
|
*/
|
|
36
32
|
export declare const isAdoptableStorefrontLiteral: (key: string, value: unknown) => value is string;
|
|
@@ -14,6 +14,9 @@ import { STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS } from "./storefrontDesignReg
|
|
|
14
14
|
* - keys whose storefront value is not a plain `{name}`-token sentence (it
|
|
15
15
|
* branches, computes, or is not a string), which a shop string cannot
|
|
16
16
|
* express.
|
|
17
|
+
* Route paths (the former `NAVIGATE_*` keys) are not copy at all: they live in
|
|
18
|
+
* the storefront's own config, and the api refuses and drops a `NAVIGATE_*`
|
|
19
|
+
* literal on every storefront surface.
|
|
17
20
|
* An editor that lists any of those offers an edit the server refuses.
|
|
18
21
|
*
|
|
19
22
|
* Interpolated values use `{name}` tokens named after the storefront's own
|
|
@@ -489,6 +492,8 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
489
492
|
'DATA_RIGHTS_COLLECTION_NOTES',
|
|
490
493
|
'DATA_RIGHTS_COLLECTION_ORDERS',
|
|
491
494
|
'DATA_RIGHTS_COLLECTION_PAYMENTS',
|
|
495
|
+
'DATA_RIGHTS_COLLECTION_RECENT_PURCHASES',
|
|
496
|
+
'DATA_RIGHTS_COLLECTION_WHOLESALE_REQUESTS',
|
|
492
497
|
'DATA_RIGHTS_DIALOG_ACK',
|
|
493
498
|
'DATA_RIGHTS_DIALOG_CONFIRM',
|
|
494
499
|
'DATA_RIGHTS_DIALOG_FISCAL',
|
|
@@ -812,27 +817,6 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
812
817
|
'MOBILE_BUTTONS_FAVORITES',
|
|
813
818
|
'MOBILE_BUTTONS_INVOICES',
|
|
814
819
|
'MOBILE_BUTTONS_ORDERS',
|
|
815
|
-
'NAVIGATE_APPOINTMENTS',
|
|
816
|
-
'NAVIGATE_BASKET',
|
|
817
|
-
'NAVIGATE_BRANDS',
|
|
818
|
-
'NAVIGATE_CATEGORIES',
|
|
819
|
-
'NAVIGATE_CHECKOUT',
|
|
820
|
-
'NAVIGATE_FAVORITES',
|
|
821
|
-
'NAVIGATE_HOME',
|
|
822
|
-
'NAVIGATE_INCOMES',
|
|
823
|
-
'NAVIGATE_INVOICES',
|
|
824
|
-
'NAVIGATE_LOGIN',
|
|
825
|
-
'NAVIGATE_LOGOUT',
|
|
826
|
-
'NAVIGATE_MAINTENANCE',
|
|
827
|
-
'NAVIGATE_NEW_ARRIVALS',
|
|
828
|
-
'NAVIGATE_NOTIFICATIONS',
|
|
829
|
-
'NAVIGATE_ORDERS',
|
|
830
|
-
'NAVIGATE_PAYMENTS',
|
|
831
|
-
'NAVIGATE_PROFILE',
|
|
832
|
-
'NAVIGATE_RECOVER',
|
|
833
|
-
'NAVIGATE_REGISTRATION',
|
|
834
|
-
'NAVIGATE_SALE',
|
|
835
|
-
'NAVIGATE_SESSIONS',
|
|
836
820
|
'NETWORK_OFFLINE',
|
|
837
821
|
'NETWORK_REQUEST_FAILED',
|
|
838
822
|
'NETWORK_TIMEOUT',
|
|
@@ -1075,6 +1059,7 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
1075
1059
|
'PREVIEW_LOADING',
|
|
1076
1060
|
'PREVIEW_READ_FAILED',
|
|
1077
1061
|
'PREVIEW_READ_ONLY',
|
|
1062
|
+
'PREVIEW_RECENTLY_BOUGHT',
|
|
1078
1063
|
'PREVIEW_RESTART',
|
|
1079
1064
|
'PREVIEW_RETRY',
|
|
1080
1065
|
'PREVIEW_TITLE',
|
|
@@ -1343,6 +1328,7 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
1343
1328
|
'STOREFRONT_FOOTER_NAV_ARIA',
|
|
1344
1329
|
'STOREFRONT_MERCHANT_NAV_ARIA',
|
|
1345
1330
|
'STOREFRONT_NAV_GROUP_FALLBACK',
|
|
1331
|
+
'STOREFRONT_PRODUCTS_REFRESH_FAILED',
|
|
1346
1332
|
'STOREFRONT_SEARCH',
|
|
1347
1333
|
'STOREFRONT_SOCIAL_FACEBOOK',
|
|
1348
1334
|
'STOREFRONT_SOCIAL_INSTAGRAM',
|
|
@@ -1370,32 +1356,12 @@ export const STOREFRONT_WEB_LITERAL_KEYS = Object.freeze([
|
|
|
1370
1356
|
const STOREFRONT_WEB_LITERAL_KEY_SET = new Set(STOREFRONT_WEB_LITERAL_KEYS);
|
|
1371
1357
|
/** Whether a shop may override `key` — membership in {@link STOREFRONT_WEB_LITERAL_KEYS}. */
|
|
1372
1358
|
export const isStorefrontLiteralKey = (key) => STOREFRONT_WEB_LITERAL_KEY_SET.has(key);
|
|
1373
|
-
/** Keys whose value is a storefront route the storefront renders as a link target. */
|
|
1374
|
-
const STOREFRONT_NAVIGATE_LITERAL_PREFIX = 'NAVIGATE_';
|
|
1375
|
-
/**
|
|
1376
|
-
* A same-origin path: a leading `/`, a second character that is neither `/` nor `\`
|
|
1377
|
-
* (`//host` and `/\host` are protocol-relative to a browser), and no whitespace,
|
|
1378
|
-
* backslash, control character (`\p{Cc}`) or invisible format character (`\p{Cf}`: every
|
|
1379
|
-
* bidi mark including U+061C, zero-width characters, the soft hyphen, tag characters)
|
|
1380
|
-
* anywhere. Browsers strip tab and newline before parsing, so `/\t/host` becomes `//host`;
|
|
1381
|
-
* invisible and directional characters can also disguise the link target. A scheme
|
|
1382
|
-
* cannot start with `/`.
|
|
1383
|
-
*/
|
|
1384
|
-
const STOREFRONT_SAME_ORIGIN_PATH_RE = /^\/(?![/\\])[^\s\\\p{Cc}\p{Cf}]*$/u;
|
|
1385
|
-
/**
|
|
1386
|
-
* Whether `value` may be stored, published or served for `key`. A `NAVIGATE_*` key is
|
|
1387
|
-
* rendered as a link, so it must be a same-origin path — otherwise an override could
|
|
1388
|
-
* send a shop's customers to an off-site look-alike login page. Every other key is
|
|
1389
|
-
* unconstrained here (key membership, well-formedness and length are checked apart).
|
|
1390
|
-
*/
|
|
1391
|
-
export const isAllowedStorefrontLiteralValue = (key, value) => !key.startsWith(STOREFRONT_NAVIGATE_LITERAL_PREFIX) || STOREFRONT_SAME_ORIGIN_PATH_RE.test(value);
|
|
1392
1359
|
/**
|
|
1393
1360
|
* Whether a shop's stored `value` for `key` is one a save keeps, a read serves and the editor
|
|
1394
|
-
* baseline adopts: a shop-overridable key
|
|
1395
|
-
* {@link STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS} code points
|
|
1361
|
+
* baseline adopts: a shop-overridable key and a well-formed string within
|
|
1362
|
+
* {@link STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS} code points.
|
|
1396
1363
|
*/
|
|
1397
1364
|
export const isAdoptableStorefrontLiteral = (key, value) => isStorefrontLiteralKey(key) &&
|
|
1398
1365
|
typeof value === 'string' &&
|
|
1399
1366
|
value.isWellFormed() &&
|
|
1400
|
-
Array.from(value).length <= STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS
|
|
1401
|
-
isAllowedStorefrontLiteralValue(key, value);
|
|
1367
|
+
Array.from(value).length <= STOREFRONT_DESIGN_LITERAL_MAX_CODE_POINTS;
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A signed-in storefront buyer's request for a wholesale account, and the
|
|
3
|
+
* operator queue that decides it.
|
|
4
|
+
*
|
|
5
|
+
* One row per customer (`WHOLESALEREQ#{storeId}` / `{customerId}`). A new
|
|
6
|
+
* request overwrites the previous one only once that one is decided, so a
|
|
7
|
+
* customer holds at most one pending request.
|
|
8
|
+
*
|
|
9
|
+
* The flow is offered only while the store's
|
|
10
|
+
* `ecommerce.config.wholesaleRequests` is `true` (absent = off).
|
|
11
|
+
*/
|
|
12
|
+
/** The three states a request moves through. */
|
|
13
|
+
export declare const WHOLESALE_REQUEST_STATES: readonly ["pending", "approved", "declined"];
|
|
14
|
+
/** `GET /customers?mode=wholesale-requests` page ceiling; the default page is 50. */
|
|
15
|
+
export declare const WHOLESALE_REQUESTS_PAGE_MAX = 100;
|
|
16
|
+
/** Character ceilings on the buyer's free text, counted after trimming. */
|
|
17
|
+
export declare const WHOLESALE_REQUEST_RAZON_SOCIAL_MAX = 200;
|
|
18
|
+
export declare const WHOLESALE_REQUEST_ACTIVITY_MAX = 120;
|
|
19
|
+
export declare const WHOLESALE_REQUEST_MESSAGE_MAX = 1000;
|
|
20
|
+
export declare const WHOLESALE_REQUEST_PHONE_MAX = 30;
|
|
21
|
+
/** Character ceiling on the operator's decline reason, counted after trimming. */
|
|
22
|
+
export declare const WHOLESALE_REQUEST_REASON_MAX = 500;
|
|
23
|
+
/**
|
|
24
|
+
* Every `error` code the wholesale-request flow answers on its own behalf:
|
|
25
|
+
* - web `POST /user { mode: 'wholesale-request' }` (`stacks/web/lambdas/user/_wholesaleRequest.ts`):
|
|
26
|
+
* `WHOLESALE_REQUESTS_DISABLED`, `CUSTOMER_NOT_FOUND`, `CUSTOMER_ANONYMIZED`, `USER_NOT_ENABLED`,
|
|
27
|
+
* `CUSTOMER_ALREADY_WHOLESALE`, `WHOLESALE_REQUEST_CONTENDED`, `WHOLESALE_REQUEST_PENDING`;
|
|
28
|
+
* - app `GET /customers?mode=wholesale-requests` and `POST /customers { mode: 'wholesale-decision' }`
|
|
29
|
+
* (`stacks/lambdas/customers/_wholesaleRequests.ts`): `ROLE_FORBIDDEN`, `ADMIN_REQUIRED`,
|
|
30
|
+
* `INVALID_CURSOR`, `WHOLESALE_REQUEST_NOT_PENDING`, `WHOLESALE_DECISION_CONTENDED`,
|
|
31
|
+
* `CUSTOMER_NOT_FOUND`, `CUSTOMER_ANONYMIZED`, `CUSTOMER_MERGED`, `PRICE_LIST_NOT_FOUND`,
|
|
32
|
+
* `PRICE_LIST_NOT_WHOLESALE`, `WHOLESALE_REQUEST_NOT_FOUND`;
|
|
33
|
+
* - the opt-in refusal both configuration writers answer when `ecommerce.config.wholesaleRequests`
|
|
34
|
+
* turns on with no wholesale-class list: `WHOLESALE_LIST_REQUIRED` (`409`). It is also in
|
|
35
|
+
* `STORE_PATCH_ERROR_CODES`, on purpose: a wholesale client handles it without reading that tuple.
|
|
36
|
+
*
|
|
37
|
+
* The rule: a code is listed when a wholesale handler writes it as its own literal, and each of
|
|
38
|
+
* those literals is typed `satisfies WholesaleRequestErrorCode`, so one this list misses fails
|
|
39
|
+
* typecheck. A code those routes forward from a shared helper is not listed, though a wholesale
|
|
40
|
+
* route can answer it: the customer-access refusals (`refuseCustomerAccess`), `VALIDATION_FAILED`
|
|
41
|
+
* (`validateRequest`), the handler wrapper's own codes and the authorizer's.
|
|
42
|
+
*/
|
|
43
|
+
export declare const WHOLESALE_REQUEST_ERROR_CODES: readonly ["WHOLESALE_REQUESTS_DISABLED", "CUSTOMER_NOT_FOUND", "CUSTOMER_ANONYMIZED", "USER_NOT_ENABLED", "CUSTOMER_ALREADY_WHOLESALE", "WHOLESALE_REQUEST_CONTENDED", "WHOLESALE_REQUEST_PENDING", "ROLE_FORBIDDEN", "ADMIN_REQUIRED", "INVALID_CURSOR", "WHOLESALE_REQUEST_NOT_PENDING", "WHOLESALE_DECISION_CONTENDED", "CUSTOMER_MERGED", "PRICE_LIST_NOT_FOUND", "PRICE_LIST_NOT_WHOLESALE", "WHOLESALE_REQUEST_NOT_FOUND", "WHOLESALE_LIST_REQUIRED"];
|
|
44
|
+
export type WholesaleRequestErrorCode = (typeof WHOLESALE_REQUEST_ERROR_CODES)[number];
|
|
45
|
+
declare global {
|
|
46
|
+
type WholesaleRequestState = (typeof WHOLESALE_REQUEST_STATES)[number];
|
|
47
|
+
/**
|
|
48
|
+
* The company data a buyer submits. `cuit`, `razonSocial` and `condFiscal`
|
|
49
|
+
* are required; the rest is optional.
|
|
50
|
+
*
|
|
51
|
+
* ⚠️ PII under Ley 25.326. It reaches the operator queue and the buyer's own
|
|
52
|
+
* data export, and never a bell, a log line or an ERROR row.
|
|
53
|
+
*/
|
|
54
|
+
interface WholesaleRequestCompany {
|
|
55
|
+
/** 11 digits with a valid check digit, stored digits-only. */
|
|
56
|
+
cuit: string;
|
|
57
|
+
razonSocial: string;
|
|
58
|
+
/** A `condFiscal` code from `FISCAL_CONDITIONS`. */
|
|
59
|
+
condFiscal: number;
|
|
60
|
+
activity?: string;
|
|
61
|
+
phone?: string;
|
|
62
|
+
message?: string;
|
|
63
|
+
}
|
|
64
|
+
/** Web `POST /user` body that submits a request. */
|
|
65
|
+
interface WholesaleRequestInput extends WholesaleRequestCompany {
|
|
66
|
+
mode: 'wholesale-request';
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* What the BUYER sees of their request: web `GET /user?mode=wholesale-request`
|
|
70
|
+
* (`data`, or `data: null` when they never asked), the submit's answer and
|
|
71
|
+
* the status frame. Never the decline reason, the deciding operator or the
|
|
72
|
+
* list it moved them to.
|
|
73
|
+
*/
|
|
74
|
+
interface WholesaleRequestStatus {
|
|
75
|
+
status: WholesaleRequestState;
|
|
76
|
+
/** Unix ms the request was submitted. */
|
|
77
|
+
createdAt: number;
|
|
78
|
+
/** Unix ms of the decision. Absent while pending. */
|
|
79
|
+
decidedAt?: number;
|
|
80
|
+
}
|
|
81
|
+
/** The operator's view of one request (admin tier only). */
|
|
82
|
+
interface WholesaleRequest extends WholesaleRequestCompany {
|
|
83
|
+
customerId: string;
|
|
84
|
+
status: WholesaleRequestState;
|
|
85
|
+
createdAt: number;
|
|
86
|
+
updatedAt?: number;
|
|
87
|
+
decidedAt?: number;
|
|
88
|
+
/** The deciding operator's user id. */
|
|
89
|
+
decidedBy?: string;
|
|
90
|
+
/** On approval: the wholesale-class list the customer was moved to. */
|
|
91
|
+
priceList?: number;
|
|
92
|
+
/** On decline: the operator's note. Operator-only, never sent to the buyer. */
|
|
93
|
+
reason?: string;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Where the requesting customer stands now, read off the CUSTOMER row when
|
|
97
|
+
* the queue page is served. A request whose customer is gone is listed with
|
|
98
|
+
* that state, never dropped.
|
|
99
|
+
*/
|
|
100
|
+
type WholesaleRequestCustomerState = 'active' | 'disabled' | 'missing' | 'anonymized' | 'merged';
|
|
101
|
+
/** One `GET /customers?mode=wholesale-requests` item. */
|
|
102
|
+
interface WholesaleRequestQueueEntry extends WholesaleRequest {
|
|
103
|
+
customer: {
|
|
104
|
+
state: WholesaleRequestCustomerState;
|
|
105
|
+
fullName?: string;
|
|
106
|
+
/** The customer's CURRENT stored list. */
|
|
107
|
+
priceList?: number;
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* `GET /customers?mode=wholesale-requests` answer. `LastEvaluatedKey` is an
|
|
112
|
+
* opaque cursor, sent back as `?startKey=`; present (with `truncated: true`)
|
|
113
|
+
* only while more rows may follow.
|
|
114
|
+
*/
|
|
115
|
+
interface WholesaleRequestsQueueResponse {
|
|
116
|
+
message: string;
|
|
117
|
+
data: WholesaleRequestQueueEntry[];
|
|
118
|
+
LastEvaluatedKey?: string;
|
|
119
|
+
truncated?: boolean;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* App `POST /customers` body that decides a request. `'approved'` requires a
|
|
123
|
+
* wholesale-class `priceList` and refuses a `reason`; `'declined'` refuses a
|
|
124
|
+
* `priceList`. `createdAt` is the request's own, as the queue served it: a
|
|
125
|
+
* request declined and resubmitted since is a different request, refused
|
|
126
|
+
* `409 WHOLESALE_REQUEST_NOT_PENDING` rather than decided unseen.
|
|
127
|
+
*/
|
|
128
|
+
type WholesaleDecisionInput = {
|
|
129
|
+
mode: 'wholesale-decision';
|
|
130
|
+
customerId: string;
|
|
131
|
+
createdAt: number;
|
|
132
|
+
decision: 'approved';
|
|
133
|
+
priceList: number;
|
|
134
|
+
} | {
|
|
135
|
+
mode: 'wholesale-decision';
|
|
136
|
+
customerId: string;
|
|
137
|
+
createdAt: number;
|
|
138
|
+
decision: 'declined';
|
|
139
|
+
reason?: string;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* App `POST /customers { mode: 'wholesale-decision' }` answer: the request as
|
|
143
|
+
* it now stands. `fiscalIdentityApplied` rides an APPROVAL only — `true` when
|
|
144
|
+
* the submitted CUIT, razón social and condición fiscal were written into
|
|
145
|
+
* `Customer.afip`; `false` when they were not, because the customer already
|
|
146
|
+
* held an `afip` entry or a top-level `cuit` naming another taxpayer (then
|
|
147
|
+
* neither `afip` nor `cuit` is written, and the list still moves).
|
|
148
|
+
*/
|
|
149
|
+
interface WholesaleDecisionResponse {
|
|
150
|
+
message: string;
|
|
151
|
+
data: WholesaleRequest;
|
|
152
|
+
fiscalIdentityApplied?: boolean;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The `customers` frame a buyer's own socket receives when their request is
|
|
156
|
+
* decided. Carries no `priceList`: the CUSTOMER row's own `customers` frame
|
|
157
|
+
* carries that on approval.
|
|
158
|
+
*/
|
|
159
|
+
interface WholesaleRequestStatusFrame {
|
|
160
|
+
customerId: string;
|
|
161
|
+
wholesaleRequest: WholesaleRequestStatus;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Which request a `CUSTOMER_WHOLESALE_REQUEST` operator bell is about. A
|
|
165
|
+
* POINTER, never an audience — the top-level `customerId` would deliver the
|
|
166
|
+
* bell to the buyer. Operator surfaces only.
|
|
167
|
+
*/
|
|
168
|
+
interface WholesaleRequestRef {
|
|
169
|
+
customerId: string;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* The request as it appears in the customer's own data export (both
|
|
173
|
+
* surfaces): the company data they submitted and the state, never the
|
|
174
|
+
* decline reason or the deciding operator.
|
|
175
|
+
*/
|
|
176
|
+
interface WholesaleRequestExportEntry extends WholesaleRequestCompany {
|
|
177
|
+
/** The id the request was filed under — the subject's, or a merged-away id folded into it. */
|
|
178
|
+
customerId: string;
|
|
179
|
+
status: WholesaleRequestState;
|
|
180
|
+
createdAt: number;
|
|
181
|
+
decidedAt?: number;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A signed-in storefront buyer's request for a wholesale account, and the
|
|
3
|
+
* operator queue that decides it.
|
|
4
|
+
*
|
|
5
|
+
* One row per customer (`WHOLESALEREQ#{storeId}` / `{customerId}`). A new
|
|
6
|
+
* request overwrites the previous one only once that one is decided, so a
|
|
7
|
+
* customer holds at most one pending request.
|
|
8
|
+
*
|
|
9
|
+
* The flow is offered only while the store's
|
|
10
|
+
* `ecommerce.config.wholesaleRequests` is `true` (absent = off).
|
|
11
|
+
*/
|
|
12
|
+
/** The three states a request moves through. */
|
|
13
|
+
export const WHOLESALE_REQUEST_STATES = ['pending', 'approved', 'declined'];
|
|
14
|
+
/** `GET /customers?mode=wholesale-requests` page ceiling; the default page is 50. */
|
|
15
|
+
export const WHOLESALE_REQUESTS_PAGE_MAX = 100;
|
|
16
|
+
/** Character ceilings on the buyer's free text, counted after trimming. */
|
|
17
|
+
export const WHOLESALE_REQUEST_RAZON_SOCIAL_MAX = 200;
|
|
18
|
+
export const WHOLESALE_REQUEST_ACTIVITY_MAX = 120;
|
|
19
|
+
export const WHOLESALE_REQUEST_MESSAGE_MAX = 1000;
|
|
20
|
+
export const WHOLESALE_REQUEST_PHONE_MAX = 30;
|
|
21
|
+
/** Character ceiling on the operator's decline reason, counted after trimming. */
|
|
22
|
+
export const WHOLESALE_REQUEST_REASON_MAX = 500;
|
|
23
|
+
/**
|
|
24
|
+
* Every `error` code the wholesale-request flow answers on its own behalf:
|
|
25
|
+
* - web `POST /user { mode: 'wholesale-request' }` (`stacks/web/lambdas/user/_wholesaleRequest.ts`):
|
|
26
|
+
* `WHOLESALE_REQUESTS_DISABLED`, `CUSTOMER_NOT_FOUND`, `CUSTOMER_ANONYMIZED`, `USER_NOT_ENABLED`,
|
|
27
|
+
* `CUSTOMER_ALREADY_WHOLESALE`, `WHOLESALE_REQUEST_CONTENDED`, `WHOLESALE_REQUEST_PENDING`;
|
|
28
|
+
* - app `GET /customers?mode=wholesale-requests` and `POST /customers { mode: 'wholesale-decision' }`
|
|
29
|
+
* (`stacks/lambdas/customers/_wholesaleRequests.ts`): `ROLE_FORBIDDEN`, `ADMIN_REQUIRED`,
|
|
30
|
+
* `INVALID_CURSOR`, `WHOLESALE_REQUEST_NOT_PENDING`, `WHOLESALE_DECISION_CONTENDED`,
|
|
31
|
+
* `CUSTOMER_NOT_FOUND`, `CUSTOMER_ANONYMIZED`, `CUSTOMER_MERGED`, `PRICE_LIST_NOT_FOUND`,
|
|
32
|
+
* `PRICE_LIST_NOT_WHOLESALE`, `WHOLESALE_REQUEST_NOT_FOUND`;
|
|
33
|
+
* - the opt-in refusal both configuration writers answer when `ecommerce.config.wholesaleRequests`
|
|
34
|
+
* turns on with no wholesale-class list: `WHOLESALE_LIST_REQUIRED` (`409`). It is also in
|
|
35
|
+
* `STORE_PATCH_ERROR_CODES`, on purpose: a wholesale client handles it without reading that tuple.
|
|
36
|
+
*
|
|
37
|
+
* The rule: a code is listed when a wholesale handler writes it as its own literal, and each of
|
|
38
|
+
* those literals is typed `satisfies WholesaleRequestErrorCode`, so one this list misses fails
|
|
39
|
+
* typecheck. A code those routes forward from a shared helper is not listed, though a wholesale
|
|
40
|
+
* route can answer it: the customer-access refusals (`refuseCustomerAccess`), `VALIDATION_FAILED`
|
|
41
|
+
* (`validateRequest`), the handler wrapper's own codes and the authorizer's.
|
|
42
|
+
*/
|
|
43
|
+
export const WHOLESALE_REQUEST_ERROR_CODES = [
|
|
44
|
+
'WHOLESALE_REQUESTS_DISABLED',
|
|
45
|
+
'CUSTOMER_NOT_FOUND',
|
|
46
|
+
'CUSTOMER_ANONYMIZED',
|
|
47
|
+
'USER_NOT_ENABLED',
|
|
48
|
+
'CUSTOMER_ALREADY_WHOLESALE',
|
|
49
|
+
'WHOLESALE_REQUEST_CONTENDED',
|
|
50
|
+
'WHOLESALE_REQUEST_PENDING',
|
|
51
|
+
'ROLE_FORBIDDEN',
|
|
52
|
+
'ADMIN_REQUIRED',
|
|
53
|
+
'INVALID_CURSOR',
|
|
54
|
+
'WHOLESALE_REQUEST_NOT_PENDING',
|
|
55
|
+
'WHOLESALE_DECISION_CONTENDED',
|
|
56
|
+
'CUSTOMER_MERGED',
|
|
57
|
+
'PRICE_LIST_NOT_FOUND',
|
|
58
|
+
'PRICE_LIST_NOT_WHOLESALE',
|
|
59
|
+
'WHOLESALE_REQUEST_NOT_FOUND',
|
|
60
|
+
'WHOLESALE_LIST_REQUIRED',
|
|
61
|
+
];
|