@kirjapro/mcp-server 0.9.1 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,104 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.0] - Unreleased
4
+
5
+ Requires the database migrations `20261002070106`–`20261002071619` (ALV report
6
+ audit batch 2), in particular `book_purchase_with_vat`, and
7
+ `20261002120000`–`20261002120001` (batch 3, the OmaVero field map); release it
8
+ only after `db:deploy`. Version 0.10.0 keeps working against that database: its
9
+ standard purchases book plain vouchers without a VAT fact (listed by the ALV
10
+ reconciliation), the database applies the new sales-account rule, and its ALV
11
+ summary passes the new `omavero` key through. Against a database without the
12
+ batch 3 map, 0.11.0 derives the same fields from the older keys.
13
+
14
+ - `kirjapro_book_purchase`: for a VAT-registered company a purchase with a rate
15
+ above 0 is booked with `book_purchase_with_vat`, which writes the voucher and
16
+ its VAT fact together, so the VAT reaches field 307. `reverseCharge` works
17
+ again with the new `reverseChargeCategory` (`eu_goods`, `eu_services`,
18
+ `non_eu_services`, `import`, `construction`, `scrap_metal`) and a Finnish
19
+ rate. A company that is not VAT-registered is refused a rate above 0 or a
20
+ reverse charge and books the purchase gross with `vatRate` `0`; the refusal
21
+ says that the VAT on a service from a foreign seller is still owed and goes
22
+ on a non-deductible Ostojen ALV row in the web app and on a voucher D expense
23
+ / K 4210 (Customs collects such a company's import VAT). New optional
24
+ `supplierName`; the result adds `vat_entry_ids`. The database refuses (P0001)
25
+ a deduction on law-based non-deductible accounts, a foreign rate and VAT rows
26
+ that do not match the 2320/4210 lines.
27
+ - `kirjapro_create_from_template`: a purchase template that still debits 2320
28
+ books its domestic VAT fact through `book_purchase_with_vat`; for a company
29
+ that is not VAT-registered it is refused. `kirjapro_create_from_template_batch`
30
+ refuses purchase templates with VAT before any write (the atomic batch cannot
31
+ write VAT facts). `kirjapro_list_templates` hides `purchase_expense` and
32
+ `service_expense` from a company that is not VAT-registered.
33
+ - `kirjapro_create_transaction(s_batch)` and the server instructions describe
34
+ the net sales-account rule (U-3): a net credit on 5000-5499, a sales line
35
+ with a 4210 or 2320 line and a net 4210 credit are refused; a transfer out of
36
+ a sales account without a VAT line, such as a fixed-asset disposal, is
37
+ allowed. They also say that a free 2320 line does not reach field 307.
38
+ - `kirjapro_get_alv_summary` documents `output_vat_non_eu_services`, the VAT on
39
+ services bought from outside the EU that fields 301-303 now include.
40
+ - `kirjapro_get_alv_summary` returns every official OmaVero field in
41
+ `omavero_fields` (VSRALVKV v1.10: 301-314 and 318-320) from the database's
42
+ `omavero` map: 307 is the total deductible VAT including reverse charge and
43
+ import VAT, 308 = 301+302+303+304+305+306+318 - 307, 309 excludes EU sales,
44
+ and 311 follows AVL 138 a §. The legacy keys `field_307`…`field_313` (and
45
+ their `_base` keys), `field_318`-`field_320`, `total_zero_rated` and
46
+ `reverse_charge_base` are no longer returned: `field_307` was only the
47
+ domestic deductible VAT and the others were purchase categories, not field
48
+ numbers. New `exempt_sales_base` (sales without a deduction right, in no
49
+ field); unclassified legacy reverse-charge invoice lines add a `warning`.
50
+ - `kirjapro://compliance/*`: field 309 excludes EU sales (311, 312) and exempt
51
+ sales; cash-basis VAT needs no OmaVero opt-in and leaves EU and import items
52
+ on the accrual basis; a correction within 45 days carries no late-filing fee
53
+ regardless of the amount; the reverse-charge field list names 301-303 for
54
+ non-EU services, 304/310 for imports and the base fields.
55
+ - Review the fingerprints of `kirjapro_book_purchase`,
56
+ `kirjapro_create_from_template(_batch)`, `kirjapro_list_templates`,
57
+ `kirjapro_create_transaction(s_batch)` and `kirjapro_get_alv_summary`.
58
+
59
+ ## [0.10.0] - 2026-10-02
60
+
61
+ Requires the database migrations `20261001204555`–`20261001204559` (ALV report
62
+ audit batch 1). Version 0.9.1 keeps working against that database: its sales
63
+ and reverse-charge vouchers of a VAT-registered company are refused by the
64
+ database with the same guidance, and its settlement default date stays the
65
+ old one.
66
+
67
+ - `kirjapro_book_sale`, the sale templates (`cash_sale_*`, `bank_sale_*`,
68
+ `invoice_sale_25`) of `kirjapro_create_from_template(_batch)` and
69
+ `kirjapro_book_purchase` with `reverseCharge` are refused for a
70
+ VAT-registered (ALV) company before any write: a journal voucher never
71
+ reaches the ALV report. Sales are created with `kirjapro_create_invoice` and
72
+ delivered with `kirjapro_send_invoice`; reverse-charge purchases are booked
73
+ as received invoices in the web app for now. `kirjapro_list_templates` no
74
+ longer lists the sale templates for such a company. Other companies are
75
+ unchanged.
76
+ - `kirjapro_create_transaction(s_batch)`: the database refuses lines on sales
77
+ accounts 5000-5499 and a net 4210 credit for a VAT-registered company; the
78
+ descriptions say so.
79
+ - `kirjapro_book_vat_settlement` settles the company's VAT period (month,
80
+ quarter or year from `companies.vat_period`) containing `month`. The default
81
+ date is the statutory due date (12th of the second month after the period;
82
+ annual period: end of February), and the fiscal year defaults to the open
83
+ year containing that date, so a December settlement is booked in the next
84
+ year. The database now excludes earlier settlements, closing entries and the
85
+ previous-year import, refuses a settled period, a period whose ledger does
86
+ not match the ALV report and negative balances, and books only non-zero
87
+ lines. The result and the dry run name the period. A company that is not
88
+ VAT-registered is refused.
89
+ - `kirjapro_get_alv_summary` returns `reconciliation`: the 4210/2320 ledger
90
+ against the report (status `match`, `explained` or `mismatch`, the
91
+ differences and the explaining vouchers, invoices, fact mismatches and
92
+ manual rows), and sets `warning` on a mismatch.
93
+ - The server instructions (core and full surface) say that a VAT-registered
94
+ company books sales as invoices, that `kirjapro_create_transaction` cannot
95
+ replace `kirjapro_book_sale` for it, and that its sale templates are hidden.
96
+ - Review the fingerprints of `kirjapro_book_sale`, `kirjapro_book_purchase`,
97
+ `kirjapro_book_vat_settlement`, `kirjapro_create_from_template`,
98
+ `kirjapro_create_from_template_batch`, `kirjapro_list_templates`,
99
+ `kirjapro_create_transaction`, `kirjapro_create_transactions_batch` and
100
+ `kirjapro_get_alv_summary` for their changed descriptions and schemas.
101
+
3
102
  ## [0.9.1] - 2026-09-29
4
103
 
5
104
  Requires the database migration `20260929120200` (bank reconciliation
package/dist/index.js CHANGED
@@ -4148,13 +4148,14 @@ var require_fast_uri = __commonJS({
4148
4148
  if (!malformedIPLiteral) {
4149
4149
  malformedHost = canonicalizeHost(parsed, options, schemeHandler, isIP);
4150
4150
  }
4151
- if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4152
- if (uri.indexOf("%") !== -1) {
4153
- if (parsed.host !== void 0 && !malformedIPLiteral) {
4154
- const host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4155
- parsed.host = reescapeHostDelimiters(host, isIP);
4156
- }
4151
+ if (uri.indexOf("%") !== -1 && parsed.host !== void 0 && !malformedIPLiteral) {
4152
+ let host = isIP ? parsed.host : normalizePercentEncoding(parsed.host, true);
4153
+ if (!isIP) {
4154
+ host = normalizePercentEncoding(host.toLowerCase());
4157
4155
  }
4156
+ parsed.host = reescapeHostDelimiters(host, isIP);
4157
+ }
4158
+ if (!schemeHandler || schemeHandler && !schemeHandler.skipNormalize) {
4158
4159
  if (parsed.path) {
4159
4160
  parsed.path = normalizePathEncoding(parsed.path);
4160
4161
  }
@@ -35678,7 +35679,7 @@ var StdioServerTransport = class {
35678
35679
  import { createClient } from "@supabase/supabase-js";
35679
35680
 
35680
35681
  // src/version.ts
35681
- var VERSION = "0.9.1";
35682
+ var VERSION = "0.11.0";
35682
35683
 
35683
35684
  // src/auth.ts
35684
35685
  var SUPABASE_PROJECT_URL = "https://tifewzsimmyoiepblhld.supabase.co";
@@ -36881,7 +36882,7 @@ function registerTransactionTools(server2) {
36881
36882
  server2.registerTool(
36882
36883
  "kirjapro_create_transaction",
36883
36884
  {
36884
- description: "Create a transaction (tosite) with journal entries. Entries must balance (debits = credits). Accepts account numbers resolved to UUIDs server-side. Prefer kirjapro_create_from_template for standard patterns.",
36885
+ description: "Create a transaction (tosite) with journal entries. Entries must balance (debits = credits). Accepts account numbers resolved to UUIDs server-side. Prefer kirjapro_create_from_template for standard patterns. For a VAT-registered (ALV) company the database refuses a net credit on sales accounts 5000-5499, a sales-account line together with a 4210 or 2320 line, and a net 4210 credit: sales are invoices. Moving an amount out of a sales account without a VAT line (e.g. a fixed-asset disposal D 5010 / K 1600) is allowed. A purchase with deductible VAT belongs in kirjapro_book_purchase so the VAT reaches field 307; a free 2320 line is listed by the ALV reconciliation.",
36885
36886
  inputSchema: {
36886
36887
  fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
36887
36888
  transactionDate: ISO_DATE_SCHEMA.describe("Transaction date (YYYY-MM-DD)"),
@@ -37020,7 +37021,7 @@ function registerTransactionTools(server2) {
37020
37021
  server2.registerTool(
37021
37022
  "kirjapro_create_transactions_batch",
37022
37023
  {
37023
- description: "Preview or atomically create multiple transactions in one call (up to 50). dryRun defaults to true. Commit is always all-or-nothing; partial/best-effort writes are intentionally unavailable.",
37024
+ description: "Preview or atomically create multiple transactions in one call (up to 50). dryRun defaults to true. Commit is always all-or-nothing; partial/best-effort writes are intentionally unavailable. For a VAT-registered (ALV) company the database refuses a net credit on sales accounts 5000-5499, a sales-account line together with a 4210 or 2320 line, and a net 4210 credit: sales are invoices. Moving an amount out of a sales account without a VAT line (e.g. a fixed-asset disposal D 5010 / K 1600) is allowed. A purchase with deductible VAT belongs in kirjapro_book_purchase so the VAT reaches field 307; a free 2320 line is listed by the ALV reconciliation.",
37024
37025
  inputSchema: {
37025
37026
  fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
37026
37027
  dryRun: external_exports.boolean().default(true).describe(
@@ -38438,6 +38439,83 @@ function registerCustomerTools(server2) {
38438
38439
  );
38439
38440
  }
38440
38441
 
38442
+ // src/compliance/omavero-fields.ts
38443
+ var OMAVERO_FIELD_NUMBERS = [
38444
+ "301",
38445
+ "302",
38446
+ "303",
38447
+ "304",
38448
+ "305",
38449
+ "306",
38450
+ "307",
38451
+ "308",
38452
+ "309",
38453
+ "310",
38454
+ "311",
38455
+ "312",
38456
+ "313",
38457
+ "314",
38458
+ "318",
38459
+ "319",
38460
+ "320"
38461
+ ];
38462
+ var LEGACY_ALV_SUMMARY_KEYS = [
38463
+ "field_307",
38464
+ "field_307_base",
38465
+ "field_308",
38466
+ "field_308_base",
38467
+ "field_309",
38468
+ "field_309_base",
38469
+ "field_310",
38470
+ "field_310_base",
38471
+ "field_312",
38472
+ "field_312_base",
38473
+ "field_313",
38474
+ "field_313_base",
38475
+ "field_318",
38476
+ "field_319",
38477
+ "field_320",
38478
+ "total_zero_rated",
38479
+ "reverse_charge_base",
38480
+ "omavero"
38481
+ ];
38482
+ function cents(value) {
38483
+ return Math.round(value * 100) / 100;
38484
+ }
38485
+ function num(value) {
38486
+ const n = typeof value === "number" ? value : Number(value ?? 0);
38487
+ return Number.isFinite(n) ? n : 0;
38488
+ }
38489
+ function omaveroFieldsFromSummary(s) {
38490
+ const fields = {
38491
+ "301": num(s.field_301),
38492
+ "302": num(s.field_302),
38493
+ "303": num(s.field_303),
38494
+ "304": num(s.output_vat_import),
38495
+ "305": num(s.output_vat_eu_goods),
38496
+ "306": num(s.output_vat_eu_services),
38497
+ "307": num(s.total_input_vat),
38498
+ "308": num(s.net_vat_payable),
38499
+ "309": num(s.zero_vat_turnover),
38500
+ "310": num(s.field_312_base),
38501
+ "311": num(s.eu_goods_sales_base),
38502
+ "312": num(s.eu_service_sales_base),
38503
+ "313": num(s.field_308_base),
38504
+ "314": num(s.field_309_base),
38505
+ "318": num(s.output_vat_construction_scrap),
38506
+ "319": num(s.reverse_charge_sales_base),
38507
+ "320": cents(num(s.field_310_base) + num(s.field_313_base))
38508
+ };
38509
+ const map2 = s.omavero;
38510
+ if (map2 && typeof map2 === "object") {
38511
+ const values = map2;
38512
+ for (const field of OMAVERO_FIELD_NUMBERS) {
38513
+ if (values[field] !== void 0 && values[field] !== null) fields[field] = num(values[field]);
38514
+ }
38515
+ }
38516
+ return fields;
38517
+ }
38518
+
38441
38519
  // src/lib/incomeStatementSynthesis.ts
38442
38520
  function synthesizeIncomeStatementLines(lineTypes, currentByLine, previousByLine) {
38443
38521
  const lineAmounts = new Map(currentByLine);
@@ -38592,15 +38670,35 @@ var ALV_SUMMARY_OUTPUT_SHAPE = {
38592
38670
  input_vat: external_exports.unknown().optional(),
38593
38671
  net_vat_payable: external_exports.number().optional(),
38594
38672
  net_vat_payable_formatted: external_exports.string().optional(),
38595
- omavero_fields: external_exports.record(external_exports.string(), external_exports.number()).optional(),
38673
+ // Every official OmaVero field (VSRALVKV v1.10: 301-314, 318-320), ALV
38674
+ // report audit 2026-10-01, T2-11. The only field-numbered output.
38675
+ omavero_fields: external_exports.record(external_exports.string(), external_exports.number()).describe(
38676
+ "Official OmaVero VAT-return fields 301-314 and 318-320. 307 is all deductible VAT; 308 = 301+302+303+304+305+306+318 - 307."
38677
+ ).optional(),
38678
+ // Sales without a deduction right: in no OmaVero field (informational).
38679
+ exempt_sales_base: external_exports.number().optional(),
38596
38680
  zero_vat_turnover: external_exports.number().optional(),
38597
38681
  eu_goods_sales_base: external_exports.number().optional(),
38598
38682
  reverse_charge_sales_base: external_exports.number().optional(),
38599
38683
  eu_service_sales_base: external_exports.number().optional(),
38600
38684
  unclassified_legacy_reverse_charge_base: external_exports.number().optional(),
38685
+ // VAT on services bought from outside the EU, already inside field_301-303
38686
+ // and total_output_vat (ALV report audit 2026-10-01, T2-13).
38687
+ output_vat_non_eu_services: external_exports.number().optional(),
38688
+ // get_alv_summary.reconciliation: the report against the 4210/2320 ledger
38689
+ // (ALV report audit 2026-10-01, T2-1). Absent on an older database.
38690
+ reconciliation: external_exports.object({ status: external_exports.enum(["match", "explained", "mismatch"]) }).passthrough().optional(),
38601
38691
  warning: external_exports.string().optional(),
38602
38692
  message: external_exports.string().optional()
38603
38693
  };
38694
+ var LEGACY_REVERSE_CHARGE_WARNING = "Legacy reverse-charge invoice lines must be reclassified before the fields are complete.";
38695
+ function alvReconciliationWarning(reconciliation) {
38696
+ if (!reconciliation || typeof reconciliation !== "object") return void 0;
38697
+ const r = reconciliation;
38698
+ if (r.status !== "mismatch") return void 0;
38699
+ const amount = (value) => (Number(value) || 0).toFixed(2);
38700
+ return `Ledger accounts 4210/2320 differ from the ALV report (output VAT difference ${amount(r.output_difference)} \u20AC, input VAT difference ${amount(r.input_difference)} \u20AC). Review reconciliation before filing.`;
38701
+ }
38604
38702
  var BALANCE_SHEET_ROW_SHAPE = external_exports.object({
38605
38703
  account_number: external_exports.string(),
38606
38704
  name: external_exports.string(),
@@ -39076,7 +39174,7 @@ function registerReportTools(server2) {
39076
39174
  server2.registerTool(
39077
39175
  "kirjapro_get_alv_summary",
39078
39176
  {
39079
- description: "Get VAT (ALV) summary for a date range. Returns output/input VAT breakdown by rate and OmaVero field codes (301-320). For filing deadlines, read kirjapro://compliance/deadlines.",
39177
+ description: "Get the VAT (ALV) summary for a date range. Returns `omavero_fields`, every official OmaVero VAT-return field (VSRALVKV v1.10: 301-314, 318-320). 307 is the total deductible VAT including reverse charge and import VAT; 308 = (301+302+303+304+305+306+318) - 307. 309 excludes 311/312 sales; exempt sales without a deduction right (`exempt_sales_base`) belong in no field. 311 follows AVL 138 a \xA7 (the delivery month when invoiced on or after the delivery in that month; otherwise, including an advance invoice, the next month). Fields 301-303 include the self-assessed VAT on services bought from outside the EU (output_vat_non_eu_services). Copy fields only from `omavero_fields`. Also returns a reconciliation object comparing the 4210/2320 ledger with the report (status match, explained or mismatch, with the differences and the vouchers, invoices, fact mismatches and manual rows that explain them); a mismatch or unclassified legacy reverse-charge lines set warning. For filing deadlines, read kirjapro://compliance/deadlines.",
39080
39178
  inputSchema: {
39081
39179
  dateFrom: ISO_DATE_SCHEMA.describe("Period start date (YYYY-MM-DD)"),
39082
39180
  dateTo: ISO_DATE_SCHEMA.describe("Period end date (YYYY-MM-DD)")
@@ -39116,24 +39214,21 @@ function registerReportTools(server2) {
39116
39214
  });
39117
39215
  }
39118
39216
  const raw = result;
39119
- const {
39120
- field_318: _legacyField318,
39121
- field_319: _legacyField319,
39122
- field_320: _legacyField320,
39123
- ...safeResult
39124
- } = raw;
39217
+ const omaveroFields = omaveroFieldsFromSummary(raw);
39218
+ const legacyKeys = new Set(LEGACY_ALV_SUMMARY_KEYS);
39219
+ const safeResult = Object.fromEntries(
39220
+ Object.entries(raw).filter(([key]) => !legacyKeys.has(key))
39221
+ );
39222
+ const warnings = [
39223
+ alvReconciliationWarning(raw.reconciliation),
39224
+ Number(raw.unclassified_legacy_reverse_charge_base) ? LEGACY_REVERSE_CHARGE_WARNING : void 0
39225
+ ].filter((text) => Boolean(text));
39125
39226
  return toResult({
39126
39227
  ...safeResult,
39228
+ ...warnings.length > 0 && { warning: warnings.join(" ") },
39127
39229
  period: { from: params.dateFrom, to: params.dateTo },
39128
39230
  net_vat_payable_formatted: formatCurrency(result.net_vat_payable || 0),
39129
- omavero_fields: {
39130
- "309": Number(raw.zero_vat_turnover) || 0,
39131
- "311": Number(raw.eu_goods_sales_base) || 0,
39132
- "312": Number(raw.eu_service_sales_base) || 0,
39133
- "318": Number(raw.output_vat_construction_scrap) || 0,
39134
- "319": Number(raw.reverse_charge_sales_base) || 0,
39135
- "320": (Number(raw.field_310_base) || 0) + (Number(raw.field_313_base) || 0)
39136
- }
39231
+ omavero_fields: omaveroFields
39137
39232
  });
39138
39233
  }
39139
39234
  );
@@ -39745,6 +39840,35 @@ var ACCOUNTS = {
39745
39840
  VAKUUTUKSET: "7970"
39746
39841
  };
39747
39842
 
39843
+ // src/compliance/vat-guard.ts
39844
+ var ALV_REGISTERED_SALE_REFUSAL = "This company is VAT-registered (ALV). A sale booked as a journal voucher never reaches the ALV report (fields 301-303 and 309), so Kirjapro refuses it. Create the sale with kirjapro_create_invoice, deliver it with kirjapro_send_invoice and record the payment with kirjapro_record_payment or bank matching.";
39845
+ var NOT_VAT_REGISTERED_PURCHASE_REFUSAL = 'This company is not VAT-registered (ALV), so it deducts no VAT on purchases: book the purchase gross with vatRate "0". A service bought from a foreign seller (in another EU country or outside the EU) still makes the company liable for the self-assessed VAT: tell the user to add it as a non-deductible row on the Ostojen ALV page of the Kirjapro web app, to book the same VAT as a voucher debiting the expense account and crediting 4210 (ALV-velka), for example with kirjapro_create_transaction, and to check whether the company must register for VAT. Customs (Tulli) collects the import VAT of such a company. If the company is VAT-registered, update its settings in the Kirjapro web app first.';
39846
+ var REVERSE_CHARGE_CATEGORIES = [
39847
+ "eu_goods",
39848
+ "eu_services",
39849
+ "non_eu_services",
39850
+ "import",
39851
+ "construction",
39852
+ "scrap_metal"
39853
+ ];
39854
+ var REVERSE_CHARGE_CATEGORY_REQUIRED = "reverseChargeCategory is required: eu_goods (fields 305/313), eu_services (306/314), non_eu_services (301-303), import (304/310), construction or scrap_metal (318/320).";
39855
+ var REVERSE_CHARGE_RATE_REQUIRED = "A reverse-charge purchase needs the Finnish rate the buyer self-assesses (25.5, 13.5 or 10).";
39856
+ var PURCHASE_TEMPLATE_NOT_VAT_REGISTERED_REFUSAL = 'This company is not VAT-registered (ALV), so a purchase template with VAT (2320) cannot be used. Book the purchase gross with kirjapro_book_purchase and vatRate "0".';
39857
+ function purchaseTemplateBatchRefusal(indexes) {
39858
+ return `Purchase templates with VAT (index ${indexes.join(", ")}) are booked one at a time with kirjapro_create_from_template or kirjapro_book_purchase so the VAT reaches the ALV report; nothing was written.`;
39859
+ }
39860
+ var NOT_VAT_REGISTERED_SETTLEMENT_REFUSAL = "This company is not VAT-registered (ALV), so it has no VAT settlement to book.";
39861
+ async function readCompanyVat(supabase2, companyId2) {
39862
+ const { data, error: error61 } = await supabase2.from("companies").select("alv_registered, vat_period").eq("id", companyId2).single();
39863
+ if (error61 || !data) {
39864
+ return toErrorContent(
39865
+ error61 ? sanitizeDbError("Failed to verify the company VAT registration", error61) : "Failed to verify the company VAT registration."
39866
+ );
39867
+ }
39868
+ const row = data;
39869
+ return { alvRegistered: row.alv_registered === true, vatPeriod: row.vat_period ?? "monthly" };
39870
+ }
39871
+
39748
39872
  // src/tools/templates.ts
39749
39873
  function splitVat(gross, vatRate) {
39750
39874
  if (vatRate === 0) return { net: roundCents(gross), vat: 0 };
@@ -39844,24 +39968,28 @@ var TEMPLATES = [
39844
39968
  // Cash sales
39845
39969
  {
39846
39970
  id: "cash_sale_25",
39971
+ kind: "sale",
39847
39972
  description: "K\xE4teismyynti ALV 25,5%",
39848
39973
  vatRate: 25.5,
39849
39974
  expand: saleCashTemplate(ACCOUNTS.MYYNTI_25_5, 25.5)
39850
39975
  },
39851
39976
  {
39852
39977
  id: "cash_sale_13_5",
39978
+ kind: "sale",
39853
39979
  description: "K\xE4teismyynti ALV 13,5%",
39854
39980
  vatRate: 13.5,
39855
39981
  expand: saleCashTemplate(ACCOUNTS.MYYNTI_13_5, 13.5)
39856
39982
  },
39857
39983
  {
39858
39984
  id: "cash_sale_10",
39985
+ kind: "sale",
39859
39986
  description: "K\xE4teismyynti ALV 10%",
39860
39987
  vatRate: 10,
39861
39988
  expand: saleCashTemplate(ACCOUNTS.MYYNTI_10, 10)
39862
39989
  },
39863
39990
  {
39864
39991
  id: "cash_sale_0",
39992
+ kind: "sale",
39865
39993
  description: "K\xE4teismyynti ALV 0%",
39866
39994
  vatRate: 0,
39867
39995
  expand: saleCashTemplate(ACCOUNTS.MYYNTI_0, 0)
@@ -39869,18 +39997,21 @@ var TEMPLATES = [
39869
39997
  // Bank sales
39870
39998
  {
39871
39999
  id: "bank_sale_25",
40000
+ kind: "sale",
39872
40001
  description: "Pankkimyynti ALV 25,5%",
39873
40002
  vatRate: 25.5,
39874
40003
  expand: saleBankTemplate(ACCOUNTS.MYYNTI_25_5, 25.5)
39875
40004
  },
39876
40005
  {
39877
40006
  id: "bank_sale_13_5",
40007
+ kind: "sale",
39878
40008
  description: "Pankkimyynti ALV 13,5%",
39879
40009
  vatRate: 13.5,
39880
40010
  expand: saleBankTemplate(ACCOUNTS.MYYNTI_13_5, 13.5)
39881
40011
  },
39882
40012
  {
39883
40013
  id: "bank_sale_10",
40014
+ kind: "sale",
39884
40015
  description: "Pankkimyynti ALV 10%",
39885
40016
  vatRate: 10,
39886
40017
  expand: saleBankTemplate(ACCOUNTS.MYYNTI_10, 10)
@@ -39888,6 +40019,7 @@ var TEMPLATES = [
39888
40019
  // Invoice sale
39889
40020
  {
39890
40021
  id: "invoice_sale_25",
40022
+ kind: "sale",
39891
40023
  description: "Laskumyynti ALV 25,5% (myyntisaamiset)",
39892
40024
  vatRate: 25.5,
39893
40025
  expand: (amount, opts) => {
@@ -39909,6 +40041,7 @@ var TEMPLATES = [
39909
40041
  // Purchase expenses
39910
40042
  {
39911
40043
  id: "purchase_expense",
40044
+ kind: "purchase",
39912
40045
  description: "Osto ALV 25,5% (pankkitililt\xE4)",
39913
40046
  vatRate: 25.5,
39914
40047
  expand: (amount, opts) => {
@@ -39929,6 +40062,7 @@ var TEMPLATES = [
39929
40062
  },
39930
40063
  {
39931
40064
  id: "service_expense",
40065
+ kind: "purchase",
39932
40066
  description: "Ulkopuolinen palvelu ALV 25,5%",
39933
40067
  vatRate: 25.5,
39934
40068
  expand: (amount, opts) => {
@@ -39950,6 +40084,7 @@ var TEMPLATES = [
39950
40084
  // Rent (no VAT)
39951
40085
  {
39952
40086
  id: "rent_payment",
40087
+ kind: "other",
39953
40088
  description: "Vuokranmaksu (pankkitililt\xE4)",
39954
40089
  vatRate: null,
39955
40090
  expand: (amount, opts) => applyOverrides(
@@ -39971,6 +40106,7 @@ var TEMPLATES = [
39971
40106
  // Bank transfer
39972
40107
  {
39973
40108
  id: "bank_transfer",
40109
+ kind: "other",
39974
40110
  description: "Tilisiirto kassasta pankkitilille",
39975
40111
  vatRate: null,
39976
40112
  expand: (amount, opts) => applyOverrides(
@@ -39991,6 +40127,21 @@ var TEMPLATES = [
39991
40127
  }
39992
40128
  ];
39993
40129
  var TEMPLATE_MAP = new Map(TEMPLATES.map((t) => [t.id, t]));
40130
+ function isSaleTemplate(templateId) {
40131
+ return TEMPLATE_MAP.get(templateId)?.kind === "sale";
40132
+ }
40133
+ function inputVatOf(entries) {
40134
+ return roundCents(
40135
+ entries.filter((e) => e.accountNumber === ACCOUNTS.ALV_SAAMISET).reduce((sum, e) => sum + e.debitAmount - e.creditAmount, 0)
40136
+ );
40137
+ }
40138
+ function isVatPurchaseTemplate(templateId) {
40139
+ const template = TEMPLATE_MAP.get(templateId);
40140
+ return template?.kind === "purchase" && inputVatOf(template.expand(100)) !== 0;
40141
+ }
40142
+ function availableTemplates(alvRegistered) {
40143
+ return alvRegistered ? TEMPLATES.filter((t) => t.kind !== "sale") : TEMPLATES.filter((t) => !isVatPurchaseTemplate(t.id));
40144
+ }
39994
40145
  function expandTemplate(templateId, amount, opts) {
39995
40146
  const template = TEMPLATE_MAP.get(templateId);
39996
40147
  if (!template) {
@@ -40053,7 +40204,7 @@ function toJournalEntries(entries, accountMap) {
40053
40204
  credit_amount: e.creditAmount
40054
40205
  }));
40055
40206
  }
40056
- async function resolveAndCreate(params) {
40207
+ async function preparePosting(params) {
40057
40208
  const supabase2 = getSupabase();
40058
40209
  const companyId2 = getCompanyId();
40059
40210
  let fiscalYearId = params.fiscalYearId;
@@ -40082,16 +40233,41 @@ async function resolveAndCreate(params) {
40082
40233
  `Template produced unbalanced entries: debits ${totalDebit.toFixed(2)} != credits ${totalCredit.toFixed(2)}.`
40083
40234
  );
40084
40235
  }
40085
- const journalEntries = toJournalEntries(params.entries, accountResult);
40086
- const { data: transactionId, error: txError } = await supabase2.rpc(
40236
+ return { fiscalYearId, journalEntries: toJournalEntries(params.entries, accountResult) };
40237
+ }
40238
+ async function resolveAndCreatePurchase(params) {
40239
+ const prepared = await preparePosting(params);
40240
+ if ("isError" in prepared) return prepared;
40241
+ const { data, error: error61 } = await getSupabase().rpc("book_purchase_with_vat", {
40242
+ p_company_id: getCompanyId(),
40243
+ p_fiscal_year_id: prepared.fiscalYearId,
40244
+ p_transaction_date: params.transactionDate,
40245
+ p_description: params.description,
40246
+ p_document_number: params.documentNumber || "",
40247
+ p_entries: prepared.journalEntries,
40248
+ p_vat_rows: params.vatRows,
40249
+ ...params.supplierName ? { p_supplier_name: params.supplierName } : {}
40250
+ });
40251
+ const result = data;
40252
+ if (error61 || !result?.transaction_id) {
40253
+ return toErrorContent(
40254
+ error61 ? sanitizeDbError("Failed to book the purchase", error61) : "Failed to book the purchase: no ID returned."
40255
+ );
40256
+ }
40257
+ return { id: result.transaction_id, vatEntryIds: result.vat_entry_ids ?? [] };
40258
+ }
40259
+ async function resolveAndCreate(params) {
40260
+ const prepared = await preparePosting(params);
40261
+ if ("isError" in prepared) return prepared;
40262
+ const { data: transactionId, error: txError } = await getSupabase().rpc(
40087
40263
  "create_transaction_with_entries",
40088
40264
  {
40089
- p_company_id: companyId2,
40090
- p_fiscal_year_id: fiscalYearId,
40265
+ p_company_id: getCompanyId(),
40266
+ p_fiscal_year_id: prepared.fiscalYearId,
40091
40267
  p_transaction_date: params.transactionDate,
40092
40268
  p_description: params.description,
40093
40269
  p_document_number: params.documentNumber || "",
40094
- p_entries: journalEntries
40270
+ p_entries: prepared.journalEntries
40095
40271
  }
40096
40272
  );
40097
40273
  if (txError || !transactionId) {
@@ -40105,7 +40281,7 @@ function registerTemplateTools(server2) {
40105
40281
  server2.registerTool(
40106
40282
  "kirjapro_list_templates",
40107
40283
  {
40108
- description: "List available transaction templates. Templates handle double-entry balancing and VAT splitting automatically.",
40284
+ description: "List available transaction templates. Templates handle double-entry balancing and VAT splitting automatically. Sale templates are not listed for a VAT-registered (ALV) company: its sales are invoices. Purchase templates with VAT are not listed for a company that is not VAT-registered.",
40109
40285
  inputSchema: {},
40110
40286
  outputSchema: {
40111
40287
  templates: external_exports.array(external_exports.object({
@@ -40117,7 +40293,9 @@ function registerTemplateTools(server2) {
40117
40293
  annotations: READ_ONLY_ANNOTATIONS
40118
40294
  },
40119
40295
  async () => {
40120
- const list = TEMPLATES.map((t) => ({
40296
+ const companyVat = await readCompanyVat(getSupabase(), getCompanyId());
40297
+ if ("isError" in companyVat) return companyVat;
40298
+ const list = availableTemplates(companyVat.alvRegistered).map((t) => ({
40121
40299
  id: t.id,
40122
40300
  description: t.description,
40123
40301
  vatRate: t.vatRate
@@ -40128,7 +40306,7 @@ function registerTemplateTools(server2) {
40128
40306
  server2.registerTool(
40129
40307
  "kirjapro_create_from_template",
40130
40308
  {
40131
- description: 'Create a transaction from a template. Use kirjapro_list_templates for available templates. accountOverrides swaps default accounts (e.g. {"2320": "2330"}).',
40309
+ description: 'Create a transaction from a template. Use kirjapro_list_templates for available templates. accountOverrides swaps default accounts (e.g. {"2320": "2330"}). Sale templates are refused for a VAT-registered (ALV) company: create an invoice instead. A purchase template with VAT (2320) books its VAT fact so the VAT reaches field 307; for a company that is not VAT-registered it is refused (book the purchase gross).',
40132
40310
  inputSchema: {
40133
40311
  template: external_exports.string().describe('Template ID (e.g. "cash_sale_25", "purchase_expense")'),
40134
40312
  fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
@@ -40144,6 +40322,16 @@ function registerTemplateTools(server2) {
40144
40322
  async (params) => {
40145
40323
  const supabase2 = getSupabase();
40146
40324
  const companyId2 = getCompanyId();
40325
+ const template = TEMPLATE_MAP.get(params.template);
40326
+ let alvRegistered = null;
40327
+ if (template && (template.kind === "sale" || template.kind === "purchase")) {
40328
+ const companyVat = await readCompanyVat(supabase2, companyId2);
40329
+ if ("isError" in companyVat) return companyVat;
40330
+ alvRegistered = companyVat.alvRegistered;
40331
+ if (template.kind === "sale" && alvRegistered) {
40332
+ return toErrorContent(ALV_REGISTERED_SALE_REFUSAL);
40333
+ }
40334
+ }
40147
40335
  return withIdempotency(
40148
40336
  supabase2,
40149
40337
  companyId2,
@@ -40161,7 +40349,27 @@ function registerTemplateTools(server2) {
40161
40349
  if ("error" in result) {
40162
40350
  return toErrorContent(result.error);
40163
40351
  }
40164
- const createResult = await resolveAndCreate({
40352
+ const inputVat = template?.kind === "purchase" ? inputVatOf(result.entries) : 0;
40353
+ if (inputVat !== 0 && alvRegistered === false) {
40354
+ return toErrorContent(PURCHASE_TEMPLATE_NOT_VAT_REGISTERED_REFUSAL);
40355
+ }
40356
+ const createResult = inputVat !== 0 ? await resolveAndCreatePurchase({
40357
+ fiscalYearId: params.fiscalYearId,
40358
+ transactionDate: params.transactionDate,
40359
+ description: params.description,
40360
+ documentNumber: params.documentNumber,
40361
+ entries: result.entries,
40362
+ vatRows: [
40363
+ {
40364
+ category: "domestic",
40365
+ vat_rate: template?.vatRate ?? 0,
40366
+ tax_base: roundCents(
40367
+ result.entries.filter((e) => e.accountNumber !== ACCOUNTS.ALV_SAAMISET).reduce((sum, e) => sum + e.debitAmount, 0)
40368
+ ),
40369
+ vat_amount: inputVat
40370
+ }
40371
+ ]
40372
+ }) : await resolveAndCreate({
40165
40373
  fiscalYearId: params.fiscalYearId,
40166
40374
  transactionDate: params.transactionDate,
40167
40375
  description: params.description,
@@ -40175,7 +40383,8 @@ function registerTemplateTools(server2) {
40175
40383
  message: "Transaction created from template",
40176
40384
  template: params.template,
40177
40385
  transaction_id: createResult.id,
40178
- entries: result.entries
40386
+ entries: result.entries,
40387
+ ..."vatEntryIds" in createResult && { vat_entry_ids: createResult.vatEntryIds }
40179
40388
  });
40180
40389
  }
40181
40390
  );
@@ -40184,7 +40393,7 @@ function registerTemplateTools(server2) {
40184
40393
  server2.registerTool(
40185
40394
  "kirjapro_create_from_template_batch",
40186
40395
  {
40187
- description: "Preview or atomically create multiple template transactions in one call (up to 50). dryRun defaults to true. Commit is always all-or-nothing; partial/best-effort writes are intentionally unavailable.",
40396
+ description: "Preview or atomically create multiple template transactions in one call (up to 50). dryRun defaults to true. Commit is always all-or-nothing; partial/best-effort writes are intentionally unavailable. A batch with a sale template is refused for a VAT-registered (ALV) company before any write. Purchase templates with VAT (2320) are refused in a batch: book them one at a time with kirjapro_create_from_template so the VAT reaches the ALV report.",
40188
40397
  inputSchema: {
40189
40398
  fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
40190
40399
  dryRun: external_exports.boolean().default(true).describe(
@@ -40207,6 +40416,27 @@ function registerTemplateTools(server2) {
40207
40416
  async (params) => {
40208
40417
  const supabase2 = getSupabase();
40209
40418
  const companyId2 = getCompanyId();
40419
+ const vatPurchaseIndexes = params.transactions.map((tx, index) => {
40420
+ const template = TEMPLATE_MAP.get(tx.template);
40421
+ if (template?.kind !== "purchase") return -1;
40422
+ const expanded = expandTemplate(tx.template, tx.amount, {
40423
+ accountOverrides: tx.accountOverrides
40424
+ });
40425
+ return "entries" in expanded && inputVatOf(expanded.entries) !== 0 ? index : -1;
40426
+ }).filter((index) => index >= 0);
40427
+ if (vatPurchaseIndexes.length > 0) {
40428
+ return toErrorContent(purchaseTemplateBatchRefusal(vatPurchaseIndexes));
40429
+ }
40430
+ const saleIndexes = params.transactions.map((tx, index) => isSaleTemplate(tx.template) ? index : -1).filter((index) => index >= 0);
40431
+ if (saleIndexes.length > 0) {
40432
+ const companyVat = await readCompanyVat(supabase2, companyId2);
40433
+ if ("isError" in companyVat) return companyVat;
40434
+ if (companyVat.alvRegistered) {
40435
+ return toErrorContent(
40436
+ `${ALV_REGISTERED_SALE_REFUSAL} Sale templates at index ${saleIndexes.join(", ")}; nothing was written.`
40437
+ );
40438
+ }
40439
+ }
40210
40440
  return withIdempotency(
40211
40441
  supabase2,
40212
40442
  companyId2,
@@ -40430,6 +40660,35 @@ function freshnessMeta(rates = FINNISH_RATES_2026, today = /* @__PURE__ */ new D
40430
40660
  };
40431
40661
  }
40432
40662
 
40663
+ // src/compliance/vat-settlement-date.ts
40664
+ var pad = (value) => String(value).padStart(2, "0");
40665
+ function vatPeriodRange(month, vatPeriod) {
40666
+ const [yearText, monthText] = month.split("-");
40667
+ const year = Number(yearText);
40668
+ const monthNumber = Number(monthText);
40669
+ const period = vatPeriod ?? "monthly";
40670
+ const startMonth = period === "yearly" ? 1 : period === "quarterly" ? Math.floor((monthNumber - 1) / 3) * 3 + 1 : monthNumber;
40671
+ const endMonth = period === "yearly" ? 12 : period === "quarterly" ? startMonth + 2 : monthNumber;
40672
+ const lastDay = new Date(Date.UTC(year, endMonth, 0)).getUTCDate();
40673
+ const label = period === "yearly" ? String(year) : period === "quarterly" ? `${year}-Q${(startMonth + 2) / 3}` : month;
40674
+ return {
40675
+ start: `${year}-${pad(startMonth)}-01`,
40676
+ end: `${year}-${pad(endMonth)}-${pad(lastDay)}`,
40677
+ label
40678
+ };
40679
+ }
40680
+ function defaultSettlementDate(month, vatPeriod) {
40681
+ const { end } = vatPeriodRange(month, vatPeriod);
40682
+ const [endYear, endMonth] = end.split("-").map(Number);
40683
+ if ((vatPeriod ?? "monthly") === "yearly") {
40684
+ const februaryDays = new Date(Date.UTC(endYear + 1, 2, 0)).getUTCDate();
40685
+ return `${endYear + 1}-02-${pad(februaryDays)}`;
40686
+ }
40687
+ const dueMonthIndex = endMonth - 1 + 2;
40688
+ const dueYear = endYear + Math.floor(dueMonthIndex / 12);
40689
+ return `${dueYear}-${pad(dueMonthIndex % 12 + 1)}-12`;
40690
+ }
40691
+
40433
40692
  // src/tools/bookings.ts
40434
40693
  function expenseAccountWarning(account) {
40435
40694
  const first = account.trim().charAt(0);
@@ -40456,7 +40715,7 @@ function registerBookingTools(server2) {
40456
40715
  server2.registerTool(
40457
40716
  "kirjapro_book_purchase",
40458
40717
  {
40459
- description: "Book a purchase (osto) with automatic VAT splitting. Supports reverse charge (AVL 9\xA7) and entertainment (edustus, AVL 114\xA7). Rate rules: food/restaurants 13.5%, insurance 0% (exempt), alcohol 25.5%.",
40718
+ description: "Book a purchase (osto) with automatic VAT splitting. For a VAT-registered (ALV) company the VAT is recorded as a VAT fact, so it reaches field 307 (and the reverse-charge fields). Reverse charge (reverseCharge=true, with reverseChargeCategory) self-assesses the VAT for EU goods and services, services from outside the EU, import, construction and scrap metal. Entertainment (edustus, AVL 114\xA7) is booked gross. Statutorily non-deductible accounts (wages, insurance, interest, depreciation, taxes, representation) are refused with VAT: book them with vatRate 0. A company that is not VAT-registered books purchases gross with vatRate 0. Rate rules: food/restaurants 13.5%, insurance 0% (exempt), alcohol 25.5%.",
40460
40719
  inputSchema: {
40461
40720
  amount: external_exports.number().finite().positive().max(1e9).describe(
40462
40721
  "EUR amount. Standard purchase: GROSS (VAT-inclusive) \u2014 VAT is split out of this. Reverse charge (reverseCharge=true): NET foreign-invoice amount \u2014 the foreign supplier does not charge Finnish VAT, so the parameter must be net. VAT is self-assessed on top per AVL 9\xA7 producing equal debit to ALV-saamiset and credit to ALV-velka. Entertainment (edustus=true): GROSS (full amount to expense, no VAT deduction per AVL 114\xA7)."
@@ -40465,7 +40724,11 @@ function registerBookingTools(server2) {
40465
40724
  date: ISO_DATE_SCHEMA.describe("Transaction date (YYYY-MM-DD)"),
40466
40725
  vatRate: external_exports.enum(["25.5", "13.5", "10", "0"]).describe("Finnish VAT rate"),
40467
40726
  expenseAccount: external_exports.string().describe('Expense account number (e.g. "8080")'),
40468
- reverseCharge: external_exports.boolean().default(false).describe(`EU reverse charge: D expense + D ${ACCOUNTS.ALV_SAAMISET} ALV-saamiset + C ${ACCOUNTS.ALV_VELKA} ALV-velka + C bank`),
40727
+ reverseCharge: external_exports.boolean().default(false).describe(`Reverse charge (k\xE4\xE4nnetty verovelvollisuus): D expense + D ${ACCOUNTS.ALV_SAAMISET} ALV-saamiset + C ${ACCOUNTS.ALV_VELKA} ALV-velka + C bank, with a VAT fact of reverseChargeCategory`),
40728
+ reverseChargeCategory: external_exports.enum(REVERSE_CHARGE_CATEGORIES).optional().describe(
40729
+ "Required with reverseCharge: eu_goods (fields 305/313), eu_services (306/314), non_eu_services (services from outside the EU, 301-303), import (304/310), construction or scrap_metal (318/320)"
40730
+ ),
40731
+ supplierName: external_exports.string().max(200).optional().describe("Supplier name, stored on the VAT fact"),
40469
40732
  edustus: external_exports.boolean().default(false).describe("Entertainment expense (AVL 114\xA7): 0% VAT deduction, full gross to expense"),
40470
40733
  paymentAccount: external_exports.string().default(ACCOUNTS.PANKKITILI).describe(`Payment account (default "${ACCOUNTS.PANKKITILI}" Pankkitili)`),
40471
40734
  fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
@@ -40479,6 +40742,16 @@ function registerBookingTools(server2) {
40479
40742
  const rate = Number.parseFloat(params.vatRate);
40480
40743
  const gross = roundCents(params.amount);
40481
40744
  let entries;
40745
+ let vatRows = [];
40746
+ const companyVat = await readCompanyVat(getSupabase(), getCompanyId());
40747
+ if ("isError" in companyVat) return companyVat;
40748
+ if (!params.edustus && !companyVat.alvRegistered && (rate !== 0 || params.reverseCharge)) {
40749
+ return toErrorContent(NOT_VAT_REGISTERED_PURCHASE_REFUSAL);
40750
+ }
40751
+ if (params.reverseCharge && !params.edustus) {
40752
+ if (!params.reverseChargeCategory) return toErrorContent(REVERSE_CHARGE_CATEGORY_REQUIRED);
40753
+ if (rate === 0) return toErrorContent(REVERSE_CHARGE_RATE_REQUIRED);
40754
+ }
40482
40755
  if (params.edustus) {
40483
40756
  entries = [
40484
40757
  { accountNumber: params.expenseAccount, debitAmount: gross, creditAmount: 0 },
@@ -40494,6 +40767,14 @@ function registerBookingTools(server2) {
40494
40767
  // ALV-velka (output)
40495
40768
  { accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
40496
40769
  ];
40770
+ vatRows = [
40771
+ {
40772
+ category: params.reverseChargeCategory ?? "eu_services",
40773
+ vat_rate: rate,
40774
+ tax_base: gross,
40775
+ vat_amount: vat
40776
+ }
40777
+ ];
40497
40778
  } else if (rate === 0) {
40498
40779
  entries = [
40499
40780
  { accountNumber: params.expenseAccount, debitAmount: gross, creditAmount: 0 },
@@ -40506,6 +40787,7 @@ function registerBookingTools(server2) {
40506
40787
  { accountNumber: ACCOUNTS.ALV_SAAMISET, debitAmount: vat, creditAmount: 0 },
40507
40788
  { accountNumber: params.paymentAccount, debitAmount: 0, creditAmount: gross }
40508
40789
  ];
40790
+ vatRows = [{ category: "domestic", vat_rate: rate, tax_base: net, vat_amount: vat }];
40509
40791
  }
40510
40792
  const purchaseAccountWarning = expenseAccountWarning(params.expenseAccount);
40511
40793
  if (params.dryRun) {
@@ -40516,6 +40798,7 @@ function registerBookingTools(server2) {
40516
40798
  description: params.description,
40517
40799
  date: params.date,
40518
40800
  entries,
40801
+ ...vatRows.length > 0 && { vat_rows: vatRows },
40519
40802
  ...purchaseAccountWarning && { account_warning: purchaseAccountWarning }
40520
40803
  });
40521
40804
  }
@@ -40526,18 +40809,24 @@ function registerBookingTools(server2) {
40526
40809
  params.idempotencyKey,
40527
40810
  params,
40528
40811
  async () => {
40529
- const result = await resolveAndCreate({
40812
+ const posting = {
40530
40813
  fiscalYearId: params.fiscalYearId,
40531
40814
  transactionDate: params.date,
40532
40815
  description: params.description,
40533
40816
  documentNumber: params.documentNumber,
40534
40817
  entries
40535
- });
40818
+ };
40819
+ const result = vatRows.length > 0 ? await resolveAndCreatePurchase({
40820
+ ...posting,
40821
+ vatRows,
40822
+ supplierName: params.supplierName
40823
+ }) : await resolveAndCreate(posting);
40536
40824
  if ("isError" in result) return result;
40537
40825
  return toResult({
40538
40826
  message: "Purchase booked",
40539
40827
  transaction_id: result.id,
40540
40828
  entries,
40829
+ ..."vatEntryIds" in result && { vat_entry_ids: result.vatEntryIds },
40541
40830
  ...purchaseAccountWarning && { warning: purchaseAccountWarning }
40542
40831
  });
40543
40832
  }
@@ -40547,11 +40836,11 @@ function registerBookingTools(server2) {
40547
40836
  server2.registerTool(
40548
40837
  "kirjapro_book_vat_settlement",
40549
40838
  {
40550
- description: `Book ALV-tilitys (VAT settlement) for a month. Calculates net from ALV accounts (${ACCOUNTS.ALV_SAAMISET}/${ACCOUNTS.ALV_VELKA}) and creates settlement. Default date: 12th of month+1.`,
40839
+ description: `Book ALV-tilitys (VAT settlement) for the company's VAT period that contains the given month. Settles the period's net from ALV accounts (${ACCOUNTS.ALV_SAAMISET}/${ACCOUNTS.ALV_VELKA}) by voucher date, excluding earlier settlements, closing entries and the previous-year import. Default date: the statutory due date (12th of the second month after the period; annual period: end of February), not moved to a business day. The period follows the company's VAT period. Refuses a period already settled, a period whose ledger does not match the ALV report, and negative balances.`,
40551
40840
  inputSchema: {
40552
- month: external_exports.string().regex(/^\d{4}-\d{2}$/).describe("Settlement month (YYYY-MM)"),
40553
- fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID (auto-resolved if omitted)"),
40554
- settlementDate: ISO_DATE_SCHEMA.optional().describe("Settlement date (default: 12th of month+1)"),
40841
+ month: external_exports.string().regex(/^\d{4}-(0[1-9]|1[0-2])$/).describe("A month of the VAT period to settle (YYYY-MM); a quarterly or annual company settles the whole period containing it"),
40842
+ fiscalYearId: external_exports.string().uuid().optional().describe("Fiscal year UUID of the settlement date (default: the open fiscal year containing the settlement date)"),
40843
+ settlementDate: ISO_DATE_SCHEMA.optional().describe("Settlement date (default: the statutory due date, 12th of the second month after the period; annual period: end of February)"),
40555
40844
  paymentAccount: external_exports.string().default(ACCOUNTS.PANKKITILI).describe(`Bank account (default "${ACCOUNTS.PANKKITILI}" Pankkitili)`),
40556
40845
  idempotencyKey: IDEMPOTENCY_KEY_SCHEMA,
40557
40846
  dryRun: DRY_RUN_SCHEMA
@@ -40561,16 +40850,25 @@ function registerBookingTools(server2) {
40561
40850
  async (params) => {
40562
40851
  const supabase2 = getSupabase();
40563
40852
  const companyId2 = getCompanyId();
40853
+ const companyVat = await readCompanyVat(supabase2, companyId2);
40854
+ if ("isError" in companyVat) return companyVat;
40855
+ if (!companyVat.alvRegistered) return toErrorContent(NOT_VAT_REGISTERED_SETTLEMENT_REFUSAL);
40856
+ const period = vatPeriodRange(params.month, companyVat.vatPeriod);
40857
+ const dueDate = defaultSettlementDate(params.month, companyVat.vatPeriod);
40858
+ const settlementDate = params.settlementDate ?? dueDate;
40564
40859
  let fiscalYearId = params.fiscalYearId;
40565
40860
  if (!fiscalYearId) {
40566
- const resolved = await resolveOpenFiscalYear(supabase2, companyId2);
40567
- if ("isError" in resolved) return resolved;
40568
- fiscalYearId = resolved.id;
40861
+ const { data: years, error: yearsError } = await supabase2.from("fiscal_years").select("id").eq("company_id", companyId2).eq("is_closed", false).lte("start_date", settlementDate).gte("end_date", settlementDate).order("start_date", { ascending: false }).limit(1);
40862
+ if (yearsError) {
40863
+ return toErrorContent(sanitizeDbError("Failed to query fiscal years", yearsError));
40864
+ }
40865
+ if (!years || years.length === 0) {
40866
+ return toErrorContent(
40867
+ `No open fiscal year contains the settlement date ${settlementDate} (statutory due date for ${period.label}: ${dueDate}). Create that fiscal year or pass settlementDate inside an open fiscal year.`
40868
+ );
40869
+ }
40870
+ fiscalYearId = years[0].id;
40569
40871
  }
40570
- const [year, monthStr] = params.month.split("-");
40571
- const monthNum = Number.parseInt(monthStr, 10);
40572
- const nextMonth = monthNum === 12 ? `${Number.parseInt(year, 10) + 1}-01` : `${year}-${String(monthNum + 1).padStart(2, "0")}`;
40573
- const settlementDate = params.settlementDate ?? `${nextMonth}-12`;
40574
40872
  const { data: fy } = await supabase2.from("fiscal_years").select("start_date, end_date, is_closed").eq("id", fiscalYearId).eq("company_id", companyId2).single();
40575
40873
  if (!fy) {
40576
40874
  return toErrorContent(
@@ -40584,7 +40882,7 @@ function registerBookingTools(server2) {
40584
40882
  }
40585
40883
  if (settlementDate > fy.end_date) {
40586
40884
  return toErrorContent(
40587
- `Settlement date ${settlementDate} is after fiscal year end ${fy.end_date}. Pass an explicit settlementDate within ${fy.start_date}..${fy.end_date} (typical: ${fy.end_date}) \u2014 silent clamping would shift the ALV-kausi.`
40885
+ `Settlement date ${settlementDate} is after fiscal year end ${fy.end_date}. Pass an explicit settlementDate within ${fy.start_date}..${fy.end_date} or the fiscal year that contains ${settlementDate} \u2014 silent clamping would shift the ALV-kausi.`
40588
40886
  );
40589
40887
  }
40590
40888
  if (settlementDate < fy.start_date) {
@@ -40596,9 +40894,14 @@ function registerBookingTools(server2) {
40596
40894
  return toDryRunResponse("kirjapro_book_vat_settlement", {
40597
40895
  action: "book_vat_settlement",
40598
40896
  month: params.month,
40897
+ vat_period: companyVat.vatPeriod,
40898
+ period_start: period.start,
40899
+ period_end: period.end,
40599
40900
  settlement_date: settlementDate,
40901
+ statutory_due_date: dueDate,
40902
+ fiscal_year_id: fiscalYearId,
40600
40903
  payment_account: params.paymentAccount,
40601
- hint: "Call kirjapro_get_alv_summary with the same month to preview the concrete net VAT before committing."
40904
+ hint: `Call kirjapro_get_alv_summary for ${period.start}..${period.end} to preview the net VAT and its reconciliation before committing.`
40602
40905
  });
40603
40906
  }
40604
40907
  return withIdempotency(
@@ -40621,20 +40924,27 @@ function registerBookingTools(server2) {
40621
40924
  );
40622
40925
  }
40623
40926
  const result = data;
40927
+ const periodStart = result.period_start ?? period.start;
40928
+ const periodEnd = result.period_end ?? period.end;
40624
40929
  if (result.direction === "none") {
40625
40930
  return toResult({
40626
- message: result.message ?? `No VAT entries found for ${params.month}. Nothing to settle.`,
40931
+ message: result.message ?? `No VAT entries found for ${period.label}. Nothing to settle.`,
40627
40932
  alv_saamiset: 0,
40628
- alv_velka: 0
40933
+ alv_velka: 0,
40934
+ period_start: periodStart,
40935
+ period_end: periodEnd
40629
40936
  });
40630
40937
  }
40631
40938
  return toResult({
40632
- message: `VAT settlement booked for ${params.month}`,
40939
+ message: `VAT settlement booked for ${period.label}`,
40633
40940
  transaction_id: result.transaction_id,
40634
40941
  alv_saamiset: result.alv_saamiset,
40635
40942
  alv_velka: result.alv_velka,
40636
40943
  net_vat: result.net_vat,
40637
- direction: result.direction
40944
+ direction: result.direction,
40945
+ period_start: periodStart,
40946
+ period_end: periodEnd,
40947
+ settlement_date: settlementDate
40638
40948
  });
40639
40949
  }
40640
40950
  );
@@ -40643,7 +40953,7 @@ function registerBookingTools(server2) {
40643
40953
  server2.registerTool(
40644
40954
  "kirjapro_book_sale",
40645
40955
  {
40646
- description: "Book a sale (myynti) with automatic VAT splitting. Payment types: invoice (default, D Myyntisaamiset), cash (D bank), prepaid.",
40956
+ description: "Book a sale (myynti) with automatic VAT splitting for a company that is not VAT-registered. Payment types: invoice (default, D Myyntisaamiset), cash (D bank), prepaid. A VAT-registered (ALV) company is refused: its sales reach the ALV report only as invoices (kirjapro_create_invoice + kirjapro_send_invoice).",
40647
40957
  inputSchema: {
40648
40958
  amount: external_exports.number().finite().positive().max(1e9).describe("Gross EUR amount (VAT-inclusive)"),
40649
40959
  description: external_exports.string().min(1).max(500).describe('Transaction description (e.g. "Konsultointi Acme Oy, 40h \xD7 95 \u20AC")'),
@@ -40660,6 +40970,9 @@ function registerBookingTools(server2) {
40660
40970
  annotations: WRITE_ANNOTATIONS
40661
40971
  },
40662
40972
  async (params) => {
40973
+ const companyVat = await readCompanyVat(getSupabase(), getCompanyId());
40974
+ if ("isError" in companyVat) return companyVat;
40975
+ if (companyVat.alvRegistered) return toErrorContent(ALV_REGISTERED_SALE_REFUSAL);
40663
40976
  const rate = Number.parseFloat(params.vatRate);
40664
40977
  const gross = roundCents(params.amount);
40665
40978
  let entries;
@@ -42187,7 +42500,7 @@ var VAT_COMPLIANCE = {
42187
42500
  ],
42188
42501
  booking: "4 entries: D expense + D ALV-saamiset + C bank + C ALV-velka (k\xE4\xE4nnetty)",
42189
42502
  note: "Net VAT effect is zero but BOTH must be reported on ALV-ilmoitus. Invoice must NOT show VAT rate or VAT amount",
42190
- omavero_fields: "Foreign: 305 (EU goods), 306 (EU services). Domestic construction/scrap: 318 (VAT amount), 319/320 (tax bases)"
42503
+ omavero_fields: "EU goods: 305 (VAT) / 313 (base); EU services: 306 / 314; services from outside the EU: VAT in 301-303 by rate (no base field); imports: 304 / 310; domestic construction/scrap purchases: 318 / 320, sales 319. The deductible part of each is in 307."
42191
42504
  },
42192
42505
  common_mistakes: [
42193
42506
  "Food/coffee at 25.5% \u2014 should be 13.5% (AVL 85\xA7)",
@@ -42266,14 +42579,18 @@ var FILING_DEADLINES = {
42266
42579
  },
42267
42580
  maksuperusteinen_alv: {
42268
42581
  threshold: 5e5,
42269
- note: "Revenue under 500,000 \u20AC: may opt for cash-basis VAT (report when payment received/made, not when invoiced)",
42270
- application: "Opt-in via OmaVero"
42582
+ note: "Turnover up to 500,000 \u20AC per calendar year: domestic sales VAT may be reported when payment is received and purchase VAT deducted when paid (cash basis), consistently for all domestic sales and purchases; imports, exports, intra-EU sales and acquisitions and cross-border reverse-charge services stay on the accrual basis. Kirjapro reports on the accrual basis only.",
42583
+ application: "No application: the business applies the cash basis in its bookkeeping. Since 2025 the VAT return has no cash-basis field.",
42584
+ source: "https://www.vero.fi/yritykset-ja-yhteisot/verot-ja-maksut/arvonlisaverotus/pienyrityksen-maksuperusteinen-alv/",
42585
+ sources_verified_at: "2026-10-02"
42271
42586
  },
42272
42587
  late_penalties: {
42273
- myohastymismaksu: "3 \u20AC/day for days 1-45 (max 135 \u20AC), then +2% of tax amount (max 15,000 \u20AC per tax type)",
42588
+ myohastymismaksu: "3 \u20AC/day for at most 45 days (135 \u20AC); more than 45 days late: 135 \u20AC + 2 % of the declared payable tax (max 15,000 \u20AC per tax type and period); also for returns with no tax payable",
42274
42589
  viivastyskorko_2026: "9.5% for most taxes, 4.5% for inheritance tax",
42275
- correction_grace: "Corrections within 45 days of original deadline: no penalty if change \u2264 500 \u20AC",
42276
- nil_return: "Even when no VAT activity occurred, a nil return must be filed for each verokausi"
42590
+ correction_grace: "A correction return filed within 45 days of the original due date carries no late-filing fee (no amount condition). Later corrections: 2 % of the additional payable tax.",
42591
+ nil_return: "Even when no VAT activity occurred, a nil return must be filed for each verokausi",
42592
+ source: "https://www.vero.fi/syventavat-vero-ohjeet/ohje-hakusivu/48377/seuraamusmaksut-oma-aloitteisessa-verotuksessa5/",
42593
+ sources_verified_at: "2026-10-02"
42277
42594
  },
42278
42595
  payment_deadline: "Same as filing deadline (12th of M+2 for monthly/quarterly, last day of Feb for annual)",
42279
42596
  omavero_field_codes: {
@@ -42291,16 +42608,17 @@ var FILING_DEADLINES = {
42291
42608
  318: "Vero rakentamispalvelun ja metalliromun ostoista (construction/scrap reverse charge VAT)"
42292
42609
  },
42293
42610
  tax_bases: {
42294
- 309: "0-verokannan alainen liikevaihto (zero-rate turnover: exports, EU sales with deduction right)",
42611
+ 309: "0-verokannan alainen liikevaihto (zero-rate turnover, e.g. exports of goods outside the EU, AVL 55-72 h \xA7). Does NOT include EU goods sales (311) or EU service sales (312). Exempt sales without a deduction right (healthcare, education, finance, insurance) are reported in no field.",
42295
42612
  310: "Tavaroiden maahantuonnit EU:n ulkopuolelta (goods imports from outside EU)",
42296
- 311: "Tavaroiden myynnit muihin EU-maihin (goods sales to EU)",
42613
+ 311: "Tavaroiden myynnit muihin EU-maihin (goods sales to EU; reported for the delivery month when invoiced on or after the delivery in that month, otherwise, including an advance invoice, the next month, AVL 138 a \xA7)",
42297
42614
  312: "Palveluiden myynnit muihin EU-maihin (service sales to EU)",
42298
42615
  313: "Tavaraostot muista EU-maista (goods purchases from EU)",
42299
42616
  314: "Palveluostot muista EU-maista (service purchases from EU)",
42300
42617
  319: "Rakentamispalvelun ja metalliromun myynnit (construction/scrap sales)",
42301
42618
  320: "Rakentamispalvelun ja metalliromun ostot (construction/scrap purchases)"
42302
42619
  },
42303
- removed_2025: "315, 316, 317 (alarajahuojennus fields) removed from 1.1.2025"
42620
+ removed_2025: "315, 316, 317 (alarajahuojennus fields) removed from 1.1.2025",
42621
+ sources_verified_at: "2026-10-02"
42304
42622
  }
42305
42623
  };
42306
42624
  var SOCIAL_INSURANCE_2026 = {
@@ -42581,7 +42899,7 @@ Do not invent a "closest match" tool for these tasks. A confidently wrong action
42581
42899
  A bank transaction that is already booked (a receipt or purchase booked as paid from the bank account, a manual invoice payment, a paid purchase invoice, a Kirjapro payment batch) is linked, never posted again: \`kirjapro_list_bank_link_candidates\` \u2192 \`kirjapro_link_bank_transaction\`. Never post a manual voucher for an already-booked or skipped bank transaction; a wrong reconciliation is undone in the Kirjapro app (Pankki \u2192 T\xE4sm\xE4ytys \u2192 T\xE4sm\xE4ytetyt \u2192 Pura t\xE4sm\xE4ytys) or, on the full surface, with \`kirjapro_unreconcile_bank_transaction\`, and then matched again.
42582
42900
 
42583
42901
  ### Reports
42584
- \`kirjapro_get_dashboard\` (overview) | \`kirjapro_get_income_statement\` (tuloslaskelma) | \`kirjapro_get_balance_sheet\` (tase \u2014 \`equity.synthetic_tilikauden_tulos\` is the unclosed P/L residual already included in equity; never add it again. The cumulative report groups current earnings under 3600 and retained earnings under 3500. Report rows can differ from raw ledger account balances. Do not present book equity as an approved distribution limit) | \`kirjapro_get_ar_aging\` (bounded myyntisaamisten aggregate; weighted age is not revenue-based DSO) | \`kirjapro_get_alv_summary\` (ALV-yhteenveto with OmaVero field codes 301-320).
42902
+ \`kirjapro_get_dashboard\` (overview) | \`kirjapro_get_income_statement\` (tuloslaskelma) | \`kirjapro_get_balance_sheet\` (tase \u2014 \`equity.synthetic_tilikauden_tulos\` is the unclosed P/L residual already included in equity; never add it again. The cumulative report groups current earnings under 3600 and retained earnings under 3500. Report rows can differ from raw ledger account balances. Do not present book equity as an approved distribution limit) | \`kirjapro_get_ar_aging\` (bounded myyntisaamisten aggregate; weighted age is not revenue-based DSO) | \`kirjapro_get_alv_summary\` (ALV-yhteenveto; the official OmaVero fields are in \`omavero_fields\`).
42585
42903
 
42586
42904
  ## Safety Rules
42587
42905
 
@@ -42647,7 +42965,7 @@ var INSTRUCTIONS_CORE_ADDENDUM = `
42647
42965
  The following workflows are NOT exposed in the current (core) surface. When a user asks for them, redirect to the web UI rather than reaching for the closest available tool:
42648
42966
 
42649
42967
  - **Recurring invoice templates** \u2014 created and managed in the web app; cron generates the issued invoices
42650
- - **Booking shortcuts** (book_purchase / book_sale / book_vat_settlement) \u2014 for these, you CAN compose the journal entries manually via \`kirjapro_create_transaction\` with explicit debit/credit lines if the user prefers; otherwise redirect
42968
+ - **Booking shortcuts** (book_purchase / book_sale / book_vat_settlement) \u2014 for these, you CAN compose the journal entries manually via \`kirjapro_create_transaction\` with explicit debit/credit lines if the user prefers; otherwise redirect. Exception: a VAT-registered (ALV) company books sales only as invoices (\`kirjapro_create_invoice\` + \`kirjapro_send_invoice\`) and reverse-charge purchases and purchases with deductible VAT in the web app (a free 2320 line on a voucher has no VAT fact and does not reach field 307); the database refuses its vouchers with a net credit on sales accounts 5000-5499, a sales-account line together with a 4210 or 2320 line, or a net 4210 credit; moving an amount out of a sales account without a VAT line (e.g. a fixed-asset disposal D 5010 / K 1600) is allowed
42651
42969
  - **Payroll (palkanlaskenta)** \u2014 Kirjapro does NOT calculate or book payroll. Direct the user to Palkkaus.fi or Palkka.fi for the calculation, then bring the resulting payslip back and book it via \`kirjapro_create_transaction\` with explicit debit/credit lines
42652
42970
  - **Transaction templates** \u2014 manual journal entries via \`kirjapro_create_transaction\`
42653
42971
  - **Manual offer status changes** \u2014 the customer's email-link click sets status server-side; just wait for the user to confirm acceptance
@@ -42666,10 +42984,10 @@ var INSTRUCTIONS_FULL_ADDENDUM = `
42666
42984
 
42667
42985
  ### Booking Shortcuts
42668
42986
  Faster than composing journal entries via \`kirjapro_create_transaction\`:
42669
- - \`kirjapro_book_sale\` / \`kirjapro_book_purchase\` \u2014 one-liner with auto VAT splitting
42670
- - \`kirjapro_book_vat_settlement\` \u2014 reads ALV-saamiset/ALV-velka, creates settlement entry. Call \`kirjapro_get_alv_summary\` first to preview.
42987
+ - \`kirjapro_book_sale\` / \`kirjapro_book_purchase\` \u2014 one-liner with auto VAT splitting. A VAT-registered (ALV) company books sales as invoices (\`kirjapro_create_invoice\` + \`kirjapro_send_invoice\`): \`kirjapro_book_sale\` and \`kirjapro_book_purchase\` with \`reverseCharge\` are refused for it
42988
+ - \`kirjapro_book_vat_settlement\` \u2014 settles the company's VAT period from ALV-saamiset/ALV-velka. Call \`kirjapro_get_alv_summary\` for the period first to preview the net and its reconciliation.
42671
42989
  - **Payroll is intentionally NOT exposed.** Direct users to Palkkaus.fi / Palkka.fi for salary calculation; the payslip can then be booked manually via \`kirjapro_create_transaction\`.
42672
- - \`kirjapro_list_templates\` \u2192 \`kirjapro_create_from_template\` for common patterns (13 built-in templates)
42990
+ - \`kirjapro_list_templates\` \u2192 \`kirjapro_create_from_template\` for common patterns (built-in templates; the sale templates are hidden and refused for a VAT-registered company)
42673
42991
 
42674
42992
  ### Manual Offer Status
42675
42993
  - \`kirjapro_update_offer_status\` \u2014 manual override; usually the customer's email-link click handles accept/reject server-side
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kirjapro/mcp-server",
3
- "version": "0.9.1",
3
+ "version": "0.11.0",
4
4
  "description": "MCP server for Finnish accounting — exposes Kirjapro bookkeeping, invoicing, and reporting tools to Claude, ChatGPT, Mistral, and other MCP clients.",
5
5
  "type": "module",
6
6
  "bin": {
package/server.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@kirjapro/mcp-server",
3
3
  "displayName": "Kirjapro - Finnish Accounting",
4
4
  "description": "Finnish double-entry bookkeeping, invoicing, and financial reporting for growing businesses. Create transactions, generate invoices with VAT calculation, produce income statements and balance sheets, and validate Finnish business identifiers.",
5
- "version": "0.9.1",
5
+ "version": "0.11.0",
6
6
  "homepage": "https://kirjapro.fi",
7
7
  "repository": "https://github.com/Jaollo/kirja-pro",
8
8
  "license": "MIT",
@@ -2,15 +2,15 @@
2
2
  "fingerprints": [
3
3
  {
4
4
  "name": "kirjapro_book_purchase",
5
- "hash": "21897b2fd6bd8809"
5
+ "hash": "e6c7bfe42eedc619"
6
6
  },
7
7
  {
8
8
  "name": "kirjapro_book_sale",
9
- "hash": "a7ad4daef0e9947b"
9
+ "hash": "6868bdf46073f442"
10
10
  },
11
11
  {
12
12
  "name": "kirjapro_book_vat_settlement",
13
- "hash": "48ab541acbce9e66"
13
+ "hash": "39f807857fb81e5d"
14
14
  },
15
15
  {
16
16
  "name": "kirjapro_convert_offer_to_invoice",
@@ -22,11 +22,11 @@
22
22
  },
23
23
  {
24
24
  "name": "kirjapro_create_from_template",
25
- "hash": "62d7241dbdba511c"
25
+ "hash": "d410e01e95e20227"
26
26
  },
27
27
  {
28
28
  "name": "kirjapro_create_from_template_batch",
29
- "hash": "f10cad13dc519c7c"
29
+ "hash": "ff8fa037077fac9a"
30
30
  },
31
31
  {
32
32
  "name": "kirjapro_create_invoice",
@@ -42,11 +42,11 @@
42
42
  },
43
43
  {
44
44
  "name": "kirjapro_create_transaction",
45
- "hash": "88b052da9babfc50"
45
+ "hash": "8b6a32983de69413"
46
46
  },
47
47
  {
48
48
  "name": "kirjapro_create_transactions_batch",
49
- "hash": "da853c41adf6e4dd"
49
+ "hash": "a3e71d41bbf437e4"
50
50
  },
51
51
  {
52
52
  "name": "kirjapro_delete_invoice",
@@ -66,7 +66,7 @@
66
66
  },
67
67
  {
68
68
  "name": "kirjapro_get_alv_summary",
69
- "hash": "139628cea5a3df31"
69
+ "hash": "a149fdcb6307923a"
70
70
  },
71
71
  {
72
72
  "name": "kirjapro_get_ar_aging",
@@ -162,7 +162,7 @@
162
162
  },
163
163
  {
164
164
  "name": "kirjapro_list_templates",
165
- "hash": "e07b6d21afbccce9"
165
+ "hash": "91c70a7143aa96ac"
166
166
  },
167
167
  {
168
168
  "name": "kirjapro_list_transactions",