From 63ef74bdee377299a9c3782b688336338b6063ff Mon Sep 17 00:00:00 2001 From: Adrien Widart Date: Fri, 29 Apr 2022 14:47:41 +0000 Subject: [PATCH] [FIX] {sale,sale_purchase,purchase}_stock: cancel SO linked with a PO When the user cancels a SO, if there is an associated PO, there won't be any activity logged on the PO about the SO cancellation. To reproduce the issue: 1. Unarchive the MTO route 2. Create a product P: - Type: Storable - Routes: MTO & Buy - Add a vendor V 3. Create and confirm a SO with 1 x P - Note: a PO is generated 4. Cancel the SO 5. Open the PO Error: There isn't any information about the cancellation of the SO. An activity should be added to the PO. Since [1], if the PO is a draft, we no longer log an activity when the quantity of the related SO is reduced: https://github.com/odoo/odoo/blob/3bd9b7a77a1e0e05ea479945d5cd8085f6981feb/addons/purchase_stock/models/stock.py#L103-L105 However, when cancelling a SO, we call the activity logger: https://github.com/odoo/odoo/blob/846ad1f978fe36db892a9902bb4657c2079d1756/addons/sale_stock/models/sale_order.py#L184-L189 So, since the PO is not yet confirmed, we ignore it and do not log any activity [1] bda3225c6cc27bb2c2933eda3cc68c6f9708daf3 OPW-2820242 closes odoo/odoo#90553 X-original-commit: de485a39c9ee88aa31f9330a468ad31cc05842bc Signed-off-by: Steve Van Essche Signed-off-by: Adrien Widart --- addons/purchase_stock/models/stock.py | 3 +- addons/sale_purchase_stock/tests/__init__.py | 4 ++ .../tests/test_sale_purchase_stock_flow.py | 43 +++++++++++++++++++ addons/sale_stock/models/sale_order.py | 2 +- 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 addons/sale_purchase_stock/tests/__init__.py create mode 100644 addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py index f578dff9f8a..0584e23b0b9 100644 --- a/addons/purchase_stock/models/stock.py +++ b/addons/purchase_stock/models/stock.py @@ -101,7 +101,8 @@ class StockMove(models.Model): self.write({'created_purchase_line_id': False}) def _get_upstream_documents_and_responsibles(self, visited): - if self.created_purchase_line_id and self.created_purchase_line_id.state not in ('draft', 'done', 'cancel'): + if self.created_purchase_line_id and self.created_purchase_line_id.state not in ('done', 'cancel') \ + and (self.created_purchase_line_id.state != 'draft' or self._context.get('include_draft_documents')): return [(self.created_purchase_line_id.order_id, self.created_purchase_line_id.order_id.user_id, visited)] elif self.purchase_line_id and self.purchase_line_id.state not in ('done', 'cancel'): return[(self.purchase_line_id.order_id, self.purchase_line_id.order_id.user_id, visited)] diff --git a/addons/sale_purchase_stock/tests/__init__.py b/addons/sale_purchase_stock/tests/__init__.py new file mode 100644 index 00000000000..ec59b790bc1 --- /dev/null +++ b/addons/sale_purchase_stock/tests/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import test_sale_purchase_stock_flow diff --git a/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py b/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py new file mode 100644 index 00000000000..126f90bc3ad --- /dev/null +++ b/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from odoo.tests.common import TransactionCase, Form + + +class TestSalePurchaseStockFlow(TransactionCase): + + def test_cancel_so_with_draft_po(self): + """ + Sell a MTO+Buy product -> a PO is generated + Cancel the SO -> an activity should be added to the PO + """ + mto_route = self.env.ref('stock.route_warehouse0_mto') + buy_route = self.env.ref('purchase_stock.route_warehouse0_buy') + mto_route.active = True + + vendor = self.env['res.partner'].create({ + 'name': 'Super Vendor' + }) + + product = self.env['product.product'].create({ + 'name': 'SuperProduct', + 'type': 'product', + 'route_ids': [(6, 0, (mto_route + buy_route).ids)], + 'seller_ids': [(0, 0, { + 'partner_id': vendor.id, + })], + }) + + so_form = Form(self.env['sale.order']) + so_form.partner_id = self.env.user.partner_id + with so_form.order_line.new() as line: + line.product_id = product + so = so_form.save() + so.action_confirm() + + po = self.env['purchase.order'].search([('partner_id', '=', vendor.id)]) + + so.action_cancel() + + self.assertTrue(po.activity_ids) + self.assertIn(so.name, po.activity_ids.note) diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py index 3a571fe7a50..d379d865ebf 100644 --- a/addons/sale_stock/models/sale_order.py +++ b/addons/sale_stock/models/sale_order.py @@ -191,7 +191,7 @@ class SaleOrder(models.Model): for sale_order in self: if sale_order.state == 'sale' and sale_order.order_line: sale_order_lines_quantities = {order_line: (order_line.product_uom_qty, 0) for order_line in sale_order.order_line} - documents = self.env['stock.picking']._log_activity_get_documents(sale_order_lines_quantities, 'move_ids', 'UP') + documents = self.env['stock.picking'].with_context(include_draft_documents=True)._log_activity_get_documents(sale_order_lines_quantities, 'move_ids', 'UP') self.picking_ids.filtered(lambda p: p.state != 'done').action_cancel() if documents: filtered_documents = {}