@kirjapro/mcp-server 0.9.1 → 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 +99 -0
- package/dist/index.js +391 -73
- package/package.json +1 -1
- package/server.json +1 -1
- package/tool-fingerprints.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,104 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
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
|
|
60
|
+
|
|
61
|
+
Requires the database migrations `20261001204555`–`20261001204559` (ALV report
|
|
62
|
+
audit batch 1). Version 0.9.1 keeps working against that database: its sales
|
|
63
|
+
and reverse-charge vouchers of a VAT-registered company are refused by the
|
|
64
|
+
database with the same guidance, and its settlement default date stays the
|
|
65
|
+
old one.
|
|
66
|
+
|
|
67
|
+
- `kirjapro_book_sale`, the sale templates (`cash_sale_*`, `bank_sale_*`,
|
|
68
|
+
`invoice_sale_25`) of `kirjapro_create_from_template(_batch)` and
|
|
69
|
+
`kirjapro_book_purchase` with `reverseCharge` are refused for a
|
|
70
|
+
VAT-registered (ALV) company before any write: a journal voucher never
|
|
71
|
+
reaches the ALV report. Sales are created with `kirjapro_create_invoice` and
|
|
72
|
+
delivered with `kirjapro_send_invoice`; reverse-charge purchases are booked
|
|
73
|
+
as received invoices in the web app for now. `kirjapro_list_templates` no
|
|
74
|
+
longer lists the sale templates for such a company. Other companies are
|
|
75
|
+
unchanged.
|
|
76
|
+
- `kirjapro_create_transaction(s_batch)`: the database refuses lines on sales
|
|
77
|
+
accounts 5000-5499 and a net 4210 credit for a VAT-registered company; the
|
|
78
|
+
descriptions say so.
|
|
79
|
+
- `kirjapro_book_vat_settlement` settles the company's VAT period (month,
|
|
80
|
+
quarter or year from `companies.vat_period`) containing `month`. The default
|
|
81
|
+
date is the statutory due date (12th of the second month after the period;
|
|
82
|
+
annual period: end of February), and the fiscal year defaults to the open
|
|
83
|
+
year containing that date, so a December settlement is booked in the next
|
|
84
|
+
year. The database now excludes earlier settlements, closing entries and the
|
|
85
|
+
previous-year import, refuses a settled period, a period whose ledger does
|
|
86
|
+
not match the ALV report and negative balances, and books only non-zero
|
|
87
|
+
lines. The result and the dry run name the period. A company that is not
|
|
88
|
+
VAT-registered is refused.
|
|
89
|
+
- `kirjapro_get_alv_summary` returns `reconciliation`: the 4210/2320 ledger
|
|
90
|
+
against the report (status `match`, `explained` or `mismatch`, the
|
|
91
|
+
differences and the explaining vouchers, invoices, fact mismatches and
|
|
92
|
+
manual rows), and sets `warning` on a mismatch.
|
|
93
|
+
- The server instructions (core and full surface) say that a VAT-registered
|
|
94
|
+
company books sales as invoices, that `kirjapro_create_transaction` cannot
|
|
95
|
+
replace `kirjapro_book_sale` for it, and that its sale templates are hidden.
|
|
96
|
+
- Review the fingerprints of `kirjapro_book_sale`, `kirjapro_book_purchase`,
|
|
97
|
+
`kirjapro_book_vat_settlement`, `kirjapro_create_from_template`,
|
|
98
|
+
`kirjapro_create_from_template_batch`, `kirjapro_list_templates`,
|
|
99
|
+
`kirjapro_create_transaction`, `kirjapro_create_transactions_batch` and
|
|
100
|
+
`kirjapro_get_alv_summary` for their changed descriptions and schemas.
|
|
101
|
+
|
|
3
102
|
## [0.9.1] - 2026-09-29
|
|
4
103
|
|
|
5
104
|
Requires the database migration `20260929120200` (bank reconciliation
|
package/dist/index.js
CHANGED
|
@@ -4148,13 +4148,14 @@ var require_fast_uri = __commonJS({
|
|
|
4148
4148
|
if (!malformedIPLiteral) {
|
|
4149
4149
|
malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
|
|
4150
4150
|
}
|
|
4151
|
-
if (
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4156
|
-
}
|
|
4151
|
+
if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
|
|
4152
|
+
let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
|
|
4153
|
+
if (!isIP) {
|
|
4154
|
+
host = normalizePercentEncoding(host.toLowerCase());
|
|
4157
4155
|
}
|
|
4156
|
+
parsed.host = reescapeHostDelimiters(host, isIP);
|
|
4157
|
+
}
|
|
4158
|
+
if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
|
|
4158
4159
|
if (parsed.path) {
|
|
4159
4160
|
parsed.path = normalizePathEncoding(parsed.path);
|
|
4160
4161
|
}
|
|
@@ -35678,7 +35679,7 @@ var StdioServerTransport = class {
|
|
|
35678
35679
|
import { createClient } from "@supabase/supabase-js";
|
|
35679
35680
|
|
|
35680
35681
|
// src/version.ts
|
|
35681
|
-
var VERSION = "0.
|
|
35682
|
+
var VERSION = "0.11.0";
|
|
35682
35683
|
|
|
35683
35684
|
// src/auth.ts
|
|
35684
35685
|
var SUPABASE_PROJECT_URL = "https://tifewzsimmyoiepblhld.supabase.co";
|
|
@@ -36881,7 +36882,7 @@ function registerTransactionTools(server2) {
|
|
|
36881
36882
|
server2.registerTool(
|
|
36882
36883
|
"kirjapro_create_transaction",
|
|
36883
36884
|
{
|
|
36884
|
-
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.",
|
|
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.",
|
|
36885
36886
|
inputSchema: {
|
|
36886
36887
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
36887
36888
|
transactionDate: ISO_DATE_SCHEMA.describe("Transaction date (YYYY-MM-DD)"),
|
|
@@ -37020,7 +37021,7 @@ function registerTransactionTools(server2) {
|
|
|
37020
37021
|
server2.registerTool(
|
|
37021
37022
|
"kirjapro_create_transactions_batch",
|
|
37022
37023
|
{
|
|
37023
|
-
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.",
|
|
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.",
|
|
37024
37025
|
inputSchema: {
|
|
37025
37026
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
37026
37027
|
dryRun: external_exports.boolean().default(true).describe(
|
|
@@ -38438,6 +38439,83 @@ function registerCustomerTools(server2) {
|
|
|
38438
38439
|
);
|
|
38439
38440
|
}
|
|
38440
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
|
+
|
|
38441
38519
|
// src/lib/incomeStatementSynthesis.ts
|
|
38442
38520
|
function synthesizeIncomeStatementLines(lineTypes, currentByLine, previousByLine) {
|
|
38443
38521
|
const lineAmounts = new Map(currentByLine);
|
|
@@ -38592,15 +38670,35 @@ var ALV_SUMMARY_OUTPUT_SHAPE = {
|
|
|
38592
38670
|
input_vat: external_exports.unknown().optional(),
|
|
38593
38671
|
net_vat_payable: external_exports.number().optional(),
|
|
38594
38672
|
net_vat_payable_formatted: external_exports.string().optional(),
|
|
38595
|
-
|
|
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(),
|
|
38596
38680
|
zero_vat_turnover: external_exports.number().optional(),
|
|
38597
38681
|
eu_goods_sales_base: external_exports.number().optional(),
|
|
38598
38682
|
reverse_charge_sales_base: external_exports.number().optional(),
|
|
38599
38683
|
eu_service_sales_base: external_exports.number().optional(),
|
|
38600
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(),
|
|
38688
|
+
// get_alv_summary.reconciliation: the report against the 4210/2320 ledger
|
|
38689
|
+
// (ALV report audit 2026-10-01, T2-1). Absent on an older database.
|
|
38690
|
+
reconciliation: external_exports.object({ status: external_exports.enum(["match", "explained", "mismatch"]) }).passthrough().optional(),
|
|
38601
38691
|
warning: external_exports.string().optional(),
|
|
38602
38692
|
message: external_exports.string().optional()
|
|
38603
38693
|
};
|
|
38694
|
+
var LEGACY_REVERSE_CHARGE_WARNING = "Legacy reverse-charge invoice lines must be reclassified before the fields are complete.";
|
|
38695
|
+
function alvReconciliationWarning(reconciliation) {
|
|
38696
|
+
if (!reconciliation || typeof reconciliation !== "object") return void 0;
|
|
38697
|
+
const r = reconciliation;
|
|
38698
|
+
if (r.status !== "mismatch") return void 0;
|
|
38699
|
+
const amount = (value) => (Number(value) || 0).toFixed(2);
|
|
38700
|
+
return `Ledger accounts 4210/2320 differ from the ALV report (output VAT difference ${amount(r.output_difference)} \u20AC, input VAT difference ${amount(r.input_difference)} \u20AC). Review reconciliation before filing.`;
|
|
38701
|
+
}
|
|
38604
38702
|
var BALANCE_SHEET_ROW_SHAPE = external_exports.object({
|
|
38605
38703
|
account_number: external_exports.string(),
|
|
38606
38704
|
name: external_exports.string(),
|
|
@@ -39076,7 +39174,7 @@ function registerReportTools(server2) {
|
|
|
39076
39174
|
server2.registerTool(
|
|
39077
39175
|
"kirjapro_get_alv_summary",
|
|
39078
39176
|
{
|
|
39079
|
-
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.",
|
|
39080
39178
|
inputSchema: {
|
|
39081
39179
|
dateFrom: ISO_DATE_SCHEMA.describe("Period start date (YYYY-MM-DD)"),
|
|
39082
39180
|
dateTo: ISO_DATE_SCHEMA.describe("Period end date (YYYY-MM-DD)")
|
|
@@ -39116,24 +39214,21 @@ function registerReportTools(server2) {
|
|
|
39116
39214
|
});
|
|
39117
39215
|
}
|
|
39118
39216
|
const raw = result;
|
|
39119
|
-
const
|
|
39120
|
-
|
|
39121
|
-
|
|
39122
|
-
|
|
39123
|
-
|
|
39124
|
-
|
|
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));
|
|
39125
39226
|
return toResult({
|
|
39126
39227
|
...safeResult,
|
|
39228
|
+
...warnings.length > 0 && { warning: warnings.join(" ") },
|
|
39127
39229
|
period: { from: params.dateFrom, to: params.dateTo },
|
|
39128
39230
|
net_vat_payable_formatted: formatCurrency(result.net_vat_payable || 0),
|
|
39129
|
-
omavero_fields:
|
|
39130
|
-
"309": Number(raw.zero_vat_turnover) || 0,
|
|
39131
|
-
"311": Number(raw.eu_goods_sales_base) || 0,
|
|
39132
|
-
"312": Number(raw.eu_service_sales_base) || 0,
|
|
39133
|
-
"318": Number(raw.output_vat_construction_scrap) || 0,
|
|
39134
|
-
"319": Number(raw.reverse_charge_sales_base) || 0,
|
|
39135
|
-
"320": (Number(raw.field_310_base) || 0) + (Number(raw.field_313_base) || 0)
|
|
39136
|
-
}
|
|
39231
|
+
omavero_fields: omaveroFields
|
|
39137
39232
|
});
|
|
39138
39233
|
}
|
|
39139
39234
|
);
|
|
@@ -39745,6 +39840,35 @@ var ACCOUNTS = {
|
|
|
39745
39840
|
VAKUUTUKSET: "7970"
|
|
39746
39841
|
};
|
|
39747
39842
|
|
|
39843
|
+
// src/compliance/vat-guard.ts
|
|
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.";
|
|
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
|
+
}
|
|
39860
|
+
var NOT_VAT_REGISTERED_SETTLEMENT_REFUSAL = "This company is not VAT-registered (ALV), so it has no VAT settlement to book.";
|
|
39861
|
+
async function readCompanyVat(supabase2, companyId2) {
|
|
39862
|
+
const { data, error: error61 } = await supabase2.from("companies").select("alv_registered, vat_period").eq("id", companyId2).single();
|
|
39863
|
+
if (error61 || !data) {
|
|
39864
|
+
return toErrorContent(
|
|
39865
|
+
error61 ? sanitizeDbError("Failed to verify the company VAT registration", error61) : "Failed to verify the company VAT registration."
|
|
39866
|
+
);
|
|
39867
|
+
}
|
|
39868
|
+
const row = data;
|
|
39869
|
+
return { alvRegistered: row.alv_registered === true, vatPeriod: row.vat_period ?? "monthly" };
|
|
39870
|
+
}
|
|
39871
|
+
|
|
39748
39872
|
// src/tools/templates.ts
|
|
39749
39873
|
function splitVat(gross, vatRate) {
|
|
39750
39874
|
if (vatRate === 0) return { net: roundCents(gross), vat: 0 };
|
|
@@ -39844,24 +39968,28 @@ var TEMPLATES = [
|
|
|
39844
39968
|
// Cash sales
|
|
39845
39969
|
{
|
|
39846
39970
|
id: "cash_sale_25",
|
|
39971
|
+
kind: "sale",
|
|
39847
39972
|
description: "K\xE4teismyynti ALV 25,5%",
|
|
39848
39973
|
vatRate: 25.5,
|
|
39849
39974
|
expand: saleCashTemplate(ACCOUNTS.MYYNTI_25_5, 25.5)
|
|
39850
39975
|
},
|
|
39851
39976
|
{
|
|
39852
39977
|
id: "cash_sale_13_5",
|
|
39978
|
+
kind: "sale",
|
|
39853
39979
|
description: "K\xE4teismyynti ALV 13,5%",
|
|
39854
39980
|
vatRate: 13.5,
|
|
39855
39981
|
expand: saleCashTemplate(ACCOUNTS.MYYNTI_13_5, 13.5)
|
|
39856
39982
|
},
|
|
39857
39983
|
{
|
|
39858
39984
|
id: "cash_sale_10",
|
|
39985
|
+
kind: "sale",
|
|
39859
39986
|
description: "K\xE4teismyynti ALV 10%",
|
|
39860
39987
|
vatRate: 10,
|
|
39861
39988
|
expand: saleCashTemplate(ACCOUNTS.MYYNTI_10, 10)
|
|
39862
39989
|
},
|
|
39863
39990
|
{
|
|
39864
39991
|
id: "cash_sale_0",
|
|
39992
|
+
kind: "sale",
|
|
39865
39993
|
description: "K\xE4teismyynti ALV 0%",
|
|
39866
39994
|
vatRate: 0,
|
|
39867
39995
|
expand: saleCashTemplate(ACCOUNTS.MYYNTI_0, 0)
|
|
@@ -39869,18 +39997,21 @@ var TEMPLATES = [
|
|
|
39869
39997
|
// Bank sales
|
|
39870
39998
|
{
|
|
39871
39999
|
id: "bank_sale_25",
|
|
40000
|
+
kind: "sale",
|
|
39872
40001
|
description: "Pankkimyynti ALV 25,5%",
|
|
39873
40002
|
vatRate: 25.5,
|
|
39874
40003
|
expand: saleBankTemplate(ACCOUNTS.MYYNTI_25_5, 25.5)
|
|
39875
40004
|
},
|
|
39876
40005
|
{
|
|
39877
40006
|
id: "bank_sale_13_5",
|
|
40007
|
+
kind: "sale",
|
|
39878
40008
|
description: "Pankkimyynti ALV 13,5%",
|
|
39879
40009
|
vatRate: 13.5,
|
|
39880
40010
|
expand: saleBankTemplate(ACCOUNTS.MYYNTI_13_5, 13.5)
|
|
39881
40011
|
},
|
|
39882
40012
|
{
|
|
39883
40013
|
id: "bank_sale_10",
|
|
40014
|
+
kind: "sale",
|
|
39884
40015
|
description: "Pankkimyynti ALV 10%",
|
|
39885
40016
|
vatRate: 10,
|
|
39886
40017
|
expand: saleBankTemplate(ACCOUNTS.MYYNTI_10, 10)
|
|
@@ -39888,6 +40019,7 @@ var TEMPLATES = [
|
|
|
39888
40019
|
// Invoice sale
|
|
39889
40020
|
{
|
|
39890
40021
|
id: "invoice_sale_25",
|
|
40022
|
+
kind: "sale",
|
|
39891
40023
|
description: "Laskumyynti ALV 25,5% (myyntisaamiset)",
|
|
39892
40024
|
vatRate: 25.5,
|
|
39893
40025
|
expand: (amount, opts) => {
|
|
@@ -39909,6 +40041,7 @@ var TEMPLATES = [
|
|
|
39909
40041
|
// Purchase expenses
|
|
39910
40042
|
{
|
|
39911
40043
|
id: "purchase_expense",
|
|
40044
|
+
kind: "purchase",
|
|
39912
40045
|
description: "Osto ALV 25,5% (pankkitililt\xE4)",
|
|
39913
40046
|
vatRate: 25.5,
|
|
39914
40047
|
expand: (amount, opts) => {
|
|
@@ -39929,6 +40062,7 @@ var TEMPLATES = [
|
|
|
39929
40062
|
},
|
|
39930
40063
|
{
|
|
39931
40064
|
id: "service_expense",
|
|
40065
|
+
kind: "purchase",
|
|
39932
40066
|
description: "Ulkopuolinen palvelu ALV 25,5%",
|
|
39933
40067
|
vatRate: 25.5,
|
|
39934
40068
|
expand: (amount, opts) => {
|
|
@@ -39950,6 +40084,7 @@ var TEMPLATES = [
|
|
|
39950
40084
|
// Rent (no VAT)
|
|
39951
40085
|
{
|
|
39952
40086
|
id: "rent_payment",
|
|
40087
|
+
kind: "other",
|
|
39953
40088
|
description: "Vuokranmaksu (pankkitililt\xE4)",
|
|
39954
40089
|
vatRate: null,
|
|
39955
40090
|
expand: (amount, opts) => applyOverrides(
|
|
@@ -39971,6 +40106,7 @@ var TEMPLATES = [
|
|
|
39971
40106
|
// Bank transfer
|
|
39972
40107
|
{
|
|
39973
40108
|
id: "bank_transfer",
|
|
40109
|
+
kind: "other",
|
|
39974
40110
|
description: "Tilisiirto kassasta pankkitilille",
|
|
39975
40111
|
vatRate: null,
|
|
39976
40112
|
expand: (amount, opts) => applyOverrides(
|
|
@@ -39991,6 +40127,21 @@ var TEMPLATES = [
|
|
|
39991
40127
|
}
|
|
39992
40128
|
];
|
|
39993
40129
|
var TEMPLATE_MAP = new Map(TEMPLATES.map((t) => [t.id, t]));
|
|
40130
|
+
function isSaleTemplate(templateId) {
|
|
40131
|
+
return TEMPLATE_MAP.get(templateId)?.kind === "sale";
|
|
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
|
+
}
|
|
40142
|
+
function availableTemplates(alvRegistered) {
|
|
40143
|
+
return alvRegistered ? TEMPLATES.filter((t) => t.kind !== "sale") : TEMPLATES.filter((t) => !isVatPurchaseTemplate(t.id));
|
|
40144
|
+
}
|
|
39994
40145
|
function expandTemplate(templateId, amount, opts) {
|
|
39995
40146
|
const template = TEMPLATE_MAP.get(templateId);
|
|
39996
40147
|
if (!template) {
|
|
@@ -40053,7 +40204,7 @@ function toJournalEntries(entries, accountMap) {
|
|
|
40053
40204
|
credit_amount: e.creditAmount
|
|
40054
40205
|
}));
|
|
40055
40206
|
}
|
|
40056
|
-
async function
|
|
40207
|
+
async function preparePosting(params) {
|
|
40057
40208
|
const supabase2 = getSupabase();
|
|
40058
40209
|
const companyId2 = getCompanyId();
|
|
40059
40210
|
let fiscalYearId = params.fiscalYearId;
|
|
@@ -40082,16 +40233,41 @@ async function resolveAndCreate(params) {
|
|
|
40082
40233
|
`Template produced unbalanced entries: debits ${totalDebit.toFixed(2)} != credits ${totalCredit.toFixed(2)}.`
|
|
40083
40234
|
);
|
|
40084
40235
|
}
|
|
40085
|
-
|
|
40086
|
-
|
|
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(
|
|
40087
40263
|
"create_transaction_with_entries",
|
|
40088
40264
|
{
|
|
40089
|
-
p_company_id:
|
|
40090
|
-
p_fiscal_year_id: fiscalYearId,
|
|
40265
|
+
p_company_id: getCompanyId(),
|
|
40266
|
+
p_fiscal_year_id: prepared.fiscalYearId,
|
|
40091
40267
|
p_transaction_date: params.transactionDate,
|
|
40092
40268
|
p_description: params.description,
|
|
40093
40269
|
p_document_number: params.documentNumber || "",
|
|
40094
|
-
p_entries: journalEntries
|
|
40270
|
+
p_entries: prepared.journalEntries
|
|
40095
40271
|
}
|
|
40096
40272
|
);
|
|
40097
40273
|
if (txError || !transactionId) {
|
|
@@ -40105,7 +40281,7 @@ function registerTemplateTools(server2) {
|
|
|
40105
40281
|
server2.registerTool(
|
|
40106
40282
|
"kirjapro_list_templates",
|
|
40107
40283
|
{
|
|
40108
|
-
description: "List available transaction templates. Templates handle double-entry balancing and VAT splitting automatically.",
|
|
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.",
|
|
40109
40285
|
inputSchema: {},
|
|
40110
40286
|
outputSchema: {
|
|
40111
40287
|
templates: external_exports.array(external_exports.object({
|
|
@@ -40117,7 +40293,9 @@ function registerTemplateTools(server2) {
|
|
|
40117
40293
|
annotations: READ_ONLY_ANNOTATIONS
|
|
40118
40294
|
},
|
|
40119
40295
|
async () => {
|
|
40120
|
-
const
|
|
40296
|
+
const companyVat = await readCompanyVat(getSupabase(), getCompanyId());
|
|
40297
|
+
if ("isError" in companyVat) return companyVat;
|
|
40298
|
+
const list = availableTemplates(companyVat.alvRegistered).map((t) => ({
|
|
40121
40299
|
id: t.id,
|
|
40122
40300
|
description: t.description,
|
|
40123
40301
|
vatRate: t.vatRate
|
|
@@ -40128,7 +40306,7 @@ function registerTemplateTools(server2) {
|
|
|
40128
40306
|
server2.registerTool(
|
|
40129
40307
|
"kirjapro_create_from_template",
|
|
40130
40308
|
{
|
|
40131
|
-
description: 'Create a transaction from a template. Use kirjapro_list_templates for available templates. accountOverrides swaps default accounts (e.g. {"2320": "2330"}).',
|
|
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).',
|
|
40132
40310
|
inputSchema: {
|
|
40133
40311
|
template: external_exports.string().describe('Template ID (e.g. "cash_sale_25", "purchase_expense")'),
|
|
40134
40312
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
@@ -40144,6 +40322,16 @@ function registerTemplateTools(server2) {
|
|
|
40144
40322
|
async (params) => {
|
|
40145
40323
|
const supabase2 = getSupabase();
|
|
40146
40324
|
const companyId2 = getCompanyId();
|
|
40325
|
+
const template = TEMPLATE_MAP.get(params.template);
|
|
40326
|
+
let alvRegistered = null;
|
|
40327
|
+
if (template && (template.kind === "sale" || template.kind === "purchase")) {
|
|
40328
|
+
const companyVat = await readCompanyVat(supabase2, companyId2);
|
|
40329
|
+
if ("isError" in companyVat) return companyVat;
|
|
40330
|
+
alvRegistered = companyVat.alvRegistered;
|
|
40331
|
+
if (template.kind === "sale" && alvRegistered) {
|
|
40332
|
+
return toErrorContent(ALV_REGISTERED_SALE_REFUSAL);
|
|
40333
|
+
}
|
|
40334
|
+
}
|
|
40147
40335
|
return withIdempotency(
|
|
40148
40336
|
supabase2,
|
|
40149
40337
|
companyId2,
|
|
@@ -40161,7 +40349,27 @@ function registerTemplateTools(server2) {
|
|
|
40161
40349
|
if ("error" in result) {
|
|
40162
40350
|
return toErrorContent(result.error);
|
|
40163
40351
|
}
|
|
40164
|
-
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({
|
|
40165
40373
|
fiscalYearId: params.fiscalYearId,
|
|
40166
40374
|
transactionDate: params.transactionDate,
|
|
40167
40375
|
description: params.description,
|
|
@@ -40175,7 +40383,8 @@ function registerTemplateTools(server2) {
|
|
|
40175
40383
|
message: "Transaction created from template",
|
|
40176
40384
|
template: params.template,
|
|
40177
40385
|
transaction_id: createResult.id,
|
|
40178
|
-
entries: result.entries
|
|
40386
|
+
entries: result.entries,
|
|
40387
|
+
..."vatEntryIds" in createResult && { vat_entry_ids: createResult.vatEntryIds }
|
|
40179
40388
|
});
|
|
40180
40389
|
}
|
|
40181
40390
|
);
|
|
@@ -40184,7 +40393,7 @@ function registerTemplateTools(server2) {
|
|
|
40184
40393
|
server2.registerTool(
|
|
40185
40394
|
"kirjapro_create_from_template_batch",
|
|
40186
40395
|
{
|
|
40187
|
-
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.",
|
|
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.",
|
|
40188
40397
|
inputSchema: {
|
|
40189
40398
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
40190
40399
|
dryRun: external_exports.boolean().default(true).describe(
|
|
@@ -40207,6 +40416,27 @@ function registerTemplateTools(server2) {
|
|
|
40207
40416
|
async (params) => {
|
|
40208
40417
|
const supabase2 = getSupabase();
|
|
40209
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
|
+
}
|
|
40430
|
+
const saleIndexes = params.transactions.map((tx, index) => isSaleTemplate(tx.template) ? index : -1).filter((index) => index >= 0);
|
|
40431
|
+
if (saleIndexes.length > 0) {
|
|
40432
|
+
const companyVat = await readCompanyVat(supabase2, companyId2);
|
|
40433
|
+
if ("isError" in companyVat) return companyVat;
|
|
40434
|
+
if (companyVat.alvRegistered) {
|
|
40435
|
+
return toErrorContent(
|
|
40436
|
+
`${ALV_REGISTERED_SALE_REFUSAL} Sale templates at index ${saleIndexes.join(", ")}; nothing was written.`
|
|
40437
|
+
);
|
|
40438
|
+
}
|
|
40439
|
+
}
|
|
40210
40440
|
return withIdempotency(
|
|
40211
40441
|
supabase2,
|
|
40212
40442
|
companyId2,
|
|
@@ -40430,6 +40660,35 @@ function freshnessMeta(rates = FINNISH_RATES_2026, today = /* @__PURE__ */ new D
|
|
|
40430
40660
|
};
|
|
40431
40661
|
}
|
|
40432
40662
|
|
|
40663
|
+
// src/compliance/vat-settlement-date.ts
|
|
40664
|
+
var pad = (value) => String(value).padStart(2, "0");
|
|
40665
|
+
function vatPeriodRange(month, vatPeriod) {
|
|
40666
|
+
const [yearText, monthText] = month.split("-");
|
|
40667
|
+
const year = Number(yearText);
|
|
40668
|
+
const monthNumber = Number(monthText);
|
|
40669
|
+
const period = vatPeriod ?? "monthly";
|
|
40670
|
+
const startMonth = period === "yearly" ? 1 : period === "quarterly" ? Math.floor((monthNumber - 1) / 3) * 3 + 1 : monthNumber;
|
|
40671
|
+
const endMonth = period === "yearly" ? 12 : period === "quarterly" ? startMonth + 2 : monthNumber;
|
|
40672
|
+
const lastDay = new Date(Date.UTC(year, endMonth, 0)).getUTCDate();
|
|
40673
|
+
const label = period === "yearly" ? String(year) : period === "quarterly" ? `${year}-Q${(startMonth + 2) / 3}` : month;
|
|
40674
|
+
return {
|
|
40675
|
+
start: `${year}-${pad(startMonth)}-01`,
|
|
40676
|
+
end: `${year}-${pad(endMonth)}-${pad(lastDay)}`,
|
|
40677
|
+
label
|
|
40678
|
+
};
|
|
40679
|
+
}
|
|
40680
|
+
function defaultSettlementDate(month, vatPeriod) {
|
|
40681
|
+
const { end } = vatPeriodRange(month, vatPeriod);
|
|
40682
|
+
const [endYear, endMonth] = end.split("-").map(Number);
|
|
40683
|
+
if ((vatPeriod ?? "monthly") === "yearly") {
|
|
40684
|
+
const februaryDays = new Date(Date.UTC(endYear + 1, 2, 0)).getUTCDate();
|
|
40685
|
+
return `${endYear + 1}-02-${pad(februaryDays)}`;
|
|
40686
|
+
}
|
|
40687
|
+
const dueMonthIndex = endMonth - 1 + 2;
|
|
40688
|
+
const dueYear = endYear + Math.floor(dueMonthIndex / 12);
|
|
40689
|
+
return `${dueYear}-${pad(dueMonthIndex % 12 + 1)}-12`;
|
|
40690
|
+
}
|
|
40691
|
+
|
|
40433
40692
|
// src/tools/bookings.ts
|
|
40434
40693
|
function expenseAccountWarning(account) {
|
|
40435
40694
|
const first = account.trim().charAt(0);
|
|
@@ -40456,7 +40715,7 @@ function registerBookingTools(server2) {
|
|
|
40456
40715
|
server2.registerTool(
|
|
40457
40716
|
"kirjapro_book_purchase",
|
|
40458
40717
|
{
|
|
40459
|
-
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%.",
|
|
40460
40719
|
inputSchema: {
|
|
40461
40720
|
amount: external_exports.number().finite().positive().max(1e9).describe(
|
|
40462
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)."
|
|
@@ -40465,7 +40724,11 @@ function registerBookingTools(server2) {
|
|
|
40465
40724
|
date: ISO_DATE_SCHEMA.describe("Transaction date (YYYY-MM-DD)"),
|
|
40466
40725
|
vatRate: external_exports.enum(["25.5", "13.5", "10", "0"]).describe("Finnish VAT rate"),
|
|
40467
40726
|
expenseAccount: external_exports.string().describe('Expense account number (e.g. "8080")'),
|
|
40468
|
-
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"),
|
|
40469
40732
|
edustus: external_exports.boolean().default(false).describe("Entertainment expense (AVL 114\xA7): 0% VAT deduction, full gross to expense"),
|
|
40470
40733
|
paymentAccount: external_exports.string().default(ACCOUNTS.PANKKITILI).describe(`Payment account (default "${ACCOUNTS.PANKKITILI}" Pankkitili)`),
|
|
40471
40734
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
@@ -40479,6 +40742,16 @@ function registerBookingTools(server2) {
|
|
|
40479
40742
|
const rate = Number.parseFloat(params.vatRate);
|
|
40480
40743
|
const gross = roundCents(params.amount);
|
|
40481
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
|
+
}
|
|
40751
|
+
if (params.reverseCharge && !params.edustus) {
|
|
40752
|
+
if (!params.reverseChargeCategory) return toErrorContent(REVERSE_CHARGE_CATEGORY_REQUIRED);
|
|
40753
|
+
if (rate === 0) return toErrorContent(REVERSE_CHARGE_RATE_REQUIRED);
|
|
40754
|
+
}
|
|
40482
40755
|
if (params.edustus) {
|
|
40483
40756
|
entries = [
|
|
40484
40757
|
{ accountNumber: params.expenseAccount, debitAmount: gross, creditAmount: 0 },
|
|
@@ -40494,6 +40767,14 @@ function registerBookingTools(server2) {
|
|
|
40494
40767
|
// ALV-velka (output)
|
|
40495
40768
|
{ accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
|
|
40496
40769
|
];
|
|
40770
|
+
vatRows = [
|
|
40771
|
+
{
|
|
40772
|
+
category: params.reverseChargeCategory ?? "eu_services",
|
|
40773
|
+
vat_rate: rate,
|
|
40774
|
+
tax_base: gross,
|
|
40775
|
+
vat_amount: vat
|
|
40776
|
+
}
|
|
40777
|
+
];
|
|
40497
40778
|
} else if (rate === 0) {
|
|
40498
40779
|
entries = [
|
|
40499
40780
|
{ accountNumber: params.expenseAccount, debitAmount: gross, creditAmount: 0 },
|
|
@@ -40506,6 +40787,7 @@ function registerBookingTools(server2) {
|
|
|
40506
40787
|
{ accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat, creditAmount: 0 },
|
|
40507
40788
|
{ accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
|
|
40508
40789
|
];
|
|
40790
|
+
vatRows = [{ category: "domestic", vat_rate: rate, tax_base: net, vat_amount: vat }];
|
|
40509
40791
|
}
|
|
40510
40792
|
const purchaseAccountWarning = expenseAccountWarning(params.expenseAccount);
|
|
40511
40793
|
if (params.dryRun) {
|
|
@@ -40516,6 +40798,7 @@ function registerBookingTools(server2) {
|
|
|
40516
40798
|
description: params.description,
|
|
40517
40799
|
date: params.date,
|
|
40518
40800
|
entries,
|
|
40801
|
+
...vatRows.length > 0 && { vat_rows: vatRows },
|
|
40519
40802
|
...purchaseAccountWarning && { account_warning: purchaseAccountWarning }
|
|
40520
40803
|
});
|
|
40521
40804
|
}
|
|
@@ -40526,18 +40809,24 @@ function registerBookingTools(server2) {
|
|
|
40526
40809
|
params.idempotencyKey,
|
|
40527
40810
|
params,
|
|
40528
40811
|
async () => {
|
|
40529
|
-
const
|
|
40812
|
+
const posting = {
|
|
40530
40813
|
fiscalYearId: params.fiscalYearId,
|
|
40531
40814
|
transactionDate: params.date,
|
|
40532
40815
|
description: params.description,
|
|
40533
40816
|
documentNumber: params.documentNumber,
|
|
40534
40817
|
entries
|
|
40535
|
-
}
|
|
40818
|
+
};
|
|
40819
|
+
const result = vatRows.length > 0 ? await resolveAndCreatePurchase({
|
|
40820
|
+
...posting,
|
|
40821
|
+
vatRows,
|
|
40822
|
+
supplierName: params.supplierName
|
|
40823
|
+
}) : await resolveAndCreate(posting);
|
|
40536
40824
|
if ("isError" in result) return result;
|
|
40537
40825
|
return toResult({
|
|
40538
40826
|
message: "Purchase booked",
|
|
40539
40827
|
transaction_id: result.id,
|
|
40540
40828
|
entries,
|
|
40829
|
+
..."vatEntryIds" in result && { vat_entry_ids: result.vatEntryIds },
|
|
40541
40830
|
...purchaseAccountWarning && { warning: purchaseAccountWarning }
|
|
40542
40831
|
});
|
|
40543
40832
|
}
|
|
@@ -40547,11 +40836,11 @@ function registerBookingTools(server2) {
|
|
|
40547
40836
|
server2.registerTool(
|
|
40548
40837
|
"kirjapro_book_vat_settlement",
|
|
40549
40838
|
{
|
|
40550
|
-
description: `Book ALV-tilitys (VAT settlement) for
|
|
40839
|
+
description: `Book ALV-tilitys (VAT settlement) for the company's VAT period that contains the given month. Settles the period's net from ALV accounts (${ACCOUNTS.ALV_SAAMISET}/${ACCOUNTS.ALV_VELKA}) by voucher date, excluding earlier settlements, closing entries and the previous-year import. Default date: the statutory due date (12th of the second month after the period; annual period: end of February), not moved to a business day. The period follows the company's VAT period. Refuses a period already settled, a period whose ledger does not match the ALV report, and negative balances.`,
|
|
40551
40840
|
inputSchema: {
|
|
40552
|
-
month: external_exports.string().regex(/^\d{4}
|
|
40553
|
-
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (
|
|
40554
|
-
settlementDate: ISO_DATE_SCHEMA.optional().describe("Settlement date (default: 12th of month
|
|
40841
|
+
month: external_exports.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).describe("A month of the VAT period to settle (YYYY-MM); a quarterly or annual company settles the whole period containing it"),
|
|
40842
|
+
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID of the settlement date (default: the open fiscal year containing the settlement date)"),
|
|
40843
|
+
settlementDate: ISO_DATE_SCHEMA.optional().describe("Settlement date (default: the statutory due date, 12th of the second month after the period; annual period: end of February)"),
|
|
40555
40844
|
paymentAccount: external_exports.string().default(ACCOUNTS.PANKKITILI).describe(`Bank account (default "${ACCOUNTS.PANKKITILI}" Pankkitili)`),
|
|
40556
40845
|
idempotencyKey: IDEMPOTENCY_KEY_SCHEMA,
|
|
40557
40846
|
dryRun: DRY_RUN_SCHEMA
|
|
@@ -40561,16 +40850,25 @@ function registerBookingTools(server2) {
|
|
|
40561
40850
|
async (params) => {
|
|
40562
40851
|
const supabase2 = getSupabase();
|
|
40563
40852
|
const companyId2 = getCompanyId();
|
|
40853
|
+
const companyVat = await readCompanyVat(supabase2, companyId2);
|
|
40854
|
+
if ("isError" in companyVat) return companyVat;
|
|
40855
|
+
if (!companyVat.alvRegistered) return toErrorContent(NOT_VAT_REGISTERED_SETTLEMENT_REFUSAL);
|
|
40856
|
+
const period = vatPeriodRange(params.month, companyVat.vatPeriod);
|
|
40857
|
+
const dueDate = defaultSettlementDate(params.month, companyVat.vatPeriod);
|
|
40858
|
+
const settlementDate = params.settlementDate ?? dueDate;
|
|
40564
40859
|
let fiscalYearId = params.fiscalYearId;
|
|
40565
40860
|
if (!fiscalYearId) {
|
|
40566
|
-
const
|
|
40567
|
-
if (
|
|
40568
|
-
|
|
40861
|
+
const { data: years, error: yearsError } = await supabase2.from("fiscal_years").select("id").eq("company_id", companyId2).eq("is_closed", false).lte("start_date", settlementDate).gte("end_date", settlementDate).order("start_date", { ascending: false }).limit(1);
|
|
40862
|
+
if (yearsError) {
|
|
40863
|
+
return toErrorContent(sanitizeDbError("Failed to query fiscal years", yearsError));
|
|
40864
|
+
}
|
|
40865
|
+
if (!years || years.length === 0) {
|
|
40866
|
+
return toErrorContent(
|
|
40867
|
+
`No open fiscal year contains the settlement date ${settlementDate} (statutory due date for ${period.label}: ${dueDate}). Create that fiscal year or pass settlementDate inside an open fiscal year.`
|
|
40868
|
+
);
|
|
40869
|
+
}
|
|
40870
|
+
fiscalYearId = years[0].id;
|
|
40569
40871
|
}
|
|
40570
|
-
const [year, monthStr] = params.month.split("-");
|
|
40571
|
-
const monthNum = Number.parseInt(monthStr, 10);
|
|
40572
|
-
const nextMonth = monthNum === 12 ? `${Number.parseInt(year, 10) + 1}-01` : `${year}-${String(monthNum + 1).padStart(2, "0")}`;
|
|
40573
|
-
const settlementDate = params.settlementDate ?? `${nextMonth}-12`;
|
|
40574
40872
|
const { data: fy } = await supabase2.from("fiscal_years").select("start_date, end_date, is_closed").eq("id", fiscalYearId).eq("company_id", companyId2).single();
|
|
40575
40873
|
if (!fy) {
|
|
40576
40874
|
return toErrorContent(
|
|
@@ -40584,7 +40882,7 @@ function registerBookingTools(server2) {
|
|
|
40584
40882
|
}
|
|
40585
40883
|
if (settlementDate > fy.end_date) {
|
|
40586
40884
|
return toErrorContent(
|
|
40587
|
-
`Settlement date ${settlementDate} is after fiscal year end ${fy.end_date}. Pass an explicit settlementDate within ${fy.start_date}..${fy.end_date}
|
|
40885
|
+
`Settlement date ${settlementDate} is after fiscal year end ${fy.end_date}. Pass an explicit settlementDate within ${fy.start_date}..${fy.end_date} or the fiscal year that contains ${settlementDate} \u2014 silent clamping would shift the ALV-kausi.`
|
|
40588
40886
|
);
|
|
40589
40887
|
}
|
|
40590
40888
|
if (settlementDate < fy.start_date) {
|
|
@@ -40596,9 +40894,14 @@ function registerBookingTools(server2) {
|
|
|
40596
40894
|
return toDryRunResponse("kirjapro_book_vat_settlement", {
|
|
40597
40895
|
action: "book_vat_settlement",
|
|
40598
40896
|
month: params.month,
|
|
40897
|
+
vat_period: companyVat.vatPeriod,
|
|
40898
|
+
period_start: period.start,
|
|
40899
|
+
period_end: period.end,
|
|
40599
40900
|
settlement_date: settlementDate,
|
|
40901
|
+
statutory_due_date: dueDate,
|
|
40902
|
+
fiscal_year_id: fiscalYearId,
|
|
40600
40903
|
payment_account: params.paymentAccount,
|
|
40601
|
-
hint:
|
|
40904
|
+
hint: `Call kirjapro_get_alv_summary for ${period.start}..${period.end} to preview the net VAT and its reconciliation before committing.`
|
|
40602
40905
|
});
|
|
40603
40906
|
}
|
|
40604
40907
|
return withIdempotency(
|
|
@@ -40621,20 +40924,27 @@ function registerBookingTools(server2) {
|
|
|
40621
40924
|
);
|
|
40622
40925
|
}
|
|
40623
40926
|
const result = data;
|
|
40927
|
+
const periodStart = result.period_start ?? period.start;
|
|
40928
|
+
const periodEnd = result.period_end ?? period.end;
|
|
40624
40929
|
if (result.direction === "none") {
|
|
40625
40930
|
return toResult({
|
|
40626
|
-
message: result.message ?? `No VAT entries found for ${
|
|
40931
|
+
message: result.message ?? `No VAT entries found for ${period.label}. Nothing to settle.`,
|
|
40627
40932
|
alv_saamiset: 0,
|
|
40628
|
-
alv_velka: 0
|
|
40933
|
+
alv_velka: 0,
|
|
40934
|
+
period_start: periodStart,
|
|
40935
|
+
period_end: periodEnd
|
|
40629
40936
|
});
|
|
40630
40937
|
}
|
|
40631
40938
|
return toResult({
|
|
40632
|
-
message: `VAT settlement booked for ${
|
|
40939
|
+
message: `VAT settlement booked for ${period.label}`,
|
|
40633
40940
|
transaction_id: result.transaction_id,
|
|
40634
40941
|
alv_saamiset: result.alv_saamiset,
|
|
40635
40942
|
alv_velka: result.alv_velka,
|
|
40636
40943
|
net_vat: result.net_vat,
|
|
40637
|
-
direction: result.direction
|
|
40944
|
+
direction: result.direction,
|
|
40945
|
+
period_start: periodStart,
|
|
40946
|
+
period_end: periodEnd,
|
|
40947
|
+
settlement_date: settlementDate
|
|
40638
40948
|
});
|
|
40639
40949
|
}
|
|
40640
40950
|
);
|
|
@@ -40643,7 +40953,7 @@ function registerBookingTools(server2) {
|
|
|
40643
40953
|
server2.registerTool(
|
|
40644
40954
|
"kirjapro_book_sale",
|
|
40645
40955
|
{
|
|
40646
|
-
description: "Book a sale (myynti) with automatic VAT splitting. Payment types: invoice (default, D Myyntisaamiset), cash (D bank), prepaid.",
|
|
40956
|
+
description: "Book a sale (myynti) with automatic VAT splitting for a company that is not VAT-registered. Payment types: invoice (default, D Myyntisaamiset), cash (D bank), prepaid. A VAT-registered (ALV) company is refused: its sales reach the ALV report only as invoices (kirjapro_create_invoice + kirjapro_send_invoice).",
|
|
40647
40957
|
inputSchema: {
|
|
40648
40958
|
amount: external_exports.number().finite().positive().max(1e9).describe("Gross EUR amount (VAT-inclusive)"),
|
|
40649
40959
|
description: external_exports.string().min(1).max(500).describe('Transaction description (e.g. "Konsultointi Acme Oy, 40h \xD7 95 \u20AC")'),
|
|
@@ -40660,6 +40970,9 @@ function registerBookingTools(server2) {
|
|
|
40660
40970
|
annotations: WRITE_ANNOTATIONS
|
|
40661
40971
|
},
|
|
40662
40972
|
async (params) => {
|
|
40973
|
+
const companyVat = await readCompanyVat(getSupabase(), getCompanyId());
|
|
40974
|
+
if ("isError" in companyVat) return companyVat;
|
|
40975
|
+
if (companyVat.alvRegistered) return toErrorContent(ALV_REGISTERED_SALE_REFUSAL);
|
|
40663
40976
|
const rate = Number.parseFloat(params.vatRate);
|
|
40664
40977
|
const gross = roundCents(params.amount);
|
|
40665
40978
|
let entries;
|
|
@@ -42187,7 +42500,7 @@ var VAT_COMPLIANCE = {
|
|
|
42187
42500
|
],
|
|
42188
42501
|
booking: "4 entries: D expense + D ALV-saamiset + C bank + C ALV-velka (k\xE4\xE4nnetty)",
|
|
42189
42502
|
note: "Net VAT effect is zero but BOTH must be reported on ALV-ilmoitus. Invoice must NOT show VAT rate or VAT amount",
|
|
42190
|
-
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."
|
|
42191
42504
|
},
|
|
42192
42505
|
common_mistakes: [
|
|
42193
42506
|
"Food/coffee at 25.5% \u2014 should be 13.5% (AVL 85\xA7)",
|
|
@@ -42266,14 +42579,18 @@ var FILING_DEADLINES = {
|
|
|
42266
42579
|
},
|
|
42267
42580
|
maksuperusteinen_alv: {
|
|
42268
42581
|
threshold: 5e5,
|
|
42269
|
-
note: "
|
|
42270
|
-
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"
|
|
42271
42586
|
},
|
|
42272
42587
|
late_penalties: {
|
|
42273
|
-
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",
|
|
42274
42589
|
viivastyskorko_2026: "9.5% for most taxes, 4.5% for inheritance tax",
|
|
42275
|
-
correction_grace: "
|
|
42276
|
-
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"
|
|
42277
42594
|
},
|
|
42278
42595
|
payment_deadline: "Same as filing deadline (12th of M+2 for monthly/quarterly, last day of Feb for annual)",
|
|
42279
42596
|
omavero_field_codes: {
|
|
@@ -42291,16 +42608,17 @@ var FILING_DEADLINES = {
|
|
|
42291
42608
|
318: "Vero rakentamispalvelun ja metalliromun ostoista (construction/scrap reverse charge VAT)"
|
|
42292
42609
|
},
|
|
42293
42610
|
tax_bases: {
|
|
42294
|
-
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.",
|
|
42295
42612
|
310: "Tavaroiden maahantuonnit EU:n ulkopuolelta (goods imports from outside EU)",
|
|
42296
|
-
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)",
|
|
42297
42614
|
312: "Palveluiden myynnit muihin EU-maihin (service sales to EU)",
|
|
42298
42615
|
313: "Tavaraostot muista EU-maista (goods purchases from EU)",
|
|
42299
42616
|
314: "Palveluostot muista EU-maista (service purchases from EU)",
|
|
42300
42617
|
319: "Rakentamispalvelun ja metalliromun myynnit (construction/scrap sales)",
|
|
42301
42618
|
320: "Rakentamispalvelun ja metalliromun ostot (construction/scrap purchases)"
|
|
42302
42619
|
},
|
|
42303
|
-
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"
|
|
42304
42622
|
}
|
|
42305
42623
|
};
|
|
42306
42624
|
var SOCIAL_INSURANCE_2026 = {
|
|
@@ -42581,7 +42899,7 @@ Do not invent a "closest match" tool for these tasks. A confidently wrong action
|
|
|
42581
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.
|
|
42582
42900
|
|
|
42583
42901
|
### Reports
|
|
42584
|
-
\`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\`).
|
|
42585
42903
|
|
|
42586
42904
|
## Safety Rules
|
|
42587
42905
|
|
|
@@ -42647,7 +42965,7 @@ var INSTRUCTIONS_CORE_ADDENDUM = `
|
|
|
42647
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:
|
|
42648
42966
|
|
|
42649
42967
|
- **Recurring invoice templates** \u2014 created and managed in the web app; cron generates the issued invoices
|
|
42650
|
-
- **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
|
|
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
|
|
42651
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
|
|
42652
42970
|
- **Transaction templates** \u2014 manual journal entries via \`kirjapro_create_transaction\`
|
|
42653
42971
|
- **Manual offer status changes** \u2014 the customer's email-link click sets status server-side; just wait for the user to confirm acceptance
|
|
@@ -42666,10 +42984,10 @@ var INSTRUCTIONS_FULL_ADDENDUM = `
|
|
|
42666
42984
|
|
|
42667
42985
|
### Booking Shortcuts
|
|
42668
42986
|
Faster than composing journal entries via \`kirjapro_create_transaction\`:
|
|
42669
|
-
- \`kirjapro_book_sale\` / \`kirjapro_book_purchase\` \u2014 one-liner with auto VAT splitting
|
|
42670
|
-
- \`kirjapro_book_vat_settlement\` \u2014
|
|
42987
|
+
- \`kirjapro_book_sale\` / \`kirjapro_book_purchase\` \u2014 one-liner with auto VAT splitting. A VAT-registered (ALV) company books sales as invoices (\`kirjapro_create_invoice\` + \`kirjapro_send_invoice\`): \`kirjapro_book_sale\` and \`kirjapro_book_purchase\` with \`reverseCharge\` are refused for it
|
|
42988
|
+
- \`kirjapro_book_vat_settlement\` \u2014 settles the company's VAT period from ALV-saamiset/ALV-velka. Call \`kirjapro_get_alv_summary\` for the period first to preview the net and its reconciliation.
|
|
42671
42989
|
- **Payroll is intentionally NOT exposed.** Direct users to Palkkaus.fi / Palkka.fi for salary calculation; the payslip can then be booked manually via \`kirjapro_create_transaction\`.
|
|
42672
|
-
- \`kirjapro_list_templates\` \u2192 \`kirjapro_create_from_template\` for common patterns (
|
|
42990
|
+
- \`kirjapro_list_templates\` \u2192 \`kirjapro_create_from_template\` for common patterns (built-in templates; the sale templates are hidden and refused for a VAT-registered company)
|
|
42673
42991
|
|
|
42674
42992
|
### Manual Offer Status
|
|
42675
42993
|
- \`kirjapro_update_offer_status\` \u2014 manual override; usually the customer's email-link click handles accept/reject server-side
|
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,15 +2,15 @@
|
|
|
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",
|
|
9
|
-
"hash": "
|
|
9
|
+
"hash": "6868bdf46073f442"
|
|
10
10
|
},
|
|
11
11
|
{
|
|
12
12
|
"name": "kirjapro_book_vat_settlement",
|
|
13
|
-
"hash": "
|
|
13
|
+
"hash": "39f807857fb81e5d"
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
16
|
"name": "kirjapro_convert_offer_to_invoice",
|
|
@@ -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",
|