diff --git a/addons/account/account.py b/addons/account/account.py index 1ab819c01c6..cd9527ff903 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -1938,15 +1938,15 @@ class account_tax(osv.osv): # 'base_code_id': fields.many2one('account.tax.code', 'Account Base Code', help="Use this code for the tax declaration."), 'tax_code_id': fields.many2one('account.tax.code', 'Account Tax Code', help="Use this code for the tax declaration."), - 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1."), - 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1."), + 'base_sign': fields.float('Base Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()), + 'tax_sign': fields.float('Tax Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()), # Same fields for refund invoices 'ref_base_code_id': fields.many2one('account.tax.code', 'Refund Base Code', help="Use this code for the tax declaration."), 'ref_tax_code_id': fields.many2one('account.tax.code', 'Refund Tax Code', help="Use this code for the tax declaration."), - 'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1."), - 'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1."), + 'ref_base_sign': fields.float('Refund Base Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()), + 'ref_tax_sign': fields.float('Refund Tax Code Sign', help="Usually 1 or -1.", digits_compute=get_precision_tax()), 'include_base_amount': fields.boolean('Included in base amount', help="Indicates if the amount of tax must be included in the base amount for the computation of the next taxes"), 'company_id': fields.many2one('res.company', 'Company', required=True), 'description': fields.char('Tax Code'), diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index 784358dceed..adb0ef784d5 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -680,19 +680,23 @@ class calendar_event(osv.Model): return [d.astimezone(pytz.UTC) for d in rset1] def _get_recurrency_end_date(self, data, context=None): - if data.get('recurrency') and data.get('end_type') in ('count', unicode('count')): - data_date_deadline = datetime.strptime(data.get('date_deadline'), '%Y-%m-%d %H:%M:%S') - if data.get('rrule_type') in ('daily', unicode('count')): - rel_date = relativedelta(days=data.get('count') + 1) - elif data.get('rrule_type') in ('weekly', unicode('weekly')): - rel_date = relativedelta(days=(data.get('count') + 1) * 7) - elif data.get('rrule_type') in ('monthly', unicode('monthly')): - rel_date = relativedelta(months=data.get('count') + 1) - elif data.get('rrule_type') in ('yearly', unicode('yearly')): - rel_date = relativedelta(years=data.get('count') + 1) - end_date = data_date_deadline + rel_date - else: - end_date = data.get('end_date') + if not data.get('recurrency'): + return False + + end_type = data.get('end_type') + end_date = data.get('end_date') + + if end_type == 'count' and all(data.get(key) for key in ['count', 'rrule_type', 'date_deadline']): + count = data['count'] + 1 + delay, mult = { + 'daily': ('days', 1), + 'weekly': ('days', 7), + 'monthly': ('months', 1), + 'yearly': ('years', 1), + }[data['rrule_type']] + + deadline = datetime.strptime(data['date_deadline'], tools.DEFAULT_SERVER_DATETIME_FORMAT) + return deadline + relativedelta(**{delay: count * mult}) return end_date def _find_my_attendee(self, cr, uid, meeting_ids, context=None): @@ -774,7 +778,11 @@ class calendar_event(osv.Model): result[event] = "" return result + # retro compatibility function def _rrule_write(self, cr, uid, ids, field_name, field_value, args, context=None): + return self._set_rulestring(self, cr, uid, ids, field_name, field_value, args, context=context) + + def _set_rulestring(self, cr, uid, ids, field_name, field_value, args, context=None): if not isinstance(ids, list): ids = [ids] data = self._get_empty_rrule_data() @@ -812,7 +820,7 @@ class calendar_event(osv.Model): 'class': fields.selection([('public', 'Public'), ('private', 'Private'), ('confidential', 'Public for Employees')], 'Privacy', states={'done': [('readonly', True)]}), 'location': fields.char('Location', help="Location of Event", track_visibility='onchange', states={'done': [('readonly', True)]}), 'show_as': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Show Time as', states={'done': [('readonly', True)]}), - 'rrule': fields.function(_get_rulestring, type='char', fnct_inv=_rrule_write, store=True, string='Recurrent Rule'), + 'rrule': fields.function(_get_rulestring, type='char', fnct_inv=_set_rulestring, store=True, string='Recurrent Rule'), 'rrule_type': fields.selection([('daily', 'Day(s)'), ('weekly', 'Week(s)'), ('monthly', 'Month(s)'), ('yearly', 'Year(s)')], 'Recurrency', states={'done': [('readonly', True)]}, help="Let the event automatically repeat at that interval"), 'recurrency': fields.boolean('Recurrent', help="Recurrent Meeting"), 'recurrent_id': fields.integer('Recurrent ID'), @@ -1169,7 +1177,7 @@ class calendar_event(osv.Model): #repeat monthly by nweekday ((weekday, weeknumber), ) if r._bynweekday: data['week_list'] = day_list[r._bynweekday[0][0]].upper() - data['byday'] = r._bynweekday[0][1] + data['byday'] = str(r._bynweekday[0][1]) data['month_by'] = 'day' data['rrule_type'] = 'monthly' @@ -1423,7 +1431,7 @@ class calendar_event(osv.Model): # set end_date for calendar searching if values.get('recurrency', True) and values.get('end_type', 'count') in ('count', unicode('count')) and \ (values.get('rrule_type') or values.get('count') or values.get('date') or values.get('date_deadline')): - for data in self.read(cr, uid, ids, ['date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context): + for data in self.read(cr, uid, ids, ['end_date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context): end_date = self._get_recurrency_end_date(data, context=context) super(calendar_event, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context) @@ -1454,11 +1462,12 @@ class calendar_event(osv.Model): if not 'user_id' in vals: # Else bug with quick_create when we are filter on an other user vals['user_id'] = uid - if vals.get('recurrency', True) and vals.get('end_type', 'count') in ('count', unicode('count')) and \ - (vals.get('rrule_type') or vals.get('count') or vals.get('date') or vals.get('date_deadline')): - vals['end_date'] = self._get_recurrency_end_date(vals, context=context) - res = super(calendar_event, self).create(cr, uid, vals, context=context) + + data = self.read(cr, uid, [res], ['end_date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context)[0] + end_date = self._get_recurrency_end_date(data, context=context) + self.write(cr, uid, [res], {'end_date': end_date}, context=context) + self.create_attendees(cr, uid, [res], context=context) return res diff --git a/addons/calendar/test/calendar_test.yml b/addons/calendar/test/calendar_test.yml index 1454a635a4f..44290d8166f 100644 --- a/addons/calendar/test/calendar_test.yml +++ b/addons/calendar/test/calendar_test.yml @@ -65,3 +65,20 @@ - !python {model: calendar.event}: | self.write(cr, uid, [ref("calendar_event_alldaytestevent0")], {'alarm_ids': [(6,0,[ref("res_alarm_daybeforeeventstarts0")])]}) +- + I create a recuring rule for my event +- + !record {model: calendar.event, id: calendar.event_sprintreview1}: + name: Begin of month meeting + date: !eval time.strftime('%Y-%m-%d 12:00:00') + recurrency: true + rrule: FREQ=MONTHLY;INTERVAL=1;COUNT=12;BYDAY=1MO +- + I check that the attributes are set correctly +- + !assert {model: calendar.event, id: calendar.event_sprintreview1}: + - rrule_type == 'monthly' + - count == 12 + - month_by == 'day' + - byday == '1' + - week_list == 'MO' diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index c64406546c3..347475d100f 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -21,6 +21,7 @@ from openerp.osv import fields, osv from openerp.tools.translate import _ +from openerp.tools import email_split import re class crm_lead2opportunity_partner(osv.osv_memory): @@ -42,20 +43,21 @@ class crm_lead2opportunity_partner(osv.osv_memory): return {'value': {'partner_id': False if action != 'exist' else self._find_matching_partner(cr, uid, context=context)}} def _get_duplicated_leads(self, cr, uid, partner_id, email, context=None): + """ + Search for opportunities that have the same partner and that arent done or cancelled + """ lead_obj = self.pool.get('crm.lead') - results = [] + emails = set(email_split(email) + [email]) + final_stage_domain = [('stage_id.probability', '<', 100), '|', ('stage_id.probability', '>', 0), ('stage_id.sequence', '<=', 1)] + partner_match_domain = [] + for email in emails: + partner_match_domain.append(('email_from', '=ilike', email)) if partner_id: - # Search for opportunities that have the same partner and that arent done or cancelled - ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), '|', ('stage_id.probability', '=', False), ('stage_id.probability', '<', '100')]) - for id in ids: - results.append(id) - email = re.findall(r'([^ ,<@]+@[^> ,]+)', email or '') - if email: - ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), '|', ('stage_id.probability', '=', False), ('stage_id.probability', '<', '100')]) - for id in ids: - results.append(id) - return list(set(results)) - + partner_match_domain.append(('partner_id', '=', partner_id)) + partner_match_domain = ['|'] * (len(partner_match_domain) - 1) + partner_match_domain + if not partner_match_domain: + return [] + return lead_obj.search(cr, uid, partner_match_domain + final_stage_domain) def default_get(self, cr, uid, fields, context=None): """ diff --git a/addons/crm_partner_assign/crm_lead_view.xml b/addons/crm_partner_assign/crm_lead_view.xml index aa9638fd579..d47e9ac5cbc 100644 --- a/addons/crm_partner_assign/crm_lead_view.xml +++ b/addons/crm_partner_assign/crm_lead_view.xml @@ -15,10 +15,10 @@

( - + N S - + E W ) @@ -88,10 +88,10 @@

( - + N S - + E W ) diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 33cc5481108..0c5ea5ed65b 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -160,6 +160,7 @@ class mail_thread(osv.AbstractModel): if res[id]['message_unread_count']: title = res[id]['message_unread_count'] > 1 and _("You have %d unread messages") % res[id]['message_unread_count'] or _("You have one unread message") res[id]['message_summary'] = "9 %d %s" % (title, res[id].pop('message_unread_count'), _("New")) + res[id].pop('message_unread_count', None) return res def read_followers_data(self, cr, uid, follower_ids, context=None): diff --git a/addons/pad/pad.py b/addons/pad/pad.py index 474321edb1d..ab6f3d6b18c 100644 --- a/addons/pad/pad.py +++ b/addons/pad/pad.py @@ -14,6 +14,10 @@ _logger = logging.getLogger(__name__) class pad_common(osv.osv_memory): _name = 'pad.common' + def pad_is_configured(self, cr, uid, context=None): + user = self.pool.get('res.users').browse(cr, uid, uid, context=context) + return bool(user.company_id.pad_server) + def pad_generate_url(self, cr, uid, context=None): company = self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id; diff --git a/addons/pad/static/src/css/etherpad.css b/addons/pad/static/src/css/etherpad.css index d8281ef4d48..a076cdfc6f4 100644 --- a/addons/pad/static/src/css/etherpad.css +++ b/addons/pad/static/src/css/etherpad.css @@ -70,6 +70,7 @@ .oe_pad_loading{ text-align: center; opacity: 0.75; + font-style: italic; } .etherpad_readonly ul, .etherpad_readonly ol { diff --git a/addons/pad/static/src/js/pad.js b/addons/pad/static/src/js/pad.js index 2d7f2c2737f..d3a6394ee5d 100644 --- a/addons/pad/static/src/js/pad.js +++ b/addons/pad/static/src/js/pad.js @@ -1,66 +1,82 @@ openerp.pad = function(instance) { + var _t = instance.web._t; instance.web.form.FieldPad = instance.web.form.AbstractField.extend(instance.web.form.ReinitializeWidgetMixin, { template: 'FieldPad', content: "", init: function() { + var self = this; this._super.apply(this, arguments); - this.set("configured", true); - this.on("change:configured", this, this.switch_configured); + this._configured_deferred = this.view.dataset.call('pad_is_configured').done(function(data) { + self.set("configured", !!data); + }).fail(function(data, event) { + event.preventDefault(); + self.set("configured", true); + }); }, initialize_content: function() { var self = this; - this.switch_configured(); this.$('.oe_pad_switch').click(function() { self.$el.toggleClass('oe_pad_fullscreen'); self.$el.find('.oe_pad_switch').toggleClass('fa-expand fa-compress'); self.view.$el.find('.oe_chatter').toggle(); }); + this._configured_deferred.always(function() { + var configured = self.get('configured'); + self.$(".oe_unconfigured").toggle(!configured); + self.$(".oe_configured").toggle(configured); + }); this.render_value(); }, - switch_configured: function() { - this.$(".oe_unconfigured").toggle(! this.get("configured")); - this.$(".oe_configured").toggle(this.get("configured")); - }, render_value: function() { - var self = this; - if (this.get("configured") && ! this.get("value")) { - self.view.dataset.call('pad_generate_url', { - context: { - model: self.view.model, - field_name: self.name, - object_id: self.view.datarecord.id - }, - }).done(function(data) { - if (! data.url) { - self.set("configured", false); + var self = this; + this._configured_deferred.always(function() { + if (! self.get('configured')) { + return; + }; + var value = self.get('value'); + if (self.get('effective_readonly')) { + if (_.str.startsWith(value, 'http')) { + this.pad_loading_request = self.view.dataset.call('pad_get_content', {url: value}).done(function(data) { + self.$('.oe_pad_content').removeClass('oe_pad_loading').html('
'); + self.$('.oe_pad_readonly').html(data); + }).fail(function() { + self.$('.oe_pad_content').text(_t('Unable to load pad')); + }); } else { - self.set("value", data.url); + self.$('.oe_pad_content').addClass('oe_pad_loading').show().text(_t("This pad will be initialized on first edit")); } - }); - } - this.$('.oe_pad_content').html(""); - var value = this.get('value'); - if (this.pad_loading_request) { - this.pad_loading_request.abort(); - } - if (_.str.startsWith(value, 'http')) { - if (! this.get('effective_readonly')) { - var content = ''; - this.$('.oe_pad_content').html(content); - this._dirty_flag = true; - } else { - this.content = '
... Loading pad ...
'; - this.pad_loading_request = $.get(value + '/export/html').done(function(data) { - groups = /\<\s*body\s*\>(.*?)\<\s*\/body\s*\>/.exec(data); - data = (groups || []).length >= 2 ? groups[1] : ''; - self.$('.oe_pad_content').html('
'); - self.$('.oe_pad_readonly').html(data); - }).fail(function() { - self.$('.oe_pad_content').text('Unable to load pad'); + } + else { + var def = $.when(); + if (! value || !_.str.startsWith(value, 'http')) { + def = self.view.dataset.call('pad_generate_url', { + context: { + model: self.view.model, + field_name: self.name, + object_id: self.view.datarecord.id + }, + }).done(function(data) { + if (! data.url) { + self.set("configured", false); + } else { + self.set("value", data.url); + } + }); + } + def.then(function() { + value = self.get('value'); + if (_.str.startsWith(value, 'http')) { + var content = ''; + self.$('.oe_pad_content').html(content); + self._dirty_flag = true; + } + else { + self.$('.oe_pad_content').text(value); + } }); } - } + }); }, }); diff --git a/addons/share/wizard/share_wizard.py b/addons/share/wizard/share_wizard.py index 22b880956cc..10937015dd6 100644 --- a/addons/share/wizard/share_wizard.py +++ b/addons/share/wizard/share_wizard.py @@ -906,7 +906,7 @@ class share_result_line(osv.osv_memory): 'login': fields.related('user_id', 'login', string='Login', type='char', size=64, required=True, readonly=True), 'password': fields.char('Password', size=64, readonly=True), 'share_url': fields.function(_share_url, string='Share URL', type='char', size=512), - 'share_wizard_id': fields.many2one('share.wizard', 'Share Wizard', required=True), + 'share_wizard_id': fields.many2one('share.wizard', 'Share Wizard', required=True, ondelete='cascade'), 'newly_created': fields.boolean('Newly created', readonly=True), } _defaults = { diff --git a/addons/stock/stock_view.xml b/addons/stock/stock_view.xml index de76c3e066a..e96652c11aa 100644 --- a/addons/stock/stock_view.xml +++ b/addons/stock/stock_view.xml @@ -792,7 +792,7 @@ - + @@ -926,7 +926,7 @@ - +
@@ -1053,7 +1053,7 @@ - +
diff --git a/addons/website/static/src/js/website.snippets.editor.js b/addons/website/static/src/js/website.snippets.editor.js index ee07840b12d..0607691ccaa 100644 --- a/addons/website/static/src/js/website.snippets.editor.js +++ b/addons/website/static/src/js/website.snippets.editor.js @@ -1436,7 +1436,7 @@ this.$target.find('.carousel-control').off('click').on('click', function () { self.$target.carousel( $(this).data('slide')); }); - this.$target.find('.carousel-inner .content > div').attr('contentEditable', 'true'); + this.$target.find('.carousel-image, .carousel-inner .content > div').attr('contentEditable', 'true'); this.$target.find('.carousel-image').attr('attributeEditable', 'true'); this._super(); }, diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index eaca3608aa7..3aaabb596f1 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -603,7 +603,7 @@ class Ecommerce(http.Controller): if tx: acquirer_ids = [tx.acquirer_id.id] else: - acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True)], context=context) + acquirer_ids = payment_obj.search(cr, SUPERUSER_ID, [('website_published', '=', True), '|', ('company_id', '=', order.company_id.id), ('company_id', '=', False)], context=context) values['acquirers'] = payment_obj.browse(cr, uid, acquirer_ids, context=context) render_ctx = dict(context, submit_class='btn btn-primary', submit_txt='Pay Now') for acquirer in values['acquirers']: