price component

This commit is contained in:
2026-04-09 22:46:32 +02:00
parent 472806ef06
commit 90eab73430
5 changed files with 131 additions and 12 deletions

View File

@@ -124,3 +124,15 @@ Toujours fournir:
3. Proposer le patch minimal.
4. Implementer + tester cible.
5. Rendre avec le contrat de sortie (section 8).
- Rappels session 2026-04-09:
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: poids et unites depuis `lot.qt.hist` / `lot_unit_line`, priorite lots `physic`, sinon lot `virtual` unique.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: infos shipment depuis les lots reels des lignes facture; ne rien afficher si plusieurs shipments differents.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: `S/I` = `shipment.reference`; `NB BALES: 0` => `Unchanged` sur le final.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: quantites uniformisees a `2` decimales; conversion `LBS` via UoM, jamais via un facteur fixe aveugle.
- `invoice_ict.fodt` / `invoice_ict_final.fodt`: si plusieurs lignes reutilisent le meme lot, les lignes detaillees suivent la quantite facturee convertie, mais le `GROSS` global doit rester le vrai delta historique du lot.
- `sale_ict.fodt`: meme priorite lots; les mots suivent l'unite reelle; le total convertit vers une unite commune, qui est celle du lot virtuel seulement s'il y a un seul lot virtuel sur tout le report.
- `lot.report.r_del_period`: utiliser `sale.line.del_period` pour `lot_s` sans `lot_p`, sinon `purchase.line.del_period`.
- `lot.do_weighing`: `lot_qt` editable et ecrasement direct de `lot.lot_qt`.
- `account.invoice`: `Validate` cree aussi le `account.move` pour les factures client; `Post` ne doit plus forcer une fresh session sur ce flux.
- `pricing.pricing`: saisie manuelle autorisee meme sans composant; `eod_price` non editable et calcule en prix moyen pondere; `last=True` gere par groupe `line + component`, choisi sur la `pricing_date` la plus grande.
- `purchase_trade`: `trader` filtre sur `TRADER`, `operator` sur `OPERATOR`; fallback sur `quantity` si `quantity_theorical` est vide dans les quotas/pricings.

View File

@@ -1244,6 +1244,10 @@ class Line(metaclass=PoolMeta):
PS = Pool().get('purchase.pricing.summary')
ps = PS.search(['line','=',self.id])
if ps:
if not self.price_components:
manual = [e for e in ps if not e.price_component]
if manual:
return manual[0].progress or 0
return sum((e.progress if e.progress else 0) * (e.ratio if e.ratio else 0) / 100 for e in ps)
def getVirtualLot(self):
@@ -1296,6 +1300,14 @@ class Line(metaclass=PoolMeta):
for t in self.terms:
price += (t.manual_price if t.manual_price else Decimal(0))
else:
if not self.price_components:
PP = Pool().get('purchase.pricing.summary')
pp = PP.search([
('line', '=', self.id),
('price_component', '=', None),
], limit=1)
if pp:
return round(Decimal(pp[0].price or 0), 4)
for pc in self.price_components:
PP = Pool().get('purchase.pricing.summary')
pp = PP.search([('price_component','=',pc.id),('line','=',self.id)])

View File

@@ -1171,6 +1171,10 @@ class SaleLine(metaclass=PoolMeta):
PS = Pool().get('sale.pricing.summary')
ps = PS.search(['sale_line','=',self.id])
if ps:
if not self.price_components:
manual = [e for e in ps if not e.price_component]
if manual:
return manual[0].progress or 0
return sum((e.progress if e.progress else 0) * (e.ratio if e.ratio else 0) / 100 for e in ps)
def getVirtualLot(self):
@@ -1225,6 +1229,14 @@ class SaleLine(metaclass=PoolMeta):
def _get_basis_component_price(self):
price = Decimal(0)
if not self.price_components:
PP = Pool().get('sale.pricing.summary')
pp = PP.search([
('sale_line', '=', self.id),
('price_component', '=', None),
], limit=1)
if pp:
return round(Decimal(pp[0].price or 0), 4)
for pc in self.price_components:
PP = Pool().get('sale.pricing.summary')
pp = PP.search([('price_component','=',pc.id),('sale_line','=',self.id)])

View File

@@ -336,6 +336,51 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(
Purchase.operator.domain, [('categories.name', '=', 'OPERATOR')])
def test_sale_line_basis_price_and_progress_use_manual_summary_without_component(self):
'sale line basis values use manual summary rows even without a component'
SaleLine = Pool().get('sale.line')
summary_model = Mock()
summary_model.search.side_effect = [
[Mock(price=Decimal('150'), progress=1, price_component=None)],
[Mock(price=Decimal('150'), progress=1, price_component=None)],
]
line = SaleLine()
line.id = 1
line.price_type = 'basis'
line.price_components = []
line.enable_linked_currency = False
line.linked_currency = None
with patch('trytond.modules.purchase_trade.sale.Pool') as PoolMock:
PoolMock.return_value.get.return_value = summary_model
self.assertEqual(line.get_basis_price(), Decimal('150.0000'))
self.assertEqual(line.get_progress('progress'), 1)
def test_purchase_line_basis_price_and_progress_use_manual_summary_without_component(self):
'purchase line basis values use manual summary rows even without a component'
PurchaseLine = Pool().get('purchase.line')
summary_model = Mock()
summary_model.search.side_effect = [
[Mock(price=Decimal('150'), progress=1, price_component=None)],
[Mock(price=Decimal('150'), progress=1, price_component=None)],
]
line = PurchaseLine()
line.id = 1
line.price_type = 'basis'
line.price_components = []
line.terms = []
line.enable_linked_currency = False
line.linked_currency = None
with patch('trytond.modules.purchase_trade.purchase.Pool') as PoolMock:
PoolMock.return_value.get.return_value = summary_model
self.assertEqual(line.get_basis_price(), Decimal('150.0000'))
self.assertEqual(line.get_progress('progress'), 1)
def test_sale_line_write_updates_virtual_lot_when_theorical_qty_increases(self):
'sale line write increases virtual lot and open lot.qt when contractual qty grows'
SaleLine = Pool().get('sale.line')

View File

@@ -49,3 +49,41 @@ Scope: templates Relatorio + ponts `report_*` Python.
- La source metier est-elle correcte (compagnie vs client, total vs unit_price, maturity date vs payment term)?
- Les formats sont-ils conformes (date, devise, montant en lettres)?
- Le template est-il bien expose dans la config + menu d'impression de la forme cible?
## 5) Session 2026-04-09 - Rappels metier purchase_trade / account_invoice
- Factures trade:
- `NET` et `GROSS` viennent de `lot.qt.hist` du lot retenu, pas de `invoice.line.quantity`.
- priorite lots: `physic` d'abord, sinon lot `virtual` unique.
- l'unite affichee vient de `lot.lot_unit_line`.
- les infos shipment doivent dependre des lots reels des lignes facture.
- le label `S/I` doit afficher `shipment.reference`.
- `invoice_ict_final.fodt`: si `NB BALES = 0`, afficher `Unchanged`.
- les quantites de `invoice_ict` et `invoice_ict_final` sont uniformisees a `2` decimales.
- la conversion vers `LBS` doit passer par `product.uom.compute_qty`, pas un facteur fixe.
- si plusieurs lignes reutilisent le meme lot, les lignes detaillees utilisent la quantite facturee convertie, mais le `GROSS` global doit continuer a refleter le vrai delta historique du lot.
- CN / DN:
- cote `sale` / `out`: montant negatif => `Credit Note`, montant positif => `Debit Note`.
- cote `purchase` / `in`: logique inverse conservee.
- Report sale:
- meme priorite lots que facture.
- les quantites en lettres suivent l'unite reelle (`MT`, `KILOGRAM`, `LBS`).
- le total convertit les lignes vers une unite commune.
- l'unite commune est celle du lot virtuel seulement s'il y a un seul lot virtuel sur tout le report.
- Lots:
- `lot.report.r_del_period` affiche `sale.line.del_period` pour `lot_s` sans `lot_p`, sinon `purchase.line.del_period`.
- dans `Do weighing`, `lot_qt` doit etre editable et ecraser directement `lot.lot_qt`.
- Factures client / fournisseur:
- `Validate` cree aussi le `account.move` pour les factures client.
- `Post` ne doit plus forcer une fresh session / demande de mot de passe sur ce flux.
- Pricing:
- `pricing.pricing` peut etre saisi manuellement meme sans composant.
- `fixed_qt`, `fixed_qt_price`, `unfixed_qt`, `unfixed_qt_price` sont editables.
- `eod_price` reste non editable et suit le prix moyen pondere.
- le mode auto suit la meme formule.
- `last` est gere par groupe metier (`line + component`), avec un seul `last=True` par groupe.
- la ligne `last=True` est celle de `pricing_date` la plus grande; `id` ne sert qu'en tie-break.
- Divers:
- `trader` filtre sur `TRADER`.
- `operator` filtre sur `OPERATOR`.
- les quotas/pricings doivent fallback sur `quantity` si `quantity_theorical` est vide.