diff --git a/modules/purchase_trade/purchase.py b/modules/purchase_trade/purchase.py
index e2f82e4..50ae76b 100755
--- a/modules/purchase_trade/purchase.py
+++ b/modules/purchase_trade/purchase.py
@@ -417,11 +417,15 @@ class Purchase(metaclass=PoolMeta):
LotQt = Pool().get('lot.qt')
quantity = Decimal(0)
unit = getattr(line, 'unit', None)
- for lot in getattr(line, 'lots', None) or []:
- if getattr(lot, 'lot_type', None) == 'physic':
- quantity += Decimal(str(
+ physical_lots = [
+ lot for lot in getattr(line, 'lots', None) or []
+ if getattr(lot, 'lot_type', None) == 'physic']
+ if physical_lots:
+ return sum(
+ Decimal(str(
lot.get_current_quantity_converted(unit=unit) or 0))
- continue
+ for lot in physical_lots)
+ for lot in getattr(line, 'lots', None) or []:
if getattr(lot, 'lot_type', None) != 'virtual':
continue
for lqt in LotQt.search([('lot_p', '=', lot.id)]):
@@ -1354,10 +1358,16 @@ class Line(metaclass=PoolMeta):
tol_max_qt = fields.Numeric("Tol +",states={
'readonly': (Eval('inherit_tol')),
})
- inherit_tol = fields.Boolean("Inherit tolerance")
- tol_min_v = fields.Function(fields.Numeric("Qt min"),'get_tol_min')
- tol_max_v = fields.Function(fields.Numeric("Qt max"),'get_tol_max')
- # certification = fields.Selection([
+ inherit_tol = fields.Boolean("Inherit tolerance")
+ tol_min_v = fields.Function(fields.Numeric("Qt min"),'get_tol_min')
+ tol_max_v = fields.Function(fields.Numeric("Qt max"),'get_tol_max')
+ tolerance_used = fields.Function(
+ fields.Numeric("Tolerance used"), 'get_tolerance_used')
+ tolerance_min = fields.Function(
+ fields.Numeric("Tolerance min"), 'get_tolerance_min')
+ tolerance_max = fields.Function(
+ fields.Numeric("Tolerance max"), 'get_tolerance_max')
+ # certification = fields.Selection([
# (None, ''),
# ('bci', 'BCI'),
# ],"Certification",states={'readonly': (Eval('inherit_cer')),})
@@ -1496,14 +1506,74 @@ class Line(metaclass=PoolMeta):
if self.tol_min and self.quantity_theorical:
return round((1-(self.tol_min/100))*Decimal(self.quantity_theorical),3)
- def get_tol_max(self,name):
- if self.inherit_tol:
- if self.purchase.tol_max and self.quantity_theorical:
- return round((1+(self.purchase.tol_max/100))*Decimal(self.quantity_theorical),3)
- else:
- if self.tol_max and self.quantity_theorical:
- return round((1+(self.tol_max/100))*Decimal(self.quantity_theorical),3)
-
+ def get_tol_max(self,name):
+ if self.inherit_tol:
+ if self.purchase.tol_max and self.quantity_theorical:
+ return round((1+(self.purchase.tol_max/100))*Decimal(self.quantity_theorical),3)
+ else:
+ if self.tol_max and self.quantity_theorical:
+ return round((1+(self.tol_max/100))*Decimal(self.quantity_theorical),3)
+
+ def _line_quantity_in_unit(self, quantity, from_unit, to_unit):
+ if not from_unit or not to_unit or from_unit == to_unit:
+ return Decimal(str(quantity or 0))
+ Uom = Pool().get('product.uom')
+ factor = None
+ rate = None
+ if from_unit.category.id != to_unit.category.id:
+ factor = 1
+ rate = 1
+ return Decimal(str(Uom.compute_qty(
+ from_unit, float(quantity or 0), to_unit, True, factor, rate)))
+
+ def _tolerance_theoretical_quantity(self):
+ return Decimal(str(
+ getattr(self, 'quantity_theorical', None)
+ or getattr(self, 'quantity', None)
+ or 0))
+
+ def _tolerance_actual_quantity(self):
+ LotQt = Pool().get('lot.qt')
+ unit = getattr(self, 'unit', None)
+ physical_lots = [
+ lot for lot in getattr(self, 'lots', None) or []
+ if getattr(lot, 'lot_type', None) == 'physic']
+ if physical_lots:
+ return sum(
+ Decimal(str(
+ lot.get_current_quantity_converted(unit=unit) or 0))
+ for lot in physical_lots)
+ quantity = Decimal(0)
+ for lot in getattr(self, 'lots', None) or []:
+ if getattr(lot, 'lot_type', None) != 'virtual':
+ continue
+ for lqt in LotQt.search([('lot_p', '=', lot.id)]):
+ quantity += self._line_quantity_in_unit(
+ getattr(lqt, 'lot_quantity', 0),
+ getattr(lqt, 'lot_unit', None),
+ unit)
+ return quantity
+
+ def get_tolerance_used(self, name):
+ theoretical = self._tolerance_theoretical_quantity()
+ if not theoretical:
+ return Decimal(0)
+ return round(
+ ((self._tolerance_actual_quantity() - theoretical)
+ / theoretical) * Decimal(100), 5)
+
+ def get_tolerance_min(self, name):
+ purchase = getattr(self, 'purchase', None)
+ if self.inherit_tol and purchase:
+ return -Decimal(str(purchase.tol_min or 0))
+ return -Decimal(str(self.tol_min or 0))
+
+ def get_tolerance_max(self, name):
+ purchase = getattr(self, 'purchase', None)
+ if self.inherit_tol and purchase:
+ return Decimal(str(purchase.tol_max or 0))
+ return Decimal(str(self.tol_max or 0))
+
def get_progress(self,name):
PS = Pool().get('purchase.pricing.summary')
ps = PS.search(['line','=',self.id])
diff --git a/modules/purchase_trade/sale.py b/modules/purchase_trade/sale.py
index 84b1546..db5ea13 100755
--- a/modules/purchase_trade/sale.py
+++ b/modules/purchase_trade/sale.py
@@ -381,11 +381,15 @@ class Sale(metaclass=PoolMeta):
LotQt = Pool().get('lot.qt')
quantity = Decimal(0)
unit = getattr(line, 'unit', None)
- for lot in getattr(line, 'lots', None) or []:
- if getattr(lot, 'lot_type', None) == 'physic':
- quantity += abs(Decimal(str(
+ physical_lots = [
+ lot for lot in getattr(line, 'lots', None) or []
+ if getattr(lot, 'lot_type', None) == 'physic']
+ if physical_lots:
+ return sum(
+ abs(Decimal(str(
lot.get_current_quantity_converted(unit=unit) or 0)))
- continue
+ for lot in physical_lots)
+ for lot in getattr(line, 'lots', None) or []:
if getattr(lot, 'lot_type', None) != 'virtual':
continue
for lqt in LotQt.search([('lot_s', '=', lot.id)]):
@@ -1339,6 +1343,12 @@ class SaleLine(metaclass=PoolMeta):
inherit_tol = fields.Boolean("Inherit tolerance")
tol_min_v = fields.Function(fields.Numeric("Qt min"),'get_tol_min')
tol_max_v = fields.Function(fields.Numeric("Qt max"),'get_tol_max')
+ tolerance_used = fields.Function(
+ fields.Numeric("Tolerance used"), 'get_tolerance_used')
+ tolerance_min = fields.Function(
+ fields.Numeric("Tolerance min"), 'get_tolerance_min')
+ tolerance_max = fields.Function(
+ fields.Numeric("Tolerance max"), 'get_tolerance_max')
certif = fields.Many2One('purchase.certification',"Certification",states={'readonly': (Eval('inherit_cer')),})
# certification = fields.Selection([
# (None, ''),
@@ -1494,6 +1504,66 @@ class SaleLine(metaclass=PoolMeta):
if self.tol_max and self.quantity_theorical:
return round((1+(self.tol_max/100))*Decimal(self.quantity_theorical),3)
+ def _line_quantity_in_unit(self, quantity, from_unit, to_unit):
+ if not from_unit or not to_unit or from_unit == to_unit:
+ return abs(Decimal(str(quantity or 0)))
+ Uom = Pool().get('product.uom')
+ factor = None
+ rate = None
+ if from_unit.category.id != to_unit.category.id:
+ factor = 1
+ rate = 1
+ return abs(Decimal(str(Uom.compute_qty(
+ from_unit, float(quantity or 0), to_unit, True, factor, rate))))
+
+ def _tolerance_theoretical_quantity(self):
+ return abs(Decimal(str(
+ getattr(self, 'quantity_theorical', None)
+ or getattr(self, 'quantity', None)
+ or 0)))
+
+ def _tolerance_actual_quantity(self):
+ LotQt = Pool().get('lot.qt')
+ unit = getattr(self, 'unit', None)
+ physical_lots = [
+ lot for lot in getattr(self, 'lots', None) or []
+ if getattr(lot, 'lot_type', None) == 'physic']
+ if physical_lots:
+ return sum(
+ abs(Decimal(str(
+ lot.get_current_quantity_converted(unit=unit) or 0)))
+ for lot in physical_lots)
+ quantity = Decimal(0)
+ for lot in getattr(self, 'lots', None) or []:
+ if getattr(lot, 'lot_type', None) != 'virtual':
+ continue
+ for lqt in LotQt.search([('lot_s', '=', lot.id)]):
+ quantity += self._line_quantity_in_unit(
+ getattr(lqt, 'lot_quantity', 0),
+ getattr(lqt, 'lot_unit', None),
+ unit)
+ return quantity
+
+ def get_tolerance_used(self, name):
+ theoretical = self._tolerance_theoretical_quantity()
+ if not theoretical:
+ return Decimal(0)
+ return round(
+ ((self._tolerance_actual_quantity() - theoretical)
+ / theoretical) * Decimal(100), 5)
+
+ def get_tolerance_min(self, name):
+ sale = getattr(self, 'sale', None)
+ if self.inherit_tol and sale:
+ return -Decimal(str(sale.tol_min or 0))
+ return -Decimal(str(self.tol_min or 0))
+
+ def get_tolerance_max(self, name):
+ sale = getattr(self, 'sale', None)
+ if self.inherit_tol and sale:
+ return Decimal(str(sale.tol_max or 0))
+ return Decimal(str(self.tol_max or 0))
+
def get_progress(self,name):
PS = Pool().get('sale.pricing.summary')
ps = PS.search(['sale_line','=',self.id])
diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py
index db86cb2..d193b65 100644
--- a/modules/purchase_trade/tests/test_module.py
+++ b/modules/purchase_trade/tests/test_module.py
@@ -2837,29 +2837,32 @@ class PurchaseTradeTestCase(ModuleTestCase):
self.assertEqual(result['lot_s'][0]['lot_qt_max'], Decimal('80.00000'))
self.assertEqual(result['lot_s'][0]['lot_cp'], 40)
- def test_purchase_tolerance_used_sums_open_and_physical_quantities(self):
- 'purchase tolerance gauge uses lot.qt plus physical lot quantities'
+ def test_purchase_tolerance_used_is_weighted_line_average(self):
+ 'purchase tolerance gauge averages line gauges by theoretical quantity'
unit = Mock()
- virtual = Mock(id=10, lot_type='virtual')
physical = Mock(lot_type='physic')
- physical.get_current_quantity_converted.return_value = Decimal('10')
- line = Mock(
+ physical.get_current_quantity_converted.return_value = Decimal('105')
+ physical_line = Mock(
type='line', unit=unit, quantity_theorical=Decimal('100'),
- lots=[virtual, physical])
+ lots=[physical])
+ virtual = Mock(id=10, lot_type='virtual')
+ open_line = Mock(
+ type='line', unit=unit, quantity_theorical=Decimal('300'),
+ lots=[virtual])
purchase = purchase_module.Purchase()
- purchase.lines = [line]
+ purchase.lines = [physical_line, open_line]
purchase.tol_min = Decimal('5')
purchase.tol_max = Decimal('10')
lot_qt_model = Mock()
lot_qt_model.search.return_value = [
- Mock(lot_quantity=Decimal('95'), lot_unit=unit)]
+ Mock(lot_quantity=Decimal('270'), lot_unit=unit)]
pool = Mock()
pool.get.return_value = lot_qt_model
with patch.object(purchase_module, 'Pool', return_value=pool):
self.assertEqual(
purchase.get_tolerance_used('tolerance_used'),
- Decimal('5.00000'))
+ Decimal('-6.25000'))
self.assertEqual(
purchase.get_tolerance_min('tolerance_min'),
Decimal('-5'))
@@ -2867,20 +2870,78 @@ class PurchaseTradeTestCase(ModuleTestCase):
purchase.get_tolerance_max('tolerance_max'),
Decimal('10'))
- def test_sale_tolerance_used_sums_open_and_physical_quantities(self):
- 'sale tolerance gauge uses absolute lot.qt plus physical quantities'
+ def test_sale_tolerance_used_is_weighted_line_average(self):
+ 'sale tolerance gauge averages line gauges by theoretical quantity'
unit = Mock()
- virtual = Mock(id=20, lot_type='virtual')
physical = Mock(lot_type='physic')
- physical.get_current_quantity_converted.return_value = Decimal('10')
- line = Mock(
+ physical.get_current_quantity_converted.return_value = Decimal('105')
+ physical_line = Mock(
type='line', unit=unit, quantity_theorical=Decimal('100'),
- lots=[virtual, physical])
+ lots=[physical])
+ virtual = Mock(id=20, lot_type='virtual')
+ open_line = Mock(
+ type='line', unit=unit, quantity_theorical=Decimal('300'),
+ lots=[virtual])
sale = sale_module.Sale()
- sale.lines = [line]
+ sale.lines = [physical_line, open_line]
sale.tol_min = Decimal('5')
sale.tol_max = Decimal('10')
lot_qt_model = Mock()
+ lot_qt_model.search.return_value = [
+ Mock(lot_quantity=Decimal('-270'), lot_unit=unit)]
+ pool = Mock()
+ pool.get.return_value = lot_qt_model
+
+ with patch.object(sale_module, 'Pool', return_value=pool):
+ self.assertEqual(
+ sale.get_tolerance_used('tolerance_used'),
+ Decimal('-6.25000'))
+ self.assertEqual(
+ sale.get_tolerance_min('tolerance_min'),
+ Decimal('-5'))
+ self.assertEqual(
+ sale.get_tolerance_max('tolerance_max'),
+ Decimal('10'))
+
+ def test_purchase_line_tolerance_uses_physical_lots_when_present(self):
+ 'purchase line tolerance ignores open lot.qt when physical lots exist'
+ unit = Mock()
+ virtual = Mock(id=10, lot_type='virtual')
+ physical = Mock(lot_type='physic')
+ physical.get_current_quantity_converted.return_value = Decimal('105')
+ line = purchase_module.Line()
+ line.unit = unit
+ line.quantity_theorical = Decimal('100')
+ line.quantity = Decimal('100')
+ line.lots = [virtual, physical]
+ line.inherit_tol = False
+ line.tol_min = Decimal('5')
+ line.tol_max = Decimal('10')
+ lot_qt_model = Mock()
+ lot_qt_model.search.return_value = [
+ Mock(lot_quantity=Decimal('80'), lot_unit=unit)]
+ pool = Mock()
+ pool.get.return_value = lot_qt_model
+
+ with patch.object(purchase_module, 'Pool', return_value=pool):
+ self.assertEqual(
+ line.get_tolerance_used('tolerance_used'),
+ Decimal('5.00000'))
+ lot_qt_model.search.assert_not_called()
+
+ def test_sale_line_tolerance_uses_open_lot_qt_without_physical_lots(self):
+ 'sale line tolerance uses absolute lot.qt when no physical lot exists'
+ unit = Mock()
+ virtual = Mock(id=20, lot_type='virtual')
+ line = sale_module.SaleLine()
+ line.unit = unit
+ line.quantity_theorical = Decimal('100')
+ line.quantity = Decimal('100')
+ line.lots = [virtual]
+ line.inherit_tol = False
+ line.tol_min = Decimal('5')
+ line.tol_max = Decimal('10')
+ lot_qt_model = Mock()
lot_qt_model.search.return_value = [
Mock(lot_quantity=Decimal('-95'), lot_unit=unit)]
pool = Mock()
@@ -2888,14 +2949,8 @@ class PurchaseTradeTestCase(ModuleTestCase):
with patch.object(sale_module, 'Pool', return_value=pool):
self.assertEqual(
- sale.get_tolerance_used('tolerance_used'),
- Decimal('5.00000'))
- self.assertEqual(
- sale.get_tolerance_min('tolerance_min'),
- Decimal('-5'))
- self.assertEqual(
- sale.get_tolerance_max('tolerance_max'),
- Decimal('10'))
+ line.get_tolerance_used('tolerance_used'),
+ Decimal('-5.00000'))
def test_lot_context_group_defaults_to_all(self):
'lots management opens without grouping by physical lot'
diff --git a/modules/purchase_trade/view/purchase_form.xml b/modules/purchase_trade/view/purchase_form.xml
index 2919131..8d8dd5e 100755
--- a/modules/purchase_trade/view/purchase_form.xml
+++ b/modules/purchase_trade/view/purchase_form.xml
@@ -32,12 +32,12 @@ this repository contains the full copyright notices and license terms. -->
-
-
+
+
diff --git a/modules/purchase_trade/view/purchase_line_form.xml b/modules/purchase_trade/view/purchase_line_form.xml
index 85fa7fe..993db6d 100755
--- a/modules/purchase_trade/view/purchase_line_form.xml
+++ b/modules/purchase_trade/view/purchase_line_form.xml
@@ -39,13 +39,21 @@ this repository contains the full copyright notices and license terms. -->
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/modules/purchase_trade/view/sale_form.xml b/modules/purchase_trade/view/sale_form.xml
index 29ee55d..33d5f96 100755
--- a/modules/purchase_trade/view/sale_form.xml
+++ b/modules/purchase_trade/view/sale_form.xml
@@ -32,12 +32,12 @@ this repository contains the full copyright notices and license terms. -->
-
-
+
+
diff --git a/modules/purchase_trade/view/sale_line_form.xml b/modules/purchase_trade/view/sale_line_form.xml
index 3f54ae4..83ad212 100755
--- a/modules/purchase_trade/view/sale_line_form.xml
+++ b/modules/purchase_trade/view/sale_line_form.xml
@@ -39,13 +39,21 @@ this repository contains the full copyright notices and license terms. -->
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+