diff --git a/CHANGELOG.md b/CHANGELOG.md index 25680d3..d36b77f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,19 @@ folgen [Semantic Versioning](https://semver.org/lang/de/). Die Version steht in ## [Unveröffentlicht] -## [0.6.0] - 2026-10-11 +## [0.7.0] - 2026-10-12 ### Hinzugefügt - `column -d Spalte ...` (und `project -d`) löscht Spalten: alle anderen bleiben in ihrer Reihenfolge. Fehler bei unbekannter Spalte oder wenn alle gelöscht würden. Dazu Abschnitt in `man/column.man`, Beispiel in `testall.cmds` und Tests. +- Kantonstarife 2026 für `tax`: `demo/taxCH_AI2026`, `taxCH_AR2026`, `taxCH_SG2026`, `taxCH_GR2026` (Präfix `taxCH_` für die Schweiz; einfache Staatssteuer für Alleinstehende, vor Steuerfuss, Gemeinde und Kirche), erzeugt mit `tools/gen_tax_tables.rb`. Mit Tests, Beispiel in `testall.cmds` und Erklärung zum Steuerfuss im README. + +### Behoben +- `rdb man dashline` (und `headline`, `backup`, `dbase`, `accounting_ter`) zeigte als Überschrift eine Makrozeile (`dashline -- .PP`). Themen-Seiten ohne Beschreibung nehmen jetzt den Titel aus der `.TH`-Zeile. +- `splittable -0` und mehr als 676 Stücke endeten mit einem Ruby-Abbruch (im zweiten Fall nach 676 geschriebenen Dateien). Jetzt eine Fehlermeldung, und es wird vorher geprüft, damit nichts halb geschrieben wird. + +## [0.6.0] - 2026-10-11 + +### Hinzugefügt - Steuertabellen 2026 für `tax`: `demo/taxDE2026`, `taxAT2026`, `taxCH2026` (nur Bundessteuer), `taxLI2026` (nur Landessteuer), erzeugt mit `tools/gen_tax_tables.rb`, mit Tests. ### Geändert @@ -18,8 +27,6 @@ folgen [Semantic Versioning](https://semver.org/lang/de/). Die Version steht in - README und `man/row.man` erklären, warum `select` in bash/zsh/ksh einen Syntaxfehler gibt und wie man es trotzdem aufruft. ### Behoben -- `rdb man dashline` (und `headline`, `backup`, `dbase`, `accounting_ter`) zeigte als Überschrift eine Makrozeile (`dashline -- .PP`). Themen-Seiten ohne Beschreibung nehmen jetzt den Titel aus der `.TH`-Zeile. -- `splittable -0` und mehr als 676 Stücke endeten mit einem Ruby-Abbruch (im zweiten Fall nach 676 geschriebenen Dateien). Jetzt eine Fehlermeldung, und es wird vorher geprüft, damit nichts halb geschrieben wird. - `rdb man` zeigte `\\select` in den Man-Pages als `\elect`: ein doppelter Backslash wird jetzt zuerst geschützt. ## [0.5.0] - 2026-10-11 diff --git a/README.md b/README.md index 180b910..d4b738b 100644 --- a/README.md +++ b/README.md @@ -130,6 +130,10 @@ Alleinstehende (Grundtarif). Erzeugt mit `ruby tools/gen_tax_tables.rb`. | `taxAT2026` | Österreich, Einkommensteuer | § 33 EStG (0 % bis 13 539 €, dann 20/30/40/48/50/55 %) | exakt, vor Absetzbeträgen | | `taxCH2026` | Schweiz, **nur direkte Bundessteuer** | Art. 36 DBG, ESTV-Rundschreiben 2-215-D-2025 | Stufen exakt, das Gesetz rechnet in 100-Franken-Schritten | | `taxLI2026` | Liechtenstein, **nur Landessteuer** | Art. 19 SteG (LR 640.0, Fassung 01.07.2026) | exakt | +| `taxCH_AI2026` | Kanton Appenzell I.Rh., einfache Staatssteuer | Art. 38 StG (ESTV-Kantonsblatt) | exakt | +| `taxCH_AR2026` | Kanton Appenzell A.Rh., einfache Staatssteuer (Alleinstehendentarif) | Art. 39 StG, Beträge ab 2024 | exakt | +| `taxCH_SG2026` | Kanton St. Gallen, einfache Staatssteuer | Art. 50 StG, Beträge ab 2026 | exakt | +| `taxCH_GR2026` | Kanton Graubünden, einfache Kantonssteuer | Art. 39 StG, indexierte Beträge ab 2025 | exakt | ``` tax 60000 < demo/taxDE2026 14233 @@ -143,6 +147,20 @@ Kirchensteuer und Soli sind nicht enthalten. Für die Schweiz kommen Kanton und höher als der Bund), für Liechtenstein der Gemeindezuschlag: gesamt = Landessteuer × (1 + Zuschlag/100). Ehepaar- und Alleinerziehendentarife fehlen. Das ist keine Steuerberatung. +**Kantone (AI, AR, SG, GR):** Die Dateien tragen den Präfix `taxCH_` (Schweiz) vor dem Kantonskürzel; `taxCH2026` ist die Bundessteuer. Die Tabellen geben die *einfache Steuer* wieder. Sie wird mit dem Steuerfuss multipliziert, +und Gemeinde und Kirche kommen dazu. Der Kanton setzt den Steuerfuss pro Jahr fest: + +| Kanton | Steuerfuss Kanton 2026 | Quelle | +|---|---|---| +| AI | 96 % | Kantonsblatt AI (Grossratsbeschluss 1.12.2025) | +| SG | 105 % | Budgetbeschluss des Kantonsrats (Websuche, nicht im Kantonsblatt) | +| GR | 92 % | Grosser Rat, Budget 2026 (Websuche) | +| AR | nicht ermittelt | AR rechnet in Steuereinheiten, die Zahl für 2026 habe ich nicht gefunden | + +Beispiel AI bei 60 000: `tax 60000 < demo/taxCH_AI2026` = 3770, mal 0,96 = 3619 (Kantonssteuer, ohne Bezirk, Gemeinde und Kirche). +Oberhalb einer Grenze gilt bei allen vier Kantonen ein fester Satz auf das *ganze* Einkommen (AI ab 200 000 mit 8 %, +AR ab 260 800 mit 2,6 %, SG ab 264 500 mit 8,5 %, GR ab 780 440 mit 11 %); in der Tabelle ist das die letzte Zeile. + ## Befehle | Gruppe | Befehle | diff --git a/demo/taxCH_AI2026 b/demo/taxCH_AI2026 new file mode 100644 index 0000000..c2936b6 --- /dev/null +++ b/demo/taxCH_AI2026 @@ -0,0 +1,16 @@ +income tax percent +------ --- ------- +0 0.00 0 +3000 0.00 1 +6000 30.00 2 +9000 90.00 3 +12000 180.00 4 +15000 300.00 5 +18000 450.00 6 +22000 690.00 7 +26000 970.00 7.5 +30000 1270.00 8 +40000 2070.00 8.5 +74000 4960.00 9 +140000 10900.00 8.5 +200000 16000.00 8 diff --git a/demo/taxCH_AR2026 b/demo/taxCH_AR2026 new file mode 100644 index 0000000..ae6c5cd --- /dev/null +++ b/demo/taxCH_AR2026 @@ -0,0 +1,14 @@ +income tax percent +------ --- ------- +0 0.00 0 +8300 0.00 0.6 +9900 9.60 1 +11500 25.60 1.5 +15700 88.60 1.8 +27200 295.60 2.2 +41800 616.80 2.4 +54300 916.80 2.6 +74100 1431.60 2.7 +88700 1825.80 2.8 +125200 2847.80 2.9 +260800 6780.80 2.6 diff --git a/demo/taxCH_GR2026 b/demo/taxCH_GR2026 new file mode 100644 index 0000000..0f59944 --- /dev/null +++ b/demo/taxCH_GR2026 @@ -0,0 +1,21 @@ +income tax percent +------ --- ------- +0 0.00 0 +16895 0.00 2.5 +17985 27.25 4 +19075 70.85 5 +20165 125.35 6 +21255 190.75 6.5 +22345 261.60 7 +24525 414.20 8 +31065 937.40 8.5 +35425 1308.00 9 +39785 1700.40 9.5 +44145 2114.60 10.3 +65945 4360.00 10.6 +87745 6670.80 10.7 +109545 9003.40 11.2 +218545 21211.40 11.3 +327545 33528.40 11.4 +436545 45954.40 11.6 +780440 85848.40 11 diff --git a/demo/taxCH_SG2026 b/demo/taxCH_SG2026 new file mode 100644 index 0000000..bac3d85 --- /dev/null +++ b/demo/taxCH_SG2026 @@ -0,0 +1,9 @@ +income tax percent +------ --- ------- +0 0.00 0 +11600 0.00 4 +15800 168.00 6 +33800 1248.00 8 +60300 3368.00 9.2 +98400 6873.20 9.4 +264500 22482.50 8.5 diff --git a/demo/testall.cmds b/demo/testall.cmds index fad1f19..58c481c 100644 --- a/demo/testall.cmds +++ b/demo/testall.cmds @@ -238,6 +238,11 @@ tax 60000 < taxDE2026 tax 60000 < taxAT2026 tax 60000 < taxCH2026 tax 60000 < taxLI2026 +## Swiss cantons (einfache Staatssteuer, single; still times the cantonal Steuerfuss, plus Gemeinde): AI AR SG GR +tax 60000 < taxCH_AI2026 +tax 60000 < taxCH_AR2026 +tax 60000 < taxCH_SG2026 +tax 60000 < taxCH_GR2026 ## explode: parts list -> all subparts with counts (part, amount) explode 10001 2 parttable ## chartdup: look for duplicate account numbers and short names in chart diff --git a/demo/testall.old b/demo/testall.old index c032c61..5cde987 100644 --- a/demo/testall.old +++ b/demo/testall.old @@ -880,7 +880,7 @@ Item Amount Cost Value Description ## tabletom4 / tabletosed / tabletostruct were shown above ## ===== Help and the terminal ===== $ rdb --version -rdb 0.6.0 +rdb 0.7.0 $ howmany 136 $ whatis row @@ -980,6 +980,15 @@ $ tax 60000 < taxCH2026 671 $ tax 60000 < taxLI2026 1396 +## Swiss cantons (einfache Staatssteuer, single; still times the cantonal Steuerfuss, plus Gemeinde): AI AR SG GR +$ tax 60000 < taxCH_AI2026 + 3770 +$ tax 60000 < taxCH_AR2026 + 1065 +$ tax 60000 < taxCH_SG2026 + 3344 +$ tax 60000 < taxCH_GR2026 + 3748 ## explode: parts list -> all subparts with counts (part, amount) $ explode 10001 2 parttable Part Amount diff --git a/lib/rdb/version.rb b/lib/rdb/version.rb index 78388fc..6b07dd5 100644 --- a/lib/rdb/version.rb +++ b/lib/rdb/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module Rdb - VERSION = '0.6.0' + VERSION = '0.7.0' end diff --git a/test/rdb_test.rb b/test/rdb_test.rb index cc665e3..f77f6b4 100644 --- a/test/rdb_test.rb +++ b/test/rdb_test.rb @@ -812,6 +812,19 @@ class TaxTablesTest < RdbCase assert_equal 0, tax(15_855, 'taxLI2026') end + def test_swiss_cantons_2026_einfache_staatssteuer + assert_equal 3770, tax(60_000, 'taxCH_AI2026') # 2 070 + 8,5 % of 20 000 + assert_equal 16_000, tax(200_000, 'taxCH_AI2026') + assert_equal 24_000, tax(300_000, 'taxCH_AI2026') # 8 % of the whole income + assert_equal 1065, tax(60_000, 'taxCH_AR2026') + assert_equal 3344, tax(60_000, 'taxCH_SG2026') # 1 248 + 8 % + 9,2 % pieces + assert_equal 25_500, tax(300_000, 'taxCH_SG2026') # 8,5 % of the whole income + assert_equal 3748, tax(60_000, 'taxCH_GR2026') + assert_equal 0, tax(16_895, 'taxCH_GR2026') + assert_equal 20, tax(5_000, 'taxCH_AI2026') # 1 % of 2 000 + %w[AR SG GR].each { |c| assert_equal 0, tax(5_000, "taxCH_#{c}2026") } + end + # § 32a EStG: the table is a straight-line approximation of the curved zones def test_de_2026_follows_the_formula [12_348, 14_000, 17_799, 20_000, 33_333, 50_000, 69_878, 100_000, 277_825, 300_000].each do |x| diff --git a/tools/gen_tax_tables.rb b/tools/gen_tax_tables.rb index 2645d47..8a4204a 100644 --- a/tools/gen_tax_tables.rb +++ b/tools/gen_tax_tables.rb @@ -61,3 +61,34 @@ li_rates.each do |limit, rate| prev = [limit, rate] end write_table('taxLI2026', pts) + +# --- Swiss cantons: the "einfache Staatssteuer" for single persons, before the Steuerfuss and +# without municipal and church tax. Source: ESTV Kantonsblätter (Steuerinformationen 2026). +# Each tariff is a list of [width, rate] pieces; above the last piece one flat rate applies to +# the whole income (the cantons call this "für das ganze Einkommen"). +def staircase(pieces, flat_rate, expect_limit) + acc = 0.0 + start = 0 + pts = [] + pieces.each do |width, rate| + pts << [start, acc, rate] + acc += width * rate / 100.0 + start += width + end + raise "limit #{start} != #{expect_limit}" unless start == expect_limit + + pts << [start, start * flat_rate / 100.0, flat_rate] +end + +# AI: Art. 38 StG +write_table('taxCH_AI2026', staircase([[3_000, 0], [3_000, 1], [3_000, 2], [3_000, 3], [3_000, 4], [3_000, 5], [4_000, 6], + [4_000, 7], [4_000, 7.5], [10_000, 8], [34_000, 8.5], [66_000, 9], [60_000, 8.5]], 8, 200_000)) +# AR: Art. 39 Abs. 1 lit. b StG (Alleinstehendentarif, Beträge ab Steuerperiode 2024); multiplied by the Steuereinheiten +write_table('taxCH_AR2026', staircase([[8_300, 0], [1_600, 0.6], [1_600, 1.0], [4_200, 1.5], [11_500, 1.8], [14_600, 2.2], + [12_500, 2.4], [19_800, 2.6], [14_600, 2.7], [36_500, 2.8], [135_600, 2.9]], 2.6, 260_800)) +# SG: Art. 50 StG (Beträge ab Steuerperiode 2026) +write_table('taxCH_SG2026', staircase([[11_600, 0], [4_200, 4], [18_000, 6], [26_500, 8], [38_100, 9.2], [166_100, 9.4]], 8.5, 264_500)) +# GR: Art. 39 Abs. 1 StG (indexierte Beträge ab Steuerperiode 2025) +write_table('taxCH_GR2026', staircase([[16_895, 0], [1_090, 2.5], [1_090, 4], [1_090, 5], [1_090, 6], [1_090, 6.5], [2_180, 7], + [6_540, 8], [4_360, 8.5], [4_360, 9], [4_360, 9.5], [21_800, 10.3], [21_800, 10.6], + [21_800, 10.7], [109_000, 11.2], [109_000, 11.3], [109_000, 11.4], [343_895, 11.6]], 11.0, 780_440))