summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoryhu-odoo <yhu@odoo.com>2020-06-09 20:58:08 +0000
committeryhu-odoo <yhu@odoo.com>2020-06-29 13:27:17 +0000
commit0d3d26aca1cccd6b06bca46f0ec8fdaf2fda3d6e (patch)
tree880dae32d11f7e88fd5d9d65f32e0b1bdda2afb5
parent687a845cdbeab38c32c885539a5ee777ba7fd206 (diff)
[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
-rw-r--r--addons/purchase/controllers/portal.py30
-rw-r--r--addons/purchase/models/purchase.py61
-rw-r--r--addons/purchase/static/src/js/purchase_datetimepicker.js3
-rw-r--r--addons/purchase/tests/test_purchase.py38
-rw-r--r--addons/purchase/views/portal_templates.xml25
-rw-r--r--addons/purchase_stock/models/purchase.py36
-rw-r--r--addons/purchase_stock/tests/test_purchase_order.py36
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)
+ # update the date on PO line
+ 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 = _('<p> %s modified receipt dates for the following products:</p>', self.partner_id.name)
+ def _create_update_date_activity(self, updated_dates):
+ note = _('<p> %s modified receipt dates for the following products:</p>') % self.partner_id.name
for line, date in updated_dates:
- note += _(
- '<p> &nbsp; - %(product_name)s from %(date_start)s to %(date_end)s </p>',
- product_name=line.product_id.display_name,
- date_start=line.date_planned,
- date_end=date
- )
- return note
+ note += _('<p> &nbsp; - %s from %s to %s </p>') % (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 += _('<p> &nbsp; - %s from %s to %s </p>') % (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,
+ '<p> vendor1 modified receipt dates for the following products:</p><p> \xa0 - Product A from 2020-06-06 to %s </p>' % 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,
+ '<p> vendor1 modified receipt dates for the following products:</p><p> \xa0 - Product A from 2020-06-06 to %s </p><p> \xa0 - Product B from 2020-06-06 to %s </p>' % (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 @@
</div>
</div>
<h3 class="font-weight-normal">Pricing</h3>
-
- <form t-attf-action="/my/purchase/#{order.id}/update?access_token=#{order.access_token}" method="post">
- <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
-
<table class="table table-sm">
<thead class="bg-100">
<tr>
@@ -235,7 +231,7 @@
<th class="text-right d-none d-sm-table-cell">Unit Price</th>
<th class="text-right">Quantity</th>
<th class="text-right">Scheduled Date</th>
- <th class="text-right">Updated Date</th>
+ <th class="text-right" style="color:#3aadaa"><strong>Update Dates Here</strong></th>
<th class="text-right">Subtotal</th>
</tr>
</thead>
@@ -253,17 +249,20 @@
<span t-esc="ol.product_qty"/>
</td>
<td class="text-right">
- <span t-esc="ol.date_planned"/>
+ <span t-esc="ol.date_planned.date()"/>
</td>
<td class="text-right">
- <div class="container">
- <div class="form-group">
- <div class="input-group date">
- <input type="text" class="form-control datetimepicker-input o-purchase-datetimepicker" t-attf-id="datetimepicker_#{ol.id}" t-att-name="ol.id"
- data-toggle="datetimepicker" data-date-format="YYYY-MM-DD HH:mm:ss" t-attf-data-target="#datetimepicker_#{ol.id}"/>
+ <form t-attf-action="/my/purchase/#{order.id}/update?access_token=#{order.access_token}" method="post">
+ <input type="hidden" name="csrf_token" t-att-value="request.csrf_token()"/>
+ <div class="container">
+ <div class="form-group">
+ <div class="input-group date">
+ <input type="text" class="form-control datetimepicker-input o-purchase-datetimepicker" t-attf-id="datetimepicker_#{ol.id}" t-att-name="ol.id"
+ data-toggle="datetimepicker" data-date-format="YYYY-MM-DD" t-attf-data-target="#datetimepicker_#{ol.id}"/>
+ </div>
</div>
</div>
- </div>
+ </form>
</td>
<td class="text-right">
<span t-field="ol.price_subtotal" t-options='{"widget": "monetary", "display_currency": order.currency_id}'/>
@@ -304,8 +303,6 @@
</table>
</div>
</div>
- <input class="btn btn-primary" type="submit" value="Send Updated Dates"/>
- </form>
</div>
</xpath>
</template>
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 += _("<p>Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.</p>", validated_picking.name)
+ activity.note += _("<p>Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.</p>") % validated_picking[0].name
+ elif not self.picking_ids:
+ activity.note += _("<p>Corresponding receipt not found.</p>")
else:
- note += _("<p>Those dates have been updated accordingly on the receipt %s.</p>", 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)
-
- self.activity_schedule(
- 'mail.mail_activity_data_warning',
- summary="Date Updated",
- note=note,
- user_id=self.user_id.id or SUPERUSER_ID
- )
+ activity.note += _("<p>Those dates have been updated accordingly on the receipt %s.</p>") % self.picking_ids[0].name
+
+ 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('<p>')
+ note_lines.pop()
+ activity.note = '<p>'.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,
+ '<p> Wood Corner Partner modified receipt dates for the following products:</p><p> \xa0 - Large Desk from %s to %s </p><p>Those dates have been updated accordingly on the receipt %s.</p>' % (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,
+ '<p> Wood Corner Partner modified receipt dates for the following products:</p><p> \xa0 - Large Desk from %s to %s </p><p> \xa0 - Conference Chair from %s to %s </p><p>Those dates couldn’t be modified accordingly on the receipt %s which had already been validated.</p>' % (today.date(), tomorrow.date(), today.date(), tomorrow.date(), po.picking_ids.name)
+ )