@kirjapro/mcp-server 0.10.0 → 0.12.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 +82 -1
- package/dist/index.js +1050 -335
- package/package.json +1 -1
- package/server.json +1 -1
- package/tool-fingerprints.json +10 -10
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.12.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(
|
|
@@ -37395,7 +37395,7 @@ function validateVatTreatmentEligibility({
|
|
|
37395
37395
|
if (code === "standard_13_5" && documentDate < "2026-01-01") {
|
|
37396
37396
|
return "standard_13_5 is not valid before 2026-01-01.";
|
|
37397
37397
|
}
|
|
37398
|
-
const requiresBusiness = code.startsWith("reverse_") || code === "intra_eu_goods";
|
|
37398
|
+
const requiresBusiness = code.startsWith("reverse_") || code === "intra_eu_goods" || code === "service_outside_eu";
|
|
37399
37399
|
if (requiresBusiness && customer?.customer_kind !== "business") {
|
|
37400
37400
|
return "The selected VAT treatment requires a business customer.";
|
|
37401
37401
|
}
|
|
@@ -37410,7 +37410,7 @@ function validateVatTreatmentEligibility({
|
|
|
37410
37410
|
return "EU sale requires a non-Finnish EU country and a matching buyer VAT number.";
|
|
37411
37411
|
}
|
|
37412
37412
|
}
|
|
37413
|
-
if (code === "export_outside_eu") {
|
|
37413
|
+
if (code === "export_outside_eu" || code === "service_outside_eu") {
|
|
37414
37414
|
const country = (customer?.country ?? "").trim().toUpperCase();
|
|
37415
37415
|
if (!country || EU_COUNTRY_CODES.has(country)) {
|
|
37416
37416
|
return "Export outside the EU requires a buyer country outside the EU.";
|
|
@@ -37423,7 +37423,7 @@ function validateVatTreatmentEligibility({
|
|
|
37423
37423
|
var CREDIT_PAGE_SIZE = 500;
|
|
37424
37424
|
async function fetchFinalizedCreditTotal(supabase2, companyId2, invoiceId) {
|
|
37425
37425
|
let offset = 0;
|
|
37426
|
-
let
|
|
37426
|
+
let total2 = 0;
|
|
37427
37427
|
let expectedCount;
|
|
37428
37428
|
for (; ; ) {
|
|
37429
37429
|
const { data, count, error: error61 } = await supabase2.from("invoices").select("id, total", { count: "exact" }).eq("company_id", companyId2).eq("document_type", "credit_note").eq("status", "sent").eq("original_invoice_id", invoiceId).order("id", { ascending: true }).range(offset, offset + CREDIT_PAGE_SIZE - 1);
|
|
@@ -37441,24 +37441,24 @@ async function fetchFinalizedCreditTotal(supabase2, companyId2, invoiceId) {
|
|
|
37441
37441
|
if (!Number.isFinite(credit.total)) {
|
|
37442
37442
|
return { total: null, error: { message: "A finalized credit has an invalid amount." } };
|
|
37443
37443
|
}
|
|
37444
|
-
|
|
37444
|
+
total2 += credit.total;
|
|
37445
37445
|
}
|
|
37446
37446
|
offset += data.length;
|
|
37447
|
-
if (offset === count) return { total: roundCents(
|
|
37447
|
+
if (offset === count) return { total: roundCents(total2), error: null };
|
|
37448
37448
|
}
|
|
37449
37449
|
}
|
|
37450
|
-
function calculateInvoiceBalance(
|
|
37450
|
+
function calculateInvoiceBalance(total2, payments, creditedTotal) {
|
|
37451
37451
|
const paidTotal = roundCents(
|
|
37452
37452
|
(payments ?? []).reduce((sum, payment) => sum + (payment.amount || 0), 0)
|
|
37453
37453
|
);
|
|
37454
|
-
return { paidTotal, remainingBalance: roundCents(
|
|
37454
|
+
return { paidTotal, remainingBalance: roundCents(total2 - paidTotal - creditedTotal) };
|
|
37455
37455
|
}
|
|
37456
37456
|
|
|
37457
37457
|
// src/tools/invoices.ts
|
|
37458
37458
|
function generateReferenceNumber(baseNumber) {
|
|
37459
|
-
const digits = baseNumber.replace(/\D/g, "");
|
|
37460
|
-
if (digits.length <
|
|
37461
|
-
throw new Error("Base number must be
|
|
37459
|
+
const digits = baseNumber.replace(/\D/g, "").replace(/^0+/, "");
|
|
37460
|
+
if (digits.length < 3 || digits.length > 19) {
|
|
37461
|
+
throw new Error("Base number must be 3-19 digits without leading zeros");
|
|
37462
37462
|
}
|
|
37463
37463
|
const weights = [7, 3, 1];
|
|
37464
37464
|
let sum = 0;
|
|
@@ -37597,6 +37597,7 @@ function registerInvoiceTools(server2) {
|
|
|
37597
37597
|
"reverse_scrap",
|
|
37598
37598
|
"reverse_eu_service",
|
|
37599
37599
|
"export_outside_eu",
|
|
37600
|
+
"service_outside_eu",
|
|
37600
37601
|
"intra_eu_goods",
|
|
37601
37602
|
"exempt_healthcare",
|
|
37602
37603
|
"exempt_social_welfare",
|
|
@@ -37604,7 +37605,7 @@ function registerInvoiceTools(server2) {
|
|
|
37604
37605
|
"exempt_financial",
|
|
37605
37606
|
"exempt_insurance",
|
|
37606
37607
|
"exempt_other"
|
|
37607
|
-
]).optional().describe("Structured VAT treatment. Required when VAT rate is 0.")
|
|
37608
|
+
]).optional().describe("Structured VAT treatment. Required when VAT rate is 0. service_outside_eu is ONLY for AVL 65 general-rule B2B services: verify business use, evidence of buyer status and receiving establishment outside the EU; special place-of-supply services are excluded. Check foreign buyer liability and invoice notes. PDF delivery only; e-invoice delivery is blocked.")
|
|
37608
37609
|
})
|
|
37609
37610
|
).min(1).max(MAX_DOCUMENT_LINES).describe(`Invoice line items (max ${MAX_DOCUMENT_LINES})`),
|
|
37610
37611
|
idempotencyKey: IDEMPOTENCY_KEY_SCHEMA
|
|
@@ -37658,6 +37659,7 @@ function registerInvoiceTools(server2) {
|
|
|
37658
37659
|
reverse_scrap: 0,
|
|
37659
37660
|
reverse_eu_service: 0,
|
|
37660
37661
|
export_outside_eu: 0,
|
|
37662
|
+
service_outside_eu: 0,
|
|
37661
37663
|
intra_eu_goods: 0,
|
|
37662
37664
|
exempt_healthcare: 0,
|
|
37663
37665
|
exempt_social_welfare: 0,
|
|
@@ -37691,7 +37693,7 @@ function registerInvoiceTools(server2) {
|
|
|
37691
37693
|
const gross = roundCents(line.quantity * line.unitPrice);
|
|
37692
37694
|
const vatAmount = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? roundCents(gross * vatRate / (100 + vatRate)) : roundCents(gross * (vatRate / 100));
|
|
37693
37695
|
const subtotal2 = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? roundCents(gross - vatAmount) : gross;
|
|
37694
|
-
const
|
|
37696
|
+
const total3 = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? gross : roundCents(subtotal2 + vatAmount);
|
|
37695
37697
|
return {
|
|
37696
37698
|
line_number: i + 1,
|
|
37697
37699
|
description: line.description,
|
|
@@ -37709,7 +37711,7 @@ function registerInvoiceTools(server2) {
|
|
|
37709
37711
|
eu_goods_transport_confirmed: params.euGoodsTransportConfirmed,
|
|
37710
37712
|
subtotal: subtotal2,
|
|
37711
37713
|
vat_amount: vatAmount,
|
|
37712
|
-
total:
|
|
37714
|
+
total: total3
|
|
37713
37715
|
};
|
|
37714
37716
|
});
|
|
37715
37717
|
const subtotal = roundCents(
|
|
@@ -37718,7 +37720,7 @@ function registerInvoiceTools(server2) {
|
|
|
37718
37720
|
const vatTotal = roundCents(
|
|
37719
37721
|
calculatedLines.reduce((s, l) => s + l.vat_amount, 0)
|
|
37720
37722
|
);
|
|
37721
|
-
const
|
|
37723
|
+
const total2 = roundCents(subtotal + vatTotal);
|
|
37722
37724
|
const { data: invoiceId, error: invError } = await supabase2.rpc(
|
|
37723
37725
|
"create_invoice_with_lines",
|
|
37724
37726
|
{
|
|
@@ -37731,7 +37733,7 @@ function registerInvoiceTools(server2) {
|
|
|
37731
37733
|
p_status: "draft",
|
|
37732
37734
|
p_subtotal: subtotal,
|
|
37733
37735
|
p_vat_total: vatTotal,
|
|
37734
|
-
p_total:
|
|
37736
|
+
p_total: total2,
|
|
37735
37737
|
p_notes: params.notes || "",
|
|
37736
37738
|
p_lines: calculatedLines
|
|
37737
37739
|
}
|
|
@@ -38439,6 +38441,83 @@ function registerCustomerTools(server2) {
|
|
|
38439
38441
|
);
|
|
38440
38442
|
}
|
|
38441
38443
|
|
|
38444
|
+
// src/compliance/omavero-fields.ts
|
|
38445
|
+
var OMAVERO_FIELD_NUMBERS = [
|
|
38446
|
+
"301",
|
|
38447
|
+
"302",
|
|
38448
|
+
"303",
|
|
38449
|
+
"304",
|
|
38450
|
+
"305",
|
|
38451
|
+
"306",
|
|
38452
|
+
"307",
|
|
38453
|
+
"308",
|
|
38454
|
+
"309",
|
|
38455
|
+
"310",
|
|
38456
|
+
"311",
|
|
38457
|
+
"312",
|
|
38458
|
+
"313",
|
|
38459
|
+
"314",
|
|
38460
|
+
"318",
|
|
38461
|
+
"319",
|
|
38462
|
+
"320"
|
|
38463
|
+
];
|
|
38464
|
+
var LEGACY_ALV_SUMMARY_KEYS = [
|
|
38465
|
+
"field_307",
|
|
38466
|
+
"field_307_base",
|
|
38467
|
+
"field_308",
|
|
38468
|
+
"field_308_base",
|
|
38469
|
+
"field_309",
|
|
38470
|
+
"field_309_base",
|
|
38471
|
+
"field_310",
|
|
38472
|
+
"field_310_base",
|
|
38473
|
+
"field_312",
|
|
38474
|
+
"field_312_base",
|
|
38475
|
+
"field_313",
|
|
38476
|
+
"field_313_base",
|
|
38477
|
+
"field_318",
|
|
38478
|
+
"field_319",
|
|
38479
|
+
"field_320",
|
|
38480
|
+
"total_zero_rated",
|
|
38481
|
+
"reverse_charge_base",
|
|
38482
|
+
"omavero"
|
|
38483
|
+
];
|
|
38484
|
+
function cents(value) {
|
|
38485
|
+
return Math.round(value * 100) / 100;
|
|
38486
|
+
}
|
|
38487
|
+
function num(value) {
|
|
38488
|
+
const n = typeof value === "number" ? value : Number(value ?? 0);
|
|
38489
|
+
return Number.isFinite(n) ? n : 0;
|
|
38490
|
+
}
|
|
38491
|
+
function omaveroFieldsFromSummary(s) {
|
|
38492
|
+
const fields = {
|
|
38493
|
+
"301": num(s.field_301),
|
|
38494
|
+
"302": num(s.field_302),
|
|
38495
|
+
"303": num(s.field_303),
|
|
38496
|
+
"304": num(s.output_vat_import),
|
|
38497
|
+
"305": num(s.output_vat_eu_goods),
|
|
38498
|
+
"306": num(s.output_vat_eu_services),
|
|
38499
|
+
"307": num(s.total_input_vat),
|
|
38500
|
+
"308": num(s.net_vat_payable),
|
|
38501
|
+
"309": num(s.zero_vat_turnover),
|
|
38502
|
+
"310": num(s.field_312_base),
|
|
38503
|
+
"311": num(s.eu_goods_sales_base),
|
|
38504
|
+
"312": num(s.eu_service_sales_base),
|
|
38505
|
+
"313": num(s.field_308_base),
|
|
38506
|
+
"314": num(s.field_309_base),
|
|
38507
|
+
"318": num(s.output_vat_construction_scrap),
|
|
38508
|
+
"319": num(s.reverse_charge_sales_base),
|
|
38509
|
+
"320": cents(num(s.field_310_base) + num(s.field_313_base))
|
|
38510
|
+
};
|
|
38511
|
+
const map2 = s.omavero;
|
|
38512
|
+
if (map2 && typeof map2 === "object") {
|
|
38513
|
+
const values = map2;
|
|
38514
|
+
for (const field of OMAVERO_FIELD_NUMBERS) {
|
|
38515
|
+
if (values[field] !== void 0 && values[field] !== null) fields[field] = num(values[field]);
|
|
38516
|
+
}
|
|
38517
|
+
}
|
|
38518
|
+
return fields;
|
|
38519
|
+
}
|
|
38520
|
+
|
|
38442
38521
|
// src/lib/incomeStatementSynthesis.ts
|
|
38443
38522
|
function synthesizeIncomeStatementLines(lineTypes, currentByLine, previousByLine) {
|
|
38444
38523
|
const lineAmounts = new Map(currentByLine);
|
|
@@ -38593,24 +38672,34 @@ var ALV_SUMMARY_OUTPUT_SHAPE = {
|
|
|
38593
38672
|
input_vat: external_exports.unknown().optional(),
|
|
38594
38673
|
net_vat_payable: external_exports.number().optional(),
|
|
38595
38674
|
net_vat_payable_formatted: external_exports.string().optional(),
|
|
38596
|
-
|
|
38675
|
+
// Every official OmaVero field (VSRALVKV v1.10: 301-314, 318-320), ALV
|
|
38676
|
+
// report audit 2026-10-01, T2-11. The only field-numbered output.
|
|
38677
|
+
omavero_fields: external_exports.record(external_exports.string(), external_exports.number()).describe(
|
|
38678
|
+
"Official OmaVero VAT-return fields 301-314 and 318-320. 307 is all deductible VAT; 308 = 301+302+303+304+305+306+318 - 307."
|
|
38679
|
+
).optional(),
|
|
38680
|
+
// Sales without a deduction right: in no OmaVero field (informational).
|
|
38681
|
+
exempt_sales_base: external_exports.number().optional(),
|
|
38597
38682
|
zero_vat_turnover: external_exports.number().optional(),
|
|
38598
38683
|
eu_goods_sales_base: external_exports.number().optional(),
|
|
38599
38684
|
reverse_charge_sales_base: external_exports.number().optional(),
|
|
38600
38685
|
eu_service_sales_base: external_exports.number().optional(),
|
|
38601
38686
|
unclassified_legacy_reverse_charge_base: external_exports.number().optional(),
|
|
38687
|
+
// VAT on services bought from outside the EU, already inside field_301-303
|
|
38688
|
+
// and total_output_vat (ALV report audit 2026-10-01, T2-13).
|
|
38689
|
+
output_vat_non_eu_services: external_exports.number().optional(),
|
|
38602
38690
|
// get_alv_summary.reconciliation: the report against the 4210/2320 ledger
|
|
38603
38691
|
// (ALV report audit 2026-10-01, T2-1). Absent on an older database.
|
|
38604
38692
|
reconciliation: external_exports.object({ status: external_exports.enum(["match", "explained", "mismatch"]) }).passthrough().optional(),
|
|
38605
38693
|
warning: external_exports.string().optional(),
|
|
38606
38694
|
message: external_exports.string().optional()
|
|
38607
38695
|
};
|
|
38696
|
+
var LEGACY_REVERSE_CHARGE_WARNING = "Legacy reverse-charge invoice lines must be reclassified before the fields are complete.";
|
|
38608
38697
|
function alvReconciliationWarning(reconciliation) {
|
|
38609
38698
|
if (!reconciliation || typeof reconciliation !== "object") return void 0;
|
|
38610
38699
|
const r = reconciliation;
|
|
38611
|
-
if (r.status !== "mismatch") return void 0;
|
|
38700
|
+
if (r.status !== "mismatch" && r.status !== "explained") return void 0;
|
|
38612
38701
|
const amount = (value) => (Number(value) || 0).toFixed(2);
|
|
38613
|
-
return `
|
|
38702
|
+
return `VAT amounts or the audit trail need review (output VAT difference ${amount(r.output_difference)} \u20AC, input VAT difference ${amount(r.input_difference)} \u20AC). Equal amounts do not link a manual VAT row to a voucher. Review reconciliation before filing; do not duplicate the purchase expense.`;
|
|
38614
38703
|
}
|
|
38615
38704
|
var BALANCE_SHEET_ROW_SHAPE = external_exports.object({
|
|
38616
38705
|
account_number: external_exports.string(),
|
|
@@ -39087,7 +39176,7 @@ function registerReportTools(server2) {
|
|
|
39087
39176
|
server2.registerTool(
|
|
39088
39177
|
"kirjapro_get_alv_summary",
|
|
39089
39178
|
{
|
|
39090
|
-
description: "Get VAT (ALV) summary for a date range. Returns
|
|
39179
|
+
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
39180
|
inputSchema: {
|
|
39092
39181
|
dateFrom: ISO_DATE_SCHEMA.describe("Period start date (YYYY-MM-DD)"),
|
|
39093
39182
|
dateTo: ISO_DATE_SCHEMA.describe("Period end date (YYYY-MM-DD)")
|
|
@@ -39127,26 +39216,21 @@ function registerReportTools(server2) {
|
|
|
39127
39216
|
});
|
|
39128
39217
|
}
|
|
39129
39218
|
const raw = result;
|
|
39130
|
-
const
|
|
39131
|
-
|
|
39132
|
-
|
|
39133
|
-
|
|
39134
|
-
|
|
39135
|
-
|
|
39136
|
-
|
|
39219
|
+
const omaveroFields = omaveroFieldsFromSummary(raw);
|
|
39220
|
+
const legacyKeys = new Set(LEGACY_ALV_SUMMARY_KEYS);
|
|
39221
|
+
const safeResult = Object.fromEntries(
|
|
39222
|
+
Object.entries(raw).filter(([key]) => !legacyKeys.has(key))
|
|
39223
|
+
);
|
|
39224
|
+
const warnings = [
|
|
39225
|
+
alvReconciliationWarning(raw.reconciliation),
|
|
39226
|
+
Number(raw.unclassified_legacy_reverse_charge_base) ? LEGACY_REVERSE_CHARGE_WARNING : void 0
|
|
39227
|
+
].filter((text) => Boolean(text));
|
|
39137
39228
|
return toResult({
|
|
39138
39229
|
...safeResult,
|
|
39139
|
-
...
|
|
39230
|
+
...warnings.length > 0 && { warning: warnings.join(" ") },
|
|
39140
39231
|
period: { from: params.dateFrom, to: params.dateTo },
|
|
39141
39232
|
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
|
-
}
|
|
39233
|
+
omavero_fields: omaveroFields
|
|
39150
39234
|
});
|
|
39151
39235
|
}
|
|
39152
39236
|
);
|
|
@@ -39612,9 +39696,9 @@ function validateFinnishIBAN(input2) {
|
|
|
39612
39696
|
return { valid: true };
|
|
39613
39697
|
}
|
|
39614
39698
|
function generateReferenceNumber2(baseNumber) {
|
|
39615
|
-
const digits = baseNumber.replace(/\D/g, "");
|
|
39616
|
-
if (digits.length <
|
|
39617
|
-
return { reference: "", error: "Base number must be
|
|
39699
|
+
const digits = baseNumber.replace(/\D/g, "").replace(/^0+/, "");
|
|
39700
|
+
if (digits.length < 3 || digits.length > 19) {
|
|
39701
|
+
return { reference: "", error: "Base number must be 3-19 digits without leading zeros." };
|
|
39618
39702
|
}
|
|
39619
39703
|
const weights = [7, 3, 1];
|
|
39620
39704
|
let sum = 0;
|
|
@@ -39666,7 +39750,7 @@ function registerValidationTools(server2) {
|
|
|
39666
39750
|
{
|
|
39667
39751
|
description: "Generate a Finnish bank reference number (viitenumero) from a base number. Appends the 7-3-1 weighted check digit. No database access \u2014 pure computation.",
|
|
39668
39752
|
inputSchema: {
|
|
39669
|
-
baseNumber: external_exports.string().describe('Base number (
|
|
39753
|
+
baseNumber: external_exports.string().describe('Base number (3-19 digits excluding leading zeros, e.g. "20260042")')
|
|
39670
39754
|
},
|
|
39671
39755
|
outputSchema: REFERENCE_OUTPUT_SHAPE,
|
|
39672
39756
|
annotations: READ_ONLY_ANNOTATIONS
|
|
@@ -39715,6 +39799,392 @@ function registerFiscalYearTools(server2) {
|
|
|
39715
39799
|
);
|
|
39716
39800
|
}
|
|
39717
39801
|
|
|
39802
|
+
// src/compliance/purchase-vat-input.ts
|
|
39803
|
+
var PURCHASE_VAT_BASIS_SCHEMA = external_exports.object({
|
|
39804
|
+
version: external_exports.literal(1),
|
|
39805
|
+
kind: external_exports.enum([
|
|
39806
|
+
"supply",
|
|
39807
|
+
"supply_month",
|
|
39808
|
+
"advance_payment",
|
|
39809
|
+
"continuous_period_end",
|
|
39810
|
+
"continuous_year_end",
|
|
39811
|
+
"eu_acquisition_month",
|
|
39812
|
+
"customs_acceptance",
|
|
39813
|
+
"original_document_rate"
|
|
39814
|
+
]),
|
|
39815
|
+
date: ISO_DATE_SCHEMA.optional(),
|
|
39816
|
+
month: external_exports.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).optional(),
|
|
39817
|
+
evidence_reference: external_exports.string().trim().min(1).max(2e3),
|
|
39818
|
+
correction_reason: external_exports.string().trim().min(1).max(500).optional()
|
|
39819
|
+
}).strict().describe("Confirmed rate-selection evidence, not the posting date. Supply/advance/period-end/customs use date; supply_month and EU acquisition use month. Advance date is receipt of funds by seller. original_document_rate requires correction_reason and the original document/period reference, no invented date.");
|
|
39820
|
+
|
|
39821
|
+
// src/compliance/regulatory-data.ts
|
|
39822
|
+
var MCP_REGISTRY = {
|
|
39823
|
+
schemaVersion: 1,
|
|
39824
|
+
jurisdiction: "FI",
|
|
39825
|
+
sources: {
|
|
39826
|
+
vat: {
|
|
39827
|
+
title: "Verohallinto: arvonlis\xE4veroprosentit",
|
|
39828
|
+
url: "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/arvonlisaveroprosentit/"
|
|
39829
|
+
},
|
|
39830
|
+
"vat-2013-transition": {
|
|
39831
|
+
title: "Verohallinto A119/200/2012: arvonlis\xE4verokantojen muutos 1.1.2013 (virallisohjeen Edilex-kopio)",
|
|
39832
|
+
url: "https://www.edilex.fi/verohallinnon_ohjeet/2012_1220.html",
|
|
39833
|
+
watch: false
|
|
39834
|
+
},
|
|
39835
|
+
"vat-vehicles": {
|
|
39836
|
+
title: "Vero: ajoneuvojen ja k\xE4ytt\xF6kulujen ALV",
|
|
39837
|
+
url: "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/ajoneuvon-ja-sen-kayttokulujen-arvonlisavero/"
|
|
39838
|
+
},
|
|
39839
|
+
"vat-own-use": {
|
|
39840
|
+
title: "Vero: oman k\xE4yt\xF6n ja alivastikkeellisten luovutusten ALV",
|
|
39841
|
+
url: "https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/47899/oman-k%C3%A4yt%C3%B6n-ja-alivastikkeellisten-luovutusten-arvonlis%C3%A4verotus/"
|
|
39842
|
+
},
|
|
39843
|
+
"vat-return": {
|
|
39844
|
+
title: "Vero: ALV-ilmoituksen t\xE4ytt\xF6ohje",
|
|
39845
|
+
url: "https://www.vero.fi/alv/tayttoohje"
|
|
39846
|
+
},
|
|
39847
|
+
"vat-small-business": {
|
|
39848
|
+
title: "Vero: arvonlis\xE4veroton v\xE4h\xE4inen toiminta",
|
|
39849
|
+
url: "https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48658/arvonlisaveroton-vahainen-toiminta/"
|
|
39850
|
+
},
|
|
39851
|
+
"vat-recap": {
|
|
39852
|
+
title: "Vero: yhteenvetoilmoitus",
|
|
39853
|
+
url: "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/ulkomaankauppa/yhteisokauppa/yhteenvetoilmoitus-ilmoitusohjeet/"
|
|
39854
|
+
},
|
|
39855
|
+
"vat-invoices": {
|
|
39856
|
+
title: "Vero: laskutusvaatimukset",
|
|
39857
|
+
url: "https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48090/laskutusvaatimukset-arvonlisaverotuksessa3/"
|
|
39858
|
+
},
|
|
39859
|
+
"einvoice-law": {
|
|
39860
|
+
title: "Valtiokonttori: verkkolaskulaki ja valtion vastaanotto",
|
|
39861
|
+
url: "https://www.valtiokonttori.fi/verkkolasku/"
|
|
39862
|
+
},
|
|
39863
|
+
payroll: {
|
|
39864
|
+
title: "Verohallinto: sosiaalivakuutusmaksut",
|
|
39865
|
+
url: "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/yritys_tyonantajana/sosiaalivakuutusmaksut/"
|
|
39866
|
+
},
|
|
39867
|
+
unemployment: {
|
|
39868
|
+
title: "Ty\xF6llisyysrahasto: vahvistetut maksut 2026",
|
|
39869
|
+
url: "https://www.tyollisyysrahasto.fi/uutiset/vuoden-2026-tyottomyysvakuutusmaksut-on-vahvistettu/"
|
|
39870
|
+
},
|
|
39871
|
+
unemploymentScope: {
|
|
39872
|
+
title: "Ty\xF6llisyysrahasto: maksuvelvollisuus",
|
|
39873
|
+
url: "https://www.tyollisyysrahasto.fi/tyottomyysvakuusmaksut/tyottomyysvakuutusmaksuvelvollisuus/"
|
|
39874
|
+
},
|
|
39875
|
+
tyel: {
|
|
39876
|
+
title: "Ilmarinen: TyEL-maksut",
|
|
39877
|
+
url: "https://www.ilmarinen.fi/tyonantaja/tyel-maksut/"
|
|
39878
|
+
},
|
|
39879
|
+
"incomes-register": {
|
|
39880
|
+
title: "Tulorekisteri: palkkojen ilmoittaminen",
|
|
39881
|
+
url: "https://www.vero.fi/tulorekisteri/yritykset-ja-organisaatiot/yksityiskohtaiset-tulorekisterin-ohjeet/90124/palkat-tietojen-ilmoittaminen-tulorekisteriin6/"
|
|
39882
|
+
},
|
|
39883
|
+
"company-size": {
|
|
39884
|
+
title: "Suomi.fi: tilinp\xE4\xE4t\xF6s",
|
|
39885
|
+
url: "https://www.suomi.fi/yritykselle/talouden-hallinta-ja-verotus/yrityksen-taloushallinto/opas/tilinpaatos-ja-tilintarkastus/tilinpaatos"
|
|
39886
|
+
},
|
|
39887
|
+
"bookkeeping-act": {
|
|
39888
|
+
title: "Finlex: ajantasainen kirjanpitolaki",
|
|
39889
|
+
url: "https://www.finlex.fi/fi/laki/ajantasa/1997/19971336",
|
|
39890
|
+
format: "finlex-akn",
|
|
39891
|
+
workUri: "/akn/fi/act/statute-consolidated/1997/1336",
|
|
39892
|
+
requiredText: "Luettelo kirjanpidoista ja aineistoista"
|
|
39893
|
+
},
|
|
39894
|
+
"bookkeeping-snapshot": {
|
|
39895
|
+
title: "Kirjanpitolaki, ajantasaisen lain virallinen tallenne",
|
|
39896
|
+
url: "https://eur-lex.europa.eu/legal-content/FI/TXT/PDF/?uri=NIM%3A202604826",
|
|
39897
|
+
watch: false
|
|
39898
|
+
},
|
|
39899
|
+
"small-company-transition": {
|
|
39900
|
+
title: "KILA 1982/2018: yksitt\xE4isen pienyrityksen kokorajasiirtym\xE4, kohta 2.2",
|
|
39901
|
+
url: "https://kirjanpitolautakunta.fi/-/pienyrityksen-ja-pienkonsernin-raja-arvojen-arvioimises-1"
|
|
39902
|
+
}
|
|
39903
|
+
},
|
|
39904
|
+
rules: {
|
|
39905
|
+
vat: {
|
|
39906
|
+
kind: "statutory",
|
|
39907
|
+
units: {
|
|
39908
|
+
general: "percent",
|
|
39909
|
+
reduced: "percent",
|
|
39910
|
+
newspapers: "percent",
|
|
39911
|
+
zero: "percent"
|
|
39912
|
+
},
|
|
39913
|
+
conditions: "Kanta valitaan tapahtuman ja hy\xF6dykkeen verokohtelun perusteella. 0 % ei ole sama kuin verosta vapautettu myynti. Ei muuta tallennettujen tapahtumien historiallisia kantoja.",
|
|
39914
|
+
periods: [
|
|
39915
|
+
{
|
|
39916
|
+
validFrom: "2013-01-01",
|
|
39917
|
+
validUntil: "2024-08-31",
|
|
39918
|
+
reviewedOn: "2026-10-03",
|
|
39919
|
+
reviewDue: "2026-11-20",
|
|
39920
|
+
sourceIds: ["vat", "vat-2013-transition"],
|
|
39921
|
+
values: {
|
|
39922
|
+
general: 24,
|
|
39923
|
+
reduced: 14,
|
|
39924
|
+
newspapers: 10,
|
|
39925
|
+
zero: 0
|
|
39926
|
+
}
|
|
39927
|
+
},
|
|
39928
|
+
{
|
|
39929
|
+
validFrom: "2024-09-01",
|
|
39930
|
+
validUntil: "2025-12-31",
|
|
39931
|
+
reviewedOn: "2026-09-05",
|
|
39932
|
+
reviewDue: "2026-11-20",
|
|
39933
|
+
sourceIds: ["vat"],
|
|
39934
|
+
values: {
|
|
39935
|
+
general: 25.5,
|
|
39936
|
+
reduced: 14,
|
|
39937
|
+
newspapers: 10,
|
|
39938
|
+
zero: 0
|
|
39939
|
+
}
|
|
39940
|
+
},
|
|
39941
|
+
{
|
|
39942
|
+
validFrom: "2026-01-01",
|
|
39943
|
+
validUntil: null,
|
|
39944
|
+
reviewedOn: "2026-09-06",
|
|
39945
|
+
reviewDue: "2026-11-20",
|
|
39946
|
+
sourceIds: [
|
|
39947
|
+
"vat",
|
|
39948
|
+
"vat-vehicles",
|
|
39949
|
+
"vat-own-use",
|
|
39950
|
+
"vat-return",
|
|
39951
|
+
"vat-small-business",
|
|
39952
|
+
"vat-recap",
|
|
39953
|
+
"vat-invoices",
|
|
39954
|
+
"einvoice-law"
|
|
39955
|
+
],
|
|
39956
|
+
values: {
|
|
39957
|
+
general: 25.5,
|
|
39958
|
+
reduced: 13.5,
|
|
39959
|
+
newspapers: 10,
|
|
39960
|
+
zero: 0
|
|
39961
|
+
}
|
|
39962
|
+
}
|
|
39963
|
+
]
|
|
39964
|
+
},
|
|
39965
|
+
payroll: {
|
|
39966
|
+
kind: "statutory",
|
|
39967
|
+
units: {
|
|
39968
|
+
tyelEmployee: "percent",
|
|
39969
|
+
health: "percent",
|
|
39970
|
+
unemploymentEmployee: "percent",
|
|
39971
|
+
unemploymentLower: "percent",
|
|
39972
|
+
unemploymentUpper: "percent",
|
|
39973
|
+
unemploymentThreshold: "EUR",
|
|
39974
|
+
unemploymentExemption: "EUR",
|
|
39975
|
+
tyelMonthlyMinimum: "EUR"
|
|
39976
|
+
},
|
|
39977
|
+
conditions: "Tavallinen Suomessa vakuutettu 18\u201364-vuotias ty\xF6ntekij\xE4, ei osaomistaja, yritt\xE4j\xE4 tai ulkomaanty\xF6. TyEL ja ty\xF6nantajan vakuutusten sopimushinnat eritell\xE4\xE4n arvioista.",
|
|
39978
|
+
periods: [
|
|
39979
|
+
{
|
|
39980
|
+
validFrom: "2026-01-01",
|
|
39981
|
+
validUntil: "2026-12-31",
|
|
39982
|
+
reviewedOn: "2026-09-06",
|
|
39983
|
+
reviewDue: "2026-11-20",
|
|
39984
|
+
sourceIds: ["payroll", "unemployment", "unemploymentScope", "tyel", "incomes-register"],
|
|
39985
|
+
values: {
|
|
39986
|
+
tyelEmployee: 7.3,
|
|
39987
|
+
health: 1.91,
|
|
39988
|
+
unemploymentEmployee: 0.89,
|
|
39989
|
+
unemploymentLower: 0.31,
|
|
39990
|
+
unemploymentUpper: 1.23,
|
|
39991
|
+
unemploymentThreshold: 2509500,
|
|
39992
|
+
unemploymentExemption: 1500,
|
|
39993
|
+
tyelMonthlyMinimum: 71.72
|
|
39994
|
+
}
|
|
39995
|
+
}
|
|
39996
|
+
]
|
|
39997
|
+
},
|
|
39998
|
+
payrollEstimates: {
|
|
39999
|
+
kind: "estimate",
|
|
40000
|
+
units: {
|
|
40001
|
+
tyelBaseEmployer: "percent",
|
|
40002
|
+
tyelAverageEmployer: "percent",
|
|
40003
|
+
tyelTemporaryEmployer: "percent",
|
|
40004
|
+
accidentLow: "percent",
|
|
40005
|
+
accidentAverage: "percent",
|
|
40006
|
+
accidentHigh: "percent",
|
|
40007
|
+
accidentVeryHigh: "percent",
|
|
40008
|
+
groupLife: "percent",
|
|
40009
|
+
holidayBonus: "months/year",
|
|
40010
|
+
workingHours: "hours/month"
|
|
40011
|
+
},
|
|
40012
|
+
conditions: "TyEL-vaihtoehdot ovat mallin arvioita (perusmaksu ilman yhti\xF6kohtaista hoitokustannusta, keskiarvo, tilap\xE4inen). Tapaturma- ja ryhm\xE4henkivakuutus ovat sopimuskohtaisia. Lomaraha mallinnetaan puolen kuukausipalkan suuruisena, ei lakis\xE4\xE4teisen\xE4 etuna.",
|
|
40013
|
+
periods: [
|
|
40014
|
+
{
|
|
40015
|
+
validFrom: "2026-01-01",
|
|
40016
|
+
validUntil: "2026-12-31",
|
|
40017
|
+
reviewedOn: "2026-09-05",
|
|
40018
|
+
reviewDue: "2026-11-20",
|
|
40019
|
+
sourceIds: ["tyel", "payroll"],
|
|
40020
|
+
values: {
|
|
40021
|
+
tyelBaseEmployer: 17.55,
|
|
40022
|
+
tyelAverageEmployer: 17.1,
|
|
40023
|
+
tyelTemporaryEmployer: 18.55,
|
|
40024
|
+
accidentLow: 0.25,
|
|
40025
|
+
accidentAverage: 0.51,
|
|
40026
|
+
accidentHigh: 1.5,
|
|
40027
|
+
accidentVeryHigh: 2.8,
|
|
40028
|
+
groupLife: 0.06,
|
|
40029
|
+
holidayBonus: 0.5,
|
|
40030
|
+
workingHours: 158
|
|
40031
|
+
}
|
|
40032
|
+
}
|
|
40033
|
+
]
|
|
40034
|
+
},
|
|
40035
|
+
companySizes: {
|
|
40036
|
+
kind: "statutory",
|
|
40037
|
+
conditions: "Kokoluokka, toiminimen tilinp\xE4\xE4t\xF6svelvollisuus ja kirjanpitotapa ovat eri testej\xE4. Rajan saavuttaminen ei ole ylitys. Toiminimen tilinp\xE4\xE4t\xF6svelvollisuus: v\xE4hint\xE4\xE4n kaksi mikroyrityksen rajaa ylittyy sek\xE4 p\xE4\xE4ttyneell\xE4 ett\xE4 edelt\xE4v\xE4ll\xE4 tilikaudella; my\xF6s kalenterivuodesta poikkeava tilikausi edellytt\xE4\xE4 tilinp\xE4\xE4t\xF6st\xE4. Vakiintunut pienyritysluokka vaihtuu vasta kahden per\xE4kk\xE4isen tilikauden saman suuntaisen rajamuutoksen j\xE4lkeen (KILA 1982/2018, 2.2); yksi ylitysvuosi ei yksin poista pienyritysasemaa. Aiempi kokoluokka ja todellinen ensimm\xE4inen tilikausi ratkaistaan erikseen. Rekisterin 2026 kattavuus ei ole lain 605/2024 voimaantulo: uudet kokorajat ovat pakolliset 1.1.2025 alkaville tilikausille, valinnaiset 1.1.2024 alkaville. Kahdenkertaista kirjanpitoa edellytet\xE4\xE4n, jos v\xE4hint\xE4\xE4n kaksi sen omaa rajaa ylittyy p\xE4\xE4ttyneell\xE4 TAI edelt\xE4v\xE4ll\xE4 tilikaudella. Tilintarkastusvelvollisuus arvioidaan erikseen.",
|
|
40038
|
+
units: {
|
|
40039
|
+
soleTraderStatements: "euroa / henkil\xF6\xE4; katso avaimet",
|
|
40040
|
+
smallCompany: "euroa / henkil\xF6\xE4; katso avaimet",
|
|
40041
|
+
singleEntry: "euroa / henkil\xF6\xE4; katso avaimet"
|
|
40042
|
+
},
|
|
40043
|
+
periods: [
|
|
40044
|
+
{
|
|
40045
|
+
validFrom: "2026-01-01",
|
|
40046
|
+
validUntil: null,
|
|
40047
|
+
reviewedOn: "2026-09-06",
|
|
40048
|
+
reviewDue: "2026-12-01",
|
|
40049
|
+
sourceIds: [
|
|
40050
|
+
"company-size",
|
|
40051
|
+
"bookkeeping-act",
|
|
40052
|
+
"bookkeeping-snapshot",
|
|
40053
|
+
"small-company-transition"
|
|
40054
|
+
],
|
|
40055
|
+
values: {
|
|
40056
|
+
soleTraderStatements: {
|
|
40057
|
+
assets: 45e4,
|
|
40058
|
+
revenue: 9e5,
|
|
40059
|
+
employees: 10
|
|
40060
|
+
},
|
|
40061
|
+
smallCompany: {
|
|
40062
|
+
assets: 75e5,
|
|
40063
|
+
revenue: 15e6,
|
|
40064
|
+
employees: 50
|
|
40065
|
+
},
|
|
40066
|
+
singleEntry: {
|
|
40067
|
+
assets: 1e5,
|
|
40068
|
+
revenue: 2e5,
|
|
40069
|
+
employees: 3
|
|
40070
|
+
}
|
|
40071
|
+
}
|
|
40072
|
+
}
|
|
40073
|
+
]
|
|
40074
|
+
}
|
|
40075
|
+
}
|
|
40076
|
+
};
|
|
40077
|
+
|
|
40078
|
+
// src/compliance/date-local.ts
|
|
40079
|
+
var DATE_LOCAL_SHAPE = /^\d{4}-\d{2}-\d{2}$/;
|
|
40080
|
+
function parseDateLocal(dateStr) {
|
|
40081
|
+
if (!DATE_LOCAL_SHAPE.test(dateStr)) return new Date(Number.NaN);
|
|
40082
|
+
const [y, m, d] = dateStr.split("-").map(Number);
|
|
40083
|
+
if (y === void 0 || m === void 0 || d === void 0) return new Date(Number.NaN);
|
|
40084
|
+
return new Date(y, m - 1, d);
|
|
40085
|
+
}
|
|
40086
|
+
function formatDateLocal(date5) {
|
|
40087
|
+
const y = date5.getFullYear();
|
|
40088
|
+
const m = String(date5.getMonth() + 1).padStart(2, "0");
|
|
40089
|
+
const d = String(date5.getDate()).padStart(2, "0");
|
|
40090
|
+
return `${y}-${m}-${d}`;
|
|
40091
|
+
}
|
|
40092
|
+
|
|
40093
|
+
// src/compliance/purchase-vat-basis.ts
|
|
40094
|
+
var DAY_KINDS = /* @__PURE__ */ new Set([
|
|
40095
|
+
"supply",
|
|
40096
|
+
"advance_payment",
|
|
40097
|
+
"continuous_period_end",
|
|
40098
|
+
"continuous_year_end",
|
|
40099
|
+
"customs_acceptance"
|
|
40100
|
+
]);
|
|
40101
|
+
var MONTH_KINDS = /* @__PURE__ */ new Set(["supply_month", "eu_acquisition_month"]);
|
|
40102
|
+
var CATEGORIES = /* @__PURE__ */ new Set([
|
|
40103
|
+
"domestic",
|
|
40104
|
+
"eu_goods",
|
|
40105
|
+
"eu_services",
|
|
40106
|
+
"non_eu_services",
|
|
40107
|
+
"import",
|
|
40108
|
+
"construction",
|
|
40109
|
+
"scrap_metal"
|
|
40110
|
+
]);
|
|
40111
|
+
function isDay(value) {
|
|
40112
|
+
if (typeof value !== "string") return false;
|
|
40113
|
+
const date5 = parseDateLocal(value);
|
|
40114
|
+
return !Number.isNaN(date5.getTime()) && formatDateLocal(date5) === value;
|
|
40115
|
+
}
|
|
40116
|
+
function isMonth(value) {
|
|
40117
|
+
return typeof value === "string" && /^\d{4}-(0[1-9]|1[0-2])$/.test(value) && isDay(`${value}-01`);
|
|
40118
|
+
}
|
|
40119
|
+
function hasText(value, max = 500) {
|
|
40120
|
+
return typeof value === "string" && value.trim().length > 0 && value.length <= max;
|
|
40121
|
+
}
|
|
40122
|
+
function purchaseVatBasisError(value, category) {
|
|
40123
|
+
if (!CATEGORIES.has(category)) return "Valitse tunnettu oston ALV-k\xE4sittely.";
|
|
40124
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
40125
|
+
return "Vahvista oston ALV-kannan peruste ennen kirjausta.";
|
|
40126
|
+
const basis = value;
|
|
40127
|
+
if (basis.version !== 1 || typeof basis.kind !== "string")
|
|
40128
|
+
return "ALV-kannan peruste on puutteellinen tai sen versio ei ole tuettu.";
|
|
40129
|
+
const dayKind = DAY_KINDS.has(basis.kind);
|
|
40130
|
+
const monthKind = MONTH_KINDS.has(basis.kind);
|
|
40131
|
+
const original = basis.kind === "original_document_rate";
|
|
40132
|
+
if (!dayKind && !monthKind && !original) return "Valitse tunnettu ALV-kannan peruste.";
|
|
40133
|
+
const keys = /* @__PURE__ */ new Set([
|
|
40134
|
+
"version",
|
|
40135
|
+
"kind",
|
|
40136
|
+
"evidence_reference",
|
|
40137
|
+
...dayKind ? ["date"] : monthKind ? ["month"] : ["correction_reason"]
|
|
40138
|
+
]);
|
|
40139
|
+
if (Object.keys(basis).some((key) => !keys.has(key)))
|
|
40140
|
+
return "ALV-kannan perusteessa on ristiriitaisia tai tuntemattomia tietoja.";
|
|
40141
|
+
if (!hasText(basis.evidence_reference, 2e3))
|
|
40142
|
+
return "Yksil\xF6i ALV-kannan perusteen tosite tai muu selvitys (enint\xE4\xE4n 2000 merkki\xE4).";
|
|
40143
|
+
if (dayKind && !isDay(basis.date)) return "Anna ALV-kannan perusteen todellinen p\xE4iv\xE4m\xE4\xE4r\xE4.";
|
|
40144
|
+
if (monthKind && !isMonth(basis.month))
|
|
40145
|
+
return "Anna ALV-kannan perusteen kuukausi muodossa VVVV-KK.";
|
|
40146
|
+
if (original && !hasText(basis.correction_reason))
|
|
40147
|
+
return "Kerro oikaisun syy ja yksil\xF6i alkuper\xE4inen tosite tai hyvitetty ajanjakso.";
|
|
40148
|
+
if (!original) {
|
|
40149
|
+
if (category === "eu_goods" !== (basis.kind === "eu_acquisition_month"))
|
|
40150
|
+
return "EU-tavaraoston kanta m\xE4\xE4r\xE4ytyy yhteis\xF6hankinnan kohdistuskuukaudelta. Tarkista vastaanotto ja laskun saaminen.";
|
|
40151
|
+
if (category === "import" !== (basis.kind === "customs_acceptance"))
|
|
40152
|
+
return "Maahantuonnin kanta vaatii tulli-ilmoituksen hyv\xE4ksymisen perusteen. Tarkista tullausasiakirja.";
|
|
40153
|
+
}
|
|
40154
|
+
if (basis.kind === "continuous_year_end" && (!["eu_services", "non_eu_services"].includes(category) || !basis.date.endsWith("-12-31")))
|
|
40155
|
+
return "Vuoden lopun peruste koskee yleiss\xE4\xE4nn\xF6n jatkuvaa ulkomaanpalvelua ilman tilityksi\xE4 tai maksuja; p\xE4iv\xE4ksi tulee 31.12.";
|
|
40156
|
+
return null;
|
|
40157
|
+
}
|
|
40158
|
+
function purchaseVatRatesForBasis(basis) {
|
|
40159
|
+
let from;
|
|
40160
|
+
let until;
|
|
40161
|
+
if ("date" in basis) from = until = basis.date;
|
|
40162
|
+
if ("month" in basis) {
|
|
40163
|
+
from = `${basis.month}-01`;
|
|
40164
|
+
const first = parseDateLocal(from);
|
|
40165
|
+
until = formatDateLocal(new Date(first.getFullYear(), first.getMonth() + 1, 0));
|
|
40166
|
+
}
|
|
40167
|
+
const periods = MCP_REGISTRY.rules.vat.periods.filter(
|
|
40168
|
+
(period) => !from || period.validFrom <= from && (!period.validUntil || until !== void 0 && period.validUntil >= until)
|
|
40169
|
+
);
|
|
40170
|
+
return [
|
|
40171
|
+
...new Set(
|
|
40172
|
+
periods.flatMap((period) => [
|
|
40173
|
+
period.values.general,
|
|
40174
|
+
period.values.reduced,
|
|
40175
|
+
period.values.newspapers
|
|
40176
|
+
])
|
|
40177
|
+
)
|
|
40178
|
+
].sort((a, b) => b - a);
|
|
40179
|
+
}
|
|
40180
|
+
function purchaseVatRateError(value, category, rate) {
|
|
40181
|
+
const shape = purchaseVatBasisError(value, category);
|
|
40182
|
+
if (shape) return shape;
|
|
40183
|
+
if (!Number.isFinite(rate) || !purchaseVatRatesForBasis(value).includes(rate))
|
|
40184
|
+
return "ALV-kanta ei vastaa annettua perustetta tai sen ajankohtaa ei ole tuetussa verokantahistoriassa. Tarkista tosite ja kanta.";
|
|
40185
|
+
return null;
|
|
40186
|
+
}
|
|
40187
|
+
|
|
39718
40188
|
// src/accounts.ts
|
|
39719
40189
|
var ACCOUNTS = {
|
|
39720
40190
|
// ── Assets (2xxx) ──
|
|
@@ -39760,7 +40230,21 @@ var ACCOUNTS = {
|
|
|
39760
40230
|
|
|
39761
40231
|
// src/compliance/vat-guard.ts
|
|
39762
40232
|
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
|
|
40233
|
+
var NOT_VAT_REGISTERED_PURCHASE_REFUSAL = "This company is not VAT-registered, so it cannot deduct VAT. General-rule services from a foreign supplier can still incur full buyer VAT: use kirjapro_book_purchase with reverseCharge, eu_services/non_eu_services, deductionPercent 0 and documented vatRateBasis, or the atomic purchase form in the web app. Do not add a separate VAT row or duplicate voucher. EU goods, construction and scrap-metal registration requirements need separate assessment; do not change ordinary registration settings merely to bypass this guard. Customs collects import VAT for a buyer outside the VAT register.";
|
|
40234
|
+
var REVERSE_CHARGE_CATEGORIES = [
|
|
40235
|
+
"eu_goods",
|
|
40236
|
+
"eu_services",
|
|
40237
|
+
"non_eu_services",
|
|
40238
|
+
"import",
|
|
40239
|
+
"construction",
|
|
40240
|
+
"scrap_metal"
|
|
40241
|
+
];
|
|
40242
|
+
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).";
|
|
40243
|
+
var REVERSE_CHARGE_RATE_REQUIRED = "A reverse-charge purchase needs the positive Finnish rate established by vatRateBasis, including applicable historical rates.";
|
|
40244
|
+
var PURCHASE_TEMPLATE_NOT_VAT_REGISTERED_REFUSAL = 'This company is not VAT-registered (ALV), so a purchase template with VAT (2320) cannot be used. Use kirjapro_book_purchase with the source VAT rate, its basis and deductionPercent 0; a genuinely exempt purchase uses vatRate "0".';
|
|
40245
|
+
function purchaseTemplateBatchRefusal(indexes) {
|
|
40246
|
+
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.`;
|
|
40247
|
+
}
|
|
39764
40248
|
var NOT_VAT_REGISTERED_SETTLEMENT_REFUSAL = "This company is not VAT-registered (ALV), so it has no VAT settlement to book.";
|
|
39765
40249
|
async function readCompanyVat(supabase2, companyId2) {
|
|
39766
40250
|
const { data, error: error61 } = await supabase2.from("companies").select("alv_registered, vat_period").eq("id", companyId2).single();
|
|
@@ -39777,8 +40261,8 @@ async function readCompanyVat(supabase2, companyId2) {
|
|
|
39777
40261
|
function splitVat(gross, vatRate) {
|
|
39778
40262
|
if (vatRate === 0) return { net: roundCents(gross), vat: 0 };
|
|
39779
40263
|
const net = roundCents(gross / (1 + vatRate / 100));
|
|
39780
|
-
const
|
|
39781
|
-
return { net, vat };
|
|
40264
|
+
const vat2 = roundCents(gross - net);
|
|
40265
|
+
return { net, vat: vat2 };
|
|
39782
40266
|
}
|
|
39783
40267
|
function applyOverrides(entries, overrides) {
|
|
39784
40268
|
if (!overrides) return entries;
|
|
@@ -39828,7 +40312,7 @@ async function validateAccountOverrides(overrides) {
|
|
|
39828
40312
|
}
|
|
39829
40313
|
function saleCashTemplate(revenueAccount, vatRate) {
|
|
39830
40314
|
return (amount, opts) => {
|
|
39831
|
-
const { net, vat } = splitVat(amount, vatRate);
|
|
40315
|
+
const { net, vat: vat2 } = splitVat(amount, vatRate);
|
|
39832
40316
|
const entries = [
|
|
39833
40317
|
{
|
|
39834
40318
|
accountNumber: ACCOUNTS.KASSA,
|
|
@@ -39837,11 +40321,11 @@ function saleCashTemplate(revenueAccount, vatRate) {
|
|
|
39837
40321
|
},
|
|
39838
40322
|
{ accountNumber: revenueAccount, debitAmount: 0, creditAmount: net }
|
|
39839
40323
|
];
|
|
39840
|
-
if (
|
|
40324
|
+
if (vat2 > 0) {
|
|
39841
40325
|
entries.push({
|
|
39842
40326
|
accountNumber: ACCOUNTS.ALV_VELKA,
|
|
39843
40327
|
debitAmount: 0,
|
|
39844
|
-
creditAmount:
|
|
40328
|
+
creditAmount: vat2
|
|
39845
40329
|
});
|
|
39846
40330
|
}
|
|
39847
40331
|
return applyOverrides(entries, opts?.accountOverrides);
|
|
@@ -39849,7 +40333,7 @@ function saleCashTemplate(revenueAccount, vatRate) {
|
|
|
39849
40333
|
}
|
|
39850
40334
|
function saleBankTemplate(revenueAccount, vatRate) {
|
|
39851
40335
|
return (amount, opts) => {
|
|
39852
|
-
const { net, vat } = splitVat(amount, vatRate);
|
|
40336
|
+
const { net, vat: vat2 } = splitVat(amount, vatRate);
|
|
39853
40337
|
const entries = [
|
|
39854
40338
|
{
|
|
39855
40339
|
accountNumber: ACCOUNTS.PANKKITILI,
|
|
@@ -39858,11 +40342,11 @@ function saleBankTemplate(revenueAccount, vatRate) {
|
|
|
39858
40342
|
},
|
|
39859
40343
|
{ accountNumber: revenueAccount, debitAmount: 0, creditAmount: net }
|
|
39860
40344
|
];
|
|
39861
|
-
if (
|
|
40345
|
+
if (vat2 > 0) {
|
|
39862
40346
|
entries.push({
|
|
39863
40347
|
accountNumber: ACCOUNTS.ALV_VELKA,
|
|
39864
40348
|
debitAmount: 0,
|
|
39865
|
-
creditAmount:
|
|
40349
|
+
creditAmount: vat2
|
|
39866
40350
|
});
|
|
39867
40351
|
}
|
|
39868
40352
|
return applyOverrides(entries, opts?.accountOverrides);
|
|
@@ -39927,7 +40411,7 @@ var TEMPLATES = [
|
|
|
39927
40411
|
description: "Laskumyynti ALV 25,5% (myyntisaamiset)",
|
|
39928
40412
|
vatRate: 25.5,
|
|
39929
40413
|
expand: (amount, opts) => {
|
|
39930
|
-
const { net, vat } = splitVat(amount, 25.5);
|
|
40414
|
+
const { net, vat: vat2 } = splitVat(amount, 25.5);
|
|
39931
40415
|
return applyOverrides(
|
|
39932
40416
|
[
|
|
39933
40417
|
{
|
|
@@ -39936,7 +40420,7 @@ var TEMPLATES = [
|
|
|
39936
40420
|
creditAmount: 0
|
|
39937
40421
|
},
|
|
39938
40422
|
{ accountNumber: ACCOUNTS.MYYNTI_25_5, debitAmount: 0, creditAmount: net },
|
|
39939
|
-
{ accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount:
|
|
40423
|
+
{ accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount: vat2 }
|
|
39940
40424
|
],
|
|
39941
40425
|
opts?.accountOverrides
|
|
39942
40426
|
);
|
|
@@ -39949,11 +40433,11 @@ var TEMPLATES = [
|
|
|
39949
40433
|
description: "Osto ALV 25,5% (pankkitililt\xE4)",
|
|
39950
40434
|
vatRate: 25.5,
|
|
39951
40435
|
expand: (amount, opts) => {
|
|
39952
|
-
const { net, vat } = splitVat(amount, 25.5);
|
|
40436
|
+
const { net, vat: vat2 } = splitVat(amount, 25.5);
|
|
39953
40437
|
return applyOverrides(
|
|
39954
40438
|
[
|
|
39955
40439
|
{ accountNumber: ACCOUNTS.OSTOT, debitAmount: net, creditAmount: 0 },
|
|
39956
|
-
{ accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount:
|
|
40440
|
+
{ accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat2, creditAmount: 0 },
|
|
39957
40441
|
{
|
|
39958
40442
|
accountNumber: ACCOUNTS.PANKKITILI,
|
|
39959
40443
|
debitAmount: 0,
|
|
@@ -39970,11 +40454,11 @@ var TEMPLATES = [
|
|
|
39970
40454
|
description: "Ulkopuolinen palvelu ALV 25,5%",
|
|
39971
40455
|
vatRate: 25.5,
|
|
39972
40456
|
expand: (amount, opts) => {
|
|
39973
|
-
const { net, vat } = splitVat(amount, 25.5);
|
|
40457
|
+
const { net, vat: vat2 } = splitVat(amount, 25.5);
|
|
39974
40458
|
return applyOverrides(
|
|
39975
40459
|
[
|
|
39976
40460
|
{ accountNumber: ACCOUNTS.OSTOT_PALVELUT, debitAmount: net, creditAmount: 0 },
|
|
39977
|
-
{ accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount:
|
|
40461
|
+
{ accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat2, creditAmount: 0 },
|
|
39978
40462
|
{
|
|
39979
40463
|
accountNumber: ACCOUNTS.PANKKITILI,
|
|
39980
40464
|
debitAmount: 0,
|
|
@@ -40034,8 +40518,17 @@ var TEMPLATE_MAP = new Map(TEMPLATES.map((t) => [t.id, t]));
|
|
|
40034
40518
|
function isSaleTemplate(templateId) {
|
|
40035
40519
|
return TEMPLATE_MAP.get(templateId)?.kind === "sale";
|
|
40036
40520
|
}
|
|
40521
|
+
function inputVatOf(entries) {
|
|
40522
|
+
return roundCents(
|
|
40523
|
+
entries.filter((e) => e.accountNumber === ACCOUNTS.ALV_SAAMISET).reduce((sum, e) => sum + e.debitAmount - e.creditAmount, 0)
|
|
40524
|
+
);
|
|
40525
|
+
}
|
|
40526
|
+
function isVatPurchaseTemplate(templateId) {
|
|
40527
|
+
const template = TEMPLATE_MAP.get(templateId);
|
|
40528
|
+
return template?.kind === "purchase" && inputVatOf(template.expand(100)) !== 0;
|
|
40529
|
+
}
|
|
40037
40530
|
function availableTemplates(alvRegistered) {
|
|
40038
|
-
return alvRegistered ? TEMPLATES.filter((t) => t.kind !== "sale") : TEMPLATES;
|
|
40531
|
+
return alvRegistered ? TEMPLATES.filter((t) => t.kind !== "sale") : TEMPLATES.filter((t) => !isVatPurchaseTemplate(t.id));
|
|
40039
40532
|
}
|
|
40040
40533
|
function expandTemplate(templateId, amount, opts) {
|
|
40041
40534
|
const template = TEMPLATE_MAP.get(templateId);
|
|
@@ -40099,7 +40592,7 @@ function toJournalEntries(entries, accountMap) {
|
|
|
40099
40592
|
credit_amount: e.creditAmount
|
|
40100
40593
|
}));
|
|
40101
40594
|
}
|
|
40102
|
-
async function
|
|
40595
|
+
async function preparePosting(params) {
|
|
40103
40596
|
const supabase2 = getSupabase();
|
|
40104
40597
|
const companyId2 = getCompanyId();
|
|
40105
40598
|
let fiscalYearId = params.fiscalYearId;
|
|
@@ -40128,16 +40621,49 @@ async function resolveAndCreate(params) {
|
|
|
40128
40621
|
`Template produced unbalanced entries: debits ${totalDebit.toFixed(2)} != credits ${totalCredit.toFixed(2)}.`
|
|
40129
40622
|
);
|
|
40130
40623
|
}
|
|
40131
|
-
|
|
40132
|
-
|
|
40624
|
+
return { fiscalYearId, journalEntries: toJournalEntries(params.entries, accountResult) };
|
|
40625
|
+
}
|
|
40626
|
+
async function resolveAndCreatePurchase(params) {
|
|
40627
|
+
for (const row of params.vatRows) {
|
|
40628
|
+
const basisError = purchaseVatRateError(row.vat_rate_basis, row.category, row.vat_rate);
|
|
40629
|
+
if (basisError) return toErrorContent(basisError);
|
|
40630
|
+
}
|
|
40631
|
+
const capability = await getSupabase().rpc("purchase_vat_contract_version");
|
|
40632
|
+
if (capability.error || capability.data !== 1) return toErrorContent(
|
|
40633
|
+
capability.error && capability.error.code !== "PGRST202" ? sanitizeDbError("ALV-perusteiden tarkistus ep\xE4onnistui", capability.error) : "Ostojen ALV-perusteiden p\xE4ivitys on viel\xE4 kesken. Yrit\xE4 uudelleen p\xE4ivityksen j\xE4lkeen."
|
|
40634
|
+
);
|
|
40635
|
+
const prepared = await preparePosting(params);
|
|
40636
|
+
if ("isError" in prepared) return prepared;
|
|
40637
|
+
const { data, error: error61 } = await getSupabase().rpc("book_purchase_with_vat", {
|
|
40638
|
+
p_company_id: getCompanyId(),
|
|
40639
|
+
p_fiscal_year_id: prepared.fiscalYearId,
|
|
40640
|
+
p_transaction_date: params.transactionDate,
|
|
40641
|
+
p_description: params.description,
|
|
40642
|
+
p_document_number: params.documentNumber || "",
|
|
40643
|
+
p_entries: prepared.journalEntries,
|
|
40644
|
+
p_vat_rows: params.vatRows,
|
|
40645
|
+
...params.supplierName ? { p_supplier_name: params.supplierName } : {}
|
|
40646
|
+
});
|
|
40647
|
+
const result = data;
|
|
40648
|
+
if (error61 || !result?.transaction_id) {
|
|
40649
|
+
return toErrorContent(
|
|
40650
|
+
error61 ? sanitizeDbError("Failed to book the purchase", error61) : "Failed to book the purchase: no ID returned."
|
|
40651
|
+
);
|
|
40652
|
+
}
|
|
40653
|
+
return { id: result.transaction_id, vatEntryIds: result.vat_entry_ids ?? [] };
|
|
40654
|
+
}
|
|
40655
|
+
async function resolveAndCreate(params) {
|
|
40656
|
+
const prepared = await preparePosting(params);
|
|
40657
|
+
if ("isError" in prepared) return prepared;
|
|
40658
|
+
const { data: transactionId, error: txError } = await getSupabase().rpc(
|
|
40133
40659
|
"create_transaction_with_entries",
|
|
40134
40660
|
{
|
|
40135
|
-
p_company_id:
|
|
40136
|
-
p_fiscal_year_id: fiscalYearId,
|
|
40661
|
+
p_company_id: getCompanyId(),
|
|
40662
|
+
p_fiscal_year_id: prepared.fiscalYearId,
|
|
40137
40663
|
p_transaction_date: params.transactionDate,
|
|
40138
40664
|
p_description: params.description,
|
|
40139
40665
|
p_document_number: params.documentNumber || "",
|
|
40140
|
-
p_entries: journalEntries
|
|
40666
|
+
p_entries: prepared.journalEntries
|
|
40141
40667
|
}
|
|
40142
40668
|
);
|
|
40143
40669
|
if (txError || !transactionId) {
|
|
@@ -40151,7 +40677,7 @@ function registerTemplateTools(server2) {
|
|
|
40151
40677
|
server2.registerTool(
|
|
40152
40678
|
"kirjapro_list_templates",
|
|
40153
40679
|
{
|
|
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.",
|
|
40680
|
+
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
40681
|
inputSchema: {},
|
|
40156
40682
|
outputSchema: {
|
|
40157
40683
|
templates: external_exports.array(external_exports.object({
|
|
@@ -40176,7 +40702,7 @@ function registerTemplateTools(server2) {
|
|
|
40176
40702
|
server2.registerTool(
|
|
40177
40703
|
"kirjapro_create_from_template",
|
|
40178
40704
|
{
|
|
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.',
|
|
40705
|
+
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
40706
|
inputSchema: {
|
|
40181
40707
|
template: external_exports.string().describe('Template ID (e.g. "cash_sale_25", "purchase_expense")'),
|
|
40182
40708
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
@@ -40184,6 +40710,7 @@ function registerTemplateTools(server2) {
|
|
|
40184
40710
|
amount: external_exports.number().finite().positive().max(1e9).describe("Gross amount in EUR"),
|
|
40185
40711
|
description: external_exports.string().min(1).max(500).describe("Transaction description"),
|
|
40186
40712
|
documentNumber: external_exports.string().optional().describe("Document/receipt number"),
|
|
40713
|
+
vatRateBasis: PURCHASE_VAT_BASIS_SCHEMA.optional(),
|
|
40187
40714
|
accountOverrides: external_exports.record(external_exports.string(), external_exports.string()).optional().describe('Account number swaps, e.g. {"2320": "2330"}'),
|
|
40188
40715
|
idempotencyKey: IDEMPOTENCY_KEY_SCHEMA
|
|
40189
40716
|
},
|
|
@@ -40192,10 +40719,15 @@ function registerTemplateTools(server2) {
|
|
|
40192
40719
|
async (params) => {
|
|
40193
40720
|
const supabase2 = getSupabase();
|
|
40194
40721
|
const companyId2 = getCompanyId();
|
|
40195
|
-
|
|
40722
|
+
const template = TEMPLATE_MAP.get(params.template);
|
|
40723
|
+
let alvRegistered = null;
|
|
40724
|
+
if (template && (template.kind === "sale" || template.kind === "purchase")) {
|
|
40196
40725
|
const companyVat = await readCompanyVat(supabase2, companyId2);
|
|
40197
40726
|
if ("isError" in companyVat) return companyVat;
|
|
40198
|
-
|
|
40727
|
+
alvRegistered = companyVat.alvRegistered;
|
|
40728
|
+
if (template.kind === "sale" && alvRegistered) {
|
|
40729
|
+
return toErrorContent(ALV_REGISTERED_SALE_REFUSAL);
|
|
40730
|
+
}
|
|
40199
40731
|
}
|
|
40200
40732
|
return withIdempotency(
|
|
40201
40733
|
supabase2,
|
|
@@ -40214,7 +40746,29 @@ function registerTemplateTools(server2) {
|
|
|
40214
40746
|
if ("error" in result) {
|
|
40215
40747
|
return toErrorContent(result.error);
|
|
40216
40748
|
}
|
|
40217
|
-
const
|
|
40749
|
+
const inputVat = template?.kind === "purchase" ? inputVatOf(result.entries) : 0;
|
|
40750
|
+
const recordsVat = template?.kind === "purchase" && inputVatOf(template.expand(100, { accountOverrides: params.accountOverrides })) !== 0;
|
|
40751
|
+
if (recordsVat && alvRegistered === false) {
|
|
40752
|
+
return toErrorContent(PURCHASE_TEMPLATE_NOT_VAT_REGISTERED_REFUSAL);
|
|
40753
|
+
}
|
|
40754
|
+
const createResult = recordsVat ? await resolveAndCreatePurchase({
|
|
40755
|
+
fiscalYearId: params.fiscalYearId,
|
|
40756
|
+
transactionDate: params.transactionDate,
|
|
40757
|
+
description: params.description,
|
|
40758
|
+
documentNumber: params.documentNumber,
|
|
40759
|
+
entries: result.entries,
|
|
40760
|
+
vatRows: [
|
|
40761
|
+
{
|
|
40762
|
+
category: "domestic",
|
|
40763
|
+
vat_rate_basis: params.vatRateBasis,
|
|
40764
|
+
vat_rate: template?.vatRate ?? 0,
|
|
40765
|
+
tax_base: roundCents(
|
|
40766
|
+
result.entries.filter((e) => e.accountNumber !== ACCOUNTS.ALV_SAAMISET).reduce((sum, e) => sum + e.debitAmount, 0)
|
|
40767
|
+
),
|
|
40768
|
+
vat_amount: inputVat
|
|
40769
|
+
}
|
|
40770
|
+
]
|
|
40771
|
+
}) : await resolveAndCreate({
|
|
40218
40772
|
fiscalYearId: params.fiscalYearId,
|
|
40219
40773
|
transactionDate: params.transactionDate,
|
|
40220
40774
|
description: params.description,
|
|
@@ -40228,7 +40782,8 @@ function registerTemplateTools(server2) {
|
|
|
40228
40782
|
message: "Transaction created from template",
|
|
40229
40783
|
template: params.template,
|
|
40230
40784
|
transaction_id: createResult.id,
|
|
40231
|
-
entries: result.entries
|
|
40785
|
+
entries: result.entries,
|
|
40786
|
+
..."vatEntryIds" in createResult && { vat_entry_ids: createResult.vatEntryIds }
|
|
40232
40787
|
});
|
|
40233
40788
|
}
|
|
40234
40789
|
);
|
|
@@ -40237,7 +40792,7 @@ function registerTemplateTools(server2) {
|
|
|
40237
40792
|
server2.registerTool(
|
|
40238
40793
|
"kirjapro_create_from_template_batch",
|
|
40239
40794
|
{
|
|
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.",
|
|
40795
|
+
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
40796
|
inputSchema: {
|
|
40242
40797
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
40243
40798
|
dryRun: external_exports.boolean().default(true).describe(
|
|
@@ -40260,6 +40815,14 @@ function registerTemplateTools(server2) {
|
|
|
40260
40815
|
async (params) => {
|
|
40261
40816
|
const supabase2 = getSupabase();
|
|
40262
40817
|
const companyId2 = getCompanyId();
|
|
40818
|
+
const vatPurchaseIndexes = params.transactions.map((tx, index) => {
|
|
40819
|
+
const template = TEMPLATE_MAP.get(tx.template);
|
|
40820
|
+
if (template?.kind !== "purchase") return -1;
|
|
40821
|
+
return inputVatOf(template.expand(100, { accountOverrides: tx.accountOverrides })) !== 0 ? index : -1;
|
|
40822
|
+
}).filter((index) => index >= 0);
|
|
40823
|
+
if (vatPurchaseIndexes.length > 0) {
|
|
40824
|
+
return toErrorContent(purchaseTemplateBatchRefusal(vatPurchaseIndexes));
|
|
40825
|
+
}
|
|
40263
40826
|
const saleIndexes = params.transactions.map((tx, index) => isSaleTemplate(tx.template) ? index : -1).filter((index) => index >= 0);
|
|
40264
40827
|
if (saleIndexes.length > 0) {
|
|
40265
40828
|
const companyVat = await readCompanyVat(supabase2, companyId2);
|
|
@@ -40449,49 +41012,98 @@ function registerTemplateTools(server2) {
|
|
|
40449
41012
|
);
|
|
40450
41013
|
}
|
|
40451
41014
|
|
|
41015
|
+
// src/tools/bookings.ts
|
|
41016
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
41017
|
+
|
|
41018
|
+
// src/compliance/purchase-vat-allocation.ts
|
|
41019
|
+
function allocatePurchaseVat(net, vat2, deductionPercent) {
|
|
41020
|
+
if (![net, vat2, deductionPercent].every(Number.isFinite) || net < 0 || vat2 < 0 || deductionPercent < 0 || deductionPercent > 100) {
|
|
41021
|
+
throw new Error("V\xE4hennysosuuden on oltava 0\u2013100 % ja ostosummien v\xE4hint\xE4\xE4n nolla.");
|
|
41022
|
+
}
|
|
41023
|
+
const netCents = Math.round(net * 100);
|
|
41024
|
+
const vatCents = Math.round(vat2 * 100);
|
|
41025
|
+
const deductibleVatCents = Math.round(vatCents * deductionPercent / 100 + 1e-9);
|
|
41026
|
+
const deductibleNetCents = Math.round(netCents * deductionPercent / 100 + 1e-9);
|
|
41027
|
+
return {
|
|
41028
|
+
deductibleVat: deductibleVatCents / 100,
|
|
41029
|
+
nonDeductibleVat: (vatCents - deductibleVatCents) / 100,
|
|
41030
|
+
deductibleNet: deductibleNetCents / 100,
|
|
41031
|
+
nonDeductibleNet: (netCents - deductibleNetCents) / 100,
|
|
41032
|
+
expense: (netCents + vatCents - deductibleVatCents) / 100,
|
|
41033
|
+
gross: (netCents + vatCents) / 100
|
|
41034
|
+
};
|
|
41035
|
+
}
|
|
41036
|
+
|
|
40452
41037
|
// src/compliance/rates.ts
|
|
41038
|
+
function periodForRule(rule, date5) {
|
|
41039
|
+
const periods = MCP_REGISTRY.rules[rule].periods;
|
|
41040
|
+
const period = periods.find(
|
|
41041
|
+
(candidate) => candidate.validFrom <= date5 && (!candidate.validUntil || candidate.validUntil >= date5)
|
|
41042
|
+
);
|
|
41043
|
+
if (!period) throw new Error(`No verified ${rule} rule for ${date5}`);
|
|
41044
|
+
return period;
|
|
41045
|
+
}
|
|
41046
|
+
var vat = periodForRule("vat", "2026-01-01").values;
|
|
41047
|
+
var payrollPeriod = periodForRule("payroll", "2026-01-01");
|
|
41048
|
+
var payroll = payrollPeriod.values;
|
|
41049
|
+
var estimates = periodForRule("payrollEstimates", "2026-01-01").values;
|
|
41050
|
+
var total = (employer) => Math.round((employer + payroll.tyelEmployee) * 100) / 100;
|
|
40453
41051
|
var FINNISH_RATES_2026 = {
|
|
40454
41052
|
year: 2026,
|
|
40455
|
-
validFrom:
|
|
40456
|
-
validUntil:
|
|
40457
|
-
|
|
40458
|
-
source: "AVL 1501/1993, ETK 2026, Elo 2026, vero.fi 2026",
|
|
40459
|
-
vat: {
|
|
40460
|
-
standard: 25.5,
|
|
40461
|
-
reduced: 13.5,
|
|
40462
|
-
secondReduced: 10,
|
|
40463
|
-
zero: 0
|
|
40464
|
-
},
|
|
41053
|
+
validFrom: payrollPeriod.validFrom,
|
|
41054
|
+
validUntil: payrollPeriod.validUntil,
|
|
41055
|
+
vat: { standard: vat.general, reduced: vat.reduced, secondReduced: vat.newspapers, zero: vat.zero },
|
|
40465
41056
|
social: {
|
|
40466
|
-
tyelEmployee:
|
|
40467
|
-
tyelEmployer:
|
|
40468
|
-
tyelEmployerAverage:
|
|
40469
|
-
tyelKeskimaarainen:
|
|
40470
|
-
tyelPerusmaksu:
|
|
40471
|
-
|
|
40472
|
-
|
|
40473
|
-
|
|
40474
|
-
|
|
40475
|
-
|
|
40476
|
-
|
|
40477
|
-
|
|
40478
|
-
|
|
40479
|
-
}
|
|
40480
|
-
|
|
40481
|
-
|
|
40482
|
-
const
|
|
40483
|
-
const
|
|
40484
|
-
|
|
40485
|
-
);
|
|
41057
|
+
tyelEmployee: payroll.tyelEmployee,
|
|
41058
|
+
tyelEmployer: estimates.tyelBaseEmployer,
|
|
41059
|
+
tyelEmployerAverage: estimates.tyelAverageEmployer,
|
|
41060
|
+
tyelKeskimaarainen: total(estimates.tyelAverageEmployer),
|
|
41061
|
+
tyelPerusmaksu: total(estimates.tyelBaseEmployer),
|
|
41062
|
+
tyelTemporary: total(estimates.tyelTemporaryEmployer),
|
|
41063
|
+
sotu: payroll.health,
|
|
41064
|
+
ttvEmployer: payroll.unemploymentLower,
|
|
41065
|
+
ttvEmployerLarge: payroll.unemploymentUpper,
|
|
41066
|
+
ttvEmployerLargeThreshold: payroll.unemploymentThreshold,
|
|
41067
|
+
ttvEmployee: payroll.unemploymentEmployee,
|
|
41068
|
+
tapaturmaAverage: estimates.accidentAverage,
|
|
41069
|
+
ryhmahenki: estimates.groupLife
|
|
41070
|
+
}
|
|
41071
|
+
};
|
|
41072
|
+
function regulatoryFreshness(rule, today = /* @__PURE__ */ new Date()) {
|
|
41073
|
+
const period = periodForRule(rule, "2026-01-01");
|
|
41074
|
+
const day = helsinkiDateString(today);
|
|
41075
|
+
const expired = period.validUntil !== null && day > period.validUntil;
|
|
40486
41076
|
return {
|
|
40487
|
-
|
|
40488
|
-
|
|
40489
|
-
|
|
40490
|
-
|
|
40491
|
-
|
|
40492
|
-
|
|
41077
|
+
rule,
|
|
41078
|
+
kind: MCP_REGISTRY.rules[rule].kind,
|
|
41079
|
+
conditions: MCP_REGISTRY.rules[rule].conditions,
|
|
41080
|
+
coverage_meaning: "Bundled registry coverage interval, not necessarily enactment or first legal effective date; check event/fiscal-year applicability separately",
|
|
41081
|
+
valid_from: period.validFrom,
|
|
41082
|
+
valid_through: period.validUntil,
|
|
41083
|
+
sources_verified_at: period.reviewedOn,
|
|
41084
|
+
review_due: period.reviewDue,
|
|
41085
|
+
review_overdue: day > period.reviewDue,
|
|
41086
|
+
stale: expired || day > period.reviewDue || day < period.validFrom,
|
|
41087
|
+
expired,
|
|
41088
|
+
not_yet_effective: day < period.validFrom,
|
|
41089
|
+
days_until_expiry: period.validUntil === null ? null : Math.round(
|
|
41090
|
+
(Date.parse(`${period.validUntil}T00:00:00Z`) - Date.parse(`${day}T00:00:00Z`)) / 864e5
|
|
41091
|
+
),
|
|
41092
|
+
sources: period.sourceIds.map((id) => ({
|
|
41093
|
+
id,
|
|
41094
|
+
...MCP_REGISTRY.sources[id]
|
|
41095
|
+
}))
|
|
40493
41096
|
};
|
|
40494
41097
|
}
|
|
41098
|
+
var VAT_RATE_HISTORY = MCP_REGISTRY.rules.vat.periods.map((period) => ({
|
|
41099
|
+
valid_from: period.validFrom,
|
|
41100
|
+
valid_through: period.validUntil,
|
|
41101
|
+
standard: period.values.general,
|
|
41102
|
+
reduced: period.values.reduced,
|
|
41103
|
+
second_reduced: period.values.newspapers,
|
|
41104
|
+
note: "Rate amounts only: product categories changed separately, including on 1.1.2025. Select by supply, advance payment or original corrected event; do not apply current categories backwards."
|
|
41105
|
+
}));
|
|
41106
|
+
var COMPANY_SIZE_RULE = periodForRule("companySizes", "2026-01-01");
|
|
40495
41107
|
|
|
40496
41108
|
// src/compliance/vat-settlement-date.ts
|
|
40497
41109
|
var pad = (value) => String(value).padStart(2, "0");
|
|
@@ -40526,14 +41138,14 @@ function defaultSettlementDate(month, vatPeriod) {
|
|
|
40526
41138
|
function expenseAccountWarning(account) {
|
|
40527
41139
|
const first = account.trim().charAt(0);
|
|
40528
41140
|
if (first && !"6789".includes(first)) {
|
|
40529
|
-
return `expenseAccount "${account}" is outside the
|
|
41141
|
+
return `expenseAccount "${account}" is outside the Kirjapro default expense range (6xxx materials/services, 7xxx\u20139xxx expenses). Verify it is really an expense account \u2014 booking a purchase to a non-expense account misclassifies the income statement.`;
|
|
40530
41142
|
}
|
|
40531
41143
|
return null;
|
|
40532
41144
|
}
|
|
40533
41145
|
function revenueAccountWarning(account) {
|
|
40534
41146
|
const first = account.trim().charAt(0);
|
|
40535
41147
|
if (first && first !== "5") {
|
|
40536
|
-
return `revenueAccount "${account}" is outside the
|
|
41148
|
+
return `revenueAccount "${account}" is outside the Kirjapro default revenue range (5xxx Liikevaihto). Verify it is really a revenue account \u2014 booking a sale to a non-revenue account misclassifies the income statement.`;
|
|
40537
41149
|
}
|
|
40538
41150
|
return null;
|
|
40539
41151
|
}
|
|
@@ -40548,17 +41160,24 @@ function registerBookingTools(server2) {
|
|
|
40548
41160
|
server2.registerTool(
|
|
40549
41161
|
"kirjapro_book_purchase",
|
|
40550
41162
|
{
|
|
40551
|
-
description: "Book a purchase (osto) with automatic VAT splitting.
|
|
41163
|
+
description: "Book a purchase (osto) with automatic VAT splitting and a confirmed event basis. For a VAT-registered (ALV) company the VAT fact is recorded with the voucher: deductible VAT reaches field 307 and full assessed reverse-charge VAT reaches its category 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. Domestic entertainment (edustus, AVL 114\xA7) is booked gross. With reverseCharge=true and edustus=true the full buyer VAT is payable, deduction is zero and that VAT increases the expense. Statutorily non-deductible accounts cannot claim input VAT; a genuine exempt purchase uses vatRate 0, but reverse-charge liability must not be hidden by a zero rate. A non-registered company cannot deduct VAT; general-rule foreign services still incur buyer VAT and use deductionPercent 0. The import category records ONLY customs VAT on the documented full customs tax base, without another supplier expense/payment. Rate rules: food/restaurants 13.5%, insurance 0% (exempt), alcohol 25.5%.",
|
|
40552
41164
|
inputSchema: {
|
|
40553
41165
|
amount: external_exports.number().finite().positive().max(1e9).describe(
|
|
40554
|
-
"EUR amount. Standard purchase: GROSS (VAT-inclusive) \u2014 VAT is split out of this. Reverse charge (reverseCharge=true): NET
|
|
41166
|
+
"EUR amount. Standard purchase: GROSS (VAT-inclusive) \u2014 VAT is split out of this. Reverse charge (reverseCharge=true): NET supplier amount; import instead uses the full customs tax base and records only tax. Buyer VAT is assessed on top. With edustus=true the expense includes that VAT, ALV-velka records it in full and there is no input VAT deduction; otherwise the deductionPercent chooses the deductible share. Domestic entertainment (edustus=true, reverseCharge=false): GROSS supplier amount, no input VAT deduction."
|
|
40555
41167
|
),
|
|
40556
41168
|
description: external_exports.string().min(1).max(500).describe("Transaction description"),
|
|
40557
|
-
date: ISO_DATE_SCHEMA.describe("
|
|
40558
|
-
vatRate: external_exports.enum(["25.5", "13.5", "10", "0"]).describe("Finnish VAT rate"),
|
|
41169
|
+
date: ISO_DATE_SCHEMA.describe("Posting/reporting date (YYYY-MM-DD). Choose the lawful reporting period separately from vatRateBasis; this tool does not derive it."),
|
|
41170
|
+
vatRate: external_exports.enum(["25.5", "13.5", "10", "0", "24", "14"]).describe("Finnish VAT rate on the source, including historical 24/14"),
|
|
41171
|
+
vatRateBasis: PURCHASE_VAT_BASIS_SCHEMA.optional(),
|
|
41172
|
+
deductionPercent: external_exports.number().finite().min(0).max(100).optional().describe("Deductible business-use share of original VAT, 0\u2013100. Default 100, or 0 for edustus/non-registered company. Separate private expenditure first."),
|
|
41173
|
+
deductionReason: external_exports.string().trim().min(1).max(500).optional().describe("Required documented allocation/use basis if deduction is below 100%; edustus and non-registration provide their stated basis."),
|
|
40559
41174
|
expenseAccount: external_exports.string().describe('Expense account number (e.g. "8080")'),
|
|
40560
|
-
reverseCharge: external_exports.boolean().default(false).describe(`
|
|
40561
|
-
|
|
41175
|
+
reverseCharge: external_exports.boolean().default(false).describe(`Reverse charge (k\xE4\xE4nnetty verovelvollisuus): C ${ACCOUNTS.ALV_VELKA} for full assessed VAT and a VAT fact of reverseChargeCategory. With edustus=true, debit expense including non-deductible VAT; debit ${ACCOUNTS.ALV_SAAMISET} only for the deductible share and expense for the rest. Credit net supplier payment to bank/payable, except import: tax assessment only, no supplier payment`),
|
|
41176
|
+
reverseChargeCategory: external_exports.enum(REVERSE_CHARGE_CATEGORIES).optional().describe(
|
|
41177
|
+
"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)"
|
|
41178
|
+
),
|
|
41179
|
+
supplierName: external_exports.string().max(200).optional().describe("Supplier name, stored on the VAT fact"),
|
|
41180
|
+
edustus: external_exports.boolean().default(false).describe("Entertainment expense (AVL 114\xA7): no input VAT deduction. Domestic supplier amount is gross; with reverseCharge=true supplier amount is net and self-assessed VAT is added to expense and VAT payable"),
|
|
40562
41181
|
paymentAccount: external_exports.string().default(ACCOUNTS.PANKKITILI).describe(`Payment account (default "${ACCOUNTS.PANKKITILI}" Pankkitili)`),
|
|
40563
41182
|
fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
|
|
40564
41183
|
documentNumber: external_exports.string().optional().describe("Document/receipt number"),
|
|
@@ -40568,76 +41187,109 @@ function registerBookingTools(server2) {
|
|
|
40568
41187
|
annotations: WRITE_ANNOTATIONS
|
|
40569
41188
|
},
|
|
40570
41189
|
async (params) => {
|
|
40571
|
-
const
|
|
40572
|
-
|
|
40573
|
-
|
|
40574
|
-
|
|
40575
|
-
const
|
|
40576
|
-
if ("isError" in
|
|
40577
|
-
if (
|
|
40578
|
-
|
|
40579
|
-
|
|
40580
|
-
|
|
40581
|
-
|
|
40582
|
-
|
|
40583
|
-
|
|
40584
|
-
|
|
40585
|
-
|
|
40586
|
-
|
|
40587
|
-
|
|
40588
|
-
|
|
40589
|
-
|
|
40590
|
-
|
|
40591
|
-
|
|
40592
|
-
|
|
40593
|
-
|
|
40594
|
-
|
|
40595
|
-
|
|
40596
|
-
|
|
40597
|
-
|
|
40598
|
-
|
|
40599
|
-
|
|
40600
|
-
|
|
40601
|
-
|
|
40602
|
-
|
|
40603
|
-
|
|
40604
|
-
|
|
40605
|
-
|
|
40606
|
-
|
|
40607
|
-
|
|
40608
|
-
|
|
40609
|
-
|
|
41190
|
+
const execute = async () => {
|
|
41191
|
+
const rate = Number(params.vatRate);
|
|
41192
|
+
const amount = roundCents(params.amount);
|
|
41193
|
+
if (amount <= 0) return toErrorContent("Oston summan on oltava v\xE4hint\xE4\xE4n yksi sentti.");
|
|
41194
|
+
const companyVat = await readCompanyVat(getSupabase(), getCompanyId());
|
|
41195
|
+
if ("isError" in companyVat) return companyVat;
|
|
41196
|
+
if (params.reverseCharge && !params.reverseChargeCategory) return toErrorContent(REVERSE_CHARGE_CATEGORY_REQUIRED);
|
|
41197
|
+
if (params.reverseCharge && rate === 0) return toErrorContent(REVERSE_CHARGE_RATE_REQUIRED);
|
|
41198
|
+
const category = params.reverseCharge ? params.reverseChargeCategory : "domestic";
|
|
41199
|
+
const assessmentOnly = category === "import";
|
|
41200
|
+
const percent = params.edustus || !companyVat.alvRegistered ? 0 : params.deductionPercent ?? 100;
|
|
41201
|
+
if ((params.edustus || !companyVat.alvRegistered) && (params.deductionPercent ?? 0) > 0)
|
|
41202
|
+
return toErrorContent("Edustuksesta tai rekister\xF6im\xE4tt\xF6m\xE4n yrityksen ostosta ei tehd\xE4 ALV-v\xE4hennyst\xE4. Korjaa v\xE4hennysosuus nollaksi.");
|
|
41203
|
+
if (!companyVat.alvRegistered && params.reverseCharge && !["eu_services", "non_eu_services"].includes(category))
|
|
41204
|
+
return toErrorContent(NOT_VAT_REGISTERED_PURCHASE_REFUSAL);
|
|
41205
|
+
const reason = params.edustus ? "Edustuskulu, AVL 114 \xA7 1 mom. 3 kohta" : !companyVat.alvRegistered ? "Yritys ei ole arvonlis\xE4verovelvollinen" : params.deductionReason;
|
|
41206
|
+
if (rate > 0 && percent < 100 && !reason)
|
|
41207
|
+
return toErrorContent("Kirjoita v\xE4hennysosuuden jakoperuste. Yksityiskulut erotetaan erikseen.");
|
|
41208
|
+
if (rate > 0) {
|
|
41209
|
+
const basisError = purchaseVatRateError(params.vatRateBasis, category, rate);
|
|
41210
|
+
if (basisError) return toErrorContent(basisError);
|
|
41211
|
+
}
|
|
41212
|
+
const basis = params.vatRateBasis;
|
|
41213
|
+
const { net, vat: vat2 } = params.reverseCharge ? { net: amount, vat: roundCents(amount * rate / 100) } : splitVat(amount, rate);
|
|
41214
|
+
const allocation = allocatePurchaseVat(net, vat2, percent);
|
|
41215
|
+
const keepFacts = rate > 0 && (params.reverseCharge || companyVat.alvRegistered && !params.edustus);
|
|
41216
|
+
const vatAllocation = percent > 0 && percent < 100 ? {
|
|
41217
|
+
version: 1,
|
|
41218
|
+
group_id: randomUUID2(),
|
|
41219
|
+
net,
|
|
41220
|
+
vat: vat2,
|
|
41221
|
+
deduction_percent: percent,
|
|
41222
|
+
reason
|
|
41223
|
+
} : void 0;
|
|
41224
|
+
const fact = {
|
|
41225
|
+
category,
|
|
41226
|
+
vat_rate: rate,
|
|
41227
|
+
vat_rate_basis: basis,
|
|
41228
|
+
...vatAllocation ? { vat_allocation: vatAllocation } : {}
|
|
41229
|
+
};
|
|
41230
|
+
const vatRows = keepFacts ? [
|
|
41231
|
+
...percent > 0 ? [{ ...fact, tax_base: allocation.deductibleNet, vat_amount: allocation.deductibleVat, is_deductible: true }] : [],
|
|
41232
|
+
...percent < 100 ? [{ ...fact, tax_base: allocation.nonDeductibleNet, vat_amount: allocation.nonDeductibleVat, is_deductible: false, non_deductible_reason: reason }] : []
|
|
41233
|
+
].filter((row) => row.tax_base !== 0 || row.vat_amount !== 0) : [];
|
|
41234
|
+
const expense = assessmentOnly ? allocation.nonDeductibleVat : allocation.expense;
|
|
41235
|
+
const entries = [
|
|
41236
|
+
{ accountNumber: params.expenseAccount, debitAmount: expense, creditAmount: 0 },
|
|
41237
|
+
{ accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: allocation.deductibleVat, creditAmount: 0 },
|
|
41238
|
+
...params.reverseCharge ? [{ accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount: vat2 }] : [],
|
|
41239
|
+
...!assessmentOnly ? [{ accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: params.reverseCharge ? net : amount }] : []
|
|
41240
|
+
].filter((entry) => entry.debitAmount !== 0 || entry.creditAmount !== 0);
|
|
41241
|
+
if (entries.length < 2) return toErrorContent("Tositteen veron on oltava v\xE4hint\xE4\xE4n yksi sentti.");
|
|
41242
|
+
const purchaseAccountWarning = expenseAccountWarning(params.expenseAccount);
|
|
41243
|
+
const calculation = {
|
|
41244
|
+
amount_basis: assessmentOnly ? "customs_tax_base" : params.reverseCharge ? "supplier_net" : "supplier_gross",
|
|
41245
|
+
...assessmentOnly ? {} : { supplier_amount: params.reverseCharge ? net : amount },
|
|
41246
|
+
tax_base: net,
|
|
41247
|
+
output_vat: params.reverseCharge ? vat2 : 0,
|
|
41248
|
+
source_vat: vat2,
|
|
41249
|
+
deductible_vat: allocation.deductibleVat,
|
|
41250
|
+
expense_total: expense,
|
|
41251
|
+
deduction_percent: percent,
|
|
41252
|
+
...reason ? { deduction_reason: reason } : {}
|
|
41253
|
+
};
|
|
41254
|
+
if (params.dryRun) return toDryRunResponse("kirjapro_book_purchase", {
|
|
40610
41255
|
action: "book_purchase",
|
|
40611
|
-
gross,
|
|
41256
|
+
gross: amount,
|
|
40612
41257
|
vat_rate: params.vatRate,
|
|
40613
41258
|
description: params.description,
|
|
40614
41259
|
date: params.date,
|
|
40615
41260
|
entries,
|
|
40616
|
-
|
|
41261
|
+
calculation,
|
|
41262
|
+
...params.reverseCharge ? { reverse_charge: calculation } : {},
|
|
41263
|
+
...vatRows.length ? { vat_rows: vatRows } : {},
|
|
41264
|
+
...purchaseAccountWarning ? { account_warning: purchaseAccountWarning } : {}
|
|
40617
41265
|
});
|
|
40618
|
-
|
|
41266
|
+
const posting = {
|
|
41267
|
+
fiscalYearId: params.fiscalYearId,
|
|
41268
|
+
transactionDate: params.date,
|
|
41269
|
+
description: params.description,
|
|
41270
|
+
documentNumber: params.documentNumber,
|
|
41271
|
+
entries
|
|
41272
|
+
};
|
|
41273
|
+
const result = vatRows.length ? await resolveAndCreatePurchase({ ...posting, vatRows, supplierName: params.supplierName }) : await resolveAndCreate(posting);
|
|
41274
|
+
if ("isError" in result) return result;
|
|
41275
|
+
return toResult({
|
|
41276
|
+
message: assessmentOnly ? "Customs VAT assessment booked" : "Purchase booked",
|
|
41277
|
+
transaction_id: result.id,
|
|
41278
|
+
entries,
|
|
41279
|
+
calculation,
|
|
41280
|
+
...params.reverseCharge ? { reverse_charge: calculation } : {},
|
|
41281
|
+
..."vatEntryIds" in result ? { vat_entry_ids: result.vatEntryIds } : {},
|
|
41282
|
+
...purchaseAccountWarning ? { warning: purchaseAccountWarning } : {}
|
|
41283
|
+
});
|
|
41284
|
+
};
|
|
41285
|
+
if (params.dryRun) return execute();
|
|
40619
41286
|
return withIdempotency(
|
|
40620
41287
|
getSupabase(),
|
|
40621
41288
|
getCompanyId(),
|
|
40622
41289
|
"kirjapro_book_purchase",
|
|
40623
41290
|
params.idempotencyKey,
|
|
40624
41291
|
params,
|
|
40625
|
-
|
|
40626
|
-
const result = await resolveAndCreate({
|
|
40627
|
-
fiscalYearId: params.fiscalYearId,
|
|
40628
|
-
transactionDate: params.date,
|
|
40629
|
-
description: params.description,
|
|
40630
|
-
documentNumber: params.documentNumber,
|
|
40631
|
-
entries
|
|
40632
|
-
});
|
|
40633
|
-
if ("isError" in result) return result;
|
|
40634
|
-
return toResult({
|
|
40635
|
-
message: "Purchase booked",
|
|
40636
|
-
transaction_id: result.id,
|
|
40637
|
-
entries,
|
|
40638
|
-
...purchaseAccountWarning && { warning: purchaseAccountWarning }
|
|
40639
|
-
});
|
|
40640
|
-
}
|
|
41292
|
+
execute
|
|
40641
41293
|
);
|
|
40642
41294
|
}
|
|
40643
41295
|
);
|
|
@@ -40791,21 +41443,21 @@ function registerBookingTools(server2) {
|
|
|
40791
41443
|
{ accountNumber: params.revenueAccount, debitAmount: 0, creditAmount: gross }
|
|
40792
41444
|
];
|
|
40793
41445
|
} else {
|
|
40794
|
-
const { net: net2, vat:
|
|
41446
|
+
const { net: net2, vat: vat3 } = splitVat(gross, rate);
|
|
40795
41447
|
entries = [
|
|
40796
41448
|
{ accountNumber: debitAccount, debitAmount: gross, creditAmount: 0 },
|
|
40797
41449
|
{ accountNumber: params.revenueAccount, debitAmount: 0, creditAmount: net2 },
|
|
40798
|
-
{ accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount:
|
|
41450
|
+
{ accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount: vat3 }
|
|
40799
41451
|
];
|
|
40800
41452
|
}
|
|
40801
|
-
const { net, vat } = rate > 0 ? splitVat(gross, rate) : { net: gross, vat: 0 };
|
|
41453
|
+
const { net, vat: vat2 } = rate > 0 ? splitVat(gross, rate) : { net: gross, vat: 0 };
|
|
40802
41454
|
const saleAccountWarning = revenueAccountWarning(params.revenueAccount);
|
|
40803
41455
|
if (params.dryRun) {
|
|
40804
41456
|
return toDryRunResponse("kirjapro_book_sale", {
|
|
40805
41457
|
action: "book_sale",
|
|
40806
41458
|
gross,
|
|
40807
41459
|
net,
|
|
40808
|
-
vat,
|
|
41460
|
+
vat: vat2,
|
|
40809
41461
|
vat_rate: `${params.vatRate} %`,
|
|
40810
41462
|
payment_type: params.paymentType,
|
|
40811
41463
|
debit_account: debitAccount,
|
|
@@ -40835,7 +41487,7 @@ function registerBookingTools(server2) {
|
|
|
40835
41487
|
transaction_id: result.id,
|
|
40836
41488
|
gross,
|
|
40837
41489
|
net,
|
|
40838
|
-
vat,
|
|
41490
|
+
vat: vat2,
|
|
40839
41491
|
vat_rate: `${params.vatRate} %`,
|
|
40840
41492
|
payment_type: params.paymentType,
|
|
40841
41493
|
debit_account: debitAccount,
|
|
@@ -40970,6 +41622,7 @@ function registerOfferTools(server2) {
|
|
|
40970
41622
|
"reverse_scrap",
|
|
40971
41623
|
"reverse_eu_service",
|
|
40972
41624
|
"export_outside_eu",
|
|
41625
|
+
"service_outside_eu",
|
|
40973
41626
|
"intra_eu_goods",
|
|
40974
41627
|
"exempt_healthcare",
|
|
40975
41628
|
"exempt_social_welfare",
|
|
@@ -40977,7 +41630,7 @@ function registerOfferTools(server2) {
|
|
|
40977
41630
|
"exempt_financial",
|
|
40978
41631
|
"exempt_insurance",
|
|
40979
41632
|
"exempt_other"
|
|
40980
|
-
]).optional().describe("Structured VAT treatment. Required when VAT rate is 0.")
|
|
41633
|
+
]).optional().describe("Structured VAT treatment. Required when VAT rate is 0. service_outside_eu is ONLY for AVL 65 general-rule B2B services: verify business use, evidence of buyer status and receiving establishment outside the EU; special place-of-supply services are excluded. Check foreign buyer liability and invoice notes. PDF delivery only; e-invoice delivery is blocked.")
|
|
40981
41634
|
})).min(1).max(MAX_DOCUMENT_LINES).describe(`Line items (max ${MAX_DOCUMENT_LINES})`),
|
|
40982
41635
|
idempotencyKey: IDEMPOTENCY_KEY_SCHEMA
|
|
40983
41636
|
},
|
|
@@ -41046,7 +41699,7 @@ function registerOfferTools(server2) {
|
|
|
41046
41699
|
const enteredTotal = roundCents(line.quantity * line.unitPrice);
|
|
41047
41700
|
const vatAmount = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? roundCents(enteredTotal * vatRate / (100 + vatRate)) : roundCents(enteredTotal * (vatRate / 100));
|
|
41048
41701
|
const subtotal2 = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? roundCents(enteredTotal - vatAmount) : enteredTotal;
|
|
41049
|
-
const
|
|
41702
|
+
const total3 = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? enteredTotal : roundCents(subtotal2 + vatAmount);
|
|
41050
41703
|
return {
|
|
41051
41704
|
line_number: i + 1,
|
|
41052
41705
|
description: line.description,
|
|
@@ -41064,12 +41717,12 @@ function registerOfferTools(server2) {
|
|
|
41064
41717
|
eu_goods_transport_confirmed: params.euGoodsTransportConfirmed,
|
|
41065
41718
|
subtotal: subtotal2,
|
|
41066
41719
|
vat_amount: vatAmount,
|
|
41067
|
-
total:
|
|
41720
|
+
total: total3
|
|
41068
41721
|
};
|
|
41069
41722
|
});
|
|
41070
41723
|
const subtotal = roundCents(calculatedLines.reduce((s, l) => s + l.subtotal, 0));
|
|
41071
41724
|
const vatTotal = roundCents(calculatedLines.reduce((s, l) => s + l.vat_amount, 0));
|
|
41072
|
-
const
|
|
41725
|
+
const total2 = roundCents(subtotal + vatTotal);
|
|
41073
41726
|
const { data: offerId, error: rpcError } = await supabase2.rpc("create_offer_with_lines", {
|
|
41074
41727
|
p_company_id: companyId2,
|
|
41075
41728
|
p_customer_id: params.customerId || null,
|
|
@@ -41083,7 +41736,7 @@ function registerOfferTools(server2) {
|
|
|
41083
41736
|
p_notes: params.notes || null,
|
|
41084
41737
|
p_subtotal: subtotal,
|
|
41085
41738
|
p_vat_total: vatTotal,
|
|
41086
|
-
p_total:
|
|
41739
|
+
p_total: total2,
|
|
41087
41740
|
p_lines: calculatedLines
|
|
41088
41741
|
});
|
|
41089
41742
|
if (rpcError) {
|
|
@@ -41097,7 +41750,7 @@ function registerOfferTools(server2) {
|
|
|
41097
41750
|
title: params.title,
|
|
41098
41751
|
subtotal,
|
|
41099
41752
|
vat_total: vatTotal,
|
|
41100
|
-
total,
|
|
41753
|
+
total: total2,
|
|
41101
41754
|
lines: calculatedLines
|
|
41102
41755
|
});
|
|
41103
41756
|
}
|
|
@@ -42159,7 +42812,7 @@ var VAT_RATE_METADATA = [
|
|
|
42159
42812
|
{ rate: FINNISH_RATES_2026.vat.reduced, label: "Alennettu verokanta", description: "Food, animal feed, restaurants, transport, accommodation, books, medicine, sports, culture, performing artists, sanitary products, broadcasting (from 1.1.2026, was 14%)", basis: "AVL 85\xA7, 85a\xA7" },
|
|
42160
42813
|
{ rate: FINNISH_RATES_2026.vat.secondReduced, label: "Lehdet ja aikakauslehdet", description: "Newspapers and magazines (print and digital) only", basis: "AVL 85a\xA7" },
|
|
42161
42814
|
{ rate: FINNISH_RATES_2026.vat.zero, label: "Nollaverokanta", description: "Exports (AVL 70\xA7), intra-EU B2B supplies (AVL 72a\xA7), vessels, international transport. Input VAT deductible", basis: "AVL 55-72h\xA7" },
|
|
42162
|
-
{ rate: null, label: "Verovapaat myynnit", description: "Healthcare (AVL 34-36\xA7), education (AVL 39-40\xA7), financial services (AVL 41-42\xA7), insurance (AVL 44\xA7).
|
|
42815
|
+
{ rate: null, label: "Verovapaat myynnit", description: "Healthcare (AVL 34-36\xA7), education (AVL 39-40\xA7), financial services (AVL 41-42\xA7), insurance (AVL 44\xA7). Normally no input VAT deduction; assess statutory refund exceptions separately", basis: "AVL 34-44\xA7" }
|
|
42163
42816
|
];
|
|
42164
42817
|
function registerSchemaResources(server2) {
|
|
42165
42818
|
server2.registerResource(
|
|
@@ -42181,7 +42834,7 @@ function registerSchemaResources(server2) {
|
|
|
42181
42834
|
"vat-rates",
|
|
42182
42835
|
"kirjapro://vat-rates",
|
|
42183
42836
|
{
|
|
42184
|
-
description: "Finnish VAT rates (2026). Rates: 25.5% (general), 13.5% (food/restaurants/books/medicine/transport/accommodation/sports), 10% (newspapers/magazines only), 0% (exports/vessels \u2014 input VAT deductible), exempt (
|
|
42837
|
+
description: "Finnish VAT rates (2026). Rates: 25.5% (general), 13.5% (food/restaurants/books/medicine/transport/accommodation/sports), 10% (newspapers/magazines only), 0% (exports/vessels \u2014 input VAT deductible), exempt (normally no input VAT deduction; statutory refund exceptions apply).",
|
|
42185
42838
|
mimeType: "application/json"
|
|
42186
42839
|
},
|
|
42187
42840
|
async (uri) => ({
|
|
@@ -42190,7 +42843,7 @@ function registerSchemaResources(server2) {
|
|
|
42190
42843
|
mimeType: "application/json",
|
|
42191
42844
|
text: JSON.stringify({
|
|
42192
42845
|
rates: VAT_RATE_METADATA,
|
|
42193
|
-
_meta:
|
|
42846
|
+
_meta: regulatoryFreshness("vat")
|
|
42194
42847
|
})
|
|
42195
42848
|
}]
|
|
42196
42849
|
})
|
|
@@ -42271,6 +42924,9 @@ function registerCompanyResources(server2) {
|
|
|
42271
42924
|
}
|
|
42272
42925
|
|
|
42273
42926
|
// src/resources/compliance.ts
|
|
42927
|
+
var exampleOutputVat = FINNISH_RATES_2026.vat.standard;
|
|
42928
|
+
var exampleDeductibleVat = roundCents(exampleOutputVat * 0.6);
|
|
42929
|
+
var exampleNonDeductibleVat = roundCents(exampleOutputVat - exampleDeductibleVat);
|
|
42274
42930
|
var VAT_COMPLIANCE = {
|
|
42275
42931
|
rates: {
|
|
42276
42932
|
current_year: FINNISH_RATES_2026.year,
|
|
@@ -42280,40 +42936,40 @@ var VAT_COMPLIANCE = {
|
|
|
42280
42936
|
zero: { rate: FINNISH_RATES_2026.vat.zero, finnish: "Nollaverokanta", basis: "AVL 55-72h\xA7", examples: "Exports outside EU, intra-community supplies, vessels (sale/rental/chartering), international transport" },
|
|
42281
42937
|
exempt: { rate: null, finnish: "Verovapaat myynnit", basis: "AVL 34-44\xA7", examples: "Healthcare (AVL 34-36\xA7), education (AVL 39-40\xA7), financial services (AVL 41-42\xA7), insurance (AVL 44\xA7), social services (AVL 37-38\xA7), real estate transfers (AVL 27-28\xA7)" }
|
|
42282
42938
|
},
|
|
42283
|
-
rate_history:
|
|
42284
|
-
|
|
42285
|
-
|
|
42286
|
-
{ period: "1.1.2013 \u2192 31.8.2024", standard: 24, reduced: 14, second_reduced: 10 }
|
|
42287
|
-
],
|
|
42939
|
+
rate_history: VAT_RATE_HISTORY,
|
|
42940
|
+
historical_rates: "Choose the rate from delivery/performance, or receipt of an advance payment. A 2025 supply or a 2025 advance can carry 14% on a 2026 document. Genuine credits and bad-debt adjustments retain the original rate. A return period is not a rate-selection date. Preserve historical facts.",
|
|
42941
|
+
deduction_preconditions: "Deduct only VAT legally due on a purchase for deduction-entitled activity, supported by the invoice and actual use. A business expense is not automatically VAT-deductible. Exempt activity, private use and AVL 114 restrictions are assessed separately; use the actual deductible share.",
|
|
42288
42942
|
deduction_rules: [
|
|
42289
|
-
{ type: "Edustuskulut (entertainment)", vat_deduction: "0%", income_tax: "50%", basis: "AVL 114\xA7", booking: "Full gross to expense, NO ALV-saamiset entry" },
|
|
42290
|
-
{ type: "Neuvottelukulut (meetings)", vat_deduction: "
|
|
42291
|
-
{ type: "Henkil\xF6st\xF6kulut (staff events)", vat_deduction: "
|
|
42292
|
-
{ type: "Insurance premiums", vat_deduction: "
|
|
42293
|
-
{ type: "Healthcare (staff)", vat_deduction: "
|
|
42294
|
-
{ type: "Financial services", vat_deduction: "
|
|
42295
|
-
{ type: "Henkil\xF6auto (passenger car M1)", vat_deduction: "
|
|
42296
|
-
{ type: "Pakettiauto/kuorma-auto (van N1+)", vat_deduction: "Proportional to business use", income_tax: "
|
|
42297
|
-
{ type: "Markkinointitilaisuudet (marketing events)", vat_deduction: "
|
|
42943
|
+
{ type: "Edustuskulut (entertainment)", vat_deduction: "0%", income_tax: "50% under EVL; different tax regimes require their own assessment", basis: "AVL 114 \xA7; EVL 8 \xA7 1 mom 8 kohta", booking: "Full gross to expense, NO ALV-saamiset entry" },
|
|
42944
|
+
{ type: "Neuvottelukulut (meetings)", vat_deduction: "Deduction-entitled business share; reasonable ordinary refreshments, not entertainment", income_tax: "Assess business purpose and nature separately", basis: "AVL 102, 114 and 117 \xA7", booking: "Keep participants, purpose and programme; the account label alone proves nothing" },
|
|
42945
|
+
{ type: "Henkil\xF6st\xF6kulut (staff events)", vat_deduction: "Deduction-entitled share of a genuine employer-organised joint event for all staff or an entire unit", income_tax: "Assess business purpose and any employee benefit separately", basis: "AVL 102, 117 \xA7; KHO 2015:98", booking: "No mandatory business programme. Private benefits and mixed activity need separate assessment" },
|
|
42946
|
+
{ type: "Insurance premiums", vat_deduction: "No VAT to deduct when the insurance service is exempt", income_tax: "Depends on insurance and business purpose", basis: "AVL 44 \xA7", booking: "Do not extract input VAT from vakuutusmaksuvero; separately taxable services require their own classification" },
|
|
42947
|
+
{ type: "Healthcare (staff)", vat_deduction: "No VAT on healthcare satisfying the exemption conditions", income_tax: "Assess occupational healthcare and other benefit rules separately", basis: "AVL 34\u201336 \xA7" },
|
|
42948
|
+
{ type: "Financial services", vat_deduction: "No VAT on exempt financial services; software and technical services may be taxable", income_tax: "Assess expense and business purpose separately", basis: "AVL 41\u201342 \xA7", booking: "Classify the invoiced service, not the supplier brand. A payment processor can invoice both exempt and taxable services" },
|
|
42949
|
+
{ type: "Henkil\xF6auto (passenger car M1)", vat_deduction: "Ordinary company car: exclusive deduction-entitled use required; any private, commuting or other non-deductible use removes the deduction", income_tax: "Separate business-use assessment", basis: "AVL 114 \xA7 1 mom 5 kohta and 2 mom", exceptions: "Resale, rental, professional passenger transport and driving tuition: deductible use under those exceptions can remain proportional. Other business use is not automatically deductible when private use also occurs", booking: "Mileage log or other reliable equivalent evidence" },
|
|
42950
|
+
{ type: "Pakettiauto/kuorma-auto (van N1+)", vat_deduction: "Proportional to deduction-entitled use; excludes exempt business and private use", income_tax: "Separate business-use assessment", basis: "AVL 102, 117 \xA7" },
|
|
42951
|
+
{ type: "Markkinointitilaisuudet (marketing events)", vat_deduction: "Deduction-entitled marketing programme and ordinary incidental hospitality; entertainment parts are not deductible", income_tax: "Assess the actual programme and purpose", basis: "AVL 102, 114, 117 \xA7; KHO 2013:63\u201365", booking: "Allocate mixed events by content and participants; an invitation-only event is not automatically entertainment" }
|
|
42298
42952
|
],
|
|
42299
42953
|
reverse_charge: {
|
|
42300
|
-
rule: "Buyer
|
|
42954
|
+
rule: "Buyer assesses output VAT only when the relevant reverse-charge conditions apply. Input VAT deduction is a separate test. A foreign invoice without Finnish VAT is not by itself proof of reverse charge: check place of supply, supplier establishment and applicable registration/exemptions.",
|
|
42301
42955
|
basis: "AVL 8a-8d\xA7, 9\xA7, 65\xA7; EU VAT Directive Art. 196, Art. 199",
|
|
42302
42956
|
scenarios: [
|
|
42303
|
-
{ type: "Foreign B2B", basis: "AVL 9\xA7", note: "
|
|
42304
|
-
{ type: "Construction services", basis: "AVL 8c\xA7", note: 'Since 1.4.2011. Buyer must itself sell/rent construction services "not just occasionally".
|
|
42957
|
+
{ type: "Foreign B2B", basis: "AVL 9, 65 \xA7", note: "General-rule services received by a business establishment in Finland from an eligible foreign seller; special place-of-supply rules and the seller\u2019s Finnish establishment can change treatment" },
|
|
42958
|
+
{ type: "Construction services", basis: "AVL 8c\xA7", note: 'Since 1.4.2011. Buyer must itself sell/rent construction services "not just occasionally". An ordinary buyer commissioning renovation does not qualify under this domestic construction rule alone; foreign-seller reverse charge under AVL 9 \xA7 is a separate test' },
|
|
42305
42959
|
{ type: "Metal scrap and waste", basis: "AVL 8d\xA7", note: "Since 1.1.2015. Between VAT-registered entities" },
|
|
42306
42960
|
{ type: "Emissions allowances", basis: "AVL 8b\xA7", note: "Greenhouse gas emission allowances" },
|
|
42307
42961
|
{ type: "Investment gold", basis: "AVL 8a\xA7", note: "Taxable investment gold, gold materials, gold semi-finished products" }
|
|
42308
42962
|
],
|
|
42309
|
-
booking: "
|
|
42310
|
-
note: "Net VAT effect is zero
|
|
42311
|
-
|
|
42963
|
+
booking: "D expense/asset for net amount plus non-deductible self-assessed VAT; D ALV-saamiset only for deductible VAT; C bank/payable for supplier amount; C ALV-velka for full self-assessed VAT. Use the canonical VAT purchase command, not unlinked VAT account rows.",
|
|
42964
|
+
note: "Net VAT effect is zero only with a full deduction. Report the full assessed VAT and only the deductible share. The seller does not charge VAT on a reverse-charge invoice; this does not remove the buyer\u2019s VAT liability.",
|
|
42965
|
+
implementation_limits: "The MCP purchase shortcut currently has no deduction-share parameter and does not support partial-deduction reverse charge. Do not use its default full deduction for that case: this example describes the accounting rule, not an added tool capability.",
|
|
42966
|
+
example: { net_purchase: 100, rate: FINNISH_RATES_2026.vat.standard, deductible_share: "60%", output_vat: exampleOutputVat, deductible_vat: exampleDeductibleVat, non_deductible_vat: exampleNonDeductibleVat, expense: roundCents(100 + exampleNonDeductibleVat), payable_vat: exampleNonDeductibleVat },
|
|
42967
|
+
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
42968
|
},
|
|
42313
42969
|
common_mistakes: [
|
|
42314
|
-
"
|
|
42315
|
-
"
|
|
42316
|
-
"
|
|
42970
|
+
"For a qualifying domestic food/coffee supply in 2026, use the current reduced rate, not the standard rate; original events and advances can use an earlier rate",
|
|
42971
|
+
"For a domestic passenger-transport supply in 2026, use the current reduced rate; older supplies, advances and credits retain their event-date rate",
|
|
42972
|
+
"Replacing a historical 14% rate merely because the document or return is dated in 2026 \u2014 retain the original supply/advance/credit treatment",
|
|
42317
42973
|
"Restaurant alcohol at 13.5% \u2014 alcohol is 25.5% even at restaurants",
|
|
42318
42974
|
"Newspaper/magazine at 13.5% \u2014 periodicals are 10%",
|
|
42319
42975
|
"Splitting VAT from insurance premium \u2014 insurance is VAT-exempt, not zero-rated",
|
|
@@ -42327,40 +42983,44 @@ var INVOICE_REQUIREMENTS = {
|
|
|
42327
42983
|
mandatory_fields: [
|
|
42328
42984
|
{ kohta: 1, field: "Laskun antamisp\xE4iv\xE4", english: "Invoice date", status: "implemented", relevance: "always" },
|
|
42329
42985
|
{ kohta: 2, field: "Juokseva tunniste", english: "Sequential invoice number", status: "implemented", relevance: "always", note: "Gaps must be detectable per Vero requirement" },
|
|
42330
|
-
{ kohta: 3, field: "Myyj\xE4n ALV-tunniste", english: "Seller VAT ID (FI + Y-tunnus)", status: "implemented", relevance: "
|
|
42986
|
+
{ kohta: 3, field: "Myyj\xE4n ALV-tunniste", english: "Seller VAT ID (FI + Y-tunnus)", status: "implemented", relevance: "conditional", note: "Y-tunnus identifies the Finnish business on business documents. Use the applicable VAT registration identifier; FI-format is required for the intra-EU transactions specified by Vero 5.1.3. Do not present an unregistered seller as VAT-registered" },
|
|
42331
42987
|
{ kohta: 4, field: "Ostajan ALV-tunniste", english: "Buyer VAT ID", status: "implemented", relevance: "conditional", note: "Required for reverse charge and EU B2B" },
|
|
42332
42988
|
{ kohta: 5, field: "Myyj\xE4n ja ostajan nimi ja osoite", english: "Names and addresses", status: "implemented", relevance: "always" },
|
|
42333
42989
|
{ kohta: 6, field: "Tavaroiden/palvelujen kuvaus ja m\xE4\xE4r\xE4", english: "Description and quantity", status: "implemented", relevance: "always" },
|
|
42334
42990
|
{ kohta: 7, field: "Toimitusp\xE4iv\xE4 / suoritusp\xE4iv\xE4", english: "Delivery date", status: "partial", relevance: "conditional", note: "NOT required when same as invoice date (Vero.fi 5.1.7). Required when different" },
|
|
42335
42991
|
{ kohta: 8, field: "Veron peruste, yksikk\xF6hinta, alennukset", english: "Tax base, unit price, discounts", status: "implemented", relevance: "always" },
|
|
42336
|
-
{ kohta: 9, field: "Verokanta", english: "VAT rate", status: "implemented", relevance: "
|
|
42337
|
-
{ kohta: 10, field: "Suoritettavan veron m\xE4\xE4r\xE4", english: "VAT amount", status: "implemented", relevance: "
|
|
42338
|
-
{ kohta: 11, field: "Merkint\xE4 verottomuudesta", english: "Tax exemption notation", status: "
|
|
42339
|
-
{ kohta: 12, field: "Merkint\xE4 k\xE4\xE4nnetyst\xE4 verovelvollisuudesta", english: "Reverse charge notation", status: "
|
|
42992
|
+
{ kohta: 9, field: "Verokanta", english: "VAT rate", status: "implemented", relevance: "conditional", note: "State each applicable rate when the seller owes VAT. Do not state a VAT rate on a reverse-charge invoice (Vero 5.1.9)" },
|
|
42993
|
+
{ kohta: 10, field: "Suoritettavan veron m\xE4\xE4r\xE4", english: "VAT amount", status: "implemented", relevance: "conditional", note: "State VAT payable by the seller; do not state a VAT amount on a reverse-charge or qualifying margin-scheme invoice. An unregistered seller must not state VAT/rate; pending registration has the explicit Vero 5.1.10 notation exception" },
|
|
42994
|
+
{ kohta: 11, field: "Merkint\xE4 verottomuudesta", english: "Tax exemption notation", status: "implemented_for_supported_treatments", relevance: "conditional", note: 'PDF and supported Finvoice categories render stored VAT notes. Simple "Veroton myynti" suffices. Citing AVL section is recommended but not mandatory (Vero.fi 5.1.11)' },
|
|
42995
|
+
{ kohta: 12, field: "Merkint\xE4 k\xE4\xE4nnetyst\xE4 verovelvollisuudesta", english: "Reverse charge notation", status: "implemented_for_supported_treatments", relevance: "conditional", note: 'PDF and Finvoice render the stored reverse-charge note. "K\xE4\xE4nnetty verovelvollisuus" phrase is mandatory. Legal basis citation is recommended, not required' },
|
|
42340
42996
|
{ kohta: 13, field: "Merkint\xE4 itselaskutuksesta", english: "Self-billing notation", status: "n/a", relevance: "situational", note: "When buyer issues invoice on seller behalf" },
|
|
42341
42997
|
{ kohta: 14, field: "Uuden kuljetusv\xE4lineen tiedot", english: "New means of transport details", status: "n/a", relevance: "situational" },
|
|
42342
42998
|
{ kohta: 15, field: "Voittomarginaalij\xE4rjestelm\xE4", english: "Margin scheme (used goods)", status: "n/a", relevance: "situational" },
|
|
42343
42999
|
{ kohta: 16, field: "Voittomarginaalij\xE4rjestelm\xE4 \u2014 matkatoimistot", english: "Margin scheme (travel agents)", status: "n/a", relevance: "situational" },
|
|
42344
43000
|
{ kohta: 17, field: "Verollisen sijoituskullan myynti", english: "Taxable investment gold", status: "n/a", relevance: "situational" },
|
|
42345
|
-
{ kohta: 18, field: "Viittaus alkuper\xE4iseen laskuun", english: "Credit note reference (muutoslasku)", status: "
|
|
43001
|
+
{ kohta: 18, field: "Viittaus alkuper\xE4iseen laskuun", english: "Credit note reference (muutoslasku)", status: "implemented", relevance: "conditional", note: "Credit-note PDF and Finvoice include original invoice number and date; identify the changes as well as the original invoice (AVL 209 e \xA7 18 kohta)" }
|
|
42346
43002
|
],
|
|
42347
43003
|
simplified_invoice: {
|
|
42348
43004
|
threshold: 400,
|
|
42349
|
-
note: "
|
|
43005
|
+
note: "Gross invoice total \u2264 400 \u20AC. Also qualifying retail/near-exclusive consumer sales, restaurant/catering and passenger transport (not services bought for resale), and parking-meter or similar machine receipts. Not available for intra-EU goods supplies, distance sales or another EU state\u2019s reverse-charge supplies (AVL 209 f \xA7).",
|
|
43006
|
+
required: ["Issue date", "Seller name and VAT identifier", "Quantity/type of goods or type of service", "VAT amount or tax base by rate", "For a correction: original invoice reference and the changed information"]
|
|
42350
43007
|
},
|
|
42351
43008
|
muutoslasku: {
|
|
42352
|
-
status: "
|
|
42353
|
-
mandatory: "Unambiguous reference to original invoice (
|
|
42354
|
-
recommended: "
|
|
43009
|
+
status: "implemented",
|
|
43010
|
+
mandatory: "Unambiguous reference to the original invoice and the information being changed (AVL 209 e \xA7 18 kohta)",
|
|
43011
|
+
recommended: "Label and reason help interpretation. A separate systematic credit-note numbering series is allowed; the original rate is preserved.",
|
|
43012
|
+
implementation: "Canonical credit-note flow, SPA and server PDF original reference, and Finvoice OriginalInvoiceNumber/OriginalInvoiceDate. service_outside_eu PDF is supported; its Finvoice O category remains blocked pending complete validation."
|
|
42355
43013
|
}
|
|
42356
43014
|
};
|
|
42357
43015
|
var FILING_DEADLINES = {
|
|
42358
43016
|
vat_filing: {
|
|
42359
43017
|
thresholds: [
|
|
42360
|
-
{ revenue: "
|
|
42361
|
-
{ revenue: "30,000\
|
|
43018
|
+
{ revenue: "\u2264 30,000 \u20AC", periods: ["monthly", "quarterly", "annually"], note: "Annual: deadline last day of February of following year (28.2 or 29.2 in leap year)" },
|
|
43019
|
+
{ revenue: "> 30,000 and \u2264 100,000 \u20AC", periods: ["monthly", "quarterly"], note: "Quarterly: 12th of 2nd month after quarter" },
|
|
42362
43020
|
{ revenue: "> 100,000 \u20AC", periods: ["monthly"], note: "Monthly: 12th of 2nd month after reporting month" }
|
|
42363
43021
|
],
|
|
43022
|
+
eligibility: "Calendar-year turnover or equivalent revenue, including foreign turnover. Monthly is the default. A longer period requires eligibility and registration/application; follow the Tax Administration decision and its effective date. Do not switch from turnover alone. Primary production and visual artists have separate rules.",
|
|
43023
|
+
calendar: "When a due date falls on a weekend or Finnish holiday, use the next permitted business day. Shortened transition periods can have their own due date.",
|
|
42364
43024
|
monthly_example: "January 2026 VAT \u2192 deadline 12.3.2026",
|
|
42365
43025
|
quarterly_deadlines: {
|
|
42366
43026
|
Q1: "12.5",
|
|
@@ -42370,31 +43030,35 @@ var FILING_DEADLINES = {
|
|
|
42370
43030
|
}
|
|
42371
43031
|
},
|
|
42372
43032
|
tyel: {
|
|
42373
|
-
monthly_report: "
|
|
42374
|
-
payment: "
|
|
43033
|
+
monthly_report: "Report earnings and pension-insurance details to Tulorekisteri within 5 calendar days after each payday; the pension company receives the data from the register",
|
|
43034
|
+
payment: "Pay the pension insurer\u2019s invoice by its agreed due date. For example, Ilmarinen normally uses the last day of the month after payday; a later agreed date can include insurance premium interest. No universal 20th-day TyEL deadline"
|
|
42375
43035
|
},
|
|
42376
43036
|
tulorekisteri: {
|
|
42377
43037
|
palkkatietoilmoitus: "5 calendar days from payment date \u2192 Tulorekisteri",
|
|
42378
43038
|
tyonantajan_erillisilmoitus: "5th of following month \u2192 Tulorekisteri (sotu totals)"
|
|
42379
43039
|
},
|
|
42380
|
-
sairausvakuutusmaksu_payment: "12th of
|
|
43040
|
+
sairausvakuutusmaksu_payment: "Monthly employer period: 12th of the month after payday; an approved quarterly employer period uses the 12th of the month after the quarter. Reporting to Tulorekisteri still follows its own deadlines; apply business-day adjustment",
|
|
42381
43041
|
tilinpaatos: "4 months from fiscal year end (KPL 3:6)",
|
|
42382
43042
|
prh_filing: "8 months from fiscal year end (OYL 8:10)",
|
|
42383
43043
|
pienyrittaja: {
|
|
42384
43044
|
threshold: 2e4,
|
|
42385
|
-
note: "
|
|
42386
|
-
alarajahuojennus: "
|
|
43045
|
+
note: "From 1.1.2025: qualifying domestic small-business sales can be exempt if turnover is at most 20,000 \u20AC in both the current and preceding calendar year. Registration begins at the sale crossing the current-year threshold, including that whole sale. Voluntary registration and special purchase/import obligations are separate; no general exemption from foreign-service reverse charge",
|
|
43046
|
+
alarajahuojennus: "Removed for periods from 1.1.2025; historical relief for earlier periods is not rewritten"
|
|
42387
43047
|
},
|
|
42388
43048
|
maksuperusteinen_alv: {
|
|
42389
43049
|
threshold: 5e5,
|
|
42390
|
-
note: "
|
|
42391
|
-
application: "
|
|
43050
|
+
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. Check the actual company basis and every posting/reporting path before using this legal option; this resource does not switch the application\u2019s VAT basis.",
|
|
43051
|
+
application: "No application: the business applies the cash basis in its bookkeeping. Since 2025 the VAT return has no cash-basis field.",
|
|
43052
|
+
source: "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/pienyrityksen-maksuperusteinen-alv/",
|
|
43053
|
+
sources_verified_at: "2026-10-02"
|
|
42392
43054
|
},
|
|
42393
43055
|
late_penalties: {
|
|
42394
|
-
myohastymismaksu: "3 \u20AC/day for
|
|
43056
|
+
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
43057
|
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"
|
|
43058
|
+
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.",
|
|
43059
|
+
nil_return: "Even when no VAT activity occurred, a nil return must be filed for each verokausi",
|
|
43060
|
+
source: "https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48377/seuraamusmaksut-oma-aloitteisessa-verotuksessa5/",
|
|
43061
|
+
sources_verified_at: "2026-10-02"
|
|
42398
43062
|
},
|
|
42399
43063
|
payment_deadline: "Same as filing deadline (12th of M+2 for monthly/quarterly, last day of Feb for annual)",
|
|
42400
43064
|
omavero_field_codes: {
|
|
@@ -42412,33 +43076,33 @@ var FILING_DEADLINES = {
|
|
|
42412
43076
|
318: "Vero rakentamispalvelun ja metalliromun ostoista (construction/scrap reverse charge VAT)"
|
|
42413
43077
|
},
|
|
42414
43078
|
tax_bases: {
|
|
42415
|
-
309: "0-verokannan alainen liikevaihto (zero-rate turnover
|
|
43079
|
+
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
43080
|
310: "Tavaroiden maahantuonnit EU:n ulkopuolelta (goods imports from outside EU)",
|
|
42417
|
-
311: "Tavaroiden myynnit muihin EU-maihin (goods sales to EU)",
|
|
43081
|
+
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
43082
|
312: "Palveluiden myynnit muihin EU-maihin (service sales to EU)",
|
|
42419
43083
|
313: "Tavaraostot muista EU-maista (goods purchases from EU)",
|
|
42420
43084
|
314: "Palveluostot muista EU-maista (service purchases from EU)",
|
|
42421
43085
|
319: "Rakentamispalvelun ja metalliromun myynnit (construction/scrap sales)",
|
|
42422
43086
|
320: "Rakentamispalvelun ja metalliromun ostot (construction/scrap purchases)"
|
|
42423
43087
|
},
|
|
42424
|
-
removed_2025: "315, 316, 317 (alarajahuojennus fields) removed from 1.1.2025"
|
|
43088
|
+
removed_2025: "315, 316, 317 (alarajahuojennus fields) removed from 1.1.2025",
|
|
43089
|
+
sources_verified_at: "2026-10-02"
|
|
42425
43090
|
}
|
|
42426
43091
|
};
|
|
42427
43092
|
var SOCIAL_INSURANCE_2026 = {
|
|
42428
43093
|
year: FINNISH_RATES_2026.year,
|
|
42429
43094
|
sources: [
|
|
42430
|
-
"
|
|
42431
|
-
"
|
|
42432
|
-
"https://tyel-vakuutus.fi/tyonantajan-sivukulut/"
|
|
43095
|
+
...regulatoryFreshness("payroll").sources.map((source) => source.url),
|
|
43096
|
+
...regulatoryFreshness("payrollEstimates").sources.map((source) => source.url)
|
|
42433
43097
|
],
|
|
42434
43098
|
tyel: {
|
|
42435
43099
|
keskimaarainen: FINNISH_RATES_2026.social.tyelKeskimaarainen,
|
|
42436
43100
|
perusmaksu: FINNISH_RATES_2026.social.tyelPerusmaksu,
|
|
42437
|
-
tilapaistyonantaja:
|
|
43101
|
+
tilapaistyonantaja: FINNISH_RATES_2026.social.tyelTemporary,
|
|
42438
43102
|
employee: FINNISH_RATES_2026.social.tyelEmployee,
|
|
42439
43103
|
employer_average: FINNISH_RATES_2026.social.tyelEmployerAverage,
|
|
42440
43104
|
employer_perusmaksu: FINNISH_RATES_2026.social.tyelEmployer,
|
|
42441
|
-
note: "
|
|
43105
|
+
note: "Employee share is statutory for the applicable 2026 insured employee. Base, temporary-employer and average total premiums are different models. The actual employer share follows the insurance contract/invoice, including company-specific expenses and adjustments; do not book the average as an exact liability"
|
|
42442
43106
|
},
|
|
42443
43107
|
sotu: {
|
|
42444
43108
|
rate: FINNISH_RATES_2026.social.sotu,
|
|
@@ -42450,20 +43114,19 @@ var SOCIAL_INSURANCE_2026 = {
|
|
|
42450
43114
|
employer_large: FINNISH_RATES_2026.social.ttvEmployerLarge,
|
|
42451
43115
|
employer_large_threshold: FINNISH_RATES_2026.social.ttvEmployerLargeThreshold,
|
|
42452
43116
|
employee: FINNISH_RATES_2026.social.ttvEmployee,
|
|
42453
|
-
note: "
|
|
43117
|
+
note: "Apply the lower employer share up to the calendar-year payroll threshold and the upper share only to the excess. Age, insurance scope, owner status and exemption conditions are separate; these figures describe an ordinary insured employee."
|
|
42454
43118
|
},
|
|
42455
43119
|
tapaturma: {
|
|
42456
43120
|
average: FINNISH_RATES_2026.social.tapaturmaAverage,
|
|
42457
|
-
range: "0.05\u20135.00%",
|
|
42458
43121
|
payer: "employer",
|
|
42459
43122
|
note: "Company-specific rate based on industry and claims history"
|
|
42460
43123
|
},
|
|
42461
43124
|
ryhmahenkivakuutus: {
|
|
42462
43125
|
rate: FINNISH_RATES_2026.social.ryhmahenki,
|
|
42463
43126
|
payer: "employer",
|
|
42464
|
-
note: "
|
|
43127
|
+
note: "Planning estimate only; obligation and actual premium depend on the applicable collective agreement and insurance contract"
|
|
42465
43128
|
},
|
|
42466
|
-
total_employer_side_costs: "
|
|
43129
|
+
total_employer_side_costs: "Calculate from the applicable employee scope and actual contract premiums; there is no single exact statutory total for every employer",
|
|
42467
43130
|
// Account numbers are built from the canonical chart (src/accounts.ts) so they
|
|
42468
43131
|
// can never drift from the YSE 2017 numbering the agent actually books against.
|
|
42469
43132
|
// DO NOT hardcode literals here — a wrong-but-existing number (e.g. 2320, which
|
|
@@ -42483,124 +43146,172 @@ var SOCIAL_INSURANCE_2026 = {
|
|
|
42483
43146
|
var BOOKKEEPING_RULES = {
|
|
42484
43147
|
legal_basis: "Kirjanpitolaki 1336/1997",
|
|
42485
43148
|
core_principles: [
|
|
42486
|
-
{ rule: "Double-entry (kahdenkertainen kirjanpito)", basis: "KPL 2:
|
|
43149
|
+
{ rule: "Double-entry (kahdenkertainen kirjanpito)", basis: "KPL 1:2 and 1:1a", note: "Kirjapro uses double-entry: total debits = total credits. Certain natural-person businesses may qualify for statutory single-entry bookkeeping under separate limits" },
|
|
42487
43150
|
{ rule: "Voucher principle (tositeperiaate)", basis: "KPL 2:5", note: "Every entry must be based on a numbered voucher" },
|
|
42488
43151
|
{ rule: "Audit trail (kirjausketju)", basis: "KPL 2:6", note: "Tosite \u2192 tositenumero \u2192 p\xE4iv\xE4kirja \u2192 p\xE4\xE4kirja \u2192 tilinp\xE4\xE4t\xF6s \u2014 unbroken chain" },
|
|
42489
43152
|
{ rule: "Sequential numbering", basis: "KPL 2:5", note: "Gap detection must be possible from the numbering sequence" },
|
|
42490
|
-
{ rule: "Closed period immutability", basis: "KPL 2:7", note: "
|
|
43153
|
+
{ rule: "Closed period immutability", basis: "KPL 2:7", note: "Kirjapro prevents direct changes in closed fiscal years and preserves correction links. Determine the proper accounting period and any original VAT return correction separately; a current-period voucher does not by itself correct an earlier tax return" }
|
|
42491
43154
|
],
|
|
42492
43155
|
retention: [
|
|
42493
|
-
{ type: "Accounting records
|
|
42494
|
-
{ type: "Financial statements
|
|
42495
|
-
{ type: "Vouchers, invoices
|
|
42496
|
-
{ type: "E-invoice
|
|
43156
|
+
{ type: "Accounting ledgers, chart and list of records", years: 10, starts: "From the fiscal-year closing date", basis: "KPL 2:10 1 mom" },
|
|
43157
|
+
{ type: "Financial statements and management report", years: 10, starts: "From the fiscal-year closing date", basis: "KPL 2:10 1 mom" },
|
|
43158
|
+
{ type: "Vouchers, invoices and business correspondence", years: 6, starts: "At the end of the calendar year during which the fiscal year ended", basis: "KPL 2:10 2 mom" },
|
|
43159
|
+
{ type: "E-invoice contents and their audit trail", years: 6, starts: "At the end of the calendar year during which the fiscal year ended", basis: "KPL 2:7, 2:10 2 mom; AVL 209 n\u2013p \xA7", note: "Preserve readable, reproducible information and integrity; keeping source XML is a Kirjapro archive choice, not a universal statutory file-format requirement" }
|
|
42497
43160
|
],
|
|
43161
|
+
retention_exceptions: "Other rules can require longer retention: real-estate VAT adjustment invoices/records generally 13 years from the end of the calendar year in which the adjustment period began (AVL 209 q \xA7). Do not delete solely from the general six-year limit.",
|
|
42498
43162
|
fiscal_year: {
|
|
42499
|
-
normal_duration: "12 months",
|
|
42500
|
-
max_duration: "18 months (
|
|
43163
|
+
normal_duration: "12 months; a sole trader normally uses the calendar year. A different year requires double-entry bookkeeping and financial statements under KPL 1:4.",
|
|
43164
|
+
max_duration: "18 months when starting, ending or changing the fiscal-year end (KPL 1:4)",
|
|
42501
43165
|
tilinpaatos_deadline: "4 months from fiscal year end",
|
|
42502
43166
|
prh_deadline: "8 months from fiscal year end (Oy, Oyj)"
|
|
42503
43167
|
},
|
|
42504
43168
|
company_sizes: [
|
|
42505
|
-
{ class: "Mikroyritys",
|
|
42506
|
-
{ class: "Pienyritys",
|
|
42507
|
-
|
|
42508
|
-
|
|
42509
|
-
|
|
43169
|
+
{ class: "Mikroyritys", revenue_limit: COMPANY_SIZE_RULE.values.soleTraderStatements.revenue, balance_limit: COMPANY_SIZE_RULE.values.soleTraderStatements.assets, employees_limit: COMPANY_SIZE_RULE.values.soleTraderStatements.employees, framework: "PMA micro if eligible and selected; the small-company PMA framework may be chosen as a complete alternative" },
|
|
43170
|
+
{ class: "Pienyritys", revenue_limit: COMPANY_SIZE_RULE.values.smallCompany.revenue, balance_limit: COMPANY_SIZE_RULE.values.smallCompany.assets, employees_limit: COMPANY_SIZE_RULE.values.smallCompany.employees, framework: "PMA small when applicable" }
|
|
43171
|
+
],
|
|
43172
|
+
classification: "Reaching a limit is not exceeding it. Count exceeded limits per fiscal year and inspect the preceding year and established size class. A continuing business changes its established class after two consecutive years with at least two limits exceeded, or two consecutive years with at most one exceeded on the way back down. KILA 1982/2018 covers the small-company two-year transition. Applying that continuity to micro companies remains an explicitly recorded agent interpretation, not accountant approval. First fiscal years and changes in the legal thresholds need their own dates. Using actual, rather than annualised, turnover for non-12-month micro periods is also an agent interpretation with lower confidence. These figures do not determine audit obligations.",
|
|
43173
|
+
threshold_applicability: "Act 605/2024: these increased limits are mandatory for fiscal years starting on or after 1.1.2025 and optional for those starting on or after 1.1.2024. The registry coverage start is not the first legal effective date.",
|
|
43174
|
+
framework: "Size classification, selected reporting framework, sole-trader financial-statement obligation and audit obligation are distinct. Micro does not mean that every disclosure or a complete statutory financial statement can be omitted."
|
|
43175
|
+
};
|
|
43176
|
+
var CONTENT_REVIEWS = {
|
|
43177
|
+
vat: [{
|
|
43178
|
+
checked_at: "2026-10-03",
|
|
43179
|
+
review_due: "2026-11-20",
|
|
43180
|
+
scope: "Historical rates, deduction conditions, vehicle/staff/marketing distinctions and separate reverse-charge liability/deduction; numerical rate provenance is in numeric_rules.",
|
|
43181
|
+
sources: [
|
|
43182
|
+
"https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/arvonlisaveroprosentit/Arvonlisaverokantojen-muutostilanteet/",
|
|
43183
|
+
"https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/335806/arvonlis%C3%A4verovelvollisen-opas/",
|
|
43184
|
+
"https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48679/palvelujen-ulkomaankaupan--arvonlis%C3%A4verotus2/",
|
|
43185
|
+
"https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48462/markkinointi--ja-edustustilaisuudet-arvonlis%C3%A4verotuksessa/",
|
|
43186
|
+
"https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/ajoneuvon-ja-sen-kayttokulujen-arvonlisavero/",
|
|
43187
|
+
"https://kho.fi/fi/index/paatokset/ennakkopaatokset/1434454271753.html",
|
|
43188
|
+
"https://www.finlex.fi/fi/laki/ajantasa/1993/19931501",
|
|
43189
|
+
"https://www.finlex.fi/fi/laki/ajantasa/1968/19680360"
|
|
43190
|
+
]
|
|
43191
|
+
}],
|
|
43192
|
+
invoicing: [{
|
|
43193
|
+
checked_at: "2026-10-03",
|
|
43194
|
+
review_due: "2026-11-20",
|
|
43195
|
+
scope: "AVL 209 e/f markings, simplified-invoice exclusions and correction requirements; implementation status is separately checked against the local source tree, not a claim about an already deployed release.",
|
|
43196
|
+
sources: [INVOICE_REQUIREMENTS.source]
|
|
43197
|
+
}],
|
|
43198
|
+
deadlines: [{
|
|
43199
|
+
checked_at: "2026-10-03",
|
|
43200
|
+
review_due: "2026-11-20",
|
|
43201
|
+
scope: "Inclusive 30,000/100,000 limits, application/effective dates, small-business two-calendar-year rule, earnings reports and contractual TyEL payment dates.",
|
|
43202
|
+
sources: [
|
|
43203
|
+
"https://www.finlex.fi/fi/laki/ajantasa/2016/20160768",
|
|
43204
|
+
"https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/ilmoitus-ja-maksuohjeet/verokauden-muutos/",
|
|
43205
|
+
"https://www.vero.fi/tietoa-verohallinnosta/yhteystiedot-ja-asiointi/lomakkeet/kuvaus/hakemus_omaaloitteisten_verojen_verokau/",
|
|
43206
|
+
"https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48658/arvonlisaveroton-vahainen-toiminta/",
|
|
43207
|
+
"https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/yritys_tyonantajana/nain-ilmoitat-palkat-ja-tyonantajasuoritukset/",
|
|
43208
|
+
"https://www.ilmarinen.fi/ajankohtaista/uutiset-ja-tiedotteet/2026/tyel-vakuutusmaksukorko-nousee-270-prosenttiin-1.7.2026-alkaen/"
|
|
43209
|
+
]
|
|
43210
|
+
}, {
|
|
43211
|
+
checked_at: "2026-10-02",
|
|
43212
|
+
review_due: "2026-11-20",
|
|
43213
|
+
valid_through: "2026-12-31",
|
|
43214
|
+
scope: "Earlier review retained: cash-basis scope, late filing, 2026 late-payment interest and VSRALVKV v1.10 field map. A newer deadline edit does not reverify these claims.",
|
|
43215
|
+
sources: [FILING_DEADLINES.maksuperusteinen_alv.source, FILING_DEADLINES.late_penalties.source, FILING_DEADLINES.omavero_field_codes.source_url]
|
|
43216
|
+
}],
|
|
43217
|
+
"social-insurance": [{
|
|
43218
|
+
checked_at: "2026-10-03",
|
|
43219
|
+
review_due: "2026-11-20",
|
|
43220
|
+
scope: "Distinction between statutory employee contributions and employer contract/average estimates. Numerical source checks retain their registry dates.",
|
|
43221
|
+
sources: ["https://www.ilmarinen.fi/tyonantaja/tyel-maksut/", "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/yritys_tyonantajana/sosiaalivakuutusmaksut/"]
|
|
43222
|
+
}],
|
|
43223
|
+
bookkeeping: [{
|
|
43224
|
+
checked_at: "2026-10-03",
|
|
43225
|
+
review_due: "2026-12-01",
|
|
43226
|
+
scope: "KPL retention start dates, AVL real-estate exception, size versus reporting framework and threshold equality. Micro continuity and non-12-month turnover remain labelled interpretations.",
|
|
43227
|
+
sources: [
|
|
43228
|
+
"https://www.finlex.fi/fi/laki/ajantasa/1997/19971336",
|
|
43229
|
+
"https://www.finlex.fi/fi/laki/ajantasa/1993/19931501",
|
|
43230
|
+
"https://kirjanpitolautakunta.fi/-/pienyrityksen-ja-pienkonsernin-raja-arvojen-arvioimises-1",
|
|
43231
|
+
"https://kirjanpitolautakunta.fi/-/konsernikokorajojen-arvioimisesta",
|
|
43232
|
+
"https://www.finlex.fi/fi/lainsaadanto/saadoskokoelma/2024/605"
|
|
43233
|
+
]
|
|
43234
|
+
}]
|
|
42510
43235
|
};
|
|
42511
43236
|
var TOPICS = {
|
|
42512
43237
|
vat: {
|
|
42513
|
-
description: "Finnish VAT
|
|
42514
|
-
|
|
43238
|
+
description: "Finnish VAT: current and historical rates, conditional deductions and reverse-charge liability separately from deduction. A 2026 document can lawfully carry historical VAT.",
|
|
43239
|
+
rules: ["vat"],
|
|
42515
43240
|
body: VAT_COMPLIANCE
|
|
42516
43241
|
},
|
|
42517
43242
|
invoicing: {
|
|
42518
|
-
description: "
|
|
42519
|
-
|
|
43243
|
+
description: "AVL 209 e/f invoice markings, simplified-invoice exceptions and credit-note requirements. Implementation status describes supported local code paths, not all possible VAT schemes.",
|
|
43244
|
+
rules: [],
|
|
42520
43245
|
body: INVOICE_REQUIREMENTS
|
|
42521
43246
|
},
|
|
42522
43247
|
deadlines: {
|
|
42523
|
-
description: "Finnish
|
|
42524
|
-
|
|
43248
|
+
description: "Finnish filing periods: \u226430K annual / \u2264100K quarterly eligibility and application, VAT and earnings-report deadlines, TyEL invoice terms and two-year 20K small-business exemption.",
|
|
43249
|
+
rules: [],
|
|
42525
43250
|
body: FILING_DEADLINES
|
|
42526
43251
|
},
|
|
42527
43252
|
"social-insurance": {
|
|
42528
|
-
description: "2026
|
|
42529
|
-
|
|
43253
|
+
description: "2026 employee social-insurance rates and separately labelled employer premium estimates; use the actual insurance contract for exact employer liabilities.",
|
|
43254
|
+
rules: ["payroll", "payrollEstimates"],
|
|
42530
43255
|
body: SOCIAL_INSURANCE_2026
|
|
42531
43256
|
},
|
|
42532
43257
|
bookkeeping: {
|
|
42533
|
-
description: "
|
|
42534
|
-
|
|
43258
|
+
description: "Finnish bookkeeping principles, 10/6-year retention with different start dates, fiscal-year requirements and micro/small size boundaries separate from the selected reporting framework.",
|
|
43259
|
+
rules: ["companySizes"],
|
|
42535
43260
|
body: BOOKKEEPING_RULES
|
|
42536
43261
|
}
|
|
42537
43262
|
};
|
|
42538
|
-
function
|
|
42539
|
-
const
|
|
42540
|
-
|
|
42541
|
-
|
|
42542
|
-
|
|
42543
|
-
|
|
42544
|
-
|
|
42545
|
-
|
|
42546
|
-
|
|
42547
|
-
|
|
42548
|
-
|
|
42549
|
-
|
|
42550
|
-
|
|
42551
|
-
|
|
43263
|
+
function complianceTopicMeta(slug, today = /* @__PURE__ */ new Date()) {
|
|
43264
|
+
const topic = TOPICS[slug];
|
|
43265
|
+
const day = helsinkiDateString(today);
|
|
43266
|
+
const rules = topic.rules.map((rule) => regulatoryFreshness(rule, today));
|
|
43267
|
+
const reviews = CONTENT_REVIEWS[slug];
|
|
43268
|
+
const dates = [...rules.map((rule) => rule.sources_verified_at), ...reviews.map((review) => review.checked_at)];
|
|
43269
|
+
const expiries = [...rules.map((rule) => rule.valid_through), ...reviews.map((review) => review.valid_through)].filter((date5) => !!date5).sort();
|
|
43270
|
+
const expiry = expiries[0] ?? null;
|
|
43271
|
+
const reviewDue = [...rules.map((rule) => rule.review_due), ...reviews.map((review) => review.review_due)].sort()[0];
|
|
43272
|
+
return {
|
|
43273
|
+
published_at: null,
|
|
43274
|
+
dated_rates: slug === "vat" || slug === "social-insurance" || slug === "deadlines",
|
|
43275
|
+
sources_verified_at: dates.sort()[0],
|
|
43276
|
+
verification_date_meaning: "Oldest included review, not a topic-wide claim that every fact was rechecked on one day. Consult each scope and numeric rule.",
|
|
43277
|
+
valid_from: null,
|
|
43278
|
+
valid_through: expiry,
|
|
43279
|
+
review_due: reviewDue,
|
|
43280
|
+
review_overdue: day > reviewDue,
|
|
43281
|
+
stale: rules.some((rule) => rule.stale) || day > reviewDue || expiry !== null && day > expiry,
|
|
43282
|
+
days_until_expiry: expiry === null ? null : Math.round((Date.parse(`${expiry}T00:00:00Z`) - Date.parse(`${day}T00:00:00Z`)) / 864e5),
|
|
43283
|
+
content_reviews: reviews,
|
|
43284
|
+
numeric_rules: rules,
|
|
43285
|
+
accountant_confirmed: false,
|
|
43286
|
+
decision_status: "Agent-reviewed primary sources; interpretations are provisional and separately identified. Not an accountant approval or a guarantee for a particular transaction.",
|
|
43287
|
+
implementation_verified_at: slug === "invoicing" ? "2026-10-03" : null
|
|
43288
|
+
};
|
|
43289
|
+
}
|
|
43290
|
+
function complianceResource(slug, today = /* @__PURE__ */ new Date()) {
|
|
43291
|
+
return { _meta: complianceTopicMeta(slug, today), ...TOPICS[slug].body };
|
|
42552
43292
|
}
|
|
42553
43293
|
function registerComplianceResources(server2) {
|
|
42554
|
-
for (const
|
|
43294
|
+
for (const slug of Object.keys(TOPICS)) {
|
|
42555
43295
|
server2.registerResource(
|
|
42556
43296
|
`compliance-${slug}`,
|
|
42557
43297
|
`kirjapro://compliance/${slug}`,
|
|
42558
|
-
{
|
|
42559
|
-
|
|
42560
|
-
mimeType: "application/json"
|
|
42561
|
-
},
|
|
42562
|
-
async (uri) => ({
|
|
42563
|
-
contents: [{
|
|
42564
|
-
uri: uri.href,
|
|
42565
|
-
mimeType: "application/json",
|
|
42566
|
-
text: wrapWithMeta(topic.body, topic.dated)
|
|
42567
|
-
}]
|
|
42568
|
-
})
|
|
43298
|
+
{ description: TOPICS[slug].description, mimeType: "application/json" },
|
|
43299
|
+
async (uri) => ({ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(complianceResource(slug)) }] })
|
|
42569
43300
|
);
|
|
42570
43301
|
}
|
|
42571
43302
|
server2.registerResource(
|
|
42572
43303
|
"compliance-freshness",
|
|
42573
43304
|
"kirjapro://compliance/_freshness",
|
|
42574
|
-
{
|
|
42575
|
-
|
|
42576
|
-
|
|
42577
|
-
|
|
42578
|
-
|
|
42579
|
-
|
|
42580
|
-
|
|
42581
|
-
topic: slug,
|
|
42582
|
-
|
|
42583
|
-
|
|
42584
|
-
valid_through: topic.dated ? meta3.valid_through : null,
|
|
42585
|
-
stale: topic.dated ? meta3.stale : false,
|
|
42586
|
-
days_until_expiry: topic.dated ? meta3.days_until_expiry : null
|
|
42587
|
-
}));
|
|
42588
|
-
return {
|
|
42589
|
-
contents: [{
|
|
42590
|
-
uri: uri.href,
|
|
42591
|
-
mimeType: "application/json",
|
|
42592
|
-
text: JSON.stringify({
|
|
42593
|
-
_meta: {
|
|
42594
|
-
sources_verified_at: meta3.sources_verified_at,
|
|
42595
|
-
published_at: meta3.published_at,
|
|
42596
|
-
rates_module: "src/compliance/rates.ts",
|
|
42597
|
-
source: FINNISH_RATES_2026.source
|
|
42598
|
-
},
|
|
42599
|
-
topics: entries
|
|
42600
|
-
})
|
|
42601
|
-
}]
|
|
42602
|
-
};
|
|
42603
|
-
}
|
|
43305
|
+
{ description: "Per-topic legal validity, review dates, overdue checks, sources and interpretation status. A review date is not a law\u2019s effective date; indefinite validity does not mean reviewed forever.", mimeType: "application/json" },
|
|
43306
|
+
async (uri) => ({ contents: [{
|
|
43307
|
+
uri: uri.href,
|
|
43308
|
+
mimeType: "application/json",
|
|
43309
|
+
text: JSON.stringify({
|
|
43310
|
+
_meta: { published_at: null, sources_verified_at: null, provenance: "Review dates and validity are scoped per topic below" },
|
|
43311
|
+
registry: "regulatory/registry.json (generated package-local subset)",
|
|
43312
|
+
topics: Object.keys(TOPICS).map((slug) => ({ topic: slug, uri: `kirjapro://compliance/${slug}`, ...complianceTopicMeta(slug) }))
|
|
43313
|
+
})
|
|
43314
|
+
}] })
|
|
42604
43315
|
);
|
|
42605
43316
|
}
|
|
42606
43317
|
|
|
@@ -42702,7 +43413,7 @@ Do not invent a "closest match" tool for these tasks. A confidently wrong action
|
|
|
42702
43413
|
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
43414
|
|
|
42704
43415
|
### 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
|
|
43416
|
+
\`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
43417
|
|
|
42707
43418
|
## Safety Rules
|
|
42708
43419
|
|
|
@@ -42768,7 +43479,7 @@ var INSTRUCTIONS_CORE_ADDENDUM = `
|
|
|
42768
43479
|
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
43480
|
|
|
42770
43481
|
- **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
|
|
43482
|
+
- **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
43483
|
- **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
43484
|
- **Transaction templates** \u2014 manual journal entries via \`kirjapro_create_transaction\`
|
|
42774
43485
|
- **Manual offer status changes** \u2014 the customer's email-link click sets status server-side; just wait for the user to confirm acceptance
|
|
@@ -42787,7 +43498,7 @@ var INSTRUCTIONS_FULL_ADDENDUM = `
|
|
|
42787
43498
|
|
|
42788
43499
|
### Booking Shortcuts
|
|
42789
43500
|
Faster than composing journal entries via \`kirjapro_create_transaction\`:
|
|
42790
|
-
- \`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\`
|
|
43501
|
+
- \`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\` is refused for it. Purchases use \`kirjapro_book_purchase\` with source \`vatRateBasis\` and a documented deduction share; reverse charge records the full buyer tax, including when deduction is zero. Import means customs-tax assessment only on the complete customs base, with no duplicate supplier purchase/payment
|
|
42791
43502
|
- \`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.
|
|
42792
43503
|
- **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\`.
|
|
42793
43504
|
- \`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)
|
|
@@ -42943,15 +43654,19 @@ async function main() {
|
|
|
42943
43654
|
});
|
|
42944
43655
|
registerResources(server);
|
|
42945
43656
|
registerPrompts(server);
|
|
42946
|
-
const
|
|
42947
|
-
|
|
42948
|
-
|
|
42949
|
-
|
|
42950
|
-
|
|
42951
|
-
|
|
42952
|
-
|
|
42953
|
-
|
|
42954
|
-
|
|
43657
|
+
for (const rule of ["vat", "payroll", "payrollEstimates"]) {
|
|
43658
|
+
const freshness = regulatoryFreshness(rule);
|
|
43659
|
+
if (freshness.expired || freshness.not_yet_effective) {
|
|
43660
|
+
console.error(
|
|
43661
|
+
`[kirjapro-mcp] WARNING: Bundled ${rule} rules do not cover today (valid ${freshness.valid_from} through ${freshness.valid_through ?? "no scheduled end"}). Verify the applicable event-date rule before booking; upgrade the package for current rules.`
|
|
43662
|
+
);
|
|
43663
|
+
} else if (freshness.review_overdue) {
|
|
43664
|
+
console.error(
|
|
43665
|
+
`[kirjapro-mcp] WARNING: Source review for ${rule} is overdue (${freshness.review_due}). Recheck the listed primary sources; an overdue review does not itself mean the law has changed.`
|
|
43666
|
+
);
|
|
43667
|
+
} else if (freshness.days_until_expiry !== null && freshness.days_until_expiry < 30) {
|
|
43668
|
+
console.error(`[kirjapro-mcp] NOTICE: Bundled ${rule} rules cover ${freshness.days_until_expiry} more days (${freshness.valid_through}).`);
|
|
43669
|
+
}
|
|
42955
43670
|
}
|
|
42956
43671
|
const transport = new StdioServerTransport();
|
|
42957
43672
|
transport.onclose = () => void shutdown();
|