Pnl clean up

This commit is contained in:
2026-07-01 15:39:46 +02:00
parent 99d5d2a849
commit ea85927a17
3 changed files with 141 additions and 0 deletions

View File

@@ -1,3 +1,6 @@
from sql import Table
from sql.operators import Exists
from trytond.model import ModelSingleton, ModelSQL, ModelView, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Eval
@@ -119,6 +122,10 @@ class Configuration(ModelSingleton, ModelSQL, ModelView):
'menu_coffee_cupping_session_sale',
'menu_coffee_cupping_criterion_sale',
)
_ORPHAN_ACT_WINDOW_XML_IDS = (
'act_itsa_operations_workflow',
'act_tradon_processes',
)
pricing_rule = fields.Text("Pricing Rule")
active_coffee_compatibility = fields.Boolean(
@@ -163,6 +170,27 @@ class Configuration(ModelSingleton, ModelSQL, ModelView):
shipment_packing_list_report_label = fields.Char(
"Packing List Menu Label")
@classmethod
def __register__(cls, module_name):
super().__register__(module_name)
if module_name == 'purchase_trade':
cls._cleanup_orphan_act_window_xml_ids()
@classmethod
def _cleanup_orphan_act_window_xml_ids(cls):
cursor = Transaction().connection.cursor()
model_data = Table('ir_model_data')
act_window = Table('ir_action_act_window')
cursor.execute(*model_data.delete(
where=(model_data.module == 'purchase_trade')
& (model_data.model == 'ir.action.act_window')
& model_data.fs_id.in_(cls._ORPHAN_ACT_WINDOW_XML_IDS)
& ~Exists(act_window.select(
act_window.id,
where=act_window.id == model_data.db_id))))
@classmethod
def default_active_coffee_compatibility(cls):
return False

View File

@@ -1824,6 +1824,47 @@ class PurchaseTradeTestCase(ModuleTestCase):
{'type': 'derivative', 'amount': Decimal('30')},
])
def test_delete_existing_purchase_clears_matched_sale_line_only_values(self):
'purchase valuation cleanup removes matched sale-only valuation rows'
Valuation = Pool().get('valuation.valuation')
today = datetime.date(2026, 7, 1)
sale_line = Mock(id=30)
sale_lot = Mock(sale_line=sale_line)
purchase_lot = Mock(id=20, lot_type='virtual', sale_line=None)
line = Mock(
id=10,
finished=False,
lots=[purchase_lot],
get_matched_lines=Mock(return_value=[]))
valuation_model = Mock()
valuation_line_model = Mock()
lot_qt_model = Mock()
lot_qt_model.search.return_value = [Mock(lot_s=sale_lot)]
with patch('trytond.modules.purchase_trade.valuation.Pool') as PoolMock:
PoolMock.return_value.get.side_effect = lambda name: {
'ir.date': Mock(today=Mock(return_value=today)),
'valuation.valuation': valuation_model,
'valuation.valuation.line': valuation_line_model,
'lot.qt': lot_qt_model,
}[name]
Valuation._delete_existing(line)
valuation_model.search.assert_any_call([
('line', '=', line.id),
('date', '=', today),
])
valuation_model.search.assert_any_call([
('sale_line', '=', sale_line.id),
('line', '=', None),
('date', '=', today),
])
valuation_line_model.search.assert_any_call([
('sale_line', '=', sale_line.id),
('line', '=', None),
])
def test_generate_keeps_finished_purchase_line_physical_pnl(self):
'finished purchase lines still generate physical lot valuation'
Valuation = Pool().get('valuation.valuation')
@@ -2728,6 +2769,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(segments, [{
'quantity': Decimal('18000'),
'share': Decimal('1'),
'sale_lot': None,
'sale_line': None,
'shipment_in': shipment.id,
}])

View File

@@ -340,6 +340,7 @@ class ValuationBase(ModelSQL):
Date = Pool().get('ir.date')
Valuation = Pool().get('valuation.valuation')
ValuationLine = Pool().get('valuation.valuation.line')
LotQt = Pool().get('lot.qt')
valuation_domain = [
('line', '=', line.id),
@@ -359,6 +360,47 @@ class ValuationBase(ModelSQL):
if valuation_lines:
ValuationLine.delete(valuation_lines)
for sale_line in cls._matched_sale_lines_from_purchase_line(
line, LotQt=LotQt):
cls._delete_existing_sale_line_only(
sale_line, selected_types=selected_types)
@classmethod
def _matched_sale_lines_from_purchase_line(cls, line, LotQt=None):
if LotQt is None:
LotQt = Pool().get('lot.qt')
sale_lines = []
seen = set()
def add_sale_line(sale_line):
sale_line_id = cls._record_id(sale_line)
if not sale_line or sale_line_id in seen:
return
sale_lines.append(sale_line)
seen.add(sale_line_id)
getter = getattr(line, 'get_matched_lines', None)
matched_lines = getter() if callable(getter) else []
for matched_line in matched_lines or []:
lot_s = getattr(matched_line, 'lot_s', None)
add_sale_line(getattr(lot_s, 'sale_line', None))
for lot in cls._valuation_lots(line):
add_sale_line(getattr(lot, 'sale_line', None))
lot_id = getattr(lot, 'id', None)
if not lot_id:
continue
for lqt in LotQt.search([
('lot_p', '=', lot_id),
('lot_s', '>', 0),
('lot_quantity', '>', 0),
]):
lot_s = getattr(lqt, 'lot_s', None)
add_sale_line(getattr(lot_s, 'sale_line', None))
return sale_lines
@classmethod
def _value_key(cls, value):
return (
@@ -522,6 +564,34 @@ class ValuationBase(ModelSQL):
if valuation_lines:
ValuationLine.delete(valuation_lines)
@classmethod
def _delete_existing_sale_line_only(cls, sale_line, selected_types=None):
Date = Pool().get('ir.date')
Valuation = Pool().get('valuation.valuation')
ValuationLine = Pool().get('valuation.valuation.line')
valuation_domain = [
('sale_line', '=', sale_line.id),
('line', '=', None),
('date', '=', Date.today()),
]
valuation_line_domain = [
('sale_line', '=', sale_line.id),
('line', '=', None),
]
if selected_types is not None:
valuation_domain.append(('type', 'in', list(selected_types)))
valuation_line_domain.append(('type', 'in', list(selected_types)))
valuations = Valuation.search(valuation_domain)
if valuations:
Valuation.delete(valuations)
valuation_lines = ValuationLine.search(valuation_line_domain)
if valuation_lines:
ValuationLine.delete(valuation_lines)
@classmethod
def _base_pnl(cls, *, line, lot, pnl_type, sale=None, sale_line=None,
shipment_in=None):