diff --git a/modules/purchase_trade/coffee.py b/modules/purchase_trade/coffee.py
index 1a3da4b..1f47c21 100644
--- a/modules/purchase_trade/coffee.py
+++ b/modules/purchase_trade/coffee.py
@@ -1127,6 +1127,15 @@ class CoffeeLineMixin:
'Package weight', digits=(16, 5))
coffee_packing_weight_unit = fields.Many2One(
'product.uom', 'Package weight unit')
+ coffee_packing_unit = fields.Many2One(
+ 'product.uom', 'Package unit',
+ domain=[
+ ('category', '=', Eval('coffee_packing_category')),
+ ],
+ depends=['coffee_packing_category'])
+ coffee_packing_category = fields.Function(
+ fields.Many2One('product.uom.category', 'Packing Category'),
+ 'on_change_with_coffee_packing_category')
coffee_packing_description = fields.Char('Packing description')
coffee_market_reference = fields.Many2One(
'price.price', 'Market reference')
@@ -1142,6 +1151,13 @@ class CoffeeLineMixin:
def on_change_with_active_coffee_compatibility(self, name=None):
return self.get_active_coffee_compatibility(name)
+ @fields.depends()
+ def on_change_with_coffee_packing_category(self, name=None):
+ UnitCategory = Pool().get('product.uom.category')
+ packing = UnitCategory.search([('name', '=', 'Packing')], limit=1)
+ if packing:
+ return packing[0]
+
def _coffee_attributes_name(self):
values = []
for field_name, selection in self._coffee_attribute_name_fields:
diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py
index a2bbec9..6764154 100755
--- a/modules/purchase_trade/purchase.py
+++ b/modules/purchase_trade/purchase.py
@@ -2882,6 +2882,27 @@ class Line(metaclass=PoolMeta):
def _should_regenerate_valuation(cls, values):
return bool(cls._valuation_regeneration_fields() & set(values))
+ @classmethod
+ def _sync_virtual_lot_packing(cls, line):
+ Lot = Pool().get('lot.lot')
+ if not getattr(line, 'lots', None):
+ line = cls(line.id)
+ virtual_lots = [
+ lot for lot in (line.lots or [])
+ if getattr(lot, 'lot_type', None) == 'virtual']
+ if not virtual_lots:
+ return
+ lot = virtual_lots[0]
+ packing_count = getattr(line, 'coffee_packing_count', None)
+ packing_unit = getattr(line, 'coffee_packing_unit', None)
+ lot_unit_id = getattr(lot.lot_unit, 'id', lot.lot_unit)
+ packing_unit_id = getattr(packing_unit, 'id', packing_unit)
+ if lot.lot_qt == packing_count and lot_unit_id == packing_unit_id:
+ return
+ lot.lot_qt = packing_count
+ lot.lot_unit = packing_unit
+ Lot.save([lot])
+
@classmethod
def write(cls, *args):
actions = iter(args)
@@ -3104,17 +3125,18 @@ class Line(metaclass=PoolMeta):
if Decimal(str(line.quantity_theorical or 0)) > 0:
Lot.save([lot])
#check if fees need to be updated
- if line.fees:
- for fee in line.fees:
- fl_check = FeeLots.search([('fee','=',fee.id),('lot','=',lot.id),('line','=',line.id)])
- if not fl_check:
- fl = FeeLots()
+ if line.fees:
+ for fee in line.fees:
+ fl_check = FeeLots.search([('fee','=',fee.id),('lot','=',lot.id),('line','=',line.id)])
+ if not fl_check:
+ fl = FeeLots()
fl.fee = fee.id
fl.lot = lot.id
- fl.line = line.id
- FeeLots.save([fl])
-
- if line.fee_:
+ fl.line = line.id
+ FeeLots.save([fl])
+ cls._sync_virtual_lot_packing(line)
+
+ if line.fee_:
if not line.fee_.purchase:
Fee = Pool().get('fee.fee')
f = Fee(line.fee_)
diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py
index 42ca017..a3b3b94 100755
--- a/modules/purchase_trade/sale.py
+++ b/modules/purchase_trade/sale.py
@@ -3002,6 +3002,27 @@ class SaleLine(metaclass=PoolMeta):
default.setdefault('quantity_theorical', None)
default.setdefault('price_pricing', None)
return super().copy(lines, default=default)
+
+ @classmethod
+ def _sync_virtual_lot_packing(cls, line):
+ Lot = Pool().get('lot.lot')
+ if not getattr(line, 'lots', None):
+ line = cls(line.id)
+ virtual_lots = [
+ lot for lot in (line.lots or [])
+ if getattr(lot, 'lot_type', None) == 'virtual']
+ if not virtual_lots:
+ return
+ lot = virtual_lots[0]
+ packing_count = getattr(line, 'coffee_packing_count', None)
+ packing_unit = getattr(line, 'coffee_packing_unit', None)
+ lot_unit_id = getattr(lot.lot_unit, 'id', lot.lot_unit)
+ packing_unit_id = getattr(packing_unit, 'id', packing_unit)
+ if lot.lot_qt == packing_count and lot_unit_id == packing_unit_id:
+ return
+ lot.lot_qt = packing_count
+ lot.lot_unit = packing_unit
+ Lot.save([lot])
@classmethod
def validate(cls, salelines):
@@ -3061,6 +3082,7 @@ class SaleLine(metaclass=PoolMeta):
fl.lot = lot.id
fl.sale_line = line.id
FeeLots.save([fl])
+ cls._sync_virtual_lot_packing(line)
#generate valuation for purchase and sale
LotQt = Pool().get('lot.qt')
diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py
index 1b1d056..57a4335 100644
--- a/modules/purchase_trade/tests/test_module.py
+++ b/modules/purchase_trade/tests/test_module.py
@@ -210,6 +210,62 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(
line.get_coffee_quality_status(), 'approved')
+ def test_purchase_line_syncs_packing_to_virtual_lot(self):
+ 'purchase line packing updates the existing virtual lot'
+ package_unit = Mock(id=60)
+ lot = Mock(lot_type='virtual', lot_qt=None, lot_unit=None)
+ line = Mock(
+ lots=[lot],
+ coffee_packing_count=440,
+ coffee_packing_unit=package_unit)
+ lot_model = Mock()
+ pool = Mock()
+ pool.get.return_value = lot_model
+
+ with patch.object(purchase_module, 'Pool', return_value=pool):
+ purchase_module.Line._sync_virtual_lot_packing(line)
+
+ self.assertEqual(lot.lot_qt, 440)
+ self.assertEqual(lot.lot_unit, package_unit)
+ lot_model.save.assert_called_once_with([lot])
+
+ def test_sale_line_syncs_packing_to_virtual_lot(self):
+ 'sale line packing updates the existing virtual lot'
+ package_unit = Mock(id=90)
+ old_unit = Mock(id=60)
+ lot = Mock(lot_type='virtual', lot_qt=440, lot_unit=old_unit)
+ line = Mock(
+ lots=[lot],
+ coffee_packing_count=300,
+ coffee_packing_unit=package_unit)
+ lot_model = Mock()
+ pool = Mock()
+ pool.get.return_value = lot_model
+
+ with patch.object(sale_module, 'Pool', return_value=pool):
+ sale_module.SaleLine._sync_virtual_lot_packing(line)
+
+ self.assertEqual(lot.lot_qt, 300)
+ self.assertEqual(lot.lot_unit, package_unit)
+ lot_model.save.assert_called_once_with([lot])
+
+ def test_purchase_line_does_not_resave_aligned_virtual_lot_packing(self):
+ 'aligned purchase packing does not resave the virtual lot'
+ package_unit = Mock(id=60)
+ lot = Mock(lot_type='virtual', lot_qt=440, lot_unit=Mock(id=60))
+ line = Mock(
+ lots=[lot],
+ coffee_packing_count=440,
+ coffee_packing_unit=package_unit)
+ lot_model = Mock()
+ pool = Mock()
+ pool.get.return_value = lot_model
+
+ with patch.object(purchase_module, 'Pool', return_value=pool):
+ purchase_module.Line._sync_virtual_lot_packing(line)
+
+ lot_model.save.assert_not_called()
+
def test_itsa_book_year_suffix_uses_april_fiscal_start(self):
'ITSA book year changes on April 1st'
self.assertEqual(
diff --git a/modules/purchase_trade/view/purchase_line_form.xml b/modules/purchase_trade/view/purchase_line_form.xml
index fc8a697..1f40c68 100755
--- a/modules/purchase_trade/view/purchase_line_form.xml
+++ b/modules/purchase_trade/view/purchase_line_form.xml
@@ -45,12 +45,11 @@ this repository contains the full copyright notices and license terms. -->
col_widths="min-content,1fr,min-content,1fr">
-
-
-
-
+
+
+
col_widths="min-content,1fr,min-content,1fr">
-
-
-
-
+
+
+