|
|
|
""" SuperX Parser """
|
|
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
from collections import namedtuple
|
|
|
|
|
|
|
|
from .helpers import get_sheet_of_file
|
|
|
|
from .exceptions import SuperXParserError
|
|
|
|
|
|
|
|
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",
|
|
|
|
],
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
def _check_export_headline(row):
|
|
|
|
""" checks the first line of the excel data if it's what we'd expect """
|
|
|
|
headline = row[0]
|
|
|
|
if headline != EXPECTED_HEADLINE:
|
|
|
|
raise SuperXParserError(f"unexpected headline: '{headline}'")
|
|
|
|
|
|
|
|
|
|
|
|
def _get_export_metadata(row):
|
|
|
|
""" extracts the metadata from the second row of the excel sheet """
|
|
|
|
data = row[0]
|
|
|
|
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 SuperXParserError(f"unexpected metadata: '{data}'")
|
|
|
|
if metadata["Gruppierung"] != EXPECTED_EXPORT_GROUPING:
|
|
|
|
raise SuperXParserError(
|
|
|
|
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 == EXPECTED_DATA_TABLE_HEADER:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
raise SuperXParserError("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]:
|
|
|
|
continue
|
|
|
|
yield SuperXData(*line[:10])
|
|
|
|
|
|
|
|
|
|
|
|
def parse_export_data(xls_sheet):
|
|
|
|
""" parses the exported superx data """
|
|
|
|
rows = xls_sheet.values
|
|
|
|
_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)
|
|
|
|
|
|
|
|
|
|
|
|
def parse_exported_file(file_path):
|
|
|
|
""" parses the budget file """
|
|
|
|
sheet = get_sheet_of_file(file_path, name=None)
|
|
|
|
return parse_export_data(sheet)
|