@tallyui/connector-vendure 3.7.1 → 3.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/dist/index.d.ts +181 -1
- package/dist/index.js +123 -0
- package/package.json +6 -5
- package/src/index.ts +2 -0
- package/src/orders.ts +187 -0
- package/src/refunds.ts +58 -0
package/dist/index.d.ts
CHANGED
|
@@ -128,6 +128,186 @@ declare function searchVendureCustomers(context: SyncContext, query: string, opt
|
|
|
128
128
|
declare function createVendureCustomer(context: SyncContext, input: CustomerInput): Promise<Customer>;
|
|
129
129
|
declare function getVendureCustomer(context: SyncContext, id: string): Promise<Customer | null>;
|
|
130
130
|
|
|
131
|
+
interface VendureOrderListOptions {
|
|
132
|
+
/** Offset, default 0; negative or fractional values are truncated and floored to 0. */
|
|
133
|
+
skip?: number;
|
|
134
|
+
/** Page size, default 25, clamped to 1..100. */
|
|
135
|
+
take?: number;
|
|
136
|
+
/** Vendure's DateOperators on orderPlacedAt; only these three keys are sent. */
|
|
137
|
+
orderPlacedAt?: {
|
|
138
|
+
after?: string;
|
|
139
|
+
before?: string;
|
|
140
|
+
between?: {
|
|
141
|
+
start: string;
|
|
142
|
+
end: string;
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
/** Order states, sent as `state: { in }` when non-empty. */
|
|
146
|
+
state?: string[];
|
|
147
|
+
/** The plugin's register id custom field, sent as `tallyRegisterId: { eq }` when non-empty. */
|
|
148
|
+
tallyRegisterId?: string;
|
|
149
|
+
/** The plugin's cashier custom field, sent as `tallyCashierRef: { eq }` when non-empty. */
|
|
150
|
+
tallyCashierRef?: string;
|
|
151
|
+
/** Lists through `customer(id) { orders }` when non-empty. */
|
|
152
|
+
customerId?: string;
|
|
153
|
+
/** Trimmed; when non-empty, matches order code or customer last name with `contains`. */
|
|
154
|
+
search?: string;
|
|
155
|
+
/** Select the vendurepos plugin's custom fields; default true. False for a server without the plugin. */
|
|
156
|
+
tallyFields?: boolean;
|
|
157
|
+
}
|
|
158
|
+
interface VendureOrderList<T> {
|
|
159
|
+
items: T[];
|
|
160
|
+
totalItems: number;
|
|
161
|
+
}
|
|
162
|
+
interface VendureOrderSummary {
|
|
163
|
+
id: string;
|
|
164
|
+
code: string;
|
|
165
|
+
state: string;
|
|
166
|
+
orderPlacedAt: string | null;
|
|
167
|
+
updatedAt: string;
|
|
168
|
+
currencyCode: string;
|
|
169
|
+
totalQuantity: number;
|
|
170
|
+
total: number;
|
|
171
|
+
totalWithTax: number;
|
|
172
|
+
customer: {
|
|
173
|
+
id: string;
|
|
174
|
+
firstName: string;
|
|
175
|
+
lastName: string;
|
|
176
|
+
emailAddress: string;
|
|
177
|
+
} | null;
|
|
178
|
+
customFields?: {
|
|
179
|
+
tallyClientOrderId: string | null;
|
|
180
|
+
tallySaleAt: string | null;
|
|
181
|
+
tallyRegisterId: string | null;
|
|
182
|
+
tallySessionId: string | null;
|
|
183
|
+
tallyCashierRef: string | null;
|
|
184
|
+
tallyRejected: boolean | null;
|
|
185
|
+
tallyRejectedClientOrderId: string | null;
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
interface VendureOrder extends VendureOrderSummary {
|
|
189
|
+
active: boolean;
|
|
190
|
+
lines: {
|
|
191
|
+
id: string;
|
|
192
|
+
quantity: number;
|
|
193
|
+
orderPlacedQuantity: number;
|
|
194
|
+
proratedUnitPrice: number;
|
|
195
|
+
/** Vendure's per-unit value used in refund calculations, which the till sums for `order.refund`'s `totalMinor` (ADR-080). */
|
|
196
|
+
proratedUnitPriceWithTax: number;
|
|
197
|
+
taxRate: number;
|
|
198
|
+
productVariant: {
|
|
199
|
+
id: string;
|
|
200
|
+
name: string;
|
|
201
|
+
sku: string;
|
|
202
|
+
};
|
|
203
|
+
unitPrice: number;
|
|
204
|
+
unitPriceWithTax: number;
|
|
205
|
+
linePrice: number;
|
|
206
|
+
linePriceWithTax: number;
|
|
207
|
+
discountedLinePrice: number;
|
|
208
|
+
discountedLinePriceWithTax: number;
|
|
209
|
+
discounts: {
|
|
210
|
+
description: string;
|
|
211
|
+
amount: number;
|
|
212
|
+
amountWithTax: number;
|
|
213
|
+
}[];
|
|
214
|
+
customFields?: {
|
|
215
|
+
tallyCustomName: string | null;
|
|
216
|
+
tallyCustomSku: string | null;
|
|
217
|
+
tallyUnitPrice: number | null;
|
|
218
|
+
tallyPriceIncludesTax: boolean | null;
|
|
219
|
+
tallyClientLineId: string | null;
|
|
220
|
+
};
|
|
221
|
+
}[];
|
|
222
|
+
shippingLines: {
|
|
223
|
+
shippingMethod: {
|
|
224
|
+
name: string;
|
|
225
|
+
};
|
|
226
|
+
price: number;
|
|
227
|
+
priceWithTax: number;
|
|
228
|
+
}[];
|
|
229
|
+
surcharges: {
|
|
230
|
+
description: string;
|
|
231
|
+
sku: string;
|
|
232
|
+
price: number;
|
|
233
|
+
priceWithTax: number;
|
|
234
|
+
}[];
|
|
235
|
+
subTotal: number;
|
|
236
|
+
subTotalWithTax: number;
|
|
237
|
+
shipping: number;
|
|
238
|
+
shippingWithTax: number;
|
|
239
|
+
taxSummary: {
|
|
240
|
+
description: string;
|
|
241
|
+
taxRate: number;
|
|
242
|
+
taxBase: number;
|
|
243
|
+
taxTotal: number;
|
|
244
|
+
}[];
|
|
245
|
+
discounts: {
|
|
246
|
+
description: string;
|
|
247
|
+
amount: number;
|
|
248
|
+
amountWithTax: number;
|
|
249
|
+
}[];
|
|
250
|
+
couponCodes: string[];
|
|
251
|
+
payments?: {
|
|
252
|
+
id: string;
|
|
253
|
+
method: string;
|
|
254
|
+
amount: number;
|
|
255
|
+
state: string;
|
|
256
|
+
transactionId: string;
|
|
257
|
+
createdAt: string;
|
|
258
|
+
metadata: unknown;
|
|
259
|
+
refunds: {
|
|
260
|
+
id: string;
|
|
261
|
+
total: number;
|
|
262
|
+
items: number;
|
|
263
|
+
shipping: number;
|
|
264
|
+
adjustment: number;
|
|
265
|
+
state: string;
|
|
266
|
+
reason: string;
|
|
267
|
+
metadata: unknown;
|
|
268
|
+
lines: {
|
|
269
|
+
orderLineId: string;
|
|
270
|
+
quantity: number;
|
|
271
|
+
}[];
|
|
272
|
+
}[];
|
|
273
|
+
}[];
|
|
274
|
+
fulfillments?: {
|
|
275
|
+
id: string;
|
|
276
|
+
state: string;
|
|
277
|
+
method: string;
|
|
278
|
+
trackingCode: string;
|
|
279
|
+
}[];
|
|
280
|
+
customFields?: VendureOrderSummary['customFields'] & {
|
|
281
|
+
tallyPayments: string | null;
|
|
282
|
+
tallySnapshot: string | null;
|
|
283
|
+
tallyShipping: string | null;
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
declare function listVendureOrders(context: SyncContext, options?: VendureOrderListOptions): Promise<VendureOrderList<VendureOrderSummary>>;
|
|
287
|
+
declare function getVendureOrder(context: SyncContext, id: string, options?: {
|
|
288
|
+
tallyFields?: boolean;
|
|
289
|
+
}): Promise<VendureOrder | null>;
|
|
290
|
+
|
|
291
|
+
interface VendureRefundableLine {
|
|
292
|
+
orderLineId: string;
|
|
293
|
+
quantity: number;
|
|
294
|
+
refundedQuantity: number;
|
|
295
|
+
cancelledQuantity: number;
|
|
296
|
+
refundableQuantity: number;
|
|
297
|
+
unitRefundWithTax: number;
|
|
298
|
+
}
|
|
299
|
+
interface VendureRefundable {
|
|
300
|
+
lines: VendureRefundableLine[];
|
|
301
|
+
shippingWithTax: number;
|
|
302
|
+
moneyWithTax: number;
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* What the form may offer, from the order alone. Pending refunds count as refunded.
|
|
306
|
+
* The plugin is authoritative (`quantity_exceeds`, `amount_mismatch`).
|
|
307
|
+
* Amounts are Vendure's minor units with tax.
|
|
308
|
+
*/
|
|
309
|
+
declare function vendureRefundable(order: Pick<VendureOrder, 'lines' | 'shippingWithTax' | 'payments'>): VendureRefundable;
|
|
310
|
+
|
|
131
311
|
/**
|
|
132
312
|
* Vendure connector for Tally UI. Build one per store session, anew on each sign-in or store change:
|
|
133
313
|
* each instance owns its reconcile feed, whose queued work must never reach another store's database (#307).
|
|
@@ -165,4 +345,4 @@ declare const createVendureConnector: (options?: {
|
|
|
165
345
|
*/
|
|
166
346
|
declare const vendureConnector: TallyConnector;
|
|
167
347
|
|
|
168
|
-
export { type VendureCredentialKind, type VendureCredentials, VendureTimezoneConfigError, createVendureConnector, createVendureCustomer, createVendureProductReplication, createVendureVariantFeedReplication, getVendureCustomer, searchVendureCustomers, toVendureCustomer, vendureAuth, vendureAuthFieldSets, vendureConnector, vendureGlobalStockSettings, vendureProductReplication, vendureProductSchema, vendureProductSync, vendureProductTraits, vendureSignIn, vendureStockReconcile, vendureStoreSettings };
|
|
348
|
+
export { type VendureCredentialKind, type VendureCredentials, type VendureOrder, type VendureOrderList, type VendureOrderListOptions, type VendureOrderSummary, type VendureRefundable, type VendureRefundableLine, VendureTimezoneConfigError, createVendureConnector, createVendureCustomer, createVendureProductReplication, createVendureVariantFeedReplication, getVendureCustomer, getVendureOrder, listVendureOrders, searchVendureCustomers, toVendureCustomer, vendureAuth, vendureAuthFieldSets, vendureConnector, vendureGlobalStockSettings, vendureProductReplication, vendureProductSchema, vendureProductSync, vendureProductTraits, vendureRefundable, vendureSignIn, vendureStockReconcile, vendureStoreSettings };
|
package/dist/index.js
CHANGED
|
@@ -966,6 +966,126 @@ async function vendureGlobalStockSettings(context) {
|
|
|
966
966
|
return res.data.globalSettings;
|
|
967
967
|
}
|
|
968
968
|
|
|
969
|
+
// src/orders.ts
|
|
970
|
+
var ORDER_ROW_FIELDS = `
|
|
971
|
+
id code state orderPlacedAt updatedAt currencyCode totalQuantity total totalWithTax
|
|
972
|
+
customer { id firstName lastName emailAddress }
|
|
973
|
+
`;
|
|
974
|
+
var ORDER_CUSTOM_FIELDS = "tallyClientOrderId tallySaleAt tallyRegisterId tallySessionId tallyCashierRef tallyRejected tallyRejectedClientOrderId";
|
|
975
|
+
var TALLY_ORDER_ROW_FIELDS = `${ORDER_ROW_FIELDS} customFields { ${ORDER_CUSTOM_FIELDS} }`;
|
|
976
|
+
var ORDER_DETAIL_FIELDS_START = `
|
|
977
|
+
id code state active orderPlacedAt updatedAt currencyCode totalQuantity
|
|
978
|
+
customer { id firstName lastName emailAddress }
|
|
979
|
+
lines {`;
|
|
980
|
+
var ORDER_DETAIL_FIELDS_END = `
|
|
981
|
+
id quantity orderPlacedQuantity proratedUnitPrice proratedUnitPriceWithTax taxRate productVariant { id name sku }
|
|
982
|
+
unitPrice unitPriceWithTax linePrice linePriceWithTax discountedLinePrice discountedLinePriceWithTax
|
|
983
|
+
discounts { description amount amountWithTax }
|
|
984
|
+
}
|
|
985
|
+
shippingLines { shippingMethod { name } price priceWithTax }
|
|
986
|
+
surcharges { description sku price priceWithTax }
|
|
987
|
+
subTotal subTotalWithTax shipping shippingWithTax total totalWithTax
|
|
988
|
+
taxSummary { description taxRate taxBase taxTotal }
|
|
989
|
+
discounts { description amount amountWithTax }
|
|
990
|
+
couponCodes
|
|
991
|
+
payments { id method amount state transactionId createdAt metadata refunds { id total items shipping adjustment state reason metadata lines { orderLineId quantity } } }
|
|
992
|
+
fulfillments { id state method trackingCode }
|
|
993
|
+
`;
|
|
994
|
+
var ORDER_DETAIL_FIELDS = `${ORDER_DETAIL_FIELDS_START}${ORDER_DETAIL_FIELDS_END}`;
|
|
995
|
+
var TALLY_ORDER_DETAIL_FIELDS = `${ORDER_DETAIL_FIELDS_START}
|
|
996
|
+
customFields { tallyCustomName tallyCustomSku tallyUnitPrice tallyPriceIncludesTax tallyClientLineId }
|
|
997
|
+
${ORDER_DETAIL_FIELDS_END} customFields { ${ORDER_CUSTOM_FIELDS} tallyPayments tallySnapshot tallyShipping }`;
|
|
998
|
+
var LIST_QUERY = `query Orders($options: OrderListOptions) {
|
|
999
|
+
orders(options: $options) { items { ${ORDER_ROW_FIELDS} } totalItems }
|
|
1000
|
+
}`;
|
|
1001
|
+
var TALLY_LIST_QUERY = LIST_QUERY.replace(ORDER_ROW_FIELDS, TALLY_ORDER_ROW_FIELDS);
|
|
1002
|
+
var CUSTOMER_LIST_QUERY = `query CustomerOrders($customerId: ID!, $options: OrderListOptions) {
|
|
1003
|
+
customer(id: $customerId) { orders(options: $options) { items { ${ORDER_ROW_FIELDS} } totalItems } }
|
|
1004
|
+
}`;
|
|
1005
|
+
var TALLY_CUSTOMER_LIST_QUERY = CUSTOMER_LIST_QUERY.replace(ORDER_ROW_FIELDS, TALLY_ORDER_ROW_FIELDS);
|
|
1006
|
+
var DETAIL_QUERY = `query Order($id: ID!) {
|
|
1007
|
+
order(id: $id) { ${ORDER_DETAIL_FIELDS} }
|
|
1008
|
+
}`;
|
|
1009
|
+
var TALLY_DETAIL_QUERY = `query Order($id: ID!) {
|
|
1010
|
+
order(id: $id) { ${TALLY_ORDER_DETAIL_FIELDS} }
|
|
1011
|
+
}`;
|
|
1012
|
+
async function listVendureOrders(context, options = {}) {
|
|
1013
|
+
const skip = Math.max(0, Math.trunc(Number.isFinite(options.skip) ? options.skip : 0));
|
|
1014
|
+
const take = Math.max(1, Math.min(100, Math.trunc(Number.isFinite(options.take) ? options.take : 25)));
|
|
1015
|
+
const date = options.orderPlacedAt;
|
|
1016
|
+
const orderPlacedAt = {
|
|
1017
|
+
...date?.after !== void 0 ? { after: date.after } : {},
|
|
1018
|
+
...date?.before !== void 0 ? { before: date.before } : {},
|
|
1019
|
+
...date?.between !== void 0 ? { between: date.between } : {}
|
|
1020
|
+
};
|
|
1021
|
+
const q = options.search?.trim();
|
|
1022
|
+
const filter = {
|
|
1023
|
+
orderPlacedAt: Object.keys(orderPlacedAt).length ? orderPlacedAt : { isNull: false },
|
|
1024
|
+
...options.state?.length ? { state: { in: options.state } } : {},
|
|
1025
|
+
...options.tallyRegisterId ? { tallyRegisterId: { eq: options.tallyRegisterId } } : {},
|
|
1026
|
+
...options.tallyCashierRef ? { tallyCashierRef: { eq: options.tallyCashierRef } } : {},
|
|
1027
|
+
...q ? { _or: [{ code: { contains: q } }, { customerLastName: { contains: q } }] } : {}
|
|
1028
|
+
};
|
|
1029
|
+
const variables = {
|
|
1030
|
+
options: { skip, take, sort: { orderPlacedAt: "DESC", id: "DESC" }, filter },
|
|
1031
|
+
...options.customerId ? { customerId: options.customerId } : {}
|
|
1032
|
+
};
|
|
1033
|
+
const query2 = options.customerId ? options.tallyFields === false ? CUSTOMER_LIST_QUERY : TALLY_CUSTOMER_LIST_QUERY : options.tallyFields === false ? LIST_QUERY : TALLY_LIST_QUERY;
|
|
1034
|
+
const body = await gql2(context, query2, variables);
|
|
1035
|
+
if (options.customerId && body?.data?.customer === null) return { items: [], totalItems: 0 };
|
|
1036
|
+
const result = options.customerId ? body?.data?.customer?.orders : body?.data?.orders;
|
|
1037
|
+
if (!result || typeof result !== "object" || !Array.isArray(result.items) || typeof result.totalItems !== "number") {
|
|
1038
|
+
throw new Error("Vendure sent an unexpected orders response");
|
|
1039
|
+
}
|
|
1040
|
+
return { items: result.items, totalItems: result.totalItems };
|
|
1041
|
+
}
|
|
1042
|
+
async function getVendureOrder(context, id, options) {
|
|
1043
|
+
const body = await gql2(context, options?.tallyFields === false ? DETAIL_QUERY : TALLY_DETAIL_QUERY, { id });
|
|
1044
|
+
const order = body?.data?.order;
|
|
1045
|
+
if (order === null) return null;
|
|
1046
|
+
if (typeof order !== "object" || typeof order?.id !== "string") {
|
|
1047
|
+
throw new Error("Vendure sent an unexpected order response");
|
|
1048
|
+
}
|
|
1049
|
+
return order;
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// src/refunds.ts
|
|
1053
|
+
function vendureRefundable(order) {
|
|
1054
|
+
const refundedQuantities = /* @__PURE__ */ new Map();
|
|
1055
|
+
let refundedShipping = 0;
|
|
1056
|
+
let moneyWithTax = 0;
|
|
1057
|
+
for (const payment of order.payments ?? []) {
|
|
1058
|
+
let refundedTotal = 0;
|
|
1059
|
+
for (const refund of payment.refunds ?? []) {
|
|
1060
|
+
if (refund.state === "Failed") continue;
|
|
1061
|
+
refundedTotal += Number.isFinite(refund.total) ? refund.total : 0;
|
|
1062
|
+
refundedShipping += Number.isFinite(refund.shipping) ? refund.shipping : 0;
|
|
1063
|
+
for (const line of refund.lines ?? []) {
|
|
1064
|
+
refundedQuantities.set(line.orderLineId, (refundedQuantities.get(line.orderLineId) ?? 0) + (Number.isFinite(line.quantity) ? line.quantity : 0));
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
if (payment.state === "Settled") {
|
|
1068
|
+
moneyWithTax += Math.max(0, (Number.isFinite(payment.amount) ? payment.amount : 0) - refundedTotal);
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
return {
|
|
1072
|
+
lines: (order.lines ?? []).map((line) => {
|
|
1073
|
+
const quantity = Number.isFinite(line.quantity) ? line.quantity : 0;
|
|
1074
|
+
const refundedQuantity = refundedQuantities.get(line.id) ?? 0;
|
|
1075
|
+
return {
|
|
1076
|
+
orderLineId: line.id,
|
|
1077
|
+
quantity,
|
|
1078
|
+
refundedQuantity,
|
|
1079
|
+
cancelledQuantity: Number.isFinite(line.orderPlacedQuantity) ? Math.max(0, line.orderPlacedQuantity - quantity) : 0,
|
|
1080
|
+
refundableQuantity: Math.max(0, quantity - refundedQuantity),
|
|
1081
|
+
unitRefundWithTax: Number.isFinite(line.proratedUnitPriceWithTax) ? line.proratedUnitPriceWithTax : 0
|
|
1082
|
+
};
|
|
1083
|
+
}),
|
|
1084
|
+
shippingWithTax: Math.max(0, (Number.isFinite(order.shippingWithTax) ? order.shippingWithTax : 0) - refundedShipping),
|
|
1085
|
+
moneyWithTax
|
|
1086
|
+
};
|
|
1087
|
+
}
|
|
1088
|
+
|
|
969
1089
|
// src/index.ts
|
|
970
1090
|
import { ConnectorUnauthorizedError as ConnectorUnauthorizedError6 } from "@tallyui/core";
|
|
971
1091
|
var VENDURE_IDS_PER_REQUEST = 1e3;
|
|
@@ -1024,6 +1144,8 @@ export {
|
|
|
1024
1144
|
createVendureProductReplication,
|
|
1025
1145
|
createVendureVariantFeedReplication,
|
|
1026
1146
|
getVendureCustomer,
|
|
1147
|
+
getVendureOrder,
|
|
1148
|
+
listVendureOrders,
|
|
1027
1149
|
searchVendureCustomers,
|
|
1028
1150
|
toVendureCustomer,
|
|
1029
1151
|
vendureAuth,
|
|
@@ -1034,6 +1156,7 @@ export {
|
|
|
1034
1156
|
vendureProductSchema,
|
|
1035
1157
|
vendureProductSync,
|
|
1036
1158
|
vendureProductTraits,
|
|
1159
|
+
vendureRefundable,
|
|
1037
1160
|
vendureSignIn,
|
|
1038
1161
|
vendureStockReconcile,
|
|
1039
1162
|
vendureStoreSettings
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tallyui/connector-vendure",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Vendure connector for Tally UI",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
".": {
|
|
11
11
|
"types": "./dist/index.d.ts",
|
|
12
12
|
"import": "./dist/index.js",
|
|
13
|
-
"source": "./src/index.ts"
|
|
13
|
+
"source": "./src/index.ts",
|
|
14
|
+
"default": "./dist/index.js"
|
|
14
15
|
}
|
|
15
16
|
},
|
|
16
17
|
"files": [
|
|
@@ -26,11 +27,11 @@
|
|
|
26
27
|
"directory": "connectors/vendure"
|
|
27
28
|
},
|
|
28
29
|
"peerDependencies": {
|
|
29
|
-
"@tallyui/core": "^3.
|
|
30
|
+
"@tallyui/core": "^3.9.0"
|
|
30
31
|
},
|
|
31
32
|
"devDependencies": {
|
|
32
|
-
"@tallyui/database": "3.
|
|
33
|
-
"@tallyui/pos": "3.
|
|
33
|
+
"@tallyui/database": "3.9.0",
|
|
34
|
+
"@tallyui/pos": "3.9.0",
|
|
34
35
|
"rxdb": "17.5.0"
|
|
35
36
|
},
|
|
36
37
|
"scripts": {
|
package/src/index.ts
CHANGED
|
@@ -114,6 +114,8 @@ export { vendureStockReconcile } from './reconcile/stock';
|
|
|
114
114
|
export { vendureStoreSettings } from './store-settings';
|
|
115
115
|
export { vendureGlobalStockSettings } from './global-settings';
|
|
116
116
|
export { searchVendureCustomers, createVendureCustomer, getVendureCustomer, toVendureCustomer } from './customers';
|
|
117
|
+
export { listVendureOrders, getVendureOrder, type VendureOrderListOptions, type VendureOrderList, type VendureOrderSummary, type VendureOrder } from './orders';
|
|
118
|
+
export { vendureRefundable, type VendureRefundableLine, type VendureRefundable } from './refunds';
|
|
117
119
|
|
|
118
120
|
export { ConnectorUnauthorizedError } from '@tallyui/core';
|
|
119
121
|
export { VendureTimezoneConfigError } from './replication/products';
|
package/src/orders.ts
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import type { SyncContext } from '@tallyui/core';
|
|
2
|
+
import { gql } from './replication/products';
|
|
3
|
+
|
|
4
|
+
export interface VendureOrderListOptions {
|
|
5
|
+
/** Offset, default 0; negative or fractional values are truncated and floored to 0. */
|
|
6
|
+
skip?: number;
|
|
7
|
+
/** Page size, default 25, clamped to 1..100. */
|
|
8
|
+
take?: number;
|
|
9
|
+
/** Vendure's DateOperators on orderPlacedAt; only these three keys are sent. */
|
|
10
|
+
orderPlacedAt?: { after?: string; before?: string; between?: { start: string; end: string } };
|
|
11
|
+
/** Order states, sent as `state: { in }` when non-empty. */
|
|
12
|
+
state?: string[];
|
|
13
|
+
/** The plugin's register id custom field, sent as `tallyRegisterId: { eq }` when non-empty. */
|
|
14
|
+
tallyRegisterId?: string;
|
|
15
|
+
/** The plugin's cashier custom field, sent as `tallyCashierRef: { eq }` when non-empty. */
|
|
16
|
+
tallyCashierRef?: string;
|
|
17
|
+
/** Lists through `customer(id) { orders }` when non-empty. */
|
|
18
|
+
customerId?: string;
|
|
19
|
+
/** Trimmed; when non-empty, matches order code or customer last name with `contains`. */
|
|
20
|
+
search?: string;
|
|
21
|
+
/** Select the vendurepos plugin's custom fields; default true. False for a server without the plugin. */
|
|
22
|
+
tallyFields?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface VendureOrderList<T> { items: T[]; totalItems: number }
|
|
26
|
+
|
|
27
|
+
export interface VendureOrderSummary {
|
|
28
|
+
id: string;
|
|
29
|
+
code: string;
|
|
30
|
+
state: string;
|
|
31
|
+
orderPlacedAt: string | null;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
currencyCode: string;
|
|
34
|
+
totalQuantity: number;
|
|
35
|
+
total: number;
|
|
36
|
+
totalWithTax: number;
|
|
37
|
+
customer: { id: string; firstName: string; lastName: string; emailAddress: string } | null;
|
|
38
|
+
customFields?: {
|
|
39
|
+
tallyClientOrderId: string | null;
|
|
40
|
+
tallySaleAt: string | null;
|
|
41
|
+
tallyRegisterId: string | null;
|
|
42
|
+
tallySessionId: string | null;
|
|
43
|
+
tallyCashierRef: string | null;
|
|
44
|
+
tallyRejected: boolean | null;
|
|
45
|
+
tallyRejectedClientOrderId: string | null;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface VendureOrder extends VendureOrderSummary {
|
|
50
|
+
active: boolean;
|
|
51
|
+
lines: {
|
|
52
|
+
id: string;
|
|
53
|
+
quantity: number;
|
|
54
|
+
orderPlacedQuantity: number;
|
|
55
|
+
proratedUnitPrice: number;
|
|
56
|
+
/** Vendure's per-unit value used in refund calculations, which the till sums for `order.refund`'s `totalMinor` (ADR-080). */
|
|
57
|
+
proratedUnitPriceWithTax: number;
|
|
58
|
+
taxRate: number;
|
|
59
|
+
productVariant: { id: string; name: string; sku: string };
|
|
60
|
+
unitPrice: number;
|
|
61
|
+
unitPriceWithTax: number;
|
|
62
|
+
linePrice: number;
|
|
63
|
+
linePriceWithTax: number;
|
|
64
|
+
discountedLinePrice: number;
|
|
65
|
+
discountedLinePriceWithTax: number;
|
|
66
|
+
discounts: { description: string; amount: number; amountWithTax: number }[];
|
|
67
|
+
customFields?: {
|
|
68
|
+
tallyCustomName: string | null;
|
|
69
|
+
tallyCustomSku: string | null;
|
|
70
|
+
tallyUnitPrice: number | null;
|
|
71
|
+
tallyPriceIncludesTax: boolean | null;
|
|
72
|
+
tallyClientLineId: string | null;
|
|
73
|
+
};
|
|
74
|
+
}[];
|
|
75
|
+
shippingLines: { shippingMethod: { name: string }; price: number; priceWithTax: number }[];
|
|
76
|
+
surcharges: { description: string; sku: string; price: number; priceWithTax: number }[];
|
|
77
|
+
subTotal: number;
|
|
78
|
+
subTotalWithTax: number;
|
|
79
|
+
shipping: number;
|
|
80
|
+
shippingWithTax: number;
|
|
81
|
+
taxSummary: { description: string; taxRate: number; taxBase: number; taxTotal: number }[];
|
|
82
|
+
discounts: { description: string; amount: number; amountWithTax: number }[];
|
|
83
|
+
couponCodes: string[];
|
|
84
|
+
payments?: {
|
|
85
|
+
id: string;
|
|
86
|
+
method: string;
|
|
87
|
+
amount: number;
|
|
88
|
+
state: string;
|
|
89
|
+
transactionId: string;
|
|
90
|
+
createdAt: string;
|
|
91
|
+
metadata: unknown;
|
|
92
|
+
refunds: { id: string; total: number; items: number; shipping: number; adjustment: number; state: string; reason: string; metadata: unknown; lines: { orderLineId: string; quantity: number }[] }[];
|
|
93
|
+
}[];
|
|
94
|
+
fulfillments?: { id: string; state: string; method: string; trackingCode: string }[];
|
|
95
|
+
customFields?: VendureOrderSummary['customFields'] & {
|
|
96
|
+
tallyPayments: string | null;
|
|
97
|
+
tallySnapshot: string | null;
|
|
98
|
+
tallyShipping: string | null;
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const ORDER_ROW_FIELDS = `
|
|
103
|
+
id code state orderPlacedAt updatedAt currencyCode totalQuantity total totalWithTax
|
|
104
|
+
customer { id firstName lastName emailAddress }
|
|
105
|
+
`;
|
|
106
|
+
const ORDER_CUSTOM_FIELDS = 'tallyClientOrderId tallySaleAt tallyRegisterId tallySessionId tallyCashierRef tallyRejected tallyRejectedClientOrderId';
|
|
107
|
+
const TALLY_ORDER_ROW_FIELDS = `${ORDER_ROW_FIELDS} customFields { ${ORDER_CUSTOM_FIELDS} }`;
|
|
108
|
+
const ORDER_DETAIL_FIELDS_START = `
|
|
109
|
+
id code state active orderPlacedAt updatedAt currencyCode totalQuantity
|
|
110
|
+
customer { id firstName lastName emailAddress }
|
|
111
|
+
lines {`;
|
|
112
|
+
const ORDER_DETAIL_FIELDS_END = `
|
|
113
|
+
id quantity orderPlacedQuantity proratedUnitPrice proratedUnitPriceWithTax taxRate productVariant { id name sku }
|
|
114
|
+
unitPrice unitPriceWithTax linePrice linePriceWithTax discountedLinePrice discountedLinePriceWithTax
|
|
115
|
+
discounts { description amount amountWithTax }
|
|
116
|
+
}
|
|
117
|
+
shippingLines { shippingMethod { name } price priceWithTax }
|
|
118
|
+
surcharges { description sku price priceWithTax }
|
|
119
|
+
subTotal subTotalWithTax shipping shippingWithTax total totalWithTax
|
|
120
|
+
taxSummary { description taxRate taxBase taxTotal }
|
|
121
|
+
discounts { description amount amountWithTax }
|
|
122
|
+
couponCodes
|
|
123
|
+
payments { id method amount state transactionId createdAt metadata refunds { id total items shipping adjustment state reason metadata lines { orderLineId quantity } } }
|
|
124
|
+
fulfillments { id state method trackingCode }
|
|
125
|
+
`;
|
|
126
|
+
const ORDER_DETAIL_FIELDS = `${ORDER_DETAIL_FIELDS_START}${ORDER_DETAIL_FIELDS_END}`;
|
|
127
|
+
const TALLY_ORDER_DETAIL_FIELDS = `${ORDER_DETAIL_FIELDS_START}
|
|
128
|
+
customFields { tallyCustomName tallyCustomSku tallyUnitPrice tallyPriceIncludesTax tallyClientLineId }
|
|
129
|
+
${ORDER_DETAIL_FIELDS_END} customFields { ${ORDER_CUSTOM_FIELDS} tallyPayments tallySnapshot tallyShipping }`;
|
|
130
|
+
|
|
131
|
+
const LIST_QUERY = `query Orders($options: OrderListOptions) {
|
|
132
|
+
orders(options: $options) { items { ${ORDER_ROW_FIELDS} } totalItems }
|
|
133
|
+
}`;
|
|
134
|
+
const TALLY_LIST_QUERY = LIST_QUERY.replace(ORDER_ROW_FIELDS, TALLY_ORDER_ROW_FIELDS);
|
|
135
|
+
const CUSTOMER_LIST_QUERY = `query CustomerOrders($customerId: ID!, $options: OrderListOptions) {
|
|
136
|
+
customer(id: $customerId) { orders(options: $options) { items { ${ORDER_ROW_FIELDS} } totalItems } }
|
|
137
|
+
}`;
|
|
138
|
+
const TALLY_CUSTOMER_LIST_QUERY = CUSTOMER_LIST_QUERY.replace(ORDER_ROW_FIELDS, TALLY_ORDER_ROW_FIELDS);
|
|
139
|
+
const DETAIL_QUERY = `query Order($id: ID!) {
|
|
140
|
+
order(id: $id) { ${ORDER_DETAIL_FIELDS} }
|
|
141
|
+
}`;
|
|
142
|
+
const TALLY_DETAIL_QUERY = `query Order($id: ID!) {
|
|
143
|
+
order(id: $id) { ${TALLY_ORDER_DETAIL_FIELDS} }
|
|
144
|
+
}`;
|
|
145
|
+
|
|
146
|
+
export async function listVendureOrders(context: SyncContext, options: VendureOrderListOptions = {}): Promise<VendureOrderList<VendureOrderSummary>> {
|
|
147
|
+
const skip = Math.max(0, Math.trunc(Number.isFinite(options.skip) ? options.skip! : 0));
|
|
148
|
+
const take = Math.max(1, Math.min(100, Math.trunc(Number.isFinite(options.take) ? options.take! : 25)));
|
|
149
|
+
const date = options.orderPlacedAt;
|
|
150
|
+
const orderPlacedAt = {
|
|
151
|
+
...(date?.after !== undefined ? { after: date.after } : {}),
|
|
152
|
+
...(date?.before !== undefined ? { before: date.before } : {}),
|
|
153
|
+
...(date?.between !== undefined ? { between: date.between } : {}),
|
|
154
|
+
};
|
|
155
|
+
const q = options.search?.trim();
|
|
156
|
+
const filter = {
|
|
157
|
+
orderPlacedAt: Object.keys(orderPlacedAt).length ? orderPlacedAt : { isNull: false },
|
|
158
|
+
...(options.state?.length ? { state: { in: options.state } } : {}),
|
|
159
|
+
...(options.tallyRegisterId ? { tallyRegisterId: { eq: options.tallyRegisterId } } : {}),
|
|
160
|
+
...(options.tallyCashierRef ? { tallyCashierRef: { eq: options.tallyCashierRef } } : {}),
|
|
161
|
+
...(q ? { _or: [{ code: { contains: q } }, { customerLastName: { contains: q } }] } : {}),
|
|
162
|
+
};
|
|
163
|
+
const variables = {
|
|
164
|
+
options: { skip, take, sort: { orderPlacedAt: 'DESC', id: 'DESC' }, filter },
|
|
165
|
+
...(options.customerId ? { customerId: options.customerId } : {}),
|
|
166
|
+
};
|
|
167
|
+
const query = options.customerId
|
|
168
|
+
? (options.tallyFields === false ? CUSTOMER_LIST_QUERY : TALLY_CUSTOMER_LIST_QUERY)
|
|
169
|
+
: (options.tallyFields === false ? LIST_QUERY : TALLY_LIST_QUERY);
|
|
170
|
+
const body = await gql(context, query, variables);
|
|
171
|
+
if (options.customerId && body?.data?.customer === null) return { items: [], totalItems: 0 };
|
|
172
|
+
const result = options.customerId ? body?.data?.customer?.orders : body?.data?.orders;
|
|
173
|
+
if (!result || typeof result !== 'object' || !Array.isArray(result.items) || typeof result.totalItems !== 'number') {
|
|
174
|
+
throw new Error('Vendure sent an unexpected orders response');
|
|
175
|
+
}
|
|
176
|
+
return { items: result.items, totalItems: result.totalItems };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function getVendureOrder(context: SyncContext, id: string, options?: { tallyFields?: boolean }): Promise<VendureOrder | null> {
|
|
180
|
+
const body = await gql(context, options?.tallyFields === false ? DETAIL_QUERY : TALLY_DETAIL_QUERY, { id });
|
|
181
|
+
const order = body?.data?.order;
|
|
182
|
+
if (order === null) return null;
|
|
183
|
+
if (typeof order !== 'object' || typeof order?.id !== 'string') {
|
|
184
|
+
throw new Error('Vendure sent an unexpected order response');
|
|
185
|
+
}
|
|
186
|
+
return order;
|
|
187
|
+
}
|
package/src/refunds.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import type { VendureOrder } from './orders';
|
|
2
|
+
|
|
3
|
+
export interface VendureRefundableLine {
|
|
4
|
+
orderLineId: string;
|
|
5
|
+
quantity: number; // The line's current quantity.
|
|
6
|
+
refundedQuantity: number; // Sum of its refund lines over refunds not Failed.
|
|
7
|
+
cancelledQuantity: number; // max(0, orderPlacedQuantity - quantity).
|
|
8
|
+
refundableQuantity: number; // max(0, quantity - refundedQuantity).
|
|
9
|
+
unitRefundWithTax: number; // The line's proratedUnitPriceWithTax.
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface VendureRefundable {
|
|
13
|
+
lines: VendureRefundableLine[]; // In the order's line order, one per line.
|
|
14
|
+
shippingWithTax: number; // max(0, order.shippingWithTax - shipping refunded over refunds not Failed).
|
|
15
|
+
moneyWithTax: number; // Sum over Settled payments of max(0, amount - refunds' total not Failed).
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* What the form may offer, from the order alone. Pending refunds count as refunded.
|
|
20
|
+
* The plugin is authoritative (`quantity_exceeds`, `amount_mismatch`).
|
|
21
|
+
* Amounts are Vendure's minor units with tax.
|
|
22
|
+
*/
|
|
23
|
+
export function vendureRefundable(order: Pick<VendureOrder, 'lines' | 'shippingWithTax' | 'payments'>): VendureRefundable {
|
|
24
|
+
const refundedQuantities = new Map<string, number>();
|
|
25
|
+
let refundedShipping = 0;
|
|
26
|
+
let moneyWithTax = 0;
|
|
27
|
+
for (const payment of order.payments ?? []) {
|
|
28
|
+
let refundedTotal = 0;
|
|
29
|
+
for (const refund of payment.refunds ?? []) {
|
|
30
|
+
if (refund.state === 'Failed') continue;
|
|
31
|
+
refundedTotal += Number.isFinite(refund.total) ? refund.total : 0;
|
|
32
|
+
refundedShipping += Number.isFinite(refund.shipping) ? refund.shipping : 0;
|
|
33
|
+
for (const line of refund.lines ?? []) {
|
|
34
|
+
refundedQuantities.set(line.orderLineId, (refundedQuantities.get(line.orderLineId) ?? 0)
|
|
35
|
+
+ (Number.isFinite(line.quantity) ? line.quantity : 0));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (payment.state === 'Settled') {
|
|
39
|
+
moneyWithTax += Math.max(0, (Number.isFinite(payment.amount) ? payment.amount : 0) - refundedTotal);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
lines: (order.lines ?? []).map(line => {
|
|
44
|
+
const quantity = Number.isFinite(line.quantity) ? line.quantity : 0;
|
|
45
|
+
const refundedQuantity = refundedQuantities.get(line.id) ?? 0;
|
|
46
|
+
return {
|
|
47
|
+
orderLineId: line.id,
|
|
48
|
+
quantity,
|
|
49
|
+
refundedQuantity,
|
|
50
|
+
cancelledQuantity: Number.isFinite(line.orderPlacedQuantity) ? Math.max(0, line.orderPlacedQuantity - quantity) : 0,
|
|
51
|
+
refundableQuantity: Math.max(0, quantity - refundedQuantity),
|
|
52
|
+
unitRefundWithTax: Number.isFinite(line.proratedUnitPriceWithTax) ? line.proratedUnitPriceWithTax : 0,
|
|
53
|
+
};
|
|
54
|
+
}),
|
|
55
|
+
shippingWithTax: Math.max(0, (Number.isFinite(order.shippingWithTax) ? order.shippingWithTax : 0) - refundedShipping),
|
|
56
|
+
moneyWithTax,
|
|
57
|
+
};
|
|
58
|
+
}
|