@sixtyninecommerce/admin 0.8.0 → 0.9.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 (161) hide show
  1. package/README.md +2445 -17
  2. package/dist/api-tokens/index.d.ts +15 -5
  3. package/dist/api-tokens/index.js +1 -0
  4. package/dist/apps/event-subscription.d.ts +59 -0
  5. package/dist/apps/event-subscription.js +32 -0
  6. package/dist/apps/hook-secret.d.ts +22 -0
  7. package/dist/apps/hook-secret.js +36 -0
  8. package/dist/apps/index.d.ts +54 -3
  9. package/dist/apps/index.js +17 -0
  10. package/dist/audit/index.d.ts +7 -0
  11. package/dist/auth/queries.d.ts +5 -1
  12. package/dist/auth/queries.js +6 -0
  13. package/dist/bulk/index.d.ts +323 -0
  14. package/dist/bulk/index.js +162 -0
  15. package/dist/catalog/assets.d.ts +147 -0
  16. package/dist/catalog/assets.js +164 -0
  17. package/dist/catalog/collections.d.ts +109 -0
  18. package/dist/catalog/collections.js +91 -0
  19. package/dist/catalog/facets.d.ts +103 -0
  20. package/dist/catalog/facets.js +100 -0
  21. package/dist/catalog/index.d.ts +115 -0
  22. package/dist/catalog/index.js +21 -0
  23. package/dist/catalog/options.d.ts +89 -0
  24. package/dist/catalog/options.js +88 -0
  25. package/dist/catalog/products-bulk.d.ts +21 -0
  26. package/dist/catalog/products-bulk.js +58 -0
  27. package/dist/catalog/products.d.ts +489 -0
  28. package/dist/catalog/products.js +365 -0
  29. package/dist/catalog/properties.d.ts +234 -0
  30. package/dist/catalog/properties.js +158 -0
  31. package/dist/catalog/types.d.ts +174 -0
  32. package/dist/catalog/types.js +141 -0
  33. package/dist/catalog/variants.d.ts +274 -0
  34. package/dist/catalog/variants.js +258 -0
  35. package/dist/catalog/visibility.d.ts +57 -0
  36. package/dist/catalog/visibility.js +59 -0
  37. package/dist/client.d.ts +171 -3
  38. package/dist/client.js +81 -12
  39. package/dist/compliance/index.d.ts +88 -0
  40. package/dist/compliance/index.js +88 -0
  41. package/dist/customer-data-redaction.d.ts +14 -0
  42. package/dist/customer-data-redaction.js +22 -0
  43. package/dist/customers/index.d.ts +428 -17
  44. package/dist/customers/index.js +322 -15
  45. package/dist/documents/index.d.ts +513 -16
  46. package/dist/documents/index.js +229 -3
  47. package/dist/events/index.d.ts +259 -0
  48. package/dist/events/index.js +147 -0
  49. package/dist/feeds/ai.d.ts +46 -0
  50. package/dist/feeds/ai.js +43 -0
  51. package/dist/feeds/categories.d.ts +50 -0
  52. package/dist/feeds/categories.js +56 -0
  53. package/dist/feeds/configs.d.ts +54 -0
  54. package/dist/feeds/configs.js +85 -0
  55. package/dist/feeds/identifier-fields.d.ts +25 -0
  56. package/dist/feeds/identifier-fields.js +29 -0
  57. package/dist/feeds/index.d.ts +54 -0
  58. package/dist/feeds/index.js +46 -0
  59. package/dist/feeds/preserve.d.ts +38 -0
  60. package/dist/feeds/preserve.js +74 -0
  61. package/dist/feeds/rules.d.ts +42 -0
  62. package/dist/feeds/rules.js +56 -0
  63. package/dist/feeds/types.d.ts +654 -0
  64. package/dist/feeds/types.js +135 -0
  65. package/dist/fulfillments/index.d.ts +360 -0
  66. package/dist/fulfillments/index.js +425 -0
  67. package/dist/heads/index.d.ts +60 -0
  68. package/dist/heads/index.js +45 -0
  69. package/dist/hooks/assignments.d.ts +90 -0
  70. package/dist/hooks/assignments.js +53 -0
  71. package/dist/hooks/index.d.ts +97 -0
  72. package/dist/hooks/index.js +76 -0
  73. package/dist/index.d.ts +84 -9
  74. package/dist/index.js +16 -1
  75. package/dist/jobs/bulk-action-run.d.ts +65 -0
  76. package/dist/jobs/bulk-action-run.js +70 -0
  77. package/dist/locations/index.d.ts +6 -2
  78. package/dist/meta/index.d.ts +32 -0
  79. package/dist/orders/commit-edit.d.ts +123 -0
  80. package/dist/orders/commit-edit.js +142 -0
  81. package/dist/orders/get.d.ts +722 -0
  82. package/dist/orders/get.js +530 -0
  83. package/dist/orders/index.d.ts +105 -20
  84. package/dist/orders/index.js +44 -0
  85. package/dist/orders/line-price-preview.d.ts +297 -0
  86. package/dist/orders/line-price-preview.js +50 -0
  87. package/dist/orders/manual-discounts.d.ts +132 -0
  88. package/dist/orders/manual-discounts.js +95 -0
  89. package/dist/orders/manual-pricing.d.ts +192 -0
  90. package/dist/orders/manual-pricing.js +72 -0
  91. package/dist/orders/order-entry.d.ts +566 -0
  92. package/dist/orders/order-entry.js +129 -0
  93. package/dist/orders/parcel-charge.d.ts +45 -0
  94. package/dist/orders/parcel-charge.js +55 -0
  95. package/dist/orders/plan-edit.d.ts +489 -0
  96. package/dist/orders/plan-edit.js +93 -0
  97. package/dist/orders/status.d.ts +235 -0
  98. package/dist/orders/status.js +96 -0
  99. package/dist/orders/surcharges.d.ts +75 -2
  100. package/dist/orders/surcharges.js +110 -4
  101. package/dist/payments/account-credit.d.ts +171 -0
  102. package/dist/payments/account-credit.js +107 -0
  103. package/dist/payments/index.d.ts +234 -9
  104. package/dist/payments/index.js +90 -0
  105. package/dist/platform/catalog.d.ts +148 -0
  106. package/dist/platform/catalog.js +74 -0
  107. package/dist/platform/entitlements.d.ts +111 -0
  108. package/dist/platform/entitlements.js +86 -0
  109. package/dist/platform/legal-entities.d.ts +139 -0
  110. package/dist/platform/legal-entities.js +126 -0
  111. package/dist/platform/legal-entity-numbering.d.ts +103 -0
  112. package/dist/platform/legal-entity-numbering.js +101 -0
  113. package/dist/platform/organization-membership.d.ts +80 -0
  114. package/dist/platform/organization-membership.js +71 -0
  115. package/dist/platform/organization-settings.d.ts +33 -0
  116. package/dist/platform/organization-settings.js +42 -0
  117. package/dist/platform/organization-sharing.d.ts +172 -0
  118. package/dist/platform/organization-sharing.js +120 -0
  119. package/dist/platform/organizations.d.ts +205 -0
  120. package/dist/platform/organizations.js +180 -0
  121. package/dist/platform/provision-store.d.ts +88 -8
  122. package/dist/platform/provision-store.js +12 -0
  123. package/dist/platform/reports.d.ts +46 -0
  124. package/dist/platform/reports.js +31 -0
  125. package/dist/platform/server-metrics.d.ts +58 -0
  126. package/dist/platform/server-metrics.js +11 -0
  127. package/dist/pos/index.d.ts +567 -0
  128. package/dist/pos/index.js +205 -0
  129. package/dist/pricing/index.d.ts +405 -0
  130. package/dist/pricing/index.js +351 -0
  131. package/dist/provenance/index.d.ts +93 -0
  132. package/dist/provenance/index.js +53 -0
  133. package/dist/reports/index.d.ts +199 -0
  134. package/dist/reports/index.js +154 -0
  135. package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
  136. package/dist/shipping/parcel-count-suggestion.js +73 -0
  137. package/dist/shipping/parcel-rule.d.ts +48 -0
  138. package/dist/shipping/parcel-rule.js +91 -0
  139. package/dist/stock/index.d.ts +166 -0
  140. package/dist/stock/index.js +115 -0
  141. package/dist/stock/transfers.d.ts +520 -0
  142. package/dist/stock/transfers.js +437 -0
  143. package/dist/stored-value/index.d.ts +154 -0
  144. package/dist/stored-value/index.js +130 -0
  145. package/dist/tenant/capabilities.d.ts +10 -0
  146. package/dist/tenant/entitlements.d.ts +35 -0
  147. package/dist/tenant/entitlements.js +15 -0
  148. package/dist/tenant/field-groups.d.ts +64 -0
  149. package/dist/tenant/field-groups.js +34 -0
  150. package/dist/tenant/legal-entity.d.ts +32 -0
  151. package/dist/tenant/legal-entity.js +41 -0
  152. package/dist/tenant/notifications.d.ts +148 -0
  153. package/dist/tenant/notifications.js +79 -0
  154. package/dist/tenant/retention.d.ts +10 -4
  155. package/dist/tenant/settings-sources.d.ts +103 -0
  156. package/dist/tenant/settings-sources.js +92 -0
  157. package/dist/types.d.ts +86 -11
  158. package/dist/uploads/index.d.ts +11 -7
  159. package/dist/users/index.d.ts +104 -0
  160. package/dist/users/index.js +80 -0
  161. package/package.json +3 -2
@@ -0,0 +1,130 @@
1
+ const ACCOUNT_FIELDS = `
2
+ id
3
+ createdAt
4
+ updatedAt
5
+ kind
6
+ unit
7
+ customerId
8
+ codeLast4
9
+ expiresAt
10
+ expiresOn
11
+ status
12
+ balance
13
+ spendable
14
+ `;
15
+ export const STORED_VALUE_ACCOUNTS_QUERY = /* GraphQL */ `
16
+ query StoredValueAccounts($customerId: ID, $kind: String, $take: Int, $skip: Int) {
17
+ storedValueAccounts(customerId: $customerId, kind: $kind, take: $take, skip: $skip) {
18
+ items { ${ACCOUNT_FIELDS} }
19
+ totalItems
20
+ }
21
+ }
22
+ `;
23
+ export const STORED_VALUE_ACCOUNT_QUERY = /* GraphQL */ `
24
+ query StoredValueAccount($id: ID!) {
25
+ storedValueAccount(id: $id) { ${ACCOUNT_FIELDS} }
26
+ }
27
+ `;
28
+ export const STORED_VALUE_STATEMENT_QUERY = /* GraphQL */ `
29
+ query StoredValueStatement($accountId: ID!, $from: DateTime, $to: DateTime, $take: Int, $skip: Int) {
30
+ storedValueStatement(accountId: $accountId, from: $from, to: $to, take: $take, skip: $skip) {
31
+ account { ${ACCOUNT_FIELDS} }
32
+ openingBalance
33
+ closingBalance
34
+ totalItems
35
+ entries {
36
+ id createdAt occurredAt entryType amount balanceAfter unit sourceType sourceId paymentId
37
+ orderId locationId actorType actorId onBehalfOfRef deviceRef reason
38
+ }
39
+ }
40
+ }
41
+ `;
42
+ export const STORED_VALUE_LIABILITY_QUERY = /* GraphQL */ `
43
+ query StoredValueLiability($at: DateTime, $kind: String, $locationId: ID) {
44
+ storedValueLiability(at: $at, kind: $kind, locationId: $locationId) {
45
+ unit kind accounts issued redeemed reversed adjusted expired forfeited outstanding
46
+ }
47
+ }
48
+ `;
49
+ export const ISSUE_STORED_VALUE_MUTATION = /* GraphQL */ `
50
+ mutation IssueStoredValue($input: IssueStoredValueInput!, $idempotencyKey: String) {
51
+ issueStoredValue(input: $input, idempotencyKey: $idempotencyKey) { ${ACCOUNT_FIELDS} }
52
+ }
53
+ `;
54
+ export const ADJUST_STORED_VALUE_MUTATION = /* GraphQL */ `
55
+ mutation AdjustStoredValue($input: AdjustStoredValueInput!, $idempotencyKey: String) {
56
+ adjustStoredValue(input: $input, idempotencyKey: $idempotencyKey) { ${ACCOUNT_FIELDS} }
57
+ }
58
+ `;
59
+ export const BLOCK_STORED_VALUE_MUTATION = /* GraphQL */ `
60
+ mutation BlockStoredValueAccount($id: ID!, $reason: String!) {
61
+ blockStoredValueAccount(id: $id, reason: $reason) { ${ACCOUNT_FIELDS} }
62
+ }
63
+ `;
64
+ export const UNBLOCK_STORED_VALUE_MUTATION = /* GraphQL */ `
65
+ mutation UnblockStoredValueAccount($id: ID!, $reason: String!) {
66
+ unblockStoredValueAccount(id: $id, reason: $reason) { ${ACCOUNT_FIELDS} }
67
+ }
68
+ `;
69
+ export const SET_STORED_VALUE_EXPIRY_MUTATION = /* GraphQL */ `
70
+ mutation SetStoredValueAccountExpiry(
71
+ $id: ID!
72
+ $expiresOn: String
73
+ $reason: String!
74
+ $idempotencyKey: String
75
+ ) {
76
+ setStoredValueAccountExpiry(
77
+ id: $id
78
+ expiresOn: $expiresOn
79
+ reason: $reason
80
+ idempotencyKey: $idempotencyKey
81
+ ) { ${ACCOUNT_FIELDS} }
82
+ }
83
+ `;
84
+ export const REDEEM_STORED_VALUE_MUTATION = /* GraphQL */ `
85
+ mutation RedeemStoredValue($input: RedeemStoredValueInput!, $idempotencyKey: String) {
86
+ redeemStoredValue(input: $input, idempotencyKey: $idempotencyKey) {
87
+ orderId
88
+ paymentId
89
+ accountId
90
+ amount
91
+ }
92
+ }
93
+ `;
94
+ export function createStoredValueModule(transport) {
95
+ const read = async (query, variables, options) => transport.graphql({ api: 'admin', query, variables }, options);
96
+ // The key is an ARGUMENT on the Admin API; `undefined` keeps an omitted key off the wire.
97
+ const write = async (query, variables, options) => transport.graphql({ api: 'admin', query, variables: { ...variables, idempotencyKey: options?.idempotencyKey } }, options);
98
+ return {
99
+ async accounts(filter = {}, options) {
100
+ return (await read(STORED_VALUE_ACCOUNTS_QUERY, filter, options)).storedValueAccounts;
101
+ },
102
+ async account(id, options) {
103
+ return (await read(STORED_VALUE_ACCOUNT_QUERY, { id }, options)).storedValueAccount;
104
+ },
105
+ async statement(accountId, window = {}, options) {
106
+ return (await read(STORED_VALUE_STATEMENT_QUERY, { accountId, ...window }, options)).storedValueStatement;
107
+ },
108
+ async liability(filter = {}, options) {
109
+ return (await read(STORED_VALUE_LIABILITY_QUERY, filter, options)).storedValueLiability;
110
+ },
111
+ async issue(input, options) {
112
+ return (await write(ISSUE_STORED_VALUE_MUTATION, { input }, options)).issueStoredValue;
113
+ },
114
+ async adjust(input, options) {
115
+ return (await write(ADJUST_STORED_VALUE_MUTATION, { input }, options)).adjustStoredValue;
116
+ },
117
+ async block(id, reason, options) {
118
+ return (await read(BLOCK_STORED_VALUE_MUTATION, { id, reason }, options)).blockStoredValueAccount;
119
+ },
120
+ async unblock(id, reason, options) {
121
+ return (await read(UNBLOCK_STORED_VALUE_MUTATION, { id, reason }, options)).unblockStoredValueAccount;
122
+ },
123
+ async setExpiry(id, expiresOn, reason, options) {
124
+ return (await write(SET_STORED_VALUE_EXPIRY_MUTATION, { id, expiresOn, reason }, options)).setStoredValueAccountExpiry;
125
+ },
126
+ async redeem(input, options) {
127
+ return (await write(REDEEM_STORED_VALUE_MUTATION, { input }, options)).redeemStoredValue;
128
+ },
129
+ };
130
+ }
@@ -83,6 +83,16 @@ export interface CapabilityManifest {
83
83
  * Optional because a platform older than this field simply does not send it.
84
84
  */
85
85
  assertionAudience?: string | null;
86
+ /**
87
+ * Whether this store is a sandbox (`platform.provisionStore({ sandbox: true })`,
88
+ * spec §6.6): test-mode payments only, no ERP or marketing push, left out of
89
+ * platform metrics, purged past retention holds. Fixed at provisioning.
90
+ *
91
+ * `null` when the platform could not read the flag this time (such an answer
92
+ * is never cached, so `refresh()` asks again); absent from a platform older
93
+ * than this field. Treat anything but `true` as "not a sandbox".
94
+ */
95
+ sandbox?: boolean | null;
86
96
  /** A member added after this SDK release arrives camelised and untouched. */
87
97
  [extra: string]: unknown;
88
98
  }
@@ -0,0 +1,35 @@
1
+ import type { Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ /** Where a plugin grant of the active store comes from. */
4
+ export type TenantEntitlementSource = 'channel' | 'organization';
5
+ /**
6
+ * One plugin the ACTIVE store may use: its own grant, or one its organization holds for every
7
+ * member store and its workspace. A plugin with no row is not granted.
8
+ */
9
+ export interface TenantEntitlement {
10
+ /** The plugin's catalog key (`pos`, `afas`, `product-feeds`, …), exactly as the engine sends it. */
11
+ pluginKey: string;
12
+ /**
13
+ * `channel` for the store's own grant, `organization` for one granted to its whole organization.
14
+ * When both exist the store's own row wins, so a key appears once. A string on the wire: a newer
15
+ * engine may answer a source this SDK does not name yet.
16
+ */
17
+ source: TenantEntitlementSource | (string & {});
18
+ }
19
+ /**
20
+ * The plugin grants of the ACTIVE store (plan W9-4, spec §9.2 `tenant` "entitlements (read)").
21
+ *
22
+ * `admin.tenant.entitlements(call?)` reads the engine's own union of the store's and its
23
+ * organization's grants, the one it reads to allow a plugin's back-office operations. The grant
24
+ * rows are read on every call, so a grant switched in Platform → Plugins answers on the next one.
25
+ * Only which organization a store belongs to is remembered, up to 60 s per server process: a store
26
+ * that has just joined an organization may take that long to list its organization's grants.
27
+ *
28
+ * Needs `ReadCatalog` or `ReadSettings` on the store; without either the engine answers
29
+ * `FORBIDDEN` ({@link PermissionDeniedError}, 403). No channel argument: a tenant-scoped
30
+ * credential reads its own store only, and a SuperAdmin reads any store with that store as the
31
+ * tenant.
32
+ */
33
+ export type TenantEntitlementsReader = (call?: Call) => Promise<TenantEntitlement[]>;
34
+ export declare const ACTIVE_PLUGIN_ENTITLEMENTS_QUERY = "\n query TenantEntitlements {\n activePluginEntitlements {\n pluginKey\n source\n }\n }\n";
35
+ export declare function createTenantEntitlementsReader(transport: Transport): TenantEntitlementsReader;
@@ -0,0 +1,15 @@
1
+ export const ACTIVE_PLUGIN_ENTITLEMENTS_QUERY = /* GraphQL */ `
2
+ query TenantEntitlements {
3
+ activePluginEntitlements {
4
+ pluginKey
5
+ source
6
+ }
7
+ }
8
+ `;
9
+ export function createTenantEntitlementsReader(transport) {
10
+ return async (call) => {
11
+ const data = await transport.graphql({ api: 'admin', query: ACTIVE_PLUGIN_ENTITLEMENTS_QUERY }, call);
12
+ // Only the two fields this type promises, whatever else a newer engine adds to a row.
13
+ return data.activePluginEntitlements.map(({ pluginKey, source }) => ({ pluginKey, source }));
14
+ };
15
+ }
@@ -0,0 +1,64 @@
1
+ import type { Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ /**
4
+ * One field group's owner on the ACTIVE store: the app installation or personal API token the
5
+ * store handed it to. A store hands each of the four groups — `price` (Price), `stock` (Stock),
6
+ * `product_details` (Product details), `customer_details` (Customer details) — to at most one
7
+ * owner at a time.
8
+ */
9
+ export interface FieldGroupOwnership {
10
+ /** `price` | `stock` | `product_details` | `customer_details`. */
11
+ group: string;
12
+ /** `app_installation` | `api_token`. A string on the wire: read an unknown value as neither. */
13
+ ownerKind: 'app_installation' | 'api_token' | (string & {});
14
+ /** The installation id or the API token id. */
15
+ ownerId: string;
16
+ /** The app's name, or the API token's name. */
17
+ label: string;
18
+ /** The app's slug for an installation; null for an API token. */
19
+ appSlug: string | null;
20
+ /**
21
+ * True while the owner is suspended (the store suspended the installation, or the platform
22
+ * suspended the app): its claim stands, so no other app or token can be granted the group,
23
+ * but nothing is held for it until it resumes. Always false for an API token.
24
+ */
25
+ paused: boolean;
26
+ }
27
+ /**
28
+ * A field group a writer built into the platform already writes into the active store, so it
29
+ * cannot be handed to an app or API token there (a grant naming it is refused
30
+ * `FIELD_GROUP_ERP_SYNCED`).
31
+ */
32
+ export interface FieldGroupSyncSource {
33
+ /** `price` | `stock` | `product_details` | `customer_details`. */
34
+ group: string;
35
+ /**
36
+ * `exact`: Exact Online is connected and writes all four groups (disconnect it first).
37
+ * `organization`: the store belongs to an organization, which can share its catalogue into it
38
+ * at any time and then writes its prices and product details, so they stay with it whether or
39
+ * not it shares them today. `workspace`: the channel is an organization's workspace, whose
40
+ * prices and product details are the catalogue the member stores receive. A string on the
41
+ * wire.
42
+ */
43
+ source: 'exact' | 'organization' | 'workspace' | (string & {});
44
+ /** `Exact Online`, or the organization's name (`organization` and `workspace`). */
45
+ label: string;
46
+ }
47
+ /**
48
+ * Every field group's owner on the ACTIVE store (`admin.tenant.fieldGroupOwners(call?)`), in
49
+ * group order. A group with no entry has no owner. Needs a permission that reads or changes a
50
+ * group's data (`ReadCatalog`, `ReadProduct`, `ReadCustomer`, `ReadSettings`, or a group's write
51
+ * permission), `InstallApps` or `ManageApiToken`; anything else is `FORBIDDEN`.
52
+ */
53
+ export type FieldGroupOwnersReader = (call?: Call) => Promise<FieldGroupOwnership[]>;
54
+ /**
55
+ * The field groups a built-in writer already writes into the ACTIVE store
56
+ * (`admin.tenant.fieldGroupSyncSources(call?)`), one entry per group and writer. Same
57
+ * permissions as {@link FieldGroupOwnersReader}.
58
+ */
59
+ export type FieldGroupSyncSourcesReader = (call?: Call) => Promise<FieldGroupSyncSource[]>;
60
+ export declare const FIELD_GROUP_OWNERSHIP_FIELDS = "\n group\n ownerKind\n ownerId\n label\n appSlug\n paused\n";
61
+ export declare const FIELD_GROUP_OWNERS_QUERY = "\n query FieldGroupOwners {\n fieldGroupOwners { \n group\n ownerKind\n ownerId\n label\n appSlug\n paused\n }\n }\n";
62
+ export declare const FIELD_GROUP_SYNC_SOURCES_QUERY = "\n query FieldGroupSyncSources {\n fieldGroupSyncSources {\n group\n source\n label\n }\n }\n";
63
+ export declare function createFieldGroupOwnersReader(transport: Transport): FieldGroupOwnersReader;
64
+ export declare function createFieldGroupSyncSourcesReader(transport: Transport): FieldGroupSyncSourcesReader;
@@ -0,0 +1,34 @@
1
+ export const FIELD_GROUP_OWNERSHIP_FIELDS = /* GraphQL */ `
2
+ group
3
+ ownerKind
4
+ ownerId
5
+ label
6
+ appSlug
7
+ paused
8
+ `;
9
+ export const FIELD_GROUP_OWNERS_QUERY = /* GraphQL */ `
10
+ query FieldGroupOwners {
11
+ fieldGroupOwners { ${FIELD_GROUP_OWNERSHIP_FIELDS} }
12
+ }
13
+ `;
14
+ export const FIELD_GROUP_SYNC_SOURCES_QUERY = /* GraphQL */ `
15
+ query FieldGroupSyncSources {
16
+ fieldGroupSyncSources {
17
+ group
18
+ source
19
+ label
20
+ }
21
+ }
22
+ `;
23
+ export function createFieldGroupOwnersReader(transport) {
24
+ return async (call) => {
25
+ const data = await transport.graphql({ api: 'admin', query: FIELD_GROUP_OWNERS_QUERY }, call);
26
+ return data.fieldGroupOwners;
27
+ };
28
+ }
29
+ export function createFieldGroupSyncSourcesReader(transport) {
30
+ return async (call) => {
31
+ const data = await transport.graphql({ api: 'admin', query: FIELD_GROUP_SYNC_SOURCES_QUERY }, call);
32
+ return data.fieldGroupSyncSources;
33
+ };
34
+ }
@@ -0,0 +1,32 @@
1
+ import { type Transport } from '@sixtyninecommerce/core';
2
+ import { type LegalEntity, type UpdateLegalEntityInput } from '../platform/legal-entities.js';
3
+ import { type TenantLegalEntityNumberingModule } from '../platform/legal-entity-numbering.js';
4
+ import type { Call } from '../types.js';
5
+ /**
6
+ * `admin.tenant.legalEntity` — the ACTIVE store's legal entity: the company its invoices name
7
+ * (multi-region spec §7.1). Reading it needs `ReadSettings` on the store. Changing it needs
8
+ * `UpdateSeller` on EVERY store of the entity, because the entity prints on all of their invoices;
9
+ * a store sharing its entity with stores the caller does not run reads it with `editable: false`,
10
+ * and `update` is refused with `SHARED_SELLER_NOT_ALLOWED` (403).
11
+ */
12
+ export interface LegalEntityModule {
13
+ /** Null on a store with no legal entity, an organization's workspace and the root channel. */
14
+ get(call?: Call): Promise<LegalEntity | null>;
15
+ /**
16
+ * Changes the fields given and answers the entity. Its legal name, VAT, EORI and registration
17
+ * numbers are copied onto every one of its stores in the same transaction; the registered office
18
+ * and time zone are the entity's alone. Two requests: the active store's entity is read first,
19
+ * and a store with none is refused with `LEGAL_ENTITY_NOT_FOUND` (404) before anything is
20
+ * written. Refusals from the engine: `SHARED_SELLER_NOT_ALLOWED` (403),
21
+ * `LEGAL_ENTITY_FIELD_INVALID`, `INVALID_COUNTRY_CODE`, `INVALID_TIMEZONE` (400).
22
+ */
23
+ update(input: UpdateLegalEntityInput, call?: Call): Promise<LegalEntity>;
24
+ /**
25
+ * The numbering series the active store's legal entity shares with its other stores (B6c):
26
+ * `get` needs `ReadSettings` on the store; `update` needs `UpdateSettings` on EVERY store of the
27
+ * entity (`SHARED_SELLER_NOT_ALLOWED`, 403).
28
+ */
29
+ numbering: TenantLegalEntityNumberingModule;
30
+ }
31
+ export declare const ACTIVE_LEGAL_ENTITY_QUERY = "\n query ActiveLegalEntity {\n activeLegalEntity { \n id\n name\n legalName\n vatNumber\n vatNumberUk\n eoriNumber\n registrationNumber\n registeredAddressLine1\n registeredAddressLine2\n registeredCity\n registeredPostalCode\n registeredProvince\n registeredCountry\n timezone\n stores {\n id\n code\n }\n storeCount\n editable\n createdAt\n updatedAt\n }\n }\n";
32
+ export declare function createLegalEntityModule(transport: Transport): LegalEntityModule;
@@ -0,0 +1,41 @@
1
+ import { NotFoundError } from '@sixtyninecommerce/core';
2
+ import { LEGAL_ENTITY_FIELDS, legalEntityInputOf, UPDATE_LEGAL_ENTITY_MUTATION, } from '../platform/legal-entities.js';
3
+ import { createTenantLegalEntityNumberingModule, } from '../platform/legal-entity-numbering.js';
4
+ export const ACTIVE_LEGAL_ENTITY_QUERY = /* GraphQL */ `
5
+ query ActiveLegalEntity {
6
+ activeLegalEntity { ${LEGAL_ENTITY_FIELDS} }
7
+ }
8
+ `;
9
+ const ACTIVE_LEGAL_ENTITY_ID_QUERY = /* GraphQL */ `
10
+ query ActiveLegalEntityId {
11
+ activeLegalEntity { id }
12
+ }
13
+ `;
14
+ export function createLegalEntityModule(transport) {
15
+ return {
16
+ numbering: createTenantLegalEntityNumberingModule(transport),
17
+ async get(call) {
18
+ const data = await transport.graphql({ api: 'admin', query: ACTIVE_LEGAL_ENTITY_QUERY }, call);
19
+ return data.activeLegalEntity;
20
+ },
21
+ async update(input, call) {
22
+ const active = await transport.graphql({ api: 'admin', query: ACTIVE_LEGAL_ENTITY_ID_QUERY }, call);
23
+ const id = active.activeLegalEntity?.id;
24
+ if (!id) {
25
+ throw new NotFoundError({
26
+ code: 'LEGAL_ENTITY_NOT_FOUND',
27
+ message: 'The active store has no legal entity to change.',
28
+ status: 404,
29
+ service: 'vendure',
30
+ });
31
+ }
32
+ const { name: _ignored, ...fields } = input;
33
+ const data = await transport.graphql({
34
+ api: 'admin',
35
+ query: UPDATE_LEGAL_ENTITY_MUTATION,
36
+ variables: { id, input: legalEntityInputOf(fields) },
37
+ }, call);
38
+ return data.updateLegalEntity;
39
+ },
40
+ };
41
+ }
@@ -0,0 +1,148 @@
1
+ import type { Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ /**
4
+ * Who sends a notification event's email for this store (headless spec §8.15).
5
+ *
6
+ * - `platform` — the platform renders and sends it. The default.
7
+ * - `none` — the platform does not. Your own system sends something in its
8
+ * place, triggered by the matching platform event (`admin.events`; the
9
+ * entry's `replacementEventTypes` names it), and every
10
+ * dispatch the platform skips is still recorded on the store's email delivery
11
+ * log (status `skipped`, `metadata.skipReason: 'SENDER_NONE'`), so the
12
+ * silence is auditable.
13
+ *
14
+ * The merchant-facing labels are `admin.meta.catalog().labels.notificationSender`.
15
+ */
16
+ export type NotificationSender = 'platform' | 'none';
17
+ /**
18
+ * One piece of a recipient field: a literal address (`text`) or a variable the
19
+ * platform resolves per email (`var`, e.g. `customer_email`). A field's segments
20
+ * are joined into one comma-separated list at send time.
21
+ */
22
+ export type NotificationRecipientSegment = {
23
+ type: 'text';
24
+ value: string;
25
+ } | {
26
+ type: 'var';
27
+ key: string;
28
+ };
29
+ export interface NotificationRecipients {
30
+ to: NotificationRecipientSegment[];
31
+ cc: NotificationRecipientSegment[];
32
+ bcc: NotificationRecipientSegment[];
33
+ }
34
+ /** One notification event's configuration on the active store. */
35
+ export interface NotificationEventConfig {
36
+ id: string;
37
+ /** The notification event key — `order_placed`, `magic_link_login`, … */
38
+ event: string;
39
+ /**
40
+ * `false` switches the email off altogether; nothing is logged for it. Always
41
+ * `true` for `password_reset`, `email_change` and `account_setup`, which
42
+ * cannot be switched off.
43
+ */
44
+ enabled: boolean;
45
+ sender: NotificationSender;
46
+ recipients: NotificationRecipients;
47
+ /** The email template the platform renders for it. */
48
+ templateType: string;
49
+ label: string;
50
+ description: string;
51
+ /** `order`, `customer`, `account` (the identity emails), `product` or `operational`. */
52
+ category: string;
53
+ /**
54
+ * The platform event types your system sends this email from when `sender`
55
+ * is `none` — `['order.created']` for `order_placed`. `null` when no event
56
+ * the platform emits replaces it; `sender: 'none'` is then refused.
57
+ */
58
+ replacementEventTypes: string[] | null;
59
+ }
60
+ export interface UpdateNotificationEventConfigInput {
61
+ /**
62
+ * `false` is refused with `EVENT_ALWAYS_ENABLED` ({@link ValidationError}) on
63
+ * `password_reset`, `email_change` and `account_setup`: each is the only way
64
+ * a customer resets a password (or sets the first one on an imported
65
+ * account), confirms a new address, or finishes a storefront registration,
66
+ * so they are always sent. `magic_link_login` and `customer_invite` can be
67
+ * switched off.
68
+ */
69
+ enabled?: boolean;
70
+ /**
71
+ * `none` is refused on the identity emails — the `account` category:
72
+ * password reset, magic link, invitation, email change, email and chat
73
+ * verification — with `EVENT_ALWAYS_PLATFORM_SENT` ({@link ValidationError}).
74
+ * Each carries a one-time credential only the platform can issue, and no
75
+ * platform event carries one, so there is no event your system could send it
76
+ * from. What you control there is where its link lands (your head's URL).
77
+ *
78
+ * `none` is also refused on an email whose `replacementEventTypes` is `null`,
79
+ * with `NO_REPLACEMENT_EVENT` ({@link ValidationError}): no platform event is
80
+ * emitted when it is due, so your system would never know to send it.
81
+ */
82
+ sender?: NotificationSender;
83
+ /**
84
+ * Each list REPLACES that field's segments; an omitted list is left as it is.
85
+ *
86
+ * The identity emails (the `account` category) and `new_customer` carry sign-in
87
+ * links or codes, and one login serves the person in every store, so they go to
88
+ * the account holder alone: `to` is the `customer_email` variable, `cc` and
89
+ * `bcc` are empty, and `list` answers exactly that whatever was stored before.
90
+ * Any other list on them is refused with `IDENTITY_EMAIL_RECIPIENTS_FIXED`
91
+ * ({@link ValidationError}); resending the lists `list` answered is accepted.
92
+ */
93
+ recipients?: Partial<NotificationRecipients>;
94
+ }
95
+ /**
96
+ * The store's transactional emails (`admin.tenant.notifications`) — per event:
97
+ * on or off, who sends it, and to whom.
98
+ *
99
+ * Always the ACTIVE channel; no channel argument. Reading and writing both need
100
+ * `UpdateSettings`. The list is small and fixed (one entry per notification
101
+ * event the platform knows), so it is a plain array rather than a page.
102
+ *
103
+ * Every accepted update puts ONE `notification_config.changed` on the event
104
+ * stream with the resulting `enabled` and `sender` (never the recipients), so a
105
+ * head that sends some emails itself can start or stop without polling.
106
+ *
107
+ * **A store that takes `settings.email` from its organization**
108
+ * (`admin.tenant.settingsSources`: locked, or inheritable and not overridden)
109
+ * sends the emails the platform sends for it by the organization's `enabled`
110
+ * and recipient lists, or by the platform's default for an event the
111
+ * organization has none for. `sender` stays the store's own, and an email
112
+ * your system already sends (`sender: 'none'`) keeps the store's own entry
113
+ * whole: the organization never changes what your head must do. `list`
114
+ * answers what applies (a stable `id` also for an event the store holds no
115
+ * entry of its own for), and `update` refuses any change, `sender` included;
116
+ * resending an entry as `list` answered it is accepted and changes nothing.
117
+ * `override('settings.email')` first to change them. The From address stays
118
+ * the store's own gateway's.
119
+ *
120
+ * **Coded refusals**, GraphQL errors mapped by `core`:
121
+ * - `EVENT_ALWAYS_PLATFORM_SENT` → {@link ValidationError}: `sender: 'none'` on
122
+ * an identity email. The message names the event; `details[0].field` is
123
+ * `/sender`.
124
+ * - `EVENT_ALWAYS_ENABLED` → {@link ValidationError}: `enabled: false` on
125
+ * `password_reset`, `email_change` or `account_setup`. The message names the
126
+ * event and why it cannot be switched off; `details[0].field` is `/enabled`.
127
+ * - `NO_REPLACEMENT_EVENT` → {@link ValidationError}: `sender: 'none'` on an
128
+ * email no emitted platform event replaces (`replacementEventTypes: null`).
129
+ * The message names the event, and the event planned to replace it when one
130
+ * is; `details[0].field` is `/sender`.
131
+ * - `IDENTITY_EMAIL_RECIPIENTS_FIXED` → {@link ValidationError}: a recipient
132
+ * list on an identity email (or `new_customer`) that names anyone but the
133
+ * account holder. `details[].field` names each offending list
134
+ * (`/recipientTo`, `/recipientCc`, `/recipientBcc`).
135
+ * - `INVALID_NOTIFICATION_SENDER` → {@link ValidationError}.
136
+ * - `NOTIFICATION_EVENT_NOT_FOUND` → {@link NotFoundError}: no such event key.
137
+ * - `ORGANIZATION_SETTINGS_INHERITED` → {@link BusinessRuleError} (422): the
138
+ * store inherits `settings.email` from its organization; override it first.
139
+ * - `MANAGED_BY_ORGANIZATION` → {@link BusinessRuleError} (422): the
140
+ * organization locked `settings.email`.
141
+ */
142
+ export interface NotificationsModule {
143
+ list(options?: Call): Promise<NotificationEventConfig[]>;
144
+ update(event: string, input: UpdateNotificationEventConfigInput, options?: Call): Promise<NotificationEventConfig>;
145
+ }
146
+ export declare const NOTIFICATION_CONFIGS_QUERY = "\n query NotificationEventConfigs {\n notificationEventConfigs { \n id\n event\n enabled\n sender\n recipientTo { type value key }\n recipientCc { type value key }\n recipientBcc { type value key }\n templateType\n label\n description\n category\n replacementEventTypes\n }\n }\n";
147
+ export declare const UPDATE_NOTIFICATION_CONFIG_MUTATION = "\n mutation UpdateNotificationEventConfig(\n $event: String!\n $input: UpdateNotificationEventConfigInput!\n ) {\n updateNotificationEventConfig(event: $event, input: $input) { \n id\n event\n enabled\n sender\n recipientTo { type value key }\n recipientCc { type value key }\n recipientBcc { type value key }\n templateType\n label\n description\n category\n replacementEventTypes\n }\n }\n";
148
+ export declare function createNotificationsModule(transport: Transport): NotificationsModule;
@@ -0,0 +1,79 @@
1
+ const CONFIG_FIELDS = /* GraphQL */ `
2
+ id
3
+ event
4
+ enabled
5
+ sender
6
+ recipientTo { type value key }
7
+ recipientCc { type value key }
8
+ recipientBcc { type value key }
9
+ templateType
10
+ label
11
+ description
12
+ category
13
+ replacementEventTypes
14
+ `;
15
+ export const NOTIFICATION_CONFIGS_QUERY = /* GraphQL */ `
16
+ query NotificationEventConfigs {
17
+ notificationEventConfigs { ${CONFIG_FIELDS} }
18
+ }
19
+ `;
20
+ export const UPDATE_NOTIFICATION_CONFIG_MUTATION = /* GraphQL */ `
21
+ mutation UpdateNotificationEventConfig(
22
+ $event: String!
23
+ $input: UpdateNotificationEventConfigInput!
24
+ ) {
25
+ updateNotificationEventConfig(event: $event, input: $input) { ${CONFIG_FIELDS} }
26
+ }
27
+ `;
28
+ function fromWireSegment(segment) {
29
+ // The server accepts any `type`; only null members are dropped, never a value.
30
+ return {
31
+ type: segment.type,
32
+ ...(segment.value != null ? { value: segment.value } : {}),
33
+ ...(segment.key != null ? { key: segment.key } : {}),
34
+ };
35
+ }
36
+ function fromWire(config) {
37
+ return {
38
+ id: config.id,
39
+ event: config.event,
40
+ enabled: config.enabled,
41
+ sender: config.sender,
42
+ recipients: {
43
+ to: config.recipientTo.map(fromWireSegment),
44
+ cc: config.recipientCc.map(fromWireSegment),
45
+ bcc: config.recipientBcc.map(fromWireSegment),
46
+ },
47
+ templateType: config.templateType,
48
+ label: config.label,
49
+ description: config.description,
50
+ category: config.category,
51
+ replacementEventTypes: config.replacementEventTypes ?? null,
52
+ };
53
+ }
54
+ function toWireInput(input) {
55
+ const { recipients } = input;
56
+ return {
57
+ ...(input.enabled !== undefined ? { enabled: input.enabled } : {}),
58
+ ...(input.sender !== undefined ? { sender: input.sender } : {}),
59
+ ...(recipients?.to !== undefined ? { recipientTo: recipients.to } : {}),
60
+ ...(recipients?.cc !== undefined ? { recipientCc: recipients.cc } : {}),
61
+ ...(recipients?.bcc !== undefined ? { recipientBcc: recipients.bcc } : {}),
62
+ };
63
+ }
64
+ export function createNotificationsModule(transport) {
65
+ return {
66
+ async list(options) {
67
+ const data = await transport.graphql({ api: 'admin', query: NOTIFICATION_CONFIGS_QUERY }, options);
68
+ return data.notificationEventConfigs.map(fromWire);
69
+ },
70
+ async update(event, input, options) {
71
+ const data = await transport.graphql({
72
+ api: 'admin',
73
+ query: UPDATE_NOTIFICATION_CONFIG_MUTATION,
74
+ variables: { event, input: toWireInput(input) },
75
+ }, options);
76
+ return fromWire(data.updateNotificationEventConfig);
77
+ },
78
+ };
79
+ }
@@ -19,7 +19,9 @@ export interface ChannelRetention {
19
19
  retentionClass: string;
20
20
  /**
21
21
  * A time-based hold, independent of {@link holds}. While it is in the future
22
- * the store cannot be hard-deleted, with no holder involved.
22
+ * the store cannot be hard-deleted, with no holder involved. It does NOT
23
+ * refuse a customer erasure (`customers.erase`): the window protects the
24
+ * store's records, which an erasure keeps. To block erasures, place a hold.
23
25
  */
24
26
  holdUntil: string | null;
25
27
  holds: RetentionHold[];
@@ -44,8 +46,10 @@ export interface SetRetentionInput {
44
46
  * **Coded refusals**, all delivered as GraphQL errors with `extensions.code`
45
47
  * and mapped by `core` onto the error hierarchy:
46
48
  * - `RETENTION_HOLD_ACTIVE` → {@link BusinessRuleError}: `hardDeleteChannel`
47
- * refused because a hold stands. It is thrown by the delete, not by these
48
- * methods.
49
+ * refused because a hold or the `holdUntil` window stands, or
50
+ * `customers.erase` refused because a named hold stands (the window alone
51
+ * does not refuse an erasure). It is thrown by the delete or the erasure,
52
+ * not by these methods.
49
53
  * - `RETENTION_HOLD_NOT_YOURS` → {@link PermissionDeniedError}: `release()`
50
54
  * called by a principal that holds none while another's hold stands. A
51
55
  * SuperAdmin overrides and releases every hold.
@@ -63,7 +67,9 @@ export interface RetentionModule {
63
67
  set(input: SetRetentionInput, options?: Call): Promise<ChannelRetention>;
64
68
  /**
65
69
  * Place the CALLING principal's hold. Idempotent per holder: restating
66
- * updates the reason and leaves `placedAt` where it was.
70
+ * updates the reason and leaves `placedAt` where it was. While it stands,
71
+ * both a hard delete of the store and every customer erasure on it
72
+ * (`customers.erase`) are refused with `RETENTION_HOLD_ACTIVE`.
67
73
  */
68
74
  hold(reason: string, options?: Call): Promise<ChannelRetention>;
69
75
  /**