[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
This commit is contained in:
yhu-odoo
2020-06-29 13:27:17 +00:00
parent 687a845cdb
commit 0d3d26aca1
7 changed files with 163 additions and 66 deletions
+18 -12
View File
@@ -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)
+39 -24
View File
@@ -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 = _('<p> %s modified receipt dates for the following products:</p>', self.partner_id.name)
# update the date on PO line
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
line._update_date_planned(date)
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; - %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
@@ -1,3 +1,6 @@
$(function () {
$('input.o-purchase-datetimepicker').datetimepicker();
$('input.o-purchase-datetimepicker').on("hide.datetimepicker", function () {
$(this).parents('form').submit();
});
})
+38
View File
@@ -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())
)
+11 -14
View File
@@ -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>
+19 -15
View File
@@ -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)
activity.note += _("<p>Those dates have been updated accordingly on the receipt %s.</p>") % 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('<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):
@@ -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)
)