diff --git a/addons/sale_order_dates/__manifest__.py b/addons/sale_order_dates/__manifest__.py
index 2ee5c999d25..f2704dca85b 100644
--- a/addons/sale_order_dates/__manifest__.py
+++ b/addons/sale_order_dates/__manifest__.py
@@ -3,18 +3,13 @@
{
- 'name': 'Dates on Sales Order',
+ 'name': 'Delivery Dates on Sales Order',
'version': '1.1',
'category': 'Sales',
'description': """
-Add additional date information to the sales order.
+Manage delivery dates from sales orders.
===================================================
-
-You can add the following additional dates to a sales order:
-------------------------------------------------------------
- * Requested Date (will be used as the expected date on pickings)
- * Commitment Date
- * Effective Date
+ This option introduces extra fields in the sales order to easily schedule product deliveries on your own: expected date, commitment date, effective date.
""",
'website': 'https://www.odoo.com/page/crm',
'depends': ['sale_stock'],
diff --git a/addons/sale_order_dates/models/sale_order.py b/addons/sale_order_dates/models/sale_order.py
index 0a01683040d..b38b6a507c1 100644
--- a/addons/sale_order_dates/models/sale_order.py
+++ b/addons/sale_order_dates/models/sale_order.py
@@ -9,54 +9,47 @@ class SaleOrder(models.Model):
"""Add several date fields to Sales Orders, computed or user-entered"""
_inherit = 'sale.order'
- commitment_date = fields.Datetime(compute='_compute_commitment_date', string='Commitment Date', store=True,
- help="Date by which the products are sure to be delivered. This is "
- "a date that you can promise to the customer, based on the "
- "Product Lead Times.")
- requested_date = fields.Datetime('Requested Date', readonly=True, states={'draft': [('readonly', False)],
- 'sent': [('readonly', False)]}, copy=False,
- help="Date by which the customer has requested the items to be "
- "delivered.\n"
- "When this Order gets confirmed, the Delivery Order's "
- "expected date will be computed based on this date and the "
- "Company's Security Delay.\n"
- "Leave this field empty if you want the Delivery Order to be "
- "processed as soon as possible. In that case the expected "
- "date will be computed using the default method: based on "
- "the Product Lead Times and the Company's Security Delay.")
- effective_date = fields.Date(compute='_compute_picking_ids', string='Effective Date', store=True,
- help="Date on which the first Delivery Order was created.")
+ expected_date = fields.Datetime(compute='_compute_expected_date',
+ string='Expected Date', store=False, oldname='commitment_date',
+ help="Delivery date you can promise to the customer, computed from "
+ "product lead times and from the shipping policy of the order.")
+ commitment_date = fields.Datetime('Commitment Date',
+ states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
+ copy=False, oldname='requested_date', readonly=True,
+ help="This is the delivery date promised to the customer. If set, the delivery order "
+ "will be scheduled based on this date rather than product lead times.")
+ effective_date = fields.Date(compute='_compute_effective_date',
+ string='Effective Date', store=True,
+ help="Completion date of the first delivery order.")
- @api.depends('date_order', 'order_line.customer_lead')
- def _compute_commitment_date(self):
- """Compute the commitment date"""
+ @api.depends('order_line.customer_lead', 'confirmation_date', 'picking_policy', 'order_line.state')
+ def _compute_expected_date(self):
+ """Compute the expected date"""
for order in self:
dates_list = []
- order_datetime = fields.Datetime.from_string(order.date_order)
+ confirm_date = fields.Datetime.from_string(order.confirmation_date if order.state == 'sale' else fields.Datetime.now())
for line in order.order_line.filtered(lambda x: x.state != 'cancel'):
- dt = order_datetime + timedelta(days=line.customer_lead or 0.0)
+ dt = confirm_date + timedelta(days=line.customer_lead or 0.0)
dates_list.append(dt)
if dates_list:
- commit_date = min(dates_list) if order.picking_policy == 'direct' else max(dates_list)
- order.commitment_date = fields.Datetime.to_string(commit_date)
+ expected_date = min(dates_list) if order.picking_policy == 'direct' else max(dates_list)
+ order.expected_date = fields.Datetime.to_string(expected_date)
- def _compute_picking_ids(self):
- super(SaleOrder, self)._compute_picking_ids()
+ @api.depends('picking_ids.date_done')
+ def _compute_effective_date(self):
for order in self:
- dates_list = []
- for pick in order.picking_ids:
- dates_list.append(fields.Datetime.from_string(pick.date))
- if dates_list:
- order.effective_date = fields.Datetime.to_string(min(dates_list))
+ pickings = order.picking_ids.filtered(lambda x: x.state == 'done' and x.location_dest_id.usage == 'customer')
+ dates_list = pickings.mapped('date_done')
+ order.effective_date = dates_list and min(dates_list)
- @api.onchange('requested_date')
- def onchange_requested_date(self):
- """Warn if the requested dates is sooner than the commitment date"""
- if (self.requested_date and self.commitment_date and self.requested_date < self.commitment_date):
+ @api.onchange('commitment_date')
+ def onchange_commitment_date(self):
+ """Warn if the commitment dates is sooner than the expected date"""
+ if (self.commitment_date and self.expected_date and self.commitment_date < self.expected_date):
return {'warning': {
- 'title': _('Requested date is too soon!'),
- 'message': _("The date requested by the customer is "
- "sooner than the commitment date. You may be "
+ 'title': _('Commitment date is too soon!'),
+ 'message': _("The date committed by the customer is "
+ "sooner than the expected date. You may be "
"unable to honor the customer's request.")
}
}
@@ -68,8 +61,8 @@ class SaleOrderLine(models.Model):
@api.multi
def _prepare_procurement_values(self, group_id):
vals = super(SaleOrderLine, self)._prepare_procurement_values(group_id=group_id)
- for line in self.filtered("order_id.requested_date"):
- date_planned = fields.Datetime.from_string(line.order_id.requested_date) - timedelta(days=line.order_id.company_id.security_lead)
+ for line in self.filtered("order_id.commitment_date"):
+ date_planned = fields.Datetime.from_string(line.order_id.commitment_date) - timedelta(days=line.order_id.company_id.security_lead)
vals.update({
'date_planned': fields.Datetime.to_string(date_planned),
})
diff --git a/addons/sale_order_dates/tests/__init__.py b/addons/sale_order_dates/tests/__init__.py
index 9df19b529fd..49db8141e9d 100644
--- a/addons/sale_order_dates/tests/__init__.py
+++ b/addons/sale_order_dates/tests/__init__.py
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from . import test_requested_date
+from . import test_expected_date
+from . import test_commitment_date
\ No newline at end of file
diff --git a/addons/sale_order_dates/tests/test_requested_date.py b/addons/sale_order_dates/tests/test_commitment_date.py
similarity index 56%
rename from addons/sale_order_dates/tests/test_requested_date.py
rename to addons/sale_order_dates/tests/test_commitment_date.py
index 7ed58f42f27..3b9494702ed 100644
--- a/addons/sale_order_dates/tests/test_requested_date.py
+++ b/addons/sale_order_dates/tests/test_commitment_date.py
@@ -7,16 +7,16 @@ from odoo import fields
class TestSaleOrderDates(common.TransactionCase):
- def test_sale_order_requested_date(self):
+ def test_sale_order_commitment_date(self):
- # In order to test the Requested Date feature in Sales Orders in Odoo,
- # I copy a demo Sales Order with Requested Date on 2010-07-12
- new_order = self.env.ref('sale.sale_order_6').copy({'requested_date': '2010-07-12'})
+ # In order to test the Commitment Date feature in Sales Orders in Odoo,
+ # I copy a demo Sales Order with committed Date on 2010-07-12
+ new_order = self.env.ref('sale.sale_order_6').copy({'commitment_date': '2010-07-12'})
# I confirm the Sales Order.
new_order.action_confirm()
# I verify that the Procurements and Stock Moves have been generated with the correct date
security_delay = timedelta(days=new_order.company_id.security_lead)
- requested_date = fields.Datetime.from_string(new_order.requested_date)
- right_date = fields.Datetime.to_string(requested_date - security_delay)
+ commitment_date = fields.Datetime.from_string(new_order.commitment_date)
+ right_date = fields.Datetime.to_string(commitment_date - security_delay)
for line in new_order.order_line:
self.assertEqual(line.move_ids[0].date_expected, right_date, "The expected date for the Stock Move is wrong")
diff --git a/addons/sale_order_dates/tests/test_expected_date.py b/addons/sale_order_dates/tests/test_expected_date.py
new file mode 100644
index 00000000000..a565621cb4c
--- /dev/null
+++ b/addons/sale_order_dates/tests/test_expected_date.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from datetime import timedelta
+from odoo import fields
+from odoo.tests import common
+
+
+class TestSaleExpectedDate(common.TransactionCase):
+
+ def test_sale_order_expected_date(self):
+ """ Test expected date and effective date of Sales Orders """
+ Product = self.env['product.product']
+
+ product_A = Product.create({
+ 'name': 'Product A',
+ 'type': 'product',
+ 'sale_delay': 5,
+ 'uom_id': 1,
+ })
+ product_B = Product.create({
+ 'name': 'Product B',
+ 'type': 'product',
+ 'sale_delay': 10,
+ 'uom_id': 1,
+ })
+ product_C = Product.create({
+ 'name': 'Product C',
+ 'type': 'product',
+ 'sale_delay': 15,
+ 'uom_id': 1,
+ })
+
+ self.env['stock.quant']._update_available_quantity(product_A, self.env.ref('stock.stock_location_stock'), 10)
+ self.env['stock.quant']._update_available_quantity(product_B, self.env.ref('stock.stock_location_stock'), 10)
+ self.env['stock.quant']._update_available_quantity(product_C, self.env.ref('stock.stock_location_stock'), 10)
+ sale_order = self.env['sale.order'].create({
+ 'partner_id': self.ref('base.res_partner_3'),
+ 'picking_policy': 'direct',
+ 'order_line': [
+ (0, 0, {'name': product_A.name, 'product_id': product_A.id, 'customer_lead': product_A.sale_delay, 'product_uom_qty': 5}),
+ (0, 0, {'name': product_B.name, 'product_id': product_B.id, 'customer_lead': product_B.sale_delay, 'product_uom_qty': 5}),
+ (0, 0, {'name': product_C.name, 'product_id': product_C.id, 'customer_lead': product_C.sale_delay, 'product_uom_qty': 5})
+ ],
+ })
+
+ # if Shipping Policy is set to `direct`(when SO is in draft state) then expected date should be
+ # current date + shortest lead time from all of it's order lines
+ expected_date = fields.Datetime.to_string(fields.Datetime.from_string(fields.Datetime.now()) + timedelta(days=5))
+ self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
+
+ # if Shipping Policy is set to `one`(when SO is in draft state) then expected date should be
+ # current date + longest lead time from all of it's order lines
+ sale_order.write({'picking_policy': 'one'})
+ expected_date = fields.Datetime.to_string(fields.Datetime.from_string(fields.Datetime.now()) + timedelta(days=15))
+ self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
+
+ sale_order.action_confirm()
+
+ # Setting confirmation date of SO to 5 days from today so that the expected/effective date could be checked
+ # against real confirmation date
+ confirm_date = fields.Datetime.from_string(fields.Datetime.now()) + timedelta(days=5)
+ sale_order.write({'confirmation_date': confirm_date})
+
+ # if Shipping Policy is set to `one`(when SO is confirmed) then expected date should be
+ # SO confirmation date + longest lead time from all of it's order lines
+ expected_date = fields.Datetime.to_string(confirm_date + timedelta(days=15))
+ self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
+
+ # if Shipping Policy is set to `direct`(when SO is confirmed) then expected date should be
+ # SO confirmation date + shortest lead time from all of it's order lines
+ sale_order.write({'picking_policy': 'direct'})
+ expected_date = fields.Datetime.to_string(confirm_date + timedelta(days=5))
+ self.assertEquals(expected_date, sale_order.expected_date, "Wrong expected date on sale order!")
+
+ # Check effective date, it should be date on which the first shipment successfully delivered to customer
+ picking = sale_order.picking_ids[0]
+ for ml in picking.move_line_ids:
+ ml.qty_done = ml.product_uom_qty
+ picking.action_done()
+ self.assertEquals(picking.state, 'done', "Picking not processed correctly!")
+ self.assertEquals(fields.Date.today(), sale_order.effective_date, "Wrong effective date on sale order!")
diff --git a/addons/sale_order_dates/views/sale_order_views.xml b/addons/sale_order_dates/views/sale_order_views.xml
index e2038b72a3b..352a962ba20 100644
--- a/addons/sale_order_dates/views/sale_order_views.xml
+++ b/addons/sale_order_dates/views/sale_order_views.xml
@@ -6,7 +6,7 @@
-
+
@@ -17,20 +17,20 @@
-
+
-
+
sale.order
-
+
diff --git a/addons/sale_stock/models/sale_order.py b/addons/sale_stock/models/sale_order.py
index 21f75b5cf0c..4a8a85ee606 100644
--- a/addons/sale_stock/models/sale_order.py
+++ b/addons/sale_stock/models/sale_order.py
@@ -24,7 +24,9 @@ class SaleOrder(models.Model):
('direct', 'Deliver each product when available'),
('one', 'Deliver all products at once')],
string='Shipping Policy', required=True, readonly=True, default='direct',
- states={'draft': [('readonly', False)], 'sent': [('readonly', False)]})
+ states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}
+ ,help="If you deliver all products at once, the delivery order will be scheduled based on the greatest "
+ "product lead time. Otherwise, it will be based on the shortest.")
warehouse_id = fields.Many2one(
'stock.warehouse', string='Warehouse',
required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]},
diff --git a/addons/sale_stock/views/res_config_settings_views.xml b/addons/sale_stock/views/res_config_settings_views.xml
index cf734b0dd66..532b26f4ecd 100644
--- a/addons/sale_stock/views/res_config_settings_views.xml
+++ b/addons/sale_stock/views/res_config_settings_views.xml
@@ -7,14 +7,14 @@
-
+
- Set a delivery date on orders
+ Manage delivery dates from sales orders
diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py
index a928857ab9e..47422bb080d 100644
--- a/addons/stock/models/stock_picking.py
+++ b/addons/stock/models/stock_picking.py
@@ -633,7 +633,7 @@ class Picking(models.Model):
# # Link existing moves or add moves when no one is related
for ops in pick.move_line_ids.filtered(lambda x: not x.move_id):
# Search move with this product
- moves = pick.move_lines.filtered(lambda x: x.product_id == ops.product_id)
+ moves = pick.move_lines.filtered(lambda x: x.product_id == ops.product_id)
if moves: #could search move that needs it the most (that has some quantities left)
ops.move_id = moves[0].id
else: