This commit is contained in:
2026-02-27 05:56:00 +01:00
parent 4eae444c93
commit cd2a82f61c
2 changed files with 101 additions and 39 deletions

View File

@@ -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()
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}"

View File

@@ -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 ''