@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.
@@ -1,5 +1,5 @@
1
- import { z } from "zod";
2
- import { XmlValue, escapeXml } from "../yuki-client.js";
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("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.",
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("ISO date string (YYYY-MM-DD). When provided, returns only items " +
40
- "outstanding as of this date. Omit for all outstanding items."),
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("DateDesc")
52
- .describe("Sort order for results."),
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("Include bank transaction data alongside invoice items."),
50
+ .describe('Include bank transaction data alongside invoice items.'),
58
51
  administrationId: z
59
52
  .string()
60
53
  .optional()
61
- .describe("Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var."),
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("administrationId is required (or set YUKI_DOMAIN_ID env var)");
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: "Accounting.asmx",
75
- method: "OutstandingDebtorItemsByDateOutstanding",
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: "Accounting.asmx",
86
- method: "OutstandingDebtorItems",
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 ?? "DateDesc",
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: "text",
92
+ type: 'text',
100
93
  text: JSON.stringify({
101
94
  success: true,
102
95
  count: invoices.length,
103
- note: "Only outstanding (unpaid) items are returned.",
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: "text",
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("get_purchase_invoices", {
137
- description: "Retrieve outstanding (unpaid) purchase invoices from Yuki. " +
138
- "Returns creditor name, reference, due date, open amount, and currency for each item. " +
139
- "Optionally filter by due date cutoff using dateOutstanding.",
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("ISO date string (YYYY-MM-DD). When provided, returns only items " +
145
- "outstanding as of this date. Omit for all outstanding items."),
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("DateDesc")
157
- .describe("Sort order for results."),
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("Include bank transaction data alongside invoice items."),
148
+ .describe('Include bank transaction data alongside invoice items.'),
163
149
  administrationId: z
164
150
  .string()
165
151
  .optional()
166
- .describe("Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var."),
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("administrationId is required (or set YUKI_DOMAIN_ID env var)");
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: "Accounting.asmx",
179
- method: "OutstandingCreditorItemsByDateOutstanding",
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: "Accounting.asmx",
190
- method: "OutstandingCreditorItems",
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 ?? "DateDesc",
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: "text",
189
+ type: 'text',
204
190
  text: JSON.stringify({
205
191
  success: true,
206
192
  count: invoices.length,
207
- note: "Only outstanding (unpaid) items are returned.",
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: "text",
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.string().optional().describe("Existing Yuki contact code (e.g. 'CUST001'). When provided, Yuki will link to the existing contact."),
232
- fullName: z.string().describe("Full company or person name"),
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("NL").describe("ISO 3166-1 alpha-2 country code, e.g. 'NL'"),
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("IBAN bank account number"),
233
+ bankAccount: z.string().optional().describe('IBAN bank account number'),
245
234
  bic: z.string().optional(),
246
- contactType: z.enum(["Debtor", "Creditor", "Both", "Person"]).optional(),
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("process_sales_invoice", {
269
- description: "Create and book a new sales invoice in Yuki. " +
270
- "Accepts invoice header, contact, and line items. " +
271
- "Optionally sends the invoice to the customer by email. " +
272
- "Returns the Yuki response including the assigned document ID.",
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("Invoice title shown to the customer"),
276
- date: z.string().describe("Invoice date in YYYY-MM-DD format"),
277
- dueDate: z.string().describe("Payment due date in YYYY-MM-DD format"),
278
- contact: ContactSchema.describe("Customer contact details"),
279
- lines: z.array(z.object({
280
- description: z.string().describe("Line item description"),
281
- quantity: z.number().optional().default(1).describe("Quantity (default 1)"),
282
- salesPrice: z.number().describe("Unit price excluding VAT"),
283
- vatPercentage: z.number().optional().default(21).describe("VAT percentage (default 21)"),
284
- vatIncluded: z.boolean().optional().default(false).describe("Whether salesPrice already includes VAT"),
285
- vatType: z.string().optional().default("NL_Normal").describe("VAT type code: NL_Normal (21%), NL_Reduced (9%), NL_Zero (0%), NL_None (exempt)"),
286
- glAccountCode: z.string().optional().describe("GL account code to post this line to"),
287
- productReference: z.string().optional().describe("Product/SKU reference code"),
288
- })).min(1).describe("Invoice line items"),
289
- paymentMethod: z.enum(["ElectronicTransfer", "DirectCollection", "Cash", "DebitCard", "CreditCard"])
290
- .optional().default("ElectronicTransfer"),
291
- currency: z.string().optional().default("EUR").describe("ISO 4217 currency code"),
292
- process: z.boolean().optional().default(true).describe("Immediately process/finalise the invoice in Yuki"),
293
- emailToCustomer: z.boolean().optional().default(false).describe("Send the invoice to the customer's email address"),
294
- remarks: z.string().optional().describe("Internal remarks (not visible on invoice)"),
295
- administrationId: z.string().optional().describe("Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var."),
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("administrationId is required (or set YUKI_DOMAIN_ID env var)");
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({ reference, subject, date, dueDate,
304
- contact, lines, paymentMethod, currency, process: doProcess,
305
- emailToCustomer, remarks });
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: "Sales.asmx",
309
- method: "ProcessSalesInvoices",
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: "text",
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: "text",
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("process_purchase_invoice", {
346
- description: "Book an incoming purchase invoice in Yuki. " +
347
- "Accepts invoice totals, supplier contact, and line items. " +
348
- "Optionally attach the original PDF as a base64 string. " +
349
- "Returns the Yuki response including the assigned document ID.",
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("Invoice date in YYYY-MM-DD format"),
353
- dueDate: z.string().optional().describe("Payment due date in YYYY-MM-DD format"),
354
- invoiceAmount: z.number().describe("Total invoice amount including VAT"),
355
- invoiceVatAmount: z.number().describe("Total VAT amount on the invoice"),
356
- contact: ContactSchema.describe("Supplier contact details"),
357
- lines: z.array(z.object({
358
- description: z.string().optional().describe("Line item description"),
359
- lineAmount: z.number().describe("Line amount excluding VAT"),
360
- lineVatType: z.number().optional().default(2)
361
- .describe("VAT type code: 1=None, 2=21% NL, 3=9% NL, 4=0% NL, 5=EU (see Yuki docs)"),
362
- lineVatPercentage: z.number().optional().describe("VAT percentage (e.g. 21). Derived from lineVatType if omitted."),
363
- glAccountCode: z.string().optional().describe("GL account code to post this line to (e.g. '4000' for cost of goods)"),
364
- vatDeductibilityPercentage: z.number().optional().default(100)
365
- .describe("Deductible VAT percentage (0-100). Use <100 for partially deductible costs."),
366
- })).min(1).describe("Invoice line items"),
367
- paymentMethod: z.enum(["Transfer", "DirectCollection", "Cash", "DebitCard", "CreditCard"])
368
- .optional().default("Transfer"),
369
- currency: z.string().optional().default("EUR"),
370
- process: z.boolean().optional().default(true).describe("Immediately process/finalise in Yuki"),
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("PDF content encoded as base64 string"),
402
+ documentBase64: z.string().optional().describe('PDF content encoded as base64 string'),
373
403
  remarks: z.string().optional(),
374
- administrationId: z.string().optional().describe("Administration ID (GUID). Defaults to YUKI_DOMAIN_ID env var."),
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("administrationId is required (or set YUKI_DOMAIN_ID env var)");
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({ reference, date, dueDate,
383
- invoiceAmount, invoiceVatAmount, contact, lines, paymentMethod,
384
- currency, process: doProcess, documentFileName, documentBase64, remarks });
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: "Purchase.asmx",
388
- method: "ProcessPurchaseInvoices",
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: "text",
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: "text",
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.map(line => `
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 ?? "NL_Normal")}</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>`).join("");
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 ?? "ElectronicTransfer")}</PaymentMethod>
438
- <Currency>${x(args.currency ?? "EUR")}</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.map(line => `
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>`).join("");
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 ?? "EUR")}</Currency>
486
- <PaymentMethod>${x(args.paymentMethod ?? "Transfer")}</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
- "DebtorItems", "CreditorItems",
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)