[PATCH] Upstream patch - 17112021

This commit is contained in:
Parthiv Patel committed 2021-11-17 09:36:28 +00:00
1 parent 74d2cc8fbe
commit c4ef5487e1
22 files changed
+176 -41

No files matched your search

@@ -300,7 +300,7 @@ class HolidaysAllocation(models.Model):
def _compute_can_approve(self):
for allocation in self:
try:
if allocation.state == 'confirm' and allocation.validation_type == 'both':
if allocation.state == 'confirm' and allocation.holiday_status_id.allocation_type == "fixed_allocation" and allocation.validation_type == 'both':
allocation._check_approval_update('validate1')
else:
allocation._check_approval_update('validate')
+1
View File
@@ -24,6 +24,7 @@ Allows to contact directly the employee in case of unjustified absence.
'views/hr_employee_views.xml',
'data/sms_data.xml',
'data/mail_data.xml',
'data/ir_cron.xml',
],
'demo': [],
'installable': True,
+9 -2
View File
@@ -9,6 +9,7 @@ import io
from flectra import api, fields, models, _
from flectra.exceptions import UserError
from flectra.tools import float_is_zero, pycompat
from flectra.tools.misc import get_lang
class AccountFrFec(models.TransientModel):
@@ -304,7 +305,7 @@ class AccountFrFec(models.TransientModel):
sql_query = '''
SELECT
replace(replace(aj.code, '|', '/'), '\t', '') AS JournalCode,
replace(replace(aj.name, '|', '/'), '\t', '') AS JournalLib,
replace(replace(COALESCE(aj__name.value, aj.name), '|', '/'), '\t', '') AS JournalLib,
replace(replace(am.name, '|', '/'), '\t', '') AS EcritureNum,
TO_CHAR(am.date, 'YYYYMMDD') AS EcritureDate,
aa.code AS CompteNum,
@@ -346,6 +347,11 @@ class AccountFrFec(models.TransientModel):
LEFT JOIN account_move am ON am.id=aml.move_id
LEFT JOIN res_partner rp ON rp.id=aml.partner_id
JOIN account_journal aj ON aj.id = am.journal_id
LEFT JOIN ir_translation aj__name ON aj__name.res_id = aj.id
AND aj__name.type = 'model'
AND aj__name.name = 'account.journal,name'
AND aj__name.lang = %s
AND aj__name.value != ''
JOIN account_account aa ON aa.id = aml.account_id
LEFT JOIN account_account_type aat ON aa.user_type_id = aat.id
LEFT JOIN res_currency rc ON rc.id = aml.currency_id
@@ -369,8 +375,9 @@ class AccountFrFec(models.TransientModel):
am.name,
aml.id
'''
lang = self.env.user.lang or get_lang(self.env).code
self._cr.execute(
sql_query, (self.date_from, self.date_to, company.id))
sql_query, (lang, self.date_from, self.date_to, company.id))
for row in self._cr.fetchall():
rows_to_write.append(list(row))
@@ -22,6 +22,7 @@ flectra.define('mass_mailing.mass_mailing_tour', function (require) {
edition: 'community',
}, {
trigger: '.o-kanban-button-new',
extra_trigger: '.oe_kanban_mass_mailing',
content: _t("Start by creating your first <b>Mailing</b>."),
position: 'bottom',
}, {
@@ -11,14 +11,14 @@ class Attendee(models.Model):
_name = 'calendar.attendee'
_inherit = 'calendar.attendee'
def _send_mail_to_attendees(self, mail_template, force_send=False, ignore_recurrence=False):
def _send_mail_to_attendees(self, template_xmlid, force_send=False, ignore_recurrence=False):
""" Override the super method
If not synced with Microsoft Outlook, let Flectra in charge of sending emails
Otherwise, Microsoft Outlook will send them
"""
with microsoft_calendar_token(self.env.user.sudo()) as token:
if not token:
super()._send_mail_to_attendees(mail_template, force_send, ignore_recurrence)
super()._send_mail_to_attendees(template_xmlid, force_send, ignore_recurrence)
def do_tentative(self):
# Synchronize event after state change
@@ -214,7 +214,9 @@ class MicrosoftSync(models.AbstractModel):
normal_events += [recurrent_event.flectra_id(self.env)]
else:
value = self.env['calendar.event']._microsoft_to_flectra_values(recurrent_event, (), default_values)
self.env['calendar.event'].browse(recurrent_event.flectra_id(self.env)).with_context(no_mail_to_attendees=True, mail_create_nolog=True).write(dict(value, need_sync_m=False))
event = self.env['calendar.event'].browse(recurrent_event.flectra_id(self.env)).exists()
if event:
event.with_context(no_mail_to_attendees=True, mail_create_nolog=True).write(dict(value, need_sync_m=False))
if value.get('start') and value.get('stop'):
values[(self.id, value.get('start'), value.get('stop'))] = dict(value, need_sync_m=False)
@@ -7,11 +7,13 @@ import pytz
from datetime import datetime, date
from flectra.tests.common import TransactionCase
from dateutil.relativedelta import relativedelta
from freezegun import freeze_time
class TestSyncMicrosoft2Flectra(TransactionCase):
@property
@freeze_time('2020-06-03')
def now(self):
return pytz.utc.localize(datetime.now()).isoformat()
@@ -29,10 +31,12 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.datetime_future = pytz.utc.localize(datetime.now() + relativedelta(days=1)).isoformat()
@freeze_time('2020-06-03')
def sync(self, events):
self.env['calendar.event']._sync_microsoft2flectra(events)
@freeze_time('2020-06-03')
def test_new_microsoft_recurrence(self):
recurrence = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
@@ -49,6 +53,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.assertEqual(events[2].start, datetime(2020, 5, 5, 14, 30))
self.assertEqual(events[2].stop, datetime(2020, 5, 5, 16, 00))
@freeze_time('2020-06-03')
def test_microsoft_recurrence_delete_one_event(self):
values = [
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
@@ -65,6 +70,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.assertEqual(recurrence.base_event_id, events[0])
self.assertEqual(events.mapped('name'), ['My recurrent event', 'My recurrent event'])
@freeze_time('2020-06-03')
def test_microsoft_recurrence_change_name_one_event(self):
values = [
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
@@ -82,6 +88,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.assertEqual(recurrence.base_event_id, events[0])
self.assertEqual(events.mapped('name'), ['My recurrent event', 'My recurrent event 2', 'My recurrent event'])
@freeze_time('2020-06-03')
def test_microsoft_recurrence_change_name_all_event(self):
values = [
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZKQ==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event 2', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
@@ -99,6 +106,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.assertEqual(recurrence.base_event_id, events[0])
self.assertEqual(events.mapped('name'), ['My recurrent event 2', 'My recurrent event 2', 'My recurrent event 2'])
@freeze_time('2020-06-03')
def test_microsoft_recurrence_change_date_one_event(self):
values = [
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADIaZPA=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADIaZPA==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
@@ -126,6 +134,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.assertEqual(special_event.start, datetime(2020, 5, 4, 14, 30))
self.assertEqual(special_event.stop, datetime(2020, 5, 4, 17, 00))
@freeze_time('2020-06-03')
def test_microsoft_recurrence_delete_first_event(self):
values = [
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADI/Bnw=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADI/Bnw==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:00:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-05', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
@@ -178,6 +187,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.assertEqual(events[2].start, datetime(2020, 5, 5, 14, 30))
self.assertEqual(events[2].stop, datetime(2020, 5, 5, 16, 30))
@freeze_time('2020-06-03')
def test_microsoft_recurrence_split_recurrence(self):
values = [
{'@odata.type': '#microsoft.graph.event', '@odata.etag': 'W/"ZS2uEVAVyU6BMZ3m6cHmtgAADI/Dig=="', 'createdDateTime': '2020-05-06T07:03:49.1444085Z', 'lastModifiedDateTime': self.datetime_future, 'changeKey': 'ZS2uEVAVyU6BMZ3m6cHmtgAADI/Dig==', 'categories': [], 'originalStartTimeZone': 'Romance Standard Time', 'originalEndTimeZone': 'Romance Standard Time', 'iCalUId': '040000008200E00074C5B7101A82E00800000000874F057E7423D601000000000000000010000000C6918C4B44D2D84586351FEC8B1B7F8C', 'reminderMinutesBeforeStart': 15, 'isReminderOn': True, 'hasAttachments': False, 'subject': 'My recurrent event', 'bodyPreview': '', 'importance': 'normal', 'sensitivity': 'normal', 'isAllDay': False, 'isCancelled': False, 'isOrganizer': True, 'IsRoomRequested': False, 'AutoRoomBookingStatus': 'None', 'responseRequested': True, 'seriesMasterId': None, 'showAs': 'busy', 'type': 'seriesMaster', 'webLink': 'https://outlook.live.com/owa/?itemid=AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA&exvsurl=1&path=/calendar/item', 'onlineMeetingUrl': None, 'isOnlineMeeting': False, 'onlineMeetingProvider': 'unknown', 'AllowNewTimeProposals': True, 'IsDraft': False, 'id': 'AQ8PojGtrADQATM3ZmYAZS0yY2MAMC00MDg1LTAwAi0wMAoARgAAA0By7X03vaNKv1GnWYTbFYAHAGUtrhFQFclOgTGd5unB5rYAAAIBDQAAAGUtrhFQFclOgTGd5unB5rYAAAALLLTEAAAA', 'responseStatus': {'response': 'organizer', 'time': '0001-01-01T00:00:00Z'}, 'body': {'contentType': 'html', 'content': ''}, 'start': {'dateTime': '2020-05-03T14:30:00.0000000', 'timeZone': 'UTC'}, 'end': {'dateTime': '2020-05-03T16:30:00.0000000', 'timeZone': 'UTC'}, 'location': {'displayName': '', 'locationType': 'default', 'uniqueIdType': 'unknown', 'address': {}, 'coordinates': {}}, 'locations': [], 'recurrence': {'pattern': {'type': 'daily', 'interval': 1, 'month': 0, 'dayOfMonth': 0, 'firstDayOfWeek': 'sunday', 'index': 'first'}, 'range': {'type': 'endDate', 'startDate': '2020-05-03', 'endDate': '2020-05-03', 'recurrenceTimeZone': 'Romance Standard Time', 'numberOfOccurrences': 0}}, 'attendees': [], 'organizer': {'emailAddress': {'name': 'outlook_7BA43549E5FD4413@outlook.com', 'address': 'outlook_7BA43549E5FD4413@outlook.com'}}},
@@ -211,6 +221,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.assertEqual(events_2[2].start, datetime(2020, 5, 6, 14, 30))
self.assertEqual(events_2[2].stop, datetime(2020, 5, 6, 17, 00))
@freeze_time('2020-06-03')
def test_microsoft_recurrence_delete(self):
recurrence_id = self.env['calendar.recurrence'].search([('microsoft_id', '=', self.recurrence_id)])
event_ids = self.env['calendar.event'].search([('recurrence_id', '=', recurrence_id.id)], order='start asc').ids
@@ -223,6 +234,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
self.assertFalse(recurrence, "It should remove recurrence")
self.assertFalse(events, "It should remove all events")
@freeze_time('2020-06-03')
def test_attendees_must_have_email(self):
"""
Synching with a partner without mail raises a ValidationError because Microsoft don't accept attendees without one.
@@ -240,6 +252,7 @@ class TestSyncMicrosoft2Flectra(TransactionCase):
with self.assertRaises(ValidationError):
event._sync_flectra2microsoft(MicrosoftCal)
@freeze_time('2020-06-03')
def test_cancel_occurence_of_recurrent_event(self):
""" The user is invited to a recurrent event. When synced, all events are present, there are three occurrences:
- 07/15/2021, 15:00-15:30
+3
View File
@@ -202,6 +202,9 @@ class StockMove(models.Model):
defaults['state'] = 'done'
defaults['product_uom_qty'] = 0.0
defaults['additional'] = True
elif production_id.state == 'draft':
defaults['group_id'] = production_id.procurement_group_id.id
defaults['reference'] = production_id.name
return defaults
def write(self, vals):
@@ -505,3 +505,32 @@ class TestMultistepManufacturingWarehouse(TestMrpCommon):
self.assertEqual(finished_product_SML.product_uom_qty, 2)
self.assertEqual(secondary_product_SML.product_uom.id, four_units_uom.id)
self.assertEqual(secondary_product_SML.product_uom_qty, 1)
def test_2_steps_and_additional_moves(self):
""" Suppose a 2-steps configuration. If a user adds a product to an existing draft MO and then
confirms it, the associated picking should includes this new product"""
self.warehouse.manufacture_steps = 'pbm'
mo_form = Form(self.env['mrp.production'])
mo_form.product_id = self.bom.product_id
mo_form.picking_type_id = self.warehouse.manu_type_id
mo = mo_form.save()
component_move = mo.move_raw_ids[0]
mo.with_context(default_raw_material_production_id=mo.id).move_raw_ids = [
[0, 0, {
'location_id': component_move.location_id.id,
'location_dest_id': component_move.location_dest_id.id,
'picking_type_id': component_move.picking_type_id.id,
'product_id': self.product_2.id,
'name': self.product_2.display_name,
'product_uom_qty': 1,
'product_uom': self.product_2.uom_id.id,
'warehouse_id': component_move.warehouse_id.id,
'raw_material_production_id': mo.id,
}]
]
mo.action_confirm()
self.assertEqual(self.bom.bom_line_ids.product_id + self.product_2, mo.picking_ids.move_lines.product_id)
+3 -1
View File
@@ -271,7 +271,9 @@
<field name="show_details_visible" invisible="1"/>
<field name="lot_ids" widget="many2many_tags"
groups="stock.group_production_lot" optional="hide"
attrs="{'invisible': ['|', '|', ('show_details_visible', '=', False), ('has_tracking', '!=', 'serial'), ('parent.state', '=', 'draft')], 'column_invisible': [('parent.show_lot_ids', '=', False)]}"
attrs="{'invisible': ['|', '|', ('show_details_visible', '=', False), ('has_tracking', '!=', 'serial'), ('parent.state', '=', 'draft')],
'readonly': ['|', '|', ('show_details_visible', '=', False), ('has_tracking', '!=', 'serial'), ('parent.state', '=', 'draft')],
'column_invisible': [('parent.show_lot_ids', '=', False)]}"
options="{'create': [('parent.use_create_components_lots', '!=', False)]}"
context="{'default_company_id': company_id, 'default_product_id': product_id}"
domain="[('product_id','=',product_id)]"
+3 -2
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra import api, fields, models, _
from flectra import api, fields, models, SUPERUSER_ID, _
from flectra.exceptions import UserError
@@ -70,6 +70,7 @@ class ResConfigSettings(models.TransientModel):
raise UserError(_("You can't desactivate the multi-location if you have more than once warehouse by company"))
previous_group = self.default_get(['group_stock_multi_locations', 'group_stock_production_lot', 'group_stock_tracking_lot'])
was_operations_showed = self.env['stock.picking.type'].with_user(SUPERUSER_ID)._default_show_operations()
res = super(ResConfigSettings, self).set_values()
if not self.user_has_groups('stock.group_stock_manager'):
@@ -89,7 +90,7 @@ class ResConfigSettings(models.TransientModel):
('delivery_steps', '=', 'ship_only')]
).mapped('int_type_id').write({'active': False})
if any(self[group] and not prev_value for group, prev_value in previous_group.items()):
if not was_operations_showed and self.env['stock.picking.type'].with_user(SUPERUSER_ID)._default_show_operations():
picking_types = self.env['stock.picking.type'].with_context(active_test=False).search([
('code', '!=', 'incoming'),
('show_operations', '=', False)
+12 -12
View File
@@ -452,7 +452,8 @@ class InventoryLine(models.Model):
set in an onchange of `product_id`.
Finally, this override checks we don't try to create a duplicated line.
"""
for values in vals_list:
products = self.env['product.product'].browse([vals.get('product_id') for vals in vals_list])
for product, values in zip(products, vals_list):
if 'theoretical_qty' not in values:
theoretical_qty = self.env['product.product'].get_theoretical_quantity(
values['product_id'],
@@ -464,7 +465,7 @@ class InventoryLine(models.Model):
)
values['theoretical_qty'] = theoretical_qty
if 'product_id' in values and 'product_uom_id' not in values:
values['product_uom_id'] = self.env['product.product'].browse(values['product_id']).uom_id.id
values['product_uom_id'] = product.product_tmpl_id.uom_id.id
res = super(InventoryLine, self).create(vals_list)
res._check_no_duplicate_line()
return res
@@ -475,17 +476,16 @@ class InventoryLine(models.Model):
return res
def _check_no_duplicate_line(self):
# Performance: restrict domain on 'not null' fields of stock_inventory_line.
domain = [('product_id', 'in', self.product_id.ids), ('location_id', 'in', self.location_id.ids)]
groupby_fields = ['product_id', 'location_id', 'partner_id', 'package_id', 'prod_lot_id', 'inventory_id']
lines_count = {}
for group in self.read_group(domain, ['product_id'], groupby_fields, lazy=False):
key = tuple([group[field] and group[field][0] for field in groupby_fields])
lines_count[key] = group['__count']
for line in self:
domain = [
('id', '!=', line.id),
('product_id', '=', line.product_id.id),
('location_id', '=', line.location_id.id),
('partner_id', '=', line.partner_id.id),
('package_id', '=', line.package_id.id),
('prod_lot_id', '=', line.prod_lot_id.id),
('inventory_id', '=', line.inventory_id.id)]
existings = self.search_count(domain)
if existings:
key = (line.product_id.id, line.location_id.id, line.partner_id.id, line.package_id.id, line.prod_lot_id.id, line.inventory_id.id)
if lines_count[key] > 1:
raise UserError(_("There is already one inventory adjustment line for this product,"
" you should rather modify this one instead of creating a new one."))
+2
View File
@@ -1089,6 +1089,8 @@ class Picking(models.Model):
moves_to_backorder.mapped('package_level_id').write({'picking_id':backorder_picking.id})
moves_to_backorder.mapped('move_line_ids').write({'picking_id': backorder_picking.id})
backorders |= backorder_picking
if backorders:
backorders.action_assign()
return backorders
def _log_activity_get_documents(self, orig_obj_changes, stream_field, stream, sorted_method=False, groupby_method=False):
+22 -1
View File
@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
# Part of Odoo, Flectra. See LICENSE file for full copyright and licensing details.
from flectra.exceptions import ValidationError
from flectra.exceptions import ValidationError, UserError
from flectra.tests.common import Form, SavepointCase
@@ -802,3 +802,24 @@ class TestInventory(SavepointCase):
self.assertEqual(len(line_ids_loc2), 1)
self.assertEqual(line_ids_loc1[0].theoretical_qty, 10)
self.assertEqual(line_ids_loc2[0].theoretical_qty, 0)
def test_inventory_line_duplicates(self):
""" Checks that creating duplicated inventory lines
raises a UserError.
"""
inventory = self.env['stock.inventory'].create({
'name': 'Existing Inventory',
'exhausted': True,
'location_ids': [(4, self.stock_location.id)],
'product_ids': [(4, self.product1.id)]
})
inventory.action_start()
dup_vals = [{
'inventory_id': inventory.id,
'product_id': self.product1.id,
'location_id': self.stock_location.id,
}]
with self.assertRaises(UserError), self.cr.savepoint():
self.env['stock.inventory.line'].create(dup_vals)
+2 -5
View File
@@ -122,7 +122,7 @@ class TestPickShip(TestStockCommon):
picking_client._action_done() # no new in order to create backorder
backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_client.id)])
self.assertEqual(backorder.state, 'waiting', 'Backorder should be waiting for reservation')
self.assertEqual(backorder.state, 'assigned', 'Backorder should be started')
def test_mto_moves_transfer(self):
"""
@@ -293,7 +293,7 @@ class TestPickShip(TestStockCommon):
# create a backorder
picking_pick._action_done()
picking_pick_backorder = self.env['stock.picking'].search([('backorder_id', '=', picking_pick.id)])
self.assertEqual(picking_pick_backorder.state, 'confirmed')
self.assertEqual(picking_pick_backorder.state, 'assigned')
self.assertEqual(picking_pick_backorder.move_lines.product_qty, 5.0)
self.assertEqual(picking_client.state, 'assigned')
@@ -773,11 +773,8 @@ class TestSinglePicking(TestStockCommon):
delivery_order.move_lines[0].move_line_ids[0].qty_done = 1
delivery_order._action_done()
self.assertNotEqual(delivery_order.date_done, False)
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 1.0)
backorder = self.env['stock.picking'].search([('backorder_id', '=', delivery_order.id)])
self.assertEqual(backorder.state, 'confirmed')
backorder.action_assign()
self.assertEqual(backorder.state, 'assigned')
self.assertEqual(self.env['stock.quant']._get_available_quantity(self.productA, pack_location), 0.0)
+1 -1
View File
@@ -32,7 +32,7 @@ class StockQuant(models.Model):
quant.value = 0
continue
if quant.product_id.cost_method == 'fifo':
quantity = quant.product_id.quantity_svl
quantity = quant.product_id.with_company(quant.company_id).quantity_svl
if float_is_zero(quantity, precision_rounding=quant.product_id.uom_id.rounding):
quant.value = 0.0
continue
@@ -2047,6 +2047,9 @@ var SnippetsMenu = Widget.extend({
return dragSnip;
},
start: function () {
const prom = new Promise(resolve => dragAndDropResolve = () => resolve());
self._mutex.exec(() => prom);
self.$el.find('.oe_snippet_thumbnail').addClass('o_we_already_dragging');
dropped = false;
@@ -2112,9 +2115,6 @@ var SnippetsMenu = Widget.extend({
self.draggableComponent.$scrollTarget.on('scroll.scrolling_element', function () {
self.$el.trigger('scroll');
});
const prom = new Promise(resolve => dragAndDropResolve = () => resolve());
self._mutex.exec(() => prom);
},
stop: async function (ev, ui) {
$toInsert.removeClass('oe_snippet_body');
@@ -682,6 +682,18 @@ registry.backgroundVideo = publicWidget.Widget.extend({
$(window).on('resize.' + this.iframeID, throttledUpdate);
const $modal = this.$target.closest('.modal');
if ($modal.length) {
$modal.on('show.bs.modal', () => {
const videoContainerEl = this.$target[0].querySelector('.o_bg_video_container');
videoContainerEl.classList.add('d-none');
});
$modal.on('shown.bs.modal', () => {
this._adjustIframe();
const videoContainerEl = this.$target[0].querySelector('.o_bg_video_container');
videoContainerEl.classList.remove('d-none');
});
}
return Promise.all(proms).then(() => this._appendBgVideo());
},
/**
@@ -10,6 +10,7 @@ const PopupWidget = publicWidget.Widget.extend({
events: {
'click .js_close_popup': '_onCloseClick',
'hide.bs.modal': '_onHideModal',
'show.bs.modal': '_onShowModal',
},
/**
@@ -92,6 +93,19 @@ const PopupWidget = publicWidget.Widget.extend({
const nbDays = this.$el.find('.modal').data('consentsDuration');
utils.set_cookie(this.$el.attr('id'), true, nbDays * 24 * 60 * 60);
this._popupAlreadyShown = true;
this.$target.find('.media_iframe_video iframe').each((i, iframe) => {
iframe.src = '';
});
},
/**
* @private
*/
_onShowModal() {
this.el.querySelectorAll('.media_iframe_video').forEach(media => {
const iframe = media.querySelector('iframe');
iframe.src = media.dataset.oeExpression || media.dataset.src; // TODO still oeExpression to remove someday
});
},
});
@@ -17,10 +17,21 @@ options.registry.SnippetPopup = options.Class.extend({
});
this.$target.on('shown.bs.modal.SnippetPopup', () => {
this.trigger_up('snippet_option_visibility_update', {show: true});
// TODO duplicated code from the popup public widget, this should
// be moved to a *video* public widget and be reviewed in master
this.$target[0].querySelectorAll('.media_iframe_video').forEach(media => {
const iframe = media.querySelector('iframe');
iframe.src = media.dataset.oeExpression || media.dataset.src; // TODO still oeExpression to remove someday
});
});
this.$target.on('hidden.bs.modal.SnippetPopup', () => {
this.trigger_up('snippet_option_visibility_update', {show: false});
this._removeIframeSrc();
});
// The video might be playing before entering edit mode (possibly with
// sound). Stop the video, as the user can't do it (no button on video
// in edit mode).
this._removeIframeSrc();
return this._super(...arguments);
},
/**
@@ -28,6 +39,9 @@ options.registry.SnippetPopup = options.Class.extend({
*/
destroy: function () {
this._super(...arguments);
// The video should not start before the modal opens, remove it from the
// DOM. It will be added back on modal open to start the video.
this._removeIframeSrc();
this.$target.off('.SnippetPopup');
},
/**
@@ -110,5 +124,16 @@ options.registry.SnippetPopup = options.Class.extend({
}
return this._super(...arguments);
},
/**
* Removes the iframe `src` attribute (a copy of the src is already on the
* parent `oe-expression` attribute).
*
* @private
*/
_removeIframeSrc() {
this.$target.find('.media_iframe_video iframe').each((i, iframe) => {
iframe.src = '';
});
},
});
});
@@ -10,14 +10,17 @@ const ShareWidget = publicWidget.Widget.extend({
* @override
*/
start: function () {
var urlRegex = /(\?(?:|.*&)(?:u|url|body)=)(.*?)(&|#|$)/;
var titleRegex = /(\?(?:|.*&)(?:title|text|subject)=)(.*?)(&|#|$)/;
var url = encodeURIComponent(window.location.href);
var title = encodeURIComponent($('title').text());
this.$('a').each(function () {
var $a = $(this);
$a.attr('href', function (i, href) {
return href.replace(urlRegex, function (match, a, b, c) {
const urlRegex = /(\?(?:|.*&)(?:u|url|body)=)(.*?)(&|#|$)/;
const titleRegex = /(\?(?:|.*&)(?:title|text|subject|description)=)(.*?)(&|#|$)/;
const mediaRegex = /(\?(?:|.*&)(?:media)=)(.*?)(&|#|$)/;
const url = encodeURIComponent(window.location.href);
const title = encodeURIComponent($('title').text());
const media = encodeURIComponent($('meta[property="og:image"]').attr('content'));
this.$('a').each((index, element) => {
const $a = $(element);
$a.attr('href', (i, href) => {
return href.replace(urlRegex, (match, a, b, c) => {
return a + url + c;
}).replace(titleRegex, function (match, a, b, c) {
if ($a.hasClass('s_share_whatsapp')) {
@@ -29,6 +32,8 @@ const ShareWidget = publicWidget.Widget.extend({
return a + title + url + c;
}
return a + title + c;
}).replace(mediaRegex, (match, a, b, c) => {
return a + media + c;
});
});
if ($a.attr('target') && $a.attr('target').match(/_blank/i) && !$a.closest('.o_editable').length) {
+2 -2
View File
@@ -10,13 +10,13 @@
<a href="https://twitter.com/intent/tweet?text={title}&amp;url={url}" t-attf-class="s_share_twitter #{_link_classes}" target="_blank">
<i t-attf-class="fa fa-twitter #{not _link_classes and 'rounded shadow-sm'}"/>
</a>
<a href="http://www.linkedin.com/sharing/share-offsite/?url={url}" t-attf-class="s_share_linkedin #{_link_classes}" target="_blank">
<a href="https://www.linkedin.com/sharing/share-offsite/?url={url}" t-attf-class="s_share_linkedin #{_link_classes}" target="_blank">
<i t-attf-class="fa fa-linkedin #{not _link_classes and 'rounded shadow-sm'}"/>
</a>
<a href="https://wa.me/?text={title}" t-attf-class="s_share_whatsapp #{_link_classes}" target="_blank">
<i t-attf-class="fa fa-whatsapp #{not _link_classes and 'rounded shadow-sm'}"/>
</a>
<a href="http://pinterest.com/pin/create/button/?url={url}&amp;description={title}" t-attf-class="s_share_pinterest #{_link_classes}" target="_blank">
<a href="https://pinterest.com/pin/create/button/?url={url}&amp;media={media}&amp;description={title}" t-attf-class="s_share_pinterest #{_link_classes}" target="_blank">
<i t-attf-class="fa fa-pinterest #{not _link_classes and 'rounded shadow-sm'}"/>
</a>
<a href="mailto:?body={url}&amp;subject={title}" t-attf-class="s_share_email #{_link_classes}">