@hasna/invoices 0.1.3 → 0.1.5
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/cli/index.js +65 -10
- package/dist/db/invoices.d.ts.map +1 -1
- package/dist/index.js +72 -10
- package/dist/lib/version.d.ts +1 -1
- package/dist/mcp/index.js +61 -8
- package/dist/server/index.js +61 -8
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2088,6 +2088,21 @@ function normalizePagination(options, defaults) {
|
|
|
2088
2088
|
const offset = Number.isFinite(options.offset) ? Math.max(Math.trunc(options.offset), 0) : 0;
|
|
2089
2089
|
return { limit, offset };
|
|
2090
2090
|
}
|
|
2091
|
+
function assertInvoiceStatus(status) {
|
|
2092
|
+
if (!INVOICE_STATUSES.has(status)) {
|
|
2093
|
+
throw new RangeError(`Invalid invoice status: ${String(status)}`);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
function assertPartyKind(kind) {
|
|
2097
|
+
if (!PARTY_KINDS.has(kind)) {
|
|
2098
|
+
throw new RangeError(`Invalid party kind: ${String(kind)}`);
|
|
2099
|
+
}
|
|
2100
|
+
}
|
|
2101
|
+
function assertNonBlankString(value, field) {
|
|
2102
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
2103
|
+
throw new TypeError(`${field} must not be blank`);
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2091
2106
|
function assertSafeCents(value, field) {
|
|
2092
2107
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
2093
2108
|
throw new RangeError(`${field} must be a safe nonnegative integer`);
|
|
@@ -2205,14 +2220,27 @@ function mapInvoiceLineRow(row) {
|
|
|
2205
2220
|
};
|
|
2206
2221
|
}
|
|
2207
2222
|
function createParty(db, input) {
|
|
2208
|
-
const id = input.id
|
|
2223
|
+
const id = input.id;
|
|
2224
|
+
const kind = input.kind;
|
|
2225
|
+
const legalName = input.legalName;
|
|
2226
|
+
const email = input.email;
|
|
2227
|
+
const taxId = input.taxId;
|
|
2228
|
+
const country = input.country;
|
|
2229
|
+
const address = input.address;
|
|
2230
|
+
if (id !== undefined) {
|
|
2231
|
+
assertNonBlankString(id, "id");
|
|
2232
|
+
}
|
|
2233
|
+
assertPartyKind(kind);
|
|
2234
|
+
assertNonBlankString(legalName, "legalName");
|
|
2235
|
+
const partyId = id ?? randomUUID();
|
|
2209
2236
|
db.query(`
|
|
2210
2237
|
INSERT INTO parties (id, kind, legal_name, email, tax_id, country, address)
|
|
2211
2238
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
|
2212
|
-
`).run(
|
|
2213
|
-
return getPartyById(db,
|
|
2239
|
+
`).run(partyId, kind, legalName, email ?? null, taxId ?? null, country ?? null, address ?? null);
|
|
2240
|
+
return getPartyById(db, partyId);
|
|
2214
2241
|
}
|
|
2215
2242
|
function getPartyById(db, id) {
|
|
2243
|
+
assertNonBlankString(id, "id");
|
|
2216
2244
|
const row = db.query("SELECT * FROM parties WHERE id = ?1").get(id);
|
|
2217
2245
|
return row ? mapPartyRow(row) : null;
|
|
2218
2246
|
}
|
|
@@ -2223,8 +2251,28 @@ function listParties(db, kind) {
|
|
|
2223
2251
|
return db.query("SELECT * FROM parties ORDER BY legal_name ASC").all().map(mapPartyRow);
|
|
2224
2252
|
}
|
|
2225
2253
|
function createInvoice(db, input) {
|
|
2226
|
-
const
|
|
2227
|
-
const
|
|
2254
|
+
const id = input.id;
|
|
2255
|
+
const number = input.number;
|
|
2256
|
+
const issuerId = input.issuerId;
|
|
2257
|
+
const customerId = input.customerId;
|
|
2258
|
+
const currency = input.currency;
|
|
2259
|
+
const notes = input.notes;
|
|
2260
|
+
const issuedAt = input.issuedAt;
|
|
2261
|
+
const dueAt = input.dueAt;
|
|
2262
|
+
const lines = input.lines;
|
|
2263
|
+
if (id !== undefined) {
|
|
2264
|
+
assertNonBlankString(id, "id");
|
|
2265
|
+
}
|
|
2266
|
+
assertNonBlankString(number, "number");
|
|
2267
|
+
assertNonBlankString(issuerId, "issuerId");
|
|
2268
|
+
assertNonBlankString(customerId, "customerId");
|
|
2269
|
+
assertNonBlankString(currency, "currency");
|
|
2270
|
+
assertNonBlankString(issuedAt, "issuedAt");
|
|
2271
|
+
if (dueAt !== undefined) {
|
|
2272
|
+
assertNonBlankString(dueAt, "dueAt");
|
|
2273
|
+
}
|
|
2274
|
+
const preparedLines = prepareInvoiceLines(lines);
|
|
2275
|
+
const invoiceId = id ?? randomUUID();
|
|
2228
2276
|
let subtotalCents = 0;
|
|
2229
2277
|
let taxCents = 0;
|
|
2230
2278
|
for (const line of preparedLines) {
|
|
@@ -2241,7 +2289,7 @@ INSERT INTO invoices (
|
|
|
2241
2289
|
id, number, issuer_id, customer_id, status, currency, notes, issued_at, due_at, subtotal_cents, tax_cents, total_cents
|
|
2242
2290
|
)
|
|
2243
2291
|
VALUES (?1, ?2, ?3, ?4, 'draft', ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
|
2244
|
-
`).run(invoiceId,
|
|
2292
|
+
`).run(invoiceId, number, issuerId, customerId, currency, notes ?? null, issuedAt, dueAt ?? null, subtotalCents, taxCents, totalCents);
|
|
2245
2293
|
const insertLine = db.query(`
|
|
2246
2294
|
INSERT INTO invoice_lines (
|
|
2247
2295
|
id, invoice_id, position, description, quantity, unit_price_cents, tax_rate_basis_points, line_total_cents
|
|
@@ -2267,12 +2315,14 @@ function getInvoiceById(db, id) {
|
|
|
2267
2315
|
}
|
|
2268
2316
|
function listInvoices(db, options = {}) {
|
|
2269
2317
|
const { limit, offset } = normalizePagination(options, { limit: 50 });
|
|
2270
|
-
if (options.status) {
|
|
2318
|
+
if (options.status !== undefined) {
|
|
2319
|
+
assertInvoiceStatus(options.status);
|
|
2271
2320
|
return db.query("SELECT * FROM invoices WHERE status = ?1 ORDER BY issued_at DESC, created_at DESC LIMIT ?2 OFFSET ?3").all(options.status, limit, offset).map(mapInvoiceRow);
|
|
2272
2321
|
}
|
|
2273
2322
|
return db.query("SELECT * FROM invoices ORDER BY issued_at DESC, created_at DESC LIMIT ?1 OFFSET ?2").all(limit, offset).map(mapInvoiceRow);
|
|
2274
2323
|
}
|
|
2275
2324
|
function updateInvoiceStatus(db, id, status) {
|
|
2325
|
+
assertInvoiceStatus(status);
|
|
2276
2326
|
db.query("UPDATE invoices SET status = ?2, updated_at = datetime('now') WHERE id = ?1").run(id, status);
|
|
2277
2327
|
const row = db.query("SELECT * FROM invoices WHERE id = ?1").get(id);
|
|
2278
2328
|
return row ? mapInvoiceRow(row) : null;
|
|
@@ -2280,7 +2330,8 @@ function updateInvoiceStatus(db, id, status) {
|
|
|
2280
2330
|
function searchInvoices(db, query, options = {}) {
|
|
2281
2331
|
const { limit, offset } = normalizePagination(options, { limit: 20 });
|
|
2282
2332
|
const escapedPhrase = `"${query.replace(/"/g, '""')}"`;
|
|
2283
|
-
if (options.status) {
|
|
2333
|
+
if (options.status !== undefined) {
|
|
2334
|
+
assertInvoiceStatus(options.status);
|
|
2284
2335
|
return db.query(`
|
|
2285
2336
|
SELECT i.*
|
|
2286
2337
|
FROM invoices_fts f
|
|
@@ -2302,7 +2353,11 @@ LIMIT ?2
|
|
|
2302
2353
|
OFFSET ?3
|
|
2303
2354
|
`).all(escapedPhrase, limit, offset).map(mapInvoiceRow);
|
|
2304
2355
|
}
|
|
2305
|
-
var
|
|
2356
|
+
var INVOICE_STATUSES, PARTY_KINDS;
|
|
2357
|
+
var init_invoices = __esm(() => {
|
|
2358
|
+
INVOICE_STATUSES = new Set(["draft", "sent", "partially_paid", "paid", "void", "overdue"]);
|
|
2359
|
+
PARTY_KINDS = new Set(["issuer", "customer"]);
|
|
2360
|
+
});
|
|
2306
2361
|
|
|
2307
2362
|
// src/db/database.ts
|
|
2308
2363
|
import { mkdirSync } from "fs";
|
|
@@ -2471,7 +2526,7 @@ function listAgents(db) {
|
|
|
2471
2526
|
}
|
|
2472
2527
|
|
|
2473
2528
|
// src/lib/version.ts
|
|
2474
|
-
var VERSION = "0.
|
|
2529
|
+
var VERSION = "0.1.5";
|
|
2475
2530
|
|
|
2476
2531
|
// src/cli/index.ts
|
|
2477
2532
|
init_database();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"invoices.d.ts","sourceRoot":"","sources":["../../src/db/invoices.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAG3C,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,QAAQ,GAAG,UAAU,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,OAAO,GAAG,MAAM,GAAG,gBAAgB,GAAG,MAAM,GAAG,MAAM,GAAG,SAAS,CAAC;IAC1E,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,gBAAiB,SAAQ,aAAa;IACrD,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,mBAAmB,EAAE,CAAC;CAC9B;
|
|
1
|
+
{"version":3,"file":"invoices.d.ts","sourceRoot":"","sources":["../../src/db/invoices.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
// src/lib/version.ts
|
|
3
|
-
var VERSION = "0.
|
|
3
|
+
var VERSION = "0.1.5";
|
|
4
4
|
// src/db/database.ts
|
|
5
5
|
import { mkdirSync } from "fs";
|
|
6
6
|
import { dirname, join } from "path";
|
|
@@ -205,11 +205,28 @@ function migrateDatabase(options = {}) {
|
|
|
205
205
|
}
|
|
206
206
|
// src/db/invoices.ts
|
|
207
207
|
import { randomUUID } from "crypto";
|
|
208
|
+
var INVOICE_STATUSES = new Set(["draft", "sent", "partially_paid", "paid", "void", "overdue"]);
|
|
209
|
+
var PARTY_KINDS = new Set(["issuer", "customer"]);
|
|
208
210
|
function normalizePagination(options, defaults) {
|
|
209
211
|
const limit = Number.isFinite(options.limit) ? Math.max(Math.trunc(options.limit), 0) : defaults.limit;
|
|
210
212
|
const offset = Number.isFinite(options.offset) ? Math.max(Math.trunc(options.offset), 0) : 0;
|
|
211
213
|
return { limit, offset };
|
|
212
214
|
}
|
|
215
|
+
function assertInvoiceStatus(status) {
|
|
216
|
+
if (!INVOICE_STATUSES.has(status)) {
|
|
217
|
+
throw new RangeError(`Invalid invoice status: ${String(status)}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
function assertPartyKind(kind) {
|
|
221
|
+
if (!PARTY_KINDS.has(kind)) {
|
|
222
|
+
throw new RangeError(`Invalid party kind: ${String(kind)}`);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
function assertNonBlankString(value, field) {
|
|
226
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
227
|
+
throw new TypeError(`${field} must not be blank`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
213
230
|
function assertSafeCents(value, field) {
|
|
214
231
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
215
232
|
throw new RangeError(`${field} must be a safe nonnegative integer`);
|
|
@@ -327,14 +344,35 @@ function mapInvoiceLineRow(row) {
|
|
|
327
344
|
};
|
|
328
345
|
}
|
|
329
346
|
function createParty(db, input) {
|
|
330
|
-
const id = input.id
|
|
347
|
+
const id = input.id;
|
|
348
|
+
const kind = input.kind;
|
|
349
|
+
const legalName = input.legalName;
|
|
350
|
+
const email = input.email;
|
|
351
|
+
const taxId = input.taxId;
|
|
352
|
+
const country = input.country;
|
|
353
|
+
const address = input.address;
|
|
354
|
+
if (id !== undefined) {
|
|
355
|
+
assertNonBlankString(id, "id");
|
|
356
|
+
}
|
|
357
|
+
assertPartyKind(kind);
|
|
358
|
+
assertNonBlankString(legalName, "legalName");
|
|
359
|
+
const partyId = id ?? randomUUID();
|
|
331
360
|
db.query(`
|
|
332
361
|
INSERT INTO parties (id, kind, legal_name, email, tax_id, country, address)
|
|
333
362
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
|
334
|
-
`).run(
|
|
335
|
-
return getPartyById(db,
|
|
363
|
+
`).run(partyId, kind, legalName, email ?? null, taxId ?? null, country ?? null, address ?? null);
|
|
364
|
+
return getPartyById(db, partyId);
|
|
336
365
|
}
|
|
337
366
|
function updateParty(db, id, updates) {
|
|
367
|
+
const legalName = updates.legalName;
|
|
368
|
+
const email = updates.email;
|
|
369
|
+
const taxId = updates.taxId;
|
|
370
|
+
const country = updates.country;
|
|
371
|
+
const address = updates.address;
|
|
372
|
+
assertNonBlankString(id, "id");
|
|
373
|
+
if (legalName !== undefined) {
|
|
374
|
+
assertNonBlankString(legalName, "legalName");
|
|
375
|
+
}
|
|
338
376
|
db.query(`
|
|
339
377
|
UPDATE parties
|
|
340
378
|
SET legal_name = COALESCE(?2, legal_name),
|
|
@@ -344,10 +382,11 @@ SET legal_name = COALESCE(?2, legal_name),
|
|
|
344
382
|
address = COALESCE(?6, address),
|
|
345
383
|
updated_at = datetime('now')
|
|
346
384
|
WHERE id = ?1
|
|
347
|
-
`).run(id,
|
|
385
|
+
`).run(id, legalName ?? null, email ?? null, taxId ?? null, country ?? null, address ?? null);
|
|
348
386
|
return getPartyById(db, id);
|
|
349
387
|
}
|
|
350
388
|
function getPartyById(db, id) {
|
|
389
|
+
assertNonBlankString(id, "id");
|
|
351
390
|
const row = db.query("SELECT * FROM parties WHERE id = ?1").get(id);
|
|
352
391
|
return row ? mapPartyRow(row) : null;
|
|
353
392
|
}
|
|
@@ -358,8 +397,28 @@ function listParties(db, kind) {
|
|
|
358
397
|
return db.query("SELECT * FROM parties ORDER BY legal_name ASC").all().map(mapPartyRow);
|
|
359
398
|
}
|
|
360
399
|
function createInvoice(db, input) {
|
|
361
|
-
const
|
|
362
|
-
const
|
|
400
|
+
const id = input.id;
|
|
401
|
+
const number = input.number;
|
|
402
|
+
const issuerId = input.issuerId;
|
|
403
|
+
const customerId = input.customerId;
|
|
404
|
+
const currency = input.currency;
|
|
405
|
+
const notes = input.notes;
|
|
406
|
+
const issuedAt = input.issuedAt;
|
|
407
|
+
const dueAt = input.dueAt;
|
|
408
|
+
const lines = input.lines;
|
|
409
|
+
if (id !== undefined) {
|
|
410
|
+
assertNonBlankString(id, "id");
|
|
411
|
+
}
|
|
412
|
+
assertNonBlankString(number, "number");
|
|
413
|
+
assertNonBlankString(issuerId, "issuerId");
|
|
414
|
+
assertNonBlankString(customerId, "customerId");
|
|
415
|
+
assertNonBlankString(currency, "currency");
|
|
416
|
+
assertNonBlankString(issuedAt, "issuedAt");
|
|
417
|
+
if (dueAt !== undefined) {
|
|
418
|
+
assertNonBlankString(dueAt, "dueAt");
|
|
419
|
+
}
|
|
420
|
+
const preparedLines = prepareInvoiceLines(lines);
|
|
421
|
+
const invoiceId = id ?? randomUUID();
|
|
363
422
|
let subtotalCents = 0;
|
|
364
423
|
let taxCents = 0;
|
|
365
424
|
for (const line of preparedLines) {
|
|
@@ -376,7 +435,7 @@ INSERT INTO invoices (
|
|
|
376
435
|
id, number, issuer_id, customer_id, status, currency, notes, issued_at, due_at, subtotal_cents, tax_cents, total_cents
|
|
377
436
|
)
|
|
378
437
|
VALUES (?1, ?2, ?3, ?4, 'draft', ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
|
379
|
-
`).run(invoiceId,
|
|
438
|
+
`).run(invoiceId, number, issuerId, customerId, currency, notes ?? null, issuedAt, dueAt ?? null, subtotalCents, taxCents, totalCents);
|
|
380
439
|
const insertLine = db.query(`
|
|
381
440
|
INSERT INTO invoice_lines (
|
|
382
441
|
id, invoice_id, position, description, quantity, unit_price_cents, tax_rate_basis_points, line_total_cents
|
|
@@ -402,12 +461,14 @@ function getInvoiceById(db, id) {
|
|
|
402
461
|
}
|
|
403
462
|
function listInvoices(db, options = {}) {
|
|
404
463
|
const { limit, offset } = normalizePagination(options, { limit: 50 });
|
|
405
|
-
if (options.status) {
|
|
464
|
+
if (options.status !== undefined) {
|
|
465
|
+
assertInvoiceStatus(options.status);
|
|
406
466
|
return db.query("SELECT * FROM invoices WHERE status = ?1 ORDER BY issued_at DESC, created_at DESC LIMIT ?2 OFFSET ?3").all(options.status, limit, offset).map(mapInvoiceRow);
|
|
407
467
|
}
|
|
408
468
|
return db.query("SELECT * FROM invoices ORDER BY issued_at DESC, created_at DESC LIMIT ?1 OFFSET ?2").all(limit, offset).map(mapInvoiceRow);
|
|
409
469
|
}
|
|
410
470
|
function updateInvoiceStatus(db, id, status) {
|
|
471
|
+
assertInvoiceStatus(status);
|
|
411
472
|
db.query("UPDATE invoices SET status = ?2, updated_at = datetime('now') WHERE id = ?1").run(id, status);
|
|
412
473
|
const row = db.query("SELECT * FROM invoices WHERE id = ?1").get(id);
|
|
413
474
|
return row ? mapInvoiceRow(row) : null;
|
|
@@ -432,7 +493,8 @@ FROM invoices i;
|
|
|
432
493
|
function searchInvoices(db, query, options = {}) {
|
|
433
494
|
const { limit, offset } = normalizePagination(options, { limit: 20 });
|
|
434
495
|
const escapedPhrase = `"${query.replace(/"/g, '""')}"`;
|
|
435
|
-
if (options.status) {
|
|
496
|
+
if (options.status !== undefined) {
|
|
497
|
+
assertInvoiceStatus(options.status);
|
|
436
498
|
return db.query(`
|
|
437
499
|
SELECT i.*
|
|
438
500
|
FROM invoices_fts f
|
package/dist/lib/version.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const VERSION = "0.
|
|
1
|
+
export declare const VERSION = "0.1.5";
|
|
2
2
|
//# sourceMappingURL=version.d.ts.map
|
package/dist/mcp/index.js
CHANGED
|
@@ -4201,11 +4201,28 @@ function migrateDatabase(options = {}) {
|
|
|
4201
4201
|
|
|
4202
4202
|
// src/db/invoices.ts
|
|
4203
4203
|
import { randomUUID } from "crypto";
|
|
4204
|
+
var INVOICE_STATUSES = new Set(["draft", "sent", "partially_paid", "paid", "void", "overdue"]);
|
|
4205
|
+
var PARTY_KINDS = new Set(["issuer", "customer"]);
|
|
4204
4206
|
function normalizePagination(options, defaults) {
|
|
4205
4207
|
const limit = Number.isFinite(options.limit) ? Math.max(Math.trunc(options.limit), 0) : defaults.limit;
|
|
4206
4208
|
const offset = Number.isFinite(options.offset) ? Math.max(Math.trunc(options.offset), 0) : 0;
|
|
4207
4209
|
return { limit, offset };
|
|
4208
4210
|
}
|
|
4211
|
+
function assertInvoiceStatus(status) {
|
|
4212
|
+
if (!INVOICE_STATUSES.has(status)) {
|
|
4213
|
+
throw new RangeError(`Invalid invoice status: ${String(status)}`);
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
function assertPartyKind(kind) {
|
|
4217
|
+
if (!PARTY_KINDS.has(kind)) {
|
|
4218
|
+
throw new RangeError(`Invalid party kind: ${String(kind)}`);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
function assertNonBlankString(value, field) {
|
|
4222
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4223
|
+
throw new TypeError(`${field} must not be blank`);
|
|
4224
|
+
}
|
|
4225
|
+
}
|
|
4209
4226
|
function assertSafeCents(value, field) {
|
|
4210
4227
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4211
4228
|
throw new RangeError(`${field} must be a safe nonnegative integer`);
|
|
@@ -4323,14 +4340,27 @@ function mapInvoiceLineRow(row) {
|
|
|
4323
4340
|
};
|
|
4324
4341
|
}
|
|
4325
4342
|
function createParty(db, input) {
|
|
4326
|
-
const id = input.id
|
|
4343
|
+
const id = input.id;
|
|
4344
|
+
const kind = input.kind;
|
|
4345
|
+
const legalName = input.legalName;
|
|
4346
|
+
const email = input.email;
|
|
4347
|
+
const taxId = input.taxId;
|
|
4348
|
+
const country = input.country;
|
|
4349
|
+
const address = input.address;
|
|
4350
|
+
if (id !== undefined) {
|
|
4351
|
+
assertNonBlankString(id, "id");
|
|
4352
|
+
}
|
|
4353
|
+
assertPartyKind(kind);
|
|
4354
|
+
assertNonBlankString(legalName, "legalName");
|
|
4355
|
+
const partyId = id ?? randomUUID();
|
|
4327
4356
|
db.query(`
|
|
4328
4357
|
INSERT INTO parties (id, kind, legal_name, email, tax_id, country, address)
|
|
4329
4358
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
|
4330
|
-
`).run(
|
|
4331
|
-
return getPartyById(db,
|
|
4359
|
+
`).run(partyId, kind, legalName, email ?? null, taxId ?? null, country ?? null, address ?? null);
|
|
4360
|
+
return getPartyById(db, partyId);
|
|
4332
4361
|
}
|
|
4333
4362
|
function getPartyById(db, id) {
|
|
4363
|
+
assertNonBlankString(id, "id");
|
|
4334
4364
|
const row = db.query("SELECT * FROM parties WHERE id = ?1").get(id);
|
|
4335
4365
|
return row ? mapPartyRow(row) : null;
|
|
4336
4366
|
}
|
|
@@ -4341,8 +4371,28 @@ function listParties(db, kind) {
|
|
|
4341
4371
|
return db.query("SELECT * FROM parties ORDER BY legal_name ASC").all().map(mapPartyRow);
|
|
4342
4372
|
}
|
|
4343
4373
|
function createInvoice(db, input) {
|
|
4344
|
-
const
|
|
4345
|
-
const
|
|
4374
|
+
const id = input.id;
|
|
4375
|
+
const number = input.number;
|
|
4376
|
+
const issuerId = input.issuerId;
|
|
4377
|
+
const customerId = input.customerId;
|
|
4378
|
+
const currency = input.currency;
|
|
4379
|
+
const notes = input.notes;
|
|
4380
|
+
const issuedAt = input.issuedAt;
|
|
4381
|
+
const dueAt = input.dueAt;
|
|
4382
|
+
const lines = input.lines;
|
|
4383
|
+
if (id !== undefined) {
|
|
4384
|
+
assertNonBlankString(id, "id");
|
|
4385
|
+
}
|
|
4386
|
+
assertNonBlankString(number, "number");
|
|
4387
|
+
assertNonBlankString(issuerId, "issuerId");
|
|
4388
|
+
assertNonBlankString(customerId, "customerId");
|
|
4389
|
+
assertNonBlankString(currency, "currency");
|
|
4390
|
+
assertNonBlankString(issuedAt, "issuedAt");
|
|
4391
|
+
if (dueAt !== undefined) {
|
|
4392
|
+
assertNonBlankString(dueAt, "dueAt");
|
|
4393
|
+
}
|
|
4394
|
+
const preparedLines = prepareInvoiceLines(lines);
|
|
4395
|
+
const invoiceId = id ?? randomUUID();
|
|
4346
4396
|
let subtotalCents = 0;
|
|
4347
4397
|
let taxCents = 0;
|
|
4348
4398
|
for (const line of preparedLines) {
|
|
@@ -4359,7 +4409,7 @@ INSERT INTO invoices (
|
|
|
4359
4409
|
id, number, issuer_id, customer_id, status, currency, notes, issued_at, due_at, subtotal_cents, tax_cents, total_cents
|
|
4360
4410
|
)
|
|
4361
4411
|
VALUES (?1, ?2, ?3, ?4, 'draft', ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
|
4362
|
-
`).run(invoiceId,
|
|
4412
|
+
`).run(invoiceId, number, issuerId, customerId, currency, notes ?? null, issuedAt, dueAt ?? null, subtotalCents, taxCents, totalCents);
|
|
4363
4413
|
const insertLine = db.query(`
|
|
4364
4414
|
INSERT INTO invoice_lines (
|
|
4365
4415
|
id, invoice_id, position, description, quantity, unit_price_cents, tax_rate_basis_points, line_total_cents
|
|
@@ -4385,12 +4435,14 @@ function getInvoiceById(db, id) {
|
|
|
4385
4435
|
}
|
|
4386
4436
|
function listInvoices(db, options = {}) {
|
|
4387
4437
|
const { limit, offset } = normalizePagination(options, { limit: 50 });
|
|
4388
|
-
if (options.status) {
|
|
4438
|
+
if (options.status !== undefined) {
|
|
4439
|
+
assertInvoiceStatus(options.status);
|
|
4389
4440
|
return db.query("SELECT * FROM invoices WHERE status = ?1 ORDER BY issued_at DESC, created_at DESC LIMIT ?2 OFFSET ?3").all(options.status, limit, offset).map(mapInvoiceRow);
|
|
4390
4441
|
}
|
|
4391
4442
|
return db.query("SELECT * FROM invoices ORDER BY issued_at DESC, created_at DESC LIMIT ?1 OFFSET ?2").all(limit, offset).map(mapInvoiceRow);
|
|
4392
4443
|
}
|
|
4393
4444
|
function updateInvoiceStatus(db, id, status) {
|
|
4445
|
+
assertInvoiceStatus(status);
|
|
4394
4446
|
db.query("UPDATE invoices SET status = ?2, updated_at = datetime('now') WHERE id = ?1").run(id, status);
|
|
4395
4447
|
const row = db.query("SELECT * FROM invoices WHERE id = ?1").get(id);
|
|
4396
4448
|
return row ? mapInvoiceRow(row) : null;
|
|
@@ -4402,7 +4454,8 @@ function deleteInvoice(db, id) {
|
|
|
4402
4454
|
function searchInvoices(db, query, options = {}) {
|
|
4403
4455
|
const { limit, offset } = normalizePagination(options, { limit: 20 });
|
|
4404
4456
|
const escapedPhrase = `"${query.replace(/"/g, '""')}"`;
|
|
4405
|
-
if (options.status) {
|
|
4457
|
+
if (options.status !== undefined) {
|
|
4458
|
+
assertInvoiceStatus(options.status);
|
|
4406
4459
|
return db.query(`
|
|
4407
4460
|
SELECT i.*
|
|
4408
4461
|
FROM invoices_fts f
|
package/dist/server/index.js
CHANGED
|
@@ -4009,11 +4009,28 @@ function openInvoiceDatabase(options = {}) {
|
|
|
4009
4009
|
|
|
4010
4010
|
// src/db/invoices.ts
|
|
4011
4011
|
import { randomUUID } from "crypto";
|
|
4012
|
+
var INVOICE_STATUSES = new Set(["draft", "sent", "partially_paid", "paid", "void", "overdue"]);
|
|
4013
|
+
var PARTY_KINDS = new Set(["issuer", "customer"]);
|
|
4012
4014
|
function normalizePagination(options, defaults) {
|
|
4013
4015
|
const limit = Number.isFinite(options.limit) ? Math.max(Math.trunc(options.limit), 0) : defaults.limit;
|
|
4014
4016
|
const offset = Number.isFinite(options.offset) ? Math.max(Math.trunc(options.offset), 0) : 0;
|
|
4015
4017
|
return { limit, offset };
|
|
4016
4018
|
}
|
|
4019
|
+
function assertInvoiceStatus(status) {
|
|
4020
|
+
if (!INVOICE_STATUSES.has(status)) {
|
|
4021
|
+
throw new RangeError(`Invalid invoice status: ${String(status)}`);
|
|
4022
|
+
}
|
|
4023
|
+
}
|
|
4024
|
+
function assertPartyKind(kind) {
|
|
4025
|
+
if (!PARTY_KINDS.has(kind)) {
|
|
4026
|
+
throw new RangeError(`Invalid party kind: ${String(kind)}`);
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
function assertNonBlankString(value, field) {
|
|
4030
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
4031
|
+
throw new TypeError(`${field} must not be blank`);
|
|
4032
|
+
}
|
|
4033
|
+
}
|
|
4017
4034
|
function assertSafeCents(value, field) {
|
|
4018
4035
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
4019
4036
|
throw new RangeError(`${field} must be a safe nonnegative integer`);
|
|
@@ -4131,14 +4148,27 @@ function mapInvoiceLineRow(row) {
|
|
|
4131
4148
|
};
|
|
4132
4149
|
}
|
|
4133
4150
|
function createParty(db, input) {
|
|
4134
|
-
const id = input.id
|
|
4151
|
+
const id = input.id;
|
|
4152
|
+
const kind = input.kind;
|
|
4153
|
+
const legalName = input.legalName;
|
|
4154
|
+
const email = input.email;
|
|
4155
|
+
const taxId = input.taxId;
|
|
4156
|
+
const country = input.country;
|
|
4157
|
+
const address = input.address;
|
|
4158
|
+
if (id !== undefined) {
|
|
4159
|
+
assertNonBlankString(id, "id");
|
|
4160
|
+
}
|
|
4161
|
+
assertPartyKind(kind);
|
|
4162
|
+
assertNonBlankString(legalName, "legalName");
|
|
4163
|
+
const partyId = id ?? randomUUID();
|
|
4135
4164
|
db.query(`
|
|
4136
4165
|
INSERT INTO parties (id, kind, legal_name, email, tax_id, country, address)
|
|
4137
4166
|
VALUES (?1, ?2, ?3, ?4, ?5, ?6, ?7)
|
|
4138
|
-
`).run(
|
|
4139
|
-
return getPartyById(db,
|
|
4167
|
+
`).run(partyId, kind, legalName, email ?? null, taxId ?? null, country ?? null, address ?? null);
|
|
4168
|
+
return getPartyById(db, partyId);
|
|
4140
4169
|
}
|
|
4141
4170
|
function getPartyById(db, id) {
|
|
4171
|
+
assertNonBlankString(id, "id");
|
|
4142
4172
|
const row = db.query("SELECT * FROM parties WHERE id = ?1").get(id);
|
|
4143
4173
|
return row ? mapPartyRow(row) : null;
|
|
4144
4174
|
}
|
|
@@ -4149,8 +4179,28 @@ function listParties(db, kind) {
|
|
|
4149
4179
|
return db.query("SELECT * FROM parties ORDER BY legal_name ASC").all().map(mapPartyRow);
|
|
4150
4180
|
}
|
|
4151
4181
|
function createInvoice(db, input) {
|
|
4152
|
-
const
|
|
4153
|
-
const
|
|
4182
|
+
const id = input.id;
|
|
4183
|
+
const number = input.number;
|
|
4184
|
+
const issuerId = input.issuerId;
|
|
4185
|
+
const customerId = input.customerId;
|
|
4186
|
+
const currency = input.currency;
|
|
4187
|
+
const notes = input.notes;
|
|
4188
|
+
const issuedAt = input.issuedAt;
|
|
4189
|
+
const dueAt = input.dueAt;
|
|
4190
|
+
const lines = input.lines;
|
|
4191
|
+
if (id !== undefined) {
|
|
4192
|
+
assertNonBlankString(id, "id");
|
|
4193
|
+
}
|
|
4194
|
+
assertNonBlankString(number, "number");
|
|
4195
|
+
assertNonBlankString(issuerId, "issuerId");
|
|
4196
|
+
assertNonBlankString(customerId, "customerId");
|
|
4197
|
+
assertNonBlankString(currency, "currency");
|
|
4198
|
+
assertNonBlankString(issuedAt, "issuedAt");
|
|
4199
|
+
if (dueAt !== undefined) {
|
|
4200
|
+
assertNonBlankString(dueAt, "dueAt");
|
|
4201
|
+
}
|
|
4202
|
+
const preparedLines = prepareInvoiceLines(lines);
|
|
4203
|
+
const invoiceId = id ?? randomUUID();
|
|
4154
4204
|
let subtotalCents = 0;
|
|
4155
4205
|
let taxCents = 0;
|
|
4156
4206
|
for (const line of preparedLines) {
|
|
@@ -4167,7 +4217,7 @@ INSERT INTO invoices (
|
|
|
4167
4217
|
id, number, issuer_id, customer_id, status, currency, notes, issued_at, due_at, subtotal_cents, tax_cents, total_cents
|
|
4168
4218
|
)
|
|
4169
4219
|
VALUES (?1, ?2, ?3, ?4, 'draft', ?5, ?6, ?7, ?8, ?9, ?10, ?11)
|
|
4170
|
-
`).run(invoiceId,
|
|
4220
|
+
`).run(invoiceId, number, issuerId, customerId, currency, notes ?? null, issuedAt, dueAt ?? null, subtotalCents, taxCents, totalCents);
|
|
4171
4221
|
const insertLine = db.query(`
|
|
4172
4222
|
INSERT INTO invoice_lines (
|
|
4173
4223
|
id, invoice_id, position, description, quantity, unit_price_cents, tax_rate_basis_points, line_total_cents
|
|
@@ -4193,12 +4243,14 @@ function getInvoiceById(db, id) {
|
|
|
4193
4243
|
}
|
|
4194
4244
|
function listInvoices(db, options = {}) {
|
|
4195
4245
|
const { limit, offset } = normalizePagination(options, { limit: 50 });
|
|
4196
|
-
if (options.status) {
|
|
4246
|
+
if (options.status !== undefined) {
|
|
4247
|
+
assertInvoiceStatus(options.status);
|
|
4197
4248
|
return db.query("SELECT * FROM invoices WHERE status = ?1 ORDER BY issued_at DESC, created_at DESC LIMIT ?2 OFFSET ?3").all(options.status, limit, offset).map(mapInvoiceRow);
|
|
4198
4249
|
}
|
|
4199
4250
|
return db.query("SELECT * FROM invoices ORDER BY issued_at DESC, created_at DESC LIMIT ?1 OFFSET ?2").all(limit, offset).map(mapInvoiceRow);
|
|
4200
4251
|
}
|
|
4201
4252
|
function updateInvoiceStatus(db, id, status) {
|
|
4253
|
+
assertInvoiceStatus(status);
|
|
4202
4254
|
db.query("UPDATE invoices SET status = ?2, updated_at = datetime('now') WHERE id = ?1").run(id, status);
|
|
4203
4255
|
const row = db.query("SELECT * FROM invoices WHERE id = ?1").get(id);
|
|
4204
4256
|
return row ? mapInvoiceRow(row) : null;
|
|
@@ -4206,7 +4258,8 @@ function updateInvoiceStatus(db, id, status) {
|
|
|
4206
4258
|
function searchInvoices(db, query, options = {}) {
|
|
4207
4259
|
const { limit, offset } = normalizePagination(options, { limit: 20 });
|
|
4208
4260
|
const escapedPhrase = `"${query.replace(/"/g, '""')}"`;
|
|
4209
|
-
if (options.status) {
|
|
4261
|
+
if (options.status !== undefined) {
|
|
4262
|
+
assertInvoiceStatus(options.status);
|
|
4210
4263
|
return db.query(`
|
|
4211
4264
|
SELECT i.*
|
|
4212
4265
|
FROM invoices_fts f
|