@codemill-solutions/yuki-mcp 1.0.0 → 1.1.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 +9 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +24 -20
- package/dist/index.js.map +1 -1
- package/dist/tools/accounting.d.ts +2 -2
- package/dist/tools/accounting.d.ts.map +1 -1
- package/dist/tools/accounting.js +14 -17
- package/dist/tools/accounting.js.map +1 -1
- package/dist/tools/administrations.d.ts +2 -2
- package/dist/tools/administrations.d.ts.map +1 -1
- package/dist/tools/administrations.js +9 -9
- package/dist/tools/administrations.js.map +1 -1
- package/dist/tools/backoffice.d.ts +13 -0
- package/dist/tools/backoffice.d.ts.map +1 -0
- package/dist/tools/backoffice.js +126 -0
- package/dist/tools/backoffice.js.map +1 -0
- package/dist/tools/documents.d.ts +2 -2
- package/dist/tools/documents.d.ts.map +1 -1
- package/dist/tools/documents.js +87 -79
- package/dist/tools/documents.js.map +1 -1
- package/dist/tools/invoices.d.ts +2 -2
- package/dist/tools/invoices.d.ts.map +1 -1
- package/dist/tools/invoices.js +231 -190
- package/dist/tools/invoices.js.map +1 -1
- package/dist/tools/relations.d.ts +2 -2
- package/dist/tools/relations.d.ts.map +1 -1
- package/dist/tools/relations.js +106 -88
- package/dist/tools/relations.js.map +1 -1
- package/dist/tools/transactions.d.ts +2 -2
- package/dist/tools/transactions.d.ts.map +1 -1
- package/dist/tools/transactions.js +71 -63
- package/dist/tools/transactions.js.map +1 -1
- package/dist/yuki-client.d.ts.map +1 -1
- package/dist/yuki-client.js +42 -42
- package/dist/yuki-client.js.map +1 -1
- package/package.json +10 -2
package/dist/tools/invoices.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z } from
|
|
2
|
-
import { XmlValue, escapeXml } from
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { XmlValue, escapeXml } from '../yuki-client.js';
|
|
3
3
|
/**
|
|
4
4
|
* Register tools for retrieving outstanding invoices from Yuki.
|
|
5
5
|
*
|
|
@@ -28,51 +28,44 @@ export function registerInvoiceTools(server, client) {
|
|
|
28
28
|
*
|
|
29
29
|
* Rate cost: 1 request.
|
|
30
30
|
*/
|
|
31
|
-
server.registerTool(
|
|
32
|
-
description:
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
server.registerTool('get_sales_invoices', {
|
|
32
|
+
description: 'Retrieve outstanding (unpaid) sales invoices from Yuki. ' +
|
|
33
|
+
'Returns debtor name, reference, due date, open amount, and currency for each item. ' +
|
|
34
|
+
'Optionally filter by due date cutoff using dateOutstanding.',
|
|
35
35
|
inputSchema: {
|
|
36
36
|
dateOutstanding: z
|
|
37
37
|
.string()
|
|
38
38
|
.optional()
|
|
39
|
-
.describe(
|
|
40
|
-
|
|
39
|
+
.describe('ISO date string (YYYY-MM-DD). When provided, returns only items ' +
|
|
40
|
+
'outstanding as of this date. Omit for all outstanding items.'),
|
|
41
41
|
sortOrder: z
|
|
42
|
-
.enum([
|
|
43
|
-
"ContactAsc",
|
|
44
|
-
"ContactDesc",
|
|
45
|
-
"AmountAsc",
|
|
46
|
-
"AmountDesc",
|
|
47
|
-
"DateAsc",
|
|
48
|
-
"DateDesc",
|
|
49
|
-
])
|
|
42
|
+
.enum(['ContactAsc', 'ContactDesc', 'AmountAsc', 'AmountDesc', 'DateAsc', 'DateDesc'])
|
|
50
43
|
.optional()
|
|
51
|
-
.default(
|
|
52
|
-
.describe(
|
|
44
|
+
.default('DateDesc')
|
|
45
|
+
.describe('Sort order for results.'),
|
|
53
46
|
includeBankTransactions: z
|
|
54
47
|
.boolean()
|
|
55
48
|
.optional()
|
|
56
49
|
.default(false)
|
|
57
|
-
.describe(
|
|
50
|
+
.describe('Include bank transaction data alongside invoice items.'),
|
|
58
51
|
administrationId: z
|
|
59
52
|
.string()
|
|
60
53
|
.optional()
|
|
61
|
-
.describe(
|
|
54
|
+
.describe('Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var.'),
|
|
62
55
|
},
|
|
63
56
|
}, async ({ dateOutstanding, sortOrder, includeBankTransactions, administrationId }) => {
|
|
64
57
|
try {
|
|
65
58
|
const adminId = administrationId ?? client.defaultDomainId;
|
|
66
59
|
if (!adminId) {
|
|
67
|
-
throw new Error(
|
|
60
|
+
throw new Error('administrationId is required (or set YUKI_DOMAIN_ID env var)');
|
|
68
61
|
}
|
|
69
62
|
const sessionID = await client.getSessionID();
|
|
70
63
|
let result;
|
|
71
64
|
if (dateOutstanding) {
|
|
72
65
|
// Filter by specific outstanding date
|
|
73
66
|
result = await client.callSoap({
|
|
74
|
-
service:
|
|
75
|
-
method:
|
|
67
|
+
service: 'Accounting.asmx',
|
|
68
|
+
method: 'OutstandingDebtorItemsByDateOutstanding',
|
|
76
69
|
params: {
|
|
77
70
|
sessionID,
|
|
78
71
|
administrationID: adminId,
|
|
@@ -82,13 +75,13 @@ export function registerInvoiceTools(server, client) {
|
|
|
82
75
|
}
|
|
83
76
|
else {
|
|
84
77
|
result = await client.callSoap({
|
|
85
|
-
service:
|
|
86
|
-
method:
|
|
78
|
+
service: 'Accounting.asmx',
|
|
79
|
+
method: 'OutstandingDebtorItems',
|
|
87
80
|
params: {
|
|
88
81
|
sessionID,
|
|
89
82
|
administrationID: adminId,
|
|
90
83
|
includeBankTransactions: includeBankTransactions ?? false,
|
|
91
|
-
sortOrder: sortOrder ??
|
|
84
|
+
sortOrder: sortOrder ?? 'DateDesc',
|
|
92
85
|
},
|
|
93
86
|
});
|
|
94
87
|
}
|
|
@@ -96,11 +89,11 @@ export function registerInvoiceTools(server, client) {
|
|
|
96
89
|
return {
|
|
97
90
|
content: [
|
|
98
91
|
{
|
|
99
|
-
type:
|
|
92
|
+
type: 'text',
|
|
100
93
|
text: JSON.stringify({
|
|
101
94
|
success: true,
|
|
102
95
|
count: invoices.length,
|
|
103
|
-
note:
|
|
96
|
+
note: 'Only outstanding (unpaid) items are returned.',
|
|
104
97
|
invoices,
|
|
105
98
|
}, null, 2),
|
|
106
99
|
},
|
|
@@ -112,7 +105,7 @@ export function registerInvoiceTools(server, client) {
|
|
|
112
105
|
return {
|
|
113
106
|
content: [
|
|
114
107
|
{
|
|
115
|
-
type:
|
|
108
|
+
type: 'text',
|
|
116
109
|
text: JSON.stringify({ success: false, error: message }, null, 2),
|
|
117
110
|
},
|
|
118
111
|
],
|
|
@@ -133,50 +126,43 @@ export function registerInvoiceTools(server, client) {
|
|
|
133
126
|
*
|
|
134
127
|
* Rate cost: 1 request.
|
|
135
128
|
*/
|
|
136
|
-
server.registerTool(
|
|
137
|
-
description:
|
|
138
|
-
|
|
139
|
-
|
|
129
|
+
server.registerTool('get_purchase_invoices', {
|
|
130
|
+
description: 'Retrieve outstanding (unpaid) purchase invoices from Yuki. ' +
|
|
131
|
+
'Returns creditor name, reference, due date, open amount, and currency for each item. ' +
|
|
132
|
+
'Optionally filter by due date cutoff using dateOutstanding.',
|
|
140
133
|
inputSchema: {
|
|
141
134
|
dateOutstanding: z
|
|
142
135
|
.string()
|
|
143
136
|
.optional()
|
|
144
|
-
.describe(
|
|
145
|
-
|
|
137
|
+
.describe('ISO date string (YYYY-MM-DD). When provided, returns only items ' +
|
|
138
|
+
'outstanding as of this date. Omit for all outstanding items.'),
|
|
146
139
|
sortOrder: z
|
|
147
|
-
.enum([
|
|
148
|
-
"ContactAsc",
|
|
149
|
-
"ContactDesc",
|
|
150
|
-
"AmountAsc",
|
|
151
|
-
"AmountDesc",
|
|
152
|
-
"DateAsc",
|
|
153
|
-
"DateDesc",
|
|
154
|
-
])
|
|
140
|
+
.enum(['ContactAsc', 'ContactDesc', 'AmountAsc', 'AmountDesc', 'DateAsc', 'DateDesc'])
|
|
155
141
|
.optional()
|
|
156
|
-
.default(
|
|
157
|
-
.describe(
|
|
142
|
+
.default('DateDesc')
|
|
143
|
+
.describe('Sort order for results.'),
|
|
158
144
|
includeBankTransactions: z
|
|
159
145
|
.boolean()
|
|
160
146
|
.optional()
|
|
161
147
|
.default(false)
|
|
162
|
-
.describe(
|
|
148
|
+
.describe('Include bank transaction data alongside invoice items.'),
|
|
163
149
|
administrationId: z
|
|
164
150
|
.string()
|
|
165
151
|
.optional()
|
|
166
|
-
.describe(
|
|
152
|
+
.describe('Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var.'),
|
|
167
153
|
},
|
|
168
154
|
}, async ({ dateOutstanding, sortOrder, includeBankTransactions, administrationId }) => {
|
|
169
155
|
try {
|
|
170
156
|
const adminId = administrationId ?? client.defaultDomainId;
|
|
171
157
|
if (!adminId) {
|
|
172
|
-
throw new Error(
|
|
158
|
+
throw new Error('administrationId is required (or set YUKI_DOMAIN_ID env var)');
|
|
173
159
|
}
|
|
174
160
|
const sessionID = await client.getSessionID();
|
|
175
161
|
let result;
|
|
176
162
|
if (dateOutstanding) {
|
|
177
163
|
result = await client.callSoap({
|
|
178
|
-
service:
|
|
179
|
-
method:
|
|
164
|
+
service: 'Accounting.asmx',
|
|
165
|
+
method: 'OutstandingCreditorItemsByDateOutstanding',
|
|
180
166
|
params: {
|
|
181
167
|
sessionID,
|
|
182
168
|
administrationID: adminId,
|
|
@@ -186,13 +172,13 @@ export function registerInvoiceTools(server, client) {
|
|
|
186
172
|
}
|
|
187
173
|
else {
|
|
188
174
|
result = await client.callSoap({
|
|
189
|
-
service:
|
|
190
|
-
method:
|
|
175
|
+
service: 'Accounting.asmx',
|
|
176
|
+
method: 'OutstandingCreditorItems',
|
|
191
177
|
params: {
|
|
192
178
|
sessionID,
|
|
193
179
|
administrationID: adminId,
|
|
194
180
|
includeBankTransactions: includeBankTransactions ?? false,
|
|
195
|
-
sortOrder: sortOrder ??
|
|
181
|
+
sortOrder: sortOrder ?? 'DateDesc',
|
|
196
182
|
},
|
|
197
183
|
});
|
|
198
184
|
}
|
|
@@ -200,11 +186,11 @@ export function registerInvoiceTools(server, client) {
|
|
|
200
186
|
return {
|
|
201
187
|
content: [
|
|
202
188
|
{
|
|
203
|
-
type:
|
|
189
|
+
type: 'text',
|
|
204
190
|
text: JSON.stringify({
|
|
205
191
|
success: true,
|
|
206
192
|
count: invoices.length,
|
|
207
|
-
note:
|
|
193
|
+
note: 'Only outstanding (unpaid) items are returned.',
|
|
208
194
|
invoices,
|
|
209
195
|
}, null, 2),
|
|
210
196
|
},
|
|
@@ -216,7 +202,7 @@ export function registerInvoiceTools(server, client) {
|
|
|
216
202
|
return {
|
|
217
203
|
content: [
|
|
218
204
|
{
|
|
219
|
-
type:
|
|
205
|
+
type: 'text',
|
|
220
206
|
text: JSON.stringify({ success: false, error: message }, null, 2),
|
|
221
207
|
},
|
|
222
208
|
],
|
|
@@ -228,22 +214,25 @@ export function registerInvoiceTools(server, client) {
|
|
|
228
214
|
// ── Zod schemas shared between write tools ────────────────────────────────────
|
|
229
215
|
/** Contact block used in both sales and purchase invoice XML documents. */
|
|
230
216
|
const ContactSchema = z.object({
|
|
231
|
-
contactCode: z
|
|
232
|
-
|
|
217
|
+
contactCode: z
|
|
218
|
+
.string()
|
|
219
|
+
.optional()
|
|
220
|
+
.describe("Existing Yuki contact code (e.g. 'CUST001'). When provided, Yuki will link to the existing contact."),
|
|
221
|
+
fullName: z.string().describe('Full company or person name'),
|
|
233
222
|
firstName: z.string().optional(),
|
|
234
223
|
lastName: z.string().optional(),
|
|
235
224
|
emailAddress: z.string().optional(),
|
|
236
225
|
phone: z.string().optional(),
|
|
237
|
-
countryCode: z.string().optional().default(
|
|
226
|
+
countryCode: z.string().optional().default('NL').describe("ISO 3166-1 alpha-2 country code, e.g. 'NL'"),
|
|
238
227
|
city: z.string().optional(),
|
|
239
228
|
zipcode: z.string().optional(),
|
|
240
229
|
addressLine1: z.string().optional(),
|
|
241
230
|
addressLine2: z.string().optional(),
|
|
242
231
|
vatNumber: z.string().optional(),
|
|
243
232
|
cocNumber: z.string().optional(),
|
|
244
|
-
bankAccount: z.string().optional().describe(
|
|
233
|
+
bankAccount: z.string().optional().describe('IBAN bank account number'),
|
|
245
234
|
bic: z.string().optional(),
|
|
246
|
-
contactType: z.enum([
|
|
235
|
+
contactType: z.enum(['Debtor', 'Creditor', 'Both', 'Person']).optional(),
|
|
247
236
|
});
|
|
248
237
|
// ── process_sales_invoice ─────────────────────────────────────────────────────
|
|
249
238
|
/**
|
|
@@ -265,48 +254,74 @@ export function registerInvoiceWriteTools(server, client) {
|
|
|
265
254
|
* Note: Sales.asmx uses sessionId / administrationId (lowercase d).
|
|
266
255
|
* Rate cost: 1 request.
|
|
267
256
|
*/
|
|
268
|
-
server.registerTool(
|
|
269
|
-
description:
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
257
|
+
server.registerTool('process_sales_invoice', {
|
|
258
|
+
description: 'Create and book a new sales invoice in Yuki. ' +
|
|
259
|
+
'Accepts invoice header, contact, and line items. ' +
|
|
260
|
+
'Optionally sends the invoice to the customer by email. ' +
|
|
261
|
+
'Returns the Yuki response including the assigned document ID.',
|
|
273
262
|
inputSchema: {
|
|
274
263
|
reference: z.string().describe("Invoice number / reference (e.g. '2024-0042')"),
|
|
275
|
-
subject: z.string().describe(
|
|
276
|
-
date: z.string().describe(
|
|
277
|
-
dueDate: z.string().describe(
|
|
278
|
-
contact: ContactSchema.describe(
|
|
279
|
-
lines: z
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
264
|
+
subject: z.string().describe('Invoice title shown to the customer'),
|
|
265
|
+
date: z.string().describe('Invoice date in YYYY-MM-DD format'),
|
|
266
|
+
dueDate: z.string().describe('Payment due date in YYYY-MM-DD format'),
|
|
267
|
+
contact: ContactSchema.describe('Customer contact details'),
|
|
268
|
+
lines: z
|
|
269
|
+
.array(z.object({
|
|
270
|
+
description: z.string().describe('Line item description'),
|
|
271
|
+
quantity: z.number().optional().default(1).describe('Quantity (default 1)'),
|
|
272
|
+
salesPrice: z.number().describe('Unit price excluding VAT'),
|
|
273
|
+
vatPercentage: z.number().optional().default(21).describe('VAT percentage (default 21)'),
|
|
274
|
+
vatIncluded: z.boolean().optional().default(false).describe('Whether salesPrice already includes VAT'),
|
|
275
|
+
vatType: z
|
|
276
|
+
.string()
|
|
277
|
+
.optional()
|
|
278
|
+
.default('NL_Normal')
|
|
279
|
+
.describe('VAT type code: NL_Normal (21%), NL_Reduced (9%), NL_Zero (0%), NL_None (exempt)'),
|
|
280
|
+
glAccountCode: z.string().optional().describe('GL account code to post this line to'),
|
|
281
|
+
productReference: z.string().optional().describe('Product/SKU reference code'),
|
|
282
|
+
}))
|
|
283
|
+
.min(1)
|
|
284
|
+
.describe('Invoice line items'),
|
|
285
|
+
paymentMethod: z
|
|
286
|
+
.enum(['ElectronicTransfer', 'DirectCollection', 'Cash', 'DebitCard', 'CreditCard'])
|
|
287
|
+
.optional()
|
|
288
|
+
.default('ElectronicTransfer'),
|
|
289
|
+
currency: z.string().optional().default('EUR').describe('ISO 4217 currency code'),
|
|
290
|
+
process: z.boolean().optional().default(true).describe('Immediately process/finalise the invoice in Yuki'),
|
|
291
|
+
emailToCustomer: z
|
|
292
|
+
.boolean()
|
|
293
|
+
.optional()
|
|
294
|
+
.default(false)
|
|
295
|
+
.describe("Send the invoice to the customer's email address"),
|
|
296
|
+
remarks: z.string().optional().describe('Internal remarks (not visible on invoice)'),
|
|
297
|
+
administrationId: z
|
|
298
|
+
.string()
|
|
299
|
+
.optional()
|
|
300
|
+
.describe('Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var.'),
|
|
296
301
|
},
|
|
297
|
-
}, async ({ reference, subject, date, dueDate, contact, lines, paymentMethod, currency, process: doProcess, emailToCustomer, remarks, administrationId }) => {
|
|
302
|
+
}, async ({ reference, subject, date, dueDate, contact, lines, paymentMethod, currency, process: doProcess, emailToCustomer, remarks, administrationId, }) => {
|
|
298
303
|
try {
|
|
299
304
|
const adminId = administrationId ?? client.defaultDomainId;
|
|
300
305
|
if (!adminId)
|
|
301
|
-
throw new Error(
|
|
306
|
+
throw new Error('administrationId is required (or set YUKI_DOMAIN_ID env var)');
|
|
302
307
|
const sessionId = await client.getSessionID();
|
|
303
|
-
const xmlDoc = buildSalesInvoiceXml({
|
|
304
|
-
|
|
305
|
-
|
|
308
|
+
const xmlDoc = buildSalesInvoiceXml({
|
|
309
|
+
reference,
|
|
310
|
+
subject,
|
|
311
|
+
date,
|
|
312
|
+
dueDate,
|
|
313
|
+
contact,
|
|
314
|
+
lines,
|
|
315
|
+
paymentMethod,
|
|
316
|
+
currency,
|
|
317
|
+
process: doProcess,
|
|
318
|
+
emailToCustomer,
|
|
319
|
+
remarks,
|
|
320
|
+
});
|
|
306
321
|
// Sales.asmx uses sessionId / administrationId (lowercase d)
|
|
307
322
|
const result = await client.callSoap({
|
|
308
|
-
service:
|
|
309
|
-
method:
|
|
323
|
+
service: 'Sales.asmx',
|
|
324
|
+
method: 'ProcessSalesInvoices',
|
|
310
325
|
params: {
|
|
311
326
|
sessionId,
|
|
312
327
|
administrationId: adminId,
|
|
@@ -314,15 +329,13 @@ export function registerInvoiceWriteTools(server, client) {
|
|
|
314
329
|
},
|
|
315
330
|
});
|
|
316
331
|
return {
|
|
317
|
-
content: [{ type:
|
|
318
|
-
text: JSON.stringify({ success: true, reference, result }, null, 2) }],
|
|
332
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true, reference, result }, null, 2) }],
|
|
319
333
|
};
|
|
320
334
|
}
|
|
321
335
|
catch (err) {
|
|
322
336
|
const message = err instanceof Error ? err.message : String(err);
|
|
323
337
|
return {
|
|
324
|
-
content: [{ type:
|
|
325
|
-
text: JSON.stringify({ success: false, error: message }, null, 2) }],
|
|
338
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: message }, null, 2) }],
|
|
326
339
|
isError: true,
|
|
327
340
|
};
|
|
328
341
|
}
|
|
@@ -342,67 +355,97 @@ export function registerInvoiceWriteTools(server, client) {
|
|
|
342
355
|
* Note: Purchase.asmx uses sessionId / administrationId (lowercase d).
|
|
343
356
|
* Rate cost: 1 request.
|
|
344
357
|
*/
|
|
345
|
-
server.registerTool(
|
|
346
|
-
description:
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
358
|
+
server.registerTool('process_purchase_invoice', {
|
|
359
|
+
description: 'Book an incoming purchase invoice in Yuki. ' +
|
|
360
|
+
'Accepts invoice totals, supplier contact, and line items. ' +
|
|
361
|
+
'Optionally attach the original PDF as a base64 string. ' +
|
|
362
|
+
'Returns the Yuki response including the assigned document ID.',
|
|
350
363
|
inputSchema: {
|
|
351
364
|
reference: z.string().optional().describe("Supplier's invoice number"),
|
|
352
|
-
date: z.string().describe(
|
|
353
|
-
dueDate: z.string().optional().describe(
|
|
354
|
-
invoiceAmount: z.number().describe(
|
|
355
|
-
invoiceVatAmount: z.number().describe(
|
|
356
|
-
contact: ContactSchema.describe(
|
|
357
|
-
lines: z
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
.describe(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
365
|
+
date: z.string().describe('Invoice date in YYYY-MM-DD format'),
|
|
366
|
+
dueDate: z.string().optional().describe('Payment due date in YYYY-MM-DD format'),
|
|
367
|
+
invoiceAmount: z.number().describe('Total invoice amount including VAT'),
|
|
368
|
+
invoiceVatAmount: z.number().describe('Total VAT amount on the invoice'),
|
|
369
|
+
contact: ContactSchema.describe('Supplier contact details'),
|
|
370
|
+
lines: z
|
|
371
|
+
.array(z.object({
|
|
372
|
+
description: z.string().optional().describe('Line item description'),
|
|
373
|
+
lineAmount: z.number().describe('Line amount excluding VAT'),
|
|
374
|
+
lineVatType: z
|
|
375
|
+
.number()
|
|
376
|
+
.optional()
|
|
377
|
+
.default(2)
|
|
378
|
+
.describe('VAT type code: 1=None, 2=21% NL, 3=9% NL, 4=0% NL, 5=EU (see Yuki docs)'),
|
|
379
|
+
lineVatPercentage: z
|
|
380
|
+
.number()
|
|
381
|
+
.optional()
|
|
382
|
+
.describe('VAT percentage (e.g. 21). Derived from lineVatType if omitted.'),
|
|
383
|
+
glAccountCode: z
|
|
384
|
+
.string()
|
|
385
|
+
.optional()
|
|
386
|
+
.describe("GL account code to post this line to (e.g. '4000' for cost of goods)"),
|
|
387
|
+
vatDeductibilityPercentage: z
|
|
388
|
+
.number()
|
|
389
|
+
.optional()
|
|
390
|
+
.default(100)
|
|
391
|
+
.describe('Deductible VAT percentage (0-100). Use <100 for partially deductible costs.'),
|
|
392
|
+
}))
|
|
393
|
+
.min(1)
|
|
394
|
+
.describe('Invoice line items'),
|
|
395
|
+
paymentMethod: z
|
|
396
|
+
.enum(['Transfer', 'DirectCollection', 'Cash', 'DebitCard', 'CreditCard'])
|
|
397
|
+
.optional()
|
|
398
|
+
.default('Transfer'),
|
|
399
|
+
currency: z.string().optional().default('EUR'),
|
|
400
|
+
process: z.boolean().optional().default(true).describe('Immediately process/finalise in Yuki'),
|
|
371
401
|
documentFileName: z.string().optional().describe("PDF filename (e.g. 'invoice-2024-0042.pdf')"),
|
|
372
|
-
documentBase64: z.string().optional().describe(
|
|
402
|
+
documentBase64: z.string().optional().describe('PDF content encoded as base64 string'),
|
|
373
403
|
remarks: z.string().optional(),
|
|
374
|
-
administrationId: z
|
|
404
|
+
administrationId: z
|
|
405
|
+
.string()
|
|
406
|
+
.optional()
|
|
407
|
+
.describe('Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var.'),
|
|
375
408
|
},
|
|
376
|
-
}, async ({ reference, date, dueDate, invoiceAmount, invoiceVatAmount, contact, lines, paymentMethod, currency, process: doProcess, documentFileName, documentBase64, remarks, administrationId }) => {
|
|
409
|
+
}, async ({ reference, date, dueDate, invoiceAmount, invoiceVatAmount, contact, lines, paymentMethod, currency, process: doProcess, documentFileName, documentBase64, remarks, administrationId, }) => {
|
|
377
410
|
try {
|
|
378
411
|
const adminId = administrationId ?? client.defaultDomainId;
|
|
379
412
|
if (!adminId)
|
|
380
|
-
throw new Error(
|
|
413
|
+
throw new Error('administrationId is required (or set YUKI_DOMAIN_ID env var)');
|
|
381
414
|
const sessionId = await client.getSessionID();
|
|
382
|
-
const xmlDoc = buildPurchaseInvoiceXml({
|
|
383
|
-
|
|
384
|
-
|
|
415
|
+
const xmlDoc = buildPurchaseInvoiceXml({
|
|
416
|
+
reference,
|
|
417
|
+
date,
|
|
418
|
+
dueDate,
|
|
419
|
+
invoiceAmount,
|
|
420
|
+
invoiceVatAmount,
|
|
421
|
+
contact,
|
|
422
|
+
lines,
|
|
423
|
+
paymentMethod,
|
|
424
|
+
currency,
|
|
425
|
+
process: doProcess,
|
|
426
|
+
documentFileName,
|
|
427
|
+
documentBase64,
|
|
428
|
+
remarks,
|
|
429
|
+
});
|
|
385
430
|
// Purchase.asmx uses sessionId / administrationId (lowercase d)
|
|
386
431
|
const result = await client.callSoap({
|
|
387
|
-
service:
|
|
388
|
-
method:
|
|
432
|
+
service: 'Purchase.asmx',
|
|
433
|
+
method: 'ProcessPurchaseInvoices',
|
|
389
434
|
params: {
|
|
390
435
|
sessionId,
|
|
391
436
|
administrationId: adminId,
|
|
392
|
-
disableAutoCorrect:
|
|
437
|
+
disableAutoCorrect: '',
|
|
393
438
|
xmlDoc: new XmlValue(xmlDoc),
|
|
394
439
|
},
|
|
395
440
|
});
|
|
396
441
|
return {
|
|
397
|
-
content: [{ type:
|
|
398
|
-
text: JSON.stringify({ success: true, reference, result }, null, 2) }],
|
|
442
|
+
content: [{ type: 'text', text: JSON.stringify({ success: true, reference, result }, null, 2) }],
|
|
399
443
|
};
|
|
400
444
|
}
|
|
401
445
|
catch (err) {
|
|
402
446
|
const message = err instanceof Error ? err.message : String(err);
|
|
403
447
|
return {
|
|
404
|
-
content: [{ type:
|
|
405
|
-
text: JSON.stringify({ success: false, error: message }, null, 2) }],
|
|
448
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: message }, null, 2) }],
|
|
406
449
|
isError: true,
|
|
407
450
|
};
|
|
408
451
|
}
|
|
@@ -413,7 +456,8 @@ export function registerInvoiceWriteTools(server, client) {
|
|
|
413
456
|
function buildSalesInvoiceXml(args) {
|
|
414
457
|
const x = escapeXml;
|
|
415
458
|
const { contact } = args;
|
|
416
|
-
const linesXml = args.lines
|
|
459
|
+
const linesXml = args.lines
|
|
460
|
+
.map((line) => `
|
|
417
461
|
<InvoiceLine>
|
|
418
462
|
<Description>${x(line.description)}</Description>
|
|
419
463
|
<ProductQuantity>${line.quantity ?? 1}</ProductQuantity>
|
|
@@ -421,11 +465,12 @@ function buildSalesInvoiceXml(args) {
|
|
|
421
465
|
<SalesPrice>${line.salesPrice}</SalesPrice>
|
|
422
466
|
<VATPercentage>${line.vatPercentage ?? 21}</VATPercentage>
|
|
423
467
|
<VATIncluded>${line.vatIncluded ?? false}</VATIncluded>
|
|
424
|
-
<VATType>${x(line.vatType ??
|
|
425
|
-
${line.glAccountCode ? `<GLAccountCode>${x(line.glAccountCode)}</GLAccountCode>` :
|
|
426
|
-
${line.productReference ? `<Reference>${x(line.productReference)}</Reference>` :
|
|
468
|
+
<VATType>${x(line.vatType ?? 'NL_Normal')}</VATType>
|
|
469
|
+
${line.glAccountCode ? `<GLAccountCode>${x(line.glAccountCode)}</GLAccountCode>` : ''}
|
|
470
|
+
${line.productReference ? `<Reference>${x(line.productReference)}</Reference>` : ''}
|
|
427
471
|
</Product>
|
|
428
|
-
</InvoiceLine>`)
|
|
472
|
+
</InvoiceLine>`)
|
|
473
|
+
.join('');
|
|
429
474
|
return `<?xml version="1.0" encoding="utf-8"?>
|
|
430
475
|
<SalesInvoices xmlns="urn:xmlns:http://www.theyukicompany.com:salesinvoices"
|
|
431
476
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
@@ -434,25 +479,25 @@ function buildSalesInvoiceXml(args) {
|
|
|
434
479
|
<Subject>${x(args.subject)}</Subject>
|
|
435
480
|
<Date>${x(args.date)}</Date>
|
|
436
481
|
<DueDate>${x(args.dueDate)}</DueDate>
|
|
437
|
-
<PaymentMethod>${x(args.paymentMethod ??
|
|
438
|
-
<Currency>${x(args.currency ??
|
|
482
|
+
<PaymentMethod>${x(args.paymentMethod ?? 'ElectronicTransfer')}</PaymentMethod>
|
|
483
|
+
<Currency>${x(args.currency ?? 'EUR')}</Currency>
|
|
439
484
|
<Process>${args.process ?? true}</Process>
|
|
440
485
|
<EmailToCustomer>${args.emailToCustomer ?? false}</EmailToCustomer>
|
|
441
|
-
${args.remarks ? `<Remarks>${x(args.remarks)}</Remarks>` :
|
|
486
|
+
${args.remarks ? `<Remarks>${x(args.remarks)}</Remarks>` : ''}
|
|
442
487
|
<Contact>
|
|
443
|
-
${contact.contactCode ? `<ContactCode>${x(contact.contactCode)}</ContactCode>` :
|
|
488
|
+
${contact.contactCode ? `<ContactCode>${x(contact.contactCode)}</ContactCode>` : ''}
|
|
444
489
|
<FullName>${x(contact.fullName)}</FullName>
|
|
445
|
-
${contact.firstName ? `<FirstName>${x(contact.firstName)}</FirstName>` :
|
|
446
|
-
${contact.lastName ? `<LastName>${x(contact.lastName)}</LastName>` :
|
|
447
|
-
${contact.emailAddress ? `<EmailAddress>${x(contact.emailAddress)}</EmailAddress>` :
|
|
448
|
-
${contact.countryCode ? `<CountryCode>${x(contact.countryCode)}</CountryCode>` :
|
|
449
|
-
${contact.city ? `<City>${x(contact.city)}</City>` :
|
|
450
|
-
${contact.zipcode ? `<Zipcode>${x(contact.zipcode)}</Zipcode>` :
|
|
451
|
-
${contact.addressLine1 ? `<AddressLine_1>${x(contact.addressLine1)}</AddressLine_1>` :
|
|
452
|
-
${contact.addressLine2 ? `<AddressLine_2>${x(contact.addressLine2)}</AddressLine_2>` :
|
|
453
|
-
${contact.vatNumber ? `<VATNumber>${x(contact.vatNumber)}</VATNumber>` :
|
|
454
|
-
${contact.cocNumber ? `<CoCNumber>${x(contact.cocNumber)}</CoCNumber>` :
|
|
455
|
-
${contact.contactType ? `<ContactType>${x(contact.contactType)}</ContactType>` :
|
|
490
|
+
${contact.firstName ? `<FirstName>${x(contact.firstName)}</FirstName>` : ''}
|
|
491
|
+
${contact.lastName ? `<LastName>${x(contact.lastName)}</LastName>` : ''}
|
|
492
|
+
${contact.emailAddress ? `<EmailAddress>${x(contact.emailAddress)}</EmailAddress>` : ''}
|
|
493
|
+
${contact.countryCode ? `<CountryCode>${x(contact.countryCode)}</CountryCode>` : ''}
|
|
494
|
+
${contact.city ? `<City>${x(contact.city)}</City>` : ''}
|
|
495
|
+
${contact.zipcode ? `<Zipcode>${x(contact.zipcode)}</Zipcode>` : ''}
|
|
496
|
+
${contact.addressLine1 ? `<AddressLine_1>${x(contact.addressLine1)}</AddressLine_1>` : ''}
|
|
497
|
+
${contact.addressLine2 ? `<AddressLine_2>${x(contact.addressLine2)}</AddressLine_2>` : ''}
|
|
498
|
+
${contact.vatNumber ? `<VATNumber>${x(contact.vatNumber)}</VATNumber>` : ''}
|
|
499
|
+
${contact.cocNumber ? `<CoCNumber>${x(contact.cocNumber)}</CoCNumber>` : ''}
|
|
500
|
+
${contact.contactType ? `<ContactType>${x(contact.contactType)}</ContactType>` : ''}
|
|
456
501
|
</Contact>
|
|
457
502
|
<InvoiceLines>${linesXml}
|
|
458
503
|
</InvoiceLines>
|
|
@@ -463,45 +508,47 @@ function buildSalesInvoiceXml(args) {
|
|
|
463
508
|
function buildPurchaseInvoiceXml(args) {
|
|
464
509
|
const x = escapeXml;
|
|
465
510
|
const { contact } = args;
|
|
466
|
-
const linesXml = args.lines
|
|
511
|
+
const linesXml = args.lines
|
|
512
|
+
.map((line) => `
|
|
467
513
|
<InvoiceLine>
|
|
468
|
-
${line.description ? `<Description>${x(line.description)}</Description>` :
|
|
514
|
+
${line.description ? `<Description>${x(line.description)}</Description>` : ''}
|
|
469
515
|
<LineAmount>${line.lineAmount}</LineAmount>
|
|
470
516
|
<LineVATType>${line.lineVatType ?? 2}</LineVATType>
|
|
471
|
-
${line.lineVatPercentage !== undefined ? `<LineVATPercentage>${line.lineVatPercentage}</LineVATPercentage>` :
|
|
472
|
-
${line.glAccountCode ? `<GLAccountCode>${x(line.glAccountCode)}</GLAccountCode>` :
|
|
473
|
-
${line.vatDeductibilityPercentage !== undefined ? `<VATDeductibilityPercentage>${line.vatDeductibilityPercentage}</VATDeductibilityPercentage>` :
|
|
474
|
-
</InvoiceLine>`)
|
|
517
|
+
${line.lineVatPercentage !== undefined ? `<LineVATPercentage>${line.lineVatPercentage}</LineVATPercentage>` : ''}
|
|
518
|
+
${line.glAccountCode ? `<GLAccountCode>${x(line.glAccountCode)}</GLAccountCode>` : ''}
|
|
519
|
+
${line.vatDeductibilityPercentage !== undefined ? `<VATDeductibilityPercentage>${line.vatDeductibilityPercentage}</VATDeductibilityPercentage>` : ''}
|
|
520
|
+
</InvoiceLine>`)
|
|
521
|
+
.join('');
|
|
475
522
|
return `<?xml version="1.0" encoding="utf-8"?>
|
|
476
523
|
<PurchaseInvoices xmlns="urn:xmlns:http://www.theyukicompany.com:purchaseinvoices"
|
|
477
524
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
|
478
525
|
<PurchaseInvoice>
|
|
479
526
|
<Process>${args.process ?? true}</Process>
|
|
480
|
-
${args.reference ? `<Reference>${x(args.reference)}</Reference>` :
|
|
527
|
+
${args.reference ? `<Reference>${x(args.reference)}</Reference>` : ''}
|
|
481
528
|
<Date>${x(args.date)}</Date>
|
|
482
|
-
${args.dueDate ? `<DueDate>${x(args.dueDate)}</DueDate>` :
|
|
529
|
+
${args.dueDate ? `<DueDate>${x(args.dueDate)}</DueDate>` : ''}
|
|
483
530
|
<InvoiceAmount>${args.invoiceAmount}</InvoiceAmount>
|
|
484
531
|
<InvoiceVATAmount>${args.invoiceVatAmount}</InvoiceVATAmount>
|
|
485
|
-
<Currency>${x(args.currency ??
|
|
486
|
-
<PaymentMethod>${x(args.paymentMethod ??
|
|
487
|
-
${args.remarks ? `<Remarks>${x(args.remarks)}</Remarks>` :
|
|
488
|
-
${args.documentFileName ? `<DocumentFileName>${x(args.documentFileName)}</DocumentFileName>` :
|
|
489
|
-
${args.documentBase64 ? `<DocumentBase64>${args.documentBase64}</DocumentBase64>` :
|
|
532
|
+
<Currency>${x(args.currency ?? 'EUR')}</Currency>
|
|
533
|
+
<PaymentMethod>${x(args.paymentMethod ?? 'Transfer')}</PaymentMethod>
|
|
534
|
+
${args.remarks ? `<Remarks>${x(args.remarks)}</Remarks>` : ''}
|
|
535
|
+
${args.documentFileName ? `<DocumentFileName>${x(args.documentFileName)}</DocumentFileName>` : ''}
|
|
536
|
+
${args.documentBase64 ? `<DocumentBase64>${args.documentBase64}</DocumentBase64>` : ''}
|
|
490
537
|
<Contact>
|
|
491
|
-
${contact.contactCode ? `<ContactCode>${x(contact.contactCode)}</ContactCode>` :
|
|
538
|
+
${contact.contactCode ? `<ContactCode>${x(contact.contactCode)}</ContactCode>` : ''}
|
|
492
539
|
<FullName>${x(contact.fullName)}</FullName>
|
|
493
|
-
${contact.firstName ? `<FirstName>${x(contact.firstName)}</FirstName>` :
|
|
494
|
-
${contact.lastName ? `<LastName>${x(contact.lastName)}</LastName>` :
|
|
495
|
-
${contact.emailAddress ? `<EmailAddress>${x(contact.emailAddress)}</EmailAddress>` :
|
|
496
|
-
${contact.countryCode ? `<CountryCode>${x(contact.countryCode)}</CountryCode>` :
|
|
497
|
-
${contact.city ? `<City>${x(contact.city)}</City>` :
|
|
498
|
-
${contact.zipcode ? `<Zipcode>${x(contact.zipcode)}</Zipcode>` :
|
|
499
|
-
${contact.addressLine1 ? `<AddressLine_1>${x(contact.addressLine1)}</AddressLine_1>` :
|
|
500
|
-
${contact.vatNumber ? `<VATNumber>${x(contact.vatNumber)}</VATNumber>` :
|
|
501
|
-
${contact.cocNumber ? `<CoCNumber>${x(contact.cocNumber)}</CoCNumber>` :
|
|
502
|
-
${contact.bankAccount ? `<BankAccount>${x(contact.bankAccount)}</BankAccount>` :
|
|
503
|
-
${contact.bic ? `<BIC>${x(contact.bic)}</BIC>` :
|
|
504
|
-
${contact.contactType ? `<ContactType>${x(contact.contactType)}</ContactType>` :
|
|
540
|
+
${contact.firstName ? `<FirstName>${x(contact.firstName)}</FirstName>` : ''}
|
|
541
|
+
${contact.lastName ? `<LastName>${x(contact.lastName)}</LastName>` : ''}
|
|
542
|
+
${contact.emailAddress ? `<EmailAddress>${x(contact.emailAddress)}</EmailAddress>` : ''}
|
|
543
|
+
${contact.countryCode ? `<CountryCode>${x(contact.countryCode)}</CountryCode>` : ''}
|
|
544
|
+
${contact.city ? `<City>${x(contact.city)}</City>` : ''}
|
|
545
|
+
${contact.zipcode ? `<Zipcode>${x(contact.zipcode)}</Zipcode>` : ''}
|
|
546
|
+
${contact.addressLine1 ? `<AddressLine_1>${x(contact.addressLine1)}</AddressLine_1>` : ''}
|
|
547
|
+
${contact.vatNumber ? `<VATNumber>${x(contact.vatNumber)}</VATNumber>` : ''}
|
|
548
|
+
${contact.cocNumber ? `<CoCNumber>${x(contact.cocNumber)}</CoCNumber>` : ''}
|
|
549
|
+
${contact.bankAccount ? `<BankAccount>${x(contact.bankAccount)}</BankAccount>` : ''}
|
|
550
|
+
${contact.bic ? `<BIC>${x(contact.bic)}</BIC>` : ''}
|
|
551
|
+
${contact.contactType ? `<ContactType>${x(contact.contactType)}</ContactType>` : ''}
|
|
505
552
|
</Contact>
|
|
506
553
|
<InvoiceLines>${linesXml}
|
|
507
554
|
</InvoiceLines>
|
|
@@ -516,14 +563,8 @@ function normalizeItems(result) {
|
|
|
516
563
|
return result;
|
|
517
564
|
const rec = result;
|
|
518
565
|
// Common Yuki wrappers for outstanding items
|
|
519
|
-
const wrappers = [
|
|
520
|
-
|
|
521
|
-
"OutstandingItems", "Items", "Rows",
|
|
522
|
-
];
|
|
523
|
-
const itemTags = [
|
|
524
|
-
"DebtorItem", "CreditorItem",
|
|
525
|
-
"OutstandingItem", "Item", "Row",
|
|
526
|
-
];
|
|
566
|
+
const wrappers = ['DebtorItems', 'CreditorItems', 'OutstandingItems', 'Items', 'Rows'];
|
|
567
|
+
const itemTags = ['DebtorItem', 'CreditorItem', 'OutstandingItem', 'Item', 'Row'];
|
|
527
568
|
for (const wrapper of wrappers) {
|
|
528
569
|
const c = rec[wrapper];
|
|
529
570
|
if (!c)
|