@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.
Files changed (302) hide show
  1. package/README.md +5772 -118
  2. package/dist/auth/index.d.ts +7 -1
  3. package/dist/auth/index.js +8 -1
  4. package/dist/catalog/assets.d.ts +120 -8
  5. package/dist/catalog/assets.js +98 -12
  6. package/dist/catalog/collections.d.ts +279 -14
  7. package/dist/catalog/collections.js +246 -15
  8. package/dist/catalog/documents.d.ts +142 -0
  9. package/dist/catalog/documents.js +114 -0
  10. package/dist/catalog/index.d.ts +54 -6
  11. package/dist/catalog/index.js +10 -1
  12. package/dist/catalog/metadata.d.ts +244 -0
  13. package/dist/catalog/metadata.js +220 -0
  14. package/dist/catalog/products.d.ts +159 -19
  15. package/dist/catalog/products.js +88 -13
  16. package/dist/catalog/properties.d.ts +140 -14
  17. package/dist/catalog/properties.js +65 -1
  18. package/dist/catalog/recommendations.d.ts +288 -0
  19. package/dist/catalog/recommendations.js +237 -0
  20. package/dist/catalog/reviews.d.ts +381 -0
  21. package/dist/catalog/reviews.js +325 -0
  22. package/dist/catalog/types.d.ts +17 -1
  23. package/dist/catalog/types.js +10 -0
  24. package/dist/catalog/variant-find.d.ts +136 -0
  25. package/dist/catalog/variant-find.js +239 -0
  26. package/dist/catalog/variants.d.ts +293 -26
  27. package/dist/catalog/variants.js +164 -7
  28. package/dist/catalog/visibility.d.ts +38 -7
  29. package/dist/catalog/visibility.js +19 -1
  30. package/dist/client.d.ts +121 -9
  31. package/dist/client.js +53 -14
  32. package/dist/content/assets.d.ts +285 -0
  33. package/dist/content/assets.js +181 -0
  34. package/dist/content/forms.d.ts +332 -1
  35. package/dist/content/forms.js +140 -4
  36. package/dist/content/items.d.ts +292 -0
  37. package/dist/content/items.js +170 -0
  38. package/dist/content/menus.d.ts +254 -0
  39. package/dist/content/menus.js +70 -0
  40. package/dist/content/strings.d.ts +137 -0
  41. package/dist/content/strings.js +82 -0
  42. package/dist/content/types.d.ts +282 -0
  43. package/dist/content/types.js +100 -0
  44. package/dist/customers/age-verification.d.ts +66 -0
  45. package/dist/customers/age-verification.js +79 -0
  46. package/dist/customers/credit.d.ts +263 -0
  47. package/dist/customers/credit.js +244 -0
  48. package/dist/customers/delivery-preferences.d.ts +120 -0
  49. package/dist/customers/delivery-preferences.js +97 -0
  50. package/dist/customers/delivery-terms.d.ts +44 -0
  51. package/dist/customers/delivery-terms.js +32 -0
  52. package/dist/customers/groups.d.ts +207 -0
  53. package/dist/customers/groups.js +438 -0
  54. package/dist/customers/index.d.ts +462 -37
  55. package/dist/customers/index.js +317 -40
  56. package/dist/customers/invitation-status.d.ts +57 -0
  57. package/dist/customers/invitation-status.js +21 -0
  58. package/dist/customers/notes.d.ts +67 -0
  59. package/dist/customers/notes.js +51 -0
  60. package/dist/customers/notification-recipients.d.ts +97 -0
  61. package/dist/customers/notification-recipients.js +84 -0
  62. package/dist/customers/saved-lists.d.ts +42 -0
  63. package/dist/customers/saved-lists.js +61 -0
  64. package/dist/customers/vat.d.ts +83 -0
  65. package/dist/customers/vat.js +46 -0
  66. package/dist/customers/wire.d.ts +20 -0
  67. package/dist/customers/wire.js +35 -0
  68. package/dist/documents/ar.d.ts +307 -0
  69. package/dist/documents/ar.js +254 -0
  70. package/dist/documents/emails.d.ts +150 -0
  71. package/dist/documents/emails.js +220 -0
  72. package/dist/documents/index.d.ts +744 -21
  73. package/dist/documents/index.js +438 -24
  74. package/dist/documents/pdf.d.ts +2 -0
  75. package/dist/documents/pdf.js +11 -0
  76. package/dist/fulfillments/create.d.ts +167 -0
  77. package/dist/fulfillments/create.js +216 -0
  78. package/dist/fulfillments/index.d.ts +163 -11
  79. package/dist/fulfillments/index.js +135 -6
  80. package/dist/fulfillments/payment-policy.d.ts +64 -0
  81. package/dist/fulfillments/payment-policy.js +140 -0
  82. package/dist/fulfillments/warehouse.d.ts +724 -0
  83. package/dist/fulfillments/warehouse.js +914 -0
  84. package/dist/geo/index.d.ts +119 -0
  85. package/dist/geo/index.js +107 -0
  86. package/dist/index.d.ts +147 -39
  87. package/dist/index.js +51 -12
  88. package/dist/integrations/afas-actions.d.ts +234 -0
  89. package/dist/integrations/afas-actions.js +310 -0
  90. package/dist/integrations/afas-types.d.ts +483 -0
  91. package/dist/integrations/afas-types.js +5 -0
  92. package/dist/integrations/afas.d.ts +234 -0
  93. package/dist/integrations/afas.js +290 -0
  94. package/dist/integrations/connections.d.ts +103 -0
  95. package/dist/integrations/connections.js +55 -0
  96. package/dist/integrations/esp.d.ts +313 -0
  97. package/dist/integrations/esp.js +169 -0
  98. package/dist/integrations/exact-online-types.d.ts +965 -0
  99. package/dist/integrations/exact-online-types.js +6 -0
  100. package/dist/integrations/exact-online.d.ts +784 -0
  101. package/dist/integrations/exact-online.js +1089 -0
  102. package/dist/integrations/index.d.ts +44 -3
  103. package/dist/integrations/index.js +23 -3
  104. package/dist/integrations/pipeline-discoveries.d.ts +65 -0
  105. package/dist/integrations/pipeline-discoveries.js +65 -0
  106. package/dist/integrations/pipeline-keys.d.ts +13 -0
  107. package/dist/integrations/pipeline-keys.js +22 -0
  108. package/dist/integrations/pipeline-runs.d.ts +119 -0
  109. package/dist/integrations/pipeline-runs.js +87 -0
  110. package/dist/integrations/pipelines.d.ts +87 -15
  111. package/dist/integrations/pipelines.js +41 -22
  112. package/dist/integrations/price-import.d.ts +223 -0
  113. package/dist/integrations/price-import.js +124 -0
  114. package/dist/integrations/types.d.ts +367 -3
  115. package/dist/integrations/types.js +4 -3
  116. package/dist/locations/index.d.ts +66 -0
  117. package/dist/locations/index.js +31 -0
  118. package/dist/meta/index.d.ts +172 -0
  119. package/dist/metafields/index.d.ts +8 -1
  120. package/dist/metafields/index.js +10 -0
  121. package/dist/orders/cancel-with-money.d.ts +237 -0
  122. package/dist/orders/cancel-with-money.js +155 -0
  123. package/dist/orders/commit-edit.d.ts +23 -1
  124. package/dist/orders/commit-edit.js +19 -2
  125. package/dist/orders/deposit-policy.d.ts +3 -1
  126. package/dist/orders/drafts.d.ts +800 -0
  127. package/dist/orders/drafts.js +771 -0
  128. package/dist/orders/fields.d.ts +245 -0
  129. package/dist/orders/fields.js +188 -0
  130. package/dist/orders/get.d.ts +29 -19
  131. package/dist/orders/get.js +17 -8
  132. package/dist/orders/index.d.ts +66 -15
  133. package/dist/orders/index.js +25 -2
  134. package/dist/orders/lifecycle.d.ts +128 -0
  135. package/dist/orders/lifecycle.js +158 -0
  136. package/dist/orders/line-price-preview.d.ts +7 -2
  137. package/dist/orders/list.d.ts +243 -0
  138. package/dist/orders/list.js +487 -0
  139. package/dist/orders/manual-discounts.d.ts +13 -5
  140. package/dist/orders/manual-pricing.d.ts +5 -0
  141. package/dist/orders/money-view.d.ts +149 -0
  142. package/dist/orders/money-view.js +99 -0
  143. package/dist/orders/order-entry.d.ts +20 -7
  144. package/dist/orders/plan-edit.d.ts +34 -2
  145. package/dist/orders/plan-edit.js +112 -11
  146. package/dist/orders/settings.d.ts +201 -0
  147. package/dist/orders/settings.js +325 -0
  148. package/dist/orders/status.d.ts +159 -13
  149. package/dist/orders/status.js +21 -0
  150. package/dist/orders/surcharges.d.ts +20 -1
  151. package/dist/orders/surcharges.js +37 -11
  152. package/dist/payments/account-credit.d.ts +46 -0
  153. package/dist/payments/account-credit.js +42 -0
  154. package/dist/payments/ar.d.ts +292 -0
  155. package/dist/payments/ar.js +223 -0
  156. package/dist/payments/connected-account.d.ts +47 -0
  157. package/dist/payments/connected-account.js +61 -0
  158. package/dist/payments/credit-noted-fields.d.ts +19 -0
  159. package/dist/payments/credit-noted-fields.js +69 -0
  160. package/dist/payments/customer-consent.d.ts +30 -0
  161. package/dist/payments/customer-consent.js +49 -0
  162. package/dist/payments/index.d.ts +340 -19
  163. package/dist/payments/index.js +151 -1
  164. package/dist/payments/methods.d.ts +303 -8
  165. package/dist/payments/methods.js +337 -7
  166. package/dist/payments/mollie-methods.d.ts +159 -0
  167. package/dist/payments/mollie-methods.js +141 -0
  168. package/dist/payments/payment-links.d.ts +180 -0
  169. package/dist/payments/payment-links.js +97 -0
  170. package/dist/payments/provider-keys.d.ts +150 -0
  171. package/dist/payments/provider-keys.js +118 -0
  172. package/dist/payments/providers.d.ts +86 -11
  173. package/dist/payments/providers.js +29 -0
  174. package/dist/payments/requests.d.ts +147 -0
  175. package/dist/payments/requests.js +93 -0
  176. package/dist/payments/store-credit-fields.d.ts +15 -0
  177. package/dist/payments/store-credit-fields.js +51 -0
  178. package/dist/pickups/index.d.ts +106 -0
  179. package/dist/pickups/index.js +108 -0
  180. package/dist/platform/channels.d.ts +132 -0
  181. package/dist/platform/channels.js +128 -0
  182. package/dist/platform/email-gateways.d.ts +165 -0
  183. package/dist/platform/email-gateways.js +78 -0
  184. package/dist/platform/geo.d.ts +123 -0
  185. package/dist/platform/geo.js +144 -0
  186. package/dist/platform/tax.d.ts +162 -0
  187. package/dist/platform/tax.js +169 -0
  188. package/dist/pos/anomalies.d.ts +158 -0
  189. package/dist/pos/anomalies.js +64 -0
  190. package/dist/pos/assisted-selling.d.ts +79 -0
  191. package/dist/pos/assisted-selling.js +38 -0
  192. package/dist/pos/exchanges.d.ts +108 -0
  193. package/dist/pos/exchanges.js +22 -0
  194. package/dist/pos/gift-cards.d.ts +135 -0
  195. package/dist/pos/gift-cards.js +47 -0
  196. package/dist/pos/index.d.ts +2370 -58
  197. package/dist/pos/index.js +854 -7
  198. package/dist/pos/no-receipt.d.ts +94 -0
  199. package/dist/pos/no-receipt.js +25 -0
  200. package/dist/pos/offline-sales.d.ts +228 -0
  201. package/dist/pos/offline-sales.js +170 -0
  202. package/dist/pos/pay-by-link.d.ts +143 -0
  203. package/dist/pos/pay-by-link.js +57 -0
  204. package/dist/pos/quick-keys.d.ts +103 -0
  205. package/dist/pos/quick-keys.js +45 -0
  206. package/dist/pos/reports.d.ts +360 -0
  207. package/dist/pos/reports.js +62 -0
  208. package/dist/pos/returns.d.ts +244 -0
  209. package/dist/pos/returns.js +83 -0
  210. package/dist/pricing/bulk-coupons.d.ts +139 -0
  211. package/dist/pricing/bulk-coupons.js +112 -0
  212. package/dist/pricing/index.d.ts +174 -4
  213. package/dist/pricing/index.js +174 -1
  214. package/dist/pricing/price-lists.d.ts +606 -0
  215. package/dist/pricing/price-lists.js +600 -0
  216. package/dist/purchasing/alerts.d.ts +104 -0
  217. package/dist/purchasing/alerts.js +88 -0
  218. package/dist/purchasing/backorders.d.ts +61 -0
  219. package/dist/purchasing/backorders.js +92 -0
  220. package/dist/purchasing/cost.d.ts +110 -0
  221. package/dist/purchasing/cost.js +86 -0
  222. package/dist/purchasing/index.d.ts +54 -0
  223. package/dist/purchasing/index.js +24 -0
  224. package/dist/purchasing/links.d.ts +263 -0
  225. package/dist/purchasing/links.js +192 -0
  226. package/dist/purchasing/purchase-orders.d.ts +551 -0
  227. package/dist/purchasing/purchase-orders.js +491 -0
  228. package/dist/purchasing/receipts.d.ts +396 -0
  229. package/dist/purchasing/receipts.js +287 -0
  230. package/dist/purchasing/replenishment.d.ts +281 -0
  231. package/dist/purchasing/replenishment.js +240 -0
  232. package/dist/purchasing/returns.d.ts +273 -0
  233. package/dist/purchasing/returns.js +229 -0
  234. package/dist/purchasing/settings.d.ts +152 -0
  235. package/dist/purchasing/settings.js +181 -0
  236. package/dist/purchasing/suppliers.d.ts +476 -0
  237. package/dist/purchasing/suppliers.js +438 -0
  238. package/dist/quotes/index.d.ts +518 -0
  239. package/dist/quotes/index.js +439 -0
  240. package/dist/reports/index.d.ts +71 -8
  241. package/dist/reports/index.js +74 -11
  242. package/dist/returns/index.d.ts +675 -21
  243. package/dist/returns/index.js +756 -11
  244. package/dist/search/index.d.ts +76 -1
  245. package/dist/search/index.js +63 -0
  246. package/dist/seo/config.d.ts +128 -0
  247. package/dist/seo/config.js +95 -0
  248. package/dist/seo/redirects.d.ts +125 -0
  249. package/dist/seo/redirects.js +60 -1
  250. package/dist/seo/settings.d.ts +147 -0
  251. package/dist/seo/settings.js +209 -0
  252. package/dist/shipping/definitions.d.ts +29 -0
  253. package/dist/shipping/definitions.js +47 -0
  254. package/dist/shipping/fulfillment-profiles.d.ts +174 -0
  255. package/dist/shipping/fulfillment-profiles.js +104 -0
  256. package/dist/shipping/hendriks.d.ts +97 -0
  257. package/dist/shipping/hendriks.js +50 -0
  258. package/dist/shipping/index.d.ts +54 -0
  259. package/dist/shipping/index.js +27 -0
  260. package/dist/shipping/methods.d.ts +205 -0
  261. package/dist/shipping/methods.js +240 -0
  262. package/dist/shipping/myparcel.d.ts +668 -0
  263. package/dist/shipping/myparcel.js +466 -0
  264. package/dist/shipping/parcel-count-suggestion.d.ts +2 -0
  265. package/dist/shipping/parcel-rule.d.ts +6 -5
  266. package/dist/shipping/parcel-rule.js +21 -23
  267. package/dist/shipping/rate-tables.d.ts +324 -0
  268. package/dist/shipping/rate-tables.js +390 -0
  269. package/dist/shipping/ship-to-countries.d.ts +75 -0
  270. package/dist/shipping/ship-to-countries.js +47 -0
  271. package/dist/stock/by-location.d.ts +86 -0
  272. package/dist/stock/by-location.js +94 -0
  273. package/dist/stock/index.d.ts +18 -4
  274. package/dist/stock/index.js +7 -0
  275. package/dist/stock/locations.d.ts +144 -0
  276. package/dist/stock/locations.js +168 -0
  277. package/dist/stock/settings.d.ts +106 -0
  278. package/dist/stock/settings.js +103 -0
  279. package/dist/stock/transfers.d.ts +386 -52
  280. package/dist/stock/transfers.js +222 -17
  281. package/dist/stored-value/index.d.ts +312 -11
  282. package/dist/stored-value/index.js +223 -4
  283. package/dist/subscriptions/index.d.ts +682 -0
  284. package/dist/subscriptions/index.js +815 -0
  285. package/dist/tax/display-policy.d.ts +105 -0
  286. package/dist/tax/display-policy.js +84 -0
  287. package/dist/tax/index.d.ts +79 -1
  288. package/dist/tax/index.js +52 -0
  289. package/dist/tenant/email-style.d.ts +102 -0
  290. package/dist/tenant/email-style.js +72 -0
  291. package/dist/tenant/email-templates.d.ts +287 -0
  292. package/dist/tenant/email-templates.js +271 -0
  293. package/dist/tenant/notifications.d.ts +23 -17
  294. package/dist/tenant/settings-sources.d.ts +1 -1
  295. package/dist/tenant/time-zone.d.ts +17 -0
  296. package/dist/tenant/time-zone.js +11 -0
  297. package/dist/types.d.ts +50 -3
  298. package/dist/users/index.d.ts +3 -0
  299. package/dist/users/index.js +2 -0
  300. package/dist/users/warehouses.d.ts +50 -0
  301. package/dist/users/warehouses.js +51 -0
  302. package/package.json +2 -2
@@ -1,25 +1,10 @@
1
- import { createPage, PLATFORM_PASSTHROUGH_KEYS, readRestPage, } from '@sixtyninecommerce/core';
2
- /**
3
- * Every key whose VALUE is stored data, not DTO shape: the platform's passthrough set
4
- * (`credentials`, `public_credentials`, `site_analysis`, `trigger_filter`, …) plus a schema map's
5
- * jsonb columns, which carry the source's own field names and sample records. Converting them
6
- * would rename a sample's `date_created` to `dateCreated`, and a `triggerFilter`'s `toState` would
7
- * reach the reader as `to_state`, which it ignores.
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
+ }