Public release from ruodoo-project: 19.0 - 2026-05-31 21:19:12 UTC

This commit is contained in:
CI Publish Bot
2026-05-31 21:19:21 +00:00
commit aa4214c195
1213 changed files with 183945 additions and 0 deletions

View File

@ -0,0 +1,31 @@
# Report monetary helpers
Adds in report's rendering context 2 methods for printing amount in words and
1 method for formatting numbers representation/
They are accessible from template like this:
number2words(amount_variable, lang="en", to="cardinal")
currency2words(amount_variable, lang="en", to="cardinal", currency="RUB")
format_number(amount_variable, r_acc=2, dec_sep=",", div_by_3=False)
"amount_variable" should be of "int", "float" or validate "string" type.
Variants for "to" attribute:
'cardinal', 'ordinal', 'ordinal_num', 'year', 'currency'.
"cardinal" is default value.
"lang" attribute. 25 languages are supported:
'ar', 'en', 'en_IN', 'fr', 'fr_CH', 'fr_DZ', 'de', 'es', 'es_CO', 'es_VE',
'id', 'lt', 'lv', 'pl', 'ru', 'sl', 'no', 'dk', 'pt_BR', 'he', 'it',
'vi_VN', 'tr', 'nl', 'uk'.
"ru" is default value.
"currency" attribute: for russian language there are "RUB" and "EUR" currencies.
"RUB" is default value.
Full info about currencies features see in "num2words" python module.
"r_acc" attribute: Round accuracy for amount_variable, type int. Default is 2.
"dec_sep" attribute: Decimal separator symbol to set, type str. Default is ",".
"div_by_3" attribute: Bool flag to divide number's integer part by 3 digits with whitespaces.
Default is True.

View File

@ -0,0 +1,2 @@
from . import models
from . import utils

View File

@ -0,0 +1,35 @@
{
"name": "Report monetary helpers",
"summary": """""",
"description": """
Adds in report's rendering context 2 methods for printing amount in words.
They are accessible from template like this:
number2words(amount_variable, lang="en", to="cardinal")
currency2words(amount_variable, lang="en", to="cardinal", currency="RUB")
"amount_variable" should be of "int", "float" or validate "string" type.
Variants for "to" attribute:
'cardinal', 'ordinal', 'ordinal_num', 'year', 'currency'.
"cardinal" is default value.
"lang" attribute. 25 languages are supported:
'ar', 'en', 'en_IN', 'fr', 'fr_CH', 'fr_DZ', 'de', 'es', 'es_CO', 'es_VE',
'id', 'lt', 'lv', 'pl', 'ru', 'sl', 'no', 'dk', 'pt_BR', 'he', 'it',
'vi_VN', 'tr', 'nl', 'uk'.
"ru" is default value.
"currency" attribute: for russian language there are "RUB" and "EUR" currencies.
"RUB" is default value.
Full info about currencies features see in "num2words" python module.
""",
"author": "RYDLAB",
"website": "http://rydlab.ru",
"category": "Technical",
"version": "19.0.2025.11.11",
"license": "LGPL-3",
"depends": ["base"],
"external_dependencies": {"python": ["num2words"]},
"data": [],
}

View File

@ -0,0 +1,21 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * report_monetary_helpers
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-29 08:17+0000\n"
"PO-Revision-Date: 2022-12-29 08:17+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: report_monetary_helpers
#: model:ir.model,name:report_monetary_helpers.model_ir_actions_report
msgid "Report Action"
msgstr ""

View File

@ -0,0 +1,21 @@
# Translation of Odoo Server.
# This file contains the translation of the following modules:
# * report_monetary_helpers
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 16.0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-12-29 08:18+0000\n"
"PO-Revision-Date: 2022-12-29 08:18+0000\n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: report_monetary_helpers
#: model:ir.model,name:report_monetary_helpers.model_ir_actions_report
msgid "Report Action"
msgstr "Отчет о действияx "

View File

@ -0,0 +1 @@
from . import ir_actions_report

View File

@ -0,0 +1,24 @@
from logging import getLogger
from odoo import api, models
from ..utils.num2words import num2words_, num2words_currency
from ..utils.format_number import format_number
_logger = getLogger(__name__)
class IrActionsReport(models.Model):
_inherit = "ir.actions.report"
@api.model
def _get_rendering_context(self, report, docids, data):
data = super()._get_rendering_context(report, docids, data)
data.update(
{
"number2words": num2words_,
"currency2words": num2words_currency,
"format_number": format_number,
}
)
return data

View File

@ -0,0 +1,2 @@
from . import format_number
from . import num2words

View File

@ -0,0 +1,63 @@
from math import modf
from odoo.exceptions import ValidationError
def _validate_number_arg(number: int or float or str) -> int or float:
"""
Raise ValidationError if number have wrong type or string is not a valid number.
Returns int or float.
"""
if type(number) == str:
check_comma = number.split(",")
check_dot = number.split(".")
if len(check_comma) in (1, 2) and all([part.isdigit() for part in check_comma]):
return float(".".join(check_comma))
elif len(check_dot) in (1, 2) and all([part.isdigit() for part in check_dot]):
return float(number)
else:
raise ValidationError(
f"'format_number' method got an argument of string type which is not valid number: {number}"
)
if type(number) in (int, float):
return number
raise ValidationError(
f"'format_number' method got an argument of wrong type '{type(number)}'"
)
def format_number(
number: int or float or str,
r_acc: int = 2,
dec_sep: str = ",",
div_by_3: bool = True,
) -> str:
"""
Formats float and int values representation. Returns string.
:param r_acc: int, Round accuracy, default is 2.
:param dec_sep: str, separator between integer and fractional parts
:param div_by_3: bool, inserts space after each 3 digits in integer part.
}
"""
valid_number = _validate_number_arg(number)
fract_part, int_part = modf(valid_number)
new_fract_part = str(round(fract_part, r_acc))[2:].ljust(r_acc, "0")
if div_by_3:
# convert to str, cut off ".0" and reverse
int_part = str(int_part)[:-2][::-1]
counter_3 = 0
new_int_part = ""
for num in int_part:
if counter_3 < 3:
divider = ""
else:
divider = " "
counter_3 = 0
new_int_part = divider.join([new_int_part, num])
counter_3 += 1
# Reverse backward
new_int_part = new_int_part[::-1]
else:
new_int_part = str(int_part)[:-2]
return dec_sep.join([new_int_part, new_fract_part])

View File

@ -0,0 +1,51 @@
from decimal import Decimal
from num2words import num2words
from num2words import CONVERTES_TYPES
# Can use params:
# ~ number: int, float or validate string
# ~ to: num2words.CONVERTES_TYPES
# ~ lang: num2words.CONVERTER_CLASSES
# ~ currency: num2words.CONVERTER_CLASSES.CURRENCY_FORMS
def num2words_(number, **kwargs):
if _perform_convert(number):
if "lang" not in kwargs:
kwargs["lang"] = "ru"
if "to" not in kwargs or kwargs["to"] not in CONVERTES_TYPES:
kwargs["to"] = "cardinal"
return num2words(number, **kwargs)
def num2words_currency(number, **kwargs):
if _perform_convert(number):
if "lang" not in kwargs:
kwargs["lang"] = "ru"
if "to" not in kwargs or kwargs["to"] not in CONVERTES_TYPES:
kwargs["to"] = "currency"
if "currency" not in kwargs:
kwargs["currency"] = "RUB"
result = num2words(number, **kwargs)
total = result.split(",")[0]
part_word = result.split()[-1]
part_number = Decimal(str(number)) % 1
return "{total}, {part_n} {part_w}".format(
total=total.capitalize(),
part_n="{:02d}".format(int(part_number * 100)),
part_w=part_word,
)
def _perform_convert(number):
if isinstance(number, int) or isinstance(number, float):
return True
if isinstance(number, str):
try:
number = float(number)
return True
except ValueError:
return False
return False