146 lines
4.8 KiB
Python
146 lines
4.8 KiB
Python
# barbacompta - invoicing for dummies
|
|
# Copyright (C) 2019-2020 Entr'ouvert
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify it
|
|
# under the terms of the GNU Affero General Public License as published
|
|
# by the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
from decimal import Decimal, InvalidOperation
|
|
|
|
from django import forms
|
|
from django.core import validators
|
|
from django.core.exceptions import ValidationError
|
|
from django.db import models
|
|
from django.utils.six import text_type
|
|
from django.utils.translation import ugettext_lazy as _
|
|
|
|
|
|
class PercentagePerYear(list):
|
|
def __str__(self):
|
|
return ','.join(map(lambda p: ':'.join(map(str, [p[0], int(100 * p[1])])), self))
|
|
|
|
|
|
class EuroField(models.DecimalField):
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs["max_digits"] = 8
|
|
kwargs["decimal_places"] = 2
|
|
super().__init__(*args, **kwargs)
|
|
|
|
|
|
def assertion_error_to_validation_error(fun):
|
|
def f(*args, **kwargs):
|
|
try:
|
|
return fun(*args, **kwargs)
|
|
except (AssertionError, ValueError) as e:
|
|
raise ValidationError(*e.args)
|
|
|
|
return f
|
|
|
|
|
|
@assertion_error_to_validation_error
|
|
def check_percentage_per_year(value):
|
|
years = [a for a, b in value]
|
|
percentages = [b for a, b in value]
|
|
# ordered
|
|
assert years == sorted(years), "years are not ordered"
|
|
# sum equals 100
|
|
assert sum(percentages) == 1, "percentage does not sum to 100"
|
|
# no duplicate year
|
|
assert len(years) == len(set(years)), "years are not unique"
|
|
# consecutive
|
|
assert years == list(range(years[0], years[0] + len(years))), 'years are not consecutive'
|
|
|
|
|
|
def parse_percentage_per_year(value):
|
|
msg = _("field must be numeric values separated by commas")
|
|
values = value.split(",")
|
|
decimals = []
|
|
for value in values:
|
|
try:
|
|
year, decimal = value.split(":")
|
|
except ValueError:
|
|
raise ValidationError(msg)
|
|
try:
|
|
year = int(year.strip())
|
|
except ValueError:
|
|
raise ValidationError(msg)
|
|
try:
|
|
decimal = Decimal(decimal.strip()) / 100
|
|
except InvalidOperation:
|
|
raise ValidationError(msg)
|
|
decimals.append((year, decimal))
|
|
return PercentagePerYear(decimals)
|
|
|
|
|
|
class PercentagePerYearFormField(forms.Field):
|
|
default_error_messages = {
|
|
'invalid': _('field must be numeric values separated by commas'),
|
|
}
|
|
|
|
def to_python(self, value):
|
|
if value in validators.EMPTY_VALUES:
|
|
return None
|
|
if isinstance(PercentagePerYear, value):
|
|
return value
|
|
if not isinstance(text_type, value):
|
|
raise ValidationError(self.default_error_messages["invalid"])
|
|
return parse_percentage_per_year(value)
|
|
|
|
|
|
class PercentagePerYearField(models.Field):
|
|
default_validators = [check_percentage_per_year]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
kwargs["max_length"] = 64
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def from_db_value(self, value, expression, connection):
|
|
return self.to_python(value)
|
|
|
|
def to_python(self, value):
|
|
if isinstance(value, PercentagePerYear):
|
|
return value
|
|
if value is not None:
|
|
percentage_per_year = []
|
|
pairs = value.split(",")
|
|
for pair in pairs:
|
|
try:
|
|
year, percentage = map(value.__class__.strip, pair.split(":"))
|
|
except ValueError:
|
|
raise ValidationError(PercentagePerYearFormField.default_error_messages["invalid"])
|
|
year = int(year)
|
|
percentage = Decimal(percentage) / Decimal(100)
|
|
percentage_per_year.append((year, percentage))
|
|
try:
|
|
return PercentagePerYear(percentage_per_year)
|
|
except forms.ValidationError:
|
|
return value
|
|
return value
|
|
|
|
def get_prep_value(self, value):
|
|
if value is not None:
|
|
if isinstance(value, str):
|
|
value = parse_percentage_per_year(value)
|
|
else:
|
|
value = PercentagePerYear(value)
|
|
value = str(value)
|
|
return super().get_prep_value(value)
|
|
|
|
def get_internal_type(self):
|
|
return "CharField"
|
|
|
|
def formfield(self, **kwargs):
|
|
defaults = {"form_class": PercentagePerYearFormField}
|
|
defaults.update(kwargs)
|
|
return super().formfield(**kwargs)
|