[REF] stock: rescheduling propagation

This commit moves the "propagation minimum delta" from a company setting to a
per-rule setting.

We implement this idea in the stock module and take this opportunity to clean
up the override of stock move's write.

We add a few basics tests.

task-1913401
This commit is contained in:
Haresh Shyara
2019-05-29 12:48:52 +00:00
committed by William Henrotin
parent 1d6bc7286c
commit c514e8ae6a
7 changed files with 119 additions and 47 deletions
-1
View File
@@ -7,7 +7,6 @@ from odoo import api, fields, models, _
class Company(models.Model):
_inherit = "res.company"
propagation_minimum_delta = fields.Integer('Minimum Delta for Propagation of a Date Change on moves linked together', default=1)
internal_transit_location_id = fields.Many2one(
'stock.location', 'Internal Transit Location', ondelete="restrict",
help="Technical field used for resupply routes between warehouses that belong to this company")
@@ -26,12 +26,6 @@ class ResConfigSettings(models.TransientModel):
implied_group='stock.group_adv_location',
help="Add and customize route operations to process product moves in your warehouse(s): e.g. unload > quality control > stock for incoming products, pick > pack > ship for outgoing products. \n You can also set putaway strategies on warehouse locations in order to send incoming products into specific child locations straight away (e.g. specific bins, racks).")
group_warning_stock = fields.Boolean("Warnings for Stock", implied_group='stock.group_warning_stock')
propagation_minimum_delta = fields.Integer(related='company_id.propagation_minimum_delta', string="Minimum Delta for Propagation", readonly=False)
use_propagation_minimum_delta = fields.Boolean(
string="No Rescheduling Propagation",
oldname='default_new_propagation_minimum_delta',
config_parameter='stock.use_propagation_minimum_delta',
help="Rescheduling applies to any chain of operations (e.g. Make To Order, Pick Pack Ship). In the case of MTO sales, a vendor delay (updated incoming date) impacts the expected delivery date to the customer. \n This option allows to not propagate the rescheduling if the change is not critical.")
module_stock_picking_batch = fields.Boolean("Batch Pickings", oldname="module_stock_picking_wave")
module_stock_barcode = fields.Boolean("Barcode Scanner")
module_delivery_dhl = fields.Boolean("DHL USA")
@@ -44,11 +38,6 @@ class ResConfigSettings(models.TransientModel):
help="Store products in specific locations of your warehouse (e.g. bins, racks) and to track inventory accordingly.")
group_stock_multi_warehouses = fields.Boolean('Multi-Warehouses', implied_group='stock.group_stock_multi_warehouses')
@api.onchange('use_propagation_minimum_delta')
def _onchange_use_propagation_minimum_delta(self):
if not self.use_propagation_minimum_delta:
self.propagation_minimum_delta = 1
@api.onchange('group_stock_multi_locations')
def _onchange_group_stock_multi_locations(self):
if not self.group_stock_multi_locations:
+14 -19
View File
@@ -10,7 +10,6 @@ from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError
from odoo.osv import expression
from odoo.tools import DEFAULT_SERVER_DATETIME_FORMAT
from odoo.tools.float_utils import float_compare, float_round, float_is_zero
PROCUREMENT_PRIORITIES = [('0', 'Not urgent'), ('1', 'Normal'), ('2', 'Urgent'), ('3', 'Very Urgent')]
@@ -128,6 +127,10 @@ class StockMove(models.Model):
propagate = fields.Boolean(
'Propagate cancel and split', default=True,
help='If checked, when this move is cancelled, cancel the linked move too')
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')
picking_type_id = fields.Many2one('stock.picking.type', 'Operation Type')
inventory_id = fields.Many2one('stock.inventory', 'Inventory')
move_line_ids = fields.One2many('stock.move.line', 'move_id')
@@ -403,7 +406,6 @@ class StockMove(models.Model):
# TDE CLEANME: it is a gros bordel + tracking
Picking = self.env['stock.picking']
propagated_changes_dict = {}
#propagation of expected date:
propagated_date_field = False
if vals.get('date_expected'):
@@ -413,24 +415,17 @@ class StockMove(models.Model):
#propagate also any delta observed when setting the move as done
propagated_date_field = 'date'
if not self._context.get('do_not_propagate', False) and (propagated_date_field or propagated_changes_dict):
if propagated_date_field:
#any propagation is (maybe) needed
for move in self:
if move.move_dest_ids and move.propagate:
if 'date_expected' in propagated_changes_dict:
propagated_changes_dict.pop('date_expected')
if propagated_date_field:
current_date = move.date_expected
new_date = fields.Datetime.from_string(vals.get(propagated_date_field))
delta_days = (new_date - current_date).total_seconds() / 86400
if abs(delta_days) >= move.company_id.propagation_minimum_delta:
old_move_date = move.move_dest_ids[0].date_expected
new_move_date = (old_move_date + relativedelta.relativedelta(days=delta_days or 0)).strftime(DEFAULT_SERVER_DATETIME_FORMAT)
propagated_changes_dict['date_expected'] = new_move_date
#For pushed moves as well as for pulled moves, propagate by recursive call of write().
#Note that, for pulled moves we intentionally don't propagate on the procurement.
if propagated_changes_dict:
move.move_dest_ids.filtered(lambda m: m.state not in ('done', 'cancel')).write(propagated_changes_dict)
if move.move_dest_ids and move.propagate_date:
new_date = vals.get(propagated_date_field)
delta_days = (new_date - move.date_expected).total_seconds() / 86400
if abs(delta_days) < move.propagate_date_minimum_delta:
continue
for move_dest in move.move_dest_ids:
if move_dest.state not in ('done', 'cancel'):
move_dest.date_expected += relativedelta.relativedelta(days=delta_days)
track_pickings = not self._context.get('mail_notrack') and any(field in vals for field in ['state', 'picking_id', 'partially_available'])
if track_pickings:
to_track_picking_ids = set([move.picking_id.id for move in self if move.picking_id])
@@ -1217,7 +1212,7 @@ class StockMove(models.Model):
# compatible with the move's UOM.
new_product_qty = self.product_id.uom_id._compute_quantity(self.product_qty - qty, self.product_uom, round=False)
new_product_qty = float_round(new_product_qty, precision_digits=self.env['decimal.precision'].precision_get('Product Unit of Measure'))
self.with_context(do_not_propagate=True, do_not_unreserve=True, rounding_method='HALF-UP').write({'product_uom_qty': new_product_qty})
self.with_context(do_not_unreserve=True, rounding_method='HALF-UP').write({'product_uom_qty': new_product_qty})
new_move = new_move._action_confirm(merge=False)
return new_move.id
+6
View File
@@ -70,6 +70,10 @@ class StockRule(models.Model):
help="The 'Manual Operation' value will create a stock move after the current one. "
"With 'Automatic No Step Added', the location is replaced in the original move.")
rule_message = fields.Html(compute='_compute_action_message')
propagate_date = fields.Boolean(string="Propagate Rescheduling", default=True,
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', default=1)
@api.onchange('picking_type_id')
def _onchange_picking_type(self):
@@ -262,6 +266,8 @@ class StockRule(models.Model):
'date': date_expected,
'date_expected': date_expected,
'propagate': self.propagate,
'propagate_date': self.propagate_date,
'propagate_date_minimum_delta': self.propagate_date_minimum_delta,
'description_picking': product_id._get_description(self.picking_type_id),
'priority': values.get('priority', "1"),
}
+92
View File
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo.tests.common import TransactionCase
from odoo.tools import mute_logger
@@ -64,3 +66,93 @@ class TestProcRule(TransactionCase):
('move_dest_ids', 'in', [pick_output.move_lines[0].id])
])
self.assertEqual(len(moves.ids), 1, "It should have created a picking from Stock to Output with the original picking as destination")
def test_rule_propagate_1(self):
move_dest = self.env['stock.move'].create({
'name': 'move_dest',
'product_id': self.ref('product.product_product_3'),
'product_uom': self.ref('uom.product_uom_unit'),
'location_id': self.ref('stock.stock_location_output'),
'location_dest_id': self.ref('stock.stock_location_customers'),
})
move_orig = self.env['stock.move'].create({
'name': 'move_orig',
'product_id': self.ref('product.product_product_3'),
'product_uom': self.ref('uom.product_uom_unit'),
'move_dest_ids': [(4, move_dest.id)],
'propagate_date': True,
'propagate_date_minimum_delta': 5,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
})
move_dest_initial_date = move_dest.date_expected
# change above the minimum delta
move_orig.date_expected += timedelta(days=6)
self.assertEquals(move_dest.date_expected, move_dest_initial_date + timedelta(days=6), 'date should be propagated as the minimum delta is below')
# change below the minimum delta
move_dest_initial_date = move_dest.date_expected
move_orig.date_expected += timedelta(days=4)
self.assertAlmostEquals(move_dest.date_expected, move_dest_initial_date, delta=timedelta(seconds=1), msg='date should not be propagated as the minimum delta is above')
def test_rule_propagate_2(self):
move_dest = self.env['stock.move'].create({
'name': 'move_dest',
'product_id': self.ref('product.product_product_3'),
'product_uom': self.ref('uom.product_uom_unit'),
'location_id': self.ref('stock.stock_location_output'),
'location_dest_id': self.ref('stock.stock_location_customers'),
})
move_orig = self.env['stock.move'].create({
'name': 'move_orig',
'product_id': self.ref('product.product_product_3'),
'product_uom': self.ref('uom.product_uom_unit'),
'move_dest_ids': [(4, move_dest.id)],
'propagate_date': False,
'propagate_date_minimum_delta': 5,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
})
move_dest_initial_date = move_dest.date_expected
# change below the minimum delta
move_orig.date_expected += timedelta(days=4)
self.assertEquals(move_dest.date_expected, move_dest_initial_date, 'date should not be propagated')
# change above the minimum delta
move_orig.date_expected += timedelta(days=2)
self.assertEquals(move_dest.date_expected, move_dest_initial_date, 'date should not be propagated')
def test_rule_propagate_3(self):
move_dest = self.env['stock.move'].create({
'name': 'move_dest',
'product_id': self.ref('product.product_product_3'),
'product_uom': self.ref('uom.product_uom_unit'),
'location_id': self.ref('stock.stock_location_output'),
'location_dest_id': self.ref('stock.stock_location_customers'),
})
move_orig = self.env['stock.move'].create({
'name': 'move_orig',
'product_id': self.ref('product.product_product_3'),
'product_uom': self.ref('uom.product_uom_unit'),
'move_dest_ids': [(4, move_dest.id)],
'propagate_date': True,
'propagate_date_minimum_delta': 5,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
'quantity_done': 10,
})
move_orig.date_expected -= timedelta(days=6)
move_dest_initial_date = move_dest.date_expected
move_orig_initial_date = move_orig.date_expected
move_orig._action_done()
self.assertEquals(move_orig.date_expected, move_orig_initial_date, 'schedule date should not be impacted by action_done')
self.assertAlmostEquals(move_orig.date, datetime.now(), delta=timedelta(seconds=1), msg='date should be now')
self.assertEquals(move_dest.date_expected, move_dest_initial_date + timedelta(days=6), 'date should be propagated')
@@ -302,22 +302,6 @@
<div class="row mt16 o_settings_container">
<div id="sale_security_lead"/>
<div id="purchase_po_lead"/>
<div class="col-12 col-lg-6 o_setting_box" id="propagation_info" title="Rescheduling applies to any chain of operations (e.g. Make To Order, Pick Pack Ship). In the case of MTO sales, a vendor delay (updated incoming date) impacts the expected delivery date to the customer. This option allows to not propagate the rescheduling if the change is not critical.">
<div class="o_setting_left_pane">
<field name="use_propagation_minimum_delta"/>
</div>
<div class="o_setting_right_pane">
<label for="use_propagation_minimum_delta"/>
<div class="text-muted">
Don’t propagate scheduling changes through chains of operations
</div>
<div class="content-group">
<div class="mt16" attrs="{'invisible': [('use_propagation_minimum_delta', '=', False)]}">
<span>Change must be higher than <field name="propagation_minimum_delta" class="oe_inline"/> days to be propagated</span>
</div>
</div>
</div>
</div>
</div>
</div>
</xpath>
+7
View File
@@ -103,6 +103,13 @@
<field name="partner_address_id" attrs="{'invisible': [('action', '=', 'push')]}"/>
<field name="delay"/>
</group>
<group name="scheduling_group" string="Scheduling" attrs="{'invisible': [('action', '=', 'push')]}" groups="base.group_no_one">
<field name="propagate_date"/>
<label for="propagate_date_minimum_delta" attrs="{'invisible': [('propagate_date', '=', False)]}"/>
<div attrs="{'invisible': [('propagate_date', '=', False)]}">
<field name="propagate_date_minimum_delta" class="oe_inline"/> day(s)
</div>
</group>
</group>
</sheet>
</form>