Skip to content

Commit 2ac970d

Browse files
committed
improved processing of imported decimal numbers
1 parent 7647734 commit 2ac970d

File tree

2 files changed

+42
-25
lines changed

2 files changed

+42
-25
lines changed

compiler/xrechnung.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,37 @@ def _read_xlsx_sheet_columns(
131131

132132
return target
133133

134+
def _harmonise_imported_data(self):
135+
"""Method to cast the data type of numerical data to `decimal.Decimal`,
136+
and, the data of type `datetime.datetime` to ISO formatted date
137+
strings. This method affects private members `self._invoice` and
138+
`self._invoice_lines`.
139+
140+
Invoice (line) monetary amounts are always rounded to two decimal places.
141+
Item price figures can have more that two decimal figures and should be
142+
represented by decimal numbers with at least two decimal figures.
143+
"""
144+
145+
decimal.getcontext().rounding = decimal.ROUND_HALF_UP
146+
147+
amount_keyword_pattern = re.compile(r"^xr\:[A-Za-z0-9\_]+_amount$")
148+
price_keyword_pattern = re.compile(r"^xr\:[A-Za-z0-9\_]+_price$")
149+
other_decimal_keyword_pattern = re.compile(
150+
r"^xr\:[A-Za-z0-9\_]+_VAT_rate$|^xr\:[A-Za-z0-9\_]+_quantity$"
151+
)
152+
for d in [self._invoice] + self._invoice_lines:
153+
for k, v in d.items():
154+
if type(v) == datetime.datetime:
155+
d[k] = v.date().isoformat()
156+
elif amount_keyword_pattern.match(k):
157+
d[k] = round(decimal.Decimal(v), 2)
158+
elif price_keyword_pattern.match(k):
159+
d[k] = decimal.Decimal(v)
160+
if d[k].as_tuple().exponent > -2:
161+
d[k] = round(d[k], 2)
162+
elif other_decimal_keyword_pattern.match(k):
163+
d[k] = decimal.Decimal(v)
164+
134165
def _calculate_tax_total(self):
135166
"""From list of dictionaries `self._invoice_lines` determine the list of
136167
dictionaries `self._tax_subtotals`, and, add key 'xr:Invoice_total_VAT_amount'
@@ -196,7 +227,7 @@ def _calculate_monetary_total(self):
196227
assert "xr:Sum_of_Invoice_line_net_amount" in self._invoice
197228
assert "xr:Invoice_total_VAT_amount" in self._invoice
198229

199-
# BT-106 Summe der Nettobeträge aller Rechnungspositionen"
230+
# BT-106 Summe der Nettobeträge aller Rechnungspositionen
200231
sum_of_invoice_line_net_amounts = decimal.Decimal(
201232
self._invoice.get("xr:Sum_of_Invoice_line_net_amount")
202233
)
@@ -361,7 +392,7 @@ def _prepare_xrechnung(self) -> list:
361392
# ---------- ---------- public methods ---------- ----------
362393

363394
def read_xlsx(self, workbook_file: Path):
364-
"""Obtain invoice data from an XLSX file that has the required
395+
"""Obtain invoice data from an XLSX workbook file, which has the required
365396
format (see the corresponding template file).
366397
"""
367398

@@ -387,18 +418,10 @@ def read_xlsx(self, workbook_file: Path):
387418

388419
# (1) general invoice data
389420
if sheet_name == "Invoice":
390-
assert "xr:Invoice_currency_code" in sheet_data.index
391421
assert "Value" in wb[sheet_name].columns
392422
sheet_data = sheet_data[sheet_data.Value.notna()]
393423
self._invoice = sheet_data["Value"].to_dict()
394-
395-
decimal.getcontext().rounding = decimal.ROUND_HALF_UP
396-
for k, v in self._invoice.items():
397-
if self._amount_keyword_pattern.match(k):
398-
self._invoice[k] = round(decimal.Decimal(v), 2)
399-
if type(v) == datetime.datetime:
400-
self._invoice[k] = v.date().isoformat()
401-
self._currency_id = self._invoice["xr:Invoice_currency_code"]
424+
self._currency_id = self._invoice.get("xr:Invoice_currency_code", "EUR")
402425

403426
# (2) invoice lines
404427
elif sheet_name == "InvoiceLines":
@@ -410,6 +433,7 @@ def read_xlsx(self, workbook_file: Path):
410433
sheet_data, workbook_file.parent
411434
)
412435

436+
self._harmonise_imported_data()
413437
self._calculate_tax_total()
414438
self._calculate_monetary_total()
415439

@@ -427,21 +451,13 @@ def read_json(self, json_file: Path):
427451
# (1) invoice currency
428452
self._currency_id = self._invoice.get("xr:Invoice_currency_code", "EUR")
429453

430-
# (2) invoice-lines
454+
# (2) invoice lines
431455
self._invoice_lines = [
432456
{k: v for k, v in invoice_line.items() if v is not None}
433457
for invoice_line in self._invoice.pop("InvoiceLines", [])
434458
]
435459
self._logger.info(f"found {len(self._invoice_lines)} invoice line(s)")
436460

437-
decimal.getcontext().rounding = decimal.ROUND_HALF_UP
438-
for invoice_line in self._invoice_lines:
439-
for k, v in invoice_line.items():
440-
if self._amount_keyword_pattern.match(k):
441-
invoice_line[k] = round(decimal.Decimal(v), 2)
442-
elif self._decimal_number_keyword_pattern.match(k):
443-
invoice_line[k] = decimal.Decimal(v)
444-
445461
# (3) additional document references
446462
self._additional_document_references = [
447463
{k: v for k, v in adr.items() if v is not None}
@@ -463,6 +479,7 @@ def read_json(self, json_file: Path):
463479
adr for adr in self._additional_document_references if adr is not None
464480
]
465481

482+
self._harmonise_imported_data()
466483
self._calculate_tax_total()
467484
self._calculate_monetary_total()
468485

template.json

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,23 +44,23 @@
4444
"xr:Invoice_line_identifier": "1",
4545
"xr:Invoiced_quantity": "1.5",
4646
"xr:Invoiced_quantity_unit_of_measure_code": "HUR",
47-
"xr:Invoice_line_net_amount": "375",
47+
"xr:Invoice_line_net_amount": 375,
4848
"xr:Invoice_line_period_start_date": "2025-01-09",
4949
"xr:Invoice_line_period_end_date": "2025-01-16",
50-
"xr:Item_net_price": "250.00",
50+
"xr:Item_net_price": 250,
5151
"xr:Invoiced_item_VAT_category_code": "S",
52-
"xr:Invoiced_item_VAT_rate": "19",
52+
"xr:Invoiced_item_VAT_rate": 19,
5353
"xr:Item_name": "Rechtsberatung",
5454
"xr:Item_description": "Entgeltpflichtiger Zeitaufwand für Rechtsberatung im Fall A"
5555
},
5656
{
5757
"xr:Invoice_line_identifier": "2",
58-
"xr:Invoiced_quantity": "1",
58+
"xr:Invoiced_quantity": 1,
5959
"xr:Invoiced_quantity_unit_of_measure_code": "C62",
6060
"xr:Invoice_line_net_amount": "25",
6161
"xr:Invoice_line_period_start_date": null,
6262
"xr:Invoice_line_period_end_date": null,
63-
"xr:Item_net_price": "25.00",
63+
"xr:Item_net_price": "25.000",
6464
"xr:Invoiced_item_VAT_category_code": "S",
6565
"xr:Invoiced_item_VAT_rate": "19",
6666
"xr:Item_name": "Handbuch",

0 commit comments

Comments
 (0)