price component
This commit is contained in:
12
AGENTS.md
12
AGENTS.md
@@ -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.
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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)])
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user