@kirjapro/mcp-server 0.10.0 → 0.11.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/CHANGELOG.md +57 -1
- package/dist/index.js +251 -54
- package/package.json +1 -1
- package/server.json +1 -1
- package/tool-fingerprints.json +7 -7
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,62 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## [0.
|
|
3
|
+
## [0.11.0] - Unreleased
|
|
4
|
+
|
|
5
|
+
Requires the database migrations `20261002070106`–`20261002071619` (ALV report
|
|
6
|
+
audit batch 2), in particular `book_purchase_with_vat`, and
|
|
7
|
+
`20261002120000`–`20261002120001` (batch 3, the OmaVero field map); release it
|
|
8
|
+
only after `db:deploy`. Version 0.10.0 keeps working against that database: its
|
|
9
|
+
standard purchases book plain vouchers without a VAT fact (listed by the ALV
|
|
10
|
+
reconciliation), the database applies the new sales-account rule, and its ALV
|
|
11
|
+
summary passes the new `omavero` key through. Against a database without the
|
|
12
|
+
batch 3 map, 0.11.0 derives the same fields from the older keys.
|
|
13
|
+
|
|
14
|
+
- `kirjapro_book_purchase`: for a VAT-registered company a purchase with a rate
|
|
15
|
+
above 0 is booked with `book_purchase_with_vat`, which writes the voucher and
|
|
16
|
+
its VAT fact together, so the VAT reaches field 307. `reverseCharge` works
|
|
17
|
+
again with the new `reverseChargeCategory` (`eu_goods`, `eu_services`,
|
|
18
|
+
`non_eu_services`, `import`, `construction`, `scrap_metal`) and a Finnish
|
|
19
|
+
rate. A company that is not VAT-registered is refused a rate above 0 or a
|
|
20
|
+
reverse charge and books the purchase gross with `vatRate` `0`; the refusal
|
|
21
|
+
says that the VAT on a service from a foreign seller is still owed and goes
|
|
22
|
+
on a non-deductible Ostojen ALV row in the web app and on a voucher D expense
|
|
23
|
+
/ K 4210 (Customs collects such a company's import VAT). New optional
|
|
24
|
+
`supplierName`; the result adds `vat_entry_ids`. The database refuses (P0001)
|
|
25
|
+
a deduction on law-based non-deductible accounts, a foreign rate and VAT rows
|
|
26
|
+
that do not match the 2320/4210 lines.
|
|
27
|
+
- `kirjapro_create_from_template`: a purchase template that still debits 2320
|
|
28
|
+
books its domestic VAT fact through `book_purchase_with_vat`; for a company
|
|
29
|
+
that is not VAT-registered it is refused. `kirjapro_create_from_template_batch`
|
|
30
|
+
refuses purchase templates with VAT before any write (the atomic batch cannot
|
|
31
|
+
write VAT facts). `kirjapro_list_templates` hides `purchase_expense` and
|
|
32
|
+
`service_expense` from a company that is not VAT-registered.
|
|
33
|
+
- `kirjapro_create_transaction(s_batch)` and the server instructions describe
|
|
34
|
+
the net sales-account rule (U-3): a net credit on 5000-5499, a sales line
|
|
35
|
+
with a 4210 or 2320 line and a net 4210 credit are refused; a transfer out of
|
|
36
|
+
a sales account without a VAT line, such as a fixed-asset disposal, is
|
|
37
|
+
allowed. They also say that a free 2320 line does not reach field 307.
|
|
38
|
+
- `kirjapro_get_alv_summary` documents `output_vat_non_eu_services`, the VAT on
|
|
39
|
+
services bought from outside the EU that fields 301-303 now include.
|
|
40
|
+
- `kirjapro_get_alv_summary` returns every official OmaVero field in
|
|
41
|
+
`omavero_fields` (VSRALVKV v1.10: 301-314 and 318-320) from the database's
|
|
42
|
+
`omavero` map: 307 is the total deductible VAT including reverse charge and
|
|
43
|
+
import VAT, 308 = 301+302+303+304+305+306+318 - 307, 309 excludes EU sales,
|
|
44
|
+
and 311 follows AVL 138 a §. The legacy keys `field_307`…`field_313` (and
|
|
45
|
+
their `_base` keys), `field_318`-`field_320`, `total_zero_rated` and
|
|
46
|
+
`reverse_charge_base` are no longer returned: `field_307` was only the
|
|
47
|
+
domestic deductible VAT and the others were purchase categories, not field
|
|
48
|
+
numbers. New `exempt_sales_base` (sales without a deduction right, in no
|
|
49
|
+
field); unclassified legacy reverse-charge invoice lines add a `warning`.
|
|
50
|
+
- `kirjapro://compliance/*`: field 309 excludes EU sales (311, 312) and exempt
|
|
51
|
+
sales; cash-basis VAT needs no OmaVero opt-in and leaves EU and import items
|
|
52
|
+
on the accrual basis; a correction within 45 days carries no late-filing fee
|
|
53
|
+
regardless of the amount; the reverse-charge field list names 301-303 for
|
|
54
|
+
non-EU services, 304/310 for imports and the base fields.
|
|
55
|
+
- Review the fingerprints of `kirjapro_book_purchase`,
|
|
56
|
+
`kirjapro_create_from_template(_batch)`, `kirjapro_list_templates`,
|
|
57
|
+
`kirjapro_create_transaction(s_batch)` and `kirjapro_get_alv_summary`.
|
|
58
|
+
|
|
59
|
+
## [0.10.0] - 2026-10-02
|
|
4
60
|
|
|
5
61
|
Requires the database migrations `20261001204555`–`20261001204559` (ALV report
|
|
6
62
|
audit batch 1). Version 0.9.1 keeps working against that database: its sales
|
package/dist/index.js
CHANGED
|
@@ -35679,7 +35679,7 @@ var StdioServerTransport = class {
|
|
|
35679
35679
|
import { createClient } from "@supabase/supabase-js";
|
|
35680
35680
|
|
|
35681
35681
|
// src/version.ts
|
|
35682
|
-
var VERSION = "0.
|
|
35682
|
+
var VERSION = "0.11.0";
|
|
35683
35683
|
|
|
35684
35684
|
// src/auth.ts
|
|
35685
35685
|
var SUPABASE_PROJECT_URL = "https://tifewzsimmyoiepblhld.supabase.co";
|
|
@@ -36882,7 +36882,7 @@ function registerTransactionTools(server2) {
|
|
|
36882
36882
|
server2.registerTool(
|
|
36883
36883
|
"kirjapro_create_transaction",
|
|
36884
36884
|
{
|
|
36885
|
-
description: "Create a transaction (tosite) with journal entries. Entries must balance (debits = credits). Accepts account numbers resolved to UUIDs server-side. Prefer kirjapro_create_from_template for standard patterns. For a VAT-registered (ALV) company the database refuses
|
|
36885
|
+
description: "Create a transaction (tosite) with journal entries. Entries must balance (debits = credits). Accepts account numbers resolved to UUIDs server-side. Prefer kirjapro_create_from_template for standard patterns. For a VAT-registered (ALV) company the database refuses a net credit on sales accounts 5000-5499, a sales-account line together with a 4210 or 2320 line, and a net 4210 credit: sales are invoices. Moving an amount out of a sales account without a VAT line (e.g. a fixed-asset disposal D 5010 / K 1600) is allowed. A purchase with deductible VAT belongs in kirjapro_book_purchase so the VAT reaches field 307; a free 2320 line is listed by the ALV reconciliation.",
|
|
36886
36886
|
inputSchema: {
|
|
36887
36887
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
36888
36888
|
transactionDate: ISO_DATE_SCHEMA.describe("Transaction date (YYYY-MM-DD)"),
|
|
@@ -37021,7 +37021,7 @@ function registerTransactionTools(server2) {
|
|
|
37021
37021
|
server2.registerTool(
|
|
37022
37022
|
"kirjapro_create_transactions_batch",
|
|
37023
37023
|
{
|
|
37024
|
-
description: "Preview or atomically create multiple transactions in one call (up to 50). dryRun defaults to true. Commit is always all-or-nothing; partial/best-effort writes are intentionally unavailable. For a VAT-registered (ALV) company the database refuses
|
|
37024
|
+
description: "Preview or atomically create multiple transactions in one call (up to 50). dryRun defaults to true. Commit is always all-or-nothing; partial/best-effort writes are intentionally unavailable. For a VAT-registered (ALV) company the database refuses a net credit on sales accounts 5000-5499, a sales-account line together with a 4210 or 2320 line, and a net 4210 credit: sales are invoices. Moving an amount out of a sales account without a VAT line (e.g. a fixed-asset disposal D 5010 / K 1600) is allowed. A purchase with deductible VAT belongs in kirjapro_book_purchase so the VAT reaches field 307; a free 2320 line is listed by the ALV reconciliation.",
|
|
37025
37025
|
inputSchema: {
|
|
37026
37026
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
37027
37027
|
dryRun: external_exports.boolean().default(true).describe(
|
|
@@ -38439,6 +38439,83 @@ function registerCustomerTools(server2) {
|
|
|
38439
38439
|
);
|
|
38440
38440
|
}
|
|
38441
38441
|
|
|
38442
|
+
// src/compliance/omavero-fields.ts
|
|
38443
|
+
var OMAVERO_FIELD_NUMBERS = [
|
|
38444
|
+
"301",
|
|
38445
|
+
"302",
|
|
38446
|
+
"303",
|
|
38447
|
+
"304",
|
|
38448
|
+
"305",
|
|
38449
|
+
"306",
|
|
38450
|
+
"307",
|
|
38451
|
+
"308",
|
|
38452
|
+
"309",
|
|
38453
|
+
"310",
|
|
38454
|
+
"311",
|
|
38455
|
+
"312",
|
|
38456
|
+
"313",
|
|
38457
|
+
"314",
|
|
38458
|
+
"318",
|
|
38459
|
+
"319",
|
|
38460
|
+
"320"
|
|
38461
|
+
];
|
|
38462
|
+
var LEGACY_ALV_SUMMARY_KEYS = [
|
|
38463
|
+
"field_307",
|
|
38464
|
+
"field_307_base",
|
|
38465
|
+
"field_308",
|
|
38466
|
+
"field_308_base",
|
|
38467
|
+
"field_309",
|
|
38468
|
+
"field_309_base",
|
|
38469
|
+
"field_310",
|
|
38470
|
+
"field_310_base",
|
|
38471
|
+
"field_312",
|
|
38472
|
+
"field_312_base",
|
|
38473
|
+
"field_313",
|
|
38474
|
+
"field_313_base",
|
|
38475
|
+
"field_318",
|
|
38476
|
+
"field_319",
|
|
38477
|
+
"field_320",
|
|
38478
|
+
"total_zero_rated",
|
|
38479
|
+
"reverse_charge_base",
|
|
38480
|
+
"omavero"
|
|
38481
|
+
];
|
|
38482
|
+
function cents(value) {
|
|
38483
|
+
return Math.round(value * 100) / 100;
|
|
38484
|
+
}
|
|
38485
|
+
function num(value) {
|
|
38486
|
+
const n = typeof value === "number" ? value : Number(value ?? 0);
|
|
38487
|
+
return Number.isFinite(n) ? n : 0;
|
|
38488
|
+
}
|
|
38489
|
+
function omaveroFieldsFromSummary(s) {
|
|
38490
|
+
const fields = {
|
|
38491
|
+
"301": num(s.field_301),
|
|
38492
|
+
"302": num(s.field_302),
|
|
38493
|
+
"303": num(s.field_303),
|
|
38494
|
+
"304": num(s.output_vat_import),
|
|
38495
|
+
"305": num(s.output_vat_eu_goods),
|
|
38496
|
+
"306": num(s.output_vat_eu_services),
|
|
38497
|
+
"307": num(s.total_input_vat),
|
|
38498
|
+
"308": num(s.net_vat_payable),
|
|
38499
|
+
"309": num(s.zero_vat_turnover),
|
|
38500
|
+
"310": num(s.field_312_base),
|
|
38501
|
+
"311": num(s.eu_goods_sales_base),
|
|
38502
|
+
"312": num(s.eu_service_sales_base),
|
|
38503
|
+
"313": num(s.field_308_base),
|
|
38504
|
+
"314": num(s.field_309_base),
|
|
38505
|
+
"318": num(s.output_vat_construction_scrap),
|
|
38506
|
+
"319": num(s.reverse_charge_sales_base),
|
|
38507
|
+
"320": cents(num(s.field_310_base) + num(s.field_313_base))
|
|
38508
|
+
};
|
|
38509
|
+
const map2 = s.omavero;
|
|
38510
|
+
if (map2 && typeof map2 === "object") {
|
|
38511
|
+
const values = map2;
|
|
38512
|
+
for (const field of OMAVERO_FIELD_NUMBERS) {
|
|
38513
|
+
if (values[field] !== void 0 && values[field] !== null) fields[field] = num(values[field]);
|
|
38514
|
+
}
|
|
38515
|
+
}
|
|
38516
|
+
return fields;
|
|
38517
|
+
}
|
|
38518
|
+
|
|
38442
38519
|
// src/lib/incomeStatementSynthesis.ts
|
|
38443
38520
|
function synthesizeIncomeStatementLines(lineTypes, currentByLine, previousByLine) {
|
|
38444
38521
|
const lineAmounts = new Map(currentByLine);
|
|
@@ -38593,18 +38670,28 @@ var ALV_SUMMARY_OUTPUT_SHAPE = {
|
|
|
38593
38670
|
input_vat: external_exports.unknown().optional(),
|
|
38594
38671
|
net_vat_payable: external_exports.number().optional(),
|
|
38595
38672
|
net_vat_payable_formatted: external_exports.string().optional(),
|
|
38596
|
-
|
|
38673
|
+
// Every official OmaVero field (VSRALVKV v1.10: 301-314, 318-320), ALV
|
|
38674
|
+
// report audit 2026-10-01, T2-11. The only field-numbered output.
|
|
38675
|
+
omavero_fields: external_exports.record(external_exports.string(), external_exports.number()).describe(
|
|
38676
|
+
"Official OmaVero VAT-return fields 301-314 and 318-320. 307 is all deductible VAT; 308 = 301+302+303+304+305+306+318 - 307."
|
|
38677
|
+
).optional(),
|
|
38678
|
+
// Sales without a deduction right: in no OmaVero field (informational).
|
|
38679
|
+
exempt_sales_base: external_exports.number().optional(),
|
|
38597
38680
|
zero_vat_turnover: external_exports.number().optional(),
|
|
38598
38681
|
eu_goods_sales_base: external_exports.number().optional(),
|
|
38599
38682
|
reverse_charge_sales_base: external_exports.number().optional(),
|
|
38600
38683
|
eu_service_sales_base: external_exports.number().optional(),
|
|
38601
38684
|
unclassified_legacy_reverse_charge_base: external_exports.number().optional(),
|
|
38685
|
+
// VAT on services bought from outside the EU, already inside field_301-303
|
|
38686
|
+
// and total_output_vat (ALV report audit 2026-10-01, T2-13).
|
|
38687
|
+
output_vat_non_eu_services: external_exports.number().optional(),
|
|
38602
38688
|
// get_alv_summary.reconciliation: the report against the 4210/2320 ledger
|
|
38603
38689
|
// (ALV report audit 2026-10-01, T2-1). Absent on an older database.
|
|
38604
38690
|
reconciliation: external_exports.object({ status: external_exports.enum(["match", "explained", "mismatch"]) }).passthrough().optional(),
|
|
38605
38691
|
warning: external_exports.string().optional(),
|
|
38606
38692
|
message: external_exports.string().optional()
|
|
38607
38693
|
};
|
|
38694
|
+
var LEGACY_REVERSE_CHARGE_WARNING = "Legacy reverse-charge invoice lines must be reclassified before the fields are complete.";
|
|
38608
38695
|
function alvReconciliationWarning(reconciliation) {
|
|
38609
38696
|
if (!reconciliation || typeof reconciliation !== "object") return void 0;
|
|
38610
38697
|
const r = reconciliation;
|
|
@@ -39087,7 +39174,7 @@ function registerReportTools(server2) {
|
|
|
39087
39174
|
server2.registerTool(
|
|
39088
39175
|
"kirjapro_get_alv_summary",
|
|
39089
39176
|
{
|
|
39090
|
-
description: "Get VAT (ALV) summary for a date range. Returns
|
|
39177
|
+
description: "Get the VAT (ALV) summary for a date range. Returns `omavero_fields`, every official OmaVero VAT-return field (VSRALVKV v1.10: 301-314, 318-320). 307 is the total deductible VAT including reverse charge and import VAT; 308 = (301+302+303+304+305+306+318) - 307. 309 excludes 311/312 sales; exempt sales without a deduction right (`exempt_sales_base`) belong in no field. 311 follows AVL 138 a \xA7 (the delivery month when invoiced on or after the delivery in that month; otherwise, including an advance invoice, the next month). Fields 301-303 include the self-assessed VAT on services bought from outside the EU (output_vat_non_eu_services). Copy fields only from `omavero_fields`. Also returns a reconciliation object comparing the 4210/2320 ledger with the report (status match, explained or mismatch, with the differences and the vouchers, invoices, fact mismatches and manual rows that explain them); a mismatch or unclassified legacy reverse-charge lines set warning. For filing deadlines, read kirjapro://compliance/deadlines.",
|
|
39091
39178
|
inputSchema: {
|
|
39092
39179
|
dateFrom: ISO_DATE_SCHEMA.describe("Period start date (YYYY-MM-DD)"),
|
|
39093
39180
|
dateTo: ISO_DATE_SCHEMA.describe("Period end date (YYYY-MM-DD)")
|
|
@@ -39127,26 +39214,21 @@ function registerReportTools(server2) {
|
|
|
39127
39214
|
});
|
|
39128
39215
|
}
|
|
39129
39216
|
const raw = result;
|
|
39130
|
-
const
|
|
39131
|
-
|
|
39132
|
-
|
|
39133
|
-
|
|
39134
|
-
|
|
39135
|
-
|
|
39136
|
-
|
|
39217
|
+
const omaveroFields = omaveroFieldsFromSummary(raw);
|
|
39218
|
+
const legacyKeys = new Set(LEGACY_ALV_SUMMARY_KEYS);
|
|
39219
|
+
const safeResult = Object.fromEntries(
|
|
39220
|
+
Object.entries(raw).filter(([key]) => !legacyKeys.has(key))
|
|
39221
|
+
);
|
|
39222
|
+
const warnings = [
|
|
39223
|
+
alvReconciliationWarning(raw.reconciliation),
|
|
39224
|
+
Number(raw.unclassified_legacy_reverse_charge_base) ? LEGACY_REVERSE_CHARGE_WARNING : void 0
|
|
39225
|
+
].filter((text) => Boolean(text));
|
|
39137
39226
|
return toResult({
|
|
39138
39227
|
...safeResult,
|
|
39139
|
-
...
|
|
39228
|
+
...warnings.length > 0 && { warning: warnings.join(" ") },
|
|
39140
39229
|
period: { from: params.dateFrom, to: params.dateTo },
|
|
39141
39230
|
net_vat_payable_formatted: formatCurrency(result.net_vat_payable || 0),
|
|
39142
|
-
omavero_fields:
|
|
39143
|
-
"309": Number(raw.zero_vat_turnover) || 0,
|
|
39144
|
-
"311": Number(raw.eu_goods_sales_base) || 0,
|
|
39145
|
-
"312": Number(raw.eu_service_sales_base) || 0,
|
|
39146
|
-
"318": Number(raw.output_vat_construction_scrap) || 0,
|
|
39147
|
-
"319": Number(raw.reverse_charge_sales_base) || 0,
|
|
39148
|
-
"320": (Number(raw.field_310_base) || 0) + (Number(raw.field_313_base) || 0)
|
|
39149
|
-
}
|
|
39231
|
+
omavero_fields: omaveroFields
|
|
39150
39232
|
});
|
|
39151
39233
|
}
|
|
39152
39234
|
);
|
|
@@ -39760,7 +39842,21 @@ var ACCOUNTS = {
|
|
|
39760
39842
|
|
|
39761
39843
|
// src/compliance/vat-guard.ts
|
|
39762
39844
|
var ALV_REGISTERED_SALE_REFUSAL = "This company is VAT-registered (ALV). A sale booked as a journal voucher never reaches the ALV report (fields 301-303 and 309), so Kirjapro refuses it. Create the sale with kirjapro_create_invoice, deliver it with kirjapro_send_invoice and record the payment with kirjapro_record_payment or bank matching.";
|
|
39763
|
-
var
|
|
39845
|
+
var NOT_VAT_REGISTERED_PURCHASE_REFUSAL = 'This company is not VAT-registered (ALV), so it deducts no VAT on purchases: book the purchase gross with vatRate "0". A service bought from a foreign seller (in another EU country or outside the EU) still makes the company liable for the self-assessed VAT: tell the user to add it as a non-deductible row on the Ostojen ALV page of the Kirjapro web app, to book the same VAT as a voucher debiting the expense account and crediting 4210 (ALV-velka), for example with kirjapro_create_transaction, and to check whether the company must register for VAT. Customs (Tulli) collects the import VAT of such a company. If the company is VAT-registered, update its settings in the Kirjapro web app first.';
|
|
39846
|
+
var REVERSE_CHARGE_CATEGORIES = [
|
|
39847
|
+
"eu_goods",
|
|
39848
|
+
"eu_services",
|
|
39849
|
+
"non_eu_services",
|
|
39850
|
+
"import",
|
|
39851
|
+
"construction",
|
|
39852
|
+
"scrap_metal"
|
|
39853
|
+
];
|
|
39854
|
+
var REVERSE_CHARGE_CATEGORY_REQUIRED = "reverseChargeCategory is required: eu_goods (fields 305/313), eu_services (306/314), non_eu_services (301-303), import (304/310), construction or scrap_metal (318/320).";
|
|
39855
|
+
var REVERSE_CHARGE_RATE_REQUIRED = "A reverse-charge purchase needs the Finnish rate the buyer self-assesses (25.5, 13.5 or 10).";
|
|
39856
|
+
var PURCHASE_TEMPLATE_NOT_VAT_REGISTERED_REFUSAL = 'This company is not VAT-registered (ALV), so a purchase template with VAT (2320) cannot be used. Book the purchase gross with kirjapro_book_purchase and vatRate "0".';
|
|
39857
|
+
function purchaseTemplateBatchRefusal(indexes) {
|
|
39858
|
+
return `Purchase templates with VAT (index ${indexes.join(", ")}) are booked one at a time with kirjapro_create_from_template or kirjapro_book_purchase so the VAT reaches the ALV report; nothing was written.`;
|
|
39859
|
+
}
|
|
39764
39860
|
var NOT_VAT_REGISTERED_SETTLEMENT_REFUSAL = "This company is not VAT-registered (ALV), so it has no VAT settlement to book.";
|
|
39765
39861
|
async function readCompanyVat(supabase2, companyId2) {
|
|
39766
39862
|
const { data, error: error61 } = await supabase2.from("companies").select("alv_registered, vat_period").eq("id", companyId2).single();
|
|
@@ -40034,8 +40130,17 @@ var TEMPLATE_MAP = new Map(TEMPLATES.map((t) => [t.id, t]));
|
|
|
40034
40130
|
function isSaleTemplate(templateId) {
|
|
40035
40131
|
return TEMPLATE_MAP.get(templateId)?.kind === "sale";
|
|
40036
40132
|
}
|
|
40133
|
+
function inputVatOf(entries) {
|
|
40134
|
+
return roundCents(
|
|
40135
|
+
entries.filter((e) => e.accountNumber === ACCOUNTS.ALV_SAAMISET).reduce((sum, e) => sum + e.debitAmount - e.creditAmount, 0)
|
|
40136
|
+
);
|
|
40137
|
+
}
|
|
40138
|
+
function isVatPurchaseTemplate(templateId) {
|
|
40139
|
+
const template = TEMPLATE_MAP.get(templateId);
|
|
40140
|
+
return template?.kind === "purchase" && inputVatOf(template.expand(100)) !== 0;
|
|
40141
|
+
}
|
|
40037
40142
|
function availableTemplates(alvRegistered) {
|
|
40038
|
-
return alvRegistered ? TEMPLATES.filter((t) => t.kind !== "sale") : TEMPLATES;
|
|
40143
|
+
return alvRegistered ? TEMPLATES.filter((t) => t.kind !== "sale") : TEMPLATES.filter((t) => !isVatPurchaseTemplate(t.id));
|
|
40039
40144
|
}
|
|
40040
40145
|
function expandTemplate(templateId, amount, opts) {
|
|
40041
40146
|
const template = TEMPLATE_MAP.get(templateId);
|
|
@@ -40099,7 +40204,7 @@ function toJournalEntries(entries, accountMap) {
|
|
|
40099
40204
|
credit_amount: e.creditAmount
|
|
40100
40205
|
}));
|
|
40101
40206
|
}
|
|
40102
|
-
async function
|
|
40207
|
+
async function preparePosting(params) {
|
|
40103
40208
|
const supabase2 = getSupabase();
|
|
40104
40209
|
const companyId2 = getCompanyId();
|
|
40105
40210
|
let fiscalYearId = params.fiscalYearId;
|
|
@@ -40128,16 +40233,41 @@ async function resolveAndCreate(params) {
|
|
|
40128
40233
|
`Template produced unbalanced entries: debits ${totalDebit.toFixed(2)} != credits ${totalCredit.toFixed(2)}.`
|
|
40129
40234
|
);
|
|
40130
40235
|
}
|
|
40131
|
-
|
|
40132
|
-
|
|
40236
|
+
return { fiscalYearId, journalEntries: toJournalEntries(params.entries, accountResult) };
|
|
40237
|
+
}
|
|
40238
|
+
async function resolveAndCreatePurchase(params) {
|
|
40239
|
+
const prepared = await preparePosting(params);
|
|
40240
|
+
if ("isError" in prepared) return prepared;
|
|
40241
|
+
const { data, error: error61 } = await getSupabase().rpc("book_purchase_with_vat", {
|
|
40242
|
+
p_company_id: getCompanyId(),
|
|
40243
|
+
p_fiscal_year_id: prepared.fiscalYearId,
|
|
40244
|
+
p_transaction_date: params.transactionDate,
|
|
40245
|
+
p_description: params.description,
|
|
40246
|
+
p_document_number: params.documentNumber || "",
|
|
40247
|
+
p_entries: prepared.journalEntries,
|
|
40248
|
+
p_vat_rows: params.vatRows,
|
|
40249
|
+
...params.supplierName ? { p_supplier_name: params.supplierName } : {}
|
|
40250
|
+
});
|
|
40251
|
+
const result = data;
|
|
40252
|
+
if (error61 || !result?.transaction_id) {
|
|
40253
|
+
return toErrorContent(
|
|
40254
|
+
error61 ? sanitizeDbError("Failed to book the purchase", error61) : "Failed to book the purchase: no ID returned."
|
|
40255
|
+
);
|
|
40256
|
+
}
|
|
40257
|
+
return { id: result.transaction_id, vatEntryIds: result.vat_entry_ids ?? [] };
|
|
40258
|
+
}
|
|
40259
|
+
async function resolveAndCreate(params) {
|
|
40260
|
+
const prepared = await preparePosting(params);
|
|
40261
|
+
if ("isError" in prepared) return prepared;
|
|
40262
|
+
const { data: transactionId, error: txError } = await getSupabase().rpc(
|
|
40133
40263
|
"create_transaction_with_entries",
|
|
40134
40264
|
{
|
|
40135
|
-
p_company_id:
|
|
40136
|
-
p_fiscal_year_id: fiscalYearId,
|
|
40265
|
+
p_company_id: getCompanyId(),
|
|
40266
|
+
p_fiscal_year_id: prepared.fiscalYearId,
|
|
40137
40267
|
p_transaction_date: params.transactionDate,
|
|
40138
40268
|
p_description: params.description,
|
|
40139
40269
|
p_document_number: params.documentNumber || "",
|
|
40140
|
-
p_entries: journalEntries
|
|
40270
|
+
p_entries: prepared.journalEntries
|
|
40141
40271
|
}
|
|
40142
40272
|
);
|
|
40143
40273
|
if (txError || !transactionId) {
|
|
@@ -40151,7 +40281,7 @@ function registerTemplateTools(server2) {
|
|
|
40151
40281
|
server2.registerTool(
|
|
40152
40282
|
"kirjapro_list_templates",
|
|
40153
40283
|
{
|
|
40154
|
-
description: "List available transaction templates. Templates handle double-entry balancing and VAT splitting automatically. Sale templates are not listed for a VAT-registered (ALV) company: its sales are invoices.",
|
|
40284
|
+
description: "List available transaction templates. Templates handle double-entry balancing and VAT splitting automatically. Sale templates are not listed for a VAT-registered (ALV) company: its sales are invoices. Purchase templates with VAT are not listed for a company that is not VAT-registered.",
|
|
40155
40285
|
inputSchema: {},
|
|
40156
40286
|
outputSchema: {
|
|
40157
40287
|
templates: external_exports.array(external_exports.object({
|
|
@@ -40176,7 +40306,7 @@ function registerTemplateTools(server2) {
|
|
|
40176
40306
|
server2.registerTool(
|
|
40177
40307
|
"kirjapro_create_from_template",
|
|
40178
40308
|
{
|
|
40179
|
-
description: 'Create a transaction from a template. Use kirjapro_list_templates for available templates. accountOverrides swaps default accounts (e.g. {"2320": "2330"}). Sale templates are refused for a VAT-registered (ALV) company: create an invoice instead.',
|
|
40309
|
+
description: 'Create a transaction from a template. Use kirjapro_list_templates for available templates. accountOverrides swaps default accounts (e.g. {"2320": "2330"}). Sale templates are refused for a VAT-registered (ALV) company: create an invoice instead. A purchase template with VAT (2320) books its VAT fact so the VAT reaches field 307; for a company that is not VAT-registered it is refused (book the purchase gross).',
|
|
40180
40310
|
inputSchema: {
|
|
40181
40311
|
template: external_exports.string().describe('Template ID (e.g. "cash_sale_25", "purchase_expense")'),
|
|
40182
40312
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
@@ -40192,10 +40322,15 @@ function registerTemplateTools(server2) {
|
|
|
40192
40322
|
async (params) => {
|
|
40193
40323
|
const supabase2 = getSupabase();
|
|
40194
40324
|
const companyId2 = getCompanyId();
|
|
40195
|
-
|
|
40325
|
+
const template = TEMPLATE_MAP.get(params.template);
|
|
40326
|
+
let alvRegistered = null;
|
|
40327
|
+
if (template && (template.kind === "sale" || template.kind === "purchase")) {
|
|
40196
40328
|
const companyVat = await readCompanyVat(supabase2, companyId2);
|
|
40197
40329
|
if ("isError" in companyVat) return companyVat;
|
|
40198
|
-
|
|
40330
|
+
alvRegistered = companyVat.alvRegistered;
|
|
40331
|
+
if (template.kind === "sale" && alvRegistered) {
|
|
40332
|
+
return toErrorContent(ALV_REGISTERED_SALE_REFUSAL);
|
|
40333
|
+
}
|
|
40199
40334
|
}
|
|
40200
40335
|
return withIdempotency(
|
|
40201
40336
|
supabase2,
|
|
@@ -40214,7 +40349,27 @@ function registerTemplateTools(server2) {
|
|
|
40214
40349
|
if ("error" in result) {
|
|
40215
40350
|
return toErrorContent(result.error);
|
|
40216
40351
|
}
|
|
40217
|
-
const
|
|
40352
|
+
const inputVat = template?.kind === "purchase" ? inputVatOf(result.entries) : 0;
|
|
40353
|
+
if (inputVat !== 0 && alvRegistered === false) {
|
|
40354
|
+
return toErrorContent(PURCHASE_TEMPLATE_NOT_VAT_REGISTERED_REFUSAL);
|
|
40355
|
+
}
|
|
40356
|
+
const createResult = inputVat !== 0 ? await resolveAndCreatePurchase({
|
|
40357
|
+
fiscalYearId: params.fiscalYearId,
|
|
40358
|
+
transactionDate: params.transactionDate,
|
|
40359
|
+
description: params.description,
|
|
40360
|
+
documentNumber: params.documentNumber,
|
|
40361
|
+
entries: result.entries,
|
|
40362
|
+
vatRows: [
|
|
40363
|
+
{
|
|
40364
|
+
category: "domestic",
|
|
40365
|
+
vat_rate: template?.vatRate ?? 0,
|
|
40366
|
+
tax_base: roundCents(
|
|
40367
|
+
result.entries.filter((e) => e.accountNumber !== ACCOUNTS.ALV_SAAMISET).reduce((sum, e) => sum + e.debitAmount, 0)
|
|
40368
|
+
),
|
|
40369
|
+
vat_amount: inputVat
|
|
40370
|
+
}
|
|
40371
|
+
]
|
|
40372
|
+
}) : await resolveAndCreate({
|
|
40218
40373
|
fiscalYearId: params.fiscalYearId,
|
|
40219
40374
|
transactionDate: params.transactionDate,
|
|
40220
40375
|
description: params.description,
|
|
@@ -40228,7 +40383,8 @@ function registerTemplateTools(server2) {
|
|
|
40228
40383
|
message: "Transaction created from template",
|
|
40229
40384
|
template: params.template,
|
|
40230
40385
|
transaction_id: createResult.id,
|
|
40231
|
-
entries: result.entries
|
|
40386
|
+
entries: result.entries,
|
|
40387
|
+
..."vatEntryIds" in createResult && { vat_entry_ids: createResult.vatEntryIds }
|
|
40232
40388
|
});
|
|
40233
40389
|
}
|
|
40234
40390
|
);
|
|
@@ -40237,7 +40393,7 @@ function registerTemplateTools(server2) {
|
|
|
40237
40393
|
server2.registerTool(
|
|
40238
40394
|
"kirjapro_create_from_template_batch",
|
|
40239
40395
|
{
|
|
40240
|
-
description: "Preview or atomically create multiple template transactions in one call (up to 50). dryRun defaults to true. Commit is always all-or-nothing; partial/best-effort writes are intentionally unavailable. A batch with a sale template is refused for a VAT-registered (ALV) company before any write.",
|
|
40396
|
+
description: "Preview or atomically create multiple template transactions in one call (up to 50). dryRun defaults to true. Commit is always all-or-nothing; partial/best-effort writes are intentionally unavailable. A batch with a sale template is refused for a VAT-registered (ALV) company before any write. Purchase templates with VAT (2320) are refused in a batch: book them one at a time with kirjapro_create_from_template so the VAT reaches the ALV report.",
|
|
40241
40397
|
inputSchema: {
|
|
40242
40398
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
40243
40399
|
dryRun: external_exports.boolean().default(true).describe(
|
|
@@ -40260,6 +40416,17 @@ function registerTemplateTools(server2) {
|
|
|
40260
40416
|
async (params) => {
|
|
40261
40417
|
const supabase2 = getSupabase();
|
|
40262
40418
|
const companyId2 = getCompanyId();
|
|
40419
|
+
const vatPurchaseIndexes = params.transactions.map((tx, index) => {
|
|
40420
|
+
const template = TEMPLATE_MAP.get(tx.template);
|
|
40421
|
+
if (template?.kind !== "purchase") return -1;
|
|
40422
|
+
const expanded = expandTemplate(tx.template, tx.amount, {
|
|
40423
|
+
accountOverrides: tx.accountOverrides
|
|
40424
|
+
});
|
|
40425
|
+
return "entries" in expanded && inputVatOf(expanded.entries) !== 0 ? index : -1;
|
|
40426
|
+
}).filter((index) => index >= 0);
|
|
40427
|
+
if (vatPurchaseIndexes.length > 0) {
|
|
40428
|
+
return toErrorContent(purchaseTemplateBatchRefusal(vatPurchaseIndexes));
|
|
40429
|
+
}
|
|
40263
40430
|
const saleIndexes = params.transactions.map((tx, index) => isSaleTemplate(tx.template) ? index : -1).filter((index) => index >= 0);
|
|
40264
40431
|
if (saleIndexes.length > 0) {
|
|
40265
40432
|
const companyVat = await readCompanyVat(supabase2, companyId2);
|
|
@@ -40548,7 +40715,7 @@ function registerBookingTools(server2) {
|
|
|
40548
40715
|
server2.registerTool(
|
|
40549
40716
|
"kirjapro_book_purchase",
|
|
40550
40717
|
{
|
|
40551
|
-
description: "Book a purchase (osto) with automatic VAT splitting.
|
|
40718
|
+
description: "Book a purchase (osto) with automatic VAT splitting. For a VAT-registered (ALV) company the VAT is recorded as a VAT fact, so it reaches field 307 (and the reverse-charge fields). Reverse charge (reverseCharge=true, with reverseChargeCategory) self-assesses the VAT for EU goods and services, services from outside the EU, import, construction and scrap metal. Entertainment (edustus, AVL 114\xA7) is booked gross. Statutorily non-deductible accounts (wages, insurance, interest, depreciation, taxes, representation) are refused with VAT: book them with vatRate 0. A company that is not VAT-registered books purchases gross with vatRate 0. Rate rules: food/restaurants 13.5%, insurance 0% (exempt), alcohol 25.5%.",
|
|
40552
40719
|
inputSchema: {
|
|
40553
40720
|
amount: external_exports.number().finite().positive().max(1e9).describe(
|
|
40554
40721
|
"EUR amount. Standard purchase: GROSS (VAT-inclusive) \u2014 VAT is split out of this. Reverse charge (reverseCharge=true): NET foreign-invoice amount \u2014 the foreign supplier does not charge Finnish VAT, so the parameter must be net. VAT is self-assessed on top per AVL 9\xA7 producing equal debit to ALV-saamiset and credit to ALV-velka. Entertainment (edustus=true): GROSS (full amount to expense, no VAT deduction per AVL 114\xA7)."
|
|
@@ -40557,7 +40724,11 @@ function registerBookingTools(server2) {
|
|
|
40557
40724
|
date: ISO_DATE_SCHEMA.describe("Transaction date (YYYY-MM-DD)"),
|
|
40558
40725
|
vatRate: external_exports.enum(["25.5", "13.5", "10", "0"]).describe("Finnish VAT rate"),
|
|
40559
40726
|
expenseAccount: external_exports.string().describe('Expense account number (e.g. "8080")'),
|
|
40560
|
-
reverseCharge: external_exports.boolean().default(false).describe(`
|
|
40727
|
+
reverseCharge: external_exports.boolean().default(false).describe(`Reverse charge (k\xE4\xE4nnetty verovelvollisuus): D expense + D ${ACCOUNTS.ALV_SAAMISET} ALV-saamiset + C ${ACCOUNTS.ALV_VELKA} ALV-velka + C bank, with a VAT fact of reverseChargeCategory`),
|
|
40728
|
+
reverseChargeCategory: external_exports.enum(REVERSE_CHARGE_CATEGORIES).optional().describe(
|
|
40729
|
+
"Required with reverseCharge: eu_goods (fields 305/313), eu_services (306/314), non_eu_services (services from outside the EU, 301-303), import (304/310), construction or scrap_metal (318/320)"
|
|
40730
|
+
),
|
|
40731
|
+
supplierName: external_exports.string().max(200).optional().describe("Supplier name, stored on the VAT fact"),
|
|
40561
40732
|
edustus: external_exports.boolean().default(false).describe("Entertainment expense (AVL 114\xA7): 0% VAT deduction, full gross to expense"),
|
|
40562
40733
|
paymentAccount: external_exports.string().default(ACCOUNTS.PANKKITILI).describe(`Payment account (default "${ACCOUNTS.PANKKITILI}" Pankkitili)`),
|
|
40563
40734
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
@@ -40571,10 +40742,15 @@ function registerBookingTools(server2) {
|
|
|
40571
40742
|
const rate = Number.parseFloat(params.vatRate);
|
|
40572
40743
|
const gross = roundCents(params.amount);
|
|
40573
40744
|
let entries;
|
|
40745
|
+
let vatRows = [];
|
|
40746
|
+
const companyVat = await readCompanyVat(getSupabase(), getCompanyId());
|
|
40747
|
+
if ("isError" in companyVat) return companyVat;
|
|
40748
|
+
if (!params.edustus && !companyVat.alvRegistered && (rate !== 0 || params.reverseCharge)) {
|
|
40749
|
+
return toErrorContent(NOT_VAT_REGISTERED_PURCHASE_REFUSAL);
|
|
40750
|
+
}
|
|
40574
40751
|
if (params.reverseCharge && !params.edustus) {
|
|
40575
|
-
|
|
40576
|
-
if (
|
|
40577
|
-
if (vat.alvRegistered) return toErrorContent(ALV_REGISTERED_REVERSE_CHARGE_REFUSAL);
|
|
40752
|
+
if (!params.reverseChargeCategory) return toErrorContent(REVERSE_CHARGE_CATEGORY_REQUIRED);
|
|
40753
|
+
if (rate === 0) return toErrorContent(REVERSE_CHARGE_RATE_REQUIRED);
|
|
40578
40754
|
}
|
|
40579
40755
|
if (params.edustus) {
|
|
40580
40756
|
entries = [
|
|
@@ -40591,6 +40767,14 @@ function registerBookingTools(server2) {
|
|
|
40591
40767
|
// ALV-velka (output)
|
|
40592
40768
|
{ accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
|
|
40593
40769
|
];
|
|
40770
|
+
vatRows = [
|
|
40771
|
+
{
|
|
40772
|
+
category: params.reverseChargeCategory ?? "eu_services",
|
|
40773
|
+
vat_rate: rate,
|
|
40774
|
+
tax_base: gross,
|
|
40775
|
+
vat_amount: vat
|
|
40776
|
+
}
|
|
40777
|
+
];
|
|
40594
40778
|
} else if (rate === 0) {
|
|
40595
40779
|
entries = [
|
|
40596
40780
|
{ accountNumber: params.expenseAccount, debitAmount: gross, creditAmount: 0 },
|
|
@@ -40603,6 +40787,7 @@ function registerBookingTools(server2) {
|
|
|
40603
40787
|
{ accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat, creditAmount: 0 },
|
|
40604
40788
|
{ accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
|
|
40605
40789
|
];
|
|
40790
|
+
vatRows = [{ category: "domestic", vat_rate: rate, tax_base: net, vat_amount: vat }];
|
|
40606
40791
|
}
|
|
40607
40792
|
const purchaseAccountWarning = expenseAccountWarning(params.expenseAccount);
|
|
40608
40793
|
if (params.dryRun) {
|
|
@@ -40613,6 +40798,7 @@ function registerBookingTools(server2) {
|
|
|
40613
40798
|
description: params.description,
|
|
40614
40799
|
date: params.date,
|
|
40615
40800
|
entries,
|
|
40801
|
+
...vatRows.length > 0 && { vat_rows: vatRows },
|
|
40616
40802
|
...purchaseAccountWarning && { account_warning: purchaseAccountWarning }
|
|
40617
40803
|
});
|
|
40618
40804
|
}
|
|
@@ -40623,18 +40809,24 @@ function registerBookingTools(server2) {
|
|
|
40623
40809
|
params.idempotencyKey,
|
|
40624
40810
|
params,
|
|
40625
40811
|
async () => {
|
|
40626
|
-
const
|
|
40812
|
+
const posting = {
|
|
40627
40813
|
fiscalYearId: params.fiscalYearId,
|
|
40628
40814
|
transactionDate: params.date,
|
|
40629
40815
|
description: params.description,
|
|
40630
40816
|
documentNumber: params.documentNumber,
|
|
40631
40817
|
entries
|
|
40632
|
-
}
|
|
40818
|
+
};
|
|
40819
|
+
const result = vatRows.length > 0 ? await resolveAndCreatePurchase({
|
|
40820
|
+
...posting,
|
|
40821
|
+
vatRows,
|
|
40822
|
+
supplierName: params.supplierName
|
|
40823
|
+
}) : await resolveAndCreate(posting);
|
|
40633
40824
|
if ("isError" in result) return result;
|
|
40634
40825
|
return toResult({
|
|
40635
40826
|
message: "Purchase booked",
|
|
40636
40827
|
transaction_id: result.id,
|
|
40637
40828
|
entries,
|
|
40829
|
+
..."vatEntryIds" in result && { vat_entry_ids: result.vatEntryIds },
|
|
40638
40830
|
...purchaseAccountWarning && { warning: purchaseAccountWarning }
|
|
40639
40831
|
});
|
|
40640
40832
|
}
|
|
@@ -42308,7 +42500,7 @@ var VAT_COMPLIANCE = {
|
|
|
42308
42500
|
],
|
|
42309
42501
|
booking: "4 entries: D expense + D ALV-saamiset + C bank + C ALV-velka (k\xE4\xE4nnetty)",
|
|
42310
42502
|
note: "Net VAT effect is zero but BOTH must be reported on ALV-ilmoitus. Invoice must NOT show VAT rate or VAT amount",
|
|
42311
|
-
omavero_fields: "
|
|
42503
|
+
omavero_fields: "EU goods: 305 (VAT) / 313 (base); EU services: 306 / 314; services from outside the EU: VAT in 301-303 by rate (no base field); imports: 304 / 310; domestic construction/scrap purchases: 318 / 320, sales 319. The deductible part of each is in 307."
|
|
42312
42504
|
},
|
|
42313
42505
|
common_mistakes: [
|
|
42314
42506
|
"Food/coffee at 25.5% \u2014 should be 13.5% (AVL 85\xA7)",
|
|
@@ -42387,14 +42579,18 @@ var FILING_DEADLINES = {
|
|
|
42387
42579
|
},
|
|
42388
42580
|
maksuperusteinen_alv: {
|
|
42389
42581
|
threshold: 5e5,
|
|
42390
|
-
note: "
|
|
42391
|
-
application: "
|
|
42582
|
+
note: "Turnover up to 500,000 \u20AC per calendar year: domestic sales VAT may be reported when payment is received and purchase VAT deducted when paid (cash basis), consistently for all domestic sales and purchases; imports, exports, intra-EU sales and acquisitions and cross-border reverse-charge services stay on the accrual basis. Kirjapro reports on the accrual basis only.",
|
|
42583
|
+
application: "No application: the business applies the cash basis in its bookkeeping. Since 2025 the VAT return has no cash-basis field.",
|
|
42584
|
+
source: "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/pienyrityksen-maksuperusteinen-alv/",
|
|
42585
|
+
sources_verified_at: "2026-10-02"
|
|
42392
42586
|
},
|
|
42393
42587
|
late_penalties: {
|
|
42394
|
-
myohastymismaksu: "3 \u20AC/day for
|
|
42588
|
+
myohastymismaksu: "3 \u20AC/day for at most 45 days (135 \u20AC); more than 45 days late: 135 \u20AC + 2 % of the declared payable tax (max 15,000 \u20AC per tax type and period); also for returns with no tax payable",
|
|
42395
42589
|
viivastyskorko_2026: "9.5% for most taxes, 4.5% for inheritance tax",
|
|
42396
|
-
correction_grace: "
|
|
42397
|
-
nil_return: "Even when no VAT activity occurred, a nil return must be filed for each verokausi"
|
|
42590
|
+
correction_grace: "A correction return filed within 45 days of the original due date carries no late-filing fee (no amount condition). Later corrections: 2 % of the additional payable tax.",
|
|
42591
|
+
nil_return: "Even when no VAT activity occurred, a nil return must be filed for each verokausi",
|
|
42592
|
+
source: "https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48377/seuraamusmaksut-oma-aloitteisessa-verotuksessa5/",
|
|
42593
|
+
sources_verified_at: "2026-10-02"
|
|
42398
42594
|
},
|
|
42399
42595
|
payment_deadline: "Same as filing deadline (12th of M+2 for monthly/quarterly, last day of Feb for annual)",
|
|
42400
42596
|
omavero_field_codes: {
|
|
@@ -42412,16 +42608,17 @@ var FILING_DEADLINES = {
|
|
|
42412
42608
|
318: "Vero rakentamispalvelun ja metalliromun ostoista (construction/scrap reverse charge VAT)"
|
|
42413
42609
|
},
|
|
42414
42610
|
tax_bases: {
|
|
42415
|
-
309: "0-verokannan alainen liikevaihto (zero-rate turnover
|
|
42611
|
+
309: "0-verokannan alainen liikevaihto (zero-rate turnover, e.g. exports of goods outside the EU, AVL 55-72 h \xA7). Does NOT include EU goods sales (311) or EU service sales (312). Exempt sales without a deduction right (healthcare, education, finance, insurance) are reported in no field.",
|
|
42416
42612
|
310: "Tavaroiden maahantuonnit EU:n ulkopuolelta (goods imports from outside EU)",
|
|
42417
|
-
311: "Tavaroiden myynnit muihin EU-maihin (goods sales to EU)",
|
|
42613
|
+
311: "Tavaroiden myynnit muihin EU-maihin (goods sales to EU; reported for the delivery month when invoiced on or after the delivery in that month, otherwise, including an advance invoice, the next month, AVL 138 a \xA7)",
|
|
42418
42614
|
312: "Palveluiden myynnit muihin EU-maihin (service sales to EU)",
|
|
42419
42615
|
313: "Tavaraostot muista EU-maista (goods purchases from EU)",
|
|
42420
42616
|
314: "Palveluostot muista EU-maista (service purchases from EU)",
|
|
42421
42617
|
319: "Rakentamispalvelun ja metalliromun myynnit (construction/scrap sales)",
|
|
42422
42618
|
320: "Rakentamispalvelun ja metalliromun ostot (construction/scrap purchases)"
|
|
42423
42619
|
},
|
|
42424
|
-
removed_2025: "315, 316, 317 (alarajahuojennus fields) removed from 1.1.2025"
|
|
42620
|
+
removed_2025: "315, 316, 317 (alarajahuojennus fields) removed from 1.1.2025",
|
|
42621
|
+
sources_verified_at: "2026-10-02"
|
|
42425
42622
|
}
|
|
42426
42623
|
};
|
|
42427
42624
|
var SOCIAL_INSURANCE_2026 = {
|
|
@@ -42702,7 +42899,7 @@ Do not invent a "closest match" tool for these tasks. A confidently wrong action
|
|
|
42702
42899
|
A bank transaction that is already booked (a receipt or purchase booked as paid from the bank account, a manual invoice payment, a paid purchase invoice, a Kirjapro payment batch) is linked, never posted again: \`kirjapro_list_bank_link_candidates\` \u2192 \`kirjapro_link_bank_transaction\`. Never post a manual voucher for an already-booked or skipped bank transaction; a wrong reconciliation is undone in the Kirjapro app (Pankki \u2192 T\xE4sm\xE4ytys \u2192 T\xE4sm\xE4ytetyt \u2192 Pura t\xE4sm\xE4ytys) or, on the full surface, with \`kirjapro_unreconcile_bank_transaction\`, and then matched again.
|
|
42703
42900
|
|
|
42704
42901
|
### Reports
|
|
42705
|
-
\`kirjapro_get_dashboard\` (overview) | \`kirjapro_get_income_statement\` (tuloslaskelma) | \`kirjapro_get_balance_sheet\` (tase \u2014 \`equity.synthetic_tilikauden_tulos\` is the unclosed P/L residual already included in equity; never add it again. The cumulative report groups current earnings under 3600 and retained earnings under 3500. Report rows can differ from raw ledger account balances. Do not present book equity as an approved distribution limit) | \`kirjapro_get_ar_aging\` (bounded myyntisaamisten aggregate; weighted age is not revenue-based DSO) | \`kirjapro_get_alv_summary\` (ALV-yhteenveto
|
|
42902
|
+
\`kirjapro_get_dashboard\` (overview) | \`kirjapro_get_income_statement\` (tuloslaskelma) | \`kirjapro_get_balance_sheet\` (tase \u2014 \`equity.synthetic_tilikauden_tulos\` is the unclosed P/L residual already included in equity; never add it again. The cumulative report groups current earnings under 3600 and retained earnings under 3500. Report rows can differ from raw ledger account balances. Do not present book equity as an approved distribution limit) | \`kirjapro_get_ar_aging\` (bounded myyntisaamisten aggregate; weighted age is not revenue-based DSO) | \`kirjapro_get_alv_summary\` (ALV-yhteenveto; the official OmaVero fields are in \`omavero_fields\`).
|
|
42706
42903
|
|
|
42707
42904
|
## Safety Rules
|
|
42708
42905
|
|
|
@@ -42768,7 +42965,7 @@ var INSTRUCTIONS_CORE_ADDENDUM = `
|
|
|
42768
42965
|
The following workflows are NOT exposed in the current (core) surface. When a user asks for them, redirect to the web UI rather than reaching for the closest available tool:
|
|
42769
42966
|
|
|
42770
42967
|
- **Recurring invoice templates** \u2014 created and managed in the web app; cron generates the issued invoices
|
|
42771
|
-
- **Booking shortcuts** (book_purchase / book_sale / book_vat_settlement) \u2014 for these, you CAN compose the journal entries manually via \`kirjapro_create_transaction\` with explicit debit/credit lines if the user prefers; otherwise redirect. Exception: a VAT-registered (ALV) company books sales only as invoices (\`kirjapro_create_invoice\` + \`kirjapro_send_invoice\`) and reverse-charge purchases in the web app; the database refuses its vouchers with sales accounts 5000-5499 or a net 4210 credit
|
|
42968
|
+
- **Booking shortcuts** (book_purchase / book_sale / book_vat_settlement) \u2014 for these, you CAN compose the journal entries manually via \`kirjapro_create_transaction\` with explicit debit/credit lines if the user prefers; otherwise redirect. Exception: a VAT-registered (ALV) company books sales only as invoices (\`kirjapro_create_invoice\` + \`kirjapro_send_invoice\`) and reverse-charge purchases and purchases with deductible VAT in the web app (a free 2320 line on a voucher has no VAT fact and does not reach field 307); the database refuses its vouchers with a net credit on sales accounts 5000-5499, a sales-account line together with a 4210 or 2320 line, or a net 4210 credit; moving an amount out of a sales account without a VAT line (e.g. a fixed-asset disposal D 5010 / K 1600) is allowed
|
|
42772
42969
|
- **Payroll (palkanlaskenta)** \u2014 Kirjapro does NOT calculate or book payroll. Direct the user to Palkkaus.fi or Palkka.fi for the calculation, then bring the resulting payslip back and book it via \`kirjapro_create_transaction\` with explicit debit/credit lines
|
|
42773
42970
|
- **Transaction templates** \u2014 manual journal entries via \`kirjapro_create_transaction\`
|
|
42774
42971
|
- **Manual offer status changes** \u2014 the customer's email-link click sets status server-side; just wait for the user to confirm acceptance
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kirjapro/mcp-server",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "MCP server for Finnish accounting — exposes Kirjapro bookkeeping, invoicing, and reporting tools to Claude, ChatGPT, Mistral, and other MCP clients.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/server.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@kirjapro/mcp-server",
|
|
3
3
|
"displayName": "Kirjapro - Finnish Accounting",
|
|
4
4
|
"description": "Finnish double-entry bookkeeping, invoicing, and financial reporting for growing businesses. Create transactions, generate invoices with VAT calculation, produce income statements and balance sheets, and validate Finnish business identifiers.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.11.0",
|
|
6
6
|
"homepage": "https://kirjapro.fi",
|
|
7
7
|
"repository": "https://github.com/Jaollo/kirja-pro",
|
|
8
8
|
"license": "MIT",
|
package/tool-fingerprints.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"fingerprints": [
|
|
3
3
|
{
|
|
4
4
|
"name": "kirjapro_book_purchase",
|
|
5
|
-
"hash": "
|
|
5
|
+
"hash": "e6c7bfe42eedc619"
|
|
6
6
|
},
|
|
7
7
|
{
|
|
8
8
|
"name": "kirjapro_book_sale",
|
|
@@ -22,11 +22,11 @@
|
|
|
22
22
|
},
|
|
23
23
|
{
|
|
24
24
|
"name": "kirjapro_create_from_template",
|
|
25
|
-
"hash": "
|
|
25
|
+
"hash": "d410e01e95e20227"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"name": "kirjapro_create_from_template_batch",
|
|
29
|
-
"hash": "
|
|
29
|
+
"hash": "ff8fa037077fac9a"
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"name": "kirjapro_create_invoice",
|
|
@@ -42,11 +42,11 @@
|
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"name": "kirjapro_create_transaction",
|
|
45
|
-
"hash": "
|
|
45
|
+
"hash": "8b6a32983de69413"
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
48
|
"name": "kirjapro_create_transactions_batch",
|
|
49
|
-
"hash": "
|
|
49
|
+
"hash": "a3e71d41bbf437e4"
|
|
50
50
|
},
|
|
51
51
|
{
|
|
52
52
|
"name": "kirjapro_delete_invoice",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
"name": "kirjapro_get_alv_summary",
|
|
69
|
-
"hash": "
|
|
69
|
+
"hash": "a149fdcb6307923a"
|
|
70
70
|
},
|
|
71
71
|
{
|
|
72
72
|
"name": "kirjapro_get_ar_aging",
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
},
|
|
163
163
|
{
|
|
164
164
|
"name": "kirjapro_list_templates",
|
|
165
|
-
"hash": "
|
|
165
|
+
"hash": "91c70a7143aa96ac"
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
"name": "kirjapro_list_transactions",
|