Moved currency formatting/parsing into generic util package
This commit is contained in:
parent
a4930d268d
commit
b1a56f59fc
9 changed files with 62 additions and 61 deletions
2
doc
2
doc
|
@ -1 +1 @@
|
||||||
Subproject commit 92e168a288b396a7a97d200b80affdf8690bd03d
|
Subproject commit 5335524d3e57c7551f31c7e21fc04c464b23429a
|
0
matemat/util/__init__.py
Normal file
0
matemat/util/__init__.py
Normal file
56
matemat/util/currency_format.py
Normal file
56
matemat/util/currency_format.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
|
||||||
|
|
||||||
|
def format_chf(value: int, with_currencysign: bool = True) -> str:
|
||||||
|
"""
|
||||||
|
Formats a centime value into a commonly understood representation ("CHF -13.37").
|
||||||
|
|
||||||
|
:param value: The value to format, in centimes.
|
||||||
|
:param with_currencysign: Whether to include the currency prefix ("CHF ") in the output.
|
||||||
|
:return: A human-readable string representation.
|
||||||
|
"""
|
||||||
|
sign: str = ''
|
||||||
|
if value < 0:
|
||||||
|
# As // and % round towards -Inf, convert into a positive value and prepend the negative sign
|
||||||
|
sign = '-'
|
||||||
|
value = -value
|
||||||
|
# Split into full francs and fractions (centimes)
|
||||||
|
full: int = value // 100
|
||||||
|
frac: int = value % 100
|
||||||
|
csign: str = 'CHF ' if with_currencysign else ''
|
||||||
|
# Put it all together; centimes are always padded with 2 zeros
|
||||||
|
return f'{csign}{sign}{full}.{frac:02}'
|
||||||
|
|
||||||
|
|
||||||
|
def parse_chf(value: str) -> int:
|
||||||
|
"""
|
||||||
|
Parse a currency value into machine-readable format (integer centimes). The prefix "CHF", the decimal point, and
|
||||||
|
digits after the decimal point are optional.
|
||||||
|
|
||||||
|
:param value: The value to parse.
|
||||||
|
:return: An integer representation of the value.
|
||||||
|
:raises: Value error: If more than two digits after the decimal point are present.
|
||||||
|
"""
|
||||||
|
# Remove optional leading "CHF" and strip whitespace
|
||||||
|
value = value.strip()
|
||||||
|
if value.startswith('CHF'):
|
||||||
|
value = value[3:]
|
||||||
|
value = value.strip()
|
||||||
|
if '.' not in value:
|
||||||
|
# already is an integer; parse and turn into centimes
|
||||||
|
return int(value, 10) * 100
|
||||||
|
# Split at the decimal point
|
||||||
|
full, frac = value.split('.', 1)
|
||||||
|
if len(frac) > 2:
|
||||||
|
raise ValueError('Needs max. 2 digits after decimal point')
|
||||||
|
elif len(frac) < 2:
|
||||||
|
# Right-pad fraction with zeros ("x." -> "x.00", "x.x" -> "x.x0")
|
||||||
|
frac = frac + '0' * (2 - len(frac))
|
||||||
|
# Parse both parts
|
||||||
|
ifrac: int = int(frac, 10)
|
||||||
|
ifull: int = int(full, 10)
|
||||||
|
if ifrac < 0:
|
||||||
|
raise ValueError('Fraction part must not be negative.')
|
||||||
|
if full.startswith('-'):
|
||||||
|
ifrac = -ifrac
|
||||||
|
# Combine into centime integer
|
||||||
|
return ifull * 100 + ifrac
|
0
matemat/util/test/__init__.py
Normal file
0
matemat/util/test/__init__.py
Normal file
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from matemat.webserver.util import format_chf, parse_chf
|
from matemat.util.currency_format import format_chf, parse_chf
|
||||||
|
|
||||||
|
|
||||||
class TestCurrencyFormat(unittest.TestCase):
|
class TestCurrencyFormat(unittest.TestCase):
|
|
@ -17,7 +17,8 @@ import jinja2
|
||||||
from matemat import __version__ as matemat_version
|
from matemat import __version__ as matemat_version
|
||||||
from matemat.exceptions import HttpException
|
from matemat.exceptions import HttpException
|
||||||
from matemat.webserver import RequestArguments, PageletResponse, RedirectResponse, TemplateResponse
|
from matemat.webserver import RequestArguments, PageletResponse, RedirectResponse, TemplateResponse
|
||||||
from matemat.webserver.util import parse_args, format_chf
|
from matemat.webserver.util import parse_args
|
||||||
|
from matemat.util.currency_format import format_chf
|
||||||
|
|
||||||
#
|
#
|
||||||
# Python internal class hacks
|
# Python internal class hacks
|
||||||
|
|
|
@ -8,7 +8,7 @@ from matemat.webserver import pagelet, RequestArguments, PageletResponse, Redire
|
||||||
from matemat.db import MatematDatabase
|
from matemat.db import MatematDatabase
|
||||||
from matemat.primitives import Product
|
from matemat.primitives import Product
|
||||||
from matemat.exceptions import DatabaseConsistencyError, HttpException
|
from matemat.exceptions import DatabaseConsistencyError, HttpException
|
||||||
from matemat.webserver.util import parse_chf
|
from matemat.util.currency_format import parse_chf
|
||||||
|
|
||||||
|
|
||||||
@pagelet('/modproduct')
|
@pagelet('/modproduct')
|
||||||
|
|
|
@ -8,7 +8,7 @@ from matemat.webserver import pagelet, RequestArguments, PageletResponse, Redire
|
||||||
from matemat.db import MatematDatabase
|
from matemat.db import MatematDatabase
|
||||||
from matemat.primitives import User
|
from matemat.primitives import User
|
||||||
from matemat.exceptions import DatabaseConsistencyError, HttpException
|
from matemat.exceptions import DatabaseConsistencyError, HttpException
|
||||||
from matemat.webserver.util import parse_chf
|
from matemat.util.currency_format import parse_chf
|
||||||
|
|
||||||
|
|
||||||
@pagelet('/moduser')
|
@pagelet('/moduser')
|
||||||
|
|
|
@ -136,59 +136,3 @@ def parse_args(request: str, postbody: Optional[bytes] = None, enctype: str = 't
|
||||||
raise ValueError(f'Unsupported Content-Type: {enctype}')
|
raise ValueError(f'Unsupported Content-Type: {enctype}')
|
||||||
# Return the path and the parsed arguments
|
# Return the path and the parsed arguments
|
||||||
return tokens.path, args
|
return tokens.path, args
|
||||||
|
|
||||||
|
|
||||||
def format_chf(value: int, with_currencysign: bool = True) -> str:
|
|
||||||
"""
|
|
||||||
Formats a centime value into a commonly understood representation ("CHF -13.37").
|
|
||||||
|
|
||||||
:param value: The value to format, in centimes.
|
|
||||||
:param with_currencysign: Whether to include the currency prefix ("CHF ") in the output.
|
|
||||||
:return: A human-readable string representation.
|
|
||||||
"""
|
|
||||||
sign: str = ''
|
|
||||||
if value < 0:
|
|
||||||
# As // and % round towards -Inf, convert into a positive value and prepend the negative sign
|
|
||||||
sign = '-'
|
|
||||||
value = -value
|
|
||||||
# Split into full francs and fractions (centimes)
|
|
||||||
full: int = value // 100
|
|
||||||
frac: int = value % 100
|
|
||||||
csign: str = 'CHF ' if with_currencysign else ''
|
|
||||||
# Put it all together; centimes are always padded with 2 zeros
|
|
||||||
return f'{csign}{sign}{full}.{frac:02}'
|
|
||||||
|
|
||||||
|
|
||||||
def parse_chf(value: str) -> int:
|
|
||||||
"""
|
|
||||||
Parse a currency value into machine-readable format (integer centimes). The prefix "CHF", the decimal point, and
|
|
||||||
digits after the decimal point are optional.
|
|
||||||
|
|
||||||
:param value: The value to parse.
|
|
||||||
:return: An integer representation of the value.
|
|
||||||
:raises: Value error: If more than two digits after the decimal point are present.
|
|
||||||
"""
|
|
||||||
# Remove optional leading "CHF" and strip whitespace
|
|
||||||
value = value.strip()
|
|
||||||
if value.startswith('CHF'):
|
|
||||||
value = value[3:]
|
|
||||||
value = value.strip()
|
|
||||||
if '.' not in value:
|
|
||||||
# already is an integer; parse and turn into centimes
|
|
||||||
return int(value, 10) * 100
|
|
||||||
# Split at the decimal point
|
|
||||||
full, frac = value.split('.', 1)
|
|
||||||
if len(frac) > 2:
|
|
||||||
raise ValueError('Needs max. 2 digits after decimal point')
|
|
||||||
elif len(frac) < 2:
|
|
||||||
# Right-pad fraction with zeros ("x." -> "x.00", "x.x" -> "x.x0")
|
|
||||||
frac = frac + '0' * (2 - len(frac))
|
|
||||||
# Parse both parts
|
|
||||||
ifrac: int = int(frac, 10)
|
|
||||||
ifull: int = int(full, 10)
|
|
||||||
if ifrac < 0:
|
|
||||||
raise ValueError('Fraction part must not be negative.')
|
|
||||||
if full.startswith('-'):
|
|
||||||
ifrac = -ifrac
|
|
||||||
# Combine into centime integer
|
|
||||||
return ifull * 100 + ifrac
|
|
||||||
|
|
Loading…
Reference in a new issue