@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.
- package/README.md +2445 -17
- package/dist/api-tokens/index.d.ts +15 -5
- package/dist/api-tokens/index.js +1 -0
- package/dist/apps/event-subscription.d.ts +59 -0
- package/dist/apps/event-subscription.js +32 -0
- package/dist/apps/hook-secret.d.ts +22 -0
- package/dist/apps/hook-secret.js +36 -0
- package/dist/apps/index.d.ts +54 -3
- package/dist/apps/index.js +17 -0
- package/dist/audit/index.d.ts +7 -0
- package/dist/auth/queries.d.ts +5 -1
- package/dist/auth/queries.js +6 -0
- package/dist/bulk/index.d.ts +323 -0
- package/dist/bulk/index.js +162 -0
- package/dist/catalog/assets.d.ts +147 -0
- package/dist/catalog/assets.js +164 -0
- package/dist/catalog/collections.d.ts +109 -0
- package/dist/catalog/collections.js +91 -0
- package/dist/catalog/facets.d.ts +103 -0
- package/dist/catalog/facets.js +100 -0
- package/dist/catalog/index.d.ts +115 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/options.d.ts +89 -0
- package/dist/catalog/options.js +88 -0
- package/dist/catalog/products-bulk.d.ts +21 -0
- package/dist/catalog/products-bulk.js +58 -0
- package/dist/catalog/products.d.ts +489 -0
- package/dist/catalog/products.js +365 -0
- package/dist/catalog/properties.d.ts +234 -0
- package/dist/catalog/properties.js +158 -0
- package/dist/catalog/types.d.ts +174 -0
- package/dist/catalog/types.js +141 -0
- package/dist/catalog/variants.d.ts +274 -0
- package/dist/catalog/variants.js +258 -0
- package/dist/catalog/visibility.d.ts +57 -0
- package/dist/catalog/visibility.js +59 -0
- package/dist/client.d.ts +171 -3
- package/dist/client.js +81 -12
- package/dist/compliance/index.d.ts +88 -0
- package/dist/compliance/index.js +88 -0
- package/dist/customer-data-redaction.d.ts +14 -0
- package/dist/customer-data-redaction.js +22 -0
- package/dist/customers/index.d.ts +428 -17
- package/dist/customers/index.js +322 -15
- package/dist/documents/index.d.ts +513 -16
- package/dist/documents/index.js +229 -3
- package/dist/events/index.d.ts +259 -0
- package/dist/events/index.js +147 -0
- package/dist/feeds/ai.d.ts +46 -0
- package/dist/feeds/ai.js +43 -0
- package/dist/feeds/categories.d.ts +50 -0
- package/dist/feeds/categories.js +56 -0
- package/dist/feeds/configs.d.ts +54 -0
- package/dist/feeds/configs.js +85 -0
- package/dist/feeds/identifier-fields.d.ts +25 -0
- package/dist/feeds/identifier-fields.js +29 -0
- package/dist/feeds/index.d.ts +54 -0
- package/dist/feeds/index.js +46 -0
- package/dist/feeds/preserve.d.ts +38 -0
- package/dist/feeds/preserve.js +74 -0
- package/dist/feeds/rules.d.ts +42 -0
- package/dist/feeds/rules.js +56 -0
- package/dist/feeds/types.d.ts +654 -0
- package/dist/feeds/types.js +135 -0
- package/dist/fulfillments/index.d.ts +360 -0
- package/dist/fulfillments/index.js +425 -0
- package/dist/heads/index.d.ts +60 -0
- package/dist/heads/index.js +45 -0
- package/dist/hooks/assignments.d.ts +90 -0
- package/dist/hooks/assignments.js +53 -0
- package/dist/hooks/index.d.ts +97 -0
- package/dist/hooks/index.js +76 -0
- package/dist/index.d.ts +84 -9
- package/dist/index.js +16 -1
- package/dist/jobs/bulk-action-run.d.ts +65 -0
- package/dist/jobs/bulk-action-run.js +70 -0
- package/dist/locations/index.d.ts +6 -2
- package/dist/meta/index.d.ts +32 -0
- package/dist/orders/commit-edit.d.ts +123 -0
- package/dist/orders/commit-edit.js +142 -0
- package/dist/orders/get.d.ts +722 -0
- package/dist/orders/get.js +530 -0
- package/dist/orders/index.d.ts +105 -20
- package/dist/orders/index.js +44 -0
- package/dist/orders/line-price-preview.d.ts +297 -0
- package/dist/orders/line-price-preview.js +50 -0
- package/dist/orders/manual-discounts.d.ts +132 -0
- package/dist/orders/manual-discounts.js +95 -0
- package/dist/orders/manual-pricing.d.ts +192 -0
- package/dist/orders/manual-pricing.js +72 -0
- package/dist/orders/order-entry.d.ts +566 -0
- package/dist/orders/order-entry.js +129 -0
- package/dist/orders/parcel-charge.d.ts +45 -0
- package/dist/orders/parcel-charge.js +55 -0
- package/dist/orders/plan-edit.d.ts +489 -0
- package/dist/orders/plan-edit.js +93 -0
- package/dist/orders/status.d.ts +235 -0
- package/dist/orders/status.js +96 -0
- package/dist/orders/surcharges.d.ts +75 -2
- package/dist/orders/surcharges.js +110 -4
- package/dist/payments/account-credit.d.ts +171 -0
- package/dist/payments/account-credit.js +107 -0
- package/dist/payments/index.d.ts +234 -9
- package/dist/payments/index.js +90 -0
- package/dist/platform/catalog.d.ts +148 -0
- package/dist/platform/catalog.js +74 -0
- package/dist/platform/entitlements.d.ts +111 -0
- package/dist/platform/entitlements.js +86 -0
- package/dist/platform/legal-entities.d.ts +139 -0
- package/dist/platform/legal-entities.js +126 -0
- package/dist/platform/legal-entity-numbering.d.ts +103 -0
- package/dist/platform/legal-entity-numbering.js +101 -0
- package/dist/platform/organization-membership.d.ts +80 -0
- package/dist/platform/organization-membership.js +71 -0
- package/dist/platform/organization-settings.d.ts +33 -0
- package/dist/platform/organization-settings.js +42 -0
- package/dist/platform/organization-sharing.d.ts +172 -0
- package/dist/platform/organization-sharing.js +120 -0
- package/dist/platform/organizations.d.ts +205 -0
- package/dist/platform/organizations.js +180 -0
- package/dist/platform/provision-store.d.ts +88 -8
- package/dist/platform/provision-store.js +12 -0
- package/dist/platform/reports.d.ts +46 -0
- package/dist/platform/reports.js +31 -0
- package/dist/platform/server-metrics.d.ts +58 -0
- package/dist/platform/server-metrics.js +11 -0
- package/dist/pos/index.d.ts +567 -0
- package/dist/pos/index.js +205 -0
- package/dist/pricing/index.d.ts +405 -0
- package/dist/pricing/index.js +351 -0
- package/dist/provenance/index.d.ts +93 -0
- package/dist/provenance/index.js +53 -0
- package/dist/reports/index.d.ts +199 -0
- package/dist/reports/index.js +154 -0
- package/dist/shipping/parcel-count-suggestion.d.ts +52 -0
- package/dist/shipping/parcel-count-suggestion.js +73 -0
- package/dist/shipping/parcel-rule.d.ts +48 -0
- package/dist/shipping/parcel-rule.js +91 -0
- package/dist/stock/index.d.ts +166 -0
- package/dist/stock/index.js +115 -0
- package/dist/stock/transfers.d.ts +520 -0
- package/dist/stock/transfers.js +437 -0
- package/dist/stored-value/index.d.ts +154 -0
- package/dist/stored-value/index.js +130 -0
- package/dist/tenant/capabilities.d.ts +10 -0
- package/dist/tenant/entitlements.d.ts +35 -0
- package/dist/tenant/entitlements.js +15 -0
- package/dist/tenant/field-groups.d.ts +64 -0
- package/dist/tenant/field-groups.js +34 -0
- package/dist/tenant/legal-entity.d.ts +32 -0
- package/dist/tenant/legal-entity.js +41 -0
- package/dist/tenant/notifications.d.ts +148 -0
- package/dist/tenant/notifications.js +79 -0
- package/dist/tenant/retention.d.ts +10 -4
- package/dist/tenant/settings-sources.d.ts +103 -0
- package/dist/tenant/settings-sources.js +92 -0
- package/dist/types.d.ts +86 -11
- package/dist/uploads/index.d.ts +11 -7
- package/dist/users/index.d.ts +104 -0
- package/dist/users/index.js +80 -0
- package/package.json +3 -2
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import { pageFromVendureList, readOffsetCursor, } from '@sixtyninecommerce/core';
|
|
2
|
+
import { requireHumanSession } from './guard.js';
|
|
3
|
+
import { createPlatformLegalEntityNumberingModule, } from './legal-entity-numbering.js';
|
|
4
|
+
export const LEGAL_ENTITY_FIELDS = /* GraphQL */ `
|
|
5
|
+
id
|
|
6
|
+
name
|
|
7
|
+
legalName
|
|
8
|
+
vatNumber
|
|
9
|
+
vatNumberUk
|
|
10
|
+
eoriNumber
|
|
11
|
+
registrationNumber
|
|
12
|
+
registeredAddressLine1
|
|
13
|
+
registeredAddressLine2
|
|
14
|
+
registeredCity
|
|
15
|
+
registeredPostalCode
|
|
16
|
+
registeredProvince
|
|
17
|
+
registeredCountry
|
|
18
|
+
timezone
|
|
19
|
+
stores {
|
|
20
|
+
id
|
|
21
|
+
code
|
|
22
|
+
}
|
|
23
|
+
storeCount
|
|
24
|
+
editable
|
|
25
|
+
createdAt
|
|
26
|
+
updatedAt
|
|
27
|
+
`;
|
|
28
|
+
export const LEGAL_ENTITIES_QUERY = /* GraphQL */ `
|
|
29
|
+
query PlatformLegalEntities($options: LegalEntityListOptions) {
|
|
30
|
+
legalEntities(options: $options) {
|
|
31
|
+
totalItems
|
|
32
|
+
items { ${LEGAL_ENTITY_FIELDS} }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
export const LEGAL_ENTITY_QUERY = /* GraphQL */ `
|
|
37
|
+
query PlatformLegalEntity($id: ID!) {
|
|
38
|
+
legalEntity(id: $id) { ${LEGAL_ENTITY_FIELDS} }
|
|
39
|
+
}
|
|
40
|
+
`;
|
|
41
|
+
export const CREATE_LEGAL_ENTITY_MUTATION = /* GraphQL */ `
|
|
42
|
+
mutation PlatformCreateLegalEntity($input: CreateLegalEntityInput!) {
|
|
43
|
+
createLegalEntity(input: $input) { ${LEGAL_ENTITY_FIELDS} }
|
|
44
|
+
}
|
|
45
|
+
`;
|
|
46
|
+
export const UPDATE_LEGAL_ENTITY_MUTATION = /* GraphQL */ `
|
|
47
|
+
mutation UpdateLegalEntity($id: ID!, $input: UpdateLegalEntityInput!) {
|
|
48
|
+
updateLegalEntity(id: $id, input: $input) { ${LEGAL_ENTITY_FIELDS} }
|
|
49
|
+
}
|
|
50
|
+
`;
|
|
51
|
+
/** The engine's own default; it refuses a page above 100. */
|
|
52
|
+
const DEFAULT_PAGE_SIZE = 50;
|
|
53
|
+
const FIELD_NAMES = [
|
|
54
|
+
'vatNumber',
|
|
55
|
+
'vatNumberUk',
|
|
56
|
+
'eoriNumber',
|
|
57
|
+
'registrationNumber',
|
|
58
|
+
'registeredAddressLine1',
|
|
59
|
+
'registeredAddressLine2',
|
|
60
|
+
'registeredCity',
|
|
61
|
+
'registeredPostalCode',
|
|
62
|
+
'registeredProvince',
|
|
63
|
+
'registeredCountry',
|
|
64
|
+
'timezone',
|
|
65
|
+
];
|
|
66
|
+
/**
|
|
67
|
+
* The input as the engine declares it, field by field, so a caller spreading a wider object sends
|
|
68
|
+
* only what the input type has instead of failing validation. A field left out stays out (kept);
|
|
69
|
+
* `null` is sent as `null` (cleared).
|
|
70
|
+
*/
|
|
71
|
+
export function legalEntityInputOf(input) {
|
|
72
|
+
const out = {};
|
|
73
|
+
for (const field of ['name', 'legalName', ...FIELD_NAMES]) {
|
|
74
|
+
const value = input[field];
|
|
75
|
+
if (value !== undefined)
|
|
76
|
+
out[field] = value;
|
|
77
|
+
}
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
export function createPlatformLegalEntitiesModule(transport, config) {
|
|
81
|
+
// Platform-wide, so a SuperAdmin must not need a tenant.
|
|
82
|
+
const unscoped = (call) => ({ ...call, allowUnscoped: true });
|
|
83
|
+
const listPage = async (skip, take, options) => {
|
|
84
|
+
requireHumanSession(config);
|
|
85
|
+
const variables = {
|
|
86
|
+
options: { skip, take, ...(options?.term === undefined ? {} : { term: options.term }) },
|
|
87
|
+
};
|
|
88
|
+
const data = await transport.graphql({ api: 'admin', query: LEGAL_ENTITIES_QUERY, variables }, unscoped(options));
|
|
89
|
+
return pageFromVendureList(data.legalEntities, skip, take, (cursor) => listPage(readOffsetCursor(cursor), take, options));
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
numbering: createPlatformLegalEntityNumberingModule(transport, config),
|
|
93
|
+
async list(options) {
|
|
94
|
+
// Guarded here as well as per page: a bad cursor must not be reported ahead of the
|
|
95
|
+
// credential refusal.
|
|
96
|
+
requireHumanSession(config);
|
|
97
|
+
const take = options?.limit ?? DEFAULT_PAGE_SIZE;
|
|
98
|
+
const skip = options?.after === undefined ? 0 : readOffsetCursor(options.after);
|
|
99
|
+
return listPage(skip, take, options);
|
|
100
|
+
},
|
|
101
|
+
async get(id, call) {
|
|
102
|
+
requireHumanSession(config);
|
|
103
|
+
const data = await transport.graphql({ api: 'admin', query: LEGAL_ENTITY_QUERY, variables: { id } }, unscoped(call));
|
|
104
|
+
return data.legalEntity;
|
|
105
|
+
},
|
|
106
|
+
async create(input, call) {
|
|
107
|
+
requireHumanSession(config);
|
|
108
|
+
const data = await transport.graphql({
|
|
109
|
+
api: 'admin',
|
|
110
|
+
query: CREATE_LEGAL_ENTITY_MUTATION,
|
|
111
|
+
variables: { input: legalEntityInputOf(input) },
|
|
112
|
+
}, unscoped(call));
|
|
113
|
+
return data.createLegalEntity;
|
|
114
|
+
},
|
|
115
|
+
async update(id, input, call) {
|
|
116
|
+
requireHumanSession(config);
|
|
117
|
+
const { name: _ignored, ...fields } = input;
|
|
118
|
+
const data = await transport.graphql({
|
|
119
|
+
api: 'admin',
|
|
120
|
+
query: UPDATE_LEGAL_ENTITY_MUTATION,
|
|
121
|
+
variables: { id, input: legalEntityInputOf(fields) },
|
|
122
|
+
}, unscoped(call));
|
|
123
|
+
return data.updateLegalEntity;
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { AdminClientConfig } from '../client.js';
|
|
3
|
+
import type { Call } from '../types.js';
|
|
4
|
+
import type { LegalEntityStore } from './legal-entities.js';
|
|
5
|
+
/**
|
|
6
|
+
* A legal entity's numbering (multi-region B6c): the series its stores share. One store's invoice
|
|
7
|
+
* and credit-note numbers, and its AR credit notes where B2B credit is installed, are drawn from
|
|
8
|
+
* these, whichever store issues the document. A sandbox store numbers on its own rows and is not
|
|
9
|
+
* one of its stores here.
|
|
10
|
+
*/
|
|
11
|
+
export interface LegalEntityNumbering {
|
|
12
|
+
/** The legal entity's id. */
|
|
13
|
+
legalEntityId: string;
|
|
14
|
+
series: LegalEntityNumberingSeries[];
|
|
15
|
+
/** The stores sharing the series that the caller may read; `storeCount` counts them all. */
|
|
16
|
+
stores: LegalEntityStore[];
|
|
17
|
+
storeCount: number;
|
|
18
|
+
/**
|
|
19
|
+
* Whether the caller may change it: a SuperAdmin, or a caller holding `UpdateSettings` on every
|
|
20
|
+
* store of the entity. Show the series read-only when false.
|
|
21
|
+
*/
|
|
22
|
+
editable: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** The series the engine numbers: `invoice`, `credit_note`, and `ar_credit_note` with B2B credit. */
|
|
25
|
+
export type LegalEntityNumberingSeriesName = 'invoice' | 'credit_note' | 'ar_credit_note';
|
|
26
|
+
export interface LegalEntityNumberingSeries {
|
|
27
|
+
series: LegalEntityNumberingSeriesName;
|
|
28
|
+
/**
|
|
29
|
+
* False while the entity has no shared series yet. A lone numbering store's first document then
|
|
30
|
+
* sets it up; with two or more (AR credit notes: two or more that issued), every store's
|
|
31
|
+
* documents are refused (`LEGAL_ENTITY_NUMBERING_UNSET`) until an `update` sets the series up,
|
|
32
|
+
* and any `update` does, even an empty one. Every field below is null then.
|
|
33
|
+
*/
|
|
34
|
+
configured: boolean;
|
|
35
|
+
prefix: string | null;
|
|
36
|
+
suffix: string | null;
|
|
37
|
+
digits: number | null;
|
|
38
|
+
/** `none`, `short` (`26`) or `full` (`2026`). */
|
|
39
|
+
yearFormat: string | null;
|
|
40
|
+
/** Restart at 1 each fiscal year (in the entity's time zone); only with a year in the number. */
|
|
41
|
+
yearReset: boolean | null;
|
|
42
|
+
/** The next number the series issues. Read-only: only issuing a document moves it. */
|
|
43
|
+
nextNumber: number | null;
|
|
44
|
+
/** The fiscal year the series last restarted in. Read-only. */
|
|
45
|
+
lastResetYear: number | null;
|
|
46
|
+
/** `invoice` only: the provider that numbers it; null means each store's own setting. */
|
|
47
|
+
invoicingProvider: string | null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* A format change of one series. A field left out is kept; only the fields that differ from the
|
|
51
|
+
* entity's series count, so on a configured series sending back what was read changes nothing.
|
|
52
|
+
* On a series with `configured: false` any update, even an empty one, sets the series up.
|
|
53
|
+
*/
|
|
54
|
+
export interface UpdateLegalEntityNumberingInput {
|
|
55
|
+
series: LegalEntityNumberingSeriesName;
|
|
56
|
+
/** At most 30 characters, never starting with `TEST-` (`NUMBER_PREFIX_RESERVED`). */
|
|
57
|
+
prefix?: string;
|
|
58
|
+
/** At most 30 characters. */
|
|
59
|
+
suffix?: string;
|
|
60
|
+
/** A whole number from 3 to 8 (`NUMBERING_DIGITS_INVALID`). */
|
|
61
|
+
digits?: number;
|
|
62
|
+
/** `none`, `short` or `full` (`NUMBERING_YEAR_FORMAT_INVALID`). */
|
|
63
|
+
yearFormat?: 'none' | 'short' | 'full';
|
|
64
|
+
/** Restart at 1 each fiscal year; it has no effect without a year in the number. */
|
|
65
|
+
yearReset?: boolean;
|
|
66
|
+
}
|
|
67
|
+
export declare const LEGAL_ENTITY_NUMBERING_FIELDS = "\n legalEntityId\n series {\n series\n configured\n prefix\n suffix\n digits\n yearFormat\n yearReset\n nextNumber\n lastResetYear\n invoicingProvider\n }\n stores {\n id\n code\n }\n storeCount\n editable\n";
|
|
68
|
+
export declare const LEGAL_ENTITY_NUMBERING_QUERY = "\n query LegalEntityNumbering($legalEntityId: ID) {\n legalEntityNumbering(legalEntityId: $legalEntityId) { \n legalEntityId\n series {\n series\n configured\n prefix\n suffix\n digits\n yearFormat\n yearReset\n nextNumber\n lastResetYear\n invoicingProvider\n }\n stores {\n id\n code\n }\n storeCount\n editable\n }\n }\n";
|
|
69
|
+
export declare const UPDATE_LEGAL_ENTITY_NUMBERING_MUTATION = "\n mutation UpdateLegalEntityNumbering($input: UpdateLegalEntityNumberingInput!) {\n updateLegalEntityNumbering(input: $input) { \n legalEntityId\n series {\n series\n configured\n prefix\n suffix\n digits\n yearFormat\n yearReset\n nextNumber\n lastResetYear\n invoicingProvider\n }\n stores {\n id\n code\n }\n storeCount\n editable\n }\n }\n";
|
|
70
|
+
/**
|
|
71
|
+
* The input as the engine declares it, field by field, so a caller spreading a series it read
|
|
72
|
+
* (which carries counters and `configured`) sends only the format, and a field left out stays out.
|
|
73
|
+
*/
|
|
74
|
+
export declare function legalEntityNumberingInputOf(input: UpdateLegalEntityNumberingInput, legalEntityId?: string): Record<string, unknown>;
|
|
75
|
+
/**
|
|
76
|
+
* Refusals the engine answers on `update`: `SHARED_SELLER_NOT_ALLOWED` (403),
|
|
77
|
+
* `ENTITY_NOT_FOUND` (404), `NUMBERING_SERIES_INVALID`, `NUMBERING_YEAR_FORMAT_INVALID`,
|
|
78
|
+
* `NUMBERING_DIGITS_INVALID`, `NUMBERING_AFFIX_INVALID` (400), `NUMBER_PREFIX_RESERVED`,
|
|
79
|
+
* `LEGAL_ENTITY_NUMBERING_PROVIDERS_DIFFER`, `NUMBERING_RESTART_WOULD_REPEAT`,
|
|
80
|
+
* `NUMBERING_YEAR_FORMAT_UNKNOWN` (422).
|
|
81
|
+
*/
|
|
82
|
+
export interface PlatformLegalEntityNumberingModule {
|
|
83
|
+
/**
|
|
84
|
+
* Null when no legal entity has that id, or the caller may read none of its stores;
|
|
85
|
+
* `SHARED_SELLER_NOT_ALLOWED` (403) when it may read only some of them.
|
|
86
|
+
*/
|
|
87
|
+
get(legalEntityId: string, call?: Call): Promise<LegalEntityNumbering | null>;
|
|
88
|
+
/**
|
|
89
|
+
* Changes the format of one series, on the entity and on every store row of it, in one
|
|
90
|
+
* transaction, and sets the series up when it has none yet: it then continues past every number
|
|
91
|
+
* any store issued. Counters change only when an update sets a series up. Safe to repeat.
|
|
92
|
+
*/
|
|
93
|
+
update(legalEntityId: string, input: UpdateLegalEntityNumberingInput, call?: Call): Promise<LegalEntityNumbering>;
|
|
94
|
+
}
|
|
95
|
+
/** The same, for the ACTIVE store's legal entity: one request each, no id needed. */
|
|
96
|
+
export interface TenantLegalEntityNumberingModule {
|
|
97
|
+
/** Null on a store with no legal entity, an organization's workspace and the root channel. */
|
|
98
|
+
get(call?: Call): Promise<LegalEntityNumbering | null>;
|
|
99
|
+
/** `ENTITY_NOT_FOUND` (404) on a store with no legal entity; otherwise as the platform's. */
|
|
100
|
+
update(input: UpdateLegalEntityNumberingInput, call?: Call): Promise<LegalEntityNumbering>;
|
|
101
|
+
}
|
|
102
|
+
export declare function createPlatformLegalEntityNumberingModule(transport: Transport, config: AdminClientConfig): PlatformLegalEntityNumberingModule;
|
|
103
|
+
export declare function createTenantLegalEntityNumberingModule(transport: Transport): TenantLegalEntityNumberingModule;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { ValidationError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { requireHumanSession } from './guard.js';
|
|
3
|
+
export const LEGAL_ENTITY_NUMBERING_FIELDS = /* GraphQL */ `
|
|
4
|
+
legalEntityId
|
|
5
|
+
series {
|
|
6
|
+
series
|
|
7
|
+
configured
|
|
8
|
+
prefix
|
|
9
|
+
suffix
|
|
10
|
+
digits
|
|
11
|
+
yearFormat
|
|
12
|
+
yearReset
|
|
13
|
+
nextNumber
|
|
14
|
+
lastResetYear
|
|
15
|
+
invoicingProvider
|
|
16
|
+
}
|
|
17
|
+
stores {
|
|
18
|
+
id
|
|
19
|
+
code
|
|
20
|
+
}
|
|
21
|
+
storeCount
|
|
22
|
+
editable
|
|
23
|
+
`;
|
|
24
|
+
export const LEGAL_ENTITY_NUMBERING_QUERY = /* GraphQL */ `
|
|
25
|
+
query LegalEntityNumbering($legalEntityId: ID) {
|
|
26
|
+
legalEntityNumbering(legalEntityId: $legalEntityId) { ${LEGAL_ENTITY_NUMBERING_FIELDS} }
|
|
27
|
+
}
|
|
28
|
+
`;
|
|
29
|
+
export const UPDATE_LEGAL_ENTITY_NUMBERING_MUTATION = /* GraphQL */ `
|
|
30
|
+
mutation UpdateLegalEntityNumbering($input: UpdateLegalEntityNumberingInput!) {
|
|
31
|
+
updateLegalEntityNumbering(input: $input) { ${LEGAL_ENTITY_NUMBERING_FIELDS} }
|
|
32
|
+
}
|
|
33
|
+
`;
|
|
34
|
+
const INPUT_FIELDS = ['series', 'prefix', 'suffix', 'digits', 'yearFormat', 'yearReset'];
|
|
35
|
+
/**
|
|
36
|
+
* The input as the engine declares it, field by field, so a caller spreading a series it read
|
|
37
|
+
* (which carries counters and `configured`) sends only the format, and a field left out stays out.
|
|
38
|
+
*/
|
|
39
|
+
export function legalEntityNumberingInputOf(input, legalEntityId) {
|
|
40
|
+
const out = {};
|
|
41
|
+
if (legalEntityId !== undefined)
|
|
42
|
+
out.legalEntityId = legalEntityId;
|
|
43
|
+
for (const field of INPUT_FIELDS) {
|
|
44
|
+
const value = input[field];
|
|
45
|
+
if (value !== undefined && value !== null)
|
|
46
|
+
out[field] = value;
|
|
47
|
+
}
|
|
48
|
+
return out;
|
|
49
|
+
}
|
|
50
|
+
async function read(transport, legalEntityId, call) {
|
|
51
|
+
const data = await transport.graphql({
|
|
52
|
+
api: 'admin',
|
|
53
|
+
query: LEGAL_ENTITY_NUMBERING_QUERY,
|
|
54
|
+
variables: legalEntityId === undefined ? {} : { legalEntityId },
|
|
55
|
+
}, call);
|
|
56
|
+
return data.legalEntityNumbering;
|
|
57
|
+
}
|
|
58
|
+
async function write(transport, legalEntityId, input, call) {
|
|
59
|
+
const data = await transport.graphql({
|
|
60
|
+
api: 'admin',
|
|
61
|
+
query: UPDATE_LEGAL_ENTITY_NUMBERING_MUTATION,
|
|
62
|
+
variables: { input: legalEntityNumberingInputOf(input, legalEntityId) },
|
|
63
|
+
}, call);
|
|
64
|
+
return data.updateLegalEntityNumbering;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The platform methods name the entity: without an id the engine would answer for the ACTIVE
|
|
68
|
+
* store's entity instead, which on a tenant-scoped client is a different one than asked for.
|
|
69
|
+
*/
|
|
70
|
+
function requireLegalEntityId(legalEntityId) {
|
|
71
|
+
if (typeof legalEntityId !== 'string' || legalEntityId.trim() === '') {
|
|
72
|
+
throw new ValidationError({
|
|
73
|
+
code: 'LEGAL_ENTITY_ID_REQUIRED',
|
|
74
|
+
message: 'platform.legalEntities.numbering needs the legal entity id. For the active store’s ' +
|
|
75
|
+
'entity use tenant.legalEntity.numbering.',
|
|
76
|
+
status: 400,
|
|
77
|
+
service: 'vendure',
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return legalEntityId;
|
|
81
|
+
}
|
|
82
|
+
export function createPlatformLegalEntityNumberingModule(transport, config) {
|
|
83
|
+
// Platform-wide, so a SuperAdmin must not need a tenant.
|
|
84
|
+
const unscoped = (call) => ({ ...call, allowUnscoped: true });
|
|
85
|
+
return {
|
|
86
|
+
async get(legalEntityId, call) {
|
|
87
|
+
requireHumanSession(config);
|
|
88
|
+
return read(transport, requireLegalEntityId(legalEntityId), unscoped(call));
|
|
89
|
+
},
|
|
90
|
+
async update(legalEntityId, input, call) {
|
|
91
|
+
requireHumanSession(config);
|
|
92
|
+
return write(transport, requireLegalEntityId(legalEntityId), input, unscoped(call));
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
export function createTenantLegalEntityNumberingModule(transport) {
|
|
97
|
+
return {
|
|
98
|
+
get: (call) => read(transport, undefined, call),
|
|
99
|
+
update: (input, call) => write(transport, undefined, input, call),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { AdminClientConfig } from '../client.js';
|
|
3
|
+
import type { Call } from '../types.js';
|
|
4
|
+
/** A store's membership of an organization (multi-region spec §5.1, §7.10.3). */
|
|
5
|
+
export interface OrganizationMembership {
|
|
6
|
+
/** The member store's channel id. */
|
|
7
|
+
channelId: string;
|
|
8
|
+
/** The member store. Null once the store no longer exists. */
|
|
9
|
+
channel: {
|
|
10
|
+
id: string;
|
|
11
|
+
code: string;
|
|
12
|
+
} | null;
|
|
13
|
+
organizationId: string;
|
|
14
|
+
/** A free grouping label, such as `EMEA`. Null when none is set. */
|
|
15
|
+
region: string | null;
|
|
16
|
+
/** Whether the storefront region switcher lists this store. */
|
|
17
|
+
listedInRegionSwitcher: boolean;
|
|
18
|
+
/** ISO-8601 UTC. */
|
|
19
|
+
joinedAt: string;
|
|
20
|
+
}
|
|
21
|
+
export interface JoinOrganizationInput {
|
|
22
|
+
/** The organization code, not the `org-` workspace channel code. */
|
|
23
|
+
organizationCode: string;
|
|
24
|
+
/** The store's channel id. */
|
|
25
|
+
channelId: string;
|
|
26
|
+
/**
|
|
27
|
+
* A grouping label, at most 64 characters with no line breaks. Omitted keeps
|
|
28
|
+
* the region a re-join finds (a first join has none); `null` or `''` clears it.
|
|
29
|
+
*/
|
|
30
|
+
region?: string | null;
|
|
31
|
+
/**
|
|
32
|
+
* `true`: the store keeps its own values for every settings group instead of
|
|
33
|
+
* inheriting the organization's; each group can be switched to inherit later.
|
|
34
|
+
* On a re-join, a group the store inherits unlocked at that moment starts
|
|
35
|
+
* from a copy of the organization's values.
|
|
36
|
+
* Omitted or `false`: it inherits whatever the organization shares, and on a
|
|
37
|
+
* re-join nothing it already keeps changes. Needs an engine that knows the
|
|
38
|
+
* field (a `true` sent to an older one is a GraphQL validation error).
|
|
39
|
+
*/
|
|
40
|
+
keepOwnSettings?: boolean;
|
|
41
|
+
}
|
|
42
|
+
/** The membership half of `admin.platform.organizations`. */
|
|
43
|
+
export interface OrganizationMembershipMethods {
|
|
44
|
+
/**
|
|
45
|
+
* Makes a store a member of an organization and answers the membership: the
|
|
46
|
+
* organization's roles are granted on the store, and the workspace takes on
|
|
47
|
+
* the store's currencies and languages. Joining the organization the store
|
|
48
|
+
* is already in answers the same membership (`joinedAt` unchanged), changing
|
|
49
|
+
* only the region when one is given — so a join whose answer was lost is
|
|
50
|
+
* safe to repeat. `keepOwnSettings: true` makes the store keep its own
|
|
51
|
+
* settings (only the groups it does not keep yet are added on a re-join).
|
|
52
|
+
* There is no way to leave yet. SuperAdmin only.
|
|
53
|
+
*
|
|
54
|
+
* Refusals: `ORGANIZATION_MEMBERSHIP_CONFLICT` (409 `ConflictError`: the store
|
|
55
|
+
* belongs to another organization); `ORGANIZATION_NOT_FOUND` (no organization
|
|
56
|
+
* has that code) and `ORGANIZATION_MEMBER_STORE_NOT_FOUND` (no store has that
|
|
57
|
+
* id) (404 `NotFoundError`); `ORGANIZATION_MEMBER_NOT_A_STORE` (the platform's
|
|
58
|
+
* root channel or an organization's workspace) and
|
|
59
|
+
* `ORGANIZATION_MEMBER_STORE_CLOSED` (restore the store first) and
|
|
60
|
+
* `ORGANIZATION_TAX_MODE_MISMATCH` (the store's `pricesIncludeTax` differs
|
|
61
|
+
* from the organization's: every store of an organization enters prices the
|
|
62
|
+
* same way; judged on a first join only) (422
|
|
63
|
+
* `BusinessRuleError`); `ORGANIZATION_MEMBER_REGION_INVALID` (400
|
|
64
|
+
* `ValidationError`); `FORBIDDEN` (403) for a caller who is not a SuperAdmin.
|
|
65
|
+
*/
|
|
66
|
+
join(input: JoinOrganizationInput, call?: Call): Promise<OrganizationMembership>;
|
|
67
|
+
/**
|
|
68
|
+
* The organization's member stores, oldest member first. A store that no
|
|
69
|
+
* longer exists is left out. A SuperAdmin reads any organization's with no
|
|
70
|
+
* tenant; an organization admin reads its own with the workspace channel
|
|
71
|
+
* (`org-<code>`) as the tenant, and is refused elsewhere with a 403
|
|
72
|
+
* `PermissionDeniedError` (`FORBIDDEN`, or `ORGANIZATION_ACCESS_DENIED` for
|
|
73
|
+
* another organization). An unknown id is `ORGANIZATION_NOT_FOUND` (404
|
|
74
|
+
* `NotFoundError`), not an empty list.
|
|
75
|
+
*/
|
|
76
|
+
members(organizationId: string, call?: Call): Promise<OrganizationMembership[]>;
|
|
77
|
+
}
|
|
78
|
+
export declare const JOIN_ORGANIZATION_MUTATION = "\n mutation PlatformJoinOrganization($channelId: ID!, $input: JoinOrganizationInput!) {\n joinOrganization(channelId: $channelId, input: $input) { \n channelId\n channel {\n id\n code\n }\n organizationId\n region\n listedInRegionSwitcher\n joinedAt\n }\n }\n";
|
|
79
|
+
export declare const ORGANIZATION_MEMBERS_QUERY = "\n query PlatformOrganizationMembers($id: ID!) {\n organization(id: $id) {\n id\n members { \n channelId\n channel {\n id\n code\n }\n organizationId\n region\n listedInRegionSwitcher\n joinedAt\n }\n }\n }\n";
|
|
80
|
+
export declare function createOrganizationMembershipMethods(transport: Transport, config: AdminClientConfig): OrganizationMembershipMethods;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { NotFoundError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { requireHumanSession } from './guard.js';
|
|
3
|
+
const MEMBERSHIP_FIELDS = /* GraphQL */ `
|
|
4
|
+
channelId
|
|
5
|
+
channel {
|
|
6
|
+
id
|
|
7
|
+
code
|
|
8
|
+
}
|
|
9
|
+
organizationId
|
|
10
|
+
region
|
|
11
|
+
listedInRegionSwitcher
|
|
12
|
+
joinedAt
|
|
13
|
+
`;
|
|
14
|
+
export const JOIN_ORGANIZATION_MUTATION = /* GraphQL */ `
|
|
15
|
+
mutation PlatformJoinOrganization($channelId: ID!, $input: JoinOrganizationInput!) {
|
|
16
|
+
joinOrganization(channelId: $channelId, input: $input) { ${MEMBERSHIP_FIELDS} }
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
// Through `organization(id)` rather than a query of its own: the engine
|
|
20
|
+
// applies the organization reads' access rule there (a SuperAdmin, or the
|
|
21
|
+
// organization admin in its own workspace), and answers null for an unknown id.
|
|
22
|
+
export const ORGANIZATION_MEMBERS_QUERY = /* GraphQL */ `
|
|
23
|
+
query PlatformOrganizationMembers($id: ID!) {
|
|
24
|
+
organization(id: $id) {
|
|
25
|
+
id
|
|
26
|
+
members { ${MEMBERSHIP_FIELDS} }
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
`;
|
|
30
|
+
export function createOrganizationMembershipMethods(transport, config) {
|
|
31
|
+
// Platform-wide, as in `createPlatformOrganizationsModule`: the opt-in only
|
|
32
|
+
// matters when the scope is `none`, so an organization admin's workspace
|
|
33
|
+
// tenant still goes out.
|
|
34
|
+
const unscoped = (call) => ({ ...call, allowUnscoped: true });
|
|
35
|
+
return {
|
|
36
|
+
async join(input, call) {
|
|
37
|
+
requireHumanSession(config);
|
|
38
|
+
// The engine reads an ABSENT region as "keep the current one" and null as
|
|
39
|
+
// "clear it", so an omitted region must stay absent on the wire, never
|
|
40
|
+
// become null.
|
|
41
|
+
const joinInput = {
|
|
42
|
+
organizationCode: input.organizationCode,
|
|
43
|
+
};
|
|
44
|
+
if (input.region !== undefined)
|
|
45
|
+
joinInput.region = input.region;
|
|
46
|
+
// Only `true` goes on the wire: `false` means what an absent field means, and an engine
|
|
47
|
+
// older than the field refuses the whole document when it sees one it does not know.
|
|
48
|
+
if (input.keepOwnSettings === true)
|
|
49
|
+
joinInput.keepOwnSettings = true;
|
|
50
|
+
const data = await transport.graphql({
|
|
51
|
+
api: 'admin',
|
|
52
|
+
query: JOIN_ORGANIZATION_MUTATION,
|
|
53
|
+
variables: { channelId: input.channelId, input: joinInput },
|
|
54
|
+
}, unscoped(call));
|
|
55
|
+
return data.joinOrganization;
|
|
56
|
+
},
|
|
57
|
+
async members(organizationId, call) {
|
|
58
|
+
requireHumanSession(config);
|
|
59
|
+
const data = await transport.graphql({ api: 'admin', query: ORGANIZATION_MEMBERS_QUERY, variables: { id: organizationId } }, unscoped(call));
|
|
60
|
+
if (!data.organization) {
|
|
61
|
+
throw new NotFoundError({
|
|
62
|
+
code: 'ORGANIZATION_NOT_FOUND',
|
|
63
|
+
message: `No organization has the id ${organizationId}.`,
|
|
64
|
+
status: 404,
|
|
65
|
+
service: 'vendure',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return data.organization.members;
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type Transport } from '@sixtyninecommerce/core';
|
|
2
|
+
import type { AdminClientConfig } from '../client.js';
|
|
3
|
+
import type { SettingsGroup } from '../tenant/settings-sources.js';
|
|
4
|
+
import type { Call } from '../types.js';
|
|
5
|
+
/** A member store that keeps its own values for one settings group. */
|
|
6
|
+
export interface OrganizationSettingsOverride {
|
|
7
|
+
/** The member store's channel id. */
|
|
8
|
+
channelId: string;
|
|
9
|
+
channelCode: string;
|
|
10
|
+
group: SettingsGroup | (string & {});
|
|
11
|
+
/**
|
|
12
|
+
* When the store started keeping its own values: ISO-8601 in UTC (`…Z`), exactly as the engine
|
|
13
|
+
* answers it. Compare instants with `Date.parse`; format it in the viewer's time zone only for
|
|
14
|
+
* display.
|
|
15
|
+
*/
|
|
16
|
+
overriddenAt: string;
|
|
17
|
+
}
|
|
18
|
+
/** The settings half of `admin.platform.organizations`. */
|
|
19
|
+
export interface OrganizationSettingsMethods {
|
|
20
|
+
/**
|
|
21
|
+
* The member stores keeping their own values, one entry per store and group, whether or not
|
|
22
|
+
* the organization shares that group today — what "Lock for all stores" would take away from
|
|
23
|
+
* them. Ordered by store code, then by group in catalog order; a store that no longer exists
|
|
24
|
+
* is left out. A SuperAdmin reads any organization's with no tenant; an organization admin
|
|
25
|
+
* reads its own with the workspace channel (`org-<code>`) as the tenant and is refused
|
|
26
|
+
* elsewhere with a 403 `PermissionDeniedError` (`FORBIDDEN`, or `ORGANIZATION_ACCESS_DENIED` for
|
|
27
|
+
* another organization). A caller who may read the organization but not manage it gets an
|
|
28
|
+
* empty list. An unknown id is `ORGANIZATION_NOT_FOUND` (404 `NotFoundError`).
|
|
29
|
+
*/
|
|
30
|
+
settingsOverrides(organizationId: string, call?: Call): Promise<OrganizationSettingsOverride[]>;
|
|
31
|
+
}
|
|
32
|
+
export declare const ORGANIZATION_SETTINGS_OVERRIDES_QUERY = "\n query PlatformOrganizationSettingsOverrides($id: ID!) {\n organization(id: $id) {\n id\n settingsOverrides {\n channelId\n channelCode\n group\n overriddenAt\n }\n }\n }\n";
|
|
33
|
+
export declare function createOrganizationSettingsMethods(transport: Transport, config: AdminClientConfig): OrganizationSettingsMethods;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { NotFoundError } from '@sixtyninecommerce/core';
|
|
2
|
+
import { requireHumanSession } from './guard.js';
|
|
3
|
+
// Through `organization(id)`, like `members`: the engine applies the organization reads' access
|
|
4
|
+
// rule there and answers null for an unknown id. A separate document rather than a field of every
|
|
5
|
+
// organization read, so `get`/`list` keep working against an engine older than the field.
|
|
6
|
+
export const ORGANIZATION_SETTINGS_OVERRIDES_QUERY = /* GraphQL */ `
|
|
7
|
+
query PlatformOrganizationSettingsOverrides($id: ID!) {
|
|
8
|
+
organization(id: $id) {
|
|
9
|
+
id
|
|
10
|
+
settingsOverrides {
|
|
11
|
+
channelId
|
|
12
|
+
channelCode
|
|
13
|
+
group
|
|
14
|
+
overriddenAt
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
`;
|
|
19
|
+
export function createOrganizationSettingsMethods(transport, config) {
|
|
20
|
+
// As in `organizations.ts`: a SuperAdmin needs no tenant, and an organization admin's
|
|
21
|
+
// workspace tenant is left alone.
|
|
22
|
+
const unscoped = (call) => ({ ...call, allowUnscoped: true });
|
|
23
|
+
return {
|
|
24
|
+
async settingsOverrides(organizationId, call) {
|
|
25
|
+
requireHumanSession(config);
|
|
26
|
+
const data = await transport.graphql({
|
|
27
|
+
api: 'admin',
|
|
28
|
+
query: ORGANIZATION_SETTINGS_OVERRIDES_QUERY,
|
|
29
|
+
variables: { id: organizationId },
|
|
30
|
+
}, unscoped(call));
|
|
31
|
+
if (!data.organization) {
|
|
32
|
+
throw new NotFoundError({
|
|
33
|
+
code: 'ORGANIZATION_NOT_FOUND',
|
|
34
|
+
message: `No organization has the id ${organizationId}.`,
|
|
35
|
+
status: 404,
|
|
36
|
+
service: 'vendure',
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return data.organization.settingsOverrides;
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
}
|