@sixtyninecommerce/admin 0.10.0 → 0.12.0
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 +5772 -118
- package/dist/auth/index.d.ts +7 -1
- package/dist/auth/index.js +8 -1
- package/dist/catalog/assets.d.ts +120 -8
- package/dist/catalog/assets.js +98 -12
- package/dist/catalog/collections.d.ts +279 -14
- package/dist/catalog/collections.js +246 -15
- package/dist/catalog/documents.d.ts +142 -0
- package/dist/catalog/documents.js +114 -0
- package/dist/catalog/index.d.ts +54 -6
- package/dist/catalog/index.js +10 -1
- package/dist/catalog/metadata.d.ts +244 -0
- package/dist/catalog/metadata.js +220 -0
- package/dist/catalog/products.d.ts +159 -19
- package/dist/catalog/products.js +88 -13
- package/dist/catalog/properties.d.ts +140 -14
- package/dist/catalog/properties.js +65 -1
- package/dist/catalog/recommendations.d.ts +288 -0
- package/dist/catalog/recommendations.js +237 -0
- package/dist/catalog/reviews.d.ts +381 -0
- package/dist/catalog/reviews.js +325 -0
- package/dist/catalog/types.d.ts +17 -1
- package/dist/catalog/types.js +10 -0
- package/dist/catalog/variant-find.d.ts +136 -0
- package/dist/catalog/variant-find.js +239 -0
- package/dist/catalog/variants.d.ts +293 -26
- package/dist/catalog/variants.js +164 -7
- package/dist/catalog/visibility.d.ts +38 -7
- package/dist/catalog/visibility.js +19 -1
- package/dist/client.d.ts +121 -9
- package/dist/client.js +53 -14
- package/dist/content/assets.d.ts +285 -0
- package/dist/content/assets.js +181 -0
- package/dist/content/forms.d.ts +332 -1
- package/dist/content/forms.js +140 -4
- package/dist/content/items.d.ts +292 -0
- package/dist/content/items.js +170 -0
- package/dist/content/menus.d.ts +254 -0
- package/dist/content/menus.js +70 -0
- package/dist/content/strings.d.ts +137 -0
- package/dist/content/strings.js +82 -0
- package/dist/content/types.d.ts +282 -0
- package/dist/content/types.js +100 -0
- package/dist/customers/age-verification.d.ts +66 -0
- package/dist/customers/age-verification.js +79 -0
- package/dist/customers/credit.d.ts +263 -0
- package/dist/customers/credit.js +244 -0
- package/dist/customers/delivery-preferences.d.ts +120 -0
- package/dist/customers/delivery-preferences.js +97 -0
- package/dist/customers/delivery-terms.d.ts +44 -0
- package/dist/customers/delivery-terms.js +32 -0
- package/dist/customers/groups.d.ts +207 -0
- package/dist/customers/groups.js +438 -0
- package/dist/customers/index.d.ts +462 -37
- package/dist/customers/index.js +317 -40
- package/dist/customers/invitation-status.d.ts +57 -0
- package/dist/customers/invitation-status.js +21 -0
- package/dist/customers/notes.d.ts +67 -0
- package/dist/customers/notes.js +51 -0
- package/dist/customers/notification-recipients.d.ts +97 -0
- package/dist/customers/notification-recipients.js +84 -0
- package/dist/customers/saved-lists.d.ts +42 -0
- package/dist/customers/saved-lists.js +61 -0
- package/dist/customers/vat.d.ts +83 -0
- package/dist/customers/vat.js +46 -0
- package/dist/customers/wire.d.ts +20 -0
- package/dist/customers/wire.js +35 -0
- package/dist/documents/ar.d.ts +307 -0
- package/dist/documents/ar.js +254 -0
- package/dist/documents/emails.d.ts +150 -0
- package/dist/documents/emails.js +220 -0
- package/dist/documents/index.d.ts +744 -21
- package/dist/documents/index.js +438 -24
- package/dist/documents/pdf.d.ts +2 -0
- package/dist/documents/pdf.js +11 -0
- package/dist/fulfillments/create.d.ts +167 -0
- package/dist/fulfillments/create.js +216 -0
- package/dist/fulfillments/index.d.ts +163 -11
- package/dist/fulfillments/index.js +135 -6
- package/dist/fulfillments/payment-policy.d.ts +64 -0
- package/dist/fulfillments/payment-policy.js +140 -0
- package/dist/fulfillments/warehouse.d.ts +724 -0
- package/dist/fulfillments/warehouse.js +914 -0
- package/dist/geo/index.d.ts +119 -0
- package/dist/geo/index.js +107 -0
- package/dist/index.d.ts +147 -39
- package/dist/index.js +51 -12
- package/dist/integrations/afas-actions.d.ts +234 -0
- package/dist/integrations/afas-actions.js +310 -0
- package/dist/integrations/afas-types.d.ts +483 -0
- package/dist/integrations/afas-types.js +5 -0
- package/dist/integrations/afas.d.ts +234 -0
- package/dist/integrations/afas.js +290 -0
- package/dist/integrations/connections.d.ts +103 -0
- package/dist/integrations/connections.js +55 -0
- package/dist/integrations/esp.d.ts +313 -0
- package/dist/integrations/esp.js +169 -0
- package/dist/integrations/exact-online-types.d.ts +965 -0
- package/dist/integrations/exact-online-types.js +6 -0
- package/dist/integrations/exact-online.d.ts +784 -0
- package/dist/integrations/exact-online.js +1089 -0
- package/dist/integrations/index.d.ts +44 -3
- package/dist/integrations/index.js +23 -3
- package/dist/integrations/pipeline-discoveries.d.ts +65 -0
- package/dist/integrations/pipeline-discoveries.js +65 -0
- package/dist/integrations/pipeline-keys.d.ts +13 -0
- package/dist/integrations/pipeline-keys.js +22 -0
- package/dist/integrations/pipeline-runs.d.ts +119 -0
- package/dist/integrations/pipeline-runs.js +87 -0
- package/dist/integrations/pipelines.d.ts +87 -15
- package/dist/integrations/pipelines.js +41 -22
- package/dist/integrations/price-import.d.ts +223 -0
- package/dist/integrations/price-import.js +124 -0
- package/dist/integrations/types.d.ts +367 -3
- package/dist/integrations/types.js +4 -3
- package/dist/locations/index.d.ts +66 -0
- package/dist/locations/index.js +31 -0
- package/dist/meta/index.d.ts +172 -0
- package/dist/metafields/index.d.ts +8 -1
- package/dist/metafields/index.js +10 -0
- package/dist/orders/cancel-with-money.d.ts +237 -0
- package/dist/orders/cancel-with-money.js +155 -0
- package/dist/orders/commit-edit.d.ts +23 -1
- package/dist/orders/commit-edit.js +19 -2
- package/dist/orders/deposit-policy.d.ts +3 -1
- package/dist/orders/drafts.d.ts +800 -0
- package/dist/orders/drafts.js +771 -0
- package/dist/orders/fields.d.ts +245 -0
- package/dist/orders/fields.js +188 -0
- package/dist/orders/get.d.ts +29 -19
- package/dist/orders/get.js +17 -8
- package/dist/orders/index.d.ts +66 -15
- package/dist/orders/index.js +25 -2
- package/dist/orders/lifecycle.d.ts +128 -0
- package/dist/orders/lifecycle.js +158 -0
- package/dist/orders/line-price-preview.d.ts +7 -2
- package/dist/orders/list.d.ts +243 -0
- package/dist/orders/list.js +487 -0
- package/dist/orders/manual-discounts.d.ts +13 -5
- package/dist/orders/manual-pricing.d.ts +5 -0
- package/dist/orders/money-view.d.ts +149 -0
- package/dist/orders/money-view.js +99 -0
- package/dist/orders/order-entry.d.ts +20 -7
- package/dist/orders/plan-edit.d.ts +34 -2
- package/dist/orders/plan-edit.js +112 -11
- package/dist/orders/settings.d.ts +201 -0
- package/dist/orders/settings.js +325 -0
- package/dist/orders/status.d.ts +159 -13
- package/dist/orders/status.js +21 -0
- package/dist/orders/surcharges.d.ts +20 -1
- package/dist/orders/surcharges.js +37 -11
- package/dist/payments/account-credit.d.ts +46 -0
- package/dist/payments/account-credit.js +42 -0
- package/dist/payments/ar.d.ts +292 -0
- package/dist/payments/ar.js +223 -0
- package/dist/payments/connected-account.d.ts +47 -0
- package/dist/payments/connected-account.js +61 -0
- package/dist/payments/credit-noted-fields.d.ts +19 -0
- package/dist/payments/credit-noted-fields.js +69 -0
- package/dist/payments/customer-consent.d.ts +30 -0
- package/dist/payments/customer-consent.js +49 -0
- package/dist/payments/index.d.ts +340 -19
- package/dist/payments/index.js +151 -1
- package/dist/payments/methods.d.ts +303 -8
- package/dist/payments/methods.js +337 -7
- package/dist/payments/mollie-methods.d.ts +159 -0
- package/dist/payments/mollie-methods.js +141 -0
- package/dist/payments/payment-links.d.ts +180 -0
- package/dist/payments/payment-links.js +97 -0
- package/dist/payments/provider-keys.d.ts +150 -0
- package/dist/payments/provider-keys.js +118 -0
- package/dist/payments/providers.d.ts +86 -11
- package/dist/payments/providers.js +29 -0
- package/dist/payments/requests.d.ts +147 -0
- package/dist/payments/requests.js +93 -0
- package/dist/payments/store-credit-fields.d.ts +15 -0
- package/dist/payments/store-credit-fields.js +51 -0
- package/dist/pickups/index.d.ts +106 -0
- package/dist/pickups/index.js +108 -0
- package/dist/platform/channels.d.ts +132 -0
- package/dist/platform/channels.js +128 -0
- package/dist/platform/email-gateways.d.ts +165 -0
- package/dist/platform/email-gateways.js +78 -0
- package/dist/platform/geo.d.ts +123 -0
- package/dist/platform/geo.js +144 -0
- package/dist/platform/tax.d.ts +162 -0
- package/dist/platform/tax.js +169 -0
- package/dist/pos/anomalies.d.ts +158 -0
- package/dist/pos/anomalies.js +64 -0
- package/dist/pos/assisted-selling.d.ts +79 -0
- package/dist/pos/assisted-selling.js +38 -0
- package/dist/pos/exchanges.d.ts +108 -0
- package/dist/pos/exchanges.js +22 -0
- package/dist/pos/gift-cards.d.ts +135 -0
- package/dist/pos/gift-cards.js +47 -0
- package/dist/pos/index.d.ts +2370 -58
- package/dist/pos/index.js +854 -7
- package/dist/pos/no-receipt.d.ts +94 -0
- package/dist/pos/no-receipt.js +25 -0
- package/dist/pos/offline-sales.d.ts +228 -0
- package/dist/pos/offline-sales.js +170 -0
- package/dist/pos/pay-by-link.d.ts +143 -0
- package/dist/pos/pay-by-link.js +57 -0
- package/dist/pos/quick-keys.d.ts +103 -0
- package/dist/pos/quick-keys.js +45 -0
- package/dist/pos/reports.d.ts +360 -0
- package/dist/pos/reports.js +62 -0
- package/dist/pos/returns.d.ts +244 -0
- package/dist/pos/returns.js +83 -0
- package/dist/pricing/bulk-coupons.d.ts +139 -0
- package/dist/pricing/bulk-coupons.js +112 -0
- package/dist/pricing/index.d.ts +174 -4
- package/dist/pricing/index.js +174 -1
- package/dist/pricing/price-lists.d.ts +606 -0
- package/dist/pricing/price-lists.js +600 -0
- package/dist/purchasing/alerts.d.ts +104 -0
- package/dist/purchasing/alerts.js +88 -0
- package/dist/purchasing/backorders.d.ts +61 -0
- package/dist/purchasing/backorders.js +92 -0
- package/dist/purchasing/cost.d.ts +110 -0
- package/dist/purchasing/cost.js +86 -0
- package/dist/purchasing/index.d.ts +54 -0
- package/dist/purchasing/index.js +24 -0
- package/dist/purchasing/links.d.ts +263 -0
- package/dist/purchasing/links.js +192 -0
- package/dist/purchasing/purchase-orders.d.ts +551 -0
- package/dist/purchasing/purchase-orders.js +491 -0
- package/dist/purchasing/receipts.d.ts +396 -0
- package/dist/purchasing/receipts.js +287 -0
- package/dist/purchasing/replenishment.d.ts +281 -0
- package/dist/purchasing/replenishment.js +240 -0
- package/dist/purchasing/returns.d.ts +273 -0
- package/dist/purchasing/returns.js +229 -0
- package/dist/purchasing/settings.d.ts +152 -0
- package/dist/purchasing/settings.js +181 -0
- package/dist/purchasing/suppliers.d.ts +476 -0
- package/dist/purchasing/suppliers.js +438 -0
- package/dist/quotes/index.d.ts +518 -0
- package/dist/quotes/index.js +439 -0
- package/dist/reports/index.d.ts +71 -8
- package/dist/reports/index.js +74 -11
- package/dist/returns/index.d.ts +675 -21
- package/dist/returns/index.js +756 -11
- package/dist/search/index.d.ts +76 -1
- package/dist/search/index.js +63 -0
- package/dist/seo/config.d.ts +128 -0
- package/dist/seo/config.js +95 -0
- package/dist/seo/redirects.d.ts +125 -0
- package/dist/seo/redirects.js +60 -1
- package/dist/seo/settings.d.ts +147 -0
- package/dist/seo/settings.js +209 -0
- package/dist/shipping/definitions.d.ts +29 -0
- package/dist/shipping/definitions.js +47 -0
- package/dist/shipping/fulfillment-profiles.d.ts +174 -0
- package/dist/shipping/fulfillment-profiles.js +104 -0
- package/dist/shipping/hendriks.d.ts +97 -0
- package/dist/shipping/hendriks.js +50 -0
- package/dist/shipping/index.d.ts +54 -0
- package/dist/shipping/index.js +27 -0
- package/dist/shipping/methods.d.ts +205 -0
- package/dist/shipping/methods.js +240 -0
- package/dist/shipping/myparcel.d.ts +668 -0
- package/dist/shipping/myparcel.js +466 -0
- package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
- package/dist/shipping/parcel-rule.d.ts +6 -5
- package/dist/shipping/parcel-rule.js +21 -23
- package/dist/shipping/rate-tables.d.ts +324 -0
- package/dist/shipping/rate-tables.js +390 -0
- package/dist/shipping/ship-to-countries.d.ts +75 -0
- package/dist/shipping/ship-to-countries.js +47 -0
- package/dist/stock/by-location.d.ts +86 -0
- package/dist/stock/by-location.js +94 -0
- package/dist/stock/index.d.ts +18 -4
- package/dist/stock/index.js +7 -0
- package/dist/stock/locations.d.ts +144 -0
- package/dist/stock/locations.js +168 -0
- package/dist/stock/settings.d.ts +106 -0
- package/dist/stock/settings.js +103 -0
- package/dist/stock/transfers.d.ts +386 -52
- package/dist/stock/transfers.js +222 -17
- package/dist/stored-value/index.d.ts +312 -11
- package/dist/stored-value/index.js +223 -4
- package/dist/subscriptions/index.d.ts +682 -0
- package/dist/subscriptions/index.js +815 -0
- package/dist/tax/display-policy.d.ts +105 -0
- package/dist/tax/display-policy.js +84 -0
- package/dist/tax/index.d.ts +79 -1
- package/dist/tax/index.js +52 -0
- package/dist/tenant/email-style.d.ts +102 -0
- package/dist/tenant/email-style.js +72 -0
- package/dist/tenant/email-templates.d.ts +287 -0
- package/dist/tenant/email-templates.js +271 -0
- package/dist/tenant/notifications.d.ts +23 -17
- package/dist/tenant/settings-sources.d.ts +1 -1
- package/dist/tenant/time-zone.d.ts +17 -0
- package/dist/tenant/time-zone.js +11 -0
- package/dist/types.d.ts +50 -3
- package/dist/users/index.d.ts +3 -0
- package/dist/users/index.js +2 -0
- package/dist/users/warehouses.d.ts +50 -0
- package/dist/users/warehouses.js +51 -0
- package/package.json +2 -2
|
@@ -1,25 +1,10 @@
|
|
|
1
|
-
import { createPage,
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
*/
|
|
9
|
-
export const PIPELINE_DRAFT_PRESERVE_KEYS = new Set([
|
|
10
|
-
...PLATFORM_PASSTHROUGH_KEYS,
|
|
11
|
-
'source_schema',
|
|
12
|
-
'sourceSchema',
|
|
13
|
-
'target_schema',
|
|
14
|
-
'targetSchema',
|
|
15
|
-
'sub_counts',
|
|
16
|
-
'subCounts',
|
|
17
|
-
]);
|
|
18
|
-
/**
|
|
19
|
-
* A creation probes the source over the network, and a commit creates the pipeline and may start
|
|
20
|
-
* its run: both outlast the SDK's 15-second default on a slow source.
|
|
21
|
-
*/
|
|
22
|
-
export const PIPELINE_DRAFT_SLOW_TIMEOUT_MS = 60_000;
|
|
1
|
+
import { createPage, NotFoundError, readRestPage, } from '@sixtyninecommerce/core';
|
|
2
|
+
import { createPipelineConnectionsModule, listPipelineConnectors, } from './connections.js';
|
|
3
|
+
import { createPipelineDiscoveriesModule, } from './pipeline-discoveries.js';
|
|
4
|
+
import { PIPELINE_DRAFT_PRESERVE_KEYS, PIPELINE_DRAFT_SLOW_TIMEOUT_MS } from './pipeline-keys.js';
|
|
5
|
+
import { createPipelineRunsModule } from './pipeline-runs.js';
|
|
6
|
+
export { PIPELINE_DRAFT_PRESERVE_KEYS, PIPELINE_DRAFT_SLOW_TIMEOUT_MS } from './pipeline-keys.js';
|
|
7
|
+
const PIPELINES = '/pipelines';
|
|
23
8
|
const DRAFTS = '/pipelines/drafts';
|
|
24
9
|
const segment = (value) => encodeURIComponent(value);
|
|
25
10
|
export function createIntegrationPipelinesModule(transport) {
|
|
@@ -52,8 +37,42 @@ export function createIntegrationPipelinesModule(transport) {
|
|
|
52
37
|
await transport.rest('plugins', { method: 'DELETE', path: `${DRAFTS}/${segment(id)}`, expect: 'void' }, options);
|
|
53
38
|
},
|
|
54
39
|
};
|
|
40
|
+
async function listPipelines(options) {
|
|
41
|
+
const body = await transport.rest('plugins', {
|
|
42
|
+
method: 'GET',
|
|
43
|
+
path: PIPELINES,
|
|
44
|
+
// `cursor=1` asks for a keyset page; without it the door answers the whole list as an array.
|
|
45
|
+
query: {
|
|
46
|
+
cursor: 1,
|
|
47
|
+
direction: options?.direction,
|
|
48
|
+
after: options?.after,
|
|
49
|
+
limit: options?.limit,
|
|
50
|
+
},
|
|
51
|
+
}, { ...options, preserveKeys: PIPELINE_DRAFT_PRESERVE_KEYS });
|
|
52
|
+
const { items, meta } = readRestPage(body);
|
|
53
|
+
return createPage(items, meta, (cursor) => listPipelines({ ...options, after: cursor }));
|
|
54
|
+
}
|
|
55
55
|
return {
|
|
56
|
+
list: listPipelines,
|
|
57
|
+
async get(id, options) {
|
|
58
|
+
try {
|
|
59
|
+
return await read(`${PIPELINES}/${segment(id)}`, options);
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
if (error instanceof NotFoundError)
|
|
63
|
+
return null;
|
|
64
|
+
throw error;
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
update: (id, input, options) => write('PATCH', `${PIPELINES}/${segment(id)}`, input, options),
|
|
68
|
+
async delete(id, options) {
|
|
69
|
+
await transport.rest('plugins', { method: 'DELETE', path: `${PIPELINES}/${segment(id)}`, expect: 'void' }, options);
|
|
70
|
+
},
|
|
71
|
+
runs: createPipelineRunsModule(transport),
|
|
56
72
|
drafts,
|
|
73
|
+
discoveries: createPipelineDiscoveriesModule(transport),
|
|
74
|
+
connections: createPipelineConnectionsModule(transport),
|
|
75
|
+
connectors: (options) => listPipelineConnectors(transport, options),
|
|
57
76
|
async importDependencies(options) {
|
|
58
77
|
const body = await read('/pipelines/import-dependencies', options);
|
|
59
78
|
return body.data;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bulk price import (headless spec §9.2 `integrations`: "price import, AI conversion"): the
|
|
3
|
+
* plugins service's `POST /prices/import`, and its AI conversion of any supplier file into a
|
|
4
|
+
* document that import accepts (`POST /prices/import/ai/conversions`, then the
|
|
5
|
+
* `price-import-ai-conversion` progress stream).
|
|
6
|
+
*
|
|
7
|
+
* A thin wrapper over the routes the dashboard has always used (ruling G119): the same documents,
|
|
8
|
+
* the same tax handling, the same dry run and row selection. A dry run and an import are two
|
|
9
|
+
* methods because an assistant asks before an import and never before a dry run (G81).
|
|
10
|
+
*/
|
|
11
|
+
import { type PlatformError, type PriceImportAiConversionResult, type PriceImportAiConversionStep, type Transport } from '@sixtyninecommerce/core';
|
|
12
|
+
import type { Call } from '../types.js';
|
|
13
|
+
/** What the plugins service reads a price document as. */
|
|
14
|
+
export type PriceImportFormat = 'csv' | 'json' | 'xml';
|
|
15
|
+
/** Whether the amounts in a document include tax. */
|
|
16
|
+
export type PriceImportTaxMode = 'inclusive' | 'exclusive';
|
|
17
|
+
/** A price document, as its text. */
|
|
18
|
+
export interface PriceImportDocument {
|
|
19
|
+
format: PriceImportFormat;
|
|
20
|
+
/** The whole document, at most 6,000,000 characters and 5,000 data rows. */
|
|
21
|
+
content: string;
|
|
22
|
+
/**
|
|
23
|
+
* Whether the document's amounts include tax. Left out, they are read in the store's own tax
|
|
24
|
+
* mode and nothing is converted. Given and different from the store's, each amount is converted
|
|
25
|
+
* at its variant's tax rate (`taxConverted` on the row).
|
|
26
|
+
*/
|
|
27
|
+
fileTaxMode?: PriceImportTaxMode;
|
|
28
|
+
}
|
|
29
|
+
export interface PriceImportRunInput extends PriceImportDocument {
|
|
30
|
+
/**
|
|
31
|
+
* The rows to write, by `rowNumber` as {@link PriceImportModule.preview} answered them. Rows
|
|
32
|
+
* left out answer `skipped`, so `[]` writes nothing. Left out entirely, every row the preview
|
|
33
|
+
* marks `updated` is written (never a `duplicate`). At most 5,000.
|
|
34
|
+
*/
|
|
35
|
+
selectedRows?: readonly number[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* One row's outcome.
|
|
39
|
+
* - `updated`: a variant matched and its price differs; written by {@link PriceImportModule.run}.
|
|
40
|
+
* - `unchanged`: the price is already the stored one.
|
|
41
|
+
* - `not_found`: no variant of the store has this SKU.
|
|
42
|
+
* - `duplicate`: an earlier row sets the same SKU and currency (`duplicateOfRow`); a preview only.
|
|
43
|
+
* A selected duplicate that is written answers `updated`.
|
|
44
|
+
* - `invalid`: the row's content failed validation (its `message` says why); never written.
|
|
45
|
+
* - `skipped`: an import only: a row left out of `selectedRows`.
|
|
46
|
+
* - `error`: the row cannot be written (an ambiguous SKU, a tax rate that cannot be resolved, a
|
|
47
|
+
* Price field group an app or API token owns) or its write failed; `message` says why.
|
|
48
|
+
*/
|
|
49
|
+
export type PriceImportRowStatus = 'updated' | 'unchanged' | 'not_found' | 'duplicate' | 'invalid' | 'skipped' | 'error';
|
|
50
|
+
export interface PriceImportRow {
|
|
51
|
+
/**
|
|
52
|
+
* Where the row is in the document: its line in a CSV (the header is line 1, so the first data
|
|
53
|
+
* row is 2; blank lines count), its position among the entries of a JSON or XML document (from
|
|
54
|
+
* 1). `selectedRows` names rows by it.
|
|
55
|
+
*/
|
|
56
|
+
rowNumber: number;
|
|
57
|
+
/** Empty when the row carried no SKU that could be read (only on `invalid`). */
|
|
58
|
+
sku: string;
|
|
59
|
+
/** The row's currency, or the store's default when the document named none. */
|
|
60
|
+
currency: string;
|
|
61
|
+
status: PriceImportRowStatus;
|
|
62
|
+
/** The amount as the document states it, in major units. */
|
|
63
|
+
amount: number;
|
|
64
|
+
/** The stored price in minor units, in the store's tax mode; null when no variant matched. */
|
|
65
|
+
oldPrice: number | null;
|
|
66
|
+
/** The price in minor units, in the store's tax mode, that is or would be written. */
|
|
67
|
+
newPrice: number | null;
|
|
68
|
+
/** True when the document's tax mode differs from the store's and the amount was converted. */
|
|
69
|
+
taxConverted: boolean;
|
|
70
|
+
/** The earlier row with the same SKU and currency, or null. */
|
|
71
|
+
duplicateOfRow: number | null;
|
|
72
|
+
message?: string;
|
|
73
|
+
}
|
|
74
|
+
export interface PriceImportSummary {
|
|
75
|
+
/** Data rows read from the document. */
|
|
76
|
+
total: number;
|
|
77
|
+
updated: number;
|
|
78
|
+
unchanged: number;
|
|
79
|
+
notFound: number;
|
|
80
|
+
duplicate: number;
|
|
81
|
+
invalid: number;
|
|
82
|
+
skipped: number;
|
|
83
|
+
errored: number;
|
|
84
|
+
}
|
|
85
|
+
/** What a dry run or an import answers: per row, and summed. */
|
|
86
|
+
export interface PriceImportReport {
|
|
87
|
+
dryRun: boolean;
|
|
88
|
+
/** Whether the store's prices include tax: the basis of `oldPrice` and `newPrice`. */
|
|
89
|
+
channelPricesIncludeTax: boolean;
|
|
90
|
+
channelDefaultCurrency: string;
|
|
91
|
+
/** The basis the amounts were read in: the one sent, else the store's own. */
|
|
92
|
+
fileTaxMode: PriceImportTaxMode;
|
|
93
|
+
summary: PriceImportSummary;
|
|
94
|
+
results: PriceImportRow[];
|
|
95
|
+
}
|
|
96
|
+
/** A file handed to the AI conversion. */
|
|
97
|
+
export interface PriceImportConvertInput {
|
|
98
|
+
/**
|
|
99
|
+
* The supplier's file: `.xlsx`, `.xlsm`, `.csv`, `.tsv`, `.txt`, `.json`, `.xml`, `.md`, `.pdf`
|
|
100
|
+
* or `.docx`, at most 20 MB (a workbook 8 MB). The service reads the format from the name.
|
|
101
|
+
*/
|
|
102
|
+
file: Blob;
|
|
103
|
+
/** The file's name. Defaults to a `File`'s own; a bare `Blob` needs one. */
|
|
104
|
+
fileName?: string;
|
|
105
|
+
}
|
|
106
|
+
/** The upload's receipt: follow the conversion with its id. */
|
|
107
|
+
export interface PriceImportUpload {
|
|
108
|
+
conversionId: string;
|
|
109
|
+
/** The name the service stored, without any path. */
|
|
110
|
+
fileName: string;
|
|
111
|
+
}
|
|
112
|
+
/** The converted document, with what the converter read. `document` goes to `preview` as `json`. */
|
|
113
|
+
export type PriceImportConversion = PriceImportAiConversionResult;
|
|
114
|
+
export type PriceImportConversionStep = PriceImportAiConversionStep;
|
|
115
|
+
/** One progress line of a conversion. */
|
|
116
|
+
export interface PriceImportConversionProgress {
|
|
117
|
+
step: PriceImportConversionStep;
|
|
118
|
+
message: string;
|
|
119
|
+
}
|
|
120
|
+
export interface PriceImportFollowOptions {
|
|
121
|
+
/** Aborting it closes the stream; `follow` then throws `REQUEST_ABORTED`. */
|
|
122
|
+
signal?: AbortSignal;
|
|
123
|
+
/** Called with each progress line, in order. A callback that throws ends `follow` with its error. */
|
|
124
|
+
onProgress?: (progress: PriceImportConversionProgress) => void;
|
|
125
|
+
/** Give up after this long without a byte, keepalives included. Default 45000 ms. */
|
|
126
|
+
idleTimeoutMs?: number;
|
|
127
|
+
/** Deadline for the stream's response headers. Defaults to the client's own timeout. */
|
|
128
|
+
connectTimeoutMs?: number;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* The store's bulk price import, and the AI conversion that turns any supplier file into a
|
|
132
|
+
* document it accepts.
|
|
133
|
+
*
|
|
134
|
+
* - **Every method needs `UpdateCatalog` or `UpdateProduct`** on the store (either is enough,
|
|
135
|
+
* so no method carries a `@permission` tag), and acts on the
|
|
136
|
+
* client's store (`admin.withTenant(...)` for another one). The prices are written through the
|
|
137
|
+
* Admin API with the caller's own credential, so the engine's rules for a price write apply
|
|
138
|
+
* to each row.
|
|
139
|
+
* - **A document the service cannot read at all is refused whole, and nothing is written:** a
|
|
140
|
+
* `BusinessRuleError` (422) coded `PRICE_IMPORT_FORMAT_ERROR` whose `details[]` name each
|
|
141
|
+
* problem (`field` is `header`, `document` or `row N`; at most 100 entries, the last one coded
|
|
142
|
+
* `TRUNCATED` and counting the rest when there are more). More than 5,000 data rows is one of
|
|
143
|
+
* those problems (`TOO_MANY_ROWS`). A row whose CONTENT is wrong is not a refusal: it comes
|
|
144
|
+
* back `invalid`.
|
|
145
|
+
* - **The service records an `idempotencyKey` and acts on nothing**: two calls are two imports.
|
|
146
|
+
*/
|
|
147
|
+
export interface PriceImportModule {
|
|
148
|
+
/**
|
|
149
|
+
* Read a document and say, row by row, what an import would do. Nothing is written.
|
|
150
|
+
*
|
|
151
|
+
* Refused: `PRICE_IMPORT_FORMAT_ERROR` (422, see the module), `VALIDATION_FAILED` (400) for a
|
|
152
|
+
* body the service does not accept (an unknown format, an empty document).
|
|
153
|
+
*
|
|
154
|
+
* @effect read
|
|
155
|
+
*/
|
|
156
|
+
preview(document: PriceImportDocument, options?: Call): Promise<PriceImportReport>;
|
|
157
|
+
/**
|
|
158
|
+
* Write the document's prices: the rows `selectedRows` names, or every `updated` row when it
|
|
159
|
+
* names none. Rows are written per variant as they come, so a row that fails (`error`) does
|
|
160
|
+
* not undo the others; the report says which were written. A selection naming two rows that
|
|
161
|
+
* set the same SKU and currency is refused whole, `PRICE_IMPORT_SELECTION_CONFLICT` (422, one
|
|
162
|
+
* `details[]` entry per pair), and nothing is written.
|
|
163
|
+
*
|
|
164
|
+
* The prices are what customers pay from the moment each is written, and the import cannot be
|
|
165
|
+
* undone except by importing the old prices again (each row's `oldPrice`). A timeout or a
|
|
166
|
+
* dropped connection does not say whether the prices were written: run {@link preview} with
|
|
167
|
+
* the same document, where rows already at their new price read `unchanged`, before repeating.
|
|
168
|
+
*
|
|
169
|
+
* @effect irreversible
|
|
170
|
+
*/
|
|
171
|
+
run(input: PriceImportRunInput, options?: Call): Promise<PriceImportReport>;
|
|
172
|
+
/**
|
|
173
|
+
* Upload a supplier file for the AI conversion and answer the id to {@link follow}. Nothing is
|
|
174
|
+
* converted until it is followed, and nothing is ever written: the converted document goes to
|
|
175
|
+
* {@link preview} and {@link run} like any other. The upload is kept for a few minutes and
|
|
176
|
+
* read once.
|
|
177
|
+
*
|
|
178
|
+
* Refused before sending: a file with no name (`PRICE_IMPORT_FILE_NAME_REQUIRED`, 400). By the
|
|
179
|
+
* service: a file with no extension, a legacy `.xls`, another unsupported type, an empty file
|
|
180
|
+
* or a workbook over 8 MB (400, the sentence says which and what to do); a file over 20 MB
|
|
181
|
+
* (413); too many uploads waiting, on the service (`AI_CONVERSION_CAPACITY_REACHED`) or for
|
|
182
|
+
* this store (`AI_CONVERSION_CHANNEL_BACKLOG`), both 503 and worth repeating later. Never
|
|
183
|
+
* retried by the client: a repeat stores the file twice.
|
|
184
|
+
*
|
|
185
|
+
* @effect write
|
|
186
|
+
*/
|
|
187
|
+
convert(input: PriceImportConvertInput, options?: Call): Promise<PriceImportUpload>;
|
|
188
|
+
/**
|
|
189
|
+
* Run the conversion of an upload and follow it to its end: progress lines through
|
|
190
|
+
* `onProgress`, then the converted document. The service sends the file's text to the
|
|
191
|
+
* platform's AI provider while this runs (30 seconds to 3 minutes). Abort it to stop the
|
|
192
|
+
* conversion. It acts on the client's store: an upload of another store answers
|
|
193
|
+
* `AI_IMPORT_NOT_FOUND`.
|
|
194
|
+
*
|
|
195
|
+
* The conversion's own refusal is thrown with its code:
|
|
196
|
+
* - `AI_IMPORT_NOT_FOUND` (`NotFoundError`): the upload expired, was already followed, or is
|
|
197
|
+
* another store's;
|
|
198
|
+
* - `AI_IMPORT_UNSUPPORTED_FILE`, `AI_IMPORT_EXTRACTION_FAILED` (a file with no text: a scan,
|
|
199
|
+
* a protected workbook), `AI_IMPORT_NO_PRICES_FOUND`, `AI_IMPORT_TOO_MANY_ROWS`
|
|
200
|
+
* (`BusinessRuleError`);
|
|
201
|
+
* - `AI_IMPORT_BUSY` (`RateLimitedError`): the service is converting as many files as it can;
|
|
202
|
+
* - `AI_IMPORT_NOT_CONFIGURED` (`UpstreamError`, 503): this deployment has no AI provider;
|
|
203
|
+
* - `AI_IMPORT_LLM_FAILED`, `AI_IMPORT_STREAM_FAILED` and any code this client does not know
|
|
204
|
+
* (`UpstreamError`, 502).
|
|
205
|
+
*
|
|
206
|
+
* The stream itself can fail with `PROGRESS_STREAM_CLOSED`, `PROGRESS_STREAM_STALLED`,
|
|
207
|
+
* `MALFORMED_RESPONSE` or `REQUEST_ABORTED`; an id that is not a conversion id is refused
|
|
208
|
+
* before anything is sent (`PROGRESS_STREAM_ID_INVALID`, 400). Nothing here is retryable: the
|
|
209
|
+
* upload is gone once its conversion ends, so convert the file again.
|
|
210
|
+
*
|
|
211
|
+
* @effect read
|
|
212
|
+
*/
|
|
213
|
+
follow(conversionId: string, options?: PriceImportFollowOptions): Promise<PriceImportConversion>;
|
|
214
|
+
}
|
|
215
|
+
/** A conversion's terminal `error` event as the error {@link PriceImportModule.follow} throws. */
|
|
216
|
+
export declare function conversionRefusal(code: string, message: string): PlatformError;
|
|
217
|
+
/**
|
|
218
|
+
* The deadlines `preview` and `run` give the service, longer than the client's default: a
|
|
219
|
+
* 5,000-row import is measured in tens of seconds (the dashboard has always allowed these).
|
|
220
|
+
*/
|
|
221
|
+
export declare const PRICE_IMPORT_PREVIEW_TIMEOUT_MS = 30000;
|
|
222
|
+
export declare const PRICE_IMPORT_RUN_TIMEOUT_MS = 120000;
|
|
223
|
+
export declare function createPriceImportModule(transport: Transport): PriceImportModule;
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bulk price import (headless spec §9.2 `integrations`: "price import, AI conversion"): the
|
|
3
|
+
* plugins service's `POST /prices/import`, and its AI conversion of any supplier file into a
|
|
4
|
+
* document that import accepts (`POST /prices/import/ai/conversions`, then the
|
|
5
|
+
* `price-import-ai-conversion` progress stream).
|
|
6
|
+
*
|
|
7
|
+
* A thin wrapper over the routes the dashboard has always used (ruling G119): the same documents,
|
|
8
|
+
* the same tax handling, the same dry run and row selection. A dry run and an import are two
|
|
9
|
+
* methods because an assistant asks before an import and never before a dry run (G81).
|
|
10
|
+
*/
|
|
11
|
+
import { errorForStatus, openProgressStream, UpstreamError, ValidationError, } from '@sixtyninecommerce/core';
|
|
12
|
+
/** The HTTP status each conversion refusal stands for, which picks its error class. */
|
|
13
|
+
const CONVERSION_REFUSAL_STATUS = {
|
|
14
|
+
AI_IMPORT_NOT_FOUND: 404,
|
|
15
|
+
AI_IMPORT_UNSUPPORTED_FILE: 422,
|
|
16
|
+
AI_IMPORT_EXTRACTION_FAILED: 422,
|
|
17
|
+
AI_IMPORT_NO_PRICES_FOUND: 422,
|
|
18
|
+
AI_IMPORT_TOO_MANY_ROWS: 422,
|
|
19
|
+
AI_IMPORT_BUSY: 429,
|
|
20
|
+
AI_IMPORT_NOT_CONFIGURED: 503,
|
|
21
|
+
AI_IMPORT_LLM_FAILED: 502,
|
|
22
|
+
AI_IMPORT_STREAM_FAILED: 502,
|
|
23
|
+
};
|
|
24
|
+
/** A conversion's terminal `error` event as the error {@link PriceImportModule.follow} throws. */
|
|
25
|
+
export function conversionRefusal(code, message) {
|
|
26
|
+
const status = Object.hasOwn(CONVERSION_REFUSAL_STATUS, code)
|
|
27
|
+
? CONVERSION_REFUSAL_STATUS[code]
|
|
28
|
+
: 502;
|
|
29
|
+
// Never retryable: the service deletes the upload when its stream ends, so the same `follow`
|
|
30
|
+
// can only answer AI_IMPORT_NOT_FOUND. The remedy is a fresh `convert`.
|
|
31
|
+
return errorForStatus(status, { code, message, service: 'plugins', retryable: false });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The deadlines `preview` and `run` give the service, longer than the client's default: a
|
|
35
|
+
* 5,000-row import is measured in tens of seconds (the dashboard has always allowed these).
|
|
36
|
+
*/
|
|
37
|
+
export const PRICE_IMPORT_PREVIEW_TIMEOUT_MS = 30_000;
|
|
38
|
+
export const PRICE_IMPORT_RUN_TIMEOUT_MS = 120_000;
|
|
39
|
+
function nameOf(file) {
|
|
40
|
+
const name = file.name;
|
|
41
|
+
return typeof name === 'string' ? name : undefined;
|
|
42
|
+
}
|
|
43
|
+
function readUpload(answer) {
|
|
44
|
+
const record = answer !== null && typeof answer === 'object' ? answer : {};
|
|
45
|
+
const { conversionId, fileName } = record;
|
|
46
|
+
if (typeof conversionId !== 'string' || conversionId.trim() === '') {
|
|
47
|
+
// The file is stored; only its ticket is missing. Repeating would store it again.
|
|
48
|
+
throw new UpstreamError({
|
|
49
|
+
code: 'MALFORMED_RESPONSE',
|
|
50
|
+
message: 'The price import upload answered without a conversion id.',
|
|
51
|
+
status: 502,
|
|
52
|
+
service: 'plugins',
|
|
53
|
+
retryable: false,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return { conversionId, fileName: typeof fileName === 'string' ? fileName : '' };
|
|
57
|
+
}
|
|
58
|
+
export function createPriceImportModule(transport) {
|
|
59
|
+
const importDocument = (body, timeoutMs, options) => {
|
|
60
|
+
const callOptions = { ...options, timeoutMs };
|
|
61
|
+
return transport.rest('plugins', { method: 'POST', path: '/prices/import', body }, callOptions);
|
|
62
|
+
};
|
|
63
|
+
const documentBody = (document) => ({
|
|
64
|
+
format: document.format,
|
|
65
|
+
content: document.content,
|
|
66
|
+
...(document.fileTaxMode === undefined ? {} : { fileTaxMode: document.fileTaxMode }),
|
|
67
|
+
});
|
|
68
|
+
return {
|
|
69
|
+
preview(document, options) {
|
|
70
|
+
return importDocument({ ...documentBody(document), dryRun: true }, PRICE_IMPORT_PREVIEW_TIMEOUT_MS, options);
|
|
71
|
+
},
|
|
72
|
+
run(input, options) {
|
|
73
|
+
return importDocument({
|
|
74
|
+
...documentBody(input),
|
|
75
|
+
dryRun: false,
|
|
76
|
+
// `[]` and "none named" differ: the first writes nothing, the second every changed row.
|
|
77
|
+
...(input.selectedRows === undefined ? {} : { selectedRows: [...input.selectedRows] }),
|
|
78
|
+
}, PRICE_IMPORT_RUN_TIMEOUT_MS, options);
|
|
79
|
+
},
|
|
80
|
+
async convert(input, options) {
|
|
81
|
+
const fileName = input.fileName ?? nameOf(input.file);
|
|
82
|
+
if (fileName === undefined || fileName.trim() === '') {
|
|
83
|
+
throw new ValidationError({
|
|
84
|
+
code: 'PRICE_IMPORT_FILE_NAME_REQUIRED',
|
|
85
|
+
message: 'A file needs a name: pass fileName, or a File that carries one. The service reads the format from its extension. Nothing was sent.',
|
|
86
|
+
status: 400,
|
|
87
|
+
service: 'plugins',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const answer = await transport.rest('plugins', {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
path: '/prices/import/ai/conversions',
|
|
93
|
+
multipart: { files: [{ field: 'file', file: input.file, fileName }] },
|
|
94
|
+
}, options);
|
|
95
|
+
return readUpload(answer);
|
|
96
|
+
},
|
|
97
|
+
async follow(conversionId, options = {}) {
|
|
98
|
+
const stream = openProgressStream(transport, { kind: 'price-import-ai-conversion', id: conversionId }, {
|
|
99
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
100
|
+
...(options.idleTimeoutMs === undefined ? {} : { idleTimeoutMs: options.idleTimeoutMs }),
|
|
101
|
+
...(options.connectTimeoutMs === undefined
|
|
102
|
+
? {}
|
|
103
|
+
: { connectTimeoutMs: options.connectTimeoutMs }),
|
|
104
|
+
});
|
|
105
|
+
for await (const event of stream) {
|
|
106
|
+
if (event.type === 'progress') {
|
|
107
|
+
options.onProgress?.({ step: event.step, message: event.message });
|
|
108
|
+
continue;
|
|
109
|
+
}
|
|
110
|
+
if (event.type === 'error')
|
|
111
|
+
throw conversionRefusal(event.code, event.message);
|
|
112
|
+
return event.result;
|
|
113
|
+
}
|
|
114
|
+
// The stream ends only after a terminal event, or throws; this is the contract's backstop.
|
|
115
|
+
throw new UpstreamError({
|
|
116
|
+
code: 'PROGRESS_STREAM_CLOSED',
|
|
117
|
+
message: 'The price import conversion ended without an answer; convert the file again.',
|
|
118
|
+
status: 502,
|
|
119
|
+
service: 'plugins',
|
|
120
|
+
retryable: false,
|
|
121
|
+
});
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|