From 208582b2265ecbbab99eabf859a79728726bc863 Mon Sep 17 00:00:00 2001 From: Urs Joss Date: Tue, 28 Jan 2025 13:34:31 +0100 Subject: [PATCH] spike: Process monthly closing from Banana (#300) Allowes us to print the output of the monthly closing data (export as xls from banana) compared to the budget to the console. Bare minimum. Extended testing deferred, automatic export of an Excel deferred, processing monthly slices deferred. --- Cargo.lock | 38 + Cargo.toml | 2 + samples/budget.toml | 296 + samples/konten_202412_20250128132200.xls | 7235 ++++++++++++++++++++++ src/close.rs | 32 + src/close/close_xml.rs | 473 ++ src/main.rs | 55 +- 7 files changed, 8124 insertions(+), 7 deletions(-) create mode 100644 samples/budget.toml create mode 100644 samples/konten_202412_20250128132200.xls create mode 100644 src/close.rs create mode 100644 src/close/close_xml.rs diff --git a/Cargo.lock b/Cargo.lock index 2ec3695..de3d9c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -793,10 +793,12 @@ dependencies = [ "clap", "polars", "pretty_assertions", + "quick-xml", "rstest", "serde", "strum", "strum_macros", + "toml", ] [[package]] @@ -1531,6 +1533,16 @@ dependencies = [ "cc", ] +[[package]] +name = "quick-xml" +version = "0.37.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "165859e9e55f79d67b96c5d96f4e88b6f2695a1972849c15a6a3f5c59fc2c003" +dependencies = [ + "memchr", + "serde", +] + [[package]] name = "quote" version = "1.0.38" @@ -1775,6 +1787,15 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "shlex" version = "1.3.0" @@ -1988,11 +2009,26 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + [[package]] name = "toml_datetime" version = "0.6.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] [[package]] name = "toml_edit" @@ -2001,6 +2037,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" dependencies = [ "indexmap", + "serde", + "serde_spanned", "toml_datetime", "winnow", ] diff --git a/Cargo.toml b/Cargo.toml index 448779d..a5bfcfa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ serde = { version = "1.0.200", features = ["derive"] } chrono= { version = "0.4.38", features = ["serde"] } strum = { version = "0.26.2" } strum_macros = { version = "0.26.2" } +quick-xml = { version = "0.37.1", features = ["serde"] } +toml = "0.8.19" [dependencies.polars] version = "0.46.0" diff --git a/samples/budget.toml b/samples/budget.toml new file mode 100644 index 0000000..2f4df58 --- /dev/null +++ b/samples/budget.toml @@ -0,0 +1,296 @@ +name = "LoLa Budget" + +[post_groups] + +[post_groups.revenue] +name = "Ertrag" +posts = ["E30", "E31", "E320", "E321", "E33", "E360", "E365", "E38"] + +[post_groups.costs_direct] +name = "Direkter Aufwand" +posts = ["A40", "A41", "A42", "AXX"] + +[post_groups.costs_staff] +name = "Personal" +posts = ["A520", "A521", "A57", "A590", "A599"] + +[post_groups.costs_remaining] +name = "Überiger Aufwand" +posts = ["A60A", "A60B", "A63", "A65X", "A651", "A66", "A68", "A69"] + +[post_groups.costs_extra] +name = "Ausserordentlicher Ertrag" +posts = ["RX", "EX", "AX"] + +[posts] + +[posts.E30] +name = "Ertrag Restauration" +sort = 1 +factor = -1 +account_codes = [ + "30100", + "30200", + "30500", + "30600", + "30700", + "30800", + "30810", + "30900", +] + +[posts.E31] +name = "Ertrag Vermietungen" +sort = 2 +factor = -1 +account_codes = ["31000", "31100"] + +[posts.E320] +name = "Kulturelle Einnahmen" +sort = 3 +factor = -1 +account_codes = ["32000"] + +[posts.E321] +name = "Projektertrag / Diverse Erträge" +sort = 4 +factor = -1 + +account_codes = ["32100", "32900"] + +[posts.E33] +name = "Mitgliederbeiträge" +sort = 5 +factor = -1 +account_codes = ["35000"] + +[posts.E360] +name = "Spenden" +sort = 6 +factor = -1 +account_codes = ["36000", "36001"] + +[posts.E365] +name = "Projektbeiträge" +sort = 7 +factor = -1 +account_codes = [ + "32020", + "32030", + "32050", + "32092", + "32093", + "36500", + "36501", + "36590", +] + +[posts.E38] +name = "Subventionen" +sort = 8 +factor = -1 +account_codes = ["38000"] + +[posts.A40] +name = "Restaurationsaufwand" +sort = 9 +factor = 1 +account_codes = ["40000", "40060"] + +[posts.A41] +name = "Projektaufwand" +sort = 10 +factor = 1 +account_codes = [ + "41000", + "41001", + "41070", + "41080", + "41090", + "41091", + "41092", + "41093", +] + +[posts.A42] +name = "Kulturelle Kosten" +sort = 11 +factor = 1 +account_codes = ["42000", "42010", "42020", "42060", "42100"] + +[posts.AXX] +name = "Betriebsaufwand" +sort = 12 +factor = 1 +account_codes = ["46000", "61000", "61200", "61500", "64000", "64600"] + +[posts.A520] +name = "Monatslöhne" +sort = 13 +factor = 1 +account_codes = ["52000", "52002", "52040", "52050"] + +[posts.A521] +name = "Kleine Entgelte + Überstunden" +sort = 14 +factor = 1 +account_codes = ["52100"] + +[posts.A57] +name = "Sozialversicherungen" +sort = 15 +factor = 1 +account_codes = ["57000", "57100", "57200", "57300", "57400"] + +[posts.A590] +name = "Drittbeiträge an Lohnkosten" +sort = 16 +factor = 1 +account_codes = ["59000"] + +[posts.A599] +name = "übr. Pers. Aufwand/Supi" +sort = 17 +factor = 1 +account_codes = ["59500", "59990", "59991"] + +[posts.A60A] +name = "Raumaufwand" +sort = 18 +factor = 1 +account_codes = ["60000", "60100"] + +[posts.A60B] +name = "Hauswartung" +sort = 19 +factor = 1 +account_codes = ["60500", "60600"] + +[posts.A63] +name = "Versicherungen, Gebühren" +sort = 20 +factor = 1 +account_codes = ["63000", "63600"] + +[posts.A65X] +name = "Verwaltungskosten" +sort = 21 +factor = 1 +account_codes = ["65000", "65001", "65030", "65120", "65200", "65300", "65900"] + +[posts.A651] +name = "Telefon/Internet" +sort = 22 +factor = 1 +account_codes = ["65100"] + +[posts.A66] +name = "Öffentlichkeitsarbeit" +sort = 23 +factor = 1 +account_codes = ["66000", "66001", "66200", "66500"] + +[posts.A68] +name = "Finanzaufwand" +sort = 24 +factor = 1 +account_codes = ["68400", "68450"] + +[posts.A69] +name = "Abschreibungen" +sort = 25 +factor = 1 +account_codes = ["69000"] + +[posts.RX] +name = "Reserve" +sort = 26 +factor = -1 +account_codes = [] + +[posts.EX] +name = "Ertrag" +sort = 27 +factor = -1 +account_codes = ["80000"] + +[posts.AX] +name = "Aufwand" +sort = 28 +factor = 1 +account_codes = ["80100"] + + +[years] + +[years.2024] + +[years.2024.amounts] +E30 = 30.00 +E31 = 31.00 +E320 = 320.00 +E321 = 321.00 +E33 = 33.00 +E360 = 360.00 +E365 = 368.00 +E38 = 38.00 + +A40 = 40.00 +A41 = 41.00 +A42 = 42.00 +AXX = 100.00 + +A520 = 520.00 +A521 = 521.00 +A57 = 57.00 +A590 = -590.00 +A599 = 599.00 + +A60A = 60.10 +A60B = 60.20 +A63 = 63.00 +A65X = 650.00 +A651 = 651.00 +A66 = 66.00 +A68 = 68.00 +A69 = 69.00 + +RX = 1.0 +EX = 2.0 +AX = 3.0 + +[years.2025] + +[years.2025.amounts] +E30 = 30.01 +E31 = 31.01 +E320 = 320.01 +E321 = 321.01 +E33 = 33.01 +E360 = 360.01 +E365 = 368.01 +E38 = 38.01 + +A40 = 40.01 +A41 = 41.01 +A42 = 42.01 +AXX = 100.01 + +A520 = 520.01 +A521 = 521.01 +A57 = 57.01 +A590 = -590.01 +A599 = 599.01 + +A60A = 60.11 +A60B = 60.21 +A63 = 63.01 +A65X = 650.01 +A651 = 651.01 +A66 = 66.01 +A68 = 68.01 +A69 = 69.01 + +RX = 1.1 +EX = 2.1 +AX = 3.1 diff --git a/samples/konten_202412_20250128132200.xls b/samples/konten_202412_20250128132200.xls new file mode 100644 index 0000000..35ba324 --- /dev/null +++ b/samples/konten_202412_20250128132200.xls @@ -0,0 +1,7235 @@ + + + + + + + + + + +10.1.24.24275 +5 +6 +3 +4 +2 +7 +8 +9 +10 +11 +12 +61 +1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +TableInfo + +Zeile + +Fehler + +Einzig + +Grundzeile + +SysCod + +Links + +Section + +Group + +Account + +Description + +Notes + +Disable + +VatNumber + +FiscalNumber + +BClass + +Gr + +Gr1 + +Gr2 + +Opening + +Debit + +Credit + +Balance + +Budget + +BudgetDifference + +Prior + +PriorDifference + +BudgetPrior + +PeriodBegin + +PeriodDebit + +PeriodCredit + +PeriodTotal + +PeriodEnd + +Note + + + +Links zu externen Dokumenten + +Name der Gruppe + +Kontonummer + +Anmerkungen + +Konto oder Gruppe deaktivieren + +Basis Klasse + +Gruppe, worin diese Zeile summiert wird + +Eröffnungssaldo + +Total Bewegung Soll + +Total Bewegung Haben + +Saldo des Kontos + +Budget-Betrag + +Differenz Budget und Saldo + +Vorjahressaldo + +Differenz zwischen Vorjahr und Saldo + +Budget Vorjahr + +Anfangssaldo Periode + +Total Bewegung Soll-Periode + +Total Bewegung Haben Periode + +Total Bewegungen Ende Periode + +Saldo Ende Periode + +Note + + + +34 + +SysCode + +Links + +Sektion + +Gruppe + +Konto + +Beschreibung + +Anmerkungen + +Deaktivieren + +MwSt/USt-Nummer + +Steuernummer + +BKlasse + +Summ. in + +Gr1 + +Gr2 + +Eröffnung + +Soll + +Haben + +Saldo + +Budget + +Diff.Budget + +Vorjahr + +Diff.Vorjahr + +BudgetVorj. + +Per. Anfang + +Periode Soll + +Periode Haben + +Per.Total + +Per.Ende + +Note + + + +222 + +CHF + +CHF + +CHF + +CHF + +CHF + +CHF + +CHF + +CHF + +CHF + + + +Konten + +Zeile + +Fehler + +Einzig + +Grundzeile + +SysCode + +Links + +Sektion + +Gruppe + +Konto + +Beschreibung + +Anmerkungen + +Deaktivieren + +MwStUStNummer + +Steuernummer + +BKlasse + +Gr + +Gr1 + +Gr2 + +Eröffnung + +Soll + +Haben + +Saldo + +Budget + +BudgetDifferenz + +Vorjahr + +VorjahrDifferenz + +BudgetVorjahr + +PeriodeAnfang + +PeriodeSoll + +PeriodeHaben + +PeriodeTotal + +PeriodeEnde + +Note + + + +Accounts + +Row + +Error + +Unique + +Base Row + +SysCod + +Links + +Section + +Group + +Account + +Description + +Notes + +Disable + +VatNumber + +FiscalNumber + +BClass + +Gr + +Gr1 + +Gr2 + +Opening + +Debit + +Credit + +Balance + +Budget + +BudgetDifference + +Prior + +PriorDifference + +BudgetPrior + +PeriodBegin + +PeriodDebit + +PeriodCredit + +PeriodTotal + +PeriodEnd + +Note + + + + + + + + + + + +String + +String + +String + +String + +String + +String + +String + +Number + +String + +String + +String + +String + +String + +String + +Number + +Number + +Number + +Number + +Number + +Number + +Number + +Number + +Number + +Number + +Number + +Number + +Number + +Number + +String + + + +1 + +0 + +2 + +0 + +* + +BILANZ + + + +2 + +0 + +3 + +0 + + + +3 + +0 + +4 + +0 + +1 + +AKTIVEN + + + +4 + +0 + +5 + +0 + + + +5 + +0 + +6 + +0 + +10000 + +Kassenguthaben + +1 + +100 + +33.00 + +33.00 + + + +6 + +0 + +7 + +0 + +10100 + +PCK 40-2519-7 + +1 + +100 + +19.00 + +19.00 + + + +7 + +0 + +0 + +0 + +10101 + +Depositokonto + +1 + +100 + + + +8 + +0 + +8 + +0 + +10110 + +PCK Sumup + +1 + +100 + + + +9 + +0 + +276 + +0 + +10900 + +Durchlaufkonto Post, Kasse + +1 + +100 + + + +10 + +0 + +275 + +0 + +10910 + +Kassenkorrekturkonto + +1 + +100 + + + +11 + +0 + +274 + +0 + +10920 + +Durchlaufkonto Sumup + +1 + +100 + + + +12 + +0 + +273 + +0 + +10930 + +Durchlaufkonto MiTi + +1 + +100 + + + +13 + +0 + +272 + +0 + +11050 + +Debitoren (Offene Posten-Verwaltung) + +1 + +100 + + + +14 + +0 + +271 + +0 + +12000 + +Warenlager + +1 + +100 + + + +15 + +0 + +270 + +0 + +13000 + +Bezahlter Aufwand des Folgejahres (TA) + +1 + +100 + + + +16 + +0 + +269 + +0 + +13010 + +Noch nicht erhalteren Ertrag (TA) + +1 + +100 + + + +17 + +0 + +265 + +0 + + + +18 + +0 + +9 + +0 + +100 + +Flüssige Mittel + +10 + +52.00 + +52.00 + + + +19 + +0 + +10 + +0 + + + +20 + +0 + +44 + +0 + + + +21 + +0 + +45 + +0 + +10 + +Umlaufvermögen + +1 + +52.00 + +52.00 + + + +22 + +0 + +46 + +0 + + + +23 + +0 + +48 + +0 + +14400 + +Darlehen Zinslos + +1 + +140 + + + +24 + +0 + +50 + +0 + +140 + +Finanzanlagen + +14 + + + +25 + +0 + +51 + +0 + + + +26 + +0 + +0 + +0 + +15100 + +Geschäftsmobiliar + +1 + +150 + + + +27 + +0 + +57 + +0 + +15200 + +EDV + +1 + +150 + + + +28 + +0 + +62 + +0 + +150 + +Mobile Sachanlagen + +14 + + + +29 + +0 + +63 + +0 + + + +30 + +0 + +67 + +0 + +14 + +Anlagevermögen + +1 + + + +31 + +0 + +68 + +0 + + + +32 + +0 + +69 + +0 + +1 + +Total Aktiven + +00 + +52.00 + +52.00 + + + +33 + +0 + +70 + +0 + + + +34 + +0 + +71 + +0 + + + +35 + +0 + +72 + +0 + +2 + +PASSIVEN + + + +36 + +0 + +293 + +0 + + + +37 + +0 + +292 + +0 + +20050 + +Kreditoren + +2 + +200 + + + +38 + +0 + +291 + +0 + +20051 + +Kreditor Sumup Mittagstisch + +2 + +200 + + + +39 + +0 + +290 + +0 + +20119 + +Durchlaufkonto Löhne + +2 + +200 + + + +40 + +0 + +348 + +0 + +20120 + +Durchlaufkonto Spesen Praktikum + +2 + +200 + + + +41 + +0 + +289 + +0 + +23000 + +Noch nicht bezahlter Aufwand (TP) + +2 + +200 + + + +42 + +0 + +288 + +0 + +23010 + +Erhaltener Ertrag des Folgejahres (TP) + +2 + +200 + + + +43 + +0 + +287 + +0 + +23050 + +Schlüsseldepot + +2 + +200 + + + +44 + +0 + +286 + +0 + +23100 + +CMS Hauskonto + +2 + +200 + + + +45 + +0 + +285 + +0 + +23900 + +unklare Buchungen + +2 + +200 + + + +46 + +0 + +284 + +0 + + + +47 + +0 + +76 + +0 + +200 + +Fremdkapital Kurzfristig + +20 + + + +48 + +0 + +77 + +0 + + + +49 + +0 + +97 + +0 + +20 + +Kurzfristiges Fremdkapital + +2A + + + +50 + +0 + +98 + +0 + + + +51 + +0 + +111 + +0 + + + +52 + +0 + +112 + +0 + +24 + +Langfristiges Fremdkapital + +2A + + + +53 + +0 + +113 + +0 + + + +54 + +0 + +114 + +0 + +2A + +Fremdkapital + +2 + + + +55 + +0 + +115 + +0 + + + +56 + +0 + +116 + +0 + +28000 + +Vereinskapital + +2 + +28 + + + +57 + +0 + +117 + +0 + +29900 + +Gewinnvortrag/Verlustvortrag + +2 + +28 + + + +58 + +0 + +118 + +0 + +PROVISORISCHER ERFOLG + +2 + +28 + + + +59 + +0 + +119 + +0 + +297 + +Jahresgewinn oder Jahresverlust + +28 + +52.00 + +-52.00 + + + +60 + +0 + +120 + +0 + + + +61 + +0 + +121 + +0 + +28 + +Eigenkapital + +2 + +52.00 + +-52.00 + + + +62 + +0 + +122 + +0 + + + +63 + +0 + +123 + +0 + +2 + +Total Passiven + +00 + +52.00 + +-52.00 + + + +64 + +0 + +124 + +0 + + + +65 + +0 + +125 + +0 + + + +66 + +0 + +126 + +0 + +* + +ERFOLGSRECHNUNG + + + +67 + +0 + +127 + +0 + +4 + + + +68 + +0 + +128 + +0 + +30200 + +Ertrag Café + +4 + +30 + +22.00 + +-22.00 + + + +69 + +0 + +129 + +0 + +30500 + +Ertrag Mittagstisch + +4 + +30 + + + +70 + +0 + +130 + +0 + +30700 + +Getränke und Verpflegung bei Vermietung + +4 + +30 + + + +71 + +0 + +131 + +0 + +30810 + +Sommerfest + +4 + +30 + + + +72 + +0 + +132 + +0 + +30900 + +Ertrag Diverse + +4 + +30 + + + +73 + +0 + +136 + +0 + +30 + +Ertrag Restauration + +3 + +22.00 + +-22.00 + + + +74 + +0 + +296 + +0 + + + +75 + +0 + +0 + +0 + +31000 + +Vermietung LOLA + +4 + +31 + + + +76 + +0 + +137 + +0 + +31 + +Ertrag Vermietungen + +3 + + + +77 + +0 + +316 + +0 + + + +78 + +0 + +315 + +0 + +32000 + +Kulturelle Einnahmen + +4 + +32 + +24.00 + +-24.00 + + + +79 + +0 + +314 + +0 + +32020 + +Kulturertrag Sommerfest + +4 + +32 + +6.00 + +-6.00 + + + +80 + +0 + +313 + +0 + +32030 + +Projektertrag Nachhaltigkeitskonzept + +4 + +32 + + + +81 + +0 + +312 + +0 + +32050 + +Projektertrag Nachhaltigkeitsparcour + +4 + +32 + + + +82 + +0 + +343 + +0 + +32092 + +Projektertrag Balsam + +4 + +32 + + + +83 + +0 + +342 + +0 + +32093 + +Projektertrag Inklusiver Jugendclub + +4 + +32 + + + +84 + +0 + +311 + +0 + +32900 + +diverse erlöse + +4 + +32 + + + +85 + +0 + +309 + +0 + +32 + +Kulturertrag + +3 + +30.00 + +-30.00 + + + +86 + +0 + +308 + +0 + + + +87 + +0 + +307 + +0 + +35000 + +Mitgliederbeiträge + +4 + +35 + + + +88 + +0 + +306 + +0 + +35 + +Vereinsertrag + +3 + + + +89 + +0 + +305 + +0 + + + +90 + +0 + +304 + +0 + +36000 + +Spenden + +4 + +36 + + + +91 + +0 + +303 + +0 + +36500 + +Projektbeiträge + +4 + +36 + + + +92 + +0 + +302 + +0 + +36501 + +Projektbeiträge LB + +4 + +36 + + + +93 + +0 + +301 + +0 + +36 + +Spenden + +3 + + + +94 + +0 + +300 + +0 + + + +95 + +0 + +299 + +0 + +38000 + +Subventionen + +4 + +38 + + + +96 + +0 + +298 + +0 + +38 + +Subventionen + +3 + + + +97 + +0 + +297 + +0 + + + +98 + +0 + +141 + +0 + +3 + +Betriebsertrag + +E1 + +52.00 + +-52.00 + + + +99 + +0 + +142 + +0 + + + +100 + +0 + +143 + +0 + +40000 + +Materialaufwand Lebensmittel + +3 + +40 + + + +101 + +0 + +144 + +0 + +40 + +Materialaufwand + +4 + + + +102 + +0 + +145 + +0 + + + +103 + +0 + +146 + +0 + +41000 + +allg. Projektaufwand + +3 + +41 + + + +104 + +0 + +0 + +0 + +41001 + +Projektaufwand LB + +3 + +41 + + + +105 + +0 + +325 + +0 + +41090 + +Projekt Nachhaltigkeitskonzept + +3 + +41 + + + +106 + +0 + +324 + +0 + +41091 + +Projekt Nachhaltigkeitsparcour + +3 + +41 + + + +107 + +0 + +345 + +0 + +41092 + +Projekt Balsam + +3 + +41 + + + +108 + +0 + +344 + +0 + +41093 + +Projekt Inklusiver Jugendclub + +3 + +41 + + + +109 + +0 + +323 + +0 + +41 + +Projektaufwand + +4 + + + +110 + +0 + +322 + +0 + + + +111 + +0 + +321 + +0 + +42000 + +Kultureller Aufwand + +3 + +42 + + + +112 + +0 + +320 + +0 + +42020 + +Aufwand Sommerfest + +3 + +42 + + + +113 + +0 + +319 + +0 + +42 + +Kulturaufwand + +4 + + + +114 + +0 + +318 + +0 + + + +115 + +0 + +317 + +0 + +46000 + +übriger direkter Aufwand + +3 + +46 + + + +116 + +0 + +147 + +0 + +46 + +übriger Aufwand + +4 + + + +117 + +0 + +148 + +0 + + + +118 + +0 + +149 + +0 + + + +119 + +0 + +150 + +0 + +4 + +Direkter Aufwand + +E1 + + + +120 + +0 + +151 + +0 + + + +121 + +0 + +152 + +0 + +E1 + +Bruttoergebnis nach Betriebsertrag und direkter Aufwand + +E2 + +52.00 + +-52.00 + + + +122 + +0 + +153 + +0 + + + +123 + +0 + +154 + +0 + +52000 + +Monatslöhne + +3 + +52 + + + +124 + +0 + +155 + +0 + +52100 + +Personal ohne AHV + +3 + +52 + + + +125 + +0 + +156 + +0 + +52 + +Lohnaufwand + +5 + + + +126 + +0 + +157 + +0 + + + +127 + +0 + +158 + +0 + +57000 + +AHV, ALV, IV, EO, FAK + +3 + +57 + + + +128 + +0 + +347 + +0 + +57100 + +FAK + +3 + +57 + + + +129 + +0 + +159 + +0 + +57200 + +Berufliche Vorsorge (BVG) + +3 + +57 + + + +130 + +0 + +160 + +0 + +57300 + +Unfallversicherung (UVG) + +3 + +57 + + + +131 + +0 + +161 + +0 + +57400 + +Krankentaggeldversicherung + +3 + +57 + + + +132 + +0 + +162 + +0 + +57 + +Sozialversicherungsaufwand + +5 + + + +133 + +0 + +336 + +0 + + + +134 + +0 + +335 + +0 + +59000 + +Drittbeiträge an Lohnkosten + +3 + +59 + + + +135 + +0 + +334 + +0 + +59500 + +Weiterbildung, Coaching + +3 + +59 + + + +136 + +0 + +333 + +0 + +59990 + +übriger Personalaufwand + +3 + +59 + + + +137 + +0 + +332 + +0 + +59991 + +übriger Personalaufwand MiTui + +3 + +59 + + + +138 + +0 + +331 + +0 + +59 + +Übriger Personalaufwand + +5 + + + +139 + +0 + +166 + +0 + + + +140 + +0 + +167 + +0 + +5 + +Personalaufwand + +E2 + + + +141 + +0 + +168 + +0 + + + +142 + +0 + +169 + +0 + +E2 + +Bruttoergebnis nach Personalaufwand + +E3 + +52.00 + +-52.00 + + + +143 + +0 + +170 + +0 + + + +144 + +0 + +171 + +0 + +60000 + +Raumaufwand + +3 + +60 + + + +145 + +0 + +172 + +0 + +60100 + +Nebenkosten + +3 + +60 + + + +146 + +0 + +173 + +0 + +60600 + +Hauswartung Lola + +3 + +60 + + + +147 + +0 + +174 + +0 + +60 + +Raumaufwand + +6 + + + +148 + +0 + +175 + +0 + +61000 + +URE Mobiliar und Apparate + +3 + +61 + + + +149 + +0 + +0 + +0 + +61200 + +Unterhalt feste Einrichtungen + +3 + +61 + + + +150 + +0 + +176 + +0 + +61500 + +Einrichtungen Lola + +3 + +61 + + + +151 + +0 + +177 + +0 + +61 + +Unterhalt, Reparaturen, Ersatz (URE); Leasing mobile Sachanlagen + +6 + + + +152 + +0 + +178 + +0 + +63000 + +Elementarversicherung + +3 + +63 + + + +153 + +0 + +179 + +0 + +63600 + +Gebühren + +3 + +63 + + + +154 + +0 + +186 + +0 + +63 + +Sachvers., Abgaben, Gebühren, Bewilligungen + +6 + + + +155 + +0 + +187 + +0 + +64000 + +Energieaufwand + +3 + +64 + + + +156 + +0 + +188 + +0 + +64600 + +Entsorgungsaufwand + +3 + +64 + + + +157 + +0 + +191 + +0 + +64 + +Energie- und Entsorgungsaufwand + +6 + + + +158 + +0 + +192 + +0 + +65000 + +Büromaterial + +3 + +65 + + + +159 + +0 + +193 + +0 + +65001 + +VW Aufwand LB + +3 + +65 + + + +160 + +0 + +194 + +0 + +65030 + +Fachliteratur + +3 + +65 + + + +161 + +0 + +195 + +0 + +65100 + +Telefon, Fax, Internet + +3 + +65 + + + +162 + +0 + +196 + +0 + +65120 + +Porti + +3 + +65 + + + +163 + +0 + +197 + +0 + +65200 + +Verbandsbeiträge + +3 + +65 + + + +164 + +0 + +198 + +0 + +65300 + +Treuhand- und Rechtsberatung + +3 + +65 + + + +165 + +0 + +201 + +0 + +65 + +Verwaltungs- und Informatikaufwand + +6 + + + +166 + +0 + +202 + +0 + +66000 + +Öffentlichkeitsarbeit + +3 + +66 + + + +167 + +0 + +203 + +0 + +66001 + +Öffentlichkeitsarbeit LB + +3 + +66 + + + +168 + +0 + +204 + +0 + +66200 + +Dekoration + +3 + +66 + + + +169 + +0 + +206 + +0 + +66 + +Werbeaufwand + +6 + + + +170 + +0 + +338 + +0 + +68400 + +Bank-, PC-Spesen + +3 + +68 + + + +171 + +0 + +207 + +0 + +68450 + +Sumup Kommission + +3 + +68 + + + +172 + +0 + +208 + +0 + +68 + +Finanzerfolg + +6 + + + +173 + +0 + +209 + +0 + +6 + +Übriger betrieblicher Aufwand + +E3 + + + +174 + +0 + +210 + +0 + + + +175 + +0 + +211 + +0 + +E3 + +Betriebliches Ergebnis vor Abschreibungen und Wertberichtigungen, Finanzerfolg und Steuern (EBITDA) + +E4 + +52.00 + +-52.00 + + + +176 + +0 + +212 + +0 + + + +177 + +0 + +213 + +0 + +69000 + +Abschreibungen + +3 + +68 + + + +178 + +0 + +214 + +0 + +69 + +Abschreibungen + +E4 + + + +179 + +0 + +215 + +0 + + + +180 + +0 + +216 + +0 + +E4 + +Betriebliches Ergebnis vor Finanzerfolg und Steuern (EBIT) + +E5 + +52.00 + +-52.00 + + + +181 + +0 + +222 + +0 + + + +182 + +0 + +223 + +0 + +E5 + +Betriebliches Ergebnis vor Steuern (EBT) + +E6 + +52.00 + +-52.00 + + + +183 + +0 + +231 + +0 + + + +184 + +0 + +0 + +0 + +80000 + +Ausserordentlicher Ertrag + +4 + +8 + + + +185 + +0 + +0 + +0 + +80100 + +Ausserordentilcher Aufwand + +3 + +8 + + + +186 + +0 + +235 + +0 + +8 + +Total A.O., betriebsfremder Erfolg + +E6 + + + +187 + +0 + +236 + +0 + + + +188 + +0 + +237 + +0 + +E6 + +Jahresgewinn oder Jahresverlust vor Steuern + +E7 + +52.00 + +-52.00 + + + +189 + +0 + +238 + +0 + + + +190 + +0 + +239 + +0 + +8900 + +Direkte Steuern + +3 + +89 + + + +191 + +0 + +240 + +0 + +89 + +Direkte Steuern + +E7 + + + +192 + +0 + +241 + +0 + + + +193 + +0 + +242 + +0 + +E7 + +Jahresgewinn oder Jahresverlust + +297 + +52.00 + +-52.00 + + + +194 + +0 + +243 + +0 + + + +195 + +0 + +244 + +0 + +00 + +Differenz muss = 0 sein + +52.00 + +52.00 + + + +196 + +0 + +245 + +0 + + + +197 + +0 + +246 + +0 + + + +198 + +0 + +247 + +0 + + + +199 + +0 + +248 + +0 + + + +200 + +0 + +249 + +0 + + + +201 + +0 + +250 + +0 + + + +202 + +0 + +251 + +0 + + + +203 + +0 + +252 + +0 + + + +204 + +0 + +253 + +0 + + + +205 + +0 + +254 + +0 + + + +206 + +0 + +255 + +0 + + + +207 + +0 + +256 + +0 + + + +208 + +0 + +257 + +0 + + + +209 + +0 + +258 + +0 + + + +210 + +0 + +259 + +0 + + + +211 + +0 + +260 + +0 + + +
+ + + + +11 +11 +2 + + +3 +2 + + +2 +12 +2 + + +True +True + + + + + + +
+ + + + + + + + + + + + + + + + +TableInfo + +Zeile + +Fehler + +Einzig + +Grundzeile + +SysCod + +Links + +Section + +Group + +Description + +Gr + +Opening + +Balance + +ReportTotalsOnly + +ReportKeepRows + +ReportWithMovements + + + +Links zu externen Dokumenten + +Name der Gruppe + +Gruppe, worin diese Zeile summiert wird + +Eröffnungssaldo + + Nur Totalsummen ausdrucken + +Zeile in Bericht halten + +Mit Bewegung + + + +16 + +SysCode + +Links + +Sektion + +Gruppe + +Beschreibung + +Summ. in + +Eröffnung + +Saldo + +Tot + +Halten + +Mit Bew. + + + +24 + +CHF + +CHF + + + +Totalsummen + +Zeile + +Fehler + +Einzig + +Grundzeile + +SysCode + +Links + +Sektion + +Gruppe + +Beschreibung + +Gr + +Eröffnung + +Saldo + +BerichtNurTotale + +BerichtHalteZeilen + +BerichtKonBewegung + + + +Totals + +Row + +Error + +Unique + +Base Row + +SysCod + +Links + +Section + +Group + +Description + +Gr + +Opening + +Balance + +ReportTotalsOnly + +ReportKeepRows + +ReportWithMovements + + + + + + + + + + + +String + +String + +String + +String + +String + +String + +Number + +Number + +Boolean + +Boolean + +Boolean + + + +1 + +0 + +1 + +0 + +1 + +Totalsumme Aktiven + +01 + +52.00 + +1 + + + +2 + +0 + +2 + +0 + +2 + +Total Passiven und Eigenkapital + +01 + +1 + + + +3 + +0 + +3 + +0 + +01 + +Gewinn(+) Verlust(-) der Bilanz + +00 + +52.00 + +1 + + + +4 + +0 + +4 + +0 + +3 + +Totalsumme Aufwand + +02 + +1 + + + +5 + +0 + +5 + +0 + +4 + +Totalsumme Ertrag + +02 + +-52.00 + +1 + + + +6 + +0 + +6 + +0 + +02 + +Verlust(+) Gewinn(-) der Erfolgsrechnung + +00 + +-52.00 + +1 + + + +7 + +0 + +7 + +0 + +00 + +Differenz muss = 0 sein + +1 + + + +8 + +0 + +8 + +0 + +5 + +Total Ausserbilanz: Aktiven + +1 + + + +9 + +0 + +9 + +0 + +6 + +Total Ausserbilanz: Passiven + +1 + + + +10 + +0 + +10 + +0 + +7 + +Totalsumme Klasse 7 + +1 + + + +11 + +0 + +11 + +0 + +8 + +Totalsumme Klasse 8 + +1 + + + +12 + +0 + +12 + +0 + +9 + +Totalsumme Klasse 9 + +1 + + + +13 + +0 + +13 + +0 + + +
+ + + + +11 +11 +2 + + +3 +2 + + +2 +12 +2 + + +True +True + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +TableInfo + +Zeile + +Fehler + +Einzig + +Grundzeile + +SysCod + +Section + +Date + +DateDocument + +DateValue + +Doc + +DocProtocol + +DocType + +DocOriginal + +DocInvoice + +InvoicePrinted + +DocLink + +ExternalReference + +Description + +Notes + +AccountDebit + +AccountDebitDes + +AccountCredit + +AccountCreditDes + +Amount + +Balance + +Cc1 + +Cc1Des + +Cc2 + +Cc2Des + +Cc3 + +Cc3Des + +Segment + +DateExpiration + +DateExpected + +DatePayment + +LockNumber + +LockAmount + +LockProgressive + +LockLine + + + +Beleg + +Beleg Protokoll + +Beleg-Typ + +Rechnung Nummer + +Rechnung ausgedruckt + +Link zu externer Datei + +Externe Referenz + +Bemerkungen + +Konto Soll + +Konto Haben + +Kostenstelle 1 + +Beschreibung Kostenstelle 1 + +Kostenstelle 2 + +Beschreibung Kostenstelle 2 + +Kostenstelle 3 + +Beschreibung Kostenstelle 3 + +Segment + +Fälligkeitsdatum + +Erwartetes Datum + +Datum Bezahlung + +Nummer der Sperre + +Totalsumme der Sperre + +Hash fortlaufend + +Hash der Zeile + + + +40 + +SysCode + +Sektion + +Datum + +Datum Beleg + +Valuta Datum + +Beleg + +Beleg Prot. + +Typ + +Beleg Original + +Rechnung + +Ausgedruckt + +Link + +Ext.Ref. + +Beschreibung + +Bemerkungen + +KtSoll + +KtSoll Beschr. + +KtHaben + +KtHaben Beschr. + +Betrag + +Saldo + +KS1 + +KS1 Beschr. + +KS2 + +KS2 Beschr. + +KS3 + +KS3 Beschr. + +Segment + +Fälligkeit + +Vorgesehen + +Zahlung + +SperreNr + +SperreBetrag + +SperreProgr + +SperreZeile + + + +17 + +CHF + + + +Journal + +Zeile + +Fehler + +Einzig + +Grundzeile + +SysCode + +Sektion + +Datum + +DatumBeleg + +DatumValuta + +Beleg + +BelegProtokoll + +BelegTyp + +BelegOriginal + +RechnungBeleg + +RechnungAusgedruckt + +DokLink + +ExterneReferenz + +Beschreibung + +Bemerkungen + +KontoSoll + +KontoSollBeschr + +KontoHaben + +KontoHabenBeschr + +Betrag + +Saldo + +KS1 + +KS1Beschr + +KS2 + +KS2Beschr + +KS3 + +KS3Beschr + +Segment + +DatumFälligkeit + +DatumErwartet + +DatumZahl + +SperreNr + +SperreBetrag + +SperreFortlaufend + +SperreZeile + + + +Journal + +Row + +Error + +Unique + +Base Row + +SysCod + +Section + +Date + +DateDocument + +DateValue + +Doc + +DocProtocol + +DocType + +DocOriginal + +DocInvoice + +InvoicePrinted + +DocLink + +ExternalReference + +Description + +Notes + +AccountDebit + +AccountDebitDes + +AccountCredit + +AccountCreditDes + +Amount + +Balance + +Cc1 + +Cc1Des + +Cc2 + +Cc2Des + +Cc3 + +Cc3Des + +Segment + +DateExpiration + +DateExpected + +DatePayment + +LockNumber + +LockAmount + +LockProgressive + +LockLine + + + + + + + + + + + +String + +String + +DateTime + +DateTime + +DateTime + +String + +String + +String + +String + +String + +String + +String + +String + +String + +String + +String + +String + +String + +String + +Number + +Number + +String + +String + +String + +String + +String + +String + +String + +DateTime + +DateTime + +DateTime + +String + +Number + +String + +String + + + +1 + +0 + +1 + +1 + +2024-01-31T00:00:00.000 + +a + +10000 + +Kassenguthaben + +30200 + +Ertrag Café + +5.00 + + + +2 + +0 + +2 + +2 + +2024-03-01T00:00:00.000 + +b + +10100 + +PCK 40-2519-7 + +30200 + +Ertrag Café + +10.00 + + + +3 + +0 + +3 + +4 + +2024-04-01T00:00:00.000 + +d + +10000 + +Kassenguthaben + +32000 + +Kulturelle Einnahmen + +15.00 + + + +4 + +0 + +4 + +5 + +2024-04-05T00:00:00.000 + +e + +10100 + +PCK 40-2519-7 + +32000 + +Kulturelle Einnahmen + +9.00 + + + +5 + +0 + +5 + +3 + +2024-08-16T00:00:00.000 + +c + +10000 + +Kassenguthaben + +30200 + +Ertrag Café + +7.00 + + + +6 + +0 + +6 + +6 + +2024-10-01T00:00:00.000 + +f + +10000 + +Kassenguthaben + +32020 + +Kulturertrag Sommerfest + +6.00 + + +
+ + + + +11 +11 +2 + + +3 +2 + + +2 +12 +2 + + +True +True + + + + + + +
+ + + + + + + + + + + + + + + +TableInfo + +Zeile + +Fehler + +Einzig + +Grundzeile + +SysCod + +Links + +Section + +SectionText + +Id + +Description + +Value + +SectionXml + +IdXml + +ValueXml + + + +Links zu externen Dokumenten + + + +15 + +SysCode + +Links + +Sektion + +Bereich + +Id + +Beschreibung + +Wert + +Bereich Xml + +ID Xml + +Wert Xml + + + +110 + + + +DateiInfos + +Zeile + +Fehler + +Einzig + +Grundzeile + +SysCode + +Links + +Sektion + +BereichText + +Id + +Beschreibung + +Wert + +BereichXml + +IdXml + +WertXml + + + +FileInfo + +Row + +Error + +Unique + +Base Row + +SysCod + +Links + +Section + +SectionText + +Id + +Description + +Value + +SectionXml + +IdXml + +ValueXml + + + + + + + + + + + +String + +String + +String + +String + +String + +String + +String + +String + +String + +String + + + +1 + +0 + +1 + +0 + +Basis + +Datei_Informationen + +Datei-Informationen + +Base + +FileInfo + + + +2 + +0 + +2 + +0 + +Basis + +Datum + +Datum + +28.01.2025 + +Base + +Date + +2025-01-28 + + + +3 + +0 + +3 + +0 + +Basis + +Zeit + +Zeit + +12:58:55 + +Base + +Time + +12:58:55.880 + + + +4 + +0 + +4 + +0 + +Basis + +Programmversion + +Programmversion + +10.1.24.24275 + +Base + +ProgramVersion + +10.1.24.24275 + + + +5 + +0 + +5 + +0 + +Basis + +HashAlgorithmus + + Hash-Algorithmus + +Sha2-256 + +Base + +HashAlgorithm + +Sha2-256 + + + +6 + +0 + +6 + +0 + +Basis + +Hashsumme + +Hashsumme + +cf17a67ec26d79557dc39ed15a6e35e1 + +Base + +HashTotal + +cf17a67ec26d79557dc39ed15a6e35e1 + + + +7 + +0 + +7 + +0 + + + +8 + +0 + +8 + +0 + +Basis + +Datei + +Datei + +Base + +File + + + +9 + +0 + +9 + +0 + +Basis + +Dateiname + +Dateiname + +/home/urs/Documents/bsn/lola/operativ/banana/samples.ac2 + +Base + +FileName + +/home/urs/Documents/bsn/lola/operativ/banana/samples.ac2 + + + +10 + +0 + +10 + +0 + +Basis + +WorkingCopyFileName + +Working copy file name + +/home/urs/.local/share/Banana.ch/BananaPlus/10.0/WorkingCopies/cc9fa92d-f8d1-4557-af5d-e26e353daee5/samples.ac2 + +Base + +WorkingCopyFileName + +/home/urs/.local/share/Banana.ch/BananaPlus/10.0/WorkingCopies/cc9fa92d-f8d1-4557-af5d-e26e353daee5/samples.ac2 + + + +11 + +0 + +11 + +0 + +Basis + +DatumLetzteSpeicherung + +Datum letzte Speicherung + +28.01.2025 + +Base + +DateLastSaved + +2025-01-28 + + + +12 + +0 + +12 + +0 + +Basis + +ZeitLetzteSpeicherung + +Zeit letzte Speicherung + +12:58:27 + +Base + +TimeLastSaved + +12:58:27.000 + + + +13 + +0 + +13 + +0 + +Basis + +Dateigrösse + +Dateigrösse + +348540 + +Base + +FileSize + +348540 + + + +14 + +0 + +14 + +0 + +Basis + +DateiPwdGeschützt + +Datei mit Passwort geschützt + +Nein + +Base + +FilePwdProtected + +No + + + +15 + +0 + +15 + +0 + +Basis + +Überschrift1 + +Überschrift1 + +Base + +HeaderLeft + + + +16 + +0 + +16 + +0 + +Basis + +Überschrift2 + +Überschrift2 + +Base + +HeaderRight + + + +17 + +0 + +17 + +0 + +Basis + +DezimalstellenBeträge + +Dezimalstellen Beträge + +2 + +Base + +DecimalsAmounts + +2 + + + +18 + +0 + +18 + +0 + +Basis + +Rundungsart + +Rundungsart + +3:Kaufmännisch (Hälfte aufrunden) + +Base + +RoundingType + +3:Commercial/Arithmetic (Half up) + + + +19 + +0 + +19 + +0 + +Basis + +Sprache + +Standard-Sprache + +Deutsch + +Base + +Language + +deu + + + +20 + +0 + +20 + +0 + +Basis + +SpracheErstellung + +Sprache zur Erstellung verwendet + +Deutsch + +Base + +LanguageCreation + +deu + + + +21 + +0 + +21 + +0 + +Basis + +Erweiterung1 + +Erweiterung 1 + +Base + +Extension1 + + + +22 + +0 + +22 + +0 + +Basis + +Erweiterung2 + +Erweiterung 2 + +Base + +Extension2 + + + +23 + +0 + +23 + +0 + +Basis + +Erweiterung3 + +Erweiterung 3 + +Base + +Extension3 + + + +24 + +0 + +24 + +0 + +Basis + +VersionErstellung + +Version Erstellung + +9.0.2 + +Base + +CreateProgramVersion + +9.0.2 + + + +25 + +0 + +25 + +0 + +Basis + +VersionLetzteSpeicherung + +Version letzte Speicherung + +10.1.18.24031 + +Base + +LastSavedProgramVersion + +10.1.18.24031 + + + +26 + +0 + +26 + +0 + +Basis + +VersionMaximumSpeicherung + +Version Maximum der Speicherung + +10.1.20.24169 + +Base + +MaxSavedProgramVersion + +10.1.20.24169 + + + +27 + +0 + +27 + +0 + +Basis + +Versionskompatibilitaet + +Version Kompatibilität + +5.0.11 + +Base + +SavedProgramVersionCompatibility + +5.0.11 + + + +28 + +0 + +28 + +0 + +Basis + +ApplikationsdateiTyp + +Typ + +Doppelte Buchhaltung + +Base + +FileType + +Double-entry + + + +29 + +0 + +29 + +0 + +Basis + +AppGruppen + +Gruppennummer + +100 + +Base + +FileTypeGroup + +100 + + + +30 + +0 + +30 + +0 + +Basis + +AppNummer + +Applikationsnummer + +100 + +Base + +FileTypeNumber + +100 + + + +31 + +0 + +31 + +0 + +Basis + +AppVersion + +Nummer Version + +100 + +Base + +FileTypeVersion + +100 + + + +32 + +0 + +32 + +0 + +StammdatenBuchhaltung + +Firma + +Firma + +AccountingDataBase + +Company + + + +33 + +0 + +33 + +0 + +StammdatenBuchhaltung + +Anrede + +Anrede + +AccountingDataBase + +Courtesy + + + +34 + +0 + +34 + +0 + +StammdatenBuchhaltung + +Name + +Name + +AccountingDataBase + +Name + + + +35 + +0 + +35 + +0 + +StammdatenBuchhaltung + +Nachname + +Nachname + +AccountingDataBase + +FamilyName + + + +36 + +0 + +36 + +0 + +StammdatenBuchhaltung + +Adresse1 + +Adresse 1 + +AccountingDataBase + +Address1 + + + +37 + +0 + +37 + +0 + +StammdatenBuchhaltung + +Adresse2 + +Adresse 2 + +AccountingDataBase + +Address2 + + + +38 + +0 + +38 + +0 + +StammdatenBuchhaltung + +Postleitzahl + +Postleitzahl + +AccountingDataBase + +Zip + + + +39 + +0 + +39 + +0 + +StammdatenBuchhaltung + +Ort + +Ort + +AccountingDataBase + +City + + + +40 + +0 + +40 + +0 + +StammdatenBuchhaltung + +Region + +Region + +AccountingDataBase + +State + + + +41 + +0 + +41 + +0 + +StammdatenBuchhaltung + +Nation + +Nation + +AccountingDataBase + +Country + + + +42 + +0 + +42 + +0 + +StammdatenBuchhaltung + +Landercode + +Ländercode + +AccountingDataBase + +CountryCode + + + +43 + +0 + +43 + +0 + +StammdatenBuchhaltung + +Web + +Web + +AccountingDataBase + +Web + + + +44 + +0 + +44 + +0 + +StammdatenBuchhaltung + +Email + +Email + +AccountingDataBase + +Email + + + +45 + +0 + +45 + +0 + +StammdatenBuchhaltung + +Telefon + +Telefon + +AccountingDataBase + +Phone + + + +46 + +0 + +46 + +0 + +StammdatenBuchhaltung + +Mobil + +Mobil + +AccountingDataBase + +Mobile + + + +47 + +0 + +47 + +0 + +StammdatenBuchhaltung + +IBAN + +IBAN + +AccountingDataBase + +IBAN + + + +48 + +0 + +48 + +0 + +StammdatenBuchhaltung + +Steuernummer + +Steuernummer + +AccountingDataBase + +FiscalNumber + + + +49 + +0 + +49 + +0 + +StammdatenBuchhaltung + +MWSTUStNummer + +MWST/USt-Nummer + +AccountingDataBase + +VatNumber + + + +50 + +0 + +50 + +0 + + + +51 + +0 + +51 + +0 + +FreieTexte + +FreieTexte + +Freie Texte + +FreeTexts + +FreeTexts + + + +52 + +0 + +52 + +0 + + + +53 + +0 + +53 + +0 + + + +54 + +0 + +54 + +0 + +StammdatenBuchhaltung + +StammdatenBuchhaltung + +Stammdaten Buchhaltung + +AccountingDataBase + +AccountingDataBase + + + +55 + +0 + +55 + +0 + +StammdatenBuchhaltung + +KomplettesNachrechnen + +Komplettes Nachrechnen + +Nein + +AccountingDataBase + +Recalculate + +No + + + +56 + +0 + +56 + +0 + +StammdatenBuchhaltung + +Basiswährung + +Basiswährung + +CHF + +AccountingDataBase + +BasicCurrency + +CHF + + + +57 + +0 + +57 + +0 + +StammdatenBuchhaltung + +BasiswährungÜberschrift + +Basiswährung Überschrift + +AccountingDataBase + +BasicCurrencyHeader + + + +58 + +0 + +58 + +0 + +StammdatenBuchhaltung + +DezimalstellenBeträgeWährung + +Dezimalstellen Beträge in Währung + +2 + +AccountingDataBase + +DecimalsAmountsCurrency + +2 + + + +59 + +0 + +59 + +0 + +StammdatenBuchhaltung + +Anfangsdatum + +Anfangsdatum + +01.01.2024 + +AccountingDataBase + +OpeningDate + +2024-01-01 + + + +60 + +0 + +60 + +0 + +StammdatenBuchhaltung + +Enddatum + +Enddatum + +31.12.2024 + +AccountingDataBase + +ClosureDate + +2024-12-31 + + + +61 + +0 + +61 + +0 + +StammdatenBuchhaltung + +DatumObligatorisch + +Datum obligatorische Buchungen + +Ja + +AccountingDataBase + +ObligatoryDate + +Yes + + + +62 + +0 + +62 + +0 + +StammdatenBuchhaltung + +MinuszeichenSegmenteTrennzeichen + +Minuszeichen als Trennzeichen der Segmente + +Nein + +AccountingDataBase + +MinusSignSegmentSeparator + +No + + + +63 + +0 + +63 + +0 + +StammdatenBuchhaltung + +KostenstellenzeichenNachBetrag + +Kostenstellenzeichen nach Betrag + +Nein + +AccountingDataBase + +CostCenterSign + +No + + + +64 + +0 + +64 + +0 + +StammdatenBuchhaltung + +DateiVorjahr + +Datei Vorjahr + +AccountingDataBase + +FileNamePreviousYear + + + +65 + +0 + +65 + +0 + +StammdatenBuchhaltung + +SmartFillVorjahresbuchungen + +Smart Fill mit Vorjahresbuchungen + +Nein + +AccountingDataBase + +SmartFillFromPreviousYear + +No + + + +66 + +0 + +66 + +0 + +StammdatenBuchhaltung + +DifferenzAnfangssaldi + +Differenz Anfangssaldi + +AccountingDataBase + +InitialBalanceDifference + + + +67 + +0 + +67 + +0 + +StammdatenBuchhaltung + +DifferenzSollHaben + +Differenz Soll/Haben + +AccountingDataBase + +DebitCreditDifference + + + +68 + +0 + +68 + +0 + + + +69 + +0 + +69 + +0 + +Konten + +Tabellenname + +Tabellenname + +Konten + +Accounts + +TableName + +Konten + + + +70 + +0 + +70 + +0 + +Konten + +XmlTabellenname + +Xml Tabellenname + +Accounts + +Accounts + +TableNameXml + +Accounts + + + +71 + +0 + +71 + +0 + +Konten + +Tabellenüberschrift + +Überschrift Tabelle + +Konten + +Accounts + +TableHeader + +Konten + + + +72 + +0 + +72 + +0 + +Konten + +AnzahlZeilen + +Anzahl Zeilen + +211 + +Accounts + +CountRows + +211 + + + +73 + +0 + +73 + +0 + +Konten + +ZeilenMitMeldungen + +Zeilen mit Meldungen + +0 + +Accounts + +CountRowsError + +0 + + + +74 + +0 + +74 + +0 + +Konten + +ZeilenMitFehlern + +Zeilen mit Fehlern + +0 + +Accounts + +CountRowsWarning + +0 + + + +75 + +0 + +75 + +0 + +Konten + +GesperrteZeilen + +Gesperrte Zeilen + +0 + +Accounts + +CountRowsProtected + +0 + + + +76 + +0 + +76 + +0 + +Konten + +GeschützteZeilen + +Geschützte Zeilen + +0 + +Accounts + +CountRowsLocked + +0 + + + +77 + +0 + +77 + +0 + +Konten + +AnzahlGruppen + +Anzahl Gruppen + +48 + +Accounts + +CountGroups + +48 + + + +78 + +0 + +78 + +0 + +Konten + +AnzahlKonten + +Anzahl Konten + +91 + +Accounts + +CountAccounts + +91 + + + +79 + +0 + +79 + +0 + +Konten + +AnzahlKontenMitSaldo + +Anzahl Konten mit Saldo + +5 + +Accounts + +CountAccountsBalance + +5 + + + +80 + +0 + +80 + +0 + +Konten + +AnzahlKontenMitBewegung + +Anzahl Konten mit Bewegung + +5 + +Accounts + +CountTransactions + +5 + + + +81 + +0 + +81 + +0 + +Konten + +HashsummeSaldos + +Hashsumme des aktuellen Saldos + +4JnBErhEILsmaYPVwxXtQNr6OHTdK9+u + +Accounts + +HashAccountsBalance + +4JnBErhEILsmaYPVwxXtQNr6OHTdK9+u + + + +82 + +0 + +82 + +0 + +Konten + +AnzahlKontenMitEröffnungssaldo + +Anzahl Konten mit Eröffnungssaldo + +Accounts + +CountAccountsOpening + + + +83 + +0 + +83 + +0 + +Konten + +HashsummeEröffnungssaldos + +Hashsumme des Eröffnungssaldos + + + +Accounts + +HashAccountsOpening + + + + + +84 + +0 + +84 + +0 + + + +85 + +0 + +85 + +0 + +Buchungen + +Tabellenname + +Tabellenname + +Buchungen + +Transactions + +TableName + +Buchungen + + + +86 + +0 + +86 + +0 + +Buchungen + +XmlTabellenname + +Xml Tabellenname + +Transactions + +Transactions + +TableNameXml + +Transactions + + + +87 + +0 + +87 + +0 + +Buchungen + +Tabellenüberschrift + +Überschrift Tabelle + +Buchungen + +Transactions + +TableHeader + +Buchungen + + + +88 + +0 + +88 + +0 + +Buchungen + +AnzahlZeilen + +Anzahl Zeilen + +6 + +Transactions + +CountRows + +6 + + + +89 + +0 + +89 + +0 + +Buchungen + +HashKomplett + +Hash komplett + +vcBntRm8gH3kp6LcGZ5AW0dfIrnU9co5 + +Transactions + +HashComplete + +vcBntRm8gH3kp6LcGZ5AW0dfIrnU9co5 + + + +90 + +0 + +90 + +0 + +Buchungen + +ZeilenMitMeldungen + +Zeilen mit Meldungen + +0 + +Transactions + +CountRowsError + +0 + + + +91 + +0 + +91 + +0 + +Buchungen + +ZeilenMitFehlern + +Zeilen mit Fehlern + +0 + +Transactions + +CountRowsWarning + +0 + + + +92 + +0 + +92 + +0 + +Buchungen + +GesperrteZeilen + +Gesperrte Zeilen + +0 + +Transactions + +CountRowsProtected + +0 + + + +93 + +0 + +93 + +0 + +Buchungen + +GeschützteZeilen + +Geschützte Zeilen + +0 + +Transactions + +CountRowsLocked + +0 + + + +94 + +0 + +94 + +0 + +Buchungen + +DatumKleinereBuchung + +Kleinstes Buchungsdatum + +31.01.2024 + +Transactions + +DateEarliestTransaction + +2024-01-31 + + + +95 + +0 + +95 + +0 + +Buchungen + +DatumMaximaleBuchung + +Grösstes Buchungsdatum + +01.10.2024 + +Transactions + +DateLatestTransaction + +2024-10-01 + + + +96 + +0 + +96 + +0 + +Buchungen + +BuchhaltungssperreBenutzt + +Buchhaltungssperre benutzt + +Nein + +Transactions + +TransactionLockUsed + +No + + + +97 + +0 + +97 + +0 + + + +98 + +0 + +98 + +0 + +BuchungenSperren + +BuchungenSperren + +BuchungenSperren + +LockTransactions + +LockTransactions + + + +99 + +0 + +99 + +0 + +BuchungenSperren + +BuchhaltungssperreBenutzt + +Buchhaltungssperre benutzt + +Nein + +LockTransactions + +TransactionLockUsed + +No + + +
+ + + + +11 +11 +2 + + +3 +2 + + +2 +12 +2 + + +True +True + + + + + + +
+ +
\ No newline at end of file diff --git a/src/close.rs b/src/close.rs new file mode 100644 index 0000000..47d6378 --- /dev/null +++ b/src/close.rs @@ -0,0 +1,32 @@ +use crate::close::close_xml::do_closing_xml; +use crate::close::close_xml::read_budget_config; +use crate::derive_month_from_accounts; + +use std::error::Error; +use std::path::Path; + +mod close_xml; + +/// Read the file with the accounts information and create the closing file +pub fn close( + budget_config_file: &Path, + accounts_file: &Path, + ts: &str, +) -> Result<(), Box> { + let account_file_name = accounts_file.as_os_str().to_str(); + if let Some(extension) = accounts_file.extension().and_then(|e| e.to_str()) { + let month = derive_month_from_accounts(account_file_name, extension)?; + let budget = read_budget_config(budget_config_file)?; + match extension { + "xls" => { + let _ = do_closing_xml(accounts_file, budget, &month, ts); + Ok(()) + } + _ => Err(Box::from(format!( + "File extension {extension} is not supported." + ))), + } + } else { + Err("No valid file extension found".into()) + } +} diff --git a/src/close/close_xml.rs b/src/close/close_xml.rs new file mode 100644 index 0000000..7d5e6ef --- /dev/null +++ b/src/close/close_xml.rs @@ -0,0 +1,473 @@ +use polars::prelude::*; +use quick_xml::events::Event; +use quick_xml::reader::Reader; +use serde::Deserialize; +use std::collections::HashMap; +use std::error::Error; +use std::fs; +use std::fs::File; +use std::io::BufReader; +use std::path::Path; + +/// read account information from xml file with ending .xls +pub fn do_closing_xml( + input_path: &Path, + budget: Budget, + month: &str, + _ts: &str, +) -> Result> { + let budget = Arc::new(budget); + let b1 = budget.clone(); + let b2 = budget.clone(); + let b3 = budget.clone(); + let b4 = budget; + let year = month.chars().take(4).collect::(); + println!("year: {year}"); + let balances = read_xml(input_path)?; + let enriched = balances + .clone() + .lazy() + .with_column( + col("Account") + .apply( + move |a| get_name_of_post(&a, &b1), + GetOutput::from_type(DataType::String), + ) + .alias("Group"), + ) + .with_column( + col("Account") + .apply( + move |a| get_factor_of_post(&a, &b2), + GetOutput::from_type(DataType::Int8), + ) + .alias("Factor"), + ) + .with_column( + col("Account") + .apply( + move |a| get_sort_of_post(&a, &b3), + GetOutput::from_type(DataType::Int8), + ) + .alias("Sort"), + ) + .with_column( + col("Account") + .apply( + move |a| get_budget_of_post(&a, &b4, &year), + GetOutput::from_type(DataType::Int64), + ) + .alias("Budget"), + ) + .filter( + // Exceptional accounts that need not be included + col("Account") + .neq(lit("8900")) + .or(col("Debit").neq(lit(0.0))) + .or(col("Credit").neq(lit(0.0))), + ) + .collect()?; + + validate_all_accounts_are_in_budget(&enriched)?; + + let aggregated = enriched + .clone() + .lazy() + .with_column((col("Balance").fill_null(lit(0.0)) * col("Factor")).alias("Net")) + .group_by(["Group", "Sort", "Budget", "Factor"]) + .agg(&[col("Net").sum()]) + .sort(["Sort"], SortMultipleOptions::default()) + .select([ + col("Group"), + col("Budget"), + col("Net"), + ((col("Budget") - col("Net")) * col("Factor")).alias("Remaining"), + ]) + .collect()?; + println!("{aggregated:?}"); + Ok(aggregated) +} + +// validates the processed data does not contain any accounts that are not in the budget +fn validate_all_accounts_are_in_budget(enriched: &DataFrame) -> Result<(), Box> { + let unmatched = enriched + .clone() + .lazy() + .filter(col("Group").is_null()) + .collect()?; + if unmatched.shape().0 > 0 { + let row_vec = unmatched.get_row(0).unwrap().0; + let account = row_vec.first().unwrap().clone(); + println!("{unmatched:?}"); + Err(format!("Account {account} is not considered in the budget definition").into()) + } else { + Ok(()) + } +} + +/// Finds the descriptions of the budget post for given account +fn get_name_of_post(col: &Column, budget: &Budget) -> PolarsResult> { + let accounts = col.str()?; + Ok(Some( + accounts + .into_iter() + .map(|a| { + a.map(|a| budget.get_post_by_account(a).map(|p| p.name.to_string())) + .or(None)? + }) + .collect::() + .into_column(), + )) +} + +/// Finds the budget amount of the budget post for given account and year +fn get_budget_of_post(col: &Column, budget: &Budget, year: &str) -> PolarsResult> { + let accounts = col.str()?; + Ok(Some( + accounts + .into_iter() + .map(|a| a.map(|a| budget.get_buget_amount_by_account(a, year))) + .collect::() + .into_column(), + )) +} + +/// Finds the sort for the post for given account +fn get_sort_of_post(col: &Column, budget: &Budget) -> PolarsResult> { + get_int_from_post(col, budget, |p| p.sort) +} + +/// Finds the factor for the post for given account +fn get_factor_of_post(col: &Column, budget: &Budget) -> PolarsResult> { + get_int_from_post(col, budget, |p| p.factor) +} + +fn get_int_from_post( + col: &Column, + budget: &Budget, + int_extractor: F, +) -> PolarsResult> +where + F: Fn(&Post) -> i64 + Copy, +{ + let accounts = col.str()?; + Ok(Some( + accounts + .into_iter() + .map(|a| { + a.map(|a| budget.get_post_by_account(a).map(int_extractor)) + .or(None)? + }) + .collect::() + .into_column(), + )) +} + +fn read_xml(input_path: &Path) -> Result> { + let file = BufReader::new(File::open(input_path)?); + let mut reader = Reader::from_reader(file); + + reader.config_mut().trim_text(true); + + let mut buf = Vec::new(); + let mut in_sheet = false; + let mut in_cell = false; + let mut cell_value = String::new(); + let mut index: Option = None; + let mut row: HashMap = HashMap::new(); + let mut df = new_empty_frame()?; + + loop { + match reader.read_event_into(&mut buf) { + Ok(Event::Start(ref e)) => match e.name().as_ref() { + b"Worksheet" => { + for attr in e.attributes().flatten() { + if attr.key.as_ref() == b"ss:Name" + && attr.unescape_value()? == Sheet::Accounts.name() + { + in_sheet = true; + } + } + } + b"Cell" => { + for attr in e.attributes().flatten() { + if attr.key.as_ref() == b"ss:Index" { + index = Some(attr.unescape_value()?.parse::()?); + in_cell = in_sheet; + } + } + } + _ => {} + }, + Ok(Event::End(ref e)) => match e.name().as_ref() { + b"Worksheet" => { + cell_value.clear(); + in_sheet = false; + in_cell = false; + row.clear(); + } + b"Cell" => { + if in_cell { + if let Some(i) = index { + row.insert(i, cell_value.clone()); + } + in_cell = false; + } + } + b"Row" => { + if in_sheet { + let account_index = AccountsColumn::Account as u32; + let description_index = AccountsColumn::Description as u32; + let debit_index = AccountsColumn::Debit as u32; + let credit_index = AccountsColumn::Credit as u32; + let balance_index = AccountsColumn::Balance as u32; + let mut df_row = new_row( + row.remove(&account_index) + .unwrap_or("account missing".into()), + row.remove(&description_index) + .unwrap_or("description missing".into()), + &row.remove(&debit_index).unwrap_or("0.0".into()), + &row.remove(&credit_index).unwrap_or("0.0".into()), + &row.remove(&balance_index).unwrap_or("0.0".into()), + )?; + df_row = df_row.fill_null(FillNullStrategy::Zero)?; + let filtered = df_row + .clone() + .lazy() + .filter( + col("Account").is_not_null().and( + col("Account") + .str() + .contains(lit(r"^[3456789]\d{3,4}$"), false), + ), + ) + .select([col("Account")]) + .collect()?; + if filtered.shape().0 > 0 { + df = df.vstack(&df_row)?; + } + row.clear(); + } + } + _ => {} + }, + Ok(Event::Text(e)) => { + if in_sheet && in_cell { + cell_value = e.unescape()?.into_owned(); + } + } + Ok(Event::Eof) => break, + Err(e) => return Err(Box::from(e)), + _ => {} + } + buf.clear(); + } + Ok(df) +} + +fn new_row( + account: String, + description: String, + debit: &str, + credit: &str, + balance: &str, +) -> Result> { + let debit_numeric: f64 = debit.parse().unwrap_or(0.0); + let credit_numeric: f64 = credit.parse().unwrap_or(0.0); + let balance_numeric: f64 = balance.parse().unwrap_or(0.0); + new_row_with_vecs( + vec![account], + vec![description], + vec![debit_numeric], + vec![credit_numeric], + vec![balance_numeric], + ) +} + +fn new_empty_frame() -> Result> { + new_row_with_vecs( + Vec::::new(), + Vec::::new(), + Vec::::new(), + Vec::::new(), + Vec::::new(), + ) +} + +fn new_row_with_vecs( + account: Vec, + description: Vec, + debit: Vec, + credit: Vec, + balance: Vec, +) -> Result> { + let df = DataFrame::new(vec![ + Column::new( + AccountsColumn::Account.name().into(), + Series::new(AccountsColumn::Account.name().into(), account), + ), + Column::new( + AccountsColumn::Description.name().into(), + Series::new(AccountsColumn::Description.name().into(), description), + ), + Column::new( + AccountsColumn::Debit.name().into(), + Series::new(AccountsColumn::Debit.name().into(), debit), + ), + Column::new( + AccountsColumn::Credit.name().into(), + Series::new(AccountsColumn::Credit.name().into(), credit), + ), + Column::new( + AccountsColumn::Balance.name().into(), + Series::new(AccountsColumn::Balance.name().into(), balance), + ), + ])?; + Ok(df) +} + +/// The worksheets in the workbook +#[derive(Debug, Clone, Copy)] +enum Sheet { + Accounts = 0, + _Totals = 1, + _Journal = 2, + _FileInfo = 3, +} + +impl Sheet { + fn name(self) -> &'static str { + match self { + Sheet::Accounts => "Accounts", + Sheet::_Totals => "Totals", + Sheet::_Journal => "Journal", + Sheet::_FileInfo => "FileInfo", + } + } +} + +/// The columns in the worksheet Accounts, index is one-based +#[derive(Debug, Clone, Copy)] +enum AccountsColumn { + Account = 10, + Description = 11, + Debit = 21, + Credit = 22, + Balance = 23, +} + +impl AccountsColumn { + fn name(self) -> &'static str { + match self { + AccountsColumn::Account => "Account", + AccountsColumn::Description => "Description", + AccountsColumn::Debit => "Debit", + AccountsColumn::Credit => "Credit", + AccountsColumn::Balance => "Balance", + } + } +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Budget { + #[serde(rename = "name")] + _name: String, + #[serde(rename = "post_groups")] + _post_groups: HashMap, + posts: HashMap, + years: HashMap, +} + +#[derive(Deserialize, Debug, Clone)] +struct PostGroup { + #[serde(rename = "name")] + _name: String, + #[serde(rename = "posts")] + _posts: Vec, +} + +#[derive(Deserialize, Debug, Clone)] +struct Post { + pub name: String, + account_codes: Vec, + sort: i64, + factor: i64, +} + +#[derive(Deserialize, Debug, Clone)] +struct Year { + amounts: HashMap, +} + +impl Budget { + /// Get the post by account code + fn get_post_by_account(&self, account: &str) -> Option<&Post> { + self.posts + .values() + .find(|p| p.account_codes.contains(&account.to_string())) + } + + fn get_post_key_by_account(&self, account: &str) -> Option { + self.posts + .iter() + .find(|(_, p)| p.account_codes.contains(&account.to_string())) + .map(|(k, _)| k.to_string()) + } + + /// get the amount for the given budget account and year + fn get_buget_amount_by_account(&self, account: &str, year: &str) -> f64 { + let post_key_option = self.get_post_key_by_account(account); + if let Some(post_key) = post_key_option { + let x = self.years.get(year); + println!("x: {x:?}"); + x.and_then(|y| y.amounts.get(&post_key)) + .copied() + .unwrap_or(-0.0) + } else { + -0.0 + } + } +} + +pub fn read_budget_config(budget_config_file: &Path) -> Result> { + let toml_str = fs::read_to_string(budget_config_file)?; + let budget: Budget = toml::from_str(&toml_str)?; + Ok(budget) +} + +#[cfg(test)] +mod tests { + use super::*; + use rstest::rstest; + use std::path::PathBuf; + + fn read_budget_from_samples() -> Budget { + let file_path = "samples/budget.toml".to_string(); + let config_file = &PathBuf::from(file_path); + let budget = read_budget_config(config_file); + let Ok(budget) = budget else { + panic!("Invalid budget: {budget:#?}"); + }; + budget + } + + #[rstest] + fn can_read_from_sample_config_file() { + let budget = read_budget_from_samples(); + let Some(post) = budget.get_post_by_account("30100") else { + panic!("Account 30100 not found in budget"); + }; + assert_eq!("Ertrag Restauration", post.name); + } + + #[rstest] + fn can_run_closing() { + let budget = read_budget_from_samples(); + + let data = "samples/konten_202412_20250128132200.xls".to_string(); + let data_file = &PathBuf::from(data); + let ts = "20250120072900"; + let _result = do_closing_xml(data_file, budget, "202410", ts) + .expect("Unable to process sample data file."); + } +} diff --git a/src/main.rs b/src/main.rs index 3c13b71..9409fb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,9 +7,11 @@ use std::path::PathBuf; use chrono::Local; use clap::{Parser, Subcommand}; +use crate::close::close; use crate::export::export; use crate::prepare::prepare; +mod close; mod export; mod prepare; @@ -47,6 +49,13 @@ enum Commands { /// the intermediate file to process intermediate_file: PathBuf, }, + /// Run the monthly closing process + Close { + /// the budget configuration file in TOML format + budget_config_file: PathBuf, + /// The spreadsheet export file from the accounting software + accounts_file: PathBuf, + }, } fn main() -> Result<(), Box> { @@ -65,9 +74,13 @@ fn main() -> Result<(), Box> { ), Commands::Export { intermediate_file } => { let file_name = intermediate_file.as_os_str().to_str(); - let month = derive_month_from(file_name)?; + let month = derive_month_from_intermediate(file_name)?; export(intermediate_file, &month, ts) } + Commands::Close { + budget_config_file, + accounts_file, + } => close(budget_config_file, accounts_file, ts), } } @@ -113,8 +126,20 @@ fn intermediate_file(month: &String, ts: &String) -> PathBuf { } /// Derives the month from the intermediate filename (e.g. `intermediate_.csv` -> ``) -fn derive_month_from(file: Option<&str>) -> Result { - let min = "intermediate_yyyymm.csv"; +fn derive_month_from_intermediate(file: Option<&str>) -> Result { + derive_month_from(file, "intermediate", "csv") +} + +/// Derives the month from the account filename (e.g. `konten_.xls` -> ``) +/// # Errors +/// Will return `Err` if `file` does not provide the information on the month. +pub fn derive_month_from_accounts(file: Option<&str>, extension: &str) -> Result { + derive_month_from(file, "konten", extension) +} + +/// Derive the month from the file starting with specified prefix +fn derive_month_from(file: Option<&str>, prefix: &str, extension: &str) -> Result { + let min = format!("{prefix}_yyyymm.{extension}"); let underscore_index = min.find('_').unwrap(); let static_part = &min[0..=underscore_index]; let Some(filename) = file else { @@ -132,13 +157,13 @@ fn derive_month_from(file: Option<&str>) -> Result { let path = std::path::Path::new(filename); if path .extension() - .is_some_and(|ext| ext.eq_ignore_ascii_case("csv")) + .is_some_and(|ext| ext.eq_ignore_ascii_case(extension)) { let start_index = underscore_index + 1; let end_index = start_index + 5; Ok(filename[start_index..=end_index].into()) } else { - Err("Filename must have extension .csv.".to_string()) + Err(format!("Filename must have extension .{extension}.").to_string()) } } } @@ -194,8 +219,24 @@ mod tests { )] #[case(Some("intermediate_202303_.cs"), "Filename must have extension .csv.")] #[case(Some("intermediate_202303aaaa"), "Filename must have extension .csv.")] - fn test_derive_month_from(#[case] input: Option<&str>, #[case] expected: String) { - let result = derive_month_from(input); + fn test_derive_month_from_intermediate(#[case] input: Option<&str>, #[case] expected: String) { + let result = derive_month_from_intermediate(input); + match result { + Ok(month) => assert_eq!(month, expected), + Err(msg) => assert_eq!(msg, expected), + } + } + + #[rstest] + #[case(Some("konten_202409.xlsx"), "xlsx", "202409")] + #[case(Some("konten_202410_20241113090027.xlsx"), "xlsx", "202410")] + #[case(Some("konten_202410.xls"), "xls", "202410")] + fn test_derive_month_from_accounts( + #[case] input: Option<&str>, + #[case] extension: &str, + #[case] expected: String, + ) { + let result = derive_month_from_accounts(input, extension); match result { Ok(month) => assert_eq!(month, expected), Err(msg) => assert_eq!(msg, expected),