[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:
@@ -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)
|
||||
|
||||
@@ -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> - %(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> - %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> - %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();
|
||||
});
|
||||
})
|
||||
|
||||
@@ -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())
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user