diff --git a/addons/account/account.py b/addons/account/account.py index 9b728ff36e0..01a4d851de8 100644 --- a/addons/account/account.py +++ b/addons/account/account.py @@ -1023,7 +1023,10 @@ class account_period(osv.osv): if not result: result = self.search(cr, uid, args, context=context) if not result: - raise osv.except_osv(_('Error!'), _('There is no period defined for this date: %s.\nPlease create one.')%dt) + model, action_id = self.pool['ir.model.data'].get_object_reference(cr, uid, 'account', 'action_account_fiscalyear') + msg = _('There is no period defined for this date: %s.\nPlease, go to Configuration/Periods and configure a fiscal year.') % dt + raise openerp.exceptions.RedirectWarning(msg, action_id, _('Go to the configuration panel')) + return result def action_draft(self, cr, uid, ids, *args): diff --git a/addons/account/account_move_line.py b/addons/account/account_move_line.py index cb242942656..b48e842caf5 100644 --- a/addons/account/account_move_line.py +++ b/addons/account/account_move_line.py @@ -849,18 +849,17 @@ class account_move_line(osv.osv): (tuple(ids), )) r = cr.fetchall() #TODO: move this check to a constraint in the account_move_reconcile object + if len(r) != 1: + raise osv.except_osv(_('Error'), _('Entries are not of the same account or already reconciled ! ')) if not unrec_lines: raise osv.except_osv(_('Error!'), _('Entry is already reconciled.')) account = account_obj.browse(cr, uid, account_id, context=context) + if not account.reconcile: + raise osv.except_osv(_('Error'), _('The account is not defined to be reconciled !')) if r[0][1] != None: raise osv.except_osv(_('Error!'), _('Some entries are already reconciled.')) - if context.get('fy_closing'): - # We don't want to generate any write-off when being called from the - # wizard used to close a fiscal year (and it doesn't give us any - # writeoff_acc_id). - pass - elif (not currency_obj.is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \ + if (not currency_obj.is_zero(cr, uid, account.company_id.currency_id, writeoff)) or \ (account.currency_id and (not currency_obj.is_zero(cr, uid, account.currency_id, currency))): if not writeoff_acc_id: raise osv.except_osv(_('Warning!'), _('You have to provide an account for the write off/exchange difference entry.')) @@ -1199,7 +1198,7 @@ class account_move_line(osv.osv): break # Automatically convert in the account's secondary currency if there is one and # the provided values were not already multi-currency - if account.currency_id and (vals.get('amount_currency', False) is False) and account.currency_id.id != account.company_id.currency_id.id: + if account.currency_id and 'amount_currency' not in vals and account.currency_id.id != account.company_id.currency_id.id: vals['currency_id'] = account.currency_id.id ctx = {} if 'date' in vals: diff --git a/addons/account/wizard/account_fiscalyear_close.py b/addons/account/wizard/account_fiscalyear_close.py index 928f0647084..266b8ccbc50 100644 --- a/addons/account/wizard/account_fiscalyear_close.py +++ b/addons/account/wizard/account_fiscalyear_close.py @@ -224,14 +224,6 @@ class account_fiscalyear_close(osv.osv_memory): query_2nd_part = "" query_2nd_part_args = [] for account in obj_acc_account.browse(cr, uid, account_ids, context={'fiscalyear': fy_id}): - balance_in_currency = 0.0 - if account.currency_id: - cr.execute('SELECT sum(COALESCE(amount_currency,0.0)) as balance_in_currency FROM account_move_line ' \ - 'WHERE account_id = %s ' \ - 'AND ' + query_line + ' ' \ - 'AND currency_id = %s', (account.id, account.currency_id.id)) - balance_in_currency = cr.dictfetchone()['balance_in_currency'] - company_currency_id = self.pool.get('res.users').browse(cr, uid, uid).company_id.currency_id if not currency_obj.is_zero(cr, uid, company_currency_id, abs(account.balance)): if query_2nd_part: @@ -246,7 +238,7 @@ class account_fiscalyear_close(osv.osv_memory): period.id, account.id, account.currency_id and account.currency_id.id or None, - balance_in_currency, + account.foreign_balance if account.currency_id else 0.0, account.company_id.id, 'draft') if query_2nd_part: diff --git a/addons/account_anglo_saxon/test/anglo_saxon.yml b/addons/account_anglo_saxon/test/anglo_saxon.yml index 7c3983223b0..042f35a5271 100644 --- a/addons/account_anglo_saxon/test/anglo_saxon.yml +++ b/addons/account_anglo_saxon/test/anglo_saxon.yml @@ -43,6 +43,7 @@ parent_id: account.cash type: other user_type: account.data_account_type_asset + reconcile: True - Configure Creditor Account Payable. - @@ -52,6 +53,7 @@ parent_id: account.a_pay type: other user_type: account.data_account_type_payable + reconcile: True - Configure Debtor Account Receivable. - @@ -61,6 +63,7 @@ parent_id: account.a_recv type: other user_type: account.data_account_type_receivable + reconcile: True - Configure Cost of Good sale Account. - diff --git a/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml b/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml index 93afc48eb11..92d331f97d7 100644 --- a/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml +++ b/addons/account_anglo_saxon/test/anglo_saxon_avg_fifo.yml @@ -43,6 +43,7 @@ parent_id: account.cash type: other user_type: account.data_account_type_asset + reconcile: True - Configure Creditor Account Payable. - @@ -52,6 +53,7 @@ parent_id: account.a_pay type: other user_type: account.data_account_type_payable + reconcile: True - Configure Debtor Account Receivable. - @@ -61,6 +63,7 @@ parent_id: account.a_recv type: other user_type: account.data_account_type_receivable + reconcile: True - Configure Cost of Good sale Account. - diff --git a/addons/account_voucher/account_voucher.py b/addons/account_voucher/account_voucher.py index d6ad6efdd90..c9516b6135f 100644 --- a/addons/account_voucher/account_voucher.py +++ b/addons/account_voucher/account_voucher.py @@ -217,7 +217,7 @@ class account_voucher(osv.osv): if context.get('type', 'sale') in ('purchase', 'payment'): nodes = doc.xpath("//field[@name='partner_id']") for node in nodes: - node.set('context', "{'search_default_supplier': 1}") + node.set('context', "{'default_customer': 0, 'search_default_supplier': 1, 'default_supplier': 1}") if context.get('invoice_type','') in ('in_invoice', 'in_refund'): node.set('string', _("Supplier")) res['arch'] = etree.tostring(doc) @@ -1329,7 +1329,7 @@ class account_voucher(osv.osv): 'date': voucher.date, 'credit': diff > 0 and diff or 0.0, 'debit': diff < 0 and -diff or 0.0, - 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher.writeoff_amount) or False, + 'amount_currency': company_currency <> current_currency and (sign * -1 * voucher.writeoff_amount) or 0.0, 'currency_id': company_currency <> current_currency and current_currency or False, 'analytic_account_id': voucher.analytic_id and voucher.analytic_id.id or False, } diff --git a/addons/analytic/analytic.py b/addons/analytic/analytic.py index 14c8f712e81..4f1de653ae1 100644 --- a/addons/analytic/analytic.py +++ b/addons/analytic/analytic.py @@ -194,7 +194,7 @@ class account_analytic_account(osv.osv): 'user_id': fields.many2one('res.users', 'Project Manager', track_visibility='onchange'), 'manager_id': fields.many2one('res.users', 'Account Manager', track_visibility='onchange'), 'date_start': fields.date('Start Date'), - 'date': fields.date('End Date', select=True, track_visibility='onchange'), + 'date': fields.date('Expiration Date', select=True, track_visibility='onchange'), 'company_id': fields.many2one('res.company', 'Company', required=False), #not required because we want to allow different companies to use the same chart of account, except for leaf accounts. 'state': fields.selection([('template', 'Template'),('draft','New'),('open','In Progress'),('pending','To Renew'),('close','Closed'),('cancelled', 'Cancelled')], 'Status', required=True, track_visibility='onchange'), 'currency_id': fields.function(_currency, fnct_inv=_set_company_currency, #the currency_id field is readonly except if it's a view account and if there is no company diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 2d1285da84f..770b32d84fd 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -1049,11 +1049,13 @@ class crm_lead(format_address, osv.osv): def schedule_phonecall_send_note(self, cr, uid, ids, phonecall_id, action, context=None): phonecall = self.pool.get('crm.phonecall').browse(cr, uid, [phonecall_id], context=context)[0] if action == 'log': - prefix = 'Logged' + message = _('Logged a call for %(date)s. %(description)s') else: - prefix = 'Scheduled' - suffix = ' %s' % phonecall.description - message = _("%s a call for %s.%s") % (prefix, phonecall.date, suffix) + message = _('Scheduled a call for %(date)s. %(description)s') + phonecall_date = datetime.strptime(phonecall.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + phonecall_usertime = fields.datetime.context_timestamp(cr, uid, phonecall_date, context=context).strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT) + html_time = "" % (phonecall.date, phonecall_usertime) + message = message % dict(date=html_time, description=phonecall.description) return self.message_post(cr, uid, ids, body=message, context=context) def log_meeting(self, cr, uid, ids, meeting_subject, meeting_date, duration, context=None): diff --git a/addons/crm_partner_assign/static/src/js/next.js b/addons/crm_partner_assign/static/src/js/next.js index c40f104d6c6..73ef09edb2e 100644 --- a/addons/crm_partner_assign/static/src/js/next.js +++ b/addons/crm_partner_assign/static/src/js/next.js @@ -1,11 +1,16 @@ openerp.crm_partner_assign = function (instance) { instance.crm_partner_assign = instance.crm_partner_assign || {}; instance.crm_partner_assign.next_or_list = function(parent) { - var form = parent.inner_widget.views.form.controller; - form.dataset.remove_ids([form.dataset.ids[form.dataset.index]]); - form.reload(); - if (!form.dataset.ids.length){ - parent.inner_widget.switch_mode('list'); + if (parent.inner_widget.active_view === "form"){ + var form = parent.inner_widget.views.form.controller; + form.dataset.remove_ids([form.dataset.ids[form.dataset.index]]); + form.reload(); + if (!form.dataset.ids.length){ + parent.inner_widget.switch_mode('list'); + } + } + else{ + parent.inner_widget.views[parent.inner_widget.active_view].controller.reload(); } parent.do_action({ type: 'ir.actions.act_window_close' }); }; diff --git a/addons/crm_partner_assign/wizard/crm_channel_interested.py b/addons/crm_partner_assign/wizard/crm_channel_interested.py index 57db36cb51f..a407ca7b910 100644 --- a/addons/crm_partner_assign/wizard/crm_channel_interested.py +++ b/addons/crm_partner_assign/wizard/crm_channel_interested.py @@ -53,7 +53,7 @@ class crm_lead_forward_to_partner(osv.TransientModel): values = {'partner_assigned_id': False} user = self.pool.get('res.users').browse(cr, uid, uid, context=context) partner_ids = self.pool.get('res.partner').search(cr, SUPERUSER_ID, [('id', 'child_of', user.partner_id.commercial_partner_id.id)], context=context) - lead_obj.message_unsubscribe(cr, SUPERUSER_ID, context.get('active_ids'), partner_ids, context=None) + lead_obj.message_unsubscribe(cr, SUPERUSER_ID, context.get('active_ids', []), partner_ids, context=None) try: stage_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'crm_partner_assign', stage)[1] except ValueError: @@ -62,11 +62,12 @@ class crm_lead_forward_to_partner(osv.TransientModel): values.update({'stage_id': stage_id}) if wizard.comment: message += '

%s

' % wizard.comment - lead_obj.message_post(cr, uid, context.get('active_ids'), body=message, context=context) + for active_id in context.get('active_ids', []): + lead_obj.message_post(cr, uid, active_id, body=message, context=context) if values: - lead_obj.write(cr, SUPERUSER_ID, context.get('active_ids'), values) + lead_obj.write(cr, SUPERUSER_ID, context.get('active_ids', []), values) if wizard.interested: - for lead in lead_obj.browse(cr, uid, context.get('active_ids'), context=context): + for lead in lead_obj.browse(cr, uid, context.get('active_ids', []), context=context): lead_obj.convert_opportunity(cr, SUPERUSER_ID, [lead.id], lead.partner_id and lead.partner_id.id or None, context=None) return { 'type': 'ir.actions.client', diff --git a/addons/crm_partner_assign/wizard/crm_forward_to_partner.py b/addons/crm_partner_assign/wizard/crm_forward_to_partner.py index 80fa2f28b04..fca2f9b746f 100644 --- a/addons/crm_partner_assign/wizard/crm_forward_to_partner.py +++ b/addons/crm_partner_assign/wizard/crm_forward_to_partner.py @@ -139,6 +139,8 @@ class crm_lead_forward_to_partner(osv.TransientModel): values = {'partner_assigned_id': partner_id, 'user_id': partner_leads['partner'].user_id.id} if stage_id: values['stage_id'] = stage_id + if partner_leads['partner'].user_id: + values['section_id'] = partner_leads['partner'].user_id.default_section_id.id lead_obj.write(cr, uid, lead_ids, values) self.pool.get('crm.lead').message_subscribe(cr, uid, lead_ids, [partner_id], context=context) return True diff --git a/addons/decimal_precision/decimal_precision.py b/addons/decimal_precision/decimal_precision.py index b237f053ebb..6c6453322c0 100644 --- a/addons/decimal_precision/decimal_precision.py +++ b/addons/decimal_precision/decimal_precision.py @@ -23,6 +23,7 @@ import openerp from openerp import SUPERUSER_ID from openerp import tools from openerp.osv import osv, fields +from openerp.modules.registry import RegistryManager class decimal_precision(osv.osv): _name = 'decimal.precision' @@ -44,23 +45,28 @@ class decimal_precision(osv.osv): res = cr.fetchone() return res[0] if res else 2 + def clear_cache(self, cr): + """clear cache and update models. Notify other workers to restart their registry.""" + self.precision_get.clear_cache(self) + for obj in self.pool.obj_list(): + for colname, col in self.pool.get(obj)._columns.items(): + if isinstance(col, (fields.float, fields.function)): + col.digits_change(cr) + RegistryManager.signal_registry_change(cr.dbname) + def create(self, cr, uid, data, context=None): res = super(decimal_precision, self).create(cr, uid, data, context=context) - self.precision_get.clear_cache(self) + self.clear_cache(cr) return res def unlink(self, cr, uid, ids, context=None): res = super(decimal_precision, self).unlink(cr, uid, ids, context=context) - self.precision_get.clear_cache(self) + self.clear_cache(cr) return res def write(self, cr, uid, ids, data, *args, **argv): res = super(decimal_precision, self).write(cr, uid, ids, data, *args, **argv) - self.precision_get.clear_cache(self) - for obj in self.pool.obj_list(): - for colname, col in self.pool[obj]._columns.items(): - if isinstance(col, (fields.float, fields.function)): - col.digits_change(cr) + self.clear_cache(cr) return res diff --git a/addons/email_template/tests/test_mail.py b/addons/email_template/tests/test_mail.py index a43bbe79713..83cabb01252 100644 --- a/addons/email_template/tests/test_mail.py +++ b/addons/email_template/tests/test_mail.py @@ -64,7 +64,7 @@ class test_message_compose(TestMail): 'body_html': '${object.description}', 'user_signature': True, 'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])], - 'email_to': 'b@b.b c@c.c', + 'email_to': 'b@b.b, c@c.c', 'email_cc': 'd@d.d' }) @@ -192,7 +192,7 @@ class test_message_compose(TestMail): email_template.write(cr, uid, [email_template_id], { 'model_id': user_model_id, 'body_html': '${object.login}', - 'email_to': '${object.email} c@c', + 'email_to': '${object.email}, c@c', 'partner_to': '%i,%i' % (p_b_id, p_c_id), 'email_cc': 'd@d', }) @@ -217,7 +217,7 @@ class test_message_compose(TestMail): 'subject': '${object.name}', 'body_html': '${object.description}', 'user_signature': True, - 'email_to': 'b@b.b c@c.c', + 'email_to': 'b@b.b, c@c.c', 'email_cc': 'd@d.d', 'partner_to': '${user.partner_id.id},%s,%s,-1' % (self.user_raoul.partner_id.id, self.user_bert.partner_id.id) }) @@ -226,7 +226,7 @@ class test_message_compose(TestMail): msg_id = email_template.send_mail(cr, uid, email_template_id, self.group_pigs_id, context=context) mail = self.mail_mail.browse(cr, uid, msg_id, context=context) self.assertEqual(mail.subject, 'Pigs', 'email_template: send_mail: wrong subject') - self.assertEqual(mail.email_to, 'b@b.b c@c.c', 'email_template: send_mail: wrong email_to') + self.assertEqual(mail.email_to, 'b@b.b, c@c.c', 'email_template: send_mail: wrong email_to') self.assertEqual(mail.email_cc, 'd@d.d', 'email_template: send_mail: wrong email_cc') self.assertEqual( set([partner.id for partner in mail.recipient_ids]), diff --git a/addons/email_template/wizard/mail_compose_message.py b/addons/email_template/wizard/mail_compose_message.py index cd417a19d3c..f8bf45b2ba0 100644 --- a/addons/email_template/wizard/mail_compose_message.py +++ b/addons/email_template/wizard/mail_compose_message.py @@ -137,7 +137,7 @@ class mail_compose_message(osv.TransientModel): def _get_or_create_partners_from_values(self, cr, uid, rendered_values, context=None): """ Check for email_to, email_cc, partner_to """ partner_ids = [] - mails = tools.email_split(rendered_values.pop('email_to', '') + ' ' + rendered_values.pop('email_cc', '')) + mails = tools.email_split(rendered_values.pop('email_to', '')) + tools.email_split(rendered_values.pop('email_cc', '')) for mail in mails: partner_id = self.pool.get('res.partner').find_or_create(cr, uid, mail, context=context) partner_ids.append(partner_id) diff --git a/addons/google_drive/google_drive.py b/addons/google_drive/google_drive.py index b69c7ddd173..f3607094040 100644 --- a/addons/google_drive/google_drive.py +++ b/addons/google_drive/google_drive.py @@ -120,7 +120,7 @@ class config(osv.Model): res['url'] = content['alternateLink'] key = self._get_key_from_url(res['url']) request_url = "https://www.googleapis.com/drive/v2/files/%s/permissions?emailMessage=This+is+a+drive+file+created+by+OpenERP&sendNotificationEmails=false&access_token=%s" % (key, access_token) - data = {'role': 'reader', 'type': 'anyone', 'value': '', 'withLink': True} + data = {'role': 'writer', 'type': 'anyone', 'value': '', 'withLink': True} try: req = urllib2.Request(request_url, json.dumps(data), headers) urllib2.urlopen(req) @@ -133,7 +133,7 @@ class config(osv.Model): req = urllib2.Request(request_url, json.dumps(data), headers) urllib2.urlopen(req) except urllib2.HTTPError: - raise self.pool.get('res.config.settings').get_config_warning(cr, _("The permission 'writer' for your email '%s' has not been written on the document. Is this email a valid Google Account ?" % user.email), context=context) + pass return res def get_google_drive_config(self, cr, uid, res_model, res_id, context=None): diff --git a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py index ecfe9a1ead3..945a6b81d76 100644 --- a/addons/hr_timesheet_invoice/hr_timesheet_invoice.py +++ b/addons/hr_timesheet_invoice/hr_timesheet_invoice.py @@ -76,10 +76,11 @@ class account_analytic_account(osv.osv): def on_change_partner_id(self, cr, uid, ids, partner_id, name, context=None): res = super(account_analytic_account, self).on_change_partner_id(cr, uid, ids, partner_id, name, context=context) - part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) - pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False - if pricelist: - res['value']['pricelist_id'] = pricelist + if partner_id: + part = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context) + pricelist = part.property_product_pricelist and part.property_product_pricelist.id or False + if pricelist: + res['value']['pricelist_id'] = pricelist return res def set_close(self, cr, uid, ids, context=None): diff --git a/addons/hr_timesheet_sheet/static/src/js/timesheet.js b/addons/hr_timesheet_sheet/static/src/js/timesheet.js index 461d8c73803..c7afd62c8ab 100644 --- a/addons/hr_timesheet_sheet/static/src/js/timesheet.js +++ b/addons/hr_timesheet_sheet/static/src/js/timesheet.js @@ -9,12 +9,14 @@ openerp.hr_timesheet_sheet = function(instance) { }, init: function() { this._super.apply(this, arguments); + var self = this; this.set({ sheets: [], date_to: false, date_from: false, }); this.updating = false; + this.defs = []; this.field_manager.on("field_changed:timesheet_ids", this, this.query_sheets); this.field_manager.on("field_changed:date_from", this, function() { this.set({"date_from": instance.web.str_to_date(this.field_manager.get_field_value("date_from"))}); @@ -29,6 +31,14 @@ openerp.hr_timesheet_sheet = function(instance) { this.res_o2m_drop = new instance.web.DropMisordered(); this.render_drop = new instance.web.DropMisordered(); this.description_line = _t("/"); + // Original save function is overwritten in order to wait all running deferreds to be done before actually applying the save. + this.view.original_save = _.bind(this.view.save, this.view); + this.view.save = function(prepend_on_create){ + self.prepend_on_create = prepend_on_create; + return $.when.apply($, self.defs).then(function(){ + return self.view.original_save(self.prepend_on_create); + }); + }; }, go_to: function(event) { var id = JSON.parse($(event.target).data("id")); @@ -192,11 +202,11 @@ openerp.hr_timesheet_sheet = function(instance) { account.days[day_count].lines[0].unit_amount += num - self.sum_box(account, day_count); var product = (account.days[day_count].lines[0].product_id instanceof Array) ? account.days[day_count].lines[0].product_id[0] : account.days[day_count].lines[0].product_id var journal = (account.days[day_count].lines[0].journal_id instanceof Array) ? account.days[day_count].lines[0].journal_id[0] : account.days[day_count].lines[0].journal_id - new instance.web.Model("hr.analytic.timesheet").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) { + self.defs.push(new instance.web.Model("hr.analytic.timesheet").call("on_change_unit_amount", [[], product, account.days[day_count].lines[0].unit_amount, false, false, journal]).then(function(res) { account.days[day_count].lines[0]['amount'] = res.value.amount || 0; self.display_totals(); self.sync(); - }); + })); if(!isNaN($(this).val())){ $(this).val(self.sum_box(account, day_count, true)); } diff --git a/addons/im/im.py b/addons/im/im.py index fd87dbacbff..20b8a966172 100644 --- a/addons/im/im.py +++ b/addons/im/im.py @@ -29,7 +29,7 @@ import openerp.tools.config import openerp.modules.registry from openerp import http from openerp.http import request -from openerp.osv import osv, fields +from openerp.osv import osv, fields, expression from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT _logger = logging.getLogger(__name__) @@ -249,11 +249,31 @@ class im_user(osv.osv): res[obj["id"]] = obj["im_last_status"] and (last_update + delta) > current return res + def _status_search(self, cr, uid, obj, name, domain, context=None): + current = datetime.datetime.now() + delta = datetime.timedelta(0, DISCONNECTION_TIMER) + field, operator, value = domain[0] + if operator in expression.NEGATIVE_TERM_OPERATORS: + value = not value + if value: + return ['&', ('im_last_status', '=', True), ('im_last_status_update', '>', (current - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT))] + else: + return ['|', ('im_last_status', '=', False), ('im_last_status_update', '<=', (current - delta).strftime(DEFAULT_SERVER_DATETIME_FORMAT))] + def search_users(self, cr, uid, text_search, fields, limit, context=None): my_id = self.get_my_id(cr, uid, None, context) - found = self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False]], + group_employee = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_user')[1] + found = self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False], ["im_status", "=", True], ["user_id.groups_id", "in", [group_employee]]], order="name asc", limit=limit, context=context) - return self.read(cr, uid, found, fields, context=context) + if len(found) < limit: + found += self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False], ["im_status", "=", True], ["id", "not in", found]], + order="name asc", limit=limit, context=context) + if len(found) < limit: + found += self.search(cr, uid, [["name", "ilike", text_search], ["id", "<>", my_id], ["uuid", "=", False], ["im_status", "=", False], ["id", "not in", found]], + order="name asc", limit=limit-len(found), context=context) + users = self.read(cr, uid, found, fields, context=context) + users.sort(key=lambda obj: found.index(obj['id'])) + return users def im_connect(self, cr, uid, uuid=None, context=None): assert_uuid(uuid) @@ -308,7 +328,7 @@ class im_user(osv.osv): 'im_last_received': fields.integer(string="Instant Messaging Last Received Message"), 'im_last_status': fields.boolean(strint="Instant Messaging Last Status"), 'im_last_status_update': fields.datetime(string="Instant Messaging Last Status Update"), - 'im_status': fields.function(_im_status, string="Instant Messaging Status", type='boolean'), + 'im_status': fields.function(_im_status, string="Instant Messaging Status", type='boolean', fnct_search=_status_search), } _defaults = { diff --git a/addons/im/static/src/audio/Ting.mp3 b/addons/im/static/src/audio/Ting.mp3 index ffbb77144b2..fd29bf599d6 100644 Binary files a/addons/im/static/src/audio/Ting.mp3 and b/addons/im/static/src/audio/Ting.mp3 differ diff --git a/addons/im/static/src/audio/Ting.ogg b/addons/im/static/src/audio/Ting.ogg index 74ee13a4e5a..cc8c0f01eff 100644 Binary files a/addons/im/static/src/audio/Ting.ogg and b/addons/im/static/src/audio/Ting.ogg differ diff --git a/addons/im/static/src/js/im_common.js b/addons/im/static/src/js/im_common.js index 43ffdd982a0..83f92265ec6 100644 --- a/addons/im/static/src/js/im_common.js +++ b/addons/im/static/src/js/im_common.js @@ -259,7 +259,7 @@ function declare($, _, openerp) { _.each(messages, function(message) { if (! message.technical) { defs.push(self.activate_session(message.session_id[0]).then(function(conv) { - received = true; + received = self.my_id !== message.from_id[0]; return conv.received_message(message); })); } else { @@ -268,12 +268,13 @@ function declare($, _, openerp) { defs.push($.when(im_common.technical_messages_handlers[json.type](self, message))); } }); - if (! this.get("window_focus") && received) { - this.set("waiting_messages", this.get("waiting_messages") + messages.length); - this.ting.play(); - this.create_ting(); - } - return $.when.apply($, defs); + return $.when.apply($, defs).then(function(){ + if (! self.get("window_focus") && received) { + self.set("waiting_messages", self.get("waiting_messages") + messages.length); + self.ting.play(); + self.create_ting(); + } + }); }, calc_positions: function() { var current = this.get("right_offset"); @@ -520,7 +521,7 @@ function declare($, _, openerp) { txt += _.escape(str.slice(last, result.index)); last = url_regex.lastIndex; var url = _.escape(result[0]); - txt += '' + url + ''; + txt += '' + url + ''; } txt += _.escape(str.slice(last, str.length)); return txt; diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 57950376beb..16df41b239a 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -252,10 +252,9 @@ class mail_thread(osv.AbstractModel): new = set(command[2]) # remove partners that are no longer followers - self.message_unsubscribe(cr, uid, [id], list(old-new)) - + self.message_unsubscribe(cr, uid, [id], list(old-new), context=context) # add new followers - self.message_subscribe(cr, uid, [id], list(new-old)) + self.message_subscribe(cr, uid, [id], list(new-old), context=context) def _search_followers(self, cr, uid, obj, name, args, context): """Search function for message_follower_ids @@ -291,7 +290,7 @@ class mail_thread(osv.AbstractModel): 'message_is_follower': fields.function(_get_followers, type='boolean', fnct_search=_search_is_follower, string='Is a Follower', multi='_get_followers,'), 'message_follower_ids': fields.function(_get_followers, fnct_inv=_set_followers, - fnct_search=_search_followers, type='many2many', + fnct_search=_search_followers, type='many2many', priority=-10, obj='res.partner', string='Followers', multi='_get_followers'), 'message_ids': fields.one2many('mail.message', 'res_id', domain=lambda self: [('model', '=', self._name)], @@ -344,16 +343,22 @@ class mail_thread(osv.AbstractModel): if context is None: context = {} - thread_id = super(mail_thread, self).create(cr, uid, values, context=context) + # subscribe uid unless asked not to + if not context.get('mail_create_nosubscribe'): + pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid).partner_id.id + message_follower_ids = values.get('message_follower_ids') or [] # webclient can send None or False + message_follower_ids.append([4, pid]) + values['message_follower_ids'] = message_follower_ids + # add operation to ignore access rule checking for subscription + context_operation = dict(context, operation='create') + else: + context_operation = context + thread_id = super(mail_thread, self).create(cr, uid, values, context=context_operation) # automatic logging unless asked not to (mainly for various testing purpose) if not context.get('mail_create_nolog'): self.message_post(cr, uid, thread_id, body=_('%s created') % (self._description), context=context) - # subscribe uid unless asked not to - if not context.get('mail_create_nosubscribe'): - self.message_subscribe_users(cr, uid, [thread_id], [uid], context=context) - # auto_subscribe: take values and defaults into account create_values = dict(values) for key, val in context.iteritems(): @@ -1115,11 +1120,23 @@ class mail_thread(osv.AbstractModel): alternative = True if part.get_content_maintype() == 'multipart': continue # skip container - filename = part.get_filename() # None if normal part + # part.get_filename returns decoded value if able to decode, coded otherwise. + # original get_filename is not able to decode iso-8859-1 (for instance). + # therefore, iso encoded attachements are not able to be decoded properly with get_filename + # code here partially copy the original get_filename method, but handle more encoding + filename=part.get_param('filename', None, 'content-disposition') + if not filename: + filename=part.get_param('name', None) + if filename: + if isinstance(filename, tuple): + # RFC2231 + filename=email.utils.collapse_rfc2231_value(filename).strip() + else: + filename=decode(filename) encoding = part.get_content_charset() # None if attachment # 1) Explicit Attachments -> attachments if filename or part.get('content-disposition', '').strip().startswith('attachment'): - attachments.append((decode(filename) or 'attachment', part.get_payload(decode=True))) + attachments.append((filename or 'attachment', part.get_payload(decode=True))) continue # 2) text/plain ->
                 if part.get_content_type() == 'text/plain' and (not alternative or not body):
@@ -1532,20 +1549,26 @@ class mail_thread(osv.AbstractModel):
 
     def message_subscribe(self, cr, uid, ids, partner_ids, subtype_ids=None, context=None):
         """ Add partners to the records followers. """
+        if context is None:
+            context = {}
+
         mail_followers_obj = self.pool.get('mail.followers')
         subtype_obj = self.pool.get('mail.message.subtype')
 
         user_pid = self.pool.get('res.users').browse(cr, uid, uid, context=context).partner_id.id
         if set(partner_ids) == set([user_pid]):
-            try:
-                self.check_access_rights(cr, uid, 'read')
-            except (osv.except_osv, orm.except_orm):
-                return
+            if context.get('operation', '') != 'create':
+                try:
+                    self.check_access_rights(cr, uid, 'read')
+                    self.check_access_rule(cr, uid, ids, 'read')
+                except (osv.except_osv, orm.except_orm):
+                    return False
         else:
             self.check_access_rights(cr, uid, 'write')
+            self.check_access_rule(cr, uid, ids, 'write')
 
         existing_pids_dict = {}
-        fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)])
+        fol_ids = mail_followers_obj.search(cr, SUPERUSER_ID, ['&', '&', ('res_model', '=', self._name), ('res_id', 'in', ids), ('partner_id', 'in', partner_ids)])
         for fol in mail_followers_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context):
             existing_pids_dict.setdefault(fol.res_id, set()).add(fol.partner_id.id)
 
@@ -1587,8 +1610,10 @@ class mail_thread(osv.AbstractModel):
         user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
         if set(partner_ids) == set([user_pid]):
             self.check_access_rights(cr, uid, 'read')
+            self.check_access_rule(cr, uid, ids, 'read')
         else:
             self.check_access_rights(cr, uid, 'write')
+            self.check_access_rule(cr, uid, ids, 'write')
         fol_obj = self.pool['mail.followers']
         fol_ids = fol_obj.search(
             cr, SUPERUSER_ID, [
diff --git a/addons/mail/static/src/js/mail.js b/addons/mail/static/src/js/mail.js
index 4378d2f75b5..43feb52784a 100644
--- a/addons/mail/static/src/js/mail.js
+++ b/addons/mail/static/src/js/mail.js
@@ -1438,7 +1438,7 @@ openerp.mail = function (session) {
         message_fetch: function (replace_domain, replace_context, ids, callback) {
             return this.ds_message.call('message_read', [
                     // ids force to read
-                    ids == false ? undefined : ids, 
+                    ids === false ? undefined : ids, 
                     // domain + additional
                     (replace_domain ? replace_domain : this.domain), 
                     // ids allready loaded
@@ -1814,7 +1814,7 @@ openerp.mail = function (session) {
             if ('display_log_button' in this.options) {
                 this.node.params.display_log_button = this.options.display_log_button;
             }
-            this.domain = this.node.params && this.node.params.domain || [];
+            this.domain = (this.node.params && this.node.params.domain) || (this.field && this.field.domain) || [];
 
             if (!this.ParentViewManager.is_action_enabled('edit')) {
                 this.node.params.show_link = false;
diff --git a/addons/marketing/marketing_view.xml b/addons/marketing/marketing_view.xml
index 6467c516a49..88ef93916b6 100644
--- a/addons/marketing/marketing_view.xml
+++ b/addons/marketing/marketing_view.xml
@@ -5,7 +5,7 @@
         
         
 
          
diff --git a/addons/marketing_campaign/marketing_campaign_view.xml b/addons/marketing_campaign/marketing_campaign_view.xml
index 0faba94379f..6503cc482ab 100644
--- a/addons/marketing_campaign/marketing_campaign_view.xml
+++ b/addons/marketing_campaign/marketing_campaign_view.xml
@@ -16,8 +16,6 @@
     
     
 
-    
-
     
     
     
@@ -177,7 +175,7 @@
         
     
 
-    
+    
     
 
     
diff --git a/addons/mass_mailing/mass_mailing_view.xml b/addons/mass_mailing/mass_mailing_view.xml
index c9594179a72..e8848fba44f 100644
--- a/addons/mass_mailing/mass_mailing_view.xml
+++ b/addons/mass_mailing/mass_mailing_view.xml
@@ -358,7 +358,7 @@
         
 
         
-        
+        
 
         
         %s created.") % ( procurement.production_id.name,)
             self.message_post(cr, uid, [procurement.id], body=body, context=context)
-    
-
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py
index 3ec2850b4aa..3973bf6b131 100644
--- a/addons/portal/wizard/portal_wizard.py
+++ b/addons/portal/wizard/portal_wizard.py
@@ -23,7 +23,7 @@ import logging
 
 from openerp.osv import fields, osv
 from openerp.tools.translate import _
-from openerp.tools import email_re
+from openerp.tools import email_split
 from openerp import SUPERUSER_ID
 
 _logger = logging.getLogger(__name__)
@@ -53,8 +53,8 @@ http://www.openerp.com
 
 def extract_email(email):
     """ extract the email address from a user-friendly email address """
-    m = email_re.search(email or "")
-    return m and m.group(0) or ""
+    addresses = email_split(email)
+    return addresses[0] if addresses else ''
 
 
 
diff --git a/addons/portal_project/tests/test_access_rights.py b/addons/portal_project/tests/test_access_rights.py
index 1b98a140b68..f8e11db5f10 100644
--- a/addons/portal_project/tests/test_access_rights.py
+++ b/addons/portal_project/tests/test_access_rights.py
@@ -51,6 +51,12 @@ class TestPortalProjectBase(TestProjectBase):
             'alias_name': 'donovan',
             'groups_id': [(6, 0, [self.group_anonymous_id])]
         })
+        self.user_manager_id = self.res_users.create(cr, uid, {
+            'name': 'Eustache Manager',
+            'login': 'eustache',
+            'alias_name': 'eustache',
+            'groups_id': [(6, 0, [self.group_project_manager_id])]
+        })
 
         # Test 'Pigs' project
         self.project_pigs_id = self.project_project.create(cr, uid, {
@@ -220,7 +226,7 @@ class TestPortalProject(TestPortalProjectBase):
 
         # Data: subscribe Alfred, Chell and Donovan as follower
         self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
-        self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id, self.user_projectuser_id])
+        self.project_task.message_subscribe_users(cr, self.user_manager_id, [self.task_1_id, self.task_3_id], [self.user_portal_id, self.user_projectuser_id])
 
         # Do: Alfred reads project -> ok (follower ok followers)
         self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
diff --git a/addons/portal_project_issue/tests/test_access_rights.py b/addons/portal_project_issue/tests/test_access_rights.py
index a7721b489a0..b2ac1938f15 100644
--- a/addons/portal_project_issue/tests/test_access_rights.py
+++ b/addons/portal_project_issue/tests/test_access_rights.py
@@ -159,7 +159,7 @@ class TestPortalIssue(TestPortalProjectBase):
 
         # Data: subscribe Alfred, Chell and Donovan as follower
         self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
-        self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id, self.user_projectuser_id])
+        self.project_issue.message_subscribe_users(cr, self.user_manager_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id, self.user_projectuser_id])
 
         # Do: Alfred reads project -> ok (follower ok followers)
         # Test: followed + assigned issues visible
diff --git a/addons/project/project.py b/addons/project/project.py
index ec10dc515a0..f03a4242de3 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -46,9 +46,15 @@ class project_task_type(osv.osv):
                                'there are no records in that stage to display.'),
     }
 
+    def _get_default_project_ids(self, cr, uid, ctx={}):
+        project_id = self.pool['project.task']._get_default_project_id(cr, uid, context=ctx)
+        if project_id:
+            return [project_id]
+        return None
+
     _defaults = {
         'sequence': 1,
-        'project_ids': lambda self, cr, uid, ctx=None: self.pool['project.task']._get_default_project_id(cr, uid, context=ctx),
+        'project_ids': _get_default_project_ids,
     }
     _order = 'sequence'
 
@@ -64,7 +70,7 @@ class project(osv.osv):
         """ Installation hook: aliases, project.project """
         # create aliases for all projects and avoid constraint errors
         alias_context = dict(context, alias_model_name='project.task')
-        self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(project, self)._auto_init,
+        return self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(project, self)._auto_init,
             'project.task', self._columns['alias_id'], 'id', alias_prefix='project+', alias_defaults={'project_id':'id'}, context=alias_context)
 
     def search(self, cr, user, args, offset=0, limit=None, order=None, context=None, count=False):
diff --git a/addons/purchase/purchase.py b/addons/purchase/purchase.py
index 4c0bed5cae7..a44e022ba27 100644
--- a/addons/purchase/purchase.py
+++ b/addons/purchase/purchase.py
@@ -21,7 +21,7 @@
 
 import time
 import pytz
-from openerp import SUPERUSER_ID
+from openerp import SUPERUSER_ID, workflow
 from datetime import datetime
 from dateutil.relativedelta import relativedelta
 from operator import attrgetter
@@ -245,7 +245,7 @@ class purchase_order(osv.osv):
     _name = "purchase.order"
     _inherit = ['mail.thread', 'ir.needaction_mixin']
     _description = "Purchase Order"
-    _order = "name desc"
+    _order = 'date_order desc, id desc'
 
     def create(self, cr, uid, vals, context=None):
         if vals.get('name','/')=='/':
@@ -1290,6 +1290,7 @@ class account_invoice(osv.Model):
         po_ids = purchase_order_obj.search(cr, user_id, [('invoice_ids', 'in', ids)], context=context)
         for po_id in po_ids:
             purchase_order_obj.message_post(cr, user_id, po_id, body=_("Invoice received"), context=context)
+            workflow.trg_write(uid, 'purchase.order', po_id, cr)
         return res
 
     def confirm_paid(self, cr, uid, ids, context=None):
diff --git a/addons/purchase/purchase_workflow.xml b/addons/purchase/purchase_workflow.xml
index 68a6cd12733..ec4632c45a9 100644
--- a/addons/purchase/purchase_workflow.xml
+++ b/addons/purchase/purchase_workflow.xml
@@ -147,7 +147,7 @@
         
             
             
-            invoice_method<>'order' and invoiced
+            invoice_method<>'order'
         
         
             
@@ -200,6 +200,7 @@
         
             
             
+            invoiced
         
 
     
diff --git a/addons/sale/sale.py b/addons/sale/sale.py
index 6584d25bdad..fb247c2779b 100644
--- a/addons/sale/sale.py
+++ b/addons/sale/sale.py
@@ -238,7 +238,7 @@ class sale_order(osv.osv):
     _sql_constraints = [
         ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'),
     ]
-    _order = 'name desc'
+    _order = 'date_order desc, id desc'
 
     # Form filling
     def unlink(self, cr, uid, ids, context=None):
diff --git a/addons/sale_mrp/sale_mrp.py b/addons/sale_mrp/sale_mrp.py
index f043791d0d9..ffdf0384913 100644
--- a/addons/sale_mrp/sale_mrp.py
+++ b/addons/sale_mrp/sale_mrp.py
@@ -88,4 +88,10 @@ class mrp_production(osv.osv):
     }
 
 
-# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
+class sale_order(osv.Model):
+    _inherit ='sale.order'
+
+    def _prepare_order_line_procurement(self, cr, uid, order, line, move_id, date_planned, context=None):
+        result = super(sale_order, self)._prepare_order_line_procurement(cr, uid, order, line, move_id, date_planned, context)
+        result['property_ids'] = [(6, 0, [x.id for x in line.property_ids])]
+        return result
diff --git a/addons/sale_stock/stock.py b/addons/sale_stock/stock.py
index ccece846390..cfc8a8e634c 100644
--- a/addons/sale_stock/stock.py
+++ b/addons/sale_stock/stock.py
@@ -124,10 +124,22 @@ class stock_picking(osv.osv):
 
     def _invoice_hook(self, cursor, user, picking, invoice_id):
         sale_obj = self.pool.get('sale.order')
+        order_line_obj = self.pool.get('sale.order.line')
+        invoice_obj = self.pool.get('account.invoice')
+        invoice_line_obj = self.pool.get('account.invoice.line')
         if picking.sale_id:
             sale_obj.write(cursor, user, [picking.sale_id.id], {
                 'invoice_ids': [(4, invoice_id)],
-                })
+            })
+            for sale_line in picking.sale_id.order_line:
+                if sale_line.product_id.type == 'service' and not sale_line.invoiced:
+                    vals = order_line_obj._prepare_order_line_invoice_line(cursor, user, sale_line, False)
+                    vals['invoice_id'] = invoice_id
+                    invoice_line_id = invoice_line_obj.create(cursor, user, vals)
+                    order_line_obj.write(cursor, user, [sale_line.id], {
+                        'invoice_lines': [(6, 0, [invoice_line_id])],
+                    })
+                    invoice_obj.button_compute(cursor, user, [invoice_id])
         return super(stock_picking, self)._invoice_hook(cursor, user, picking, invoice_id)
     
     def action_done(self, cr, uid, ids, context=None):
diff --git a/addons/sale_stock/test/picking_order_policy.yml b/addons/sale_stock/test/picking_order_policy.yml
index 609cd405bad..83610bdd043 100644
--- a/addons/sale_stock/test/picking_order_policy.yml
+++ b/addons/sale_stock/test/picking_order_policy.yml
@@ -3,6 +3,16 @@
 -
   !context
     uid: 'res_sale_stock_salesman'
+-
+  Add SO line with service type product in SO to check flow which contain service type product in SO(BUG#1167330).
+-
+  !record {model: sale.order.line, id: sale_order_1}:
+    name: 'On Site Assistance'
+    product_id: product.product_product_2
+    product_uom_qty: 1.0
+    product_uom: 1
+    price_unit: 150.0
+    order_id: sale.sale_order_6
 -
   First I check the total amount of the Quotation before Approved.
 -
@@ -75,7 +85,7 @@
       assert picking.partner_id.id == sale_order.partner_shipping_id.id,"Shipping Address is not correspond with sale order."
       assert picking.note == sale_order.note,"Note is not correspond with sale order."
       assert picking.invoice_state == (sale_order.order_policy=='picking' and '2binvoiced') or 'none',"Invoice policy is not correspond with sale order."
-      assert len(picking.move_lines) == len(sale_order.order_line), "Total move of delivery order are not corresposning with total sale order lines."
+      assert len(picking.move_lines) == len(sale_order.order_line) - 1, "Total move of delivery order are not corresposning with total sale order lines."
       location_id = sale_order.warehouse_id.lot_stock_id.id
       output_id = sale_order.warehouse_id.lot_output_id.id
       for move in picking.move_lines:
diff --git a/addons/stock/wizard/stock_partial_picking.py b/addons/stock/wizard/stock_partial_picking.py
index 50ddc2b88ed..ceedb682ed8 100644
--- a/addons/stock/wizard/stock_partial_picking.py
+++ b/addons/stock/wizard/stock_partial_picking.py
@@ -144,7 +144,7 @@ class stock_partial_picking(osv.osv_memory):
     def _partial_move_for(self, cr, uid, move):
         partial_move = {
             'product_id' : move.product_id.id,
-            'quantity' : move.product_qty if move.state == 'assigned' else 0,
+            'quantity' : move.product_qty if move.state == 'assigned' or move.picking_id.type == 'in' else 0,
             'product_uom' : move.product_uom.id,
             'prodlot_id' : move.prodlot_id.id,
             'move_id' : move.id,