From 0d3d26aca1cccd6b06bca46f0ec8fdaf2fda3d6e Mon Sep 17 00:00:00 2001 From: yhu-odoo Date: Tue, 9 Jun 2020 20:58:08 +0000 Subject: [PATCH] [IMP] purchase(_stock): portal update scheduled date 1. Show only date not datetime on update portal. When update the scheduled date, set it to be the last minute of that date. 2. Send updated date immediately when user pick a date. 3. If an activity for update the date already exist, update the note instead of creating a new one. Task #2265912 PR 52809 --- addons/purchase/controllers/portal.py | 30 +++++---- addons/purchase/models/purchase.py | 63 ++++++++++++------- .../static/src/js/purchase_datetimepicker.js | 3 + addons/purchase/tests/test_purchase.py | 38 +++++++++++ addons/purchase/views/portal_templates.xml | 25 ++++---- addons/purchase_stock/models/purchase.py | 34 +++++----- .../tests/test_purchase_order.py | 36 ++++++++++- 7 files changed, 163 insertions(+), 66 deletions(-) diff --git a/addons/purchase/controllers/portal.py b/addons/purchase/controllers/portal.py index 92136935681..600f7b8f751 100644 --- a/addons/purchase/controllers/portal.py +++ b/addons/purchase/controllers/portal.py @@ -3,10 +3,11 @@ import base64 from collections import OrderedDict +from datetime import datetime from odoo import http from odoo.exceptions import AccessError, MissingError -from odoo.http import request +from odoo.http import request, Response from odoo.tools import image_process from odoo.tools.translate import _ from odoo.addons.portal.controllers.portal import pager as portal_pager, CustomerPortal @@ -134,17 +135,22 @@ class CustomerPortal(CustomerPortal): return request.redirect('/my') updated_dates = [] - try: - for id_str, date in kw.items(): - line = order_sudo.order_line.filtered(lambda l: l.id == int(id_str)) - if not line: - return request.redirect(order_sudo.get_portal_url()) - updated_dates.append((line, date)) - except ValueError: - return request.redirect(order_sudo.get_portal_url()) + for id_str, date_str in kw.items(): + try: + line_id = int(id_str) + except ValueError: + return request.redirect(order_sudo.get_portal_url()) + line = order_sudo.order_line.filtered(lambda l: l.id == line_id) + if not line: + return request.redirect(order_sudo.get_portal_url()) + + try: + updated_date = line._convert_to_last_minute_of_day(datetime.strptime(date_str, '%Y-%m-%d')) + except ValueError: + continue + + updated_dates.append((line, updated_date)) if updated_dates: order_sudo._update_date_planned_for_lines(updated_dates) - - values = self._purchase_order_get_page_view_values(order_sudo, access_token, **kw) - return request.render("purchase.portal_my_purchase_order", values) + return Response(status=204) diff --git a/addons/purchase/models/purchase.py b/addons/purchase/models/purchase.py index d2a43bea5bd..dc233ce9498 100644 --- a/addons/purchase/models/purchase.py +++ b/addons/purchase/models/purchase.py @@ -711,30 +711,39 @@ class PurchaseOrder(models.Model): order.message_post(body="The order receipt has been acknowledged by %s." % order.partner_id.name) def _update_date_planned_for_lines(self, updated_dates): - if updated_dates: - note = self._compose_note(updated_dates) - self.activity_schedule( - 'mail.mail_activity_data_warning', - summary="Date Updated", - note=note, - user_id=self.user_id.id - ) + # create or update the activity + activity = self.env['mail.activity'].search([ + ('summary', '=', _('Date Updated')), + ('res_model_id', '=', 'purchase.order'), + ('res_id', '=', self.id), + ('user_id', '=', self.user_id.id)], limit=1) + if activity: + self._update_update_date_activity(updated_dates, activity) + else: + self._create_update_date_activity(updated_dates) - for line, date in updated_dates: - line._update_date_planned(date) - - def _compose_note(self, updated_dates): - """Helper method for creating log note when user update scheduled date - on portal website.""" - note = _('

%s modified receipt dates for the following products:

', self.partner_id.name) + # update the date on PO line for line, date in updated_dates: - note += _( - '

  - %(product_name)s from %(date_start)s to %(date_end)s

', - product_name=line.product_id.display_name, - date_start=line.date_planned, - date_end=date - ) - return note + line._update_date_planned(date) + + def _create_update_date_activity(self, updated_dates): + note = _('

%s modified receipt dates for the following products:

') % self.partner_id.name + for line, date in updated_dates: + note += _('

  - %s from %s to %s

') % (line.product_id.display_name, line.date_planned.date(), date.date()) + activity = self.activity_schedule( + 'mail.mail_activity_data_warning', + summary=_("Date Updated"), + user_id=self.user_id.id + ) + # add the note after we post the activity because the note can be soon + # changed when updating the date of the next PO line. So instead of + # sending a mail with incomplete note, we send one with no note. + activity.note = note + return activity + + def _update_update_date_activity(self, updated_dates, activity): + for line, date in updated_dates: + activity.note += _('

  - %s from %s to %s

') % (line.product_id.display_name, line.date_planned.date(), date.date()) class PurchaseOrderLine(models.Model): @@ -936,7 +945,7 @@ class PurchaseOrderLine(models.Model): date_planned = date_order + relativedelta(days=seller.delay if seller else 0) else: date_planned = datetime.today() + relativedelta(days=seller.delay if seller else 0) - return timezone(self.order_id.user_id.tz or 'UTC').localize(datetime.combine(date_planned.date(), time.max)).astimezone(UTC).replace(tzinfo=None, microsecond=0, second=0) + return self._convert_to_last_minute_of_day(date_planned) @api.depends('product_id', 'date_order') def _compute_analytic_id_and_tag_ids(self): @@ -957,7 +966,7 @@ class PurchaseOrderLine(models.Model): return # Reset date, price and quantity since _onchange_quantity will provide default values - self.date_planned = self.order_id.date_planned or timezone(self.order_id.user_id.tz or 'UTC').localize(datetime.combine(datetime.today(), time.max)).astimezone(UTC).replace(tzinfo=None, microsecond=0, second=0) + self.date_planned = self.order_id.date_planned or self._convert_to_last_minute_of_day(datetime.today()) self.price_unit = self.product_qty = 0.0 self._product_id_change() @@ -1147,5 +1156,11 @@ class PurchaseOrderLine(models.Model): 'order_id': po.id, } + def _convert_to_last_minute_of_day(self, date): + """Return a datetime which is the last minute of the input date(time) + according to order user's time zone, convert to UTC time. + """ + return timezone(self.order_id.user_id.tz or 'UTC').localize(datetime.combine(date, time.max)).astimezone(UTC).replace(tzinfo=None, microsecond=0, second=0) + def _update_date_planned(self, updated_date): self.date_planned = updated_date diff --git a/addons/purchase/static/src/js/purchase_datetimepicker.js b/addons/purchase/static/src/js/purchase_datetimepicker.js index c2aaad196c4..de1be92f6df 100644 --- a/addons/purchase/static/src/js/purchase_datetimepicker.js +++ b/addons/purchase/static/src/js/purchase_datetimepicker.js @@ -1,3 +1,6 @@ $(function () { $('input.o-purchase-datetimepicker').datetimepicker(); + $('input.o-purchase-datetimepicker').on("hide.datetimepicker", function () { + $(this).parents('form').submit(); + }); }) diff --git a/addons/purchase/tests/test_purchase.py b/addons/purchase/tests/test_purchase.py index 19734a815fa..b02154520b0 100644 --- a/addons/purchase/tests/test_purchase.py +++ b/addons/purchase/tests/test_purchase.py @@ -144,3 +144,41 @@ class TestPurchase(SavepointCase): messages_send = po.message_ids - old_messages # check no reminder send self.assertFalse(messages_send) + + def test_update_date_planned(self): + po = Form(self.env['purchase.order']) + po.partner_id = self.vendor + with po.order_line.new() as po_line: + po_line.product_id = self.product_consu + po_line.product_qty = 1 + po_line.price_unit = 100 + po_line.date_planned = '2020-06-06 00:00:00' + with po.order_line.new() as po_line: + po_line.product_id = self.product_consu2 + po_line.product_qty = 10 + po_line.price_unit = 200 + po_line.date_planned = '2020-06-06 00:00:00' + po = po.save() + po.button_confirm() + + # update first line + po._update_date_planned_for_lines([(po.order_line[0], fields.Datetime.today())]) + self.assertEqual(po.order_line[0].date_planned, fields.Datetime.today()) + activity = self.env['mail.activity'].search([ + ('summary', '=', 'Date Updated'), + ('res_model_id', '=', 'purchase.order'), + ('res_id', '=', po.id), + ]) + self.assertTrue(activity) + self.assertEqual( + activity.note, + '

vendor1 modified receipt dates for the following products:

\xa0 - Product A from 2020-06-06 to %s

' % fields.Date.today() + ) + + # update second line + po._update_date_planned_for_lines([(po.order_line[1], fields.Datetime.today())]) + self.assertEqual(po.order_line[1].date_planned, fields.Datetime.today()) + self.assertEqual( + activity.note, + '

vendor1 modified receipt dates for the following products:

\xa0 - Product A from 2020-06-06 to %s

\xa0 - Product B from 2020-06-06 to %s

' % (fields.Date.today(), fields.Date.today()) + ) diff --git a/addons/purchase/views/portal_templates.xml b/addons/purchase/views/portal_templates.xml index 0618c182943..5ec71184b6d 100644 --- a/addons/purchase/views/portal_templates.xml +++ b/addons/purchase/views/portal_templates.xml @@ -224,10 +224,6 @@

Pricing

- -
- - @@ -235,7 +231,7 @@ - + @@ -253,17 +249,20 @@
Unit Price Quantity Scheduled DateUpdated DateUpdate Dates Here Subtotal
- + -
-
-
- + + +
+
+
+ +
-
+
@@ -304,8 +303,6 @@
- - diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index 56f3a892c10..66c7c136697 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - from odoo import api, fields, models, SUPERUSER_ID, _ from odoo.tools.float_utils import float_compare from datetime import datetime @@ -253,24 +252,29 @@ class PurchaseOrder(models.Model): subtype_id=self.env.ref('mail.mt_note').id) return True - def _update_date_planned_for_lines(self, updated_dates): - note = self._compose_note(updated_dates) - + def _add_picking_info(self, activity): + """Helper method to add picking info to the Date Updated activity when + vender updates date_planned of the po lines. + """ validated_picking = self.picking_ids.filtered(lambda p: p.state == 'done') if validated_picking: - note += _("

Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.

", validated_picking.name) + activity.note += _("

Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.

") % validated_picking[0].name + elif not self.picking_ids: + activity.note += _("

Corresponding receipt not found.

") else: - note += _("

Those dates have been updated accordingly on the receipt %s.

", self.picking_ids[0].name) - for line, date in updated_dates: - date = datetime.strptime(date, '%Y-%m-%d %H:%M:%S') - line._update_date_planned(date) + activity.note += _("

Those dates have been updated accordingly on the receipt %s.

") % self.picking_ids[0].name - self.activity_schedule( - 'mail.mail_activity_data_warning', - summary="Date Updated", - note=note, - user_id=self.user_id.id or SUPERUSER_ID - ) + def _create_update_date_activity(self, updated_dates): + activity = super()._create_update_date_activity(updated_dates) + self._add_picking_info(activity) + + def _update_update_date_activity(self, updated_dates, activity): + # remove old picking info to update it + note_lines = activity.note.split('

') + note_lines.pop() + activity.note = '

'.join(note_lines) + super()._update_update_date_activity(updated_dates, activity) + self._add_picking_info(activity) @api.model def _get_orders_to_remind(self): diff --git a/addons/purchase_stock/tests/test_purchase_order.py b/addons/purchase_stock/tests/test_purchase_order.py index 4f9060ba1fd..333a53e8bf6 100644 --- a/addons/purchase_stock/tests/test_purchase_order.py +++ b/addons/purchase_stock/tests/test_purchase_order.py @@ -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.account.tests.common import AccountTestCommon @@ -224,3 +224,37 @@ class TestPurchaseOrder(AccountTestCommon): self.po.button_confirm() self.assertFalse(self.po.order_line.mapped('move_dest_ids')) self.assertEqual(self.po.picking_ids.move_lines[0].propagate_date, warehouse.buy_pull_id.propagate_date) + + def test_update_date_planned(self): + po = self.PurchaseOrder.create(self.po_vals) + po.button_confirm() + + today = datetime.today().replace(microsecond=0) + tomorrow = datetime.today().replace(microsecond=0) + timedelta(days=1) + # update first line + po._update_date_planned_for_lines([(po.order_line[0], tomorrow)]) + self.assertEqual(po.order_line[0].date_planned, tomorrow) + activity = self.env['mail.activity'].search([ + ('summary', '=', 'Date Updated'), + ('res_model_id', '=', 'purchase.order'), + ('res_id', '=', po.id), + ]) + self.assertTrue(activity) + self.assertEqual( + activity.note, + '

Wood Corner Partner modified receipt dates for the following products:

\xa0 - Large Desk from %s to %s

Those dates have been updated accordingly on the receipt %s.

' % (today.date(), tomorrow.date(), po.picking_ids.name) + ) + + # receive products + wiz_act = po.picking_ids.button_validate() + wiz = Form(self.env[wiz_act['res_model']].with_context(wiz_act['context'])).save() + wiz.process() + + # update second line + old_date = po.order_line[1].date_planned + po._update_date_planned_for_lines([(po.order_line[1], tomorrow)]) + self.assertEqual(po.order_line[1].date_planned, old_date) + self.assertEqual( + activity.note, + '

Wood Corner Partner modified receipt dates for the following products:

\xa0 - Large Desk from %s to %s

\xa0 - Conference Chair from %s to %s

Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.

' % (today.date(), tomorrow.date(), today.date(), tomorrow.date(), po.picking_ids.name) + )