02.04.26
This commit is contained in:
@@ -288,7 +288,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_invoice">
|
||||
<field name="name">Provisional Invoice</field>
|
||||
<field name="name">Invoice</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="report_name">account.invoice</field>
|
||||
<field name="report">account_invoice/invoice.fodt</field>
|
||||
@@ -314,7 +314,7 @@ this repository contains the full copyright notices and license terms. -->
|
||||
</record>
|
||||
|
||||
<record model="ir.action.report" id="report_invoice_ict_final">
|
||||
<field name="name">Final Invoice</field>
|
||||
<field name="name">CN/DN</field>
|
||||
<field name="model">account.invoice</field>
|
||||
<field name="report_name">account.invoice</field>
|
||||
<field name="report">account_invoice/invoice_ict_final.fodt</field>
|
||||
|
||||
@@ -61,6 +61,10 @@ de negoce physique:
|
||||
- ne pas multiplier des chemins d'acces concurrents
|
||||
- Le `FREIGHT VALUE` d'un template facture vient du `fee.fee` du shipment
|
||||
dont le produit est `Maritime freight`.
|
||||
- Le wizard `Create contracts` en mode `matched` peut maintenant partir de
|
||||
plusieurs `lot.qt`, mais doit conserver un matching par lot source et laisser
|
||||
`created_by_code = True` sur les lignes creees pour ne pas declencher les
|
||||
creations automatiques de lots dans les validations.
|
||||
- En valuation / PnL:
|
||||
- la valeur stockee dans `type` est la cle technique (`pur. priced`,
|
||||
`sale priced`, `pur. fee`, etc.), pas le label affiche dans l'UI
|
||||
|
||||
@@ -283,6 +283,28 @@ Owner technique: `a completer`
|
||||
- Priorite:
|
||||
- `importante`
|
||||
|
||||
### BR-PT-013 - Create Contracts multi-lots doit conserver un matching par lot source
|
||||
|
||||
- Intent: permettre la creation d'un seul contrat mirror a partir de plusieurs
|
||||
open quantities sans perdre le lien lot-a-lot.
|
||||
- Description:
|
||||
- Le wizard `Create contracts` peut etre lance avec plusieurs `lot.qt`
|
||||
selectionnes.
|
||||
- En creation `matched`, le systeme doit creer un seul contrat avec une ligne
|
||||
par lot source selectionne, et chaque ligne doit etre matchee avec son lot
|
||||
d'origine.
|
||||
- Resultat attendu:
|
||||
- la quantite totale du wizard = somme des open quantities selectionnees
|
||||
- le contrat cree porte plusieurs lignes si plusieurs lots source sont
|
||||
selectionnes
|
||||
- chaque ligne creee reutilise le `shipment_origin` et le lot source qui lui
|
||||
correspondent
|
||||
- `created_by_code` doit rester positionne sur les lignes creees par wizard
|
||||
pour eviter la recreation automatique de lots virtuels dans les `validate`
|
||||
de `purchase.line`, `sale.line` et `lot.lot`
|
||||
- Priorite:
|
||||
- `importante`
|
||||
|
||||
## 4) Exemples concrets
|
||||
|
||||
### Exemple E1 - Augmentation simple
|
||||
|
||||
@@ -138,6 +138,17 @@ Derniere mise a jour: `2026-03-27`
|
||||
- `purchase.report_delivery_period_description`
|
||||
- `invoice.report_delivery_period_description`
|
||||
|
||||
### TR-010 - En template, un contrat `basis` affiche le premium comme prix
|
||||
|
||||
- Pour les templates commerciaux/facture (`sale_ict`, `invoice_ict`, etc.),
|
||||
le prix affiche d'une ligne `basis` ne doit pas etre le prix economique total
|
||||
(`unit_price`, `linked_price` ou prix basis brut).
|
||||
- La valeur a afficher est uniquement le `premium`:
|
||||
- en devise/unite liee si `linked currency` est active
|
||||
- sinon dans la devise/unite native de la ligne
|
||||
- Le texte de curve / pricing (`ON ICE ...`) reste affiche a cote, mais la
|
||||
valeur numerique et sa version en lettres doivent representer le premium.
|
||||
|
||||
## 4) Workflow recommande pour corriger un template en erreur
|
||||
|
||||
1. Identifier le placeholder exact qui provoque l'erreur Relatorio.
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
from decimal import Decimal
|
||||
|
||||
from trytond.pool import Pool, PoolMeta
|
||||
from trytond.modules.purchase_trade.numbers_to_words import amount_to_currency_words
|
||||
|
||||
|
||||
class Invoice(metaclass=PoolMeta):
|
||||
@@ -298,6 +299,10 @@ class Invoice(metaclass=PoolMeta):
|
||||
sale = self._get_report_sale()
|
||||
if sale and sale.report_gross != '':
|
||||
return sale.report_gross
|
||||
if self.lines:
|
||||
return sum(
|
||||
Decimal(str(getattr(line, 'quantity', 0) or 0))
|
||||
for line in self._get_report_invoice_lines())
|
||||
line = self._get_report_trade_line()
|
||||
if line and line.lots:
|
||||
return sum(
|
||||
@@ -311,6 +316,10 @@ class Invoice(metaclass=PoolMeta):
|
||||
trade = self._get_report_trade()
|
||||
if trade and getattr(trade, 'report_net', '') != '':
|
||||
return trade.report_net
|
||||
if self.lines:
|
||||
return sum(
|
||||
Decimal(str(getattr(line, 'quantity', 0) or 0))
|
||||
for line in self._get_report_invoice_lines())
|
||||
line = self._get_report_trade_line()
|
||||
if line and line.lots:
|
||||
return sum(
|
||||
@@ -463,6 +472,11 @@ class InvoiceLine(metaclass=PoolMeta):
|
||||
|
||||
@property
|
||||
def report_rate_value(self):
|
||||
origin = self._get_report_trade_line()
|
||||
if origin and getattr(origin, 'price_type', None) == 'basis':
|
||||
if getattr(origin, 'enable_linked_currency', False) and getattr(origin, 'linked_currency', None):
|
||||
return Decimal(str(origin.premium or 0))
|
||||
return Decimal(str(origin._get_premium_price() or 0))
|
||||
return self.unit_price if self.unit_price is not None else ''
|
||||
|
||||
@property
|
||||
@@ -475,6 +489,12 @@ class InvoiceLine(metaclass=PoolMeta):
|
||||
|
||||
@property
|
||||
def report_rate_price_words(self):
|
||||
origin = self._get_report_trade_line()
|
||||
if origin and getattr(origin, 'price_type', None) == 'basis':
|
||||
value = self.report_rate_value
|
||||
if self.report_rate_currency_upper == 'USC':
|
||||
return amount_to_currency_words(value, 'USC', 'USC')
|
||||
return amount_to_currency_words(value)
|
||||
trade = self._get_report_trade()
|
||||
if trade and getattr(trade, 'report_price', None):
|
||||
return trade.report_price
|
||||
|
||||
@@ -343,19 +343,43 @@ class Sale(metaclass=PoolMeta):
|
||||
return text or '0'
|
||||
|
||||
def _format_report_price_words(self, line):
|
||||
value = self._get_report_display_price_value(line)
|
||||
currency = self._get_report_display_currency(line)
|
||||
if currency and (currency.rec_name or '').upper() == 'USC':
|
||||
return amount_to_currency_words(value, 'USC', 'USC')
|
||||
return amount_to_currency_words(value)
|
||||
|
||||
def _get_report_display_currency(self, line):
|
||||
if getattr(line, 'price_type', None) == 'basis':
|
||||
if getattr(line, 'enable_linked_currency', False) and getattr(line, 'linked_currency', None):
|
||||
return line.linked_currency
|
||||
return self.currency
|
||||
return getattr(line, 'linked_currency', None) or self.currency
|
||||
|
||||
def _get_report_display_unit(self, line):
|
||||
if getattr(line, 'price_type', None) == 'basis':
|
||||
if getattr(line, 'enable_linked_currency', False) and getattr(line, 'linked_unit', None):
|
||||
return line.linked_unit
|
||||
return getattr(line, 'unit', None)
|
||||
return getattr(line, 'linked_unit', None) or getattr(line, 'unit', None)
|
||||
|
||||
def _get_report_display_price_value(self, line):
|
||||
if getattr(line, 'price_type', None) == 'basis':
|
||||
if getattr(line, 'enable_linked_currency', False) and getattr(line, 'linked_currency', None):
|
||||
return Decimal(str(line.premium or 0))
|
||||
return Decimal(str(line._get_premium_price() or 0))
|
||||
if getattr(line, 'linked_price', None):
|
||||
return amount_to_currency_words(line.linked_price, 'USC', 'USC')
|
||||
return amount_to_currency_words(line.unit_price)
|
||||
return Decimal(str(line.linked_price or 0))
|
||||
return Decimal(str(line.unit_price or 0))
|
||||
|
||||
def _format_report_price_line(self, line):
|
||||
currency = getattr(line, 'linked_currency', None) or self.currency
|
||||
unit = getattr(line, 'linked_unit', None) or getattr(line, 'unit', None)
|
||||
currency = self._get_report_display_currency(line)
|
||||
unit = self._get_report_display_unit(line)
|
||||
pricing_text = getattr(line, 'get_pricing_text', '') or ''
|
||||
parts = [
|
||||
(currency.rec_name.upper() if currency and currency.rec_name else '').strip(),
|
||||
self._format_report_number(
|
||||
line.linked_price if getattr(line, 'linked_price', None)
|
||||
else line.unit_price,
|
||||
self._get_report_display_price_value(line),
|
||||
strip_trailing_zeros=False),
|
||||
'PER',
|
||||
(unit.rec_name.upper() if unit and unit.rec_name else '').strip(),
|
||||
@@ -374,16 +398,32 @@ class Sale(metaclass=PoolMeta):
|
||||
|
||||
@property
|
||||
def report_gross(self):
|
||||
line = self._get_report_first_line()
|
||||
if line:
|
||||
return sum([l.get_current_gross_quantity() for l in line.lots if l.lot_type == 'physic'])
|
||||
lines = self._get_report_lines()
|
||||
if lines:
|
||||
total = Decimal(0)
|
||||
for line in lines:
|
||||
phys_lots = [l for l in line.lots if l.lot_type == 'physic']
|
||||
if phys_lots:
|
||||
total += sum(Decimal(str(l.get_current_gross_quantity() or 0))
|
||||
for l in phys_lots)
|
||||
else:
|
||||
total += Decimal(str(line.quantity or 0))
|
||||
return total
|
||||
return ''
|
||||
|
||||
@property
|
||||
def report_net(self):
|
||||
line = self._get_report_first_line()
|
||||
if line:
|
||||
return sum([l.get_current_quantity() for l in line.lots if l.lot_type == 'physic'])
|
||||
lines = self._get_report_lines()
|
||||
if lines:
|
||||
total = Decimal(0)
|
||||
for line in lines:
|
||||
phys_lots = [l for l in line.lots if l.lot_type == 'physic']
|
||||
if phys_lots:
|
||||
total += sum(Decimal(str(l.get_current_quantity() or 0))
|
||||
for l in phys_lots)
|
||||
else:
|
||||
total += Decimal(str(line.quantity or 0))
|
||||
return total
|
||||
return ''
|
||||
|
||||
@property
|
||||
@@ -474,13 +514,7 @@ class Sale(metaclass=PoolMeta):
|
||||
def report_price(self):
|
||||
line = self._get_report_first_line()
|
||||
if line:
|
||||
if line.price_type == 'priced':
|
||||
if line.linked_price:
|
||||
return amount_to_currency_words(line.linked_price,'USC','USC')
|
||||
else:
|
||||
return amount_to_currency_words(line.unit_price)
|
||||
elif line.price_type == 'basis':
|
||||
return amount_to_currency_words(line.unit_price) + ' ' + line.get_pricing_text
|
||||
return self._format_report_price_words(line)
|
||||
return ''
|
||||
|
||||
@property
|
||||
|
||||
@@ -233,5 +233,51 @@ class PurchaseTradeTestCase(ModuleTestCase):
|
||||
with self.assertRaises(UserError):
|
||||
ContractFactory._get_line_sources(contract_detail, sources, ct)
|
||||
|
||||
def test_sale_report_price_lines_basis_displays_premium_only(self):
|
||||
'basis report pricing displays only the premium in templates'
|
||||
Sale = Pool().get('sale.sale')
|
||||
|
||||
line = Mock()
|
||||
line.type = 'line'
|
||||
line.price_type = 'basis'
|
||||
line.enable_linked_currency = True
|
||||
line.linked_currency = Mock(rec_name='USC')
|
||||
line.linked_unit = Mock(rec_name='POUND')
|
||||
line.unit = Mock(rec_name='MT')
|
||||
line.unit_price = Decimal('1598.3495')
|
||||
line.linked_price = Decimal('72.5000')
|
||||
line.premium = Decimal('8.3000')
|
||||
line.get_pricing_text = 'ON ICE Cotton #2 MARCH 2026'
|
||||
|
||||
sale = Sale()
|
||||
sale.currency = Mock(rec_name='USD')
|
||||
sale.lines = [line]
|
||||
|
||||
self.assertEqual(
|
||||
sale.report_price_lines,
|
||||
'USC 8.3000 PER POUND (EIGHT USC AND THIRTY CENTS) ON ICE Cotton #2 MARCH 2026')
|
||||
|
||||
def test_sale_report_net_and_gross_sum_all_lines(self):
|
||||
'sale report totals aggregate every line instead of the first one only'
|
||||
Sale = Pool().get('sale.sale')
|
||||
|
||||
def make_lot(quantity):
|
||||
lot = Mock()
|
||||
lot.lot_type = 'physic'
|
||||
lot.get_current_quantity.return_value = Decimal(quantity)
|
||||
lot.get_current_gross_quantity.return_value = Decimal(quantity)
|
||||
return lot
|
||||
|
||||
line_a = Mock(type='line', quantity=Decimal('1000'))
|
||||
line_a.lots = [make_lot('1000')]
|
||||
line_b = Mock(type='line', quantity=Decimal('1000'))
|
||||
line_b.lots = [make_lot('1000')]
|
||||
|
||||
sale = Sale()
|
||||
sale.lines = [line_a, line_b]
|
||||
|
||||
self.assertEqual(sale.report_net, Decimal('2000'))
|
||||
self.assertEqual(sale.report_gross, Decimal('2000'))
|
||||
|
||||
|
||||
del ModuleTestCase
|
||||
|
||||
Reference in New Issue
Block a user