[MERGE] Forward-port of latest 7.0 bugfixes, up to rev. 9929 revid:dle@openerp.com...
authorDenis Ledoux <dle@openerp.com>
Tue, 25 Mar 2014 11:24:13 +0000 (12:24 +0100)
committerDenis Ledoux <dle@openerp.com>
Tue, 25 Mar 2014 11:24:13 +0000 (12:24 +0100)
bzr revid: dle@openerp.com-20140310133913-465x5t3n1bo7fu98
bzr revid: dle@openerp.com-20140311164146-2s599kogbqj8e94l
bzr revid: dle@openerp.com-20140312111149-u6y036aej3ywq7et
bzr revid: dle@openerp.com-20140312121145-06s1jvct9v4m7l6s
bzr revid: mat@openerp.com-20140313084409-c0kckxemy3in0mlo
bzr revid: chs@openerp.com-20140313163420-ifa4hyixj722d6jo
bzr revid: chs@openerp.com-20140314102034-g0nzryvwhrpzdas9
bzr revid: chs@openerp.com-20140314104803-19c5snl2fcuih91o
bzr revid: odo@openerp.com-20140314143519-x6rzcfkkqxwc0e1g
bzr revid: chs@openerp.com-20140318110229-y8098w3shdpmoig2
bzr revid: dle@openerp.com-20140318151824-sscl4m8p87emjl8s
bzr revid: dle@openerp.com-20140320112618-yfyihlwz66ryeqf5
bzr revid: dle@openerp.com-20140320161003-j01cc6sqves2wnp2
bzr revid: dle@openerp.com-20140320181252-4gqcog22p1ex0pz7
bzr revid: dle@openerp.com-20140321140450-8ulnhr76qybiadks
bzr revid: dle@openerp.com-20140324104743-ubvu8st7emq9pg1q
bzr revid: dle@openerp.com-20140325112413-xs5z7y81rhyjt0rd

21 files changed:
1  2 
addons/account/account.py
addons/account_check_writing/account_voucher.py
addons/audittrail/audittrail.py
addons/base_calendar/base_calendar.py
addons/base_import/models.py
addons/crm/crm_lead.py
addons/mail/mail_thread.py
addons/pad/pad.py
addons/point_of_sale/point_of_sale.py
addons/point_of_sale/security/point_of_sale_security.xml
addons/portal/mail_message.py
addons/project/project.py
addons/purchase/purchase.py
addons/purchase_requisition/purchase_requisition.py
addons/report_webkit/webkit_report.py
addons/sale/sale.py
addons/sale/wizard/sale_make_invoice.py
addons/share/wizard/share_wizard.py
addons/stock/stock.py
addons/stock/stock_view.xml
addons/survey/wizard/survey_send_invitation.py

@@@ -27,10 -27,10 +27,10 @@@ import tim
  
  import openerp
  from openerp import SUPERUSER_ID
 -from openerp import pooler, tools
 +from openerp import tools
  from openerp.osv import fields, osv, expression
  from openerp.tools.translate import _
- from openerp.tools.float_utils import float_round
+ from openerp.tools.float_utils import float_round as round
  
  import openerp.addons.decimal_precision as dp
  
@@@ -349,188 -413,123 +349,190 @@@ def get_data(cr, uid, pool, res_ids, mo
                      x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False
                      assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj))
                      x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id)
 -                    # the resource_ids that need to be checked are the sum of both old and previous values (because we
 -                    # need to log also creation or deletion in those lists).
 -                    x2m_old_values_ids = old_values.get(key, {'value': {}})['value'].get(field_name, [])
 -                    x2m_new_values_ids = new_values.get(key, {'value': {}})['value'].get(field_name, [])
 -                    # We use list(set(...)) to remove duplicates.
 -                    res_ids = list(set(x2m_old_values_ids + x2m_new_values_ids))
 +                    field_resource_ids = list(set(resource[field]))
                      if model.model == x2m_model.model:
                          # we need to remove current resource_id from the many2many to prevent an infinit loop
 -                        if resource_id in res_ids:
 -                            res_ids.remove(resource_id)
 -                    for res_id in res_ids:
 -                        lines.update(self.prepare_audittrail_log_line(cr, SUPERUSER_ID, pool, x2m_model, res_id, method, old_values, new_values, field_list))
 -            # if the value value is different than the old value: record the change
 -            if key not in old_values or key not in new_values or old_values[key]['value'][field_name] != new_values[key]['value'][field_name]:
 -                data = {
 -                      'name': field_name,
 -                      'new_value': key in new_values and new_values[key]['value'].get(field_name),
 -                      'old_value': key in old_values and old_values[key]['value'].get(field_name),
 -                      'new_value_text': key in new_values and new_values[key]['text'].get(field_name),
 -                      'old_value_text': key in old_values and old_values[key]['text'].get(field_name)
 -                }
 -                lines[key].append(data)
 -        return lines
 +                        if resource_id in field_resource_ids:
 +                            field_resource_ids.remove(resource_id)
 +                    data.update(get_data(cr, SUPERUSER_ID, pool, field_resource_ids, x2m_model, method))
  
 -    def process_data(self, cr, uid, pool, res_ids, model, method, old_values=None, new_values=None, field_list=None):
 -        """
 -        This function processes and iterates recursively to log the difference between the old
 -        data (i.e before the method was executed) and the new data and creates audittrail log
 -        accordingly.
 +        data[(model.id, resource_id)] = {'text':values_text, 'value': values}
 +    return data
  
 -        :param cr: the current row, from the database cursor,
 -        :param uid: the current user’s ID,
 -        :param pool: current db's pooler object.
 -        :param res_ids: Id's of resource to be logged/compared.
 -        :param model: model object which values are being changed
 -        :param method: method to log: create, read, unlink, write, actions, workflow actions
 -        :param old_values: dict of values read before execution of the method
 -        :param new_values: dict of values read after execution of the method
 -        :param field_list: optional argument containing the list of fields to log. Currently only
 -            used when performing a read, it could be usefull later on if we want to log the write
 -            on specific fields only.
 -        :return: True
 -        """
 -        if field_list is None:
 -            field_list = []
 -        # loop on all the given ids
 -        for res_id in res_ids:
 -            # compare old and new values and get audittrail log lines accordingly
 -            lines = self.prepare_audittrail_log_line(cr, uid, pool, model, res_id, method, old_values, new_values, field_list)
 -
 -            # if at least one modification has been found
 -            for model_id, resource_id in lines:
 -                line_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, model_id).model
 -
 -                vals = {
 -                    'method': method,
 -                    'object_id': model_id,
 -                    'user_id': uid,
 -                    'res_id': resource_id,
 -                }
 -                if (model_id, resource_id) not in old_values and method not in ('copy', 'read'):
 -                    # the resource was not existing so we are forcing the method to 'create'
 -                    # (because it could also come with the value 'write' if we are creating
 -                    #  new record through a one2many field)
 -                    vals.update({'method': 'create'})
 -                if (model_id, resource_id) not in new_values and method not in ('copy', 'read'):
 -                    # the resource is not existing anymore so we are forcing the method to 'unlink'
 -                    # (because it could also come with the value 'write' if we are deleting the
 -                    #  record through a one2many field)
 -                    name = old_values[(model_id, resource_id)]['value'].get('name',False)
 -                    vals.update({'method': 'unlink'})
 -                else :
 -                    name = pool[line_model].name_get(cr, uid, [resource_id])[0][1]
 -                vals.update({'name': name})
 -                # create the audittrail log in super admin mode, only if a change has been detected
 -                if lines[(model_id, resource_id)]:
 -                    log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals)
 -                    model = pool.get('ir.model').browse(cr, uid, model_id)
 -                    self.create_log_line(cr, SUPERUSER_ID, log_id, model, lines[(model_id, resource_id)])
 -        return True
 +def prepare_audittrail_log_line(cr, uid, pool, model, resource_id, method, old_values, new_values, field_list=None):
 +    """
 +    This function compares the old data (i.e before the method was executed) and the new data
 +    (after the method was executed) and returns a structure with all the needed information to
 +    log those differences.
 +
 +    :param cr: the current row, from the database cursor,
 +    :param uid: the current user’s ID. This parameter is currently not used as every
 +        operation to get data is made as super admin. Though, it could be usefull later.
 +    :param pool: current db's pooler object.
 +    :param model: model object which values are being changed
 +    :param resource_id: ID of record to which values are being changed
 +    :param method: method to log: create, read, unlink, write, actions, workflow actions
 +    :param old_values: dict of values read before execution of the method
 +    :param new_values: dict of values read after execution of the method
 +    :param field_list: optional argument containing the list of fields to log. Currently only
 +        used when performing a read, it could be usefull later on if we want to log the write
 +        on specific fields only.
 +
 +    :return: dictionary with
 +        * keys: tuples build as ID of model object to log and ID of resource to log
 +        * values: list of all the changes in field values for this couple (model, resource)
 +          return {
 +            (model.id, resource_id): []
 +          }
 +
 +    The reason why the structure returned is build as above is because when modifying an existing
 +    record, we may have to log a change done in a x2many field of that object
 +    """
 +    if field_list is None:
 +        field_list = []
 +    key = (model.id, resource_id)
 +    lines = {
 +        key: []
 +    }
 +    # loop on all the fields
 +    for field_name, field_definition in pool[model.model]._all_columns.items():
 +        if field_name in ('__last_update', 'id'):
 +            continue
 +        #if the field_list param is given, skip all the fields not in that list
 +        if field_list and field_name not in field_list:
 +            continue
 +        field_obj = field_definition.column
 +        if field_obj._type in ('one2many','many2many'):
 +            # checking if an audittrail rule apply in super admin mode
 +            if check_rules(cr, SUPERUSER_ID, field_obj._obj, method):
 +                # checking if the model associated to a *2m field exists, in super admin mode
 +                x2m_model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', field_obj._obj)])
 +                x2m_model_id = x2m_model_ids and x2m_model_ids[0] or False
 +                assert x2m_model_id, _("'%s' Model does not exist..." %(field_obj._obj))
 +                x2m_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, x2m_model_id)
 +                # the resource_ids that need to be checked are the sum of both old and previous values (because we
 +                # need to log also creation or deletion in those lists).
 +                x2m_old_values_ids = old_values.get(key, {'value': {}})['value'].get(field_name, [])
 +                x2m_new_values_ids = new_values.get(key, {'value': {}})['value'].get(field_name, [])
 +                # We use list(set(...)) to remove duplicates.
 +                res_ids = list(set(x2m_old_values_ids + x2m_new_values_ids))
 +                if model.model == x2m_model.model:
 +                    # we need to remove current resource_id from the many2many to prevent an infinit loop
 +                    if resource_id in res_ids:
 +                        res_ids.remove(resource_id)
 +                for res_id in res_ids:
 +                    lines.update(prepare_audittrail_log_line(cr, SUPERUSER_ID, pool, x2m_model, res_id, method, old_values, new_values, field_list))
 +        # if the value value is different than the old value: record the change
 +        if key not in old_values or key not in new_values or old_values[key]['value'][field_name] != new_values[key]['value'][field_name]:
 +            data = {
 +                  'name': field_name,
 +                  'new_value': key in new_values and new_values[key]['value'].get(field_name),
 +                  'old_value': key in old_values and old_values[key]['value'].get(field_name),
 +                  'new_value_text': key in new_values and new_values[key]['text'].get(field_name),
 +                  'old_value_text': key in old_values and old_values[key]['text'].get(field_name)
 +            }
 +            lines[key].append(data)
 +    return lines
  
 -    def check_rules(self, cr, uid, model, method):
 -        """
 -        Checks if auditrails is installed for that db and then if one rule match
 -        @param cr: the current row, from the database cursor,
 -        @param uid: the current user’s ID,
 -        @param model: value of _name of the object which values are being changed
 -        @param method: method to log: create, read, unlink,write,actions,workflow actions
 -        @return: True or False
 -        """
 -        pool = pooler.get_pool(cr.dbname)
 -        if 'audittrail.rule' in pool.models:
 -            model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', model)])
 -            model_id = model_ids and model_ids[0] or False
 -            if model_id:
 -                rule_ids = pool.get('audittrail.rule').search(cr, SUPERUSER_ID, [('object_id', '=', model_id), ('state', '=', 'subscribed')])
 -                for rule in pool.get('audittrail.rule').read(cr, SUPERUSER_ID, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']):
 -                    if len(rule['user_id']) == 0 or uid in rule['user_id']:
 -                        if rule.get('log_'+method,0):
 +def process_data(cr, uid, pool, res_ids, model, method, old_values=None, new_values=None, field_list=None):
 +    """
 +    This function processes and iterates recursively to log the difference between the old
 +    data (i.e before the method was executed) and the new data and creates audittrail log
 +    accordingly.
 +
 +    :param cr: the current row, from the database cursor,
 +    :param uid: the current user’s ID,
 +    :param pool: current db's pooler object.
 +    :param res_ids: Id's of resource to be logged/compared.
 +    :param model: model object which values are being changed
 +    :param method: method to log: create, read, unlink, write, actions, workflow actions
 +    :param old_values: dict of values read before execution of the method
 +    :param new_values: dict of values read after execution of the method
 +    :param field_list: optional argument containing the list of fields to log. Currently only
 +        used when performing a read, it could be usefull later on if we want to log the write
 +        on specific fields only.
 +    :return: True
 +    """
 +    if field_list is None:
 +        field_list = []
 +    # loop on all the given ids
 +    for res_id in res_ids:
 +        # compare old and new values and get audittrail log lines accordingly
 +        lines = prepare_audittrail_log_line(cr, uid, pool, model, res_id, method, old_values, new_values, field_list)
 +
 +        # if at least one modification has been found
 +        for model_id, resource_id in lines:
 +            line_model = pool.get('ir.model').browse(cr, SUPERUSER_ID, model_id).model
-             name = pool.get(line_model).name_get(cr, uid, [resource_id])[0][1]
 +
 +            vals = {
 +                'method': method,
 +                'object_id': model_id,
 +                'user_id': uid,
 +                'res_id': resource_id,
-                 'name': name,
 +            }
 +            if (model_id, resource_id) not in old_values and method not in ('copy', 'read'):
 +                # the resource was not existing so we are forcing the method to 'create'
 +                # (because it could also come with the value 'write' if we are creating
 +                #  new record through a one2many field)
 +                vals.update({'method': 'create'})
 +            if (model_id, resource_id) not in new_values and method not in ('copy', 'read'):
 +                # the resource is not existing anymore so we are forcing the method to 'unlink'
 +                # (because it could also come with the value 'write' if we are deleting the
 +                #  record through a one2many field)
++                name = old_values[(model_id, resource_id)]['value'].get('name',False)
 +                vals.update({'method': 'unlink'})
++            else :
++                name = pool[line_model].name_get(cr, uid, [resource_id])[0][1]
++            vals.update({'name': name})
 +            # create the audittrail log in super admin mode, only if a change has been detected
 +            if lines[(model_id, resource_id)]:
 +                log_id = pool.get('audittrail.log').create(cr, SUPERUSER_ID, vals)
 +                model = pool.get('ir.model').browse(cr, uid, model_id)
 +                create_log_line(cr, SUPERUSER_ID, log_id, model, lines[(model_id, resource_id)])
 +    return True
 +
 +def check_rules(cr, uid, model, method):
 +    """
 +    Checks if auditrails is installed for that db and then if one rule match
 +    @param cr: the current row, from the database cursor,
 +    @param uid: the current user’s ID,
 +    @param model: value of _name of the object which values are being changed
 +    @param method: method to log: create, read, unlink,write,actions,workflow actions
 +    @return: True or False
 +    """
 +    pool = openerp.registry(cr.dbname)
 +    if 'audittrail.rule' in pool.models:
 +        model_ids = pool.get('ir.model').search(cr, SUPERUSER_ID, [('model', '=', model)])
 +        model_id = model_ids and model_ids[0] or False
 +        if model_id:
 +            rule_ids = pool.get('audittrail.rule').search(cr, SUPERUSER_ID, [('object_id', '=', model_id), ('state', '=', 'subscribed')])
 +            for rule in pool.get('audittrail.rule').read(cr, SUPERUSER_ID, rule_ids, ['user_id','log_read','log_write','log_create','log_unlink','log_action','log_workflow']):
 +                if len(rule['user_id']) == 0 or uid in rule['user_id']:
 +                    if rule.get('log_'+method,0):
 +                        return True
 +                    elif method not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink', 'write', 'create', 'read_group', 'import_data'):
 +                        if rule['log_action']:
                              return True
 -                        elif method not in ('default_get','read','fields_view_get','fields_get','search','search_count','name_search','name_get','get','request_get', 'get_sc', 'unlink', 'write', 'create', 'read_group', 'import_data'):
 -                            if rule['log_action']:
 -                                return True
 -
 -    def execute_cr(self, cr, uid, model, method, *args, **kw):
 -        fct_src = super(audittrail_objects_proxy, self).execute_cr
 -        if self.check_rules(cr,uid,model,method):
 -            return self.log_fct(cr, uid, model, method, fct_src, *args, **kw)
 -        return fct_src(cr, uid, model, method, *args, **kw)
 -
 -    def exec_workflow_cr(self, cr, uid, model, method, *args, **kw):
 -        fct_src = super(audittrail_objects_proxy, self).exec_workflow_cr
 -        if self.check_rules(cr,uid,model,'workflow'):
 -            return self.log_fct(cr, uid, model, method, fct_src, *args, **kw)
 -        return fct_src(cr, uid, model, method, *args, **kw)
 -
 -audittrail_objects_proxy()
 +
 +# Replace the openerp.service.model functions.
 +
 +original_execute_cr = openerp.service.model.execute_cr
 +original_exec_workflow_cr = openerp.service.model.exec_workflow_cr
 +
 +def execute_cr(cr, uid, model, method, *args, **kw):
 +    fct_src = original_execute_cr
 +    if check_rules(cr,uid,model,method):
 +        return log_fct(cr, uid, model, method, fct_src, *args, **kw)
 +    return fct_src(cr, uid, model, method, *args, **kw)
 +
 +def exec_workflow_cr(cr, uid, model, method, *args, **kw):
 +    fct_src = original_exec_workflow_cr
 +    if check_rules(cr,uid,model,'workflow'):
 +        return log_fct(cr, uid, model, method, fct_src, *args, **kw)
 +    return fct_src(cr, uid, model, method, *args, **kw)
 +
 +openerp.service.model.execute_cr = execute_cr
 +openerp.service.model.exec_workflow_cr = exec_workflow_cr
  
  # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
  
Simple merge
Simple merge
@@@ -308,13 -332,13 +308,16 @@@ class crm_lead(format_address, osv.osv)
  
      def onchange_stage_id(self, cr, uid, ids, stage_id, context=None):
          if not stage_id:
 -            return {'value':{}}
 -        stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context)
 +            return {'value': {}}
 +        stage = self.pool.get('crm.case.stage').browse(cr, uid, stage_id, context=context)
          if not stage.on_change:
 -            return {'value':{}}
 -        return {'value':{'probability': stage.probability}}
 +            return {'value': {}}
-         return {'value': {'probability': stage.probability}}
++        vals = {'probability': stage.probability}
++        if stage.probability >= 100 or (stage.probability == 0 and stage.sequence > 1):
++                vals['date_closed'] = fields.datetime.now()
++        return {'value': vals}
  
 -    def on_change_partner(self, cr, uid, ids, partner_id, context=None):
 +    def on_change_partner_id(self, cr, uid, ids, partner_id, context=None):
          values = {}
          if partner_id:
              partner = self.pool.get('res.partner').browse(cr, uid, partner_id, context=context)
              return stage_ids[0]
          return False
  
 -    def case_cancel(self, cr, uid, ids, context=None):
 -        """ Overrides case_cancel from base_stage to set probability """
 -        res = super(crm_lead, self).case_cancel(cr, uid, ids, context=context)
 -        self.write(cr, uid, ids, {'probability' : 0.0}, context=context)
 -        return res
 -
 -    def case_reset(self, cr, uid, ids, context=None):
 -        """ Overrides case_reset from base_stage to set probability """
 -        res = super(crm_lead, self).case_reset(cr, uid, ids, context=context)
 -        self.write(cr, uid, ids, {'probability': 0.0}, context=context)
 -        return res
 -
      def case_mark_lost(self, cr, uid, ids, context=None):
 -        """ Mark the case as lost: state=cancel and probability=0 """
 -        for lead in self.browse(cr, uid, ids):
 -            stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0),('on_change','=',True)], context=context)
 +        """ Mark the case as lost: state=cancel and probability=0
 +            :deprecated: this method will be removed in OpenERP v8.
 +        """
 +        stages_leads = {}
 +        for lead in self.browse(cr, uid, ids, context=context):
 +            stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 0.0), ('fold', '=', True), ('sequence', '>', 1)], context=context)
              if stage_id:
 -                self.case_set(cr, uid, [lead.id], values_to_update={'probability': 0.0, 'date_closed': fields.datetime.now()}, new_stage_id=stage_id, context=context)
 +                if stages_leads.get(stage_id):
 +                    stages_leads[stage_id].append(lead.id)
 +                else:
 +                    stages_leads[stage_id] = [lead.id]
 +            else:
 +                raise osv.except_osv(_('Warning!'),
 +                    _('To relieve your sales pipe and group all Lost opportunities, configure one of your sales stage as follow:\n'
 +                        'probability = 0 %, select "Change Probability Automatically".\n'
 +                        'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.'))
 +        for stage_id, lead_ids in stages_leads.items():
-             self.write(cr, uid, lead_ids, {'stage_id': stage_id, 'date_closed': fields.datetime.now()}, context=context)
++            self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context)
          return True
  
      def case_mark_won(self, cr, uid, ids, context=None):
 -        """ Mark the case as won: state=done and probability=100 """
 -        for lead in self.browse(cr, uid, ids):
 -            stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0),('on_change','=',True)], context=context)
 +        """ Mark the case as won: state=done and probability=100
 +            :deprecated: this method will be removed in OpenERP v8.
 +        """
 +        stages_leads = {}
 +        for lead in self.browse(cr, uid, ids, context=context):
 +            stage_id = self.stage_find(cr, uid, [lead], lead.section_id.id or False, [('probability', '=', 100.0), ('fold', '=', True)], context=context)
              if stage_id:
 -                self.case_set(cr, uid, [lead.id], values_to_update={'probability': 100.0, 'date_closed': fields.datetime.now()}, new_stage_id=stage_id, context=context)
 +                if stages_leads.get(stage_id):
 +                    stages_leads[stage_id].append(lead.id)
 +                else:
 +                    stages_leads[stage_id] = [lead.id]
 +            else:
 +                raise osv.except_osv(_('Warning!'),
 +                    _('To relieve your sales pipe and group all Won opportunities, configure one of your sales stage as follow:\n'
 +                        'probability = 100 % and select "Change Probability Automatically".\n'
 +                        'Create a specific stage or edit an existing one by editing columns of your opportunity pipe.'))
 +        for stage_id, lead_ids in stages_leads.items():
-             self.write(cr, uid, lead_ids, {'stage_id': stage_id, 'date_closed': fields.datetime.now()}, context=context)
++            self.write(cr, uid, lead_ids, {'stage_id': stage_id}, context=context)
 +        return True
 +
 +    def case_escalate(self, cr, uid, ids, context=None):
 +        """ Escalates case to parent level """
 +        for case in self.browse(cr, uid, ids, context=context):
 +            data = {'active': True}
 +            if case.section_id.parent_id:
 +                data['section_id'] = case.section_id.parent_id.id
 +                if case.section_id.parent_id.change_responsible:
 +                    if case.section_id.parent_id.user_id:
 +                        data['user_id'] = case.section_id.parent_id.user_id.id
 +            else:
 +                raise osv.except_osv(_('Error!'), _("You are already at the top level of your sales-team category.\nTherefore you cannot escalate furthermore."))
 +            self.write(cr, uid, [case.id], data, context=context)
          return True
  
 -    def set_priority(self, cr, uid, ids, priority):
 +    def set_priority(self, cr, uid, ids, priority, context=None):
          """ Set lead priority
          """
 -        return self.write(cr, uid, ids, {'priority' : priority})
 +        return self.write(cr, uid, ids, {'priority': priority}, context=context)
  
      def set_high_priority(self, cr, uid, ids, context=None):
          """ Set lead priority to high
@@@ -156,22 -111,10 +156,23 @@@ 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'] = "<span class='oe_kanban_mail_new' title='%s'><span class='oe_e'>9</span> %d %s</span>" % (title, res[id].pop('message_unread_count'), _("New"))
+             res[id].pop('message_unread_count', None)
          return res
  
 -    def _get_subscription_data(self, cr, uid, ids, name, args, context=None):
 +    def read_followers_data(self, cr, uid, follower_ids, context=None):
 +        result = []
 +        technical_group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_no_one')
 +        for follower in self.pool.get('res.partner').browse(cr, uid, follower_ids, context=context):
 +            is_editable = uid in map(lambda x: x.id, technical_group.users)
 +            is_uid = uid in map(lambda x: x.id, follower.user_ids)
 +            data = (follower.id,
 +                    follower.name,
 +                    {'is_editable': is_editable, 'is_uid': is_uid},
 +                    )
 +            result.append(data)
 +        return result
 +
 +    def _get_subscription_data(self, cr, uid, ids, name, args, user_pid=None, context=None):
          """ Computes:
              - message_subtype_data: data about document subtypes: which are
                  available, which are followed if any """
Simple merge
@@@ -497,14 -488,15 +497,18 @@@ class pos_order(osv.osv)
      _description = "Point of Sale"
      _order = "id desc"
  
-     def create_from_ui(self, cr, uid, orders, context=None):
-         #_logger.info("orders: %r", orders)
+     def create_from_ui(self, cr, uid, orders, context=None):      
+         # Keep only new orders
+         submitted_references = [o['data']['name'] for o in orders]
+         existing_orders = self.search_read(cr, uid, domain=[('pos_reference', 'in', submitted_references)], fields=['pos_reference'], context=context)
+         existing_references = set([o['pos_reference'] for o in existing_orders])
+         orders_to_save = [o for o in orders if o['data']['name'] not in existing_references]
          order_ids = []
-         for tmp_order in orders:
+         for tmp_order in orders_to_save:
 +            to_invoice = tmp_order['to_invoice']
              order = tmp_order['data']
 +
 +
              order_id = self.create(cr, uid, {
                  'name': order['name'],
                  'user_id': order['user_id'] or False,
          <field name="global" eval="True" />
          <field name="domain_force">[('company_id', '=', user.company_id.id)]</field>
      </record>
+     <record id="rule_pos_config_multi_company" model="ir.rule">
+         <field name="name">Point Of Sale Config</field>
+         <field name="model_id" ref="model_pos_config" />
+         <field name="global" eval="True" />
 -        <field name="domain_force">['|',('shop_id.company_id','=',False),('shop_id.company_id','child_of',[user.company_id.id])]</field>
++        <field name="domain_force">[('warehouse_id.company_id','child_of',[user.company_id.id])]</field>
+     </record>
  </data>
  </openerp>
@@@ -39,10 -39,10 +39,10 @@@ class mail_message(osv.Model)
          group_ids = self.pool.get('res.users').browse(cr, uid, uid, context=context).groups_id
          group_user_id = self.pool.get("ir.model.data").get_object_reference(cr, uid, 'base', 'group_user')[1]
          if group_user_id not in [group.id for group in group_ids]:
 -            args = ['&', '|', ('type', '!=', 'comment'), ('subtype_id', '!=', False)] + list(args)
 +            args = [('subtype_id', '!=', False)] + list(args)
  
          return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
-             context=context, count=False, access_rights_uid=access_rights_uid)
+             context=context, count=count, access_rights_uid=access_rights_uid)
  
      def check_access_rule(self, cr, uid, ids, operation, context=None):
          """ Add Access rules of mail.message for non-employee user:
Simple merge
Simple merge
@@@ -83,35 -58,10 +83,36 @@@ def mako_template(text)
  
      This template uses UTF-8 encoding
      """
 -    tmp_lookup  = TemplateLookup() #we need it in order to allow inclusion and inheritance
 -    return Template(text, input_encoding='utf-8', output_encoding='utf-8', lookup=tmp_lookup)
 +
 +    return mako_template_env.from_string(text)
 +
 +_extender_functions = {}
 +
 +def webkit_report_extender(report_name):
 +    """
 +    A decorator to define functions to extend the context used in a template rendering.
 +    report_name must be the xml id of the desired report (it is mandatory to indicate the
 +    module in that xml id).
 +
 +    The given function will be called at the creation of the report. The following arguments
 +    will be passed to it (in this order):
 +    - pool The model pool.
 +    - cr The cursor.
 +    - uid The user id.
 +    - localcontext The context given to the template engine to render the templates for the
 +        current report. This is the context that should be modified.
 +    - context The OpenERP context.
 +    """
 +    def fct1(fct):
 +        lst = _extender_functions.get(report_name)
 +        if not lst:
 +            lst = []
 +            _extender_functions[report_name] = lst
 +        lst.append(fct)
 +        return fct
 +    return fct1
  
  class WebKitParser(report_sxw):
      """Custom class that use webkit to render HTML reports
         Code partially taken from report openoffice. Thanks guys :)
                                   ),
                                  'w'
                              )
-             head_file.write(header.encode('utf-8'))
 -            head_file.write(self._sanitize_html(header))
++            head_file.write(self._sanitize_html(header.encode('utf-8')))
              head_file.close()
              file_to_del.append(head_file.name)
              command.extend(['--header-html', head_file.name])
                                   ),
                                  'w'
                              )
-             foot_file.write(footer.encode('utf-8'))
 -            foot_file.write(self._sanitize_html(footer))
++            foot_file.write(self._sanitize_html(footer.encode('utf-8')))
              foot_file.close()
              file_to_del.append(foot_file.name)
              command.extend(['--footer-html', foot_file.name])
          for html in html_list :
              html_file = file(os.path.join(tmp_dir, str(time.time()) + str(count) +'.body.html'), 'w')
              count += 1
-             html_file.write(html.encode('utf-8'))
 -            html_file.write(self._sanitize_html(html))
++            html_file.write(self._sanitize_html(html.encode('utf-8')))
              html_file.close()
              file_to_del.append(html_file.name)
              command.append(html_file.name)
Simple merge
@@@ -55,11 -57,13 +55,12 @@@ class sale_make_invoice(osv.osv_memory)
                  raise osv.except_osv(_('Warning!'), _("You shouldn't manually invoice the following sale order %s") % (sale_order.name))
  
          order_obj.action_invoice_create(cr, uid, context.get(('active_ids'), []), data['grouped'], date_invoice=data['invoice_date'])
-         for o in order_obj.browse(cr, uid, context.get(('active_ids'), []), context=context):
+         orders = order_obj.browse(cr, uid, context.get(('active_ids'), []), context=context)
+         for o in orders:
              for i in o.invoice_ids:
                  newinv.append(i.id)
+         # Dummy call to workflow, will not create another invoice but bind the new invoice to the subflow
 -        for id in [o.id for o in orders if o.order_policy == 'manual']:
 -            wf_service.trg_validate(uid, 'sale.order', id, 'manual_invoice', cr)
++        order_obj.signal_manual_invoice(cr, uid, [o.id for o in orders if o.order_policy == 'manual'])
          result = mod_obj.get_object_reference(cr, uid, 'account', 'action_invoice_tree1')
          id = result and result[1] or False
          result = act_obj.read(cr, uid, [id], context=context)[0]
Simple merge
@@@ -2232,17 -2245,19 +2232,17 @@@ class stock_move(osv.osv)
                  if move.picking_id:
                      pickings.add(move.picking_id.id)
              if move.move_dest_id and move.move_dest_id.state == 'waiting':
-                 self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'})
+                 self.write(cr, uid, [move.move_dest_id.id], {'state': 'confirmed'}, context=context)
                  if context.get('call_unlink',False) and move.move_dest_id.picking_id:
 -                    wf_service = netsvc.LocalService("workflow")
 -                    wf_service.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)
 +                    workflow.trg_write(uid, 'stock.picking', move.move_dest_id.picking_id.id, cr)
-         self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False})
+         self.write(cr, uid, ids, {'state': 'cancel', 'move_dest_id': False}, context=context)
          if not context.get('call_unlink',False):
              for pick in self.pool.get('stock.picking').browse(cr, uid, list(pickings), context=context):
                  if all(move.state == 'cancel' for move in pick.move_lines):
-                     self.pool.get('stock.picking').write(cr, uid, [pick.id], {'state': 'cancel'})
+                     self.pool.get('stock.picking').write(cr, uid, [pick.id], {'state': 'cancel'}, context=context)
  
 -        wf_service = netsvc.LocalService("workflow")
          for id in ids:
 -            wf_service.trg_trigger(uid, 'stock.move', id, cr)
 +            workflow.trg_trigger(uid, 'stock.move', id, cr)
          return True
  
      def _get_accounting_data_for_valuation(self, cr, uid, move, context=None):
Simple merge
@@@ -128,17 -127,10 +128,8 @@@ Thanks,''') % (name, self.pool.get('ir.
              for use in exist_user:
                  new_user.append(use.id)
          for id in survey_ref.browse(cr, uid, survey_ids):
-             report = self.create_report(cr, uid, [id.id], 'report.survey.form', id.title)
-             file = open(get_module_resource('survey', 'report') + id.title +".pdf")
-             file_data = ""
-             while 1:
-                 line = file.readline()
-                 file_data += line
-                 if not line:
-                     break
-             file.close()
-             attachments[id.title +".pdf"] = file_data
-             os.remove(get_module_resource('survey', 'report') + id.title +".pdf")
 -            service = netsvc.LocalService('report.survey.form');
 -            (result, format) = service.create(cr, uid, [id.id], {}, {})
 -            
++            result, format = openerp.report.render_report(cr, uid, [id.id], 'survey.form', {}, {})
+             attachments[id.title +".pdf"] = result
  
          for partner in self.pool.get('res.partner').browse(cr, uid, partner_ids):
              if not partner.email: