diff options
| author | Christophe Simonis <chs@odoo.com> | 2018-01-19 11:30:35 +0100 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2018-01-19 11:30:35 +0100 |
| commit | f99c4f2b48b29f19a0d2ee1cd999008c92ab2a89 (patch) | |
| tree | e28f4ba34bc0bad8576150dfab61e236d9d8da14 | |
| parent | 56052dee60967e22ec9648da9641e27c629a1c54 (diff) | |
| parent | 0c54862cbf120c7ef0fa4a4b10ee17c1d3c510ae (diff) | |
[MERGE] forward port branch saas-15 up to 0c54862cbf1
| -rw-r--r-- | addons/account/models/account_invoice.py | 2 | ||||
| -rw-r--r-- | addons/account/views/res_config_view.xml | 4 | ||||
| -rw-r--r-- | addons/calendar/views/calendar_views.xml | 6 | ||||
| -rw-r--r-- | addons/hr_payroll/models/hr_payslip.py | 1 | ||||
| -rw-r--r-- | addons/mail/models/mail_message.py | 30 | ||||
| -rw-r--r-- | addons/mail/static/src/js/chatter.js | 2 | ||||
| -rw-r--r-- | addons/mail/static/src/js/utils.js | 18 | ||||
| -rw-r--r-- | addons/mail/tests/test_mail_features.py | 1 | ||||
| -rw-r--r-- | addons/mail/tests/test_mail_message.py | 67 | ||||
| -rw-r--r-- | addons/maintenance/models/maintenance.py | 9 | ||||
| -rw-r--r-- | addons/website_form/static/src/js/website_form.js | 7 | ||||
| -rw-r--r-- | addons/website_sale/views/templates.xml | 1 | ||||
| -rw-r--r-- | addons/website_slides/view/website_slides.xml | 5 | ||||
| -rw-r--r-- | doc/cla/individual/nguyenductamlhp.md | 11 | ||||
| -rw-r--r-- | doc/cla/individual/sswapnesh.md | 12 |
15 files changed, 146 insertions, 30 deletions
diff --git a/addons/account/models/account_invoice.py b/addons/account/models/account_invoice.py index 565b81c5b53..6afbc6671bd 100644 --- a/addons/account/models/account_invoice.py +++ b/addons/account/models/account_invoice.py @@ -316,7 +316,7 @@ class AccountInvoice(models.Model): payment_move_line_ids = fields.Many2many('account.move.line', string='Payment Move Lines', compute='_compute_payments', store=True) user_id = fields.Many2one('res.users', string='Salesperson', track_visibility='onchange', readonly=True, states={'draft': [('readonly', False)]}, - default=lambda self: self.env.user) + default=lambda self: self.env.user, copy=False) fiscal_position_id = fields.Many2one('account.fiscal.position', string='Fiscal Position', oldname='fiscal_position', readonly=True, states={'draft': [('readonly', False)]}) commercial_partner_id = fields.Many2one('res.partner', string='Commercial Entity', compute_sudo=True, diff --git a/addons/account/views/res_config_view.xml b/addons/account/views/res_config_view.xml index a58960c2b4e..e1415fdc009 100644 --- a/addons/account/views/res_config_view.xml +++ b/addons/account/views/res_config_view.xml @@ -155,6 +155,10 @@ <label for="currency_exchange_journal_id" class="col-md-3 o_light_label"/> <field name="currency_exchange_journal_id"/> </div> + <div class="row mt16"> + <label for="currency_exchange_journal_id" class="col-md-3 o_light_label"/> + <field name="currency_exchange_journal_id"/> + </div> </div> </div> </div> diff --git a/addons/calendar/views/calendar_views.xml b/addons/calendar/views/calendar_views.xml index ed2d32df4e5..34049fc9d9f 100644 --- a/addons/calendar/views/calendar_views.xml +++ b/addons/calendar/views/calendar_views.xml @@ -119,10 +119,10 @@ <field name="stop" attrs="{'invisible': True}"/> <field name="id" attrs="{'invisible': True}"/> - <field name="start_date" string="Starting at" attrs="{'invisible': [('allday','=',False)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> - <field name="stop_date" string="Ending at" attrs="{'invisible': [('allday','=',False)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> + <field name="start_date" string="Starting at" attrs="{'required': [('allday','=',True)], 'invisible': [('allday','=',False)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> + <field name="stop_date" string="Ending at" attrs="{'required': [('allday','=',True)],'invisible': [('allday','=',False)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> - <field name="start_datetime" string="Starting at" attrs="{'invisible': [('allday','=',True)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> + <field name="start_datetime" string="Starting at" attrs="{'required': [('allday','=',False)], 'invisible': [('allday','=',True)], 'readonly': [('id', '!=', False), ('recurrency','=',True)]}"/> <field name="stop_datetime" invisible="1"/> <label for="duration" attrs="{'invisible': [('allday','=',True)]}"/> <div attrs="{'invisible': [('allday','=',True)]}"> diff --git a/addons/hr_payroll/models/hr_payslip.py b/addons/hr_payroll/models/hr_payslip.py index 58ef4371f76..0963f4eef2c 100644 --- a/addons/hr_payroll/models/hr_payslip.py +++ b/addons/hr_payroll/models/hr_payslip.py @@ -103,6 +103,7 @@ class HrPayslip(models.Model): def refund_sheet(self): for payslip in self: copied_payslip = payslip.copy({'credit_note': True, 'name': _('Refund: ') + payslip.name}) + copied_payslip.compute_sheet() copied_payslip.action_payslip_done() formview_ref = self.env.ref('hr_payroll.view_hr_payslip_form', False) treeview_ref = self.env.ref('hr_payroll.view_hr_payslip_tree', False) diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 719baf8cc94..56eaba8d3bd 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -149,7 +149,7 @@ class Message(models.Model): given, restrict to messages written in one of those channels. """ partner_id = self.env.user.partner_id.id delete_mode = not self.env.user.share # delete employee notifs, keep customer ones - if domain is None and delete_mode: + if not domain and delete_mode: query = "DELETE FROM mail_message_res_partner_needaction_rel WHERE res_partner_id IN %s" args = [(partner_id,)] if channel_ids: @@ -467,6 +467,7 @@ class Message(models.Model): - if author_id == pid, uid is the author, OR - uid belongs to a notified channel, OR - uid is in the specified recipients, OR + - uid has a notification on the message, OR - uid have read access to the related document is model, res_id - otherwise: remove the id """ @@ -494,17 +495,22 @@ class Message(models.Model): # check read access rights before checking the actual rules on the given ids super(Message, self.sudo(access_rights_uid or self._uid)).check_access_rights('read') - self._cr.execute("""SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, partner_rel.res_partner_id, channel_partner.channel_id as channel_id + self._cr.execute(""" + SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, + COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id), + channel_partner.channel_id as channel_id FROM "%s" m LEFT JOIN "mail_message_res_partner_rel" partner_rel - ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = (%%s) + ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = %%(pid)s + LEFT JOIN "mail_message_res_partner_needaction_rel" needaction_rel + ON needaction_rel.mail_message_id = m.id AND needaction_rel.res_partner_id = %%(pid)s LEFT JOIN "mail_message_mail_channel_rel" channel_rel ON channel_rel.mail_message_id = m.id LEFT JOIN "mail_channel" channel ON channel.id = channel_rel.mail_channel_id LEFT JOIN "mail_channel_partner" channel_partner - ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = (%%s) - WHERE m.id = ANY (%%s)""" % self._table, (pid, pid, ids,)) + ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = %%(pid)s + WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=pid, ids=ids)) for id, rmod, rid, author_id, partner_id, channel_id in self._cr.fetchall(): if author_id == pid: author_ids.add(id) @@ -532,6 +538,7 @@ class Message(models.Model): - read: if - author_id == pid, uid is the author OR - uid is in the recipients (partner_ids) OR + - uid has been notified (needaction) OR - uid is member of a listern channel (channel_ids.partner_ids) OR - uid have read access to the related document if model, res_id - otherwise: raise @@ -582,17 +589,22 @@ class Message(models.Model): message_values = dict((res_id, {}) for res_id in self.ids) if operation in ['read', 'write']: - self._cr.execute("""SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, partner_rel.res_partner_id, channel_partner.channel_id as channel_id + self._cr.execute(""" + SELECT DISTINCT m.id, m.model, m.res_id, m.author_id, m.parent_id, + COALESCE(partner_rel.res_partner_id, needaction_rel.res_partner_id), + channel_partner.channel_id as channel_id FROM "%s" m LEFT JOIN "mail_message_res_partner_rel" partner_rel - ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = (%%s) + ON partner_rel.mail_message_id = m.id AND partner_rel.res_partner_id = %%(pid)s + LEFT JOIN "mail_message_res_partner_needaction_rel" needaction_rel + ON needaction_rel.mail_message_id = m.id AND needaction_rel.res_partner_id = %%(pid)s LEFT JOIN "mail_message_mail_channel_rel" channel_rel ON channel_rel.mail_message_id = m.id LEFT JOIN "mail_channel" channel ON channel.id = channel_rel.mail_channel_id LEFT JOIN "mail_channel_partner" channel_partner - ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = (%%s) - WHERE m.id = ANY (%%s)""" % self._table, (self.env.user.partner_id.id, self.env.user.partner_id.id, self.ids,)) + ON channel_partner.channel_id = channel.id AND channel_partner.partner_id = %%(pid)s + WHERE m.id = ANY (%%(ids)s)""" % self._table, dict(pid=self.env.user.partner_id.id, ids=self.ids)) for mid, rmod, rid, author_id, parent_id, partner_id, channel_id in self._cr.fetchall(): message_values[mid] = { 'model': rmod, diff --git a/addons/mail/static/src/js/chatter.js b/addons/mail/static/src/js/chatter.js index f5a85948762..c6b58ea2de0 100644 --- a/addons/mail/static/src/js/chatter.js +++ b/addons/mail/static/src/js/chatter.js @@ -257,7 +257,7 @@ var Chatter = Widget.extend(chat_mixin, { var suggested_partners = []; var thread_recipients = result[self.context.default_res_id]; _.each(thread_recipients, function (recipient) { - var parsed_email = utils.parse_email(recipient[1]); + var parsed_email = recipient[1] && utils.parse_email(recipient[1]); suggested_partners.push({ checked: true, partner_id: recipient[0], diff --git a/addons/mail/static/src/js/utils.js b/addons/mail/static/src/js/utils.js index 6364be77eb5..49d1705eccd 100644 --- a/addons/mail/static/src/js/utils.js +++ b/addons/mail/static/src/js/utils.js @@ -100,15 +100,17 @@ function inline (node, transform_children) { // Parses text to find email: Tagada <address@mail.fr> -> [Tagada, address@mail.fr] or False function parse_email (text) { - var result = text.match(/(.*)<(.*@.*)>/); - if (result) { - return [_.str.trim(result[1]), _.str.trim(result[2])]; - } - result = text.match(/(.*@.*)/); - if (result) { - return [_.str.trim(result[1]), _.str.trim(result[1])]; + if (text){ + var result = text.match(/(.*)<(.*@.*)>/); + if (result) { + return [_.str.trim(result[1]), _.str.trim(result[2])]; + } + result = text.match(/(.*@.*)/); + if (result) { + return [_.str.trim(result[1]), _.str.trim(result[1])]; + } + return [text, false]; } - return [text, false]; } // Replaces textarea text into html text (add <p>, <a>) diff --git a/addons/mail/tests/test_mail_features.py b/addons/mail/tests/test_mail_features.py index 92613f55e51..5a0fb8b9418 100644 --- a/addons/mail/tests/test_mail_features.py +++ b/addons/mail/tests/test_mail_features.py @@ -23,7 +23,6 @@ class TestMailFeatures(TestMail): self.env['res.partner'].create({'name': 'Actual Partner'}) self.assertTrue(record.name) - @mute_logger('odoo.addons.mail.models.mail_mail') def test_needaction(self): # needaction use Inbox notification diff --git a/addons/mail/tests/test_mail_message.py b/addons/mail/tests/test_mail_message.py index db6dc9457a5..5d3471c5f80 100644 --- a/addons/mail/tests/test_mail_message.py +++ b/addons/mail/tests/test_mail_message.py @@ -288,3 +288,70 @@ class TestMailMessageAccess(TestMail): msg_cnt = len(self.test_pigs.message_ids) self.test_pigs.message_post(body='Hi!', subject='test') self.assertEqual(len(self.test_pigs.message_ids), msg_cnt + 1) + + @mute_logger('openerp.addons.mail.models.mail_mail') + def test_mark_all_as_read(self): + self.user_employee.notification_type = 'inbox' + emp_partner = self.user_employee.partner_id.sudo(self.user_employee.id) + + group_private = self.env['mail.channel'].with_context({ + 'mail_create_nolog': True, + 'mail_create_nosubscribe': True, + 'mail_channel_noautofollow': True, + }).create({ + 'name': 'Private', + 'description': 'Private James R.', + 'public': 'private', + 'alias_name': 'private', + 'alias_contact': 'followers'} + ).with_context({'mail_create_nosubscribe': False}) + + # mark all as read clear needactions + group_private.message_post(body='Test', message_type='comment', subtype='mail.mt_comment', partner_ids=[emp_partner.id]) + emp_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[]) + na_count = emp_partner.get_needaction_count() + self.assertEqual(na_count, 0, "mark all as read should conclude all needactions") + + # mark all as read also clear inaccessible needactions + new_msg = group_private.message_post(body='Zest', message_type='comment', subtype='mail.mt_comment', partner_ids=[emp_partner.id]) + needaction_accessible = len(emp_partner.env['mail.message'].search([['needaction', '=', True]])) + self.assertEqual(needaction_accessible, 1, "a new message to a partner is readable to that partner") + + new_msg.sudo().partner_ids = self.env['res.partner'] + emp_partner.env['mail.message'].search([['needaction', '=', True]]) + needaction_length = len(emp_partner.env['mail.message'].search([['needaction', '=', True]])) + self.assertEqual(needaction_length, 1, "message should still be readable when notified") + + na_count = emp_partner.get_needaction_count() + self.assertEqual(na_count, 1, "message not accessible is currently still counted") + + emp_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[]) + na_count = emp_partner.get_needaction_count() + self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones") + + @mute_logger('openerp.addons.mail.models.mail_mail') + def test_mark_all_as_read_share(self): + self.user_portal.notification_type = 'inbox' + portal_partner = self.user_portal.partner_id.sudo(self.user_portal.id) + + # mark all as read clear needactions + self.group_pigs.message_post(body='Test', message_type='comment', subtype='mail.mt_comment', partner_ids=[portal_partner.id]) + portal_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[]) + na_count = portal_partner.get_needaction_count() + self.assertEqual(na_count, 0, "mark all as read should conclude all needactions") + + # mark all as read also clear inaccessible needactions + new_msg = self.group_pigs.message_post(body='Zest', message_type='comment', subtype='mail.mt_comment', partner_ids=[portal_partner.id]) + needaction_accessible = len(portal_partner.env['mail.message'].search([['needaction', '=', True]])) + self.assertEqual(needaction_accessible, 1, "a new message to a partner is readable to that partner") + + new_msg.sudo().partner_ids = self.env['res.partner'] + needaction_length = len(portal_partner.env['mail.message'].search([['needaction', '=', True]])) + self.assertEqual(needaction_length, 1, "message should still be readable when notified") + + na_count = portal_partner.get_needaction_count() + self.assertEqual(na_count, 1, "message not accessible is currently still counted") + + portal_partner.env['mail.message'].mark_all_as_read(channel_ids=[], domain=[]) + na_count = portal_partner.get_needaction_count() + self.assertEqual(na_count, 0, "mark all read should conclude all needactions even inacessible ones") diff --git a/addons/maintenance/models/maintenance.py b/addons/maintenance/models/maintenance.py index 3e14687d35e..90a6ad1a4a1 100644 --- a/addons/maintenance/models/maintenance.py +++ b/addons/maintenance/models/maintenance.py @@ -214,6 +214,15 @@ class MaintenanceEquipment(models.Model): return super(MaintenanceEquipment, self).write(vals) @api.model + def _message_get_auto_subscribe_fields(self, updated_fields, auto_follow_fields=None): + """ mail.thread override so user_id which has no special access allowance is not + automatically subscribed. + """ + if auto_follow_fields is None: + auto_follow_fields = [] + return super(MaintenanceEquipment, self)._message_get_auto_subscribe_fields(updated_fields, auto_follow_fields) + + @api.model def _read_group_category_ids(self, categories, domain, order): """ Read group customization in order to display all the categories in the kanban view, even if they are empty. diff --git a/addons/website_form/static/src/js/website_form.js b/addons/website_form/static/src/js/website_form.js index e95f37ef335..234ce4da1b4 100644 --- a/addons/website_form/static/src/js/website_form.js +++ b/addons/website_form/static/src/js/website_form.js @@ -23,7 +23,7 @@ odoo.define('website_form.animation', function (require) { start: function() { var self = this; - qweb.add_template('/website_form/static/src/xml/website_form.xml'); + this.templates_loaded = ajax.loadXML('/website_form/static/src/xml/website_form.xml', qweb); this.$target.find('.o_website_form_send').on('click',function(e) {self.send(e);}); // Initialize datetimepickers @@ -233,7 +233,10 @@ odoo.define('website_form.animation', function (require) { if (status != 'success') { // Restore send button behavior if result is an error this.$target.find('.o_website_form_send').on('click',function(e) {self.send(e);}); } - this.$target.find('#o_website_form_result').replaceWith(qweb.render("website_form.status_" + status)) + var $result = this.$('#o_website_form_result'); + this.templates_loaded.done(function () { + $result.replaceWith(qweb.render("website_form.status_" + status)); + }); }, }); }); diff --git a/addons/website_sale/views/templates.xml b/addons/website_sale/views/templates.xml index 332c1bb238c..0a38c108ee1 100644 --- a/addons/website_sale/views/templates.xml +++ b/addons/website_sale/views/templates.xml @@ -725,6 +725,7 @@ </template> <template id="wizard_checkout" name="Wizard Checkout"> + <t t-set="website_sale_order" t-value="website.sale_get_order()"/> <ul class="wizard pull-right hidden-xs"> <li t-att-class="step==10 and 'text-primary' or 'text-muted'" id="step10"> <t t-if="step>10 and step<50"> diff --git a/addons/website_slides/view/website_slides.xml b/addons/website_slides/view/website_slides.xml index 4cea61c1c3e..f925a5319e3 100644 --- a/addons/website_slides/view/website_slides.xml +++ b/addons/website_slides/view/website_slides.xml @@ -112,11 +112,6 @@ <i class="fa fa-cloud-upload"></i> Upload </a> </t> - <t t-if="channel.can_upload and is_public_user"> - <a class="btn btn-primary" title="Upload Presentation" t-attf-href="/web/login?redirect=/slides/#{slug(channel)}"> - <i class="fa fa-cloud-upload"></i> Upload - </a> - </t> </h3> </div> <!-- Search box --> diff --git a/doc/cla/individual/nguyenductamlhp.md b/doc/cla/individual/nguyenductamlhp.md new file mode 100644 index 00000000000..898f465c52b --- /dev/null +++ b/doc/cla/individual/nguyenductamlhp.md @@ -0,0 +1,11 @@ +Vietnam, 2018-01-16, + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Nguyen Duc Tam nguyenductamlhp@gmail.com https://github.com/nguyenductamlhp diff --git a/doc/cla/individual/sswapnesh.md b/doc/cla/individual/sswapnesh.md new file mode 100644 index 00000000000..97a61b8c83c --- /dev/null +++ b/doc/cla/individual/sswapnesh.md @@ -0,0 +1,12 @@ +India, 2017-11-24 + +I hereby agree to the terms of the Odoo Individual Contributor License +Agreement v1.0. + +I declare that I am authorized and able to make this agreement and sign this +declaration. + +Signed, + +Swapensh Shah swapneshshah111@gmail.com https://github.com/sswapnesh + |
