[REF] purchase_stock: rescheduling propagation
Implement the propagation minimum delta per rule in purchase_stock. Override properly the write to handle both RFQ and PO. Take care of not merging po lines where the expecte date should be different Add propre test cases. task-1913401
This commit is contained in:
committed by
William Henrotin
parent
c514e8ae6a
commit
77b283f307
@@ -3,7 +3,7 @@
|
||||
|
||||
from odoo import api, fields, models, _
|
||||
from odoo.tools.float_utils import float_compare
|
||||
|
||||
from dateutil import relativedelta
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
from odoo.addons.purchase.models.purchase import PurchaseOrder as Purchase
|
||||
@@ -227,6 +227,8 @@ class PurchaseOrderLine(models.Model):
|
||||
move_ids = fields.One2many('stock.move', 'purchase_line_id', string='Reservation', readonly=True, ondelete='set null', copy=False)
|
||||
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint', 'Orderpoint')
|
||||
move_dest_ids = fields.One2many('stock.move', 'created_purchase_line_id', 'Downstream Moves')
|
||||
propagate_date = fields.Boolean(string="Propagate Rescheduling", help='The rescheduling is propagated to the next move.')
|
||||
propagate_date_minimum_delta = fields.Integer(string='Reschedule if Higher Than', help='The change must be higher than this value to be propagated')
|
||||
|
||||
@api.multi
|
||||
def _compute_qty_received_method(self):
|
||||
@@ -266,12 +268,18 @@ class PurchaseOrderLine(models.Model):
|
||||
|
||||
@api.multi
|
||||
def write(self, values):
|
||||
for line in self:
|
||||
if values.get('date_planned') and line.propagate_date:
|
||||
new_date = values['date_planned']
|
||||
delta_days = (new_date - line.date_planned).total_seconds() / 86400
|
||||
if abs(delta_days) < line.propagate_date_minimum_delta:
|
||||
continue
|
||||
moves_to_update = line.move_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
|
||||
if not moves_to_update:
|
||||
moves_to_update = line.move_dest_ids.filtered(lambda m: m.state not in ('done', 'cancel'))
|
||||
for move in moves_to_update:
|
||||
move.date_expected = move.date_expected + relativedelta.relativedelta(days=delta_days)
|
||||
result = super(PurchaseOrderLine, self).write(values)
|
||||
# Update expected date of corresponding moves
|
||||
if 'date_planned' in values:
|
||||
self.env['stock.move'].search([
|
||||
('purchase_line_id', 'in', self.ids), ('state', '!=', 'done')
|
||||
]).write({'date_expected': values['date_planned']})
|
||||
if 'product_qty' in values:
|
||||
self.filtered(lambda l: l.order_id.state == 'purchase')._create_or_update_picking()
|
||||
return result
|
||||
@@ -361,6 +369,8 @@ class PurchaseOrderLine(models.Model):
|
||||
'picking_type_id': self.order_id.picking_type_id.id,
|
||||
'group_id': self.order_id.group_id.id,
|
||||
'origin': self.order_id.name,
|
||||
'propagate_date': self.propagate_date,
|
||||
'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
|
||||
'description_picking': self.product_id._get_description(self.order_id.picking_type_id),
|
||||
'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,
|
||||
@@ -388,4 +398,5 @@ class PurchaseOrderLine(models.Model):
|
||||
args can be merged. If it returns an empty record then a new line will
|
||||
be created.
|
||||
"""
|
||||
return self and self[0] or self.env['purchase.order.line']
|
||||
lines = self.filtered(lambda l: l.propagate_date == values['propagate_date'] and l.propagate_date_minimum_delta == values['propagate_date_minimum_delta'])
|
||||
return lines and lines[0] or self.env['purchase.order.line']
|
||||
|
||||
@@ -51,6 +51,8 @@ class StockRule(models.Model):
|
||||
partner = supplier.name
|
||||
# we put `supplier_info` in values for extensibility purposes
|
||||
procurement.values['supplier'] = supplier
|
||||
procurement.values['propagate_date'] = rule.propagate_date
|
||||
procurement.values['propagate_date_minimum_delta'] = rule.propagate_date_minimum_delta
|
||||
|
||||
domain = rule._make_po_get_domain(procurement.company_id, procurement.values, partner)
|
||||
procurements_by_po_domain[domain].append((procurement, rule))
|
||||
@@ -115,11 +117,11 @@ class StockRule(models.Model):
|
||||
|
||||
@api.model
|
||||
def _get_procurements_to_merge_groupby(self, procurement):
|
||||
return procurement.product_id, procurement.product_uom
|
||||
return procurement.product_id, procurement.product_uom, procurement.values['propagate_date'], procurement.values['propagate_date_minimum_delta']
|
||||
|
||||
@api.model
|
||||
def _get_procurements_to_merge_sorted(self, procurement):
|
||||
return procurement.product_id.id, procurement.product_uom.id
|
||||
return procurement.product_id.id, procurement.product_uom.id, procurement.values['propagate_date'], procurement.values['propagate_date_minimum_delta']
|
||||
|
||||
@api.model
|
||||
def _get_procurements_to_merge(self, procurements):
|
||||
@@ -231,6 +233,8 @@ class StockRule(models.Model):
|
||||
'product_uom': product_id.uom_po_id.id,
|
||||
'price_unit': price_unit,
|
||||
'date_planned': date_planned,
|
||||
'propagate_date': values['propagate_date'],
|
||||
'propagate_date_minimum_delta': values['propagate_date_minimum_delta'],
|
||||
'orderpoint_id': values.get('orderpoint_id', False) and values.get('orderpoint_id').id,
|
||||
'taxes_id': [(6, 0, taxes_id.ids)],
|
||||
'order_id': po.id,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
|
||||
from odoo.addons.product.tests import common
|
||||
|
||||
@@ -313,3 +313,95 @@ class TestCreatePicking(common.TestProductCommon):
|
||||
purchase_order_2.picking_ids.button_validate()
|
||||
|
||||
self.assertEqual(sum(customer_picking.move_lines.mapped('reserved_availability')), 100.0, 'The total quantity for the customer move should be available and reserved.')
|
||||
|
||||
def create_delivery_order(self, propagate_date, propagate_date_minimum_delta):
|
||||
stock_location = self.env['ir.model.data'].xmlid_to_object('stock.stock_location_stock')
|
||||
customer_location = self.env['ir.model.data'].xmlid_to_object('stock.stock_location_customers')
|
||||
unit = self.ref("uom.product_uom_unit")
|
||||
picking_type_out = self.env['ir.model.data'].xmlid_to_object('stock.picking_type_out')
|
||||
partner = self.env['res.partner'].create({'name': 'AAA', 'email': 'from.test@example.com'})
|
||||
supplier_info1 = self.env['product.supplierinfo'].create({
|
||||
'name': partner.id,
|
||||
'price': 50,
|
||||
})
|
||||
|
||||
warehouse1 = self.env.ref('stock.warehouse0')
|
||||
route_buy = warehouse1.buy_pull_id.route_id
|
||||
route_mto = warehouse1.mto_pull_id.route_id
|
||||
|
||||
# change propagete date and Reschedule if Higher Than on rules
|
||||
route_buy.rule_ids.write({'propagate_date': propagate_date,
|
||||
'propagate_date_minimum_delta': propagate_date_minimum_delta})
|
||||
|
||||
product = self.env['product.product'].create({
|
||||
'name': 'Usb Keyboard',
|
||||
'type': 'product',
|
||||
'uom_id': unit,
|
||||
'uom_po_id': unit,
|
||||
'seller_ids': [(6, 0, [supplier_info1.id])],
|
||||
'route_ids': [(6, 0, [route_buy.id, route_mto.id])]
|
||||
})
|
||||
|
||||
delivery_order = self.env['stock.picking'].create({
|
||||
'location_id': stock_location.id,
|
||||
'location_dest_id': customer_location.id,
|
||||
'partner_id': partner.id,
|
||||
'picking_type_id': picking_type_out.id,
|
||||
})
|
||||
|
||||
customer_move = self.env['stock.move'].create({
|
||||
'name': 'move out',
|
||||
'location_id': stock_location.id,
|
||||
'location_dest_id': customer_location.id,
|
||||
'product_id': product.id,
|
||||
'product_uom': product.uom_id.id,
|
||||
'product_uom_qty': 10.0,
|
||||
'procure_method': 'make_to_order',
|
||||
'picking_id': delivery_order.id,
|
||||
})
|
||||
|
||||
customer_move._action_confirm()
|
||||
# find created po the product
|
||||
purchase_order = self.env['purchase.order'].search([('partner_id', '=', partner.id)])
|
||||
|
||||
return delivery_order, purchase_order
|
||||
|
||||
def test_05_if_propagate_date(self):
|
||||
""" In order to check scheduled date of the delivery order is changed based
|
||||
stock rules with propagate date and minimum delta.
|
||||
"""
|
||||
|
||||
# Create Delivery Order and with propagate date and minimum delta
|
||||
delivery_order, purchase_order = self.create_delivery_order(True, 5)
|
||||
|
||||
# check po is created or not
|
||||
self.assertTrue(purchase_order, 'No purchase order created.')
|
||||
|
||||
purchase_order_line = purchase_order.order_line
|
||||
|
||||
# change scheduled date of po line.
|
||||
purchase_order_line.write({'date_planned': purchase_order_line.date_planned + timedelta(days=5)})
|
||||
|
||||
# Now check scheduled date of delivery order is changed or not.
|
||||
self.assertEquals(purchase_order_line.date_planned, delivery_order.scheduled_date,
|
||||
'Delivery order schedule date should be changed as we have set date propagate.')
|
||||
|
||||
def test_06_no_propagate_date(self):
|
||||
""" In order to check scheduled date of the delivery order is changed based
|
||||
stock rules without propagate date and minimum delta.
|
||||
"""
|
||||
|
||||
# Create Delivery Order and without propagate date and minimum delta
|
||||
delivery_order, purchase_order = self.create_delivery_order(False, 5)
|
||||
|
||||
# check po is created or not
|
||||
self.assertTrue(purchase_order, 'No purchase order created.')
|
||||
|
||||
purchase_order_line = purchase_order.order_line
|
||||
|
||||
# change scheduled date of po line.
|
||||
purchase_order_line.write({'date_planned': purchase_order_line.date_planned + timedelta(days=5)})
|
||||
|
||||
# Now check scheduled date of delivery order is changed or not.
|
||||
self.assertNotEquals(purchase_order_line.date_planned, delivery_order.scheduled_date,
|
||||
'Delivery order schedule date should not changed.')
|
||||
|
||||
@@ -136,3 +136,106 @@ class TestPurchaseLeadTime(TestPurchase):
|
||||
incoming_shipment2 = self.env['stock.picking'].search([('picking_type_id', '=', self.warehouse_1.int_type_id.id), ('location_id', '=', self.warehouse_1.wh_qc_stock_loc_id.id), ('location_dest_id', '=', self.warehouse_1.lot_stock_id.id)])
|
||||
incoming_shipment2_date = schedule_date - timedelta(days=incoming_shipment2.move_lines[0].rule_id.delay)
|
||||
self.assertEqual(incoming_shipment2.scheduled_date, incoming_shipment2_date, 'Schedule date of Internal Type shipment for quality control stock location should be equal to: schedule date of Internal type shipment for input stock location + push rule delay..')
|
||||
|
||||
def test_merge_po_line(self):
|
||||
"""Chage that merging po line for same procurement is done depending on
|
||||
propagate_date and propagate_date_minimum_delta"""
|
||||
|
||||
# create a product with manufacture route
|
||||
product_1 = self.env['product.product'].create({
|
||||
'name': 'AAA',
|
||||
'route_ids': [(4, self.route_buy)],
|
||||
'seller_ids': [(0, 0, {'name': self.partner_1.id, 'delay': 5})]
|
||||
})
|
||||
|
||||
# create a move for product_1 from stock to output and reserve to trigger the
|
||||
# rule
|
||||
move_1 = self.env['stock.move'].create({
|
||||
'name': 'move_1',
|
||||
'product_id': product_1.id,
|
||||
'product_uom': self.ref('uom.product_uom_unit'),
|
||||
'propagate_date': True,
|
||||
'propagate_date_minimum_delta': 1,
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_output'),
|
||||
'product_uom_qty': 10,
|
||||
'procure_method': 'make_to_order'
|
||||
})
|
||||
|
||||
move_1._action_confirm()
|
||||
po_line = self.env['purchase.order.line'].search([
|
||||
('product_id', '=', product_1.id),
|
||||
])
|
||||
self.assertEqual(len(po_line), 1, 'the purchase order line is not created')
|
||||
self.assertEqual(po_line.product_qty, 10, 'the purchase order line has a wrong quantity')
|
||||
|
||||
move_2 = self.env['stock.move'].create({
|
||||
'name': 'move_2',
|
||||
'product_id': product_1.id,
|
||||
'product_uom': self.ref('uom.product_uom_unit'),
|
||||
'propagate_date': True,
|
||||
'propagate_date_minimum_delta': 1,
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_output'),
|
||||
'product_uom_qty': 5,
|
||||
'procure_method': 'make_to_order'
|
||||
})
|
||||
|
||||
move_2._action_confirm()
|
||||
po_line = self.env['purchase.order.line'].search([
|
||||
('product_id', '=', product_1.id),
|
||||
])
|
||||
self.assertEqual(len(po_line), 1, 'the purchase order lines should be merged')
|
||||
self.assertEqual(po_line.product_qty, 15, 'the purchase order line has a wrong quantity')
|
||||
|
||||
def test_merge_po_line_2(self):
|
||||
"""Chage that merging po line for same procurement is done depending on
|
||||
propagate_date and propagate_date_minimum_delta"""
|
||||
|
||||
# create a product with manufacture route
|
||||
product_1 = self.env['product.product'].create({
|
||||
'name': 'AAA',
|
||||
'route_ids': [(4, self.route_buy)],
|
||||
'seller_ids': [(0, 0, {'name': self.partner_1.id, 'delay': 5})]
|
||||
})
|
||||
|
||||
# create a move for product_1 from stock to output and reserve to trigger the
|
||||
# rule
|
||||
move_1 = self.env['stock.move'].create({
|
||||
'name': 'move_1',
|
||||
'product_id': product_1.id,
|
||||
'product_uom': self.ref('uom.product_uom_unit'),
|
||||
'propagate_date': True,
|
||||
'propagate_date_minimum_delta': 1,
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_output'),
|
||||
'product_uom_qty': 10,
|
||||
'procure_method': 'make_to_order'
|
||||
})
|
||||
|
||||
move_1._action_confirm()
|
||||
po_line = self.env['purchase.order.line'].search([
|
||||
('product_id', '=', product_1.id),
|
||||
])
|
||||
self.assertEqual(len(po_line), 1, 'the purchase order line is not created')
|
||||
self.assertEqual(po_line.product_qty, 10, 'the purchase order line has a wrong quantity')
|
||||
po_line.propagate_date = not po_line.propagate_date
|
||||
move_2 = self.env['stock.move'].create({
|
||||
'name': 'move_2',
|
||||
'product_id': product_1.id,
|
||||
'product_uom': self.ref('uom.product_uom_unit'),
|
||||
'propagate_date': True,
|
||||
'propagate_date_minimum_delta': 1,
|
||||
'location_id': self.ref('stock.stock_location_stock'),
|
||||
'location_dest_id': self.ref('stock.stock_location_output'),
|
||||
'product_uom_qty': 5,
|
||||
'procure_method': 'make_to_order'
|
||||
})
|
||||
|
||||
move_2._action_confirm()
|
||||
po_line = self.env['purchase.order.line'].search([
|
||||
('product_id', '=', product_1.id),
|
||||
])
|
||||
self.assertEqual(len(po_line), 2, 'the purchase order lines are not merged')
|
||||
self.assertEqual(po_line[0].product_qty, 10, 'the purchase order line has a wrong quantity')
|
||||
self.assertEqual(po_line[1].product_qty, 5, 'the purchase order line has a wrong quantity')
|
||||
|
||||
Reference in New Issue
Block a user