From db3463bff08620c5da86b2d7c9761b60d30ffd7e Mon Sep 17 00:00:00 2001 From: Holger Frey Date: Mon, 23 Mar 2020 18:22:26 +0100 Subject: [PATCH] first superx parser version --- superx_budget/__init__.py | 85 ++++++++++ ...gsnachweis_und_Kassenstand_SAP_Zahlen.xlsx | Bin 0 -> 22647 bytes tests/test_superx_budget.py | 153 +++++++++++++++++- 3 files changed, 236 insertions(+), 2 deletions(-) create mode 100644 test data/Verwendungsnachweis_und_Kassenstand_SAP_Zahlen.xlsx diff --git a/superx_budget/__init__.py b/superx_budget/__init__.py index 76d8d7c..b7def04 100644 --- a/superx_budget/__init__.py +++ b/superx_budget/__init__.py @@ -4,3 +4,88 @@ Creating a budget overview from a SuperX export """ __version__ = "0.0.1" + +from datetime import datetime +from collections import namedtuple + +EXPECTED_HEADLINE = "Verwendungsnachweis und Kassenstand SAP" +EXPECTED_METADATA_KEYS = {"Haushaltsjahr", "Stand", "Gruppierung"} +EXPECTED_EXPORT_GROUPING = "automatisch" +EXPECTED_DATA_TABLE_HEADER = "Kostenstelle" + + +SuperXResult = namedtuple( + "SuperXResult", ["account_year", "export_date", "data"] +) +SuperXData = namedtuple( + "SuperXData", + [ + "cost_center", + "fonds", + "project", + "kind", + "budget_year", + "obligo", + "expenses", + "revenue_actual", + "revenue_target", + "acutal_value", + ], +) + + +class SuperXError(ValueError): + pass + + +def _check_export_headline(row): + """ checks the first line of the excel data if it's what we'd expect """ + headline = row[0].value + if headline != EXPECTED_HEADLINE: + raise SuperXError(f"unexpected headline: '{headline}'") + + +def _get_export_metadata(row): + """ extracts the metadata from the second row of the excel sheet """ + data = row[0].value + entries = data.split(";") + parts = [entry.split(":", 1) for entry in entries] + metadata = {key.strip(): value.strip() for key, value in parts} + if EXPECTED_METADATA_KEYS - set(metadata.keys()): + raise SuperXError(f"unexpected metadata: '{data}'") + if metadata["Gruppierung"] != EXPECTED_EXPORT_GROUPING: + raise SuperXError(f"unexpected grouping: {metadata['Gruppierung']}") + return SuperXResult( + metadata["Haushaltsjahr"], + datetime.strptime(metadata["Stand"], "%d.%m.%Y"), + None, + ) + + +def _skip_export_data_until_table_header(rows): + """ skip rows until data table headers """ + for line in rows: + first_cell = line[0] + if first_cell.value == EXPECTED_DATA_TABLE_HEADER: + break + else: + raise SuperXError("could not find table header") + + +def _parse_data_table(rows): + """ parses non-empty lines of the data table """ + for line in rows: + if not line[0].value: + continue + data = [cell.value for cell in line[:10]] + yield SuperXData(*data) + + +def parse_export_data(xls_sheet): + """ parses the exported superx data """ + rows = xls_sheet.rows + _check_export_headline(next(rows)) + metadata = _get_export_metadata(next(rows)) + _skip_export_data_until_table_header(rows) + data = list(_parse_data_table(rows)) + return SuperXResult(metadata.account_year, metadata.export_date, data) diff --git a/test data/Verwendungsnachweis_und_Kassenstand_SAP_Zahlen.xlsx b/test data/Verwendungsnachweis_und_Kassenstand_SAP_Zahlen.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..52a877f4d49c637df53cf202b10add8ede97f41b GIT binary patch literal 22647 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