From cd2a82f61ceece53e036413042b721111c61547e Mon Sep 17 00:00:00 2001 From: laurentbarontini Date: Fri, 27 Feb 2026 05:56:00 +0100 Subject: [PATCH] 27.02.26 --- modules/purchase_trade/numbers_to_words.py | 134 +++++++++++++++------ modules/purchase_trade/sale.py | 6 +- 2 files changed, 101 insertions(+), 39 deletions(-) diff --git a/modules/purchase_trade/numbers_to_words.py b/modules/purchase_trade/numbers_to_words.py index 518c66a..1637b19 100644 --- a/modules/purchase_trade/numbers_to_words.py +++ b/modules/purchase_trade/numbers_to_words.py @@ -9,7 +9,7 @@ UNITS = ( TENS = "ZERO TEN TWENTY THIRTY FORTY FIFTY SIXTY SEVENTY EIGHTY NINETY".split() -def _under_thousand(n, use_and=True): +def _under_thousand(n): words = [] hundreds = n // 100 @@ -18,7 +18,7 @@ def _under_thousand(n, use_and=True): if hundreds: words.append(UNITS[hundreds]) words.append("HUNDRED") - if remainder and use_and: + if remainder: words.append("AND") if remainder: @@ -32,46 +32,108 @@ def _under_thousand(n, use_and=True): return " ".join(words) -def number_to_words(n, decimals_mode="digit", use_and=True): +def integer_to_words(n): + if n == 0: + return "ZERO" + + parts = [] + + millions = n // 1_000_000 + thousands = (n // 1_000) % 1_000 + remainder = n % 1_000 + + if millions: + parts.append(_under_thousand(millions)) + parts.append("MILLION") + + if thousands: + parts.append(_under_thousand(thousands)) + parts.append("THOUSAND") + + if remainder: + parts.append(_under_thousand(remainder)) + + return " ".join(parts) + + +# ============================== +# 💰 MONETARY +# ============================== + +def amount_to_currency_words(amount, + major_singular="DOLLAR", + major_plural="DOLLARS", + minor_singular="CENT", + minor_plural="CENTS"): """ - decimals_mode: - - "digit" → ONE HUNDRED POINT TWO FIVE - - "fraction" → ONE HUNDRED AND 25/100 + Example: + 1.20 → ONE DOLLAR AND TWENTY CENTS + 2.00 → TWO DOLLARS """ - n = Decimal(str(n)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) + amount = Decimal(str(amount)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) - integer_part = int(n) - decimal_part = int((n - integer_part) * 100) + integer_part = int(amount) + decimal_part = int((amount - integer_part) * 100) - if integer_part == 0: - words = "ZERO" + words = [] + + # Major unit + major_words = integer_to_words(integer_part) + words.append(major_words) + + if integer_part == 1: + words.append(major_singular) else: - parts = [] + words.append(major_plural) - millions = integer_part // 1_000_000 - thousands = (integer_part // 1_000) % 1_000 - remainder = integer_part % 1_000 - - if millions: - parts.append(_under_thousand(millions, use_and)) - parts.append("MILLION") - - if thousands: - parts.append(_under_thousand(thousands, use_and)) - parts.append("THOUSAND") - - if remainder: - parts.append(_under_thousand(remainder, use_and)) - - words = " ".join(parts) - - # Gestion décimales + # Minor unit if decimal_part: - if decimals_mode == "digit": - digits = " ".join(UNITS[int(d)] for d in f"{decimal_part:02d}") - words = f"{words} POINT {digits}" - elif decimals_mode == "fraction": - words = f"{words} AND {decimal_part:02d}/100" + words.append("AND") + minor_words = integer_to_words(decimal_part) + words.append(minor_words) - return words.upper() \ No newline at end of file + if decimal_part == 1: + words.append(minor_singular) + else: + words.append(minor_plural) + + return " ".join(words) + + +# ============================== +# ⚖️ QUANTITY WITH UNIT +# ============================== + +def quantity_to_words(quantity, + unit_singular="METRIC TON", + unit_plural="METRIC TONS"): + """ + Example: + 1 → ONE METRIC TON + 23 → TWENTY THREE METRIC TONS + 1.5 → ONE POINT FIVE METRIC TONS + """ + + quantity = Decimal(str(quantity)).normalize() + + if quantity == quantity.to_integral(): + integer_part = int(quantity) + words = integer_to_words(integer_part) + + if integer_part == 1: + unit = unit_singular + else: + unit = unit_plural + + return f"{words} {unit}" + + else: + # lecture décimale simple pour quantités + integer_part = int(quantity) + decimal_str = str(quantity).split(".")[1] + + words = integer_to_words(integer_part) + decimal_words = " ".join(UNITS[int(d)] for d in decimal_str) + + return f"{words} POINT {decimal_words} {unit_plural}" \ No newline at end of file diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index 8839fd8..a03e319 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -15,7 +15,7 @@ import datetime import logging import json from trytond.exceptions import UserWarning, UserError -from trytond.modules.purchase_trade.numbers_to_words import number_to_words +from trytond.modules.purchase_trade.numbers_to_words import quantity_to_words, amount_to_currency_words logger = logging.getLogger(__name__) @@ -268,14 +268,14 @@ class Sale(metaclass=PoolMeta): @property def report_qt(self): if self.lines: - return number_to_words(self.lines[0].quantity) + return quantity_to_words(self.lines[0].quantity) else: return '' @property def report_price(self): if self.lines: - return number_to_words(self.lines[0].unit_price) + return amount_to_currency_words(self.lines[0].unit_price) else: return ''