[REF] purchase_stock: push the extra quantity

With a product configured as buy on order and a warehouse configured as
receipt in two steps, if the user increases the quantity on a po line
generated by a sale order before confirming it, the system will send all
the quantity to input but only the ordered quantity to customer. The
issue is that the "extra quantity" will stay in input.

We fix this issue by creating a new move with the extra quantity to the
input location so that push rules will send the extra quantity to stock
while the ordered quantity will be sent to the customer.

There was also an issue when incrementing the quantity on the po line
after confirmation if the po line was the result of a reordering rule:
only a move from supplier to the location of the reordering rule was
created.

This commit also introduces a change of semantic:
`created_purchase_line_id` is cleared after confirming the RFQ. This
allows to merge more in `_merge_moves`.

task-1981355

closes odoo/odoo#43545

Signed-off-by: Simon Lejeune (sle) <sle@openerp.com>
This commit is contained in:
Simon Lejeune
2020-01-28 14:51:36 +00:00
parent aebce884d4
commit 9d98c43581
7 changed files with 80 additions and 22 deletions
+4 -3
View File
@@ -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',
+33 -11
View File
@@ -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:
+2
View File
@@ -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)
@@ -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)
@@ -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
@@ -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
+2 -1
View File
@@ -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