@tallyui/connector-woocommerce 3.0.4 → 3.1.1
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 +9 -0
- package/dist/index.d.ts +29 -2
- package/dist/index.js +107 -1
- package/package.json +3 -3
- package/src/customers.ts +107 -0
- package/src/index.ts +5 -0
package/README.md
CHANGED
|
@@ -35,6 +35,15 @@ The incremental pull can miss an edit: a `modified_after` that over-excludes (in
|
|
|
35
35
|
- When `wcpos/v2/status` lists `products_id_fast_path` in `capabilities`, the whole catalogue is listed in one request (`per_page=-1` with `_fields=id,date_modified_gmt,stock_quantity,stock_status`) instead of pages of 100; a store that refuses it (a 400) is listed page by page in the same pass.
|
|
36
36
|
- Both listings key on the numeric product id, never the till-local uuid; the adapter's `matchKey` is how the runner finds the till's copy.
|
|
37
37
|
|
|
38
|
+
## Customers
|
|
39
|
+
|
|
40
|
+
On WCPOS 1.10.x, `searchCustomers` uses `GET wcpos/v2/customers` with `role=customer`.
|
|
41
|
+
`getCustomer` uses the same route with `include=<id>&per_page=1&role=all`; there is no per-id route.
|
|
42
|
+
`createCustomer` uses `POST wcpos/v2/push/customers`. Create is idempotent per call through the WCPOS mutation id;
|
|
43
|
+
a repeat call after a lost answer uses new ids and gives `invalid` (email exists). There is no automatic retry.
|
|
44
|
+
`emailReceipt` uses `POST wcpos/v2/orders/<id>/email`, optionally saving the email to billing.
|
|
45
|
+
It is not idempotent: each call sends a mail, so don't auto-retry. The cashier needs `access_woocommerce_pos`.
|
|
46
|
+
|
|
38
47
|
## Errors
|
|
39
48
|
|
|
40
49
|
- `ConnectorUnauthorizedError`, from `@tallyui/core` and re-exported here: the store refused the request (401: the token is missing, expired or revoked; 403: signed in but not allowed), or `WooMissingTokenError` (a subclass) was thrown for a missing token. `error.status` is `401` (`code: 'unauthorized'`, sign in again) or `403` (`code: 'forbidden'`, signed in but not allowed: the pull retries on the store schedule; don't sign out).
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,34 @@
|
|
|
1
|
-
import { OrderCreateEnvelope, CommandResult, ProductTraits, CollectionSync, ReplicationAdapter, ReconcileFeed, CatalogueReconcileAdapter, ConnectorUnauthorizedError, TallyConnector } from '@tallyui/core';
|
|
1
|
+
import { SyncContext, Customer, CustomerInput, OrderCreateEnvelope, CommandResult, ProductTraits, CollectionSync, ReplicationAdapter, ReconcileFeed, CatalogueReconcileAdapter, ConnectorUnauthorizedError, TallyConnector } from '@tallyui/core';
|
|
2
2
|
export { ConnectorUnauthorizedError } from '@tallyui/core';
|
|
3
3
|
import { RxJsonSchema } from 'rxdb';
|
|
4
4
|
|
|
5
|
+
type WooCustomerRow = {
|
|
6
|
+
id: number | string;
|
|
7
|
+
first_name?: string | null;
|
|
8
|
+
last_name?: string | null;
|
|
9
|
+
email?: string | null;
|
|
10
|
+
billing?: {
|
|
11
|
+
first_name?: string | null;
|
|
12
|
+
last_name?: string | null;
|
|
13
|
+
email?: string | null;
|
|
14
|
+
phone?: string | null;
|
|
15
|
+
company?: string | null;
|
|
16
|
+
} | null;
|
|
17
|
+
};
|
|
18
|
+
declare function toWooCustomer(raw: WooCustomerRow): Customer;
|
|
19
|
+
declare function createWooCustomers(options?: {
|
|
20
|
+
newId?: () => string;
|
|
21
|
+
}): {
|
|
22
|
+
searchCustomers(context: SyncContext, query: string, options?: {
|
|
23
|
+
limit?: number;
|
|
24
|
+
}): Promise<Customer[]>;
|
|
25
|
+
getCustomer(context: SyncContext, id: string): Promise<Customer | null>;
|
|
26
|
+
createCustomer(context: SyncContext, input: CustomerInput): Promise<Customer>;
|
|
27
|
+
emailReceipt(context: SyncContext, orderId: string, email: string, options?: {
|
|
28
|
+
saveToBilling?: boolean;
|
|
29
|
+
}): Promise<void>;
|
|
30
|
+
};
|
|
31
|
+
|
|
5
32
|
type WooTransportOutcome = {
|
|
6
33
|
kind: 'results';
|
|
7
34
|
results: CommandResult[];
|
|
@@ -171,4 +198,4 @@ declare function createWooCommerceConnector(): TallyConnector;
|
|
|
171
198
|
*/
|
|
172
199
|
declare const woocommerceConnector: TallyConnector;
|
|
173
200
|
|
|
174
|
-
export { type WooCommandTransportOptions, WooDateFilterError, WooMissingTokenError, WooMissingUuidError, WooTillUpdateRequiredError, WooTokenRefusedError, createWooCommandTransport, createWooCommerceConnector, toWooOrderPayload, wcposClientPart, wooCatalogueReconcile, wooProductReplication, wooProductSchema, wooProductSync, wooProductTraits, wooReconcileFingerprint, woocommerceConnector };
|
|
201
|
+
export { type WooCommandTransportOptions, WooDateFilterError, WooMissingTokenError, WooMissingUuidError, WooTillUpdateRequiredError, WooTokenRefusedError, createWooCommandTransport, createWooCommerceConnector, createWooCustomers, toWooCustomer, toWooOrderPayload, wcposClientPart, wooCatalogueReconcile, wooProductReplication, wooProductSchema, wooProductSync, wooProductTraits, wooReconcileFingerprint, woocommerceConnector };
|
package/dist/index.js
CHANGED
|
@@ -592,8 +592,109 @@ function createWooReconcileFeed() {
|
|
|
592
592
|
return createReconcileFeed({ key: (doc) => doc.uuid, remote: (doc) => doc.id, fetchByIds: wooFetchByIds });
|
|
593
593
|
}
|
|
594
594
|
|
|
595
|
+
// src/customers.ts
|
|
596
|
+
import { CustomerServiceError } from "@tallyui/core";
|
|
597
|
+
function toWooCustomer(raw) {
|
|
598
|
+
const customer = { id: String(raw.id), name: String(raw.id) };
|
|
599
|
+
for (const [key, value] of [
|
|
600
|
+
["firstName", raw.first_name || raw.billing?.first_name],
|
|
601
|
+
["lastName", raw.last_name || raw.billing?.last_name],
|
|
602
|
+
["email", raw.email || raw.billing?.email],
|
|
603
|
+
["phone", raw.billing?.phone],
|
|
604
|
+
["company", raw.billing?.company]
|
|
605
|
+
]) {
|
|
606
|
+
if (typeof value === "string" && value.length > 0) customer[key] = value;
|
|
607
|
+
}
|
|
608
|
+
customer.name = [customer.firstName, customer.lastName].filter(Boolean).join(" ") || customer.company || customer.email || customer.id;
|
|
609
|
+
return customer;
|
|
610
|
+
}
|
|
611
|
+
function newWooId() {
|
|
612
|
+
if (!globalThis.crypto?.getRandomValues) throw new Error("connector-woocommerce: no random source");
|
|
613
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
614
|
+
bytes[6] = bytes[6] & 15 | 64;
|
|
615
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
616
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, "0")).join("");
|
|
617
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
618
|
+
}
|
|
619
|
+
async function customerRequest(context, route, init = {}) {
|
|
620
|
+
let response;
|
|
621
|
+
try {
|
|
622
|
+
response = await fetch(`${context.baseUrl}/${route}`, {
|
|
623
|
+
...init,
|
|
624
|
+
headers: { ...init.headers, ...context.headers },
|
|
625
|
+
signal: context.signal
|
|
626
|
+
});
|
|
627
|
+
} catch (error) {
|
|
628
|
+
throw new CustomerServiceError("network", error instanceof Error ? error.message : String(error));
|
|
629
|
+
}
|
|
630
|
+
if ([401, 403, 426].includes(response.status)) await checkResponse(response);
|
|
631
|
+
const body = await response.json().catch(() => void 0);
|
|
632
|
+
if (!response.ok) {
|
|
633
|
+
const invalid = [400, 404, 422].includes(response.status);
|
|
634
|
+
throw new CustomerServiceError(invalid ? "invalid" : "server", invalid ? body?.message ?? `HTTP ${response.status}` : `HTTP ${response.status}`);
|
|
635
|
+
}
|
|
636
|
+
return { status: response.status, body };
|
|
637
|
+
}
|
|
638
|
+
function createWooCustomers(options) {
|
|
639
|
+
const newId = options?.newId ?? newWooId;
|
|
640
|
+
return {
|
|
641
|
+
async searchCustomers(context, query, options2) {
|
|
642
|
+
const q = query.trim();
|
|
643
|
+
if (!q) return [];
|
|
644
|
+
const params = new URLSearchParams({ search: q, role: "customer", per_page: String(Math.max(1, Math.min(50, options2?.limit ?? 20))) });
|
|
645
|
+
const { body } = await customerRequest(context, `customers?${params}`, { method: "GET" });
|
|
646
|
+
if (!Array.isArray(body)) throw new CustomerServiceError("server", "unexpected response");
|
|
647
|
+
return body.map(toWooCustomer);
|
|
648
|
+
},
|
|
649
|
+
async getCustomer(context, id) {
|
|
650
|
+
if (!/^\d+$/.test(id)) return null;
|
|
651
|
+
const params = new URLSearchParams({ include: id, per_page: "1", role: "all" });
|
|
652
|
+
const { body } = await customerRequest(context, `customers?${params}`, { method: "GET" });
|
|
653
|
+
if (!Array.isArray(body)) throw new CustomerServiceError("server", "unexpected response");
|
|
654
|
+
const raw = body.find((row) => String(row.id) === id);
|
|
655
|
+
return raw ? toWooCustomer(raw) : null;
|
|
656
|
+
},
|
|
657
|
+
async createCustomer(context, input) {
|
|
658
|
+
const mutationId = newId();
|
|
659
|
+
const recordId = newId();
|
|
660
|
+
const fields = {
|
|
661
|
+
...input.email ? { email: input.email } : {},
|
|
662
|
+
...input.firstName ? { first_name: input.firstName } : {},
|
|
663
|
+
...input.lastName ? { last_name: input.lastName } : {}
|
|
664
|
+
};
|
|
665
|
+
const { status, body } = await customerRequest(context, "push/customers", {
|
|
666
|
+
method: "POST",
|
|
667
|
+
headers: { "Content-Type": "application/json" },
|
|
668
|
+
body: JSON.stringify({
|
|
669
|
+
mutationId,
|
|
670
|
+
operation: "create",
|
|
671
|
+
collection: "customers",
|
|
672
|
+
recordId,
|
|
673
|
+
baseRevision: null,
|
|
674
|
+
payload: { ...fields, billing: {
|
|
675
|
+
...fields,
|
|
676
|
+
...input.phone ? { phone: input.phone } : {},
|
|
677
|
+
...input.company ? { company: input.company } : {}
|
|
678
|
+
} }
|
|
679
|
+
})
|
|
680
|
+
});
|
|
681
|
+
if (status !== 200 && status !== 201 || !body?.document) throw new CustomerServiceError("server", "unexpected response");
|
|
682
|
+
return toWooCustomer(body.document);
|
|
683
|
+
},
|
|
684
|
+
async emailReceipt(context, orderId, email, options2) {
|
|
685
|
+
if (!/^[1-9]\d*$/.test(orderId)) throw new CustomerServiceError("invalid", "Invalid order ID");
|
|
686
|
+
const { status, body } = await customerRequest(context, `orders/${encodeURIComponent(orderId)}/email`, {
|
|
687
|
+
method: "POST",
|
|
688
|
+
headers: { "Content-Type": "application/json" },
|
|
689
|
+
body: JSON.stringify({ email, ...options2?.saveToBilling ? { save_to: "billing" } : {} })
|
|
690
|
+
});
|
|
691
|
+
if (status !== 200 || body?.success !== true) throw new CustomerServiceError("server", "unexpected response");
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
|
|
595
696
|
// package.json
|
|
596
|
-
var version = "3.
|
|
697
|
+
var version = "3.1.1";
|
|
597
698
|
|
|
598
699
|
// src/commands/transport.ts
|
|
599
700
|
import { minorUnitDigits, moneyFromDecimalString, moneyFromMajor as moneyFromMajor2 } from "@tallyui/core";
|
|
@@ -738,8 +839,11 @@ var WooMissingTokenError = class extends ConnectorUnauthorizedError2 {
|
|
|
738
839
|
};
|
|
739
840
|
function createWooCommerceConnector() {
|
|
740
841
|
const catalogueFeed = createWooReconcileFeed();
|
|
842
|
+
const customers = createWooCustomers();
|
|
741
843
|
return {
|
|
742
844
|
...wooConnectorParts,
|
|
845
|
+
// WCPOS wcpos/v2/customers, push/customers and orders/<id>/email.
|
|
846
|
+
...customers,
|
|
743
847
|
replication: {
|
|
744
848
|
// One replication per collection; the reconcile feed is last, so its fetch wins duplicates;
|
|
745
849
|
// duplicates are matched by uuid, the primary key, not the store id (#331). legacyKey
|
|
@@ -809,6 +913,8 @@ export {
|
|
|
809
913
|
WooTokenRefusedError,
|
|
810
914
|
createWooCommandTransport,
|
|
811
915
|
createWooCommerceConnector,
|
|
916
|
+
createWooCustomers,
|
|
917
|
+
toWooCustomer,
|
|
812
918
|
toWooOrderPayload,
|
|
813
919
|
wcposClientPart,
|
|
814
920
|
wooCatalogueReconcile,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tallyui/connector-woocommerce",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "WooCommerce connector for Tally UI",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -26,10 +26,10 @@
|
|
|
26
26
|
"directory": "connectors/woocommerce"
|
|
27
27
|
},
|
|
28
28
|
"peerDependencies": {
|
|
29
|
-
"@tallyui/core": "^3.
|
|
29
|
+
"@tallyui/core": "^3.1.1"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
-
"@tallyui/database": "3.
|
|
32
|
+
"@tallyui/database": "3.1.1",
|
|
33
33
|
"rxdb": "17.5.0"
|
|
34
34
|
},
|
|
35
35
|
"scripts": {
|
package/src/customers.ts
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { CustomerServiceError, type Customer, type CustomerInput, type SyncContext } from '@tallyui/core';
|
|
2
|
+
import { checkResponse } from './replication/products';
|
|
3
|
+
|
|
4
|
+
type WooCustomerRow = {
|
|
5
|
+
id: number | string;
|
|
6
|
+
first_name?: string | null;
|
|
7
|
+
last_name?: string | null;
|
|
8
|
+
email?: string | null;
|
|
9
|
+
billing?: {
|
|
10
|
+
first_name?: string | null; last_name?: string | null; email?: string | null;
|
|
11
|
+
phone?: string | null; company?: string | null;
|
|
12
|
+
} | null;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export function toWooCustomer(raw: WooCustomerRow): Customer {
|
|
16
|
+
const customer: Customer = { id: String(raw.id), name: String(raw.id) };
|
|
17
|
+
for (const [key, value] of [
|
|
18
|
+
['firstName', raw.first_name || raw.billing?.first_name],
|
|
19
|
+
['lastName', raw.last_name || raw.billing?.last_name],
|
|
20
|
+
['email', raw.email || raw.billing?.email],
|
|
21
|
+
['phone', raw.billing?.phone], ['company', raw.billing?.company],
|
|
22
|
+
] as const) {
|
|
23
|
+
if (typeof value === 'string' && value.length > 0) customer[key] = value;
|
|
24
|
+
}
|
|
25
|
+
customer.name = [customer.firstName, customer.lastName].filter(Boolean).join(' ') || customer.company || customer.email || customer.id;
|
|
26
|
+
return customer;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function newWooId(): string {
|
|
30
|
+
if (!globalThis.crypto?.getRandomValues) throw new Error('connector-woocommerce: no random source');
|
|
31
|
+
const bytes = globalThis.crypto.getRandomValues(new Uint8Array(16));
|
|
32
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
33
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
34
|
+
const hex = Array.from(bytes, (byte) => byte.toString(16).padStart(2, '0')).join('');
|
|
35
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function customerRequest(context: SyncContext, route: string, init: RequestInit = {}) {
|
|
39
|
+
let response: Response;
|
|
40
|
+
try {
|
|
41
|
+
response = await fetch(`${context.baseUrl}/${route}`, {
|
|
42
|
+
...init, headers: { ...init.headers, ...context.headers }, signal: context.signal,
|
|
43
|
+
});
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new CustomerServiceError('network', error instanceof Error ? error.message : String(error));
|
|
46
|
+
}
|
|
47
|
+
if ([401, 403, 426].includes(response.status)) await checkResponse(response);
|
|
48
|
+
const body = await response.json().catch(() => undefined);
|
|
49
|
+
if (!response.ok) {
|
|
50
|
+
const invalid = [400, 404, 422].includes(response.status);
|
|
51
|
+
throw new CustomerServiceError(invalid ? 'invalid' : 'server', invalid ? body?.message ?? `HTTP ${response.status}` : `HTTP ${response.status}`);
|
|
52
|
+
}
|
|
53
|
+
return { status: response.status, body };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createWooCustomers(options?: { newId?: () => string }) {
|
|
57
|
+
const newId = options?.newId ?? newWooId;
|
|
58
|
+
return {
|
|
59
|
+
async searchCustomers(context: SyncContext, query: string, options?: { limit?: number }): Promise<Customer[]> {
|
|
60
|
+
const q = query.trim();
|
|
61
|
+
if (!q) return [];
|
|
62
|
+
const params = new URLSearchParams({ search: q, role: 'customer', per_page: String(Math.max(1, Math.min(50, options?.limit ?? 20))) });
|
|
63
|
+
const { body } = await customerRequest(context, `customers?${params}`, { method: 'GET' });
|
|
64
|
+
if (!Array.isArray(body)) throw new CustomerServiceError('server', 'unexpected response');
|
|
65
|
+
return body.map(toWooCustomer);
|
|
66
|
+
},
|
|
67
|
+
|
|
68
|
+
async getCustomer(context: SyncContext, id: string): Promise<Customer | null> {
|
|
69
|
+
if (!/^\d+$/.test(id)) return null;
|
|
70
|
+
const params = new URLSearchParams({ include: id, per_page: '1', role: 'all' });
|
|
71
|
+
const { body } = await customerRequest(context, `customers?${params}`, { method: 'GET' });
|
|
72
|
+
if (!Array.isArray(body)) throw new CustomerServiceError('server', 'unexpected response');
|
|
73
|
+
const raw = body.find((row: WooCustomerRow) => String(row.id) === id);
|
|
74
|
+
return raw ? toWooCustomer(raw) : null;
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async createCustomer(context: SyncContext, input: CustomerInput): Promise<Customer> {
|
|
78
|
+
const mutationId = newId();
|
|
79
|
+
const recordId = newId();
|
|
80
|
+
const fields = {
|
|
81
|
+
...(input.email ? { email: input.email } : {}),
|
|
82
|
+
...(input.firstName ? { first_name: input.firstName } : {}),
|
|
83
|
+
...(input.lastName ? { last_name: input.lastName } : {}),
|
|
84
|
+
};
|
|
85
|
+
const { status, body } = await customerRequest(context, 'push/customers', {
|
|
86
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
87
|
+
body: JSON.stringify({
|
|
88
|
+
mutationId, operation: 'create', collection: 'customers', recordId, baseRevision: null,
|
|
89
|
+
payload: { ...fields, billing: {
|
|
90
|
+
...fields, ...(input.phone ? { phone: input.phone } : {}), ...(input.company ? { company: input.company } : {}),
|
|
91
|
+
} },
|
|
92
|
+
}),
|
|
93
|
+
});
|
|
94
|
+
if ((status !== 200 && status !== 201) || !body?.document) throw new CustomerServiceError('server', 'unexpected response');
|
|
95
|
+
return toWooCustomer(body.document);
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
async emailReceipt(context: SyncContext, orderId: string, email: string, options?: { saveToBilling?: boolean }): Promise<void> {
|
|
99
|
+
if (!/^[1-9]\d*$/.test(orderId)) throw new CustomerServiceError('invalid', 'Invalid order ID');
|
|
100
|
+
const { status, body } = await customerRequest(context, `orders/${encodeURIComponent(orderId)}/email`, {
|
|
101
|
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
102
|
+
body: JSON.stringify({ email, ...(options?.saveToBilling ? { save_to: 'billing' } : {}) }),
|
|
103
|
+
});
|
|
104
|
+
if (status !== 200 || body?.success !== true) throw new CustomerServiceError('server', 'unexpected response');
|
|
105
|
+
},
|
|
106
|
+
};
|
|
107
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { wooProductSync } from './sync/products';
|
|
|
6
6
|
import { wooProductReplication } from './replication/products';
|
|
7
7
|
import { wooCatalogueReconcile } from './reconcile/catalogue';
|
|
8
8
|
import { createWooReconcileFeed, MAX_IDS_PER_REQUEST } from './reconcile/feed';
|
|
9
|
+
import { createWooCustomers } from './customers';
|
|
9
10
|
import { version } from '../package.json';
|
|
10
11
|
|
|
11
12
|
/** One part of X-WCPOS-Client as WCPOS keeps it: lowercase [a-z0-9._-], at most 32 characters. */
|
|
@@ -41,8 +42,11 @@ export class WooMissingTokenError extends ConnectorUnauthorizedError {
|
|
|
41
42
|
export function createWooCommerceConnector(): TallyConnector {
|
|
42
43
|
// The catalogue reconcile's corrections reach `products` only through this pull adapter (#248).
|
|
43
44
|
const catalogueFeed = createWooReconcileFeed();
|
|
45
|
+
const customers = createWooCustomers();
|
|
44
46
|
return {
|
|
45
47
|
...wooConnectorParts,
|
|
48
|
+
// WCPOS wcpos/v2/customers, push/customers and orders/<id>/email.
|
|
49
|
+
...customers,
|
|
46
50
|
replication: {
|
|
47
51
|
// One replication per collection; the reconcile feed is last, so its fetch wins duplicates;
|
|
48
52
|
// duplicates are matched by uuid, the primary key, not the store id (#331). legacyKey
|
|
@@ -115,6 +119,7 @@ const wooConnectorParts = {
|
|
|
115
119
|
export const woocommerceConnector: TallyConnector = createWooCommerceConnector();
|
|
116
120
|
|
|
117
121
|
// Re-export pieces for advanced usage
|
|
122
|
+
export { createWooCustomers, toWooCustomer } from './customers';
|
|
118
123
|
export { createWooCommandTransport, toWooOrderPayload, type WooCommandTransportOptions } from './commands/transport';
|
|
119
124
|
export { ConnectorUnauthorizedError } from '@tallyui/core';
|
|
120
125
|
export { wooProductSchema } from './schemas/products';
|