From ccfc54113da51e2d137b994649417cec0d80c1fa Mon Sep 17 00:00:00 2001
From: Fabien Pinckaers
Date: Thu, 28 May 2020 13:18:18 +0000
Subject: [PATCH] [IMP] purchase: Improve reminder mail usability
Some change to imporve reminder mail usability:
1. Scheduled date on POL of the PO form now is always editable.
2. Improve tooltip for the reminder email
3. don't show number of days when reminder email not checked on
both PO and partner form.
4. only show "confirm receipt date" button in debug model
5. In the mail, format date by partner.lang
6. Add button to send preview reminder mail
7. On default, receipt_reminder_email on res.partner is False
8. merge expected_date with date_planned (done by FP)
Task 2265912
PR #52693
---
addons/purchase/data/mail_template_data.xml | 22 +++----
addons/purchase/data/purchase_data.xml | 2 +-
addons/purchase/data/purchase_demo.xml | 12 ++++
addons/purchase/models/purchase.py | 63 +++++++++++++------
addons/purchase/models/res_partner.py | 4 +-
addons/purchase/tests/test_purchase.py | 12 ++--
addons/purchase/views/purchase_views.xml | 41 +++++++-----
addons/purchase/views/res_partner_views.xml | 10 ++-
addons/purchase_stock/models/purchase.py | 2 +-
.../purchase_stock/report/purchase_report.py | 2 +-
.../purchase_stock/views/purchase_views.xml | 15 ++---
11 files changed, 117 insertions(+), 68 deletions(-)
diff --git a/addons/purchase/data/mail_template_data.xml b/addons/purchase/data/mail_template_data.xml
index 35bd59cf12c..f60c96c6d0b 100644
--- a/addons/purchase/data/mail_template_data.xml
+++ b/addons/purchase/data/mail_template_data.xml
@@ -50,13 +50,9 @@
% endif
amounting in ${format_amount(object.amount_total, object.currency_id)}
from ${object.company_id.name}.
- % if object.date_planned or object.expected_date:
- The receipt is planned for
- % if object.date_planned:
- ${object.date_planned.date()}.
- % elif object.expected_date:
- ${object.expected_date.date()}.
- % endif
+
+ % if object.date_planned:
+ The receipt is expected for ${format_date(object.date_planned)}.
Could you please acknowledge the receipt of this order?
% endif
@@ -82,13 +78,15 @@
(${object.partner_id.parent_id.name})
% endif
- Here is a reminder that the delivery of the purchase order ${object.name} is planned for
+ Here is a reminder that the delivery of the purchase order ${object.name}
+ % if object.partner_ref:
+ (${object.partner_ref})
+ % endif
+ is expected for
% if object.date_planned:
- ${object.date_planned.date()}.
- % else:
- ${object.expected_date.date()}.
+ ${format_date(object.date_planned)}.
% endif
- Could you please confirm it will be on time?
+ Could you please confirm it will be delivered on time?
diff --git a/addons/purchase/data/purchase_data.xml b/addons/purchase/data/purchase_data.xml
index fa1af422096..93d6e048a87 100644
--- a/addons/purchase/data/purchase_data.xml
+++ b/addons/purchase/data/purchase_data.xml
@@ -42,7 +42,7 @@
receipt_reminder_email
-
+ reminder_date_before_receipt
diff --git a/addons/purchase/data/purchase_demo.xml b/addons/purchase/data/purchase_demo.xml
index 2283fd28778..8f4f80fe21d 100644
--- a/addons/purchase/data/purchase_demo.xml
+++ b/addons/purchase/data/purchase_demo.xml
@@ -6,6 +6,18 @@
+
+ True
+
+
+
+ True
+
+
+
+ True
+
+
diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py
index ff000500ac7..674e09b0daa 100644
--- a/addons/purchase/models/purchase.py
+++ b/addons/purchase/models/purchase.py
@@ -106,10 +106,8 @@ class PurchaseOrder(models.Model):
('invoiced', 'Fully Billed'),
], string='Billing Status', compute='_get_invoiced', store=True, readonly=True, copy=False, default='no')
date_planned = fields.Datetime(
- string='Receipt Date', index=True, copy=False,
+ string='Receipt Date', index=True, copy=False, compute='_compute_date_planned', store=True, readonly=False,
help="Delivery date promised by vendor. This date is used to determine expected arrival of products.")
- expected_date = fields.Datetime("Expected Date", compute='_compute_expected_date', store=True,
- help="Delivery date expected by vendor, computed from the minimum lead time of the order lines.")
date_calendar_start = fields.Datetime(compute='_compute_date_calendar_start', readonly=True, store=True)
amount_untaxed = fields.Monetary(string='Untaxed Amount', store=True, readonly=True, compute='_amount_all', tracking=True)
@@ -130,10 +128,8 @@ class PurchaseOrder(models.Model):
mail_reminder_confirmed = fields.Boolean("Reminder Confirmed", default=False, readonly=True, copy=False, help="True if the reminder email is confirmed by the vendor.")
mail_reception_confirmed = fields.Boolean("Reception Confirmed", default=False, readonly=True, copy=False, help="True if PO reception is confirmed by the vendor.")
- receipt_reminder_email = fields.Boolean('Receipt Reminder Email', related='partner_id.receipt_reminder_email', readonly=False,
- help='Automatically send a reminder email to your vendors before receipt')
- reminder_date_before_receipt = fields.Integer('Days Before Receipt', related='partner_id.reminder_date_before_receipt', readonly=False,
- help="Number of days to send reminder email before the promised receipt date")
+ receipt_reminder_email = fields.Boolean('Receipt Reminder Email', related='partner_id.receipt_reminder_email', readonly=False)
+ reminder_date_before_receipt = fields.Integer('Days Before Receipt', related='partner_id.reminder_date_before_receipt', readonly=False)
@api.constrains('company_id', 'order_line')
def _check_order_line_company_id(self):
@@ -168,11 +164,14 @@ class PurchaseOrder(models.Model):
order.currency_rate = self.env['res.currency']._get_conversion_rate(order.company_id.currency_id, order.currency_id, order.company_id, order.date_order)
@api.depends('order_line.date_planned')
- def _compute_expected_date(self):
- """ expected_date = the earliest date_planned across all order lines. """
+ def _compute_date_planned(self):
+ """ date_planned = the earliest date_planned across all order lines. """
for order in self:
- dates_list = order.order_line.filtered(lambda x: not x.display_type).mapped('date_planned')
- order.expected_date = fields.Datetime.to_string(min(dates_list)) if dates_list else False
+ dates_list = order.order_line.filtered(lambda x: not x.display_type and x.date_planned).mapped('date_planned')
+ if dates_list:
+ order.date_planned = fields.Datetime.to_string(min(dates_list))
+ else:
+ order.date_planned = False
@api.depends('name', 'partner_ref')
def name_get(self):
@@ -219,6 +218,17 @@ class PurchaseOrder(models.Model):
line.date_planned = line._get_date_planned(seller)
return new_po
+ def onchange(self, values, field_name, field_onchange):
+ """Override onchange to NOT to update all date_planned on PO lines when
+ date_planned on PO is updated by the change of date_planned on PO lines.
+ """
+ result = super(PurchaseOrder, self).onchange(values, field_name, field_onchange)
+ if field_name == 'order_line' and 'value' in result:
+ for line in result['value'].get('order_line', []):
+ if line[0] < 2 and 'date_planned' in line[2]:
+ del line[2]['date_planned']
+ return result
+
def _track_subtype(self, init_values):
self.ensure_one()
if 'state' in init_values and self.state == 'purchase':
@@ -628,23 +638,38 @@ class PurchaseOrder(models.Model):
return result
- @api.model
- def _send_reminder_mail(self):
- if not self.user_has_groups('purchase.group_send_reminder') and not self.receipt_reminder_email:
+ def _send_reminder_mail(self, send_single=False):
+ if not self.user_has_groups('purchase.group_send_reminder'):
return
template = self.env.ref('purchase.email_template_edi_purchase_reminder', raise_if_not_found=False)
if template:
- for order in self._get_orders_to_remind():
- date = order.date_planned or order.expected_date
- if date and (date - relativedelta(days=order.reminder_date_before_receipt)).date() == datetime.today().date():
+ orders = self if send_single else self._get_orders_to_remind()
+ for order in orders:
+ date = order.date_planned
+ if date and (send_single or (date - relativedelta(days=order.reminder_date_before_receipt)).date() == datetime.today().date()):
order.with_context(is_reminder=True).message_post_with_template(template.id, email_layout_xmlid="mail.mail_notification_paynow", composition_mode='comment')
+ def send_reminder_preview(self):
+ if not self.user_has_groups('purchase.group_send_reminder') and not self.receipt_reminder_email:
+ return
+
+ template = self.env.ref('purchase.email_template_edi_purchase_reminder', raise_if_not_found=False)
+ if template and self.env.user.email:
+ for order in self:
+ template.with_context(is_reminder=True).send_mail(
+ order.id,
+ force_send=True,
+ raise_exception=False,
+ email_values={'email_to': self.env.user.email, 'recipient_ids': []},
+ notif_layout="mail.mail_notification_paynow")
+
@api.model
def _get_orders_to_remind(self):
"""When auto sending a reminder mail, only send for unconfirmed purchase
order and not all products are service."""
return self.search([
+ ('receipt_reminder_email', '=', True),
('state', 'in', ['purchase', 'done']),
('mail_reminder_confirmed', '=', False)
]).filtered(lambda p: p.mapped('order_line.product_id.product_tmpl_id.type') != ['service'])
@@ -655,7 +680,7 @@ class PurchaseOrder(models.Model):
if confirm_type in ['reminder', 'reception']:
param = url_encode({
'confirm': confirm_type,
- 'confirmed_date': (self.date_planned or self.expected_date).date(),
+ 'confirmed_date': self.date_planned.date(),
})
return self.get_portal_url(query_string='&%s' % param)
return self.get_portal_url()
@@ -670,7 +695,7 @@ class PurchaseOrder(models.Model):
for order in self:
if order.state in ['purchase', 'done'] and not order.mail_reminder_confirmed:
order.mail_reminder_confirmed = True
- date = confirmed_date or (self.date_planned or self.expected_date).date()
+ date = confirmed_date or self.date_planned.date()
order.message_post(body="%s confirmed the receipt will take place on %s." % (order.partner_id.name, date))
def _confirm_reception_mail(self):
diff --git a/addons/purchase/models/res_partner.py b/addons/purchase/models/res_partner.py
index 69c7bc1be48..c0d75ccbc01 100644
--- a/addons/purchase/models/res_partner.py
+++ b/addons/purchase/models/res_partner.py
@@ -60,7 +60,7 @@ class res_partner(models.Model):
purchase_warn = fields.Selection(WARNING_MESSAGE, 'Purchase Order', help=WARNING_HELP, default="no-message")
purchase_warn_msg = fields.Text('Message for Purchase Order')
- receipt_reminder_email = fields.Boolean('Receipt Reminder Email', default=True, company_dependent=True,
- help='Automatically send a reminder email to your vendors before receipt')
+ receipt_reminder_email = fields.Boolean('Receipt Reminder', default=False, company_dependent=True,
+ help="Automatically send a confirmation email to the vendor X days before the expected receipt date, asking him to confirm the exact date.")
reminder_date_before_receipt = fields.Integer('Days Before Receipt', default=1, company_dependent=True,
help="Number of days to send reminder email before the promised receipt date")
diff --git a/addons/purchase/tests/test_purchase.py b/addons/purchase/tests/test_purchase.py
index d25436d5562..dae88ecf0d9 100644
--- a/addons/purchase/tests/test_purchase.py
+++ b/addons/purchase/tests/test_purchase.py
@@ -22,9 +22,9 @@ class TestPurchase(SavepointCase):
cls.vendor = cls.env['res.partner'].create({'name': 'vendor1'})
cls.uom_unit = cls.env.ref('uom.product_uom_unit')
- def test_expected_date(self):
- """Set a date planned on 2 PO lines. Check that the PO expected date is the earliest PO line date
- planned. Change one of the dates so it is even earlier and check that the expected date is set to
+ def test_date_planned(self):
+ """Set a date planned on 2 PO lines. Check that the PO date_planned is the earliest PO line date
+ planned. Change one of the dates so it is even earlier and check that the date_planned is set to
this earlier date.
"""
po = Form(self.env['purchase.order'])
@@ -42,19 +42,19 @@ class TestPurchase(SavepointCase):
# Check that the same date is planned on both PO lines.
self.assertNotEqual(po.order_line[0].date_planned, False)
self.assertAlmostEqual(po.order_line[0].date_planned, po.order_line[1].date_planned, delta=timedelta(seconds=10))
- self.assertAlmostEqual(po.order_line[0].date_planned, po.expected_date, delta=timedelta(seconds=10))
+ self.assertAlmostEqual(po.order_line[0].date_planned, po.date_planned, delta=timedelta(seconds=10))
orig_date_planned = po.order_line[0].date_planned
# Set an earlier date planned on a PO line and check that the PO expected date matches it.
new_date_planned = orig_date_planned - timedelta(hours=1)
po.order_line[0].date_planned = new_date_planned
- self.assertAlmostEqual(po.order_line[0].date_planned, po.expected_date, delta=timedelta(seconds=10))
+ self.assertAlmostEqual(po.order_line[0].date_planned, po.date_planned, delta=timedelta(seconds=10))
# Set an even earlier date planned on the other PO line and check that the PO expected date matches it.
new_date_planned = orig_date_planned - timedelta(hours=72)
po.order_line[1].date_planned = new_date_planned
- self.assertAlmostEqual(po.order_line[1].date_planned, po.expected_date, delta=timedelta(seconds=10))
+ self.assertAlmostEqual(po.order_line[1].date_planned, po.date_planned, delta=timedelta(seconds=10))
def test_purchase_order_sequence(self):
PurchaseOrder = self.env['purchase.order'].with_context(tracking_disable=True)
diff --git a/addons/purchase/views/purchase_views.xml b/addons/purchase/views/purchase_views.xml
index 93982948436..81b7cb68f97 100644
--- a/addons/purchase/views/purchase_views.xml
+++ b/addons/purchase/views/purchase_views.xml
@@ -143,7 +143,7 @@
-
+
@@ -184,18 +184,18 @@
(confirmed by vendor)
-
-
-
- Per line, first on
-
- (confirmed by vendor)
-
diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py
index cb8b060eaea..67fa411ed57 100644
--- a/addons/purchase_stock/models/purchase.py
+++ b/addons/purchase_stock/models/purchase.py
@@ -340,7 +340,7 @@ class PurchaseOrderLine(models.Model):
def write(self, values):
for line in self.filtered(lambda l: not l.display_type):
# PO date_planned overrides any PO line date_planned values
- if not line.order_id.date_planned and values.get('date_planned') and line.propagate_date:
+ if values.get('date_planned') and line.propagate_date:
new_date = fields.Datetime.to_datetime(values['date_planned'])
self._update_move_expected_date(new_date)
result = super(PurchaseOrderLine, self).write(values)
diff --git a/addons/purchase_stock/report/purchase_report.py b/addons/purchase_stock/report/purchase_report.py
index 0215ec65353..8e98ac21d72 100644
--- a/addons/purchase_stock/report/purchase_report.py
+++ b/addons/purchase_stock/report/purchase_report.py
@@ -51,7 +51,7 @@ class PurchaseReport(models.Model):
if avg_receipt_delay:
query = """ SELECT AVG(receipt_delay.po_receipt_delay)::decimal(16,2) AS avg_receipt_delay
FROM (
- SELECT extract(epoch from age(po.effective_date,COALESCE(po.date_planned, po.expected_date)))/(24*60*60) AS po_receipt_delay
+ SELECT extract(epoch from age(po.effective_date, po.date_planned))/(24*60*60) AS po_receipt_delay
FROM purchase_order po
WHERE po.id IN (
SELECT "purchase_report"."order_id" FROM %s WHERE %s)
diff --git a/addons/purchase_stock/views/purchase_views.xml b/addons/purchase_stock/views/purchase_views.xml
index dbfbfcd5b58..f3efb7389e3 100644
--- a/addons/purchase_stock/views/purchase_views.xml
+++ b/addons/purchase_stock/views/purchase_views.xml
@@ -9,8 +9,8 @@
-
-
+
+ {'invisible': ['|', '|', ('state', 'not in', ('purchase', 'done')), ('mail_reminder_confirmed', '=', True), ('effective_date', '!=', False)]}
-
-