diff --git a/modules/purchase_trade/__init__.py b/modules/purchase_trade/__init__.py
index e092b88..50245c0 100755
--- a/modules/purchase_trade/__init__.py
+++ b/modules/purchase_trade/__init__.py
@@ -248,11 +248,12 @@ def register():
sale.AnalyticDimensionAssignment,
sale.PriceComposition,
module='sale', type_='model')
- Pool.register(
- lot.LotShipping,
- lot.LotMatching,
- #lot.LotMatchingUnit,
- lot.LotWeighing,
+ Pool.register(
+ lot.LotShipping,
+ lot.LotMatching,
+ lot.LotGoMatching,
+ #lot.LotMatchingUnit,
+ lot.LotWeighing,
lot.CreateContracts,
lot.LotUnmatch,
lot.LotUnship,
diff --git a/modules/purchase_trade/lot.py b/modules/purchase_trade/lot.py
index 2e10970..e678c56 100755
--- a/modules/purchase_trade/lot.py
+++ b/modules/purchase_trade/lot.py
@@ -2612,9 +2612,9 @@ class LotContext(ModelView):
def default_ps(cls):
return 'all'
- @classmethod
- def default_group(cls):
- return 'by_physic'
+ @classmethod
+ def default_group(cls):
+ return 'all'
@classmethod
def default_wh(cls):
@@ -2801,9 +2801,62 @@ class LotMatching(Wizard):
matching = StateTransition()
- def transition_start(self):
- return 'match'
-
+ def transition_start(self):
+ return 'match'
+
+ @staticmethod
+ def _record_id(record):
+ return record.id if record else None
+
+ @classmethod
+ def _matching_lot_value(cls, lqt, side):
+ lot = lqt.lot_p if side == 'purchase' else lqt.lot_s
+ line = getattr(lot, 'line', None)
+ sale_line = getattr(lot, 'sale_line', None)
+ purchase = getattr(line, 'purchase', None)
+ sale = getattr(sale_line, 'sale', None)
+ party = (
+ getattr(purchase, 'party', None) if side == 'purchase'
+ else getattr(sale, 'party', None))
+ quantity = Decimal(str(lqt.lot_quantity or 0))
+ if side == 'sale':
+ quantity = abs(quantity)
+ return {
+ 'lot_id': cls._record_id(lot),
+ 'lot_r_id': cls._record_id(lqt),
+ 'lot_purchase': cls._record_id(purchase),
+ 'lot_sale': cls._record_id(sale),
+ 'lot_shipment_in': cls._record_id(lqt.lot_shipment_in),
+ 'lot_shipment_internal': cls._record_id(
+ lqt.lot_shipment_internal),
+ 'lot_shipment_out': cls._record_id(lqt.lot_shipment_out),
+ 'lot_type': getattr(lot, 'lot_type', None),
+ 'lot_product': cls._record_id(getattr(lot, 'lot_product', None)),
+ 'lot_quantity': quantity,
+ 'lot_matched_qt': 0,
+ 'lot_cp': cls._record_id(party),
+ }
+
+ @classmethod
+ def _selected_matching_defaults(cls, active_ids):
+ LotQt = Pool().get('lot.qt')
+ lot_p = []
+ lot_s = []
+ for active_id in active_ids or []:
+ if active_id <= 10000000:
+ continue
+ lqt = LotQt(active_id - 10000000)
+ if lqt.lot_p and lqt.lot_s:
+ continue
+ if lqt.lot_p:
+ lot_p.append(cls._matching_lot_value(lqt, 'purchase'))
+ elif lqt.lot_s:
+ lot_s.append(cls._matching_lot_value(lqt, 'sale'))
+ return {
+ 'lot_p': lot_p,
+ 'lot_s': lot_s,
+ }
+
def transition_matching(self):
Warning = Pool().get('res.user.warning')
LotQt = Pool().get('lot.qt')
@@ -2831,10 +2884,30 @@ class LotMatching(Wizard):
return 'end'
- def end(self):
- return 'reload'
-
-class LotMatchingStart(ModelView):
+ def end(self):
+ return 'reload'
+
+
+class LotGoMatching(LotMatching):
+ "Go to matching"
+ __name__ = "lot.go_matching"
+
+ match = StateView(
+ 'lot.matching.start',
+ 'purchase_trade.go_matching_view_form', [
+ Button("Cancel", 'end', 'tryton-cancel'),
+ Button("Match", 'matching', 'tryton-ok', default=True),
+ ])
+
+ def default_match(self, fields):
+ context = Transaction().context
+ values = self._selected_matching_defaults(
+ context.get('active_ids') or [])
+ values['qt_type'] = 'all'
+ return values
+
+
+class LotMatchingStart(ModelView):
"Matching"
__name__ = "lot.matching.start"
lot_p = fields.One2Many('lot.matching.lot','lms',"Purchase")
diff --git a/modules/purchase_trade/lot.xml b/modules/purchase_trade/lot.xml
index db40f35..097680d 100755
--- a/modules/purchase_trade/lot.xml
+++ b/modules/purchase_trade/lot.xml
@@ -164,16 +164,21 @@ this repository contains the full copyright notices and license terms. -->
tree
lot_invoicing_inv_tree
-
- lot.matching.start
- form
- lot_matching_start_form
-
-
- lot.matching.lot
- tree
- lot_matching_lot_tree
-
+
+ lot.matching.start
+ form
+ lot_matching_start_form
+
+
+ lot.matching.start
+ form
+ lot_go_matching_start_form
+
+
+ lot.matching.lot
+ tree
+ lot_matching_lot_tree
+
lot.matching.lot
tree
@@ -184,16 +189,26 @@ this repository contains the full copyright notices and license terms. -->
lot.matching
lot.report
-
- form_action
- lot.report,-1
-
-
-
- lot.weighing.start
- form
- lot_weighing_start_form
-
+
+ form_action
+ lot.report,-1
+
+
+
+ Go to matching
+ lot.go_matching
+ lot.report
+
+
+ form_action
+ lot.report,-1
+
+
+
+ lot.weighing.start
+ form
+ lot_weighing_start_form
+
lot.weighing.lot
tree
diff --git a/modules/purchase_trade/tests/test_module.py b/modules/purchase_trade/tests/test_module.py
index 7cd9fb3..dd6da10 100644
--- a/modules/purchase_trade/tests/test_module.py
+++ b/modules/purchase_trade/tests/test_module.py
@@ -2722,6 +2722,69 @@ class PurchaseTradeTestCase(ModuleTestCase):
with self.assertRaises(UserError):
lot_module.LotQt.match_lots([purchase_lot], [])
+ def test_go_matching_defaults_selected_open_lot_qts(self):
+ 'go to matching only preloads selected unmatched open lot.qt rows'
+ purchase = Mock(id=10, party=Mock(id=20))
+ sale = Mock(id=30, party=Mock(id=40))
+ product = Mock(id=50)
+ purchase_lot = Mock(
+ id=101, line=Mock(purchase=purchase), sale_line=None,
+ lot_type='virtual', lot_product=product)
+ sale_lot = Mock(
+ id=102, line=None, sale_line=Mock(sale=sale),
+ lot_type='virtual', lot_product=product)
+ matched_sale_lot = Mock(
+ id=103, line=None, sale_line=Mock(sale=sale),
+ lot_type='virtual', lot_product=product)
+ lot_qts = {
+ 1: Mock(
+ id=1, lot_p=purchase_lot, lot_s=None,
+ lot_quantity=Decimal('100'),
+ lot_shipment_in=None, lot_shipment_internal=None,
+ lot_shipment_out=None),
+ 2: Mock(
+ id=2, lot_p=None, lot_s=sale_lot,
+ lot_quantity=Decimal('80'),
+ lot_shipment_in=None, lot_shipment_internal=None,
+ lot_shipment_out=None),
+ 3: Mock(
+ id=3, lot_p=purchase_lot, lot_s=matched_sale_lot,
+ lot_quantity=Decimal('20'),
+ lot_shipment_in=None, lot_shipment_internal=None,
+ lot_shipment_out=None),
+ }
+
+ class LotQtMock:
+ def __call__(self, _id):
+ return lot_qts[_id]
+
+ pool = Mock()
+ pool.get.side_effect = (
+ lambda name: LotQtMock() if name == 'lot.qt' else Mock())
+ transaction = Mock()
+ transaction.context = {
+ 'active_ids': [10000001, 10000002, 10000003],
+ }
+
+ with patch.object(lot_module, 'Pool', return_value=pool):
+ with patch.object(
+ lot_module, 'Transaction', return_value=transaction):
+ result = lot_module.LotGoMatching().default_match([])
+
+ self.assertEqual(result['qt_type'], 'all')
+ self.assertEqual(len(result['lot_p']), 1)
+ self.assertEqual(len(result['lot_s']), 1)
+ self.assertEqual(result['lot_p'][0]['lot_r_id'], 1)
+ self.assertEqual(result['lot_p'][0]['lot_quantity'], Decimal('100'))
+ self.assertEqual(result['lot_p'][0]['lot_cp'], 20)
+ self.assertEqual(result['lot_s'][0]['lot_r_id'], 2)
+ self.assertEqual(result['lot_s'][0]['lot_quantity'], Decimal('80'))
+ self.assertEqual(result['lot_s'][0]['lot_cp'], 40)
+
+ def test_lot_context_group_defaults_to_all(self):
+ 'lots management opens without grouping by physical lot'
+ self.assertEqual(lot_module.LotContext.default_group(), 'all')
+
def test_contract_detail_defaults_sale_locations_from_dropship_purchase(self):
'create contracts copies supplier-to-customer locations from purchase to sale'
supplier = Mock(id=1, type='supplier')
diff --git a/modules/purchase_trade/view/lot_go_matching_start_form.xml b/modules/purchase_trade/view/lot_go_matching_start_form.xml
new file mode 100644
index 0000000..50ed31f
--- /dev/null
+++ b/modules/purchase_trade/view/lot_go_matching_start_form.xml
@@ -0,0 +1,12 @@
+