From 4310c18faf7a4f201a5453fd3a26632f16d678dc Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Sat, 11 Jun 2022 12:08:50 +0200 Subject: [PATCH 1/2] [IMP] OD-2049, rma: prevent the creation of zero qty moves --- rma/tests/test_rma.py | 13 +++++++++++++ rma/wizards/rma_make_picking.py | 19 +++++++++++++------ 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/rma/tests/test_rma.py b/rma/tests/test_rma.py index e415edb41..22b33b462 100644 --- a/rma/tests/test_rma.py +++ b/rma/tests/test_rma.py @@ -401,6 +401,19 @@ def test_02_customer_rma(self): self.rma_customer_id.action_view_out_shipments() self.rma_customer_id.action_view_lines() + def test_06_no_zero_qty_moves(self): + rma_lines = self.rma_customer_id.rma_line_ids + rma_lines.write({"receipt_policy": "delivered"}) + self.assertEqual(sum(rma_lines.mapped("qty_to_receive")), 0) + wizard = self.rma_make_picking.with_context({ + 'active_ids': rma_lines.ids, + 'active_model': 'rma.order.line', + 'picking_type': 'incoming', + 'active_id': 1 + }).create({}) + with self.assertRaisesRegex(ValidationError, "No quantity to transfer"): + wizard._create_picking() + # DROPSHIP def test_03_dropship(self): for line in self.rma_droship_id.rma_line_ids: diff --git a/rma/wizards/rma_make_picking.py b/rma/wizards/rma_make_picking.py index b32b84fe9..9b1cdfb4d 100644 --- a/rma/wizards/rma_make_picking.py +++ b/rma/wizards/rma_make_picking.py @@ -4,7 +4,7 @@ import time from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError -from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT +from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT as DT_FORMAT, float_compare import odoo.addons.decimal_precision as dp @@ -149,15 +149,22 @@ def _create_procurement(self, item, picking_type): else: qty = item.qty_to_deliver values = self._get_procurement_data(item, group, qty, picking_type) + product = values.get('product_id') or item.line_id.product_id + uom = self.env['uom.uom'].browse( + values.get('product_uom') or + item.line_id.product_id.product_tmpl_id.uom_id.id + ) + if float_compare(qty, 0, uom.rounding) != 1: + raise ValidationError( + _("No quantity to transfer on %s shipment of product %s.") % + (_(picking_type), product.default_code or product.name) + ) # create picking try: self.env['procurement.group'].run( - values.get('product_id') or item.line_id.product_id, + product, qty, - self.env['uom.uom'].browse( - values.get('product_uom') or - item.line_id.product_id.product_tmpl_id.uom_id.id - ), + uom, values.get('location_id'), values.get('origin'), values.get('origin'), From a7cb9154b34a025241382c16386770baaeda42eb Mon Sep 17 00:00:00 2001 From: Stefan Rijnhart Date: Wed, 15 Jun 2022 17:14:55 +0200 Subject: [PATCH 2/2] [FIX] OD-2049, prevent default stock rules from kicking in If the operation type was misconfigured, or not fully configured with a route with all the necessary rules for the right warehouse and location, default pull rules would kick in and create the incoming customer RMA transfer from the resupply warehouse rather than from the customer location. With the fix in place, it turned out that the test setup for the supplier rma test was actually creating a customer RMA. Several of the related fixes were backported from the upstream migration to 13.0 ( https://github.com/ForgeFlow/stock-rma/pull/127/commits/f3c1a1c438d4564f75a7d0517b959f0ec611f71a#diff-eba7de7269c94075cf9219526202f209b01cf3f4107e46fcce83739d2749faf5 ) --- rma/models/procurement.py | 21 +++++++++- rma/tests/test_rma.py | 73 +++++++++++++++++++++------------ rma/wizards/rma_make_picking.py | 6 ++- 3 files changed, 71 insertions(+), 29 deletions(-) diff --git a/rma/models/procurement.py b/rma/models/procurement.py index 5a9a77093..725d589eb 100644 --- a/rma/models/procurement.py +++ b/rma/models/procurement.py @@ -1,7 +1,8 @@ # Copyright (C) 2017 Eficent Business and IT Consulting Services S.L. # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) -from odoo import fields, models +from odoo import _, api, fields, models +from odoo.exceptions import ValidationError class StockRule(models.Model): @@ -45,3 +46,21 @@ class ProcurementGroup(models.Model): comodel_name='rma.order.line', string='RMA line', ondelete="set null", ) + + @api.model + def _get_rule(self, product_id, location_id, values): + """Ensure that the selected rule is from the configured route""" + res = super()._get_rule(product_id, location_id, values) + force_rule_ids = self.env.context.get("rma_force_rule_ids") + if force_rule_ids: + if res and res.id not in force_rule_ids: + raise ValidationError(_( + "No rule found in this RMA's configured route for product " + "%s and location %s" % ( + product_id.default_code or product_id.name, + location_id.complete_name, + ) + )) + # Don't enforce rules on any chained moves + force_rule_ids.clear() + return res diff --git a/rma/tests/test_rma.py b/rma/tests/test_rma.py index 22b33b462..cce9863f5 100644 --- a/rma/tests/test_rma.py +++ b/rma/tests/test_rma.py @@ -1,7 +1,7 @@ # © 2017 Eficent Business and IT Consulting Services S.L. # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html) -from odoo.tests import common +from odoo.tests import common, Form from odoo.exceptions import ValidationError @@ -119,14 +119,6 @@ def _create_rma_from_move(cls, products2move, type, partner, dropship, str(cls.partner_id.id)]) data = wizard.with_context(customer=1).\ _prepare_rma_line_from_stock_move(move) - wizard.add_lines() - - if move.product_id.rma_customer_operation_id: - move.product_id.rma_customer_operation_id.in_route_id = \ - False - move.product_id.categ_id.rma_customer_operation_id = False - move.product_id.rma_customer_operation_id = False - wizard._prepare_rma_line_from_stock_move(move) else: wizard = cls.rma_add_stock_move.new( @@ -144,16 +136,8 @@ def _create_rma_from_move(cls, products2move, type, partner, dropship, 'active_model': 'rma.order', }).default_get([str(move.id), str(cls.partner_id.id)]) - data = wizard.with_context(customer=1).\ - _prepare_rma_line_from_stock_move(move) - wizard.add_lines() - - if move.product_id.rma_customer_operation_id: - move.product_id.rma_customer_operation_id.in_route_id = \ - False - move.product_id.categ_id.rma_supplier_operation_id = False - move.product_id.rma_supplier_operation_id = False - wizard._prepare_rma_line_from_stock_move(move) + data = wizard._prepare_rma_line_from_stock_move(move) + data['type'] = 'supplier' if dropship: data.update(customer_to_supplier=dropship, @@ -414,6 +398,41 @@ def test_06_no_zero_qty_moves(self): with self.assertRaisesRegex(ValidationError, "No quantity to transfer"): wizard._create_picking() + def test_07_warehouse_mismatch(self): + """Mismatch between operation warehouse and stock rule warehouse is raised. + + * Create a second warehouse that is resupplied from the main warehouse + * Update an RMA to receive into the second warehouse + * When creating pickings, it is raised that the rules from the RMA + * config are not used. + """ + wh2 = self.env['stock.warehouse'].create({ + 'name': 'Shop.', + 'code': 'SHP', + }) + wh2.resupply_wh_ids = self.env.ref('stock.warehouse0') + wh2.rma_in_this_wh = True + wh2.lot_rma_id = self.env["stock.location"].create({ + "name": "WH2 RMA", + "usage": "internal", + "location_id": wh2.lot_stock_id.id, + }) + rma = self.rma_customer_id.copy() + rma.rma_line_ids = self.rma_customer_id.rma_line_ids[0].copy() + rma.rma_line_ids.product_id.route_ids += wh2.resupply_route_ids + rma_form = Form(rma) + rma_form.in_warehouse_id = wh2 + rma_form.save() + rma.rma_line_ids.action_rma_approve() + wizard = self.rma_make_picking.with_context({ + 'active_ids': rma.rma_line_ids.ids, + 'active_model': 'rma.order.line', + 'picking_type': 'incoming', + 'active_id': 1 + }).create({}) + with self.assertRaisesRegex(ValidationError, "No rule found"): + wizard._create_picking() + # DROPSHIP def test_03_dropship(self): for line in self.rma_droship_id.rma_line_ids: @@ -572,8 +591,8 @@ def test_04_supplier_rma(self): self.assertEquals(len(moves), 3, "Incorrect number of moves created") for line in self.rma_supplier_id.rma_line_ids: - self.assertEquals(line.qty_incoming, 0, - "Wrong qty incoming") + self.assertEquals(line.qty_outgoing, 0, + "Wrong qty outgoing") self.assertEquals(line.qty_received, 0, "Wrong qty received") self.assertEquals(line.qty_to_deliver, 0, @@ -581,7 +600,7 @@ def test_04_supplier_rma(self): if line.product_id == self.product_1: self.assertEquals(line.qty_to_receive, 3, "Wrong qty to receive") - self.assertEquals(line.qty_incoming, 0, + self.assertEquals(line.qty_incoming, 3, "Wrong qty incoming") if line.product_id == self.product_2: self.assertEquals(line.qty_to_receive, 5, @@ -590,25 +609,25 @@ def test_04_supplier_rma(self): self.assertEquals(line.qty_to_receive, 2, "Wrong qty to receive") picking_out.action_assign() - for mv in picking.move_lines: + for mv in picking_out.move_lines: mv.quantity_done = mv.product_uom_qty picking_out.action_done() for line in self.rma_supplier_id.rma_line_ids[0]: - self.assertEquals(line.qty_to_receive, 3, + self.assertEquals(line.qty_to_receive, 0, "Wrong qty to receive") self.assertEquals(line.qty_incoming, 0, "Wrong qty incoming") self.assertEquals(line.qty_to_deliver, 0, "Wrong qty to deliver") - self.assertEquals(line.qty_outgoing, 3, + self.assertEquals(line.qty_outgoing, 0, "Wrong qty outgoing") if line.product_id == self.product_1: - self.assertEquals(line.qty_received, 0, + self.assertEquals(line.qty_received, 3, "Wrong qty received") self.assertEquals(line.qty_delivered, 3, "Wrong qty delivered") if line.product_id == self.product_2: - self.assertEquals(line.qty_received, 0, + self.assertEquals(line.qty_received, 5, "Wrong qty received") self.assertEquals(line.qty_delivered, 5, "Wrong qty delivered") diff --git a/rma/wizards/rma_make_picking.py b/rma/wizards/rma_make_picking.py index 9b1cdfb4d..1791ef629 100644 --- a/rma/wizards/rma_make_picking.py +++ b/rma/wizards/rma_make_picking.py @@ -146,8 +146,10 @@ def _create_procurement(self, item, picking_type): group = self.env['procurement.group'].create(pg_data) if picking_type == 'incoming': qty = item.qty_to_receive + force_rule_ids = item.line_id.in_route_id.rule_ids.ids else: qty = item.qty_to_deliver + force_rule_ids = item.line_id.out_route_id.rule_ids.ids values = self._get_procurement_data(item, group, qty, picking_type) product = values.get('product_id') or item.line_id.product_id uom = self.env['uom.uom'].browse( @@ -161,7 +163,9 @@ def _create_procurement(self, item, picking_type): ) # create picking try: - self.env['procurement.group'].run( + self.env['procurement.group'].with_context( + rma_force_rule_ids=force_rule_ids + ).run( product, qty, uom,