@kirjapro/mcp-server 0.11.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/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.11.0";
35682
+ var VERSION = "0.12.0";
35683
35683
 
35684
35684
  // src/auth.ts
35685
35685
  var SUPABASE_PROJECT_URL = "https://tifewzsimmyoiepblhld.supabase.co";
@@ -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 total = 0;
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
- total += credit.total;
37444
+ total2 += credit.total;
37445
37445
  }
37446
37446
  offset += data.length;
37447
- if (offset === count) return { total: roundCents(total), error: null };
37447
+ if (offset === count) return { total: roundCents(total2), error: null };
37448
37448
  }
37449
37449
  }
37450
- function calculateInvoiceBalance(total, payments, creditedTotal) {
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(total - paidTotal - creditedTotal) };
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 < 1 || digits.length > 19) {
37461
- throw new Error("Base number must be 1-19 digits");
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 total2 = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? gross : roundCents(subtotal2 + vatAmount);
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: total2
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 total = roundCents(subtotal + vatTotal);
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: total,
37736
+ p_total: total2,
37735
37737
  p_notes: params.notes || "",
37736
37738
  p_lines: calculatedLines
37737
37739
  }
@@ -38695,9 +38697,9 @@ var LEGACY_REVERSE_CHARGE_WARNING = "Legacy reverse-charge invoice lines must be
38695
38697
  function alvReconciliationWarning(reconciliation) {
38696
38698
  if (!reconciliation || typeof reconciliation !== "object") return void 0;
38697
38699
  const r = reconciliation;
38698
- if (r.status !== "mismatch") return void 0;
38700
+ if (r.status !== "mismatch" && r.status !== "explained") return void 0;
38699
38701
  const amount = (value) => (Number(value) || 0).toFixed(2);
38700
- return `Ledger accounts 4210/2320 differ from the ALV report (output VAT difference ${amount(r.output_difference)} \u20AC, input VAT difference ${amount(r.input_difference)} \u20AC). Review reconciliation before filing.`;
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.`;
38701
38703
  }
38702
38704
  var BALANCE_SHEET_ROW_SHAPE = external_exports.object({
38703
38705
  account_number: external_exports.string(),
@@ -39694,9 +39696,9 @@ function validateFinnishIBAN(input2) {
39694
39696
  return { valid: true };
39695
39697
  }
39696
39698
  function generateReferenceNumber2(baseNumber) {
39697
- const digits = baseNumber.replace(/\D/g, "");
39698
- if (digits.length < 1 || digits.length > 19) {
39699
- return { reference: "", error: "Base number must be 1-19 digits." };
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." };
39700
39702
  }
39701
39703
  const weights = [7, 3, 1];
39702
39704
  let sum = 0;
@@ -39748,7 +39750,7 @@ function registerValidationTools(server2) {
39748
39750
  {
39749
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.",
39750
39752
  inputSchema: {
39751
- baseNumber: external_exports.string().describe('Base number (1-19 digits, e.g. invoice number "00042")')
39753
+ baseNumber: external_exports.string().describe('Base number (3-19 digits excluding leading zeros, e.g. "20260042")')
39752
39754
  },
39753
39755
  outputSchema: REFERENCE_OUTPUT_SHAPE,
39754
39756
  annotations: READ_ONLY_ANNOTATIONS
@@ -39797,6 +39799,392 @@ function registerFiscalYearTools(server2) {
39797
39799
  );
39798
39800
  }
39799
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
+
39800
40188
  // src/accounts.ts
39801
40189
  var ACCOUNTS = {
39802
40190
  // ── Assets (2xxx) ──
@@ -39842,7 +40230,7 @@ var ACCOUNTS = {
39842
40230
 
39843
40231
  // src/compliance/vat-guard.ts
39844
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.";
39845
- var NOT_VAT_REGISTERED_PURCHASE_REFUSAL = 'This company is not VAT-registered (ALV), so it deducts no VAT on purchases: book the purchase gross with vatRate "0". A service bought from a foreign seller (in another EU country or outside the EU) still makes the company liable for the self-assessed VAT: tell the user to add it as a non-deductible row on the Ostojen ALV page of the Kirjapro web app, to book the same VAT as a voucher debiting the expense account and crediting 4210 (ALV-velka), for example with kirjapro_create_transaction, and to check whether the company must register for VAT. Customs (Tulli) collects the import VAT of such a company. If the company is VAT-registered, update its settings in the Kirjapro web app first.';
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.";
39846
40234
  var REVERSE_CHARGE_CATEGORIES = [
39847
40235
  "eu_goods",
39848
40236
  "eu_services",
@@ -39852,8 +40240,8 @@ var REVERSE_CHARGE_CATEGORIES = [
39852
40240
  "scrap_metal"
39853
40241
  ];
39854
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).";
39855
- var REVERSE_CHARGE_RATE_REQUIRED = "A reverse-charge purchase needs the Finnish rate the buyer self-assesses (25.5, 13.5 or 10).";
39856
- var PURCHASE_TEMPLATE_NOT_VAT_REGISTERED_REFUSAL = 'This company is not VAT-registered (ALV), so a purchase template with VAT (2320) cannot be used. Book the purchase gross with kirjapro_book_purchase and vatRate "0".';
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".';
39857
40245
  function purchaseTemplateBatchRefusal(indexes) {
39858
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.`;
39859
40247
  }
@@ -39873,8 +40261,8 @@ async function readCompanyVat(supabase2, companyId2) {
39873
40261
  function splitVat(gross, vatRate) {
39874
40262
  if (vatRate === 0) return { net: roundCents(gross), vat: 0 };
39875
40263
  const net = roundCents(gross / (1 + vatRate / 100));
39876
- const vat = roundCents(gross - net);
39877
- return { net, vat };
40264
+ const vat2 = roundCents(gross - net);
40265
+ return { net, vat: vat2 };
39878
40266
  }
39879
40267
  function applyOverrides(entries, overrides) {
39880
40268
  if (!overrides) return entries;
@@ -39924,7 +40312,7 @@ async function validateAccountOverrides(overrides) {
39924
40312
  }
39925
40313
  function saleCashTemplate(revenueAccount, vatRate) {
39926
40314
  return (amount, opts) => {
39927
- const { net, vat } = splitVat(amount, vatRate);
40315
+ const { net, vat: vat2 } = splitVat(amount, vatRate);
39928
40316
  const entries = [
39929
40317
  {
39930
40318
  accountNumber: ACCOUNTS.KASSA,
@@ -39933,11 +40321,11 @@ function saleCashTemplate(revenueAccount, vatRate) {
39933
40321
  },
39934
40322
  { accountNumber: revenueAccount, debitAmount: 0, creditAmount: net }
39935
40323
  ];
39936
- if (vat > 0) {
40324
+ if (vat2 > 0) {
39937
40325
  entries.push({
39938
40326
  accountNumber: ACCOUNTS.ALV_VELKA,
39939
40327
  debitAmount: 0,
39940
- creditAmount: vat
40328
+ creditAmount: vat2
39941
40329
  });
39942
40330
  }
39943
40331
  return applyOverrides(entries, opts?.accountOverrides);
@@ -39945,7 +40333,7 @@ function saleCashTemplate(revenueAccount, vatRate) {
39945
40333
  }
39946
40334
  function saleBankTemplate(revenueAccount, vatRate) {
39947
40335
  return (amount, opts) => {
39948
- const { net, vat } = splitVat(amount, vatRate);
40336
+ const { net, vat: vat2 } = splitVat(amount, vatRate);
39949
40337
  const entries = [
39950
40338
  {
39951
40339
  accountNumber: ACCOUNTS.PANKKITILI,
@@ -39954,11 +40342,11 @@ function saleBankTemplate(revenueAccount, vatRate) {
39954
40342
  },
39955
40343
  { accountNumber: revenueAccount, debitAmount: 0, creditAmount: net }
39956
40344
  ];
39957
- if (vat > 0) {
40345
+ if (vat2 > 0) {
39958
40346
  entries.push({
39959
40347
  accountNumber: ACCOUNTS.ALV_VELKA,
39960
40348
  debitAmount: 0,
39961
- creditAmount: vat
40349
+ creditAmount: vat2
39962
40350
  });
39963
40351
  }
39964
40352
  return applyOverrides(entries, opts?.accountOverrides);
@@ -40023,7 +40411,7 @@ var TEMPLATES = [
40023
40411
  description: "Laskumyynti ALV 25,5% (myyntisaamiset)",
40024
40412
  vatRate: 25.5,
40025
40413
  expand: (amount, opts) => {
40026
- const { net, vat } = splitVat(amount, 25.5);
40414
+ const { net, vat: vat2 } = splitVat(amount, 25.5);
40027
40415
  return applyOverrides(
40028
40416
  [
40029
40417
  {
@@ -40032,7 +40420,7 @@ var TEMPLATES = [
40032
40420
  creditAmount: 0
40033
40421
  },
40034
40422
  { accountNumber: ACCOUNTS.MYYNTI_25_5, debitAmount: 0, creditAmount: net },
40035
- { accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount: vat }
40423
+ { accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount: vat2 }
40036
40424
  ],
40037
40425
  opts?.accountOverrides
40038
40426
  );
@@ -40045,11 +40433,11 @@ var TEMPLATES = [
40045
40433
  description: "Osto ALV 25,5% (pankkitililt\xE4)",
40046
40434
  vatRate: 25.5,
40047
40435
  expand: (amount, opts) => {
40048
- const { net, vat } = splitVat(amount, 25.5);
40436
+ const { net, vat: vat2 } = splitVat(amount, 25.5);
40049
40437
  return applyOverrides(
40050
40438
  [
40051
40439
  { accountNumber: ACCOUNTS.OSTOT, debitAmount: net, creditAmount: 0 },
40052
- { accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat, creditAmount: 0 },
40440
+ { accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat2, creditAmount: 0 },
40053
40441
  {
40054
40442
  accountNumber: ACCOUNTS.PANKKITILI,
40055
40443
  debitAmount: 0,
@@ -40066,11 +40454,11 @@ var TEMPLATES = [
40066
40454
  description: "Ulkopuolinen palvelu ALV 25,5%",
40067
40455
  vatRate: 25.5,
40068
40456
  expand: (amount, opts) => {
40069
- const { net, vat } = splitVat(amount, 25.5);
40457
+ const { net, vat: vat2 } = splitVat(amount, 25.5);
40070
40458
  return applyOverrides(
40071
40459
  [
40072
40460
  { accountNumber: ACCOUNTS.OSTOT_PALVELUT, debitAmount: net, creditAmount: 0 },
40073
- { accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat, creditAmount: 0 },
40461
+ { accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat2, creditAmount: 0 },
40074
40462
  {
40075
40463
  accountNumber: ACCOUNTS.PANKKITILI,
40076
40464
  debitAmount: 0,
@@ -40236,6 +40624,14 @@ async function preparePosting(params) {
40236
40624
  return { fiscalYearId, journalEntries: toJournalEntries(params.entries, accountResult) };
40237
40625
  }
40238
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
+ );
40239
40635
  const prepared = await preparePosting(params);
40240
40636
  if ("isError" in prepared) return prepared;
40241
40637
  const { data, error: error61 } = await getSupabase().rpc("book_purchase_with_vat", {
@@ -40314,6 +40710,7 @@ function registerTemplateTools(server2) {
40314
40710
  amount: external_exports.number().finite().positive().max(1e9).describe("Gross amount in EUR"),
40315
40711
  description: external_exports.string().min(1).max(500).describe("Transaction description"),
40316
40712
  documentNumber: external_exports.string().optional().describe("Document/receipt number"),
40713
+ vatRateBasis: PURCHASE_VAT_BASIS_SCHEMA.optional(),
40317
40714
  accountOverrides: external_exports.record(external_exports.string(), external_exports.string()).optional().describe('Account number swaps, e.g. {"2320": "2330"}'),
40318
40715
  idempotencyKey: IDEMPOTENCY_KEY_SCHEMA
40319
40716
  },
@@ -40350,10 +40747,11 @@ function registerTemplateTools(server2) {
40350
40747
  return toErrorContent(result.error);
40351
40748
  }
40352
40749
  const inputVat = template?.kind === "purchase" ? inputVatOf(result.entries) : 0;
40353
- if (inputVat !== 0 && alvRegistered === false) {
40750
+ const recordsVat = template?.kind === "purchase" && inputVatOf(template.expand(100, { accountOverrides: params.accountOverrides })) !== 0;
40751
+ if (recordsVat && alvRegistered === false) {
40354
40752
  return toErrorContent(PURCHASE_TEMPLATE_NOT_VAT_REGISTERED_REFUSAL);
40355
40753
  }
40356
- const createResult = inputVat !== 0 ? await resolveAndCreatePurchase({
40754
+ const createResult = recordsVat ? await resolveAndCreatePurchase({
40357
40755
  fiscalYearId: params.fiscalYearId,
40358
40756
  transactionDate: params.transactionDate,
40359
40757
  description: params.description,
@@ -40362,6 +40760,7 @@ function registerTemplateTools(server2) {
40362
40760
  vatRows: [
40363
40761
  {
40364
40762
  category: "domestic",
40763
+ vat_rate_basis: params.vatRateBasis,
40365
40764
  vat_rate: template?.vatRate ?? 0,
40366
40765
  tax_base: roundCents(
40367
40766
  result.entries.filter((e) => e.accountNumber !== ACCOUNTS.ALV_SAAMISET).reduce((sum, e) => sum + e.debitAmount, 0)
@@ -40419,10 +40818,7 @@ function registerTemplateTools(server2) {
40419
40818
  const vatPurchaseIndexes = params.transactions.map((tx, index) => {
40420
40819
  const template = TEMPLATE_MAP.get(tx.template);
40421
40820
  if (template?.kind !== "purchase") return -1;
40422
- const expanded = expandTemplate(tx.template, tx.amount, {
40423
- accountOverrides: tx.accountOverrides
40424
- });
40425
- return "entries" in expanded && inputVatOf(expanded.entries) !== 0 ? index : -1;
40821
+ return inputVatOf(template.expand(100, { accountOverrides: tx.accountOverrides })) !== 0 ? index : -1;
40426
40822
  }).filter((index) => index >= 0);
40427
40823
  if (vatPurchaseIndexes.length > 0) {
40428
40824
  return toErrorContent(purchaseTemplateBatchRefusal(vatPurchaseIndexes));
@@ -40616,49 +41012,98 @@ function registerTemplateTools(server2) {
40616
41012
  );
40617
41013
  }
40618
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
+
40619
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;
40620
41051
  var FINNISH_RATES_2026 = {
40621
41052
  year: 2026,
40622
- validFrom: "2026-01-01",
40623
- validUntil: "2026-12-31",
40624
- sourcesVerifiedAt: "2026-03-15",
40625
- source: "AVL 1501/1993, ETK 2026, Elo 2026, vero.fi 2026",
40626
- vat: {
40627
- standard: 25.5,
40628
- reduced: 13.5,
40629
- secondReduced: 10,
40630
- zero: 0
40631
- },
41053
+ validFrom: payrollPeriod.validFrom,
41054
+ validUntil: payrollPeriod.validUntil,
41055
+ vat: { standard: vat.general, reduced: vat.reduced, secondReduced: vat.newspapers, zero: vat.zero },
40632
41056
  social: {
40633
- tyelEmployee: 7.3,
40634
- tyelEmployer: 17.55,
40635
- tyelEmployerAverage: 17.1,
40636
- tyelKeskimaarainen: 24.4,
40637
- tyelPerusmaksu: 24.85,
40638
- sotu: 1.91,
40639
- ttvEmployer: 0.31,
40640
- ttvEmployerLarge: 1.23,
40641
- ttvEmployerLargeThreshold: 2509500,
40642
- ttvEmployee: 0.89,
40643
- tapaturmaAverage: 0.51,
40644
- ryhmahenki: 0.06
40645
- }
40646
- };
40647
- function freshnessMeta(rates = FINNISH_RATES_2026, today = /* @__PURE__ */ new Date()) {
40648
- const validUntil = /* @__PURE__ */ new Date(`${rates.validUntil}T23:59:59Z`);
40649
- const msPerDay = 864e5;
40650
- const daysUntilExpiry = Math.ceil(
40651
- (validUntil.getTime() - today.getTime()) / msPerDay
40652
- );
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;
40653
41076
  return {
40654
- published_at: rates.sourcesVerifiedAt,
40655
- valid_from: rates.validFrom,
40656
- valid_through: rates.validUntil,
40657
- sources_verified_at: rates.sourcesVerifiedAt,
40658
- stale: daysUntilExpiry < 0,
40659
- days_until_expiry: daysUntilExpiry
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
+ }))
40660
41096
  };
40661
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");
40662
41107
 
40663
41108
  // src/compliance/vat-settlement-date.ts
40664
41109
  var pad = (value) => String(value).padStart(2, "0");
@@ -40693,14 +41138,14 @@ function defaultSettlementDate(month, vatPeriod) {
40693
41138
  function expenseAccountWarning(account) {
40694
41139
  const first = account.trim().charAt(0);
40695
41140
  if (first && !"6789".includes(first)) {
40696
- return `expenseAccount "${account}" is outside the YSE 2017 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.`;
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.`;
40697
41142
  }
40698
41143
  return null;
40699
41144
  }
40700
41145
  function revenueAccountWarning(account) {
40701
41146
  const first = account.trim().charAt(0);
40702
41147
  if (first && first !== "5") {
40703
- return `revenueAccount "${account}" is outside the YSE 2017 revenue range (5xxx Liikevaihto). Verify it is really a revenue account \u2014 booking a sale to a non-revenue account misclassifies the income statement.`;
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.`;
40704
41149
  }
40705
41150
  return null;
40706
41151
  }
@@ -40715,21 +41160,24 @@ function registerBookingTools(server2) {
40715
41160
  server2.registerTool(
40716
41161
  "kirjapro_book_purchase",
40717
41162
  {
40718
- description: "Book a purchase (osto) with automatic VAT splitting. For a VAT-registered (ALV) company the VAT is recorded as a VAT fact, so it reaches field 307 (and the reverse-charge fields). Reverse charge (reverseCharge=true, with reverseChargeCategory) self-assesses the VAT for EU goods and services, services from outside the EU, import, construction and scrap metal. Entertainment (edustus, AVL 114\xA7) is booked gross. Statutorily non-deductible accounts (wages, insurance, interest, depreciation, taxes, representation) are refused with VAT: book them with vatRate 0. A company that is not VAT-registered books purchases gross with vatRate 0. Rate rules: food/restaurants 13.5%, insurance 0% (exempt), alcohol 25.5%.",
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%.",
40719
41164
  inputSchema: {
40720
41165
  amount: external_exports.number().finite().positive().max(1e9).describe(
40721
- "EUR amount. Standard purchase: GROSS (VAT-inclusive) \u2014 VAT is split out of this. Reverse charge (reverseCharge=true): NET foreign-invoice amount \u2014 the foreign supplier does not charge Finnish VAT, so the parameter must be net. VAT is self-assessed on top per AVL 9\xA7 producing equal debit to ALV-saamiset and credit to ALV-velka. Entertainment (edustus=true): GROSS (full amount to expense, no VAT deduction per AVL 114\xA7)."
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."
40722
41167
  ),
40723
41168
  description: external_exports.string().min(1).max(500).describe("Transaction description"),
40724
- date: ISO_DATE_SCHEMA.describe("Transaction date (YYYY-MM-DD)"),
40725
- 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."),
40726
41174
  expenseAccount: external_exports.string().describe('Expense account number (e.g. "8080")'),
40727
- reverseCharge: external_exports.boolean().default(false).describe(`Reverse charge (k\xE4\xE4nnetty verovelvollisuus): D expense + D ${ACCOUNTS.ALV_SAAMISET} ALV-saamiset + C ${ACCOUNTS.ALV_VELKA} ALV-velka + C bank, with a VAT fact of reverseChargeCategory`),
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`),
40728
41176
  reverseChargeCategory: external_exports.enum(REVERSE_CHARGE_CATEGORIES).optional().describe(
40729
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)"
40730
41178
  ),
40731
41179
  supplierName: external_exports.string().max(200).optional().describe("Supplier name, stored on the VAT fact"),
40732
- edustus: external_exports.boolean().default(false).describe("Entertainment expense (AVL 114\xA7): 0% VAT deduction, full gross to expense"),
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"),
40733
41181
  paymentAccount: external_exports.string().default(ACCOUNTS.PANKKITILI).describe(`Payment account (default "${ACCOUNTS.PANKKITILI}" Pankkitili)`),
40734
41182
  fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
40735
41183
  documentNumber: external_exports.string().optional().describe("Document/receipt number"),
@@ -40739,97 +41187,109 @@ function registerBookingTools(server2) {
40739
41187
  annotations: WRITE_ANNOTATIONS
40740
41188
  },
40741
41189
  async (params) => {
40742
- const rate = Number.parseFloat(params.vatRate);
40743
- const gross = roundCents(params.amount);
40744
- let entries;
40745
- let vatRows = [];
40746
- const companyVat = await readCompanyVat(getSupabase(), getCompanyId());
40747
- if ("isError" in companyVat) return companyVat;
40748
- if (!params.edustus && !companyVat.alvRegistered && (rate !== 0 || params.reverseCharge)) {
40749
- return toErrorContent(NOT_VAT_REGISTERED_PURCHASE_REFUSAL);
40750
- }
40751
- if (params.reverseCharge && !params.edustus) {
40752
- if (!params.reverseChargeCategory) return toErrorContent(REVERSE_CHARGE_CATEGORY_REQUIRED);
40753
- if (rate === 0) return toErrorContent(REVERSE_CHARGE_RATE_REQUIRED);
40754
- }
40755
- if (params.edustus) {
40756
- entries = [
40757
- { accountNumber: params.expenseAccount, debitAmount: gross, creditAmount: 0 },
40758
- { accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
40759
- ];
40760
- } else if (params.reverseCharge) {
40761
- const vat = roundCents(gross * rate / 100);
40762
- entries = [
40763
- { accountNumber: params.expenseAccount, debitAmount: gross, creditAmount: 0 },
40764
- { accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat, creditAmount: 0 },
40765
- // ALV-saamiset (input)
40766
- { accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount: vat },
40767
- // ALV-velka (output)
40768
- { accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
40769
- ];
40770
- vatRows = [
40771
- {
40772
- category: params.reverseChargeCategory ?? "eu_services",
40773
- vat_rate: rate,
40774
- tax_base: gross,
40775
- vat_amount: vat
40776
- }
40777
- ];
40778
- } else if (rate === 0) {
40779
- entries = [
40780
- { accountNumber: params.expenseAccount, debitAmount: gross, creditAmount: 0 },
40781
- { accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
40782
- ];
40783
- } else {
40784
- const { net, vat } = splitVat(gross, rate);
40785
- entries = [
40786
- { accountNumber: params.expenseAccount, debitAmount: net, creditAmount: 0 },
40787
- { accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat, creditAmount: 0 },
40788
- { accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
40789
- ];
40790
- vatRows = [{ category: "domestic", vat_rate: rate, tax_base: net, vat_amount: vat }];
40791
- }
40792
- const purchaseAccountWarning = expenseAccountWarning(params.expenseAccount);
40793
- if (params.dryRun) {
40794
- return toDryRunResponse("kirjapro_book_purchase", {
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", {
40795
41255
  action: "book_purchase",
40796
- gross,
41256
+ gross: amount,
40797
41257
  vat_rate: params.vatRate,
40798
41258
  description: params.description,
40799
41259
  date: params.date,
40800
41260
  entries,
40801
- ...vatRows.length > 0 && { vat_rows: vatRows },
40802
- ...purchaseAccountWarning && { account_warning: purchaseAccountWarning }
41261
+ calculation,
41262
+ ...params.reverseCharge ? { reverse_charge: calculation } : {},
41263
+ ...vatRows.length ? { vat_rows: vatRows } : {},
41264
+ ...purchaseAccountWarning ? { account_warning: purchaseAccountWarning } : {}
40803
41265
  });
40804
- }
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();
40805
41286
  return withIdempotency(
40806
41287
  getSupabase(),
40807
41288
  getCompanyId(),
40808
41289
  "kirjapro_book_purchase",
40809
41290
  params.idempotencyKey,
40810
41291
  params,
40811
- async () => {
40812
- const posting = {
40813
- fiscalYearId: params.fiscalYearId,
40814
- transactionDate: params.date,
40815
- description: params.description,
40816
- documentNumber: params.documentNumber,
40817
- entries
40818
- };
40819
- const result = vatRows.length > 0 ? await resolveAndCreatePurchase({
40820
- ...posting,
40821
- vatRows,
40822
- supplierName: params.supplierName
40823
- }) : await resolveAndCreate(posting);
40824
- if ("isError" in result) return result;
40825
- return toResult({
40826
- message: "Purchase booked",
40827
- transaction_id: result.id,
40828
- entries,
40829
- ..."vatEntryIds" in result && { vat_entry_ids: result.vatEntryIds },
40830
- ...purchaseAccountWarning && { warning: purchaseAccountWarning }
40831
- });
40832
- }
41292
+ execute
40833
41293
  );
40834
41294
  }
40835
41295
  );
@@ -40983,21 +41443,21 @@ function registerBookingTools(server2) {
40983
41443
  { accountNumber: params.revenueAccount, debitAmount: 0, creditAmount: gross }
40984
41444
  ];
40985
41445
  } else {
40986
- const { net: net2, vat: vat2 } = splitVat(gross, rate);
41446
+ const { net: net2, vat: vat3 } = splitVat(gross, rate);
40987
41447
  entries = [
40988
41448
  { accountNumber: debitAccount, debitAmount: gross, creditAmount: 0 },
40989
41449
  { accountNumber: params.revenueAccount, debitAmount: 0, creditAmount: net2 },
40990
- { accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount: vat2 }
41450
+ { accountNumber: ACCOUNTS.ALV_VELKA, debitAmount: 0, creditAmount: vat3 }
40991
41451
  ];
40992
41452
  }
40993
- 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 };
40994
41454
  const saleAccountWarning = revenueAccountWarning(params.revenueAccount);
40995
41455
  if (params.dryRun) {
40996
41456
  return toDryRunResponse("kirjapro_book_sale", {
40997
41457
  action: "book_sale",
40998
41458
  gross,
40999
41459
  net,
41000
- vat,
41460
+ vat: vat2,
41001
41461
  vat_rate: `${params.vatRate} %`,
41002
41462
  payment_type: params.paymentType,
41003
41463
  debit_account: debitAccount,
@@ -41027,7 +41487,7 @@ function registerBookingTools(server2) {
41027
41487
  transaction_id: result.id,
41028
41488
  gross,
41029
41489
  net,
41030
- vat,
41490
+ vat: vat2,
41031
41491
  vat_rate: `${params.vatRate} %`,
41032
41492
  payment_type: params.paymentType,
41033
41493
  debit_account: debitAccount,
@@ -41162,6 +41622,7 @@ function registerOfferTools(server2) {
41162
41622
  "reverse_scrap",
41163
41623
  "reverse_eu_service",
41164
41624
  "export_outside_eu",
41625
+ "service_outside_eu",
41165
41626
  "intra_eu_goods",
41166
41627
  "exempt_healthcare",
41167
41628
  "exempt_social_welfare",
@@ -41169,7 +41630,7 @@ function registerOfferTools(server2) {
41169
41630
  "exempt_financial",
41170
41631
  "exempt_insurance",
41171
41632
  "exempt_other"
41172
- ]).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.")
41173
41634
  })).min(1).max(MAX_DOCUMENT_LINES).describe(`Line items (max ${MAX_DOCUMENT_LINES})`),
41174
41635
  idempotencyKey: IDEMPOTENCY_KEY_SCHEMA
41175
41636
  },
@@ -41238,7 +41699,7 @@ function registerOfferTools(server2) {
41238
41699
  const enteredTotal = roundCents(line.quantity * line.unitPrice);
41239
41700
  const vatAmount = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? roundCents(enteredTotal * vatRate / (100 + vatRate)) : roundCents(enteredTotal * (vatRate / 100));
41240
41701
  const subtotal2 = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? roundCents(enteredTotal - vatAmount) : enteredTotal;
41241
- const total2 = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? enteredTotal : roundCents(subtotal2 + vatAmount);
41702
+ const total3 = params.priceEntryMode === "vat_inclusive" && vatRate > 0 ? enteredTotal : roundCents(subtotal2 + vatAmount);
41242
41703
  return {
41243
41704
  line_number: i + 1,
41244
41705
  description: line.description,
@@ -41256,12 +41717,12 @@ function registerOfferTools(server2) {
41256
41717
  eu_goods_transport_confirmed: params.euGoodsTransportConfirmed,
41257
41718
  subtotal: subtotal2,
41258
41719
  vat_amount: vatAmount,
41259
- total: total2
41720
+ total: total3
41260
41721
  };
41261
41722
  });
41262
41723
  const subtotal = roundCents(calculatedLines.reduce((s, l) => s + l.subtotal, 0));
41263
41724
  const vatTotal = roundCents(calculatedLines.reduce((s, l) => s + l.vat_amount, 0));
41264
- const total = roundCents(subtotal + vatTotal);
41725
+ const total2 = roundCents(subtotal + vatTotal);
41265
41726
  const { data: offerId, error: rpcError } = await supabase2.rpc("create_offer_with_lines", {
41266
41727
  p_company_id: companyId2,
41267
41728
  p_customer_id: params.customerId || null,
@@ -41275,7 +41736,7 @@ function registerOfferTools(server2) {
41275
41736
  p_notes: params.notes || null,
41276
41737
  p_subtotal: subtotal,
41277
41738
  p_vat_total: vatTotal,
41278
- p_total: total,
41739
+ p_total: total2,
41279
41740
  p_lines: calculatedLines
41280
41741
  });
41281
41742
  if (rpcError) {
@@ -41289,7 +41750,7 @@ function registerOfferTools(server2) {
41289
41750
  title: params.title,
41290
41751
  subtotal,
41291
41752
  vat_total: vatTotal,
41292
- total,
41753
+ total: total2,
41293
41754
  lines: calculatedLines
41294
41755
  });
41295
41756
  }
@@ -42351,7 +42812,7 @@ var VAT_RATE_METADATA = [
42351
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" },
42352
42813
  { rate: FINNISH_RATES_2026.vat.secondReduced, label: "Lehdet ja aikakauslehdet", description: "Newspapers and magazines (print and digital) only", basis: "AVL 85a\xA7" },
42353
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" },
42354
- { 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). NO input VAT deduction", basis: "AVL 34-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" }
42355
42816
  ];
42356
42817
  function registerSchemaResources(server2) {
42357
42818
  server2.registerResource(
@@ -42373,7 +42834,7 @@ function registerSchemaResources(server2) {
42373
42834
  "vat-rates",
42374
42835
  "kirjapro://vat-rates",
42375
42836
  {
42376
- 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 (healthcare/education/finance/insurance \u2014 NO input VAT deduction).",
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).",
42377
42838
  mimeType: "application/json"
42378
42839
  },
42379
42840
  async (uri) => ({
@@ -42382,7 +42843,7 @@ function registerSchemaResources(server2) {
42382
42843
  mimeType: "application/json",
42383
42844
  text: JSON.stringify({
42384
42845
  rates: VAT_RATE_METADATA,
42385
- _meta: freshnessMeta(FINNISH_RATES_2026)
42846
+ _meta: regulatoryFreshness("vat")
42386
42847
  })
42387
42848
  }]
42388
42849
  })
@@ -42463,6 +42924,9 @@ function registerCompanyResources(server2) {
42463
42924
  }
42464
42925
 
42465
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);
42466
42930
  var VAT_COMPLIANCE = {
42467
42931
  rates: {
42468
42932
  current_year: FINNISH_RATES_2026.year,
@@ -42472,40 +42936,40 @@ var VAT_COMPLIANCE = {
42472
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" },
42473
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)" }
42474
42938
  },
42475
- rate_history: [
42476
- { period: "1.1.2026 \u2192", standard: FINNISH_RATES_2026.vat.standard, reduced: FINNISH_RATES_2026.vat.reduced, second_reduced: FINNISH_RATES_2026.vat.secondReduced, note: "Reduced rate unified from 14% \u2192 13.5%" },
42477
- { period: "1.9.2024 \u2192 31.12.2025", standard: 25.5, reduced: 14, second_reduced: 10, note: "Standard raised from 24% \u2192 25.5%" },
42478
- { period: "1.1.2013 \u2192 31.8.2024", standard: 24, reduced: 14, second_reduced: 10 }
42479
- ],
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.",
42480
42942
  deduction_rules: [
42481
- { type: "Edustuskulut (entertainment)", vat_deduction: "0%", income_tax: "50%", basis: "AVL 114\xA7", booking: "Full gross to expense, NO ALV-saamiset entry" },
42482
- { type: "Neuvottelukulut (meetings)", vat_deduction: "100%", income_tax: "100%", basis: "AVL 102\xA7", booking: "Normal VAT split" },
42483
- { type: "Henkil\xF6st\xF6kulut (staff events)", vat_deduction: "100%", income_tax: "100%", basis: "AVL 102\xA7", booking: "Normal VAT split" },
42484
- { type: "Insurance premiums", vat_deduction: "0% (exempt)", income_tax: "100%", basis: "AVL 44\xA7", booking: "Full gross to expense, no VAT (vakuutusmaksuvero is separate)" },
42485
- { type: "Healthcare (staff)", vat_deduction: "0% (exempt)", income_tax: "100%", basis: "AVL 34-36\xA7" },
42486
- { type: "Financial services", vat_deduction: "0% (exempt)", income_tax: "100%", basis: "AVL 41-42\xA7" },
42487
- { type: "Henkil\xF6auto (passenger car M1)", vat_deduction: "100% if exclusive business use, 0% if ANY private use (incl. commuting)", income_tax: "Proportional", basis: "AVL 114\xA7 1 mom 4 kohta", booking: "Ajop\xE4iv\xE4kirja (mileage logbook) required as proof" },
42488
- { type: "Pakettiauto/kuorma-auto (van N1+)", vat_deduction: "Proportional to business use", income_tax: "Proportional", basis: "AVL 102\xA7, 117\xA7" },
42489
- { type: "Markkinointitilaisuudet (marketing events)", vat_deduction: "100%", income_tax: "100%", basis: "AVL 102\xA7, KHO 2013:63", booking: "Product launches, trade shows \u2014 NOT client entertainment" }
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" }
42490
42952
  ],
42491
42953
  reverse_charge: {
42492
- rule: "Buyer self-assesses both output and input VAT. Applies to: (1) foreign B2B providers not charging Finnish VAT, (2) domestic construction services, (3) metal scrap, (4) emissions allowances, (5) investment gold",
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.",
42493
42955
  basis: "AVL 8a-8d\xA7, 9\xA7, 65\xA7; EU VAT Directive Art. 196, Art. 199",
42494
42956
  scenarios: [
42495
- { type: "Foreign B2B", basis: "AVL 9\xA7", note: "Most common: EU and non-EU service providers" },
42496
- { type: "Construction services", basis: "AVL 8c\xA7", note: 'Since 1.4.2011. Buyer must itself sell/rent construction services "not just occasionally". Regular company commissioning renovation: NO reverse charge' },
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' },
42497
42959
  { type: "Metal scrap and waste", basis: "AVL 8d\xA7", note: "Since 1.1.2015. Between VAT-registered entities" },
42498
42960
  { type: "Emissions allowances", basis: "AVL 8b\xA7", note: "Greenhouse gas emission allowances" },
42499
42961
  { type: "Investment gold", basis: "AVL 8a\xA7", note: "Taxable investment gold, gold materials, gold semi-finished products" }
42500
42962
  ],
42501
- booking: "4 entries: D expense + D ALV-saamiset + C bank + C ALV-velka (k\xE4\xE4nnetty)",
42502
- note: "Net VAT effect is zero but BOTH must be reported on ALV-ilmoitus. Invoice must NOT show VAT rate or VAT amount",
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 },
42503
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."
42504
42968
  },
42505
42969
  common_mistakes: [
42506
- "Food/coffee at 25.5% \u2014 should be 13.5% (AVL 85\xA7)",
42507
- "Train ticket at 10% \u2014 should be 13.5% (transport = reduced rate)",
42508
- "Using 14% after 1.1.2026 \u2014 rate changed to 13.5%",
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",
42509
42973
  "Restaurant alcohol at 13.5% \u2014 alcohol is 25.5% even at restaurants",
42510
42974
  "Newspaper/magazine at 13.5% \u2014 periodicals are 10%",
42511
42975
  "Splitting VAT from insurance premium \u2014 insurance is VAT-exempt, not zero-rated",
@@ -42519,40 +42983,44 @@ var INVOICE_REQUIREMENTS = {
42519
42983
  mandatory_fields: [
42520
42984
  { kohta: 1, field: "Laskun antamisp\xE4iv\xE4", english: "Invoice date", status: "implemented", relevance: "always" },
42521
42985
  { kohta: 2, field: "Juokseva tunniste", english: "Sequential invoice number", status: "implemented", relevance: "always", note: "Gaps must be detectable per Vero requirement" },
42522
- { kohta: 3, field: "Myyj\xE4n ALV-tunniste", english: "Seller VAT ID (FI + Y-tunnus)", status: "implemented", relevance: "always", note: "Y-tunnus always required; FI-format strictly mandatory for EU cross-border" },
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" },
42523
42987
  { kohta: 4, field: "Ostajan ALV-tunniste", english: "Buyer VAT ID", status: "implemented", relevance: "conditional", note: "Required for reverse charge and EU B2B" },
42524
42988
  { kohta: 5, field: "Myyj\xE4n ja ostajan nimi ja osoite", english: "Names and addresses", status: "implemented", relevance: "always" },
42525
42989
  { kohta: 6, field: "Tavaroiden/palvelujen kuvaus ja m\xE4\xE4r\xE4", english: "Description and quantity", status: "implemented", relevance: "always" },
42526
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" },
42527
42991
  { kohta: 8, field: "Veron peruste, yksikk\xF6hinta, alennukset", english: "Tax base, unit price, discounts", status: "implemented", relevance: "always" },
42528
- { kohta: 9, field: "Verokanta", english: "VAT rate", status: "implemented", relevance: "always" },
42529
- { kohta: 10, field: "Suoritettavan veron m\xE4\xE4r\xE4", english: "VAT amount", status: "implemented", relevance: "always" },
42530
- { kohta: 11, field: "Merkint\xE4 verottomuudesta", english: "Tax exemption notation", status: "gap", relevance: "conditional", note: 'Simple "Veroton myynti" suffices. Citing AVL section is recommended but not mandatory (Vero.fi 5.1.11)' },
42531
- { kohta: 12, field: "Merkint\xE4 k\xE4\xE4nnetyst\xE4 verovelvollisuudesta", english: "Reverse charge notation", status: "gap", relevance: "conditional", note: '"K\xE4\xE4nnetty verovelvollisuus" phrase is mandatory. Legal basis citation is recommended, not required' },
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' },
42532
42996
  { kohta: 13, field: "Merkint\xE4 itselaskutuksesta", english: "Self-billing notation", status: "n/a", relevance: "situational", note: "When buyer issues invoice on seller behalf" },
42533
42997
  { kohta: 14, field: "Uuden kuljetusv\xE4lineen tiedot", english: "New means of transport details", status: "n/a", relevance: "situational" },
42534
42998
  { kohta: 15, field: "Voittomarginaalij\xE4rjestelm\xE4", english: "Margin scheme (used goods)", status: "n/a", relevance: "situational" },
42535
42999
  { kohta: 16, field: "Voittomarginaalij\xE4rjestelm\xE4 \u2014 matkatoimistot", english: "Margin scheme (travel agents)", status: "n/a", relevance: "situational" },
42536
43000
  { kohta: 17, field: "Verollisen sijoituskullan myynti", english: "Taxable investment gold", status: "n/a", relevance: "situational" },
42537
- { kohta: 18, field: "Viittaus alkuper\xE4iseen laskuun", english: "Credit note reference (muutoslasku)", status: "not_implemented", relevance: "conditional", note: "Only mandatory requirement: unambiguous reference to original invoice" }
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)" }
42538
43002
  ],
42539
43003
  simplified_invoice: {
42540
43004
  threshold: 400,
42541
- note: "Invoices \u2264 400 \u20AC may use simplified format (AVL 209 f \xA7). Also: retail to consumers, restaurant/catering, passenger transport \u2014 regardless of amount"
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"]
42542
43007
  },
42543
43008
  muutoslasku: {
42544
- status: "not_implemented",
42545
- mandatory: "Unambiguous reference to original invoice (number or period)",
42546
- recommended: "Hyvityslasku label, reason for credit, same numbering sequence \u2014 common practice, not legally mandated"
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."
42547
43013
  }
42548
43014
  };
42549
43015
  var FILING_DEADLINES = {
42550
43016
  vat_filing: {
42551
43017
  thresholds: [
42552
- { revenue: "< 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)" },
42553
- { revenue: "30,000\u2013100,000 \u20AC", periods: ["monthly", "quarterly"], note: "Quarterly: 12th of 2nd month after quarter" },
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" },
42554
43020
  { revenue: "> 100,000 \u20AC", periods: ["monthly"], note: "Monthly: 12th of 2nd month after reporting month" }
42555
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.",
42556
43024
  monthly_example: "January 2026 VAT \u2192 deadline 12.3.2026",
42557
43025
  quarterly_deadlines: {
42558
43026
  Q1: "12.5",
@@ -42562,24 +43030,24 @@ var FILING_DEADLINES = {
42562
43030
  }
42563
43031
  },
42564
43032
  tyel: {
42565
- monthly_report: "5th of following month \u2192 pension company",
42566
- payment: "20th of following month \u2192 pension company"
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"
42567
43035
  },
42568
43036
  tulorekisteri: {
42569
43037
  palkkatietoilmoitus: "5 calendar days from payment date \u2192 Tulorekisteri",
42570
43038
  tyonantajan_erillisilmoitus: "5th of following month \u2192 Tulorekisteri (sotu totals)"
42571
43039
  },
42572
- sairausvakuutusmaksu_payment: "12th of following month \u2192 Vero (OmaVero, oma-aloitteinen)",
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",
42573
43041
  tilinpaatos: "4 months from fiscal year end (KPL 3:6)",
42574
43042
  prh_filing: "8 months from fiscal year end (OYL 8:10)",
42575
43043
  pienyrittaja: {
42576
43044
  threshold: 2e4,
42577
- note: "Revenue under 20,000 \u20AC/year: no VAT registration obligation (from 1.1.2025, was 15,000 \u20AC)",
42578
- alarajahuojennus: "REMOVED from 1.1.2025 \u2014 hard threshold, no progressive relief"
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"
42579
43047
  },
42580
43048
  maksuperusteinen_alv: {
42581
43049
  threshold: 5e5,
42582
- note: "Turnover up to 500,000 \u20AC per calendar year: domestic sales VAT may be reported when payment is received and purchase VAT deducted when paid (cash basis), consistently for all domestic sales and purchases; imports, exports, intra-EU sales and acquisitions and cross-border reverse-charge services stay on the accrual basis. Kirjapro reports on the accrual basis only.",
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.",
42583
43051
  application: "No application: the business applies the cash basis in its bookkeeping. Since 2025 the VAT return has no cash-basis field.",
42584
43052
  source: "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/pienyrityksen-maksuperusteinen-alv/",
42585
43053
  sources_verified_at: "2026-10-02"
@@ -42624,18 +43092,17 @@ var FILING_DEADLINES = {
42624
43092
  var SOCIAL_INSURANCE_2026 = {
42625
43093
  year: FINNISH_RATES_2026.year,
42626
43094
  sources: [
42627
- "https://www.etk.fi/ajankohtaista/tyoelakemaksut-vuonna-2026/",
42628
- "https://www.elo.fi/fi-fi/tyonantaja/tyel-vakuuttaminen/tyel-maksu/sosiaalivakuutusmaksut-2026",
42629
- "https://tyel-vakuutus.fi/tyonantajan-sivukulut/"
43095
+ ...regulatoryFreshness("payroll").sources.map((source) => source.url),
43096
+ ...regulatoryFreshness("payrollEstimates").sources.map((source) => source.url)
42630
43097
  ],
42631
43098
  tyel: {
42632
43099
  keskimaarainen: FINNISH_RATES_2026.social.tyelKeskimaarainen,
42633
43100
  perusmaksu: FINNISH_RATES_2026.social.tyelPerusmaksu,
42634
- tilapaistyonantaja: 25.85,
43101
+ tilapaistyonantaja: FINNISH_RATES_2026.social.tyelTemporary,
42635
43102
  employee: FINNISH_RATES_2026.social.tyelEmployee,
42636
43103
  employer_average: FINNISH_RATES_2026.social.tyelEmployerAverage,
42637
43104
  employer_perusmaksu: FINNISH_RATES_2026.social.tyelEmployer,
42638
- note: "Perusmaksu 24.85% is what pension companies bill before company-specific adjustments. Keskim\xE4\xE4r\xE4inen 24.40% is the actual average. Age differentiation eliminated in 2026 (ETK)"
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"
42639
43106
  },
42640
43107
  sotu: {
42641
43108
  rate: FINNISH_RATES_2026.social.sotu,
@@ -42647,20 +43114,19 @@ var SOCIAL_INSURANCE_2026 = {
42647
43114
  employer_large: FINNISH_RATES_2026.social.ttvEmployerLarge,
42648
43115
  employer_large_threshold: FINNISH_RATES_2026.social.ttvEmployerLargeThreshold,
42649
43116
  employee: FINNISH_RATES_2026.social.ttvEmployee,
42650
- note: "Ty\xF6tt\xF6myysvakuutus. Employer rate 0.31% for payroll \u2264 2,509,500 \u20AC; 1.23% above. Employee up from 0.59% in 2025"
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."
42651
43118
  },
42652
43119
  tapaturma: {
42653
43120
  average: FINNISH_RATES_2026.social.tapaturmaAverage,
42654
- range: "0.05\u20135.00%",
42655
43121
  payer: "employer",
42656
43122
  note: "Company-specific rate based on industry and claims history"
42657
43123
  },
42658
43124
  ryhmahenkivakuutus: {
42659
43125
  rate: FINNISH_RATES_2026.social.ryhmahenki,
42660
43126
  payer: "employer",
42661
- note: "Standardized via collective agreement"
43127
+ note: "Planning estimate only; obligation and actual premium depend on the applicable collective agreement and insurance contract"
42662
43128
  },
42663
- total_employer_side_costs: "~20% of gross salary (incl. TyEL employer share)",
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",
42664
43130
  // Account numbers are built from the canonical chart (src/accounts.ts) so they
42665
43131
  // can never drift from the YSE 2017 numbering the agent actually books against.
42666
43132
  // DO NOT hardcode literals here — a wrong-but-existing number (e.g. 2320, which
@@ -42680,124 +43146,172 @@ var SOCIAL_INSURANCE_2026 = {
42680
43146
  var BOOKKEEPING_RULES = {
42681
43147
  legal_basis: "Kirjanpitolaki 1336/1997",
42682
43148
  core_principles: [
42683
- { rule: "Double-entry (kahdenkertainen kirjanpito)", basis: "KPL 2:1", note: "Every entry: total debits = total credits" },
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" },
42684
43150
  { rule: "Voucher principle (tositeperiaate)", basis: "KPL 2:5", note: "Every entry must be based on a numbered voucher" },
42685
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" },
42686
43152
  { rule: "Sequential numbering", basis: "KPL 2:5", note: "Gap detection must be possible from the numbering sequence" },
42687
- { rule: "Closed period immutability", basis: "KPL 2:7", note: "Corrections to closed periods must use new entries in current period" }
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" }
42688
43154
  ],
42689
43155
  retention: [
42690
- { type: "Accounting records (kirjanpitokirjat)", years: 10, basis: "KPL 2:10 1 mom" },
42691
- { type: "Financial statements (tilinp\xE4\xE4t\xF6s)", years: 10, basis: "KPL 3:9" },
42692
- { type: "Vouchers, invoices (tositteet)", years: 6, basis: "KPL 2:10 2 mom" },
42693
- { type: "E-invoice XML originals", years: 6, basis: "KPL 2:7" }
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" }
42694
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.",
42695
43162
  fiscal_year: {
42696
- normal_duration: "12 months",
42697
- max_duration: "18 months (first/changed period only)",
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)",
42698
43165
  tilinpaatos_deadline: "4 months from fiscal year end",
42699
43166
  prh_deadline: "8 months from fiscal year end (Oy, Oyj)"
42700
43167
  },
42701
43168
  company_sizes: [
42702
- { class: "Mikroyritys", revenue: "< 900K", balance: "< 450K", employees: "< 10", tilinpaatos: "Simplified (PMA)" },
42703
- { class: "Pienyritys", revenue: "< 15M", balance: "< 7.5M", employees: "< 50", tilinpaatos: "Standard (PMA)" },
42704
- { class: "Keskisuuri", revenue: "< 50M", balance: "< 25M", employees: "< 250", tilinpaatos: "Full (KPA) + toimintakertomus for Oy/Osk" },
42705
- { class: "Suuryritys", revenue: "\u2265 50M", balance: "\u2265 25M", employees: "\u2265 250", tilinpaatos: "Full + toimintakertomus + rahoituslaskelma" }
42706
- ]
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
+ }]
42707
43235
  };
42708
43236
  var TOPICS = {
42709
43237
  vat: {
42710
- description: "Finnish VAT compliance: rates (25.5/13.5/10/0%/exempt), rate history, deduction rules (edustus/neuvottelu/insurance), reverse charge, and common mistakes. CRITICAL: 0% (zero-rated, input VAT deductible) \u2260 exempt (no input VAT deduction). Legal basis: AVL 1501/1993.",
42711
- dated: true,
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"],
42712
43240
  body: VAT_COMPLIANCE
42713
43241
  },
42714
43242
  invoicing: {
42715
- description: "Invoice requirements per AVL 209 e \xA7: 18 mandatory markings (kohdat 1-18), muutoslasku/hyvityslasku rules, simplified invoice (\u2264400\u20AC + retail/restaurant). Shows implementation status. Source: vero.fi",
42716
- dated: false,
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: [],
42717
43245
  body: INVOICE_REQUIREMENTS
42718
43246
  },
42719
43247
  deadlines: {
42720
- description: "Finnish tax filing deadlines: VAT verokausi thresholds (<30K annual, <100K quarterly, >100K monthly), TyEL reporting, tilinp\xE4\xE4t\xF6s deadlines, pienyritt\xE4j\xE4 20K\u20AC threshold, maksuperusteinen ALV.",
42721
- dated: true,
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: [],
42722
43250
  body: FILING_DEADLINES
42723
43251
  },
42724
43252
  "social-insurance": {
42725
- description: "2026 Finnish social insurance rates: TyEL (perusmaksu 24.85%, keskim\xE4\xE4r\xE4inen 24.40%, employee 7.30%), sotu (1.91%), TTV (0.31%/0.89%), tapaturma (~0.51%), ryhm\xE4henki (0.06%). Sources: ETK, Elo, tyel-vakuutus.fi",
42726
- dated: true,
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"],
42727
43255
  body: SOCIAL_INSURANCE_2026
42728
43256
  },
42729
43257
  bookkeeping: {
42730
- description: "KPL (Kirjanpitolaki) requirements: double-entry, audit trail, voucher principle, retention periods (10/6 years), fiscal year rules (max 18 months), company size classification (mikro/pien/keski/suuri).",
42731
- dated: false,
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"],
42732
43260
  body: BOOKKEEPING_RULES
42733
43261
  }
42734
43262
  };
42735
- function wrapWithMeta(body, dated) {
42736
- const meta3 = freshnessMeta();
42737
- return JSON.stringify({
42738
- _meta: {
42739
- published_at: meta3.published_at,
42740
- valid_from: meta3.valid_from,
42741
- valid_through: dated ? meta3.valid_through : null,
42742
- sources_verified_at: meta3.sources_verified_at,
42743
- stale: dated ? meta3.stale : false,
42744
- days_until_expiry: dated ? meta3.days_until_expiry : null,
42745
- dated_rates: dated
42746
- },
42747
- ...body
42748
- });
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 };
42749
43292
  }
42750
43293
  function registerComplianceResources(server2) {
42751
- for (const [slug, topic] of Object.entries(TOPICS)) {
43294
+ for (const slug of Object.keys(TOPICS)) {
42752
43295
  server2.registerResource(
42753
43296
  `compliance-${slug}`,
42754
43297
  `kirjapro://compliance/${slug}`,
42755
- {
42756
- description: topic.description,
42757
- mimeType: "application/json"
42758
- },
42759
- async (uri) => ({
42760
- contents: [{
42761
- uri: uri.href,
42762
- mimeType: "application/json",
42763
- text: wrapWithMeta(topic.body, topic.dated)
42764
- }]
42765
- })
43298
+ { description: TOPICS[slug].description, mimeType: "application/json" },
43299
+ async (uri) => ({ contents: [{ uri: uri.href, mimeType: "application/json", text: JSON.stringify(complianceResource(slug)) }] })
42766
43300
  );
42767
43301
  }
42768
43302
  server2.registerResource(
42769
43303
  "compliance-freshness",
42770
43304
  "kirjapro://compliance/_freshness",
42771
- {
42772
- description: "Freshness index for every dated compliance chunk. Read this at session start. Returns valid_through/stale/days_until_expiry for each topic so the agent can detect expired rates before relying on them.",
42773
- mimeType: "application/json"
42774
- },
42775
- async (uri) => {
42776
- const meta3 = freshnessMeta();
42777
- const entries = Object.entries(TOPICS).map(([slug, topic]) => ({
42778
- topic: slug,
42779
- uri: `kirjapro://compliance/${slug}`,
42780
- dated_rates: topic.dated,
42781
- valid_through: topic.dated ? meta3.valid_through : null,
42782
- stale: topic.dated ? meta3.stale : false,
42783
- days_until_expiry: topic.dated ? meta3.days_until_expiry : null
42784
- }));
42785
- return {
42786
- contents: [{
42787
- uri: uri.href,
42788
- mimeType: "application/json",
42789
- text: JSON.stringify({
42790
- _meta: {
42791
- sources_verified_at: meta3.sources_verified_at,
42792
- published_at: meta3.published_at,
42793
- rates_module: "src/compliance/rates.ts",
42794
- source: FINNISH_RATES_2026.source
42795
- },
42796
- topics: entries
42797
- })
42798
- }]
42799
- };
42800
- }
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
+ }] })
42801
43315
  );
42802
43316
  }
42803
43317
 
@@ -42984,7 +43498,7 @@ var INSTRUCTIONS_FULL_ADDENDUM = `
42984
43498
 
42985
43499
  ### Booking Shortcuts
42986
43500
  Faster than composing journal entries via \`kirjapro_create_transaction\`:
42987
- - \`kirjapro_book_sale\` / \`kirjapro_book_purchase\` \u2014 one-liner with auto VAT splitting. A VAT-registered (ALV) company books sales as invoices (\`kirjapro_create_invoice\` + \`kirjapro_send_invoice\`): \`kirjapro_book_sale\` and \`kirjapro_book_purchase\` with \`reverseCharge\` are refused for it
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
42988
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.
42989
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\`.
42990
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)
@@ -43140,15 +43654,19 @@ async function main() {
43140
43654
  });
43141
43655
  registerResources(server);
43142
43656
  registerPrompts(server);
43143
- const freshness = freshnessMeta(FINNISH_RATES_2026);
43144
- if (freshness.stale) {
43145
- console.error(
43146
- `[kirjapro-mcp] WARNING: Finnish rates dataset is STALE (valid through ${freshness.valid_through}). Running on expired VAT and social-insurance rates. Upgrade @kirjapro/mcp-server immediately.`
43147
- );
43148
- } else if (freshness.days_until_expiry < 30) {
43149
- console.error(
43150
- `[kirjapro-mcp] NOTICE: Finnish rates dataset expires in ${freshness.days_until_expiry} days (${freshness.valid_through}). Upgrade @kirjapro/mcp-server before year-end to avoid stale rates.`
43151
- );
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
+ }
43152
43670
  }
43153
43671
  const transport = new StdioServerTransport();
43154
43672
  transport.onclose = () => void shutdown();