diff --git a/modules/purchase_trade/numbers_to_words.py b/modules/purchase_trade/numbers_to_words.py new file mode 100644 index 0000000..518c66a --- /dev/null +++ b/modules/purchase_trade/numbers_to_words.py @@ -0,0 +1,77 @@ +from decimal import Decimal, ROUND_HALF_UP + + +UNITS = ( + "ZERO ONE TWO THREE FOUR FIVE SIX SEVEN EIGHT NINE TEN ELEVEN TWELVE " + "THIRTEEN FOURTEEN FIFTEEN SIXTEEN SEVENTEEN EIGHTEEN NINETEEN" +).split() + +TENS = "ZERO TEN TWENTY THIRTY FORTY FIFTY SIXTY SEVENTY EIGHTY NINETY".split() + + +def _under_thousand(n, use_and=True): + words = [] + + hundreds = n // 100 + remainder = n % 100 + + if hundreds: + words.append(UNITS[hundreds]) + words.append("HUNDRED") + if remainder and use_and: + words.append("AND") + + if remainder: + if remainder < 20: + words.append(UNITS[remainder]) + else: + words.append(TENS[remainder // 10]) + if remainder % 10: + words.append(UNITS[remainder % 10]) + + return " ".join(words) + + +def number_to_words(n, decimals_mode="digit", use_and=True): + """ + decimals_mode: + - "digit" → ONE HUNDRED POINT TWO FIVE + - "fraction" → ONE HUNDRED AND 25/100 + """ + + n = Decimal(str(n)).quantize(Decimal("0.01"), rounding=ROUND_HALF_UP) + + integer_part = int(n) + decimal_part = int((n - integer_part) * 100) + + if integer_part == 0: + words = "ZERO" + else: + parts = [] + + 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 + 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" + + return words.upper() \ No newline at end of file diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py index 4392c9d..8839fd8 100755 --- a/modules/purchase_trade/sale.py +++ b/modules/purchase_trade/sale.py @@ -15,6 +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 logger = logging.getLogger(__name__) @@ -264,13 +265,31 @@ class Sale(metaclass=PoolMeta): else: return '' + @property + def report_qt(self): + if self.lines: + return number_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) + else: + return '' + @property def report_shipment(self): if self.lines: if len(self.lines[0].lots)>1: shipment = self.lines[0].lots[1].lot_shipment_in if shipment: - info = 'B/L ' + shipment.bl_number + ' ' + shipment.note + info = 'B/L ' + shipment.bl_number + if shipment.container and shipment.container[0].container_no: + info += ' ' + shipment.container[0].container_no + if shipment.note: + info += ' ' + shipment.note return info else: return '' diff --git a/modules/sale/sale.fodt b/modules/sale/sale.fodt index fe65531..467307f 100755 --- a/modules/sale/sale.fodt +++ b/modules/sale/sale.fodt @@ -1,10 +1,10 @@ - LibreOffice/7.6.0.3$Windows_X86_64 LibreOffice_project/69edd8b8ebc41d00b4de3915dc82f8f0fc3b6265Invoice nowillen2026-02-26T13:52:00Z2026-02-26T21:20:26.3510000002025-12-17T14:25:00Z6PT26M24S + LibreOffice/7.6.0.3$Windows_X86_64 LibreOffice_project/69edd8b8ebc41d00b4de3915dc82f8f0fc3b6265Invoice nowillen2026-02-26T13:52:00Z2026-02-27T05:47:38.3430000002025-12-17T14:25:00Z7PT28M8S - 35108 + 40658 0 24236 11721 @@ -13,12 +13,12 @@ view2 - 8565 - 43921 + 10469 + 42757 0 - 35108 + 40658 24234 - 46828 + 52377 0 0 false @@ -91,7 +91,7 @@ false false false - 2008084 + 2082766 1846560 false false @@ -161,7 +161,7 @@ - + @@ -488,20 +488,29 @@ - - - - - + - + + + + + + + + + + + + + + @@ -510,11 +519,11 @@ - + - + @@ -523,10 +532,10 @@ - + - + @@ -542,11 +551,11 @@ - + - + @@ -554,7 +563,7 @@ - + @@ -566,20 +575,11 @@ - - - - - - - - - - + @@ -591,9 +591,15 @@ - + + + + + + + @@ -603,19 +609,19 @@ - + + + + - - - - + @@ -624,16 +630,10 @@ - + - - - - - - - + @@ -642,41 +642,45 @@ + - + + + + + + + + + + + + + + + + + + - + - - - - - - - - - - - - - - - - - - + + + + - + @@ -727,6 +731,9 @@ + + + @@ -3777,8 +3784,8 @@ - <for each="sale in records"> - + <for each="sale in records"> + @@ -3790,7 +3797,7 @@ - + @@ -3800,181 +3807,181 @@ - + - Buyers + Buyers <sale.party.rec_name> ADDRESS:<sale.report_address> - + - Sellers + Sellers - ICT TRADING S.A. - VIA MAGAZZINI GENERALI, 3 + ICT TRADING S.A. + VIA MAGAZZINI GENERALI, 3 6828 BALERNA, SWITZERLAND - Agents + Agents <sale.report_agent> - - + + - - Growth - + + Growth + - - <sale.lines[0].product.name if sale.lines and sale.lines[0].product else ''>CROP <sale.crop.name> + + <sale.lines[0].product.name if sale.lines and sale.lines[0].product else ''>CROP <sale.crop.name> - Quality - + Quality + - B/L <sale.report_shipment> + B/L <sale.report_shipment> - Quantity - + Quantity + - ABOUT <sum(line.quantity for line in sale.lines)><sale.lines[0].unit.rec_name.upper() if sale.lines and sale.lines[0].unit else ''> + ABOUT <sum(line.quantity for line in sale.lines)><sale.lines[0].unit.rec_name.upper() if sale.lines and sale.lines[0].unit else ''>(<sale.report_qt>) - + - Price + Price - <format_currency(sale.lines[0].unit_price, sale.party.lang, sale.currency) if sale.lines else ''> + <format_currency(sale.lines[0].unit_price, sale.party.lang, sale.currency) if sale.lines else ''>(<sale.report_price>) - - + + - + Delivery - - PROMPT + + PROMPT - - - + + + - - Terms + + Terms - - <sale.report_terms> + + <sale.report_terms> - + - + - + - + - Payment + Payment - <sale.payment_term.rec_name if sale.payment_term else '100% CASH AGAINST DOCUMENTS'> + <sale.payment_term.rec_name if sale.payment_term else '100% CASH AGAINST DOCUMENTS'> - + - + - Arbitration & Rules + Arbitration & Rules - - ALL DISPUTES RELATING TO THIS CONTRACT WILL BE RESOLVED THROUGH ARBITRATION IN ACCORDANCE WITH THE BYLAWS OF THE INTERNATIONAL COTTON ASSOCIATION, LIMITED. THIS AGREEMENT INCORPORATES THE BYLAWS WHICH SET OUT THE ASSOCIATION’S ARBITRATION PROCEDURE. - - EITHER PARTY MUST NOT TAKE ANY LEGAL ACTION OVER A DISPUTE SUITABLE FOR ARBITRATION, OTHER THAN TO OBTAIN SECURITY FOR ANY CLAIM, UNLESS THEY HAVE FIRST OBTAINED AN ARBITRATION AWARD FROM THE INTERNATIONAL COTTON ASSOCIATION, LIMITED AND EXHAUSTED ALL MEANS OF APPEAL ALLOWED BY THE ASSOCIATION’S BYLAWS. + - ALL DISPUTES RELATING TO THIS CONTRACT WILL BE RESOLVED THROUGH ARBITRATION IN ACCORDANCE WITH THE BYLAWS OF THE INTERNATIONAL COTTON ASSOCIATION, LIMITED. THIS AGREEMENT INCORPORATES THE BYLAWS WHICH SET OUT THE ASSOCIATION’S ARBITRATION PROCEDURE. + - EITHER PARTY MUST NOT TAKE ANY LEGAL ACTION OVER A DISPUTE SUITABLE FOR ARBITRATION, OTHER THAN TO OBTAIN SECURITY FOR ANY CLAIM, UNLESS THEY HAVE FIRST OBTAINED AN ARBITRATION AWARD FROM THE INTERNATIONAL COTTON ASSOCIATION, LIMITED AND EXHAUSTED ALL MEANS OF APPEAL ALLOWED BY THE ASSOCIATION’S BYLAWS. - + - - THE SELLERS + + THE SELLERS - - THE BUYERS + + THE BUYERS - ICT TRADING SA - + ICT TRADING SA + iVBORw0KGgoAAAANSUhEUgAAAQ8AAAEJCAIAAAAFFDV0AAAAAXNSR0IArs4c6QAA/8pJREFU eF7sfQeAXGd19etletned7Wr3VXvsootW+69gk2HQIAkJIEUElJ+SAKBhBJqEhICxEAcsDHF vcmybFlW72V7r7PT5/X2n++NJBsjxZZtjAx+DPKW2Zk37333u/eee+65tOu6lH84jsMwDL61 @@ -5601,12 +5608,12 @@ - <sale.party.rec_name> + <sale.party.rec_name> PC/AU/EL - </for> + </for> \ No newline at end of file