@mulmoclaude/accounting-plugin 0.2.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/server/io.d.ts.map +1 -1
- package/dist/server/router.d.ts.map +1 -1
- package/dist/server/service.d.ts +2 -2
- package/dist/server/service.d.ts.map +1 -1
- package/dist/server/types.d.ts +7 -6
- package/dist/server/types.d.ts.map +1 -1
- package/dist/server.cjs +59 -21
- package/dist/server.cjs.map +1 -1
- package/dist/server.js +60 -22
- package/dist/server.js.map +1 -1
- package/dist/shared/fiscalYear.d.ts +19 -8
- package/dist/shared/fiscalYear.d.ts.map +1 -1
- package/dist/shared.cjs +55 -17
- package/dist/shared.cjs.map +1 -1
- package/dist/shared.js +54 -17
- package/dist/shared.js.map +1 -1
- package/dist/style.css +1 -1
- package/dist/vue/api.d.ts +10 -10
- package/dist/vue/api.d.ts.map +1 -1
- package/dist/vue/components/BookSettings.vue.d.ts.map +1 -1
- package/dist/vue/components/NewBookForm.vue.d.ts.map +1 -1
- package/dist/vue/lang/de.d.ts.map +1 -1
- package/dist/vue/lang/en.d.ts +0 -4
- package/dist/vue/lang/en.d.ts.map +1 -1
- package/dist/vue/lang/es.d.ts.map +1 -1
- package/dist/vue/lang/fr.d.ts.map +1 -1
- package/dist/vue/lang/index.d.ts +0 -4
- package/dist/vue/lang/index.d.ts.map +1 -1
- package/dist/vue/lang/ja.d.ts.map +1 -1
- package/dist/vue/lang/ko.d.ts.map +1 -1
- package/dist/vue/lang/ptBR.d.ts.map +1 -1
- package/dist/vue/lang/zh.d.ts.map +1 -1
- package/dist/vue.cjs +16 -48
- package/dist/vue.cjs.map +1 -1
- package/dist/vue.js +17 -49
- package/dist/vue.js.map +1 -1
- package/package.json +3 -3
package/dist/server.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ACCOUNTING_ACTIONS, ACCOUNTING_API, ACCOUNTING_BOOKS_CHANNEL, ACCOUNTING_DIRS, BOOK_EVENT_KINDS,
|
|
1
|
+
import { ACCOUNTING_ACTIONS, ACCOUNTING_API, ACCOUNTING_BOOKS_CHANNEL, ACCOUNTING_DIRS, BOOK_EVENT_KINDS, FISCAL_YEAR_END_MONTHS, SUPPORTED_COUNTRY_CODES, TIME_SERIES_GRANULARITIES, TIME_SERIES_METRICS, bookChannel, errorMessage, fiscalYearEndMonth, isFiscalYearEnd, isSupportedCountryCode, resolveFiscalYearEnd } from "./shared.js";
|
|
2
2
|
import { Router } from "express";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { promises } from "node:fs";
|
|
@@ -142,8 +142,27 @@ async function readJsonStrict(filePath) {
|
|
|
142
142
|
throw err;
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
|
+
/** Migrate a legacy calendar-quarter `fiscalYearEnd` token ("Q1".."Q4")
|
|
146
|
+
* to its closing-month number in memory so every downstream consumer
|
|
147
|
+
* (reports, time-series, the UI selects) sees one shape. Absent stays
|
|
148
|
+
* absent — the field is optional and resolves to the default on read;
|
|
149
|
+
* we don't stamp an explicit December onto a book that never chose one.
|
|
150
|
+
* Nothing is written back here (no auto-migrate on disk). */
|
|
151
|
+
function normalizeBookFiscalYearEnd(book) {
|
|
152
|
+
if (book.fiscalYearEnd === void 0) return book;
|
|
153
|
+
const resolved = resolveFiscalYearEnd(book.fiscalYearEnd);
|
|
154
|
+
return book.fiscalYearEnd === resolved ? book : {
|
|
155
|
+
...book,
|
|
156
|
+
fiscalYearEnd: resolved
|
|
157
|
+
};
|
|
158
|
+
}
|
|
145
159
|
async function readConfig(workspaceRoot) {
|
|
146
|
-
|
|
160
|
+
const config = await readJsonStrict(configPath(workspaceRoot));
|
|
161
|
+
if (!config) return null;
|
|
162
|
+
return {
|
|
163
|
+
...config,
|
|
164
|
+
books: config.books.map(normalizeBookFiscalYearEnd)
|
|
165
|
+
};
|
|
147
166
|
}
|
|
148
167
|
async function writeConfig(config, workspaceRoot) {
|
|
149
168
|
await writeJsonAtomic(configPath(workspaceRoot), config);
|
|
@@ -1464,29 +1483,47 @@ function unsupportedCountryError(received) {
|
|
|
1464
1483
|
return new AccountingError(400, `unsupported country code ${JSON.stringify(received)} — must be one of: ${SUPPORTED_COUNTRY_CODES.join(", ")}`);
|
|
1465
1484
|
}
|
|
1466
1485
|
function unsupportedFiscalYearEndError(received) {
|
|
1467
|
-
return new AccountingError(400, `unsupported fiscalYearEnd ${JSON.stringify(received)} — must be
|
|
1468
|
-
}
|
|
1469
|
-
/**
|
|
1470
|
-
* (
|
|
1471
|
-
*
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
*
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1486
|
+
return new AccountingError(400, `unsupported fiscalYearEnd ${JSON.stringify(received)} — must be a closing-month number ${FISCAL_YEAR_END_MONTHS.join(", ")} (1 = January … 12 = December)`);
|
|
1487
|
+
}
|
|
1488
|
+
/** Coerce + validate a free-form `fiscalYearEnd` from any ingress path
|
|
1489
|
+
* (REST body, MCP tool args, direct callers). The service is the
|
|
1490
|
+
* validation boundary, so this is deliberately tolerant of input SHAPE
|
|
1491
|
+
* but strict about the resulting value:
|
|
1492
|
+
* - absent / null / empty string → `undefined` (field omitted; the
|
|
1493
|
+
* caller decides default-vs-no-op);
|
|
1494
|
+
* - a number, or a numeric string ("8") from a hand-rolled client →
|
|
1495
|
+
* that month;
|
|
1496
|
+
* - a legacy calendar-quarter token ("Q1".."Q4") from a stale client
|
|
1497
|
+
* → its closing month (same Q1→3 mapping the read side applies);
|
|
1498
|
+
* - anything else non-empty (a typo, garbage, an out-of-range or
|
|
1499
|
+
* non-integer number) → 400, echoing the ORIGINAL value so the
|
|
1500
|
+
* bad payload can't be silently mistaken for the default. */
|
|
1501
|
+
function coerceFiscalYearEndInput(raw) {
|
|
1502
|
+
if (raw === void 0 || raw === null || raw === "") return void 0;
|
|
1503
|
+
let month = raw;
|
|
1504
|
+
if (typeof raw === "string") {
|
|
1505
|
+
const trimmed = raw.trim();
|
|
1506
|
+
if (trimmed === "") return void 0;
|
|
1507
|
+
if (/^Q[1-4]$/.test(trimmed)) return resolveFiscalYearEnd(trimmed);
|
|
1508
|
+
month = /^-?\d+$/.test(trimmed) ? Number(trimmed) : NaN;
|
|
1509
|
+
}
|
|
1510
|
+
if (!isFiscalYearEnd(month)) throw unsupportedFiscalYearEndError(raw);
|
|
1511
|
+
return month;
|
|
1512
|
+
}
|
|
1513
|
+
/** Boundary checks shared by updateBook (name / country only —
|
|
1514
|
+
* fiscalYearEnd is coerced + validated separately via
|
|
1515
|
+
* `coerceFiscalYearEndInput`). Throws on the first failure so the
|
|
1516
|
+
* surrounding function stays under the cognitive-complexity threshold;
|
|
1517
|
+
* each rule is also unit-testable independently via the service entry
|
|
1518
|
+
* point. */
|
|
1481
1519
|
function validateUpdateBookInput(input) {
|
|
1482
1520
|
if (input.name !== void 0 && (typeof input.name !== "string" || input.name.trim() === "")) throw new AccountingError(400, "name must be a non-empty string when supplied");
|
|
1483
1521
|
if (input.country !== void 0 && input.country !== "" && !isSupportedCountryCode(input.country)) throw unsupportedCountryError(input.country);
|
|
1484
|
-
if (input.fiscalYearEnd !== void 0 && input.fiscalYearEnd !== "" && !isFiscalYearEnd(input.fiscalYearEnd)) throw unsupportedFiscalYearEndError(input.fiscalYearEnd);
|
|
1485
1522
|
}
|
|
1486
1523
|
async function createBook(input, workspaceRoot) {
|
|
1487
1524
|
if (typeof input.name !== "string" || input.name.trim() === "") throw new AccountingError(400, "name is required");
|
|
1488
1525
|
if (input.country !== void 0 && !isSupportedCountryCode(input.country)) throw unsupportedCountryError(input.country);
|
|
1489
|
-
const fiscalYearEnd =
|
|
1526
|
+
const fiscalYearEnd = coerceFiscalYearEndInput(input.fiscalYearEnd) ?? 12;
|
|
1490
1527
|
const config = await loadOrInitConfig(workspaceRoot);
|
|
1491
1528
|
const bookId = input.id ?? await generateBookId(config, workspaceRoot);
|
|
1492
1529
|
if (!isSafeBookId(bookId)) throw new AccountingError(400, `invalid book id ${JSON.stringify(bookId)} — allowed characters are A-Z a-z 0-9 _ - (1-64 chars; cannot start with _ or -)`);
|
|
@@ -1511,11 +1548,12 @@ async function updateBook(input, workspaceRoot) {
|
|
|
1511
1548
|
const target = findBook(config, input.bookId);
|
|
1512
1549
|
if (!target) throw new AccountingError(404, `book ${JSON.stringify(input.bookId)} not found`);
|
|
1513
1550
|
validateUpdateBookInput(input);
|
|
1551
|
+
const fiscalYearEnd = coerceFiscalYearEndInput(input.fiscalYearEnd);
|
|
1514
1552
|
const next = {
|
|
1515
1553
|
...target,
|
|
1516
1554
|
...input.name !== void 0 ? { name: input.name } : {},
|
|
1517
1555
|
...input.country !== void 0 && input.country !== "" ? { country: input.country } : {},
|
|
1518
|
-
...
|
|
1556
|
+
...fiscalYearEnd !== void 0 ? { fiscalYearEnd } : {}
|
|
1519
1557
|
};
|
|
1520
1558
|
if (input.country === "") delete next.country;
|
|
1521
1559
|
await writeConfig({ books: config.books.map((book) => book.id === input.bookId ? next : book) }, workspaceRoot);
|
|
@@ -1800,7 +1838,7 @@ async function loadTimeSeriesBookContext(requestedBookId, workspaceRoot) {
|
|
|
1800
1838
|
const bookId = resolveBookId(config, requestedBookId);
|
|
1801
1839
|
return {
|
|
1802
1840
|
bookId,
|
|
1803
|
-
fiscalYearEnd: findBook(config, bookId)?.fiscalYearEnd
|
|
1841
|
+
fiscalYearEnd: resolveFiscalYearEnd(findBook(config, bookId)?.fiscalYearEnd),
|
|
1804
1842
|
accounts: await readAccounts(bookId, workspaceRoot)
|
|
1805
1843
|
};
|
|
1806
1844
|
}
|
|
@@ -1911,7 +1949,7 @@ var ACTION_HANDLERS = {
|
|
|
1911
1949
|
name: String(rest.name ?? ""),
|
|
1912
1950
|
currency: typeof rest.currency === "string" ? rest.currency : void 0,
|
|
1913
1951
|
country: typeof rest.country === "string" ? rest.country : void 0,
|
|
1914
|
-
fiscalYearEnd:
|
|
1952
|
+
fiscalYearEnd: rest.fiscalYearEnd
|
|
1915
1953
|
});
|
|
1916
1954
|
return {
|
|
1917
1955
|
bookId: result.book.id,
|
|
@@ -1923,7 +1961,7 @@ var ACTION_HANDLERS = {
|
|
|
1923
1961
|
bookId: String(rest.bookId ?? ""),
|
|
1924
1962
|
name: typeof rest.name === "string" ? rest.name : void 0,
|
|
1925
1963
|
country: typeof rest.country === "string" ? rest.country : void 0,
|
|
1926
|
-
fiscalYearEnd:
|
|
1964
|
+
fiscalYearEnd: rest.fiscalYearEnd
|
|
1927
1965
|
});
|
|
1928
1966
|
return {
|
|
1929
1967
|
bookId: result.book.id,
|