diff --git a/addons/purchase_mrp/models/mrp_production.py b/addons/purchase_mrp/models/mrp_production.py index 87cc432dacc..3f365b0cc54 100644 --- a/addons/purchase_mrp/models/mrp_production.py +++ b/addons/purchase_mrp/models/mrp_production.py @@ -12,14 +12,15 @@ class MrpProduction(models.Model): compute='_compute_purchase_order_count', groups='purchase.group_purchase_user') - @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id') + @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id', 'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id') def _compute_purchase_order_count(self): for production in self: - production.purchase_order_count = len(production.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id) + production.purchase_order_count = len(production.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | + production.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id) def action_view_purchase_orders(self): self.ensure_one() - purchase_order_ids = self.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id.ids + purchase_order_ids = (self.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | self.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id).ids action = { 'res_model': 'purchase.order', 'type': 'ir.actions.act_window', diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index 173a8352fe0..17e1f63d455 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -347,11 +347,39 @@ class PurchaseOrderLine(models.Model): res = [] if self.product_id.type not in ['product', 'consu']: return res + qty = 0.0 price_unit = self._get_stock_move_price_unit() for move in self.move_ids.filtered(lambda x: x.state != 'cancel' and not x.location_dest_id.usage == "supplier"): qty += move.product_uom._compute_quantity(move.product_uom_qty, self.product_uom, rounding_method='HALF-UP') - template = { + + move_dests = self.move_dest_ids + if not move_dests: + move_dests = self.move_ids.move_dest_ids + + if not move_dests: + qty_to_attach = 0 + qty_to_push = self.product_qty - qty + else: + move_dests_initial_demand = self.product_id.uom_id._compute_quantity( + sum(move_dests.filtered(lambda m: m.state != 'cancel' and not m.location_dest_id.usage == 'supplier').mapped('product_qty')), + self.product_uom, rounding_method='HALF-UP') + qty_to_attach = move_dests_initial_demand - qty + qty_to_push = self.product_qty - move_dests_initial_demand + + if float_compare(qty_to_attach, 0.0, precision_rounding=self.product_uom.rounding) > 0: + product_uom_qty, product_uom = self.product_uom._adjust_uom_quantities(qty_to_attach, self.product_id.uom_id) + res.append(self._prepare_stock_move_vals(picking, price_unit, product_uom_qty, product_uom)) + if float_compare(qty_to_push, 0.0, precision_rounding=self.product_uom.rounding) > 0: + product_uom_qty, product_uom = self.product_uom._adjust_uom_quantities(qty_to_push, self.product_id.uom_id) + extra_move_vals = self._prepare_stock_move_vals(picking, price_unit, product_uom_qty, product_uom) + extra_move_vals['move_dest_ids'] = False # don't attach + res.append(extra_move_vals) + return res + + def _prepare_stock_move_vals(self, picking, price_unit, product_uom_qty, product_uom): + self.ensure_one() + return { # truncate to 2000 to avoid triggering index limit error # TODO: remove index in master? 'name': (self.name or '')[:2000], @@ -360,7 +388,7 @@ class PurchaseOrderLine(models.Model): 'date': self.order_id.date_order, 'date_expected': self.date_planned, 'location_id': self.order_id.partner_id.property_stock_supplier.id, - 'location_dest_id': (self.orderpoint_id and not self.move_dest_ids) and self.orderpoint_id.location_id.id or self.order_id._get_destination_location(), + 'location_dest_id': (self.orderpoint_id and not (self.move_ids | self.move_dest_ids)) and self.orderpoint_id.location_id.id or self.order_id._get_destination_location(), 'picking_id': picking.id, 'partner_id': self.order_id.dest_address_id.id, 'move_dest_ids': [(4, x) for x in self.move_dest_ids.ids], @@ -378,16 +406,9 @@ class PurchaseOrderLine(models.Model): 'delay_alert': self.delay_alert, 'route_ids': self.order_id.picking_type_id.warehouse_id and [(6, 0, [x.id for x in self.order_id.picking_type_id.warehouse_id.route_ids])] or [], 'warehouse_id': self.order_id.picking_type_id.warehouse_id.id, + 'product_uom_qty': product_uom_qty, + 'product_uom': product_uom.id, } - diff_quantity = self.product_qty - qty - if float_compare(diff_quantity, 0.0, precision_rounding=self.product_uom.rounding) > 0: - po_line_uom = self.product_uom - quant_uom = self.product_id.uom_id - product_uom_qty, product_uom = po_line_uom._adjust_uom_quantities(diff_quantity, quant_uom) - template['product_uom_qty'] = product_uom_qty - template['product_uom'] = product_uom.id - res.append(template) - return res @api.model def _prepare_purchase_order_line_from_procurement(self, product_id, product_qty, product_uom, company_id, values, po): @@ -406,6 +427,7 @@ class PurchaseOrderLine(models.Model): for line in self.filtered(lambda l: not l.display_type): for val in line._prepare_stock_moves(picking): values.append(val) + line.move_dest_ids.created_purchase_line_id = False moves = self.env['stock.move'].create(values) for move in moves: diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py index d1e2d946469..20681254bd2 100644 --- a/addons/purchase_stock/models/stock.py +++ b/addons/purchase_stock/models/stock.py @@ -93,6 +93,8 @@ class StockMove(models.Model): def _get_upstream_documents_and_responsibles(self, visited): if self.created_purchase_line_id and self.created_purchase_line_id.state not in ('done', 'cancel'): 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)] else: return super(StockMove, self)._get_upstream_documents_and_responsibles(visited) diff --git a/addons/purchase_stock/tests/test_reordering_rule.py b/addons/purchase_stock/tests/test_reordering_rule.py index eee698d0b66..d5ceda8fe47 100644 --- a/addons/purchase_stock/tests/test_reordering_rule.py +++ b/addons/purchase_stock/tests/test_reordering_rule.py @@ -27,6 +27,9 @@ class TestReorderingRule(SavepointCase): - Receive products in 2 steps - The product has a reordering rule - On the po generated, the source document should be the name of the reordering rule + - Increase the quantity on the RFQ, the extra quantity should follow the push rules + - Increase the quantity on the PO, the extra quantity should follow the push rules + - There should be one move supplier -> input and two moves input -> stock """ warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) warehouse_1.write({'reception_steps': 'two_steps'}) @@ -61,12 +64,33 @@ class TestReorderingRule(SavepointCase): # On the po generated, the source document should be the name of the reordering rule self.assertEqual(order_point.name, purchase_order.origin, 'Source document on purchase order should be the name of the reordering rule.') + self.assertEqual(purchase_order.order_line.product_qty, 10) + + # Increase the quantity on the RFQ before confirming it + purchase_order.order_line.product_qty = 12 + purchase_order.button_confirm() + + self.assertEqual(purchase_order.picking_ids.move_lines.filtered(lambda m: m.product_id == self.product_01).product_qty, 12) + next_picking = purchase_order.picking_ids.move_lines.move_dest_ids.picking_id + self.assertEqual(len(next_picking), 2) + self.assertEqual(next_picking[0].move_lines.filtered(lambda m: m.product_id == self.product_01).product_qty, 10) + self.assertEqual(next_picking[1].move_lines.filtered(lambda m: m.product_id == self.product_01).product_qty, 2) + + # Increase the quantity on the PO + purchase_order.order_line.product_qty = 15 + self.assertEqual(purchase_order.picking_ids.move_lines.product_qty, 15) + self.assertEqual(next_picking[0].move_lines.filtered(lambda m: m.product_id == self.product_01).product_qty, 10) + self.assertEqual(next_picking[1].move_lines.filtered(lambda m: m.product_id == self.product_01).product_qty, 5) def test_reordering_rule_2(self): """ - Receive products in 1 steps - The product has two reordering rules, each one applying in a sublocation - Processing the purchase order should fulfill the two sublocations + - Increase the quantity on the RFQ for one of the POL, the extra quantity will go to + the original subloc since we don't know where to push it (no move dest) + - Increase the quantity on the PO, the extra quantity should follow the push rules and + thus go to stock """ warehouse_1 = self.env['stock.warehouse'].search([('company_id', '=', self.env.user.id)], limit=1) subloc_1 = self.env['stock.location'].create({'name': 'subloc_1', 'location_id': warehouse_1.lot_stock_id.id}) @@ -115,9 +139,17 @@ class TestReorderingRule(SavepointCase): self.assertTrue(purchase_order, 'No purchase order created.') self.assertEqual(len(purchase_order.order_line), 2, 'Not enough purchase order lines created.') + # increment the qty of the first po line + purchase_order.order_line.filtered(lambda pol: pol.orderpoint_id == order_point_1).product_qty = 15 purchase_order.button_confirm() - res = purchase_order.picking_ids.button_validate() - Form(self.env[res['res_model']].with_context(res['context'])).save().process() - self.assertEqual(self.product_01.with_context(location=subloc_1.id).virtual_available, 0) + self.assertEqual(self.product_01.with_context(location=subloc_1.id).virtual_available, 5) self.assertEqual(self.product_01.with_context(location=subloc_2.id).virtual_available, 0) + # increment the qty of the second po line + purchase_order.order_line.filtered(lambda pol: pol.orderpoint_id == order_point_2).with_context(debug=True).product_qty = 15 + self.assertEqual(self.product_01.with_context(location=subloc_1.id).virtual_available, 5) + self.assertEqual(self.product_01.with_context(location=subloc_2.id).virtual_available, 0) + self.assertEqual(self.product_01.with_context(location=warehouse_1.lot_stock_id.id).virtual_available, 10) # 5 on the main loc, 5 on subloc_1 + + self.assertEqual(purchase_order.picking_ids.move_lines[-1].product_qty, 5) + self.assertEqual(purchase_order.picking_ids.move_lines[-1].location_dest_id, warehouse_1.lot_stock_id) diff --git a/addons/sale_purchase_stock/models/purchase_order.py b/addons/sale_purchase_stock/models/purchase_order.py index 59738a97d85..09ec51a7716 100644 --- a/addons/sale_purchase_stock/models/purchase_order.py +++ b/addons/sale_purchase_stock/models/purchase_order.py @@ -7,9 +7,9 @@ from odoo import api, models class PurchaseOrder(models.Model): _inherit = 'purchase.order' - @api.depends('order_line.move_dest_ids.group_id.sale_id') + @api.depends('order_line.move_dest_ids.group_id.sale_id', 'order_line.move_ids.move_dest_ids.group_id.sale_id') def _compute_sale_order_count(self): super(PurchaseOrder, self)._compute_sale_order_count() def _get_sale_orders(self): - return super(PurchaseOrder, self)._get_sale_orders() | self.order_line.move_dest_ids.group_id.sale_id + return super(PurchaseOrder, self)._get_sale_orders() | self.order_line.move_dest_ids.group_id.sale_id | self.order_line.move_ids.move_dest_ids.group_id.sale_id diff --git a/addons/sale_purchase_stock/models/sale_order.py b/addons/sale_purchase_stock/models/sale_order.py index 0a8522e9892..6f74ac76d28 100644 --- a/addons/sale_purchase_stock/models/sale_order.py +++ b/addons/sale_purchase_stock/models/sale_order.py @@ -7,9 +7,9 @@ from odoo import api, models class SaleOrder(models.Model): _inherit = 'sale.order' - @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id') + @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id', 'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id') def _compute_purchase_order_count(self): super(SaleOrder, self)._compute_purchase_order_count() def _get_purchase_orders(self): - return super(SaleOrder, self)._get_purchase_orders() | self.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id + return super(SaleOrder, self)._get_purchase_orders() | self.procurement_group_id.stock_move_ids.created_purchase_line_id.order_id | self.procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id diff --git a/addons/stock/models/stock_rule.py b/addons/stock/models/stock_rule.py index e3d7f62ea87..b608c230e45 100644 --- a/addons/stock/models/stock_rule.py +++ b/addons/stock/models/stock_rule.py @@ -207,7 +207,8 @@ class StockRule(models.Model): 'picking_type_id': self.picking_type_id.id, 'propagate_cancel': self.propagate_cancel, 'warehouse_id': self.warehouse_id.id, - 'delay_alert': self.delay_alert + 'delay_alert': self.delay_alert, + 'procure_method': 'make_to_order', } return new_move_vals