summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-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)
+ )