@sixtyninecommerce/admin 0.9.0 → 0.10.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 (41) hide show
  1. package/README.md +294 -0
  2. package/dist/catalog/products.d.ts +162 -1
  3. package/dist/catalog/products.js +94 -1
  4. package/dist/catalog/variants.d.ts +50 -0
  5. package/dist/catalog/variants.js +48 -18
  6. package/dist/client.d.ts +21 -0
  7. package/dist/client.js +6 -0
  8. package/dist/fulfillments/index.d.ts +77 -0
  9. package/dist/fulfillments/index.js +109 -0
  10. package/dist/index.d.ts +15 -4
  11. package/dist/index.js +5 -0
  12. package/dist/integrations/index.d.ts +14 -0
  13. package/dist/integrations/index.js +9 -0
  14. package/dist/integrations/pipelines.d.ts +113 -0
  15. package/dist/integrations/pipelines.js +62 -0
  16. package/dist/integrations/types.d.ts +176 -0
  17. package/dist/integrations/types.js +8 -0
  18. package/dist/jobs/bulk-action-run.d.ts +11 -2
  19. package/dist/jobs/bulk-action-run.js +4 -5
  20. package/dist/orders/bulk.d.ts +75 -0
  21. package/dist/orders/bulk.js +150 -0
  22. package/dist/orders/deposit-policy.d.ts +103 -0
  23. package/dist/orders/deposit-policy.js +202 -0
  24. package/dist/orders/index.d.ts +80 -0
  25. package/dist/orders/index.js +13 -0
  26. package/dist/orders/manual-discounts.d.ts +11 -2
  27. package/dist/orders/manual-discounts.js +2 -0
  28. package/dist/payments/index.d.ts +7 -0
  29. package/dist/payments/index.js +2 -0
  30. package/dist/payments/methods.d.ts +37 -0
  31. package/dist/payments/methods.js +33 -0
  32. package/dist/pricing/index.d.ts +3 -0
  33. package/dist/pricing/index.js +2 -0
  34. package/dist/pricing/vouchers.d.ts +103 -0
  35. package/dist/pricing/vouchers.js +69 -0
  36. package/dist/returns/index.d.ts +225 -0
  37. package/dist/returns/index.js +160 -0
  38. package/dist/search/index.d.ts +99 -0
  39. package/dist/search/index.js +62 -0
  40. package/dist/types.d.ts +2 -2
  41. package/package.json +1 -1
@@ -0,0 +1,103 @@
1
+ import { type Transport } from '@sixtyninecommerce/core';
2
+ import type { Call } from '../types.js';
3
+ /** The fewest days `autoCancelAfterDays` may be; `null` means never. */
4
+ export declare const DEPOSIT_POLICY_AUTO_CANCEL_DAYS_MIN = 1;
5
+ /** The largest `storefront.minimumPercent`: a whole percentage of the order total. */
6
+ export declare const DEPOSIT_POLICY_MINIMUM_PERCENT_MAX = 100;
7
+ /** The most payment method codes `storefront.methods` may name. */
8
+ export declare const DEPOSIT_POLICY_STOREFRONT_METHODS_MAX = 50;
9
+ /** Deposits a SHOPPER pays online at checkout. Applies only while `enabled` is on as well. */
10
+ export interface StorefrontDepositPolicy {
11
+ /** The online switch. */
12
+ enabled: boolean;
13
+ /**
14
+ * Payment method CODES a shopper may pay a deposit with: offline methods and
15
+ * Mollie. Empty: none, so no shopper can pay a deposit online.
16
+ */
17
+ methods: string[];
18
+ /**
19
+ * The least an online deposit may be, as a whole percentage (0–100) of the
20
+ * order's total, judged in the order's currency. Beside `minimum`, not instead
21
+ * of it: the larger of the two applies.
22
+ */
23
+ minimumPercent: number;
24
+ }
25
+ /**
26
+ * The store's deposit policy (`Channel.customFields.orderDepositPolicy`): whether
27
+ * staff may take a deposit and park an order (`orders.park`), and whether a
28
+ * shopper may pay one at checkout. Every field present: this is what the engine
29
+ * APPLIES, absent and malformed fields read as their defaults.
30
+ */
31
+ export interface OrderDepositPolicy {
32
+ /**
33
+ * The store-wide switch. Off: a payment must cover the whole outstanding
34
+ * amount (`payments.record` refuses a part payment `DEPOSITS_NOT_ENABLED`) and
35
+ * no order can be parked.
36
+ */
37
+ enabled: boolean;
38
+ /**
39
+ * The least a deposit must be before an order can be parked, in minor units of
40
+ * the store's DEFAULT currency; 0 means any deposit will do. An order in another
41
+ * currency is held to it converted at the rate checkout charges with.
42
+ */
43
+ minimum: number;
44
+ /** Whether an order may be parked with nothing paid at all. */
45
+ allowZeroDepositPark: boolean;
46
+ /**
47
+ * Whether the storefront shows a parked order's reason to the shopper. Off by
48
+ * default: staff write that reason for staff.
49
+ */
50
+ showParkReasonToCustomer: boolean;
51
+ /**
52
+ * RESERVED: days after which a parked order is cancelled. Saved and read back,
53
+ * but nothing cancels a parked order automatically yet. `null`: never.
54
+ */
55
+ autoCancelAfterDays: number | null;
56
+ storefront: StorefrontDepositPolicy;
57
+ }
58
+ /**
59
+ * The policy of a store that never set one, and of every field that cannot be
60
+ * read: deposits off, no minimum, no zero-deposit park, the reason kept from the
61
+ * shopper, never auto-cancelled, no online deposits.
62
+ */
63
+ export declare const DEFAULT_ORDER_DEPOSIT_POLICY: Readonly<OrderDepositPolicy>;
64
+ /**
65
+ * The ACTIVE store's deposit policy (`admin.orders.depositPolicy`), stored as a
66
+ * Channel custom field and written through core `updateChannel`.
67
+ *
68
+ * Reading needs an authenticated administrator on the store; writing needs
69
+ * `UpdateChannel`.
70
+ */
71
+ export interface OrderDepositPolicyModule {
72
+ /**
73
+ * The policy the engine applies, read as tolerantly as the engine reads it: a
74
+ * store that never set one, a malformed column and a field of the wrong type
75
+ * all answer their defaults ({@link DEFAULT_ORDER_DEPOSIT_POLICY}), so this is
76
+ * what `orders.park` and `payments.record` will hold an order to.
77
+ */
78
+ get(options?: Call): Promise<OrderDepositPolicy>;
79
+ /**
80
+ * Replace the whole policy, and answer it as the engine now applies it.
81
+ *
82
+ * Every field is required: read it with {@link get}, change what you mean to,
83
+ * and send it back. Only the schema's fields are stored (anything else on the
84
+ * object is left out), and the method codes are stored as given.
85
+ *
86
+ * Refused before any request with `DEPOSIT_POLICY_INVALID` ({@link ValidationError},
87
+ * one `details[]` entry per failing field, named by its path such as
88
+ * `storefront.minimumPercent`): a non-boolean switch, a `minimum` that is not a
89
+ * whole number of 0 or more, an `autoCancelAfterDays` that is neither `null` nor
90
+ * a whole number of 1 or more, a `minimumPercent` outside 0–100, a blank or
91
+ * repeated method code, or more than 50 of them. The engine itself stores any
92
+ * JSON in the column and reads what it cannot use as the default, so this check
93
+ * is what keeps a bad value from silently turning deposits off.
94
+ *
95
+ * Absent and "set to the defaults" mean the same thing to the engine, and a
96
+ * store's column is never backfilled: write the policy when a merchant changed
97
+ * it, not as a side effect of saving something else.
98
+ */
99
+ set(policy: OrderDepositPolicy, options?: Call): Promise<OrderDepositPolicy>;
100
+ }
101
+ export declare const ORDER_DEPOSIT_POLICY_QUERY = "\n query OrderDepositPolicy {\n activeChannel { \n id\n customFields {\n orderDepositPolicy\n }\n }\n }\n";
102
+ export declare const SET_ORDER_DEPOSIT_POLICY_MUTATION = "\n mutation SetOrderDepositPolicy($input: UpdateChannelInput!) {\n updateChannel(input: $input) {\n ... on Channel { \n id\n customFields {\n orderDepositPolicy\n }\n }\n ... on ErrorResult { errorCode message }\n }\n }\n";
103
+ export declare function createOrderDepositPolicyModule(transport: Transport): OrderDepositPolicyModule;
@@ -0,0 +1,202 @@
1
+ import { errorFromErrorResult, isErrorResult, ValidationError, } from '@sixtyninecommerce/core';
2
+ // The bounds of the meta catalog's `order_deposit_policy` schema
3
+ // (`packages/shared/src/meta-catalog/settings-schemas.ts`), which the spec reads
4
+ // back from that schema. Nothing on the engine validates the column on write, so
5
+ // these are the only check a value gets before the engine reads it.
6
+ /** The fewest days `autoCancelAfterDays` may be; `null` means never. */
7
+ export const DEPOSIT_POLICY_AUTO_CANCEL_DAYS_MIN = 1;
8
+ /** The largest `storefront.minimumPercent`: a whole percentage of the order total. */
9
+ export const DEPOSIT_POLICY_MINIMUM_PERCENT_MAX = 100;
10
+ /** The most payment method codes `storefront.methods` may name. */
11
+ export const DEPOSIT_POLICY_STOREFRONT_METHODS_MAX = 50;
12
+ /**
13
+ * The policy of a store that never set one, and of every field that cannot be
14
+ * read: deposits off, no minimum, no zero-deposit park, the reason kept from the
15
+ * shopper, never auto-cancelled, no online deposits.
16
+ */
17
+ export const DEFAULT_ORDER_DEPOSIT_POLICY = Object.freeze({
18
+ enabled: false,
19
+ minimum: 0,
20
+ allowZeroDepositPark: false,
21
+ showParkReasonToCustomer: false,
22
+ autoCancelAfterDays: null,
23
+ storefront: Object.freeze({ enabled: false, methods: [], minimumPercent: 0 }),
24
+ });
25
+ const POLICY_FIELDS = /* GraphQL */ `
26
+ id
27
+ customFields {
28
+ orderDepositPolicy
29
+ }
30
+ `;
31
+ export const ORDER_DEPOSIT_POLICY_QUERY = /* GraphQL */ `
32
+ query OrderDepositPolicy {
33
+ activeChannel { ${POLICY_FIELDS} }
34
+ }
35
+ `;
36
+ export const SET_ORDER_DEPOSIT_POLICY_MUTATION = /* GraphQL */ `
37
+ mutation SetOrderDepositPolicy($input: UpdateChannelInput!) {
38
+ updateChannel(input: $input) {
39
+ ... on Channel { ${POLICY_FIELDS} }
40
+ ... on ErrorResult { errorCode message }
41
+ }
42
+ }
43
+ `;
44
+ function isRecord(value) {
45
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
46
+ }
47
+ const isWhole = (value) => typeof value === 'number' && Number.isSafeInteger(value);
48
+ /**
49
+ * The column, read as the engine reads it (`resolveDepositPolicy` in
50
+ * `services/vendure/src/payment/deposit-policy.ts`): anything unreadable is its
51
+ * default.
52
+ */
53
+ function readOrderDepositPolicy(raw) {
54
+ let parsed = raw;
55
+ if (typeof raw === 'string') {
56
+ try {
57
+ parsed = JSON.parse(raw);
58
+ }
59
+ catch {
60
+ parsed = null;
61
+ }
62
+ }
63
+ if (!isRecord(parsed))
64
+ return copyOf(DEFAULT_ORDER_DEPOSIT_POLICY);
65
+ return {
66
+ enabled: parsed.enabled === true,
67
+ minimum: isWhole(parsed.minimum) && parsed.minimum >= 0 ? parsed.minimum : 0,
68
+ allowZeroDepositPark: parsed.allowZeroDepositPark === true,
69
+ showParkReasonToCustomer: parsed.showParkReasonToCustomer === true,
70
+ autoCancelAfterDays: isWhole(parsed.autoCancelAfterDays) &&
71
+ parsed.autoCancelAfterDays >= DEPOSIT_POLICY_AUTO_CANCEL_DAYS_MIN
72
+ ? parsed.autoCancelAfterDays
73
+ : null,
74
+ storefront: readStorefront(parsed.storefront),
75
+ };
76
+ }
77
+ function readStorefront(raw) {
78
+ if (!isRecord(raw))
79
+ return { enabled: false, methods: [], minimumPercent: 0 };
80
+ // Only non-blank string codes, once each and at most 50: a stray `null` or a
81
+ // number must not allow a method nobody named.
82
+ const methods = Array.isArray(raw.methods)
83
+ ? [
84
+ ...new Set(raw.methods.filter((code) => typeof code === 'string' && code.trim() !== '')),
85
+ ].slice(0, DEPOSIT_POLICY_STOREFRONT_METHODS_MAX)
86
+ : [];
87
+ const percent = raw.minimumPercent;
88
+ return {
89
+ enabled: raw.enabled === true,
90
+ methods,
91
+ minimumPercent: isWhole(percent) && percent >= 0 && percent <= DEPOSIT_POLICY_MINIMUM_PERCENT_MAX
92
+ ? percent
93
+ : 0,
94
+ };
95
+ }
96
+ function copyOf(policy) {
97
+ return {
98
+ ...policy,
99
+ storefront: { ...policy.storefront, methods: [...policy.storefront.methods] },
100
+ };
101
+ }
102
+ /** Every way `policy` breaks the schema, one entry per field. */
103
+ function problemsOf(policy) {
104
+ const problems = [];
105
+ const add = (field, message) => problems.push({ field, code: 'DEPOSIT_POLICY_INVALID', message });
106
+ if (!isRecord(policy)) {
107
+ add('policy', 'Pass the whole policy as an object: read it with get(), change it, send it back.');
108
+ return problems;
109
+ }
110
+ for (const field of ['enabled', 'allowZeroDepositPark', 'showParkReasonToCustomer']) {
111
+ if (typeof policy[field] !== 'boolean')
112
+ add(field, `${field} must be true or false.`);
113
+ }
114
+ if (!(isWhole(policy.minimum) && policy.minimum >= 0)) {
115
+ add('minimum', 'minimum must be a whole number of minor units, 0 or more.');
116
+ }
117
+ const days = policy.autoCancelAfterDays;
118
+ if (days !== null && !(isWhole(days) && days >= DEPOSIT_POLICY_AUTO_CANCEL_DAYS_MIN)) {
119
+ add('autoCancelAfterDays', 'autoCancelAfterDays must be null (never) or a whole number of days, 1 or more.');
120
+ }
121
+ const storefront = policy.storefront;
122
+ if (!isRecord(storefront)) {
123
+ add('storefront', 'storefront must be an object: { enabled, methods, minimumPercent }.');
124
+ return problems;
125
+ }
126
+ if (typeof storefront.enabled !== 'boolean') {
127
+ add('storefront.enabled', 'storefront.enabled must be true or false.');
128
+ }
129
+ const methods = storefront.methods;
130
+ if (!Array.isArray(methods)) {
131
+ add('storefront.methods', 'storefront.methods must be an array of payment method codes.');
132
+ }
133
+ else if (methods.some((code) => typeof code !== 'string' || code.trim() === '')) {
134
+ add('storefront.methods', 'storefront.methods may hold only non-blank payment method codes.');
135
+ }
136
+ else if (new Set(methods).size !== methods.length) {
137
+ add('storefront.methods', 'storefront.methods names a payment method code more than once.');
138
+ }
139
+ else if (methods.length > DEPOSIT_POLICY_STOREFRONT_METHODS_MAX) {
140
+ add('storefront.methods', `storefront.methods may name at most ${String(DEPOSIT_POLICY_STOREFRONT_METHODS_MAX)} payment methods.`);
141
+ }
142
+ const percent = storefront.minimumPercent;
143
+ if (!(isWhole(percent) && percent >= 0 && percent <= DEPOSIT_POLICY_MINIMUM_PERCENT_MAX)) {
144
+ add('storefront.minimumPercent', 'storefront.minimumPercent must be a whole percentage from 0 to 100.');
145
+ }
146
+ return problems;
147
+ }
148
+ /** Exactly the schema's fields, in its order: what the column stores. */
149
+ function storedOf(policy) {
150
+ return {
151
+ enabled: policy.enabled,
152
+ minimum: policy.minimum,
153
+ allowZeroDepositPark: policy.allowZeroDepositPark,
154
+ showParkReasonToCustomer: policy.showParkReasonToCustomer,
155
+ autoCancelAfterDays: policy.autoCancelAfterDays,
156
+ storefront: {
157
+ enabled: policy.storefront.enabled,
158
+ methods: [...policy.storefront.methods],
159
+ minimumPercent: policy.storefront.minimumPercent,
160
+ },
161
+ };
162
+ }
163
+ export function createOrderDepositPolicyModule(transport) {
164
+ const read = async (options) => {
165
+ const data = await transport.graphql({ api: 'admin', query: ORDER_DEPOSIT_POLICY_QUERY }, options);
166
+ return data.activeChannel;
167
+ };
168
+ return {
169
+ async get(options) {
170
+ const channel = await read(options);
171
+ return readOrderDepositPolicy(channel.customFields?.orderDepositPolicy ?? null);
172
+ },
173
+ async set(policy, options) {
174
+ const problems = problemsOf(policy);
175
+ if (problems.length > 0) {
176
+ throw new ValidationError({
177
+ code: 'DEPOSIT_POLICY_INVALID',
178
+ message: problems.map((problem) => problem.message).join(' '),
179
+ status: 400,
180
+ service: 'vendure',
181
+ details: problems,
182
+ });
183
+ }
184
+ // `updateChannel` needs the id, and the active channel is the one the
185
+ // client is scoped to — read it with the SAME call options, so a per-call
186
+ // tenant writes its own channel rather than the client's.
187
+ const { id } = await read(options);
188
+ const data = await transport.graphql({
189
+ api: 'admin',
190
+ query: SET_ORDER_DEPOSIT_POLICY_MUTATION,
191
+ variables: {
192
+ // The column is `text` holding JSON (the schema describes the
193
+ // DECODED value), so it is sent as a string.
194
+ input: { id, customFields: { orderDepositPolicy: JSON.stringify(storedOf(policy)) } },
195
+ },
196
+ }, options);
197
+ if (isErrorResult(data.updateChannel))
198
+ throw errorFromErrorResult(data.updateChannel);
199
+ return readOrderDepositPolicy(data.updateChannel.customFields?.orderDepositPolicy ?? null);
200
+ },
201
+ };
202
+ }
@@ -1,7 +1,10 @@
1
1
  import { type Transport } from '@sixtyninecommerce/core';
2
+ import type { JobHandle } from '../jobs/index.js';
2
3
  import type { OrderPaymentPosition, PaidOrder } from '../payments/index.js';
3
4
  import type { Call } from '../types.js';
5
+ import { type OrderBulkInput, type OrderBulkRun } from './bulk.js';
4
6
  import { type OrderCommitEditModule } from './commit-edit.js';
7
+ import { type OrderDepositPolicyModule } from './deposit-policy.js';
5
8
  import { type OrderGetModule } from './get.js';
6
9
  import { type OrderLinePricePreviewModule } from './line-price-preview.js';
7
10
  import { type ManualDiscountsModule } from './manual-discounts.js';
@@ -12,6 +15,9 @@ import { type OrderPlanEditModule } from './plan-edit.js';
12
15
  import { type OrderStatusModule } from './status.js';
13
16
  import { type SurchargesModule } from './surcharges.js';
14
17
  export type { PaidOrder } from '../payments/index.js';
18
+ export type { OrderBulkAction, OrderBulkCapture, OrderBulkInput, OrderBulkItem, OrderBulkRun, } from './bulk.js';
19
+ export { MAX_BULK_ORDERS, ORDER_BULK_ACTIONS, ORDER_BULK_TRANSITION_REFUSED } from './bulk.js';
20
+ export type { OrderDepositPolicy, OrderDepositPolicyModule, StorefrontDepositPolicy, } from './deposit-policy.js';
15
21
  export type { DiscountPolicy, DiscountPolicyInput, DiscountPolicyModule, DiscountReason, DiscountReasonInput, DiscountReasonsModule, ManualDiscountsModule, OrderManualDiscount, } from './manual-discounts.js';
16
22
  export type { ExternalLinePriceBasis, ExternalOrder, ExternalOrderAddressInput, ExternalOrderCall, ExternalOrderCatalogLine, ExternalOrderExternalLine, ExternalOrderFulfilment, ExternalOrderInput, ExternalOrderLine, ExternalOrderLineInput, ExternalOrderSurcharge, ExternalOrderSurchargeInput, ExternalOrderTargetState, ExternalOrderTaxLine, } from './order-entry.js';
17
23
  /** An app provider an order is running without (`admin.orders.providerFallbacks`). */
@@ -73,6 +79,80 @@ export interface OrdersModule extends OrderEntryModule, OrderGetModule, OrderPar
73
79
  * `setOneTimeDiscounts` on this module.
74
80
  */
75
81
  manualDiscounts: ManualDiscountsModule;
82
+ /**
83
+ * Move, cancel, delete or mark paid up to `MAX_BULK_ORDERS` (50) orders of the store in ONE
84
+ * call (headless spec §7 `orders.bulk`; rulings G19, G28).
85
+ *
86
+ * **The run is a job.** The call answers a receipt (`runId`) once the run is queued;
87
+ * `handle.wait()` polls it until it ends and resolves with the run, whatever the ending.
88
+ * `bulkRun(runId)` reads it again later, from any process. Each order's outcome is in
89
+ * `run.items`, in the order the ids were named.
90
+ *
91
+ * **Each order is judged as on its own page, and succeeds or fails alone.** First by its own
92
+ * status (`orders.status`): an action the order's status does not offer, or a TRANSITION target
93
+ * outside the order's own `nextStates`, answers `ORDER_ACTION_UNAVAILABLE` with `reasonCode`
94
+ * (that read's vocabulary), before anything is written. Then by its single door, under the
95
+ * door's own lock, with the door's own code: `ORDER_STATE_TRANSITION_ERROR` (and core's other
96
+ * order codes), `ORDER_CART_NOT_CANCELLABLE`, `ORDER_UNITS_SHIPPED`,
97
+ * `ORDER_HAS_ACTIVE_FULFILLMENT`, `ORDER_INVOICE_LOCKED`, `ORDER_HAS_RECORDS`,
98
+ * `ORDER_NOT_DELETED`. Another store's order answers `ENTITY_NOT_FOUND` on its item. A
99
+ * `SUCCEEDED` run can hold `FAILED` items: read them.
100
+ *
101
+ * - `TRANSITION` (`toState` required): as `transitionOrderToState`. Never `Cancelled` (use
102
+ * `CANCEL`), `PaymentSettled` (use `MARK_PAID`), `Modifying` or `AddingItems`.
103
+ * - `CANCEL` (`reason` optional): the whole order, shipping included, as `cancelOrder`. **It
104
+ * moves no money:** a paid order keeps its payment, to be refunded through `payments.refund`.
105
+ * - `DELETE`: a draft or a cart holding no payment or stock movement, as `deleteOrder`.
106
+ * - `MARK_PAID`: **captures** each payment of the order that is `Authorized` when the run reaches
107
+ * it, read by the engine (never your list's copy), each as `settlePayment` and on its own
108
+ * (ruling G19). Each order's `captured` lists every payment acted on. **A decline is a result,
109
+ * not a retry:** the order answers `SETTLE_PAYMENT_ERROR`, the payment keeps the state the
110
+ * provider left, and the run never asks again. **An unknown provider answer must be
111
+ * reconciled, never retried blind:** `IDEMPOTENCY_OUTCOME_UNKNOWN` means the provider was asked
112
+ * and its answer was lost; check the payment with the provider. `IDEMPOTENCY_IN_PROGRESS` means
113
+ * a capture under the same request may still be running. An order with no authorized payment
114
+ * answers `NOTHING_TO_CAPTURE`: it is never marked paid without money (ruling G28).
115
+ *
116
+ * **Idempotency.** `options.idempotencyKey` (minted when omitted): the same key with the same
117
+ * input answers the SAME run, so a resubmit never captures twice; another input under the key is
118
+ * `IDEMPOTENCY_CONFLICT` (409 `ConflictError`). Each order, and each capture, is keyed within the
119
+ * run, so a run the platform processes again (a worker restart) answers what it already did from
120
+ * that record and never asks a provider twice.
121
+ *
122
+ * **No dry run.** The refusals that matter are the doors' own, made under their locks, so a
123
+ * rehearsal could answer "ok" for an order the real run refuses.
124
+ *
125
+ * Needs `UpdateOrder` (`DeleteOrder` for `DELETE`); reading the run needs `ReadOrder`. Refusals
126
+ * of the call itself (nothing is queued): `BULK_ACTION_INPUT_INVALID` (`ValidationError`, 400:
127
+ * no orders, more than 50, one named twice, an unknown action, a missing or refused `toState`,
128
+ * an option the action takes none of; refused before sending too), `BULK_ACTION_NOT_ALLOWED`
129
+ * (`PermissionDeniedError`, 403), `IDEMPOTENCY_CONFLICT`.
130
+ *
131
+ * Platform releases before this method do not have the mutation and refuse it as
132
+ * `ValidationError` `GRAPHQL_VALIDATION_FAILED` (400): deploy the engine first.
133
+ */
134
+ bulk(input: OrderBulkInput, options?: Call): Promise<JobHandle<OrderBulkRun>>;
135
+ /**
136
+ * One read of a bulk order run, by the `runId` `bulk()` answered. A run of another store, or one
137
+ * that does not exist, is `BULK_ACTION_RUN_NOT_FOUND` (`NotFoundError`, 404).
138
+ */
139
+ bulkRun(runId: string, options?: Call): Promise<OrderBulkRun>;
140
+ /**
141
+ * Delete ONE order: a draft or a cart (`AddingItems`) holding no payment and no stock movement,
142
+ * as the order page's Delete (ruling G29). Needs `DeleteOrder`. Anything else is kept:
143
+ * `ORDER_INVOICE_LOCKED` (an invoiced order, for every caller), `ORDER_HAS_RECORDS` (cancel it
144
+ * instead: `orders.bulk` with `CANCEL`), or `ORDER_NOT_DELETED` (`ConflictError`, 409, as every `*_NOT_DELETED` of this SDK) with
145
+ * the engine's own reason for an order in another state, or one this store does not have.
146
+ * Placed orders are cancelled, never deleted.
147
+ */
148
+ delete(orderId: string, options?: Call): Promise<void>;
149
+ /**
150
+ * The store's deposit policy (`orderDepositPolicy`): whether staff may take a
151
+ * deposit and {@link OrdersModule.park} an order, the minimum a deposit must be,
152
+ * and whether a shopper may pay one at checkout. `get` answers what the engine
153
+ * applies; `set` replaces it, validated against the schema's bounds first.
154
+ */
155
+ depositPolicy: OrderDepositPolicyModule;
76
156
  /**
77
157
  * Hold a placed order while the rest of its money is arranged. Needs
78
158
  * `UpdateOrder`.
@@ -1,5 +1,7 @@
1
1
  import { NotFoundError } from '@sixtyninecommerce/core';
2
+ import { bulkOrders, deleteOrder, readOrderBulkRun, } from './bulk.js';
2
3
  import { createOrderCommitEditModule } from './commit-edit.js';
4
+ import { createOrderDepositPolicyModule } from './deposit-policy.js';
3
5
  import { createOrderGetModule } from './get.js';
4
6
  import { createOrderLinePricePreviewModule, } from './line-price-preview.js';
5
7
  import { createManualDiscountsModule } from './manual-discounts.js';
@@ -9,6 +11,7 @@ import { createOrderParcelChargeModule } from './parcel-charge.js';
9
11
  import { createOrderPlanEditModule } from './plan-edit.js';
10
12
  import { createOrderStatusModule } from './status.js';
11
13
  import { createSurchargesModule } from './surcharges.js';
14
+ export { MAX_BULK_ORDERS, ORDER_BULK_ACTIONS, ORDER_BULK_TRANSITION_REFUSED } from './bulk.js';
12
15
  const ORDER_PROVIDER_FALLBACKS_QUERY = /* GraphQL */ `
13
16
  query OrderProviderFallbacks($id: ID!) {
14
17
  order(id: $id) {
@@ -100,6 +103,16 @@ export function createOrdersModule(transport) {
100
103
  ...createOrderStatusModule(transport),
101
104
  surcharges: createSurchargesModule(transport),
102
105
  manualDiscounts: createManualDiscountsModule(transport),
106
+ depositPolicy: createOrderDepositPolicyModule(transport),
107
+ bulk(input, options) {
108
+ return bulkOrders(transport, input, options);
109
+ },
110
+ bulkRun(runId, options) {
111
+ return readOrderBulkRun(transport, runId, options);
112
+ },
113
+ delete(orderId, options) {
114
+ return deleteOrder(transport, orderId, options);
115
+ },
103
116
  async park(orderId, options) {
104
117
  const data = await transport.graphql({
105
118
  api: 'admin',
@@ -37,10 +37,19 @@ export interface DiscountPolicy {
37
37
  * active reasons for that line. A till discount (`pos.sale.applyDiscount`) is not held to it.
38
38
  */
39
39
  reasonRequired: boolean;
40
+ /**
41
+ * The store's ceiling on manual discounts in basis points (1000 = 10 %), or `null` for none: how
42
+ * far below the price the customer would get anyway (its contract included) staff without the
43
+ * `OverrideDiscountLimit` permission may take a line (`DISCOUNT_OVER_LIMIT`). Reducing a
44
+ * discount is never refused. The POS till is not held to it.
45
+ */
46
+ maxDiscountBps: number | null;
40
47
  }
41
48
  export interface DiscountPolicyInput {
42
49
  /** Omitted keeps the stored value. */
43
50
  reasonRequired?: boolean;
51
+ /** 0..10000 basis points; omitted keeps the stored value, `null` removes the ceiling. */
52
+ maxDiscountBps?: number | null;
44
53
  }
45
54
  /**
46
55
  * One price movement a manual-discount door made on an order. Append-only. Each row is a price
@@ -126,7 +135,7 @@ export interface ManualDiscountsModule {
126
135
  }
127
136
  export declare const DISCOUNT_REASONS_QUERY = "\n query DiscountReasons($includeInactive: Boolean) {\n discountReasons(includeInactive: $includeInactive) {\n \n id\n code\n label\n active\n createdAt\n updatedAt\n\n }\n }\n";
128
137
  export declare const UPSERT_DISCOUNT_REASON_MUTATION = "\n mutation UpsertDiscountReason($input: UpsertDiscountReasonInput!) {\n upsertDiscountReason(input: $input) {\n \n id\n code\n label\n active\n createdAt\n updatedAt\n\n }\n }\n";
129
- export declare const DISCOUNT_POLICY_QUERY = "\n query DiscountPolicy {\n discountPolicy {\n reasonRequired\n }\n }\n";
130
- export declare const UPDATE_DISCOUNT_POLICY_MUTATION = "\n mutation UpdateDiscountPolicy($input: UpdateDiscountPolicyInput!) {\n updateDiscountPolicy(input: $input) {\n reasonRequired\n }\n }\n";
138
+ export declare const DISCOUNT_POLICY_QUERY = "\n query DiscountPolicy {\n discountPolicy {\n reasonRequired\n maxDiscountBps\n }\n }\n";
139
+ export declare const UPDATE_DISCOUNT_POLICY_MUTATION = "\n mutation UpdateDiscountPolicy($input: UpdateDiscountPolicyInput!) {\n updateDiscountPolicy(input: $input) {\n reasonRequired\n maxDiscountBps\n }\n }\n";
131
140
  export declare const ORDER_MANUAL_DISCOUNTS_QUERY = "\n query OrderManualDiscounts($orderId: ID!) {\n orderManualDiscounts(orderId: $orderId) {\n \n id\n createdAt\n orderId\n orderLineId\n door\n amount\n amountWithTax\n currencyCode\n reason {\n id\n code\n label\n active\n }\n actorUserId\n onBehalfOfRef\n deviceRef\n callId\n\n }\n }\n";
132
141
  export declare function createManualDiscountsModule(transport: Transport): ManualDiscountsModule;
@@ -44,6 +44,7 @@ export const DISCOUNT_POLICY_QUERY = /* GraphQL */ `
44
44
  query DiscountPolicy {
45
45
  discountPolicy {
46
46
  reasonRequired
47
+ maxDiscountBps
47
48
  }
48
49
  }
49
50
  `;
@@ -51,6 +52,7 @@ export const UPDATE_DISCOUNT_POLICY_MUTATION = /* GraphQL */ `
51
52
  mutation UpdateDiscountPolicy($input: UpdateDiscountPolicyInput!) {
52
53
  updateDiscountPolicy(input: $input) {
53
54
  reasonRequired
55
+ maxDiscountBps
54
56
  }
55
57
  }
56
58
  `;
@@ -1,6 +1,7 @@
1
1
  import type { Transport } from '@sixtyninecommerce/core';
2
2
  import type { Call } from '../types.js';
3
3
  import { type AccountCreditModule } from './account-credit.js';
4
+ import { type PaymentMethodsModule } from './methods.js';
4
5
  import { type ProvidersModule } from './providers.js';
5
6
  /**
6
7
  * What an order has actually been paid, in minor units of its own currency.
@@ -284,6 +285,11 @@ export interface PaymentsModule {
284
285
  * to an invoice, pay it back.
285
286
  */
286
287
  accountCredit: AccountCreditModule;
288
+ /**
289
+ * The store's payment methods, as staff choose one: the `method` code
290
+ * {@link PaymentsModule.record} takes, and the codes a deposit policy names.
291
+ */
292
+ methods: PaymentMethodsModule;
287
293
  /**
288
294
  * Record part (or all) of what an order owes, and return the order.
289
295
  *
@@ -466,4 +472,5 @@ export declare const REPORT_APP_PAYMENT_MUTATION = "\n mutation ReportAppPaymen
466
472
  export declare const APP_PAYMENTS_QUERY = "\n query AppPayments($orderId: ID!) {\n appPayments(orderId: $orderId) {\n id\n orderId\n orderCode\n state\n amount\n currencyCode\n providerReference\n methodCode\n outcomeUnknown\n createdAt\n }\n }\n";
467
473
  export declare function createPaymentsModule(transport: Transport): PaymentsModule;
468
474
  export type { AccountCreditModule, ApplyUnappliedCashInput, CreditOverpaymentInput, OrderMoneyOnAccount, RefundUnappliedCashInput, UnappliedCash, UnappliedCashRefund, } from './account-credit.js';
475
+ export type { PaymentMethod, PaymentMethodsModule } from './methods.js';
469
476
  export type { ProviderConnection, ProviderCredentialPath, ProviderOnboardingStart, ProviderReadiness, ProvidersModule, ProviderTerminal, ProviderTerminalsModule, ProvisionTerminalInput, ReadinessOptions, } from './providers.js';
@@ -1,4 +1,5 @@
1
1
  import { createAccountCreditModule } from './account-credit.js';
2
+ import { createPaymentMethodsModule } from './methods.js';
2
3
  import { createProvidersModule } from './providers.js';
3
4
  const PAID_ORDER_FIELDS = /* GraphQL */ `
4
5
  id
@@ -124,6 +125,7 @@ export function createPaymentsModule(transport) {
124
125
  return {
125
126
  providers: createProvidersModule(transport),
126
127
  accountCredit: createAccountCreditModule(transport),
128
+ methods: createPaymentMethodsModule(transport),
127
129
  async record(orderId, input, options) {
128
130
  const data = await transport.graphql({
129
131
  api: 'admin',
@@ -0,0 +1,37 @@
1
+ import type { Page, Transport } from '@sixtyninecommerce/core';
2
+ import type { ListOptions } from '../types.js';
3
+ /** One payment method of the store, as staff choose one. */
4
+ export interface PaymentMethod {
5
+ id: string;
6
+ /**
7
+ * The method's code: what `payments.record` takes as `method`, and what a
8
+ * deposit policy names in `storefront.methods`.
9
+ */
10
+ code: string;
11
+ name: string;
12
+ /** A disabled method takes no new payments; it still names the ones it took. */
13
+ enabled: boolean;
14
+ /**
15
+ * The code of the handler that takes the money (`manual-payment`, `mollie`, a
16
+ * payment app's handler, …): what kind of method this is.
17
+ */
18
+ handlerCode: string;
19
+ }
20
+ /**
21
+ * The store's payment methods (`admin.payments.methods`): what staff record a
22
+ * payment with, and what a deposit policy names.
23
+ *
24
+ * Needs `ReadSettings` or `ReadPaymentMethod`. Scoped to the channel the client
25
+ * is on: another store's methods are not listed.
26
+ */
27
+ export interface PaymentMethodsModule {
28
+ /**
29
+ * Every method of the store, enabled or not, in the engine's order. Paged
30
+ * (`options.limit`, default 100, at most 1,000 or `INVALID_PAGE_SIZE`; and
31
+ * `options.after` from a previous page); `for await (const method of page)`
32
+ * walks them all.
33
+ */
34
+ list(options?: ListOptions): Promise<Page<PaymentMethod>>;
35
+ }
36
+ export declare const PAYMENT_METHODS_QUERY = "\n query StorePaymentMethods($options: PaymentMethodListOptions) {\n paymentMethods(options: $options) {\n items {\n id\n code\n name\n enabled\n handler {\n code\n }\n }\n totalItems\n }\n }\n";
37
+ export declare function createPaymentMethodsModule(transport: Transport): PaymentMethodsModule;
@@ -0,0 +1,33 @@
1
+ import { vendureListPage } from '../catalog/types.js';
2
+ /** A store has a handful; one page holds them. */
3
+ const DEFAULT_PAGE_SIZE = 100;
4
+ export const PAYMENT_METHODS_QUERY = /* GraphQL */ `
5
+ query StorePaymentMethods($options: PaymentMethodListOptions) {
6
+ paymentMethods(options: $options) {
7
+ items {
8
+ id
9
+ code
10
+ name
11
+ enabled
12
+ handler {
13
+ code
14
+ }
15
+ }
16
+ totalItems
17
+ }
18
+ }
19
+ `;
20
+ const toMethod = (method) => ({
21
+ id: method.id,
22
+ code: method.code,
23
+ name: method.name,
24
+ enabled: method.enabled,
25
+ handlerCode: method.handler.code,
26
+ });
27
+ export function createPaymentMethodsModule(transport) {
28
+ return {
29
+ list(options) {
30
+ return vendureListPage(transport, PAYMENT_METHODS_QUERY, 'paymentMethods', (skip, take) => ({ options: { skip, take } }), { ...options, limit: options?.limit ?? DEFAULT_PAGE_SIZE }, toMethod);
31
+ },
32
+ };
33
+ }
@@ -1,6 +1,7 @@
1
1
  import { type Page, type Transport } from '@sixtyninecommerce/core';
2
2
  import type { ConfigurableOperation, ConfigurableOperationInput, LanguageCode } from '../catalog/types.js';
3
3
  import type { Call } from '../types.js';
4
+ import { type PricingVouchersModule } from './vouchers.js';
4
5
  /**
5
6
  * Promotions at the register (headless spec §9.2 `pricing`; incentives spec §5,
6
7
  * sprint 1): which promotions a register can offer, coupons on the order it is
@@ -33,6 +34,8 @@ import type { Call } from '../types.js';
33
34
  export interface PricingModule {
34
35
  promotions: PricingPromotionsModule;
35
36
  coupons: PricingCouponsModule;
37
+ /** Personal vouchers: templates, issuing, a customer's vouchers (`./vouchers.ts`). */
38
+ vouchers: PricingVouchersModule;
36
39
  /**
37
40
  * The order's discounts as they stand: its coupon codes, the order-level
38
41
  * discounts, and each line's discounts, every one naming the promotion it came
@@ -1,4 +1,5 @@
1
1
  import { createPage, errorFromErrorResult, isErrorResult, NotFoundError, ValidationError, } from '@sixtyninecommerce/core';
2
+ import { createPricingVouchersModule } from './vouchers.js';
2
3
  /**
3
4
  * The targeting conditions (incentives sprint 2), encoded as the engine stores
4
5
  * them — every argument a string, lists JSON-encoded. Their argument names and
@@ -317,6 +318,7 @@ export function createPricingModule(transport) {
317
318
  return data.updatePromotion;
318
319
  },
319
320
  },
321
+ vouchers: createPricingVouchersModule(transport),
320
322
  coupons: {
321
323
  async apply(orderId, couponCode, options) {
322
324
  const data = await transport.graphql({