← Back to team overview

openerp-dev-web team mailing list archive

[Merge] lp:~openerp-dev/openobject-addons/trunl-merge-with-openerp into lp:openobject-addons

 

tfr (Openerp) has proposed merging lp:~openerp-dev/openobject-addons/trunl-merge-with-openerp into lp:openobject-addons.

Requested reviews:
  OpenERP Core Team (openerp)

For more details, see:
https://code.launchpad.net/~openerp-dev/openobject-addons/trunl-merge-with-openerp/+merge/57156
-- 
https://code.launchpad.net/~openerp-dev/openobject-addons/trunl-merge-with-openerp/+merge/57156
Your team OpenERP R&D Team is subscribed to branch lp:~openerp-dev/openobject-addons/trunl-merge-with-openerp.
=== modified file 'analytic/analytic.py'
--- analytic/analytic.py	2011-03-24 11:12:12 +0000
+++ analytic/analytic.py	2011-04-11 12:48:30 +0000
@@ -205,7 +205,7 @@
     def check_recursion(self, cr, uid, ids, parent=None):
         return super(account_analytic_account, self)._check_recursion(cr, uid, ids, parent=parent)
 
-    _order = 'date_start desc,parent_id desc,code'
+    _order = 'name asc'
     _constraints = [
         (check_recursion, 'Error! You can not create recursive analytic accounts.', ['parent_id']),
     ]

=== modified file 'crm/crm_lead.py'
--- crm/crm_lead.py	2011-03-09 06:21:48 +0000
+++ crm/crm_lead.py	2011-04-11 12:48:30 +0000
@@ -98,6 +98,27 @@
                 res[lead.id][field] = abs(int(duration))
         return res
 
+    def _history_search(self, cr, uid, obj, name, args, context=None):
+        res = []
+        msg_obj = self.pool.get('mailgate.message')
+        message_ids = msg_obj.search(cr, uid, [('history','=',True), ('name', args[0][1], args[0][2])], context=context)
+        lead_ids = self.search(cr, uid, [('message_ids', 'in', message_ids)], context=context)
+
+        if lead_ids:
+            return [('id', 'in', lead_ids)]
+        else:
+            return [('id', '=', '0')]
+
+    def _get_email_subject(self, cr, uid, ids, fields, args, context=None):
+        res = {}
+        for obj in self.browse(cr, uid, ids, context=context):
+            res[obj.id] = ''
+            for msg in obj.message_ids:
+                if msg.history:
+                    res[obj.id] = msg.name
+                    break
+        return res
+
     _columns = {
         # Overridden from res.partner.address:
         'partner_id': fields.many2one('res.partner', 'Partner', ondelete='set null',
@@ -149,6 +170,7 @@
                                   \nWhen the case is over, the state is set to \'Done\'.\
                                   \nIf the case needs to be reviewed then the state is set to \'Pending\'.'),
         'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
+        'subjects': fields.function(_get_email_subject, fnct_search=_history_search, string='Subject of Email', method=True, type='char', size=64),
     }
 
 
@@ -289,17 +311,6 @@
                 self.log(cr, uid, case.id, message)
         return super(crm_lead,self).write(cr, uid, ids, vals, context)
 
-    def stage_historize(self, cr, uid, ids, stage, context=None):
-        stage_obj = self.pool.get('crm.case.stage').browse(cr, uid, stage, context=context)
-        self.history(cr, uid, ids, _('Stage'), details=stage_obj.name)
-        for case in self.browse(cr, uid, ids, context=context):
-            if case.type == 'lead':
-                message = _("The stage of lead '%s' has been changed to '%s'.") % (case.name, stage_obj.name)
-            elif case.type == 'opportunity':
-                message = _("The stage of opportunity '%s' has been changed to '%s'.") % (case.name, stage_obj.name)
-            self.log(cr, uid, case.id, message)
-        return True
-
     def stage_next(self, cr, uid, ids, context=None):
         stage = super(crm_lead, self).stage_next(cr, uid, ids, context=context)
         if stage:

=== modified file 'crm/crm_lead_view.xml'
--- crm/crm_lead_view.xml	2011-03-04 12:53:34 +0000
+++ crm/crm_lead_view.xml	2011-04-11 12:48:30 +0000
@@ -183,7 +183,7 @@
                        <field colspan="4" name="email_cc" widget="char" size="512"/>
                    </group>
                     <field name="message_ids" colspan="4" nolabel="1" mode="tree,form">
-                        <tree string="Communication history">
+                        <tree string="History">
                             <field name="display_text" string="History Information"/>
                             <field name="history" invisible="1"/>
                             <button
@@ -192,34 +192,33 @@
                                 context="{'mail':'reply', 'model': 'crm.lead', 'include_original' : True}"
                                 icon="terp-mail-replied" type="action" />
                         </tree>
-                        <form string="Communication history">
-                            <group col="4" colspan="4">
-                                <field name="email_from"/>
-                                <field name="date"/>
-                                <field name="email_to" widget="char" size="512"/>
-                                <field name="email_cc" widget="char" size="512"/>
-                                <field name="name" colspan="4" widget="char" size="512"/>
-                                <field name="history" invisible="1"/>
-                            </group>
-                            <notebook colspan="4">
-                                <page string="Details">
-                                    <group attrs="{'invisible': [('history', '!=', True)]}">
-                                        <field name="description" colspan="4" nolabel="1" height="250"/>
-                                        <button colspan="4"
-                                            string="Reply"
-                                            name="%(crm.action_crm_send_mail)d"
-                                            context="{'mail':'reply', 'model': 'crm.lead', 'include_original' : True}"
-                                            icon="terp-mail-replied" type="action" />
-                                    </group>
-                                    <group attrs="{'invisible': [('history', '=', True)]}">
-                                        <field name="display_text" colspan="4" nolabel="1"  height="250"/>
-                                    </group>
-                                </page>
-                                <page string="Attachments">
-                                    <field name="attachment_ids" colspan="4" readonly="1" nolabel="1"/>
-                                </page>
-                            </notebook>
-                        </form>
+                            <form string="History">
+                                <group col="4" colspan="4">
+                                    <field name="email_from"/>
+                                    <field name="date"/>
+                                    <field name="email_to" size="512"/>
+                                    <field name="email_cc" size="512"/>
+                                    <field name="name" colspan="4" widget="char" attrs="{'invisible': [('history', '=', False)]}" size="512"/>
+                                    <field name="display_text" colspan="4"   attrs="{'invisible': [('history', '=', True)]}"/>
+                                    <field name="history" invisible="1"/>
+                                </group>
+                                <notebook colspan="4">
+                                    <page string="Details">
+                                        <field name="description" colspan="4" nolabel="1"/>
+                                        <group attrs="{'invisible': [('history', '!=', True)]}">
+                                            <button colspan="4"
+                                                    string="Reply"
+                                                    name="%(crm.action_crm_send_mail)d"
+                                                    context="{'mail':'reply', 'model': 'crm.lead', 'include_original' : True}"
+                                                    icon="terp-mail-replied" type="action" />
+                                            </group>
+
+                                    </page>
+                                    <page string="Attachments">
+                                        <field name="attachment_ids" colspan="4" readonly="1" nolabel="1"/>
+                                    </page>
+                                    </notebook>
+                                </form>
                     </field>
                     <button string="Add Internal Note"
                         name="%(crm.action_crm_add_note)d"
@@ -255,6 +254,7 @@
                 <field name="type_id" invisible="1"/>
                 <field name="referred" invisible="1"/>
                 <field name="channel_id" invisible="1"/>
+                <field name="subjects" invisible="1"/>
 
                 <field name="stage_id"/>
                 <button name="stage_previous" string="Previous Stage"
@@ -335,6 +335,7 @@
                         domain="[('user_id','=', False)]"
                         help="Unassigned Leads" />
                 </field>
+                <field name="subjects"/>
                 <field name="section_id" widget="selection"
                     context="{'invisible_section': False}">
                     <filter icon="terp-personal+" groups="base.group_extended"

=== modified file 'crm/crm_opportunity_view.xml'
--- crm/crm_opportunity_view.xml	2011-03-04 12:53:34 +0000
+++ crm/crm_opportunity_view.xml	2011-04-11 12:48:30 +0000
@@ -156,8 +156,8 @@
                                         <field name="date"/>
                                         <field name="email_to" size="512"/>
                                         <field name="email_cc" size="512"/>
-                                        <field name="name" colspan="4" attrs="{'invisible': [('history', '=', True)]}"/>
-                                        <field name="display_text" colspan="4"   attrs="{'invisible': [('history', '=', False)]}"/>
+                                        <field name="name" colspan="4" widget="char" attrs="{'invisible': [('history', '=', False)]}" size="512"/>
+                                        <field name="display_text" colspan="4"   attrs="{'invisible': [('history', '=', True)]}"/>
                                         <field name="history" invisible="1"/>
                                     </group>
                                     <notebook colspan="4">
@@ -230,6 +230,7 @@
                         <field name="stage_id"/>
                         <field name="channel_id" invisible="1"/>
                         <field name="type_id" invisible="1"/>
+                        <field name="subjects" invisible="1"/>
                         <button name="stage_previous" string="Previous Stage"
                             states="open,pending" type="object" icon="gtk-go-back" />
                         <button name="stage_next" string="Next Stage"
@@ -309,6 +310,7 @@
                                 domain="[]"
                                 help="Show Sales Team"/>
                         </field>
+                        <field name="subjects"/>
                         <newline/>
                         <group  expand="0" string="Extended Filters..." groups="base.group_extended">
                               <field name="stage_id" widget="selection" domain="[('type', '=', 'opportunity')]"/>

=== modified file 'crm/wizard/crm_lead_to_opportunity.py'
--- crm/wizard/crm_lead_to_opportunity.py	2011-02-22 16:24:03 +0000
+++ crm/wizard/crm_lead_to_opportunity.py	2011-04-11 12:48:30 +0000
@@ -21,6 +21,8 @@
 
 from osv import osv, fields
 from tools.translate import _
+import tools
+import re
 
 import time
 
@@ -46,21 +48,35 @@
         """
         lead_obj = self.pool.get('crm.lead')
 
-
         res = super(crm_lead2opportunity_partner, self).default_get(cr, uid, fields, context=context)
         opportunities = res.get('opportunity_ids') or []
-
         partner_id = False
+        email = False
         for lead in lead_obj.browse(cr, uid, opportunities, context=context):
             partner_id = lead.partner_id and lead.partner_id.id or False
+            email = re.findall(r'([^ ,<@]+@[^> ,]+)', lead.email_from or '')
+            email = map(lambda x: "'" + x + "'", email)
 
         if not partner_id and res.get('partner_id'):
             partner_id = res.get('partner_id')
 
         ids = []
         if partner_id:
-            ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), ('type', '=', 'opportunity')])
-            opportunities += ids
+            ids = lead_obj.search(cr, uid, [('partner_id', '=', partner_id), ('type', '=', 'opportunity'), '!', ('state', 'in', ['done', 'cancel'])])
+            if ids:
+                opportunities.append(ids[0])
+                
+                
+        if not partner_id:
+            label = False
+            opp_ids = []
+            if email:
+                # Find email of existing opportunity matches the email_from of the lead
+                cr.execute("""select id from crm_lead where type='opportunity' and
+                                substring(email_from from '([^ ,<@]+@[^> ,]+)') in (%s)""" % (','.join(email)))
+                ids = map(lambda x:x[0], cr.fetchall())
+            if ids:
+                opportunities.append(ids[0])
 
         if 'action' in fields:
             res.update({'action' : partner_id and 'exist' or 'create'})
@@ -82,7 +98,6 @@
         @param uid: the current user’s ID for security checks,
         @param fields: List of fields for default value
         @param context: A standard dictionary for contextual values
-
         """
         if context is None:
             context = {}
@@ -90,13 +105,14 @@
 
         for lead in lead_obj.browse(cr, uid, context.get('active_ids', []), context=context):
             if lead.state in ['done', 'cancel']:
-                raise osv.except_osv(_("Warning !"), _("Closed/Cancelled \
-Leads Could not convert into Opportunity"))
+                raise osv.except_osv(_("Warning !"), _("Closed/Cancelled Leads Could not convert into Opportunity"))
         return False
 
     def _convert(self, cr, uid, ids, lead, partner_id, stage_ids, context=None):
         leads = self.pool.get('crm.lead')
-        address_id = self.pool.get('res.partner.address').search(cr, uid,
+        address_id = False
+        if partner_id:
+            address_id = self.pool.get('res.partner.address').search(cr, uid,
                                                                  [('partner_id', '=', partner_id)],
                                                                  order='create_date desc',
                                                                  limit=1)
@@ -110,8 +126,10 @@
             'stage_id': stage_ids and stage_ids[0] or False,
             'date_action': time.strftime('%Y-%m-%d %H:%M:%S'),
         }
-        if address_id:
+        if partner_id and address_id:
             vals['partner_address_id'] = address_id[0]
+        else:
+            vals['partner_address_id'] = False
 
         lead.write(vals, context=context)
         leads.history(cr, uid, [lead], _('Converted to opportunity'), details='Converted to Opportunity', context=context)
@@ -122,6 +140,18 @@
                     }, context=context)
             leads.log(cr, uid, lead.id, _("Lead '%s' has been converted to an opportunity.") % lead.name)
 
+    def send_mail_to_salesman(self, lead):
+        email_to = lead.user_id and lead.user_id.user_email
+        if not email_to:
+            return
+        email_from = lead.section_id and lead.section_id.user_id and lead.section_id.user_id.user_email or email_to
+        partner = lead.partner_id and lead.partner_id.name or lead.partner_name 
+        subject = "lead %s converted into opportunity" % lead.name
+        body = "Info \n Id : %s \n Subject: %s \n Partner: %s \n Description : %s " % (lead.id, lead.name, lead.partner_id.name, lead.description)  
+        try :
+            tools.email_send(email_from, [email_to], subject, body)
+        except:
+            pass
 
     def action_apply(self, cr, uid, ids, context=None):
         """
@@ -130,6 +160,9 @@
 
         @return : View dictionary opening the Opportunity form view
         """
+        if not context:
+            context = {}
+
         record_id = context and context.get('active_ids') or False
         if not record_id:
             return {'type': 'ir.actions.act_window_close'}
@@ -154,22 +187,30 @@
                 cr, uid, opportunity_view_tree, context=context).res_id
 
         for lead in leads.browse(cr, uid, record_id, context=context):
-            if(lead.section_id):
+            if lead.section_id:
                 stage_ids = self.pool.get('crm.case.stage').search(cr, uid, [('type','=','opportunity'),('sequence','>=',1), ('section_ids','=', lead.section_id.id)])
             else:
                 stage_ids = self.pool.get('crm.case.stage').search(cr, uid, [('type','=','opportunity'),('sequence','>=',1)])
 
             data = self.browse(cr, uid, ids[0], context=context)
-            partner_ids = []
+
+
             if data.action == 'create':
+                partner_ids = []
                 partner_ids = self._create_partner(cr, uid, ids, context=context)
+                partner_id = partner_ids and partner_ids[0]
+            elif data.action == 'exist':
+                partner_id = data.partner_id and data.partner_id.id
+            else:
+                partner_id = False
 
-            partner_id = partner_ids and partner_ids[0] or data.partner_id.id
             self._convert(cr, uid, ids, lead, partner_id, stage_ids, context=context)
-            if data.name == 'merge':
+            self.send_mail_to_salesman(lead)
+            #If we convert in mass, don't merge if there is no other opportunity but no warning
+            if data.name == 'merge' and (len(data.opportunity_ids) > 1 or not context.get('mass_convert') ):
                 merge_obj = self.pool.get('crm.merge.opportunity')
                 self.write(cr, uid, ids, {'opportunity_ids' : [(6,0, [data.opportunity_ids[0].id])]}, context=context)
-                context.update({'lead_ids' : record_id})
+                context.update({'lead_ids' : record_id, "convert" : True})
                 return merge_obj.merge(cr, uid, data.opportunity_ids, context=context)
 
         return {
@@ -189,4 +230,66 @@
 
 crm_lead2opportunity_partner()
 
+class crm_lead2opportunity_mass_convert(osv.osv_memory):
+    _name = 'crm.lead2opportunity.partner.mass'
+    _description = 'Mass Lead To Opportunity Partner'
+    _inherit = 'crm.lead2opportunity.partner'
+
+
+    _columns = {
+            'user_ids':  fields.many2many('res.users', 'mass_convert_rel', 'user_id', 'wizard_id', 'Salesmans'),
+            'section_id': fields.many2one('crm.case.section', 'Sales Team'),
+
+    }
+
+    def mass_convert(self, cr, uid, ids, context=None):
+        lead_obj = self.pool.get('crm.lead')
+        if not context:
+            context = {}
+
+        active_ids = context.get('active_ids')
+        data = self.browse(cr, uid, ids, context=context)[0]
+
+        salesteam = data.section_id and data.section_id.id
+        if data.user_ids:
+            salesmans = map(lambda x : x.id, data.user_ids)
+            index = 0
+        else:
+            salesmans = False
+
+        for lead_id in active_ids:
+            value = {}
+            if salesteam:
+                value['section_id'] = salesteam
+            if salesmans:
+                value['user_id'] = salesmans[index]
+                index += 1
+                index = index < len(salesmans) and index or 0
+            if value:
+                lead_obj.write(cr, uid, [lead_id], value, context=context)
+
+            context['active_ids'] = [lead_id]
+            value = self.default_get(cr, uid, ['partner_id', 'opportunity_ids'], context=context)
+            value['opportunity_ids'] = [(6, 0, value['opportunity_ids'])]
+            self.write(cr, uid, ids, value, context=context)
+
+            self.action_apply(cr, uid, ids, context=context)
+
+
+
+        models_data = self.pool.get('ir.model.data')
+        result = models_data._get_id(cr, uid, 'crm', 'view_crm_case_opportunities_filter')
+        opportunity_view_search = models_data.browse(cr, uid, result, context=context).res_id
+
+        return {
+            'name': _('Opportunity'),
+            'view_type': 'form',
+            'view_mode': 'tree,form',
+            'res_model': 'crm.lead',
+            'domain': [('type', '=', 'opportunity'), ('id', 'in', active_ids)],
+            'type': 'ir.actions.act_window',
+            'search_view_id': opportunity_view_search,
+        }
+
+crm_lead2opportunity_mass_convert()
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== modified file 'crm/wizard/crm_lead_to_opportunity_view.xml'
--- crm/wizard/crm_lead_to_opportunity_view.xml	2011-02-09 16:42:37 +0000
+++ crm/wizard/crm_lead_to_opportunity_view.xml	2011-04-11 12:48:30 +0000
@@ -36,5 +36,42 @@
           </field>
        </record> 
 
+		<record id="view_crm_lead2opportunity_partner_mass" model="ir.ui.view">
+	          <field name="name">crm.lead2opportunity.partner.mass.form</field>
+	          <field name="model">crm.lead2opportunity.partner.mass</field>
+	          <field name="type">form</field>
+	          <field name="arch" type="xml">
+	               <form string="Convert to Opportunity">
+	                   
+	                        <field name="action"/>
+	                        <field name="name" colspan="4"/>
+	                        
+	                        <separator string="Assigned opportunities to" colspan="4" />
+							<field name="section_id" />
+							<group col="4" colspan="4">
+								<separator string="Select Salesman" colspan="4" />
+							</group>
+							<field name="user_ids" nolabel="1" colspan="4">
+								<tree>
+									<field name="name" />
+								</tree>
+							</field>
+	                        <separator string="" colspan="4" />
+	                        <group col="4" colspan="4">
+	                            <button special="cancel" string="Cancel" icon="gtk-cancel"/>
+	                            <button name="mass_convert" string="Convert into Opportunities" type="object" icon="gtk-ok"/>
+	                        </group>
+	                  
+	              </form>
+	          </field>
+	       </record>
+	       
+	      <act_window id="action_crm_send_mass_convert"
+            multi="True"
+            key2="client_action_multi" name="Convert opportunities"
+            res_model="crm.lead2opportunity.partner.mass" src_model="crm.lead"
+            view_mode="form" target="new" view_type="form"
+            context="{'mass_convert' : True}"
+            view_id="view_crm_lead2opportunity_partner_mass"/> 
      </data>
 </openerp>

=== modified file 'crm/wizard/crm_lead_to_partner.py'
--- crm/wizard/crm_lead_to_partner.py	2011-03-01 11:34:15 +0000
+++ crm/wizard/crm_lead_to_partner.py	2011-04-11 12:48:30 +0000
@@ -71,9 +71,8 @@
         contact_obj = self.pool.get('res.partner.address')
         partner_id = False
 
-        data = context and context.get('active_ids', []) or []
+        data = list(context and context.get('active_ids', []) or [])
         res = super(crm_lead2partner, self).default_get(cr, uid, fields, context=context)
-
         for lead in lead_obj.browse(cr, uid, data, context=context):
             partner_ids = []
             # Find partner address matches the email_from of the lead
@@ -85,17 +84,15 @@
                                 substring(email from '([^ ,<@]+@[^> ,]+)') in (%s)""" % (','.join(email)))
                 address_ids = map(lambda x: x[0], cr.fetchall())
                 if address_ids:
-                    addresses = contact_obj.browse(cr, uid, address_ids)
-                    partner_ids = addresses and [addresses[0].partner_id.id] or False
-
+                    partner_ids = partner_obj.search(cr, uid, [('address', 'in', address_ids)], context=context)
+                    
             # Find partner name that matches the name of the lead
             if not partner_ids and lead.partner_name:
                 partner_ids = partner_obj.search(cr, uid, [('name', '=', lead.partner_name)], context=context)
-            if not partner_ids:
-                cr.execute("""SELECT p.id from res_partner p
-                            where regexp_replace(lower(p.name), '[^a-z]*', '', 'g') = regexp_replace(%s, '[^a-z]*', '', 'g')""", (lead.name.lower(), ))
-                partner_ids = map(lambda x: x[0], cr.fetchall())
+                
             partner_id = partner_ids and partner_ids[0] or False
+            
+            
 
             if 'partner_id' in fields:
                 res.update({'partner_id': partner_id})

=== modified file 'crm/wizard/crm_merge_opportunities.py'
--- crm/wizard/crm_merge_opportunities.py	2011-02-24 15:03:09 +0000
+++ crm/wizard/crm_merge_opportunities.py	2011-04-11 12:48:30 +0000
@@ -27,13 +27,19 @@
     _name = 'crm.merge.opportunity'
     _description = 'Merge two Opportunities'
 
-    def _get_first_not_null_id(self, attr, ops):
+    def _get_first_not_null_id(self, attr, ops, oldest):
+        if hasattr(oldest, attr) and getattr(oldest, attr):
+            return getattr(oldest, attr).id
+        
         for op in ops:
             if hasattr(op, attr) and getattr(op, attr):
                 return getattr(op, attr).id
         return False
 
-    def _get_first_not_null(self, attr, ops):
+    def _get_first_not_null(self, attr, ops, oldest):
+        if hasattr(oldest, attr) and getattr(oldest, attr):
+            return getattr(oldest, attr)
+        
         for op in ops:
             if hasattr(op, attr) and getattr(op, attr):
                 return getattr(op, attr)
@@ -54,6 +60,16 @@
         attach_obj.write(cr, uid, attach_ids, {'res_id' : op_id})
 
 
+    def find_oldest(self, cr, uid, op_ids, context=None):
+        if not context:
+            context = {}
+        ids = [op_id.id for op_id in op_ids]
+        if context.get('convert'):
+            ids = list(set(ids) - set(context.get('lead_ids', False)) )
+        lead_obj = self.pool.get('crm.lead')
+        op_id = lead_obj.search(cr, uid, [('id', 'in', ids)], order='create_date' , context=context)
+        opps = lead_obj.browse(cr, uid, [op_id[0]], context=context)
+        return opps[0]
 
     def merge(self, cr, uid, op_ids, context=None):
         """
@@ -62,61 +78,68 @@
         opp_obj = self.pool.get('crm.lead')
         message_obj = self.pool.get('mailgate.message')
 
-        lead_ids = context and context.pop('lead_ids', []) or []
-
+        lead_ids = context and context.get('lead_ids', []) or []
 
         if len(op_ids) <= 1:
             raise osv.except_osv(_('Warning !'),_('Please select more than one opportunities.'))
 
         opportunities = opp_obj.browse(cr, uid, lead_ids, context=context)
         opportunities_list = list(set(op_ids) - set(opportunities))
-
+        oldest_opp = self.find_oldest(cr, uid, op_ids, context=context)
         if opportunities :
             first_opportunity = opportunities[0]
             tail_opportunities = opportunities_list
         else:
             first_opportunity = opportunities_list[0]
             tail_opportunities = opportunities_list[1:]
-
+            
 
         data = {
-                'partner_id': self._get_first_not_null_id('partner_id', op_ids),  # !!
-                'title': self._get_first_not_null_id('title', op_ids),
-                'name' : self._concat_all('name', op_ids),  #not lost
-                'categ_id' : self._get_first_not_null_id('categ_id', op_ids), # !!
-                'channel_id' : self._get_first_not_null_id('channel_id', op_ids), # !!
-                'city' : self._get_first_not_null('city', op_ids),  # !!
-                'company_id' : self._get_first_not_null_id('company_id', op_ids), #!!
-                'contact_name' : self._concat_all('contact_name', op_ids), #not lost
-                'country_id' : self._get_first_not_null_id('country_id', op_ids), #!!
-                'partner_address_id' : self._get_first_not_null_id('partner_address_id', op_ids), #!!
-                'partner_assigned_id' : hasattr(opp_obj,'partner_assigned_id') and self._get_first_not_null_id('partner_assigned_id', op_ids), #!!
-                'type_id' : self._get_first_not_null_id('type_id', op_ids), #!!
-                'user_id' : self._get_first_not_null_id('user_id', op_ids), #!!
-                'section_id' : self._get_first_not_null_id('section_id', op_ids), #!!
-                'state_id' : self._get_first_not_null_id('state_id', op_ids),
+                'partner_id': self._get_first_not_null_id('partner_id', op_ids, oldest_opp),  # !!
+                'title': self._get_first_not_null_id('title', op_ids, oldest_opp),
+                'name' : self._get_first_not_null('name', op_ids, oldest_opp),  #not lost
+                'categ_id' : self._get_first_not_null_id('categ_id', op_ids, oldest_opp), # !!
+                'channel_id' : self._get_first_not_null_id('channel_id', op_ids, oldest_opp), # !!
+                'city' : self._get_first_not_null('city', op_ids, oldest_opp),  # !!
+                'company_id' : self._get_first_not_null_id('company_id', op_ids, oldest_opp), #!!
+                'contact_name' : self._get_first_not_null('contact_name', op_ids, oldest_opp), #not lost
+                'country_id' : self._get_first_not_null_id('country_id', op_ids, oldest_opp), #!!
+                'partner_address_id' : self._get_first_not_null_id('partner_address_id', op_ids, oldest_opp), #!!
+                'partner_assigned_id' : hasattr(opp_obj,'partner_assigned_id') and self._get_first_not_null_id('partner_assigned_id', op_ids, oldest_opp), #!!
+                'type_id' : self._get_first_not_null_id('type_id', op_ids, oldest_opp), #!!
+                'user_id' : self._get_first_not_null_id('user_id', op_ids, oldest_opp), #!!
+                'section_id' : self._get_first_not_null_id('section_id', op_ids, oldest_opp), #!!
+                'state_id' : self._get_first_not_null_id('state_id', op_ids, oldest_opp),
                 'description' : self._concat_all('description', op_ids),  #not lost
-                'email' : self._get_first_not_null('email', op_ids), # !!
-                'fax' : self._get_first_not_null('fax', op_ids),
-                'mobile' : self._get_first_not_null('mobile', op_ids),
-                'partner_latitude' : hasattr(opp_obj,'partner_latitude') and self._get_first_not_null('partner_latitude', op_ids),
-                'partner_longitude' : hasattr(opp_obj,'partner_longitude') and self._get_first_not_null('partner_longitude', op_ids),
-                'partner_name' : self._get_first_not_null('partner_name', op_ids),
-                'phone' : self._get_first_not_null('phone', op_ids),
-                'probability' : self._get_first_not_null('probability', op_ids),
-                'planned_revenue' : self._get_first_not_null('planned_revenue', op_ids),
-                'street' : self._get_first_not_null('street', op_ids),
-                'street2' : self._get_first_not_null('street2', op_ids),
-                'zip' : self._get_first_not_null('zip', op_ids),
+                'email' : self._get_first_not_null('email', op_ids, oldest_opp), # !!
+                'fax' : self._get_first_not_null('fax', op_ids, oldest_opp),
+                'mobile' : self._get_first_not_null('mobile', op_ids, oldest_opp),
+                'partner_latitude' : hasattr(opp_obj,'partner_latitude') and self._get_first_not_null('partner_latitude', op_ids, oldest_opp),
+                'partner_longitude' : hasattr(opp_obj,'partner_longitude') and self._get_first_not_null('partner_longitude', op_ids, oldest_opp),
+                'partner_name' : self._get_first_not_null('partner_name', op_ids, oldest_opp),
+                'phone' : self._get_first_not_null('phone', op_ids, oldest_opp),
+                'probability' : self._get_first_not_null('probability', op_ids, oldest_opp),
+                'planned_revenue' : self._get_first_not_null('planned_revenue', op_ids, oldest_opp),
+                'street' : self._get_first_not_null('street', op_ids, oldest_opp),
+                'street2' : self._get_first_not_null('street2', op_ids, oldest_opp),
+                'zip' : self._get_first_not_null('zip', op_ids, oldest_opp),
+                'state' : 'open',
+                'create_date' : self._get_first_not_null('create_date', op_ids, oldest_opp),
+                'date_action_last': self._get_first_not_null('date_action_last', op_ids, oldest_opp),
+                'date_action_next': self._get_first_not_null('date_action_nexte', op_ids, oldest_opp),
+                'email_from' : self._get_first_not_null('email_from', op_ids, oldest_opp),
+                'email_cc' : self._get_first_not_null('email_cc', op_ids, oldest_opp),
+                'partner_name' : self._get_first_not_null('partner_name', op_ids, oldest_opp),
 
             }
-
+        
         #copy message into the first opportunity + merge attachement
-        for opp in tail_opportunities:
+        
+        for opp in tail_opportunities + [first_opportunity]:
             attach_ids = self.get_attachments(cr, uid, opp, context=context)
             self.set_attachements_res_id(cr, uid, first_opportunity.id, attach_ids)
             for history in opp.message_ids:
-                new_history = message_obj.copy(cr, uid, history.id, default={'res_id': opp.id})
+                new_history = message_obj.write(cr, uid, history.id, {'res_id': first_opportunity.id, 'name' : _("From %s : %s") % (opp.name, history.name) }, context=context)
 
         #Notification about loss of information
         details = []
@@ -214,8 +237,13 @@
         res = super(crm_merge_opportunity, self).default_get(cr, uid, fields, context=context)
 
         if record_ids:
+            opp_ids = []
+            opps = self.pool.get('crm.lead').browse(cr, uid, record_ids, context=context)
+            for opp in opps:
+                if opp.state not in ('done', 'cancel'):
+                    opp_ids.append(opp.id)
             if 'opportunity_ids' in fields:
-                res.update({'opportunity_ids': record_ids})
+                res.update({'opportunity_ids': opp_ids})
 
         return res
 

=== modified file 'crm/wizard/crm_send_email.py'
--- crm/wizard/crm_send_email.py	2011-02-28 11:08:04 +0000
+++ crm/wizard/crm_send_email.py	2011-04-11 12:48:30 +0000
@@ -57,9 +57,25 @@
         'body': fields.text('Message Body', required=True),
         'state': fields.selection(AVAILABLE_STATES, string='Set New State To', required=True),
         'attachment_ids' : fields.one2many('crm.send.mail.attachment', 'wizard_id', 'Attachment'),
-        'html': fields.boolean('HTML formatting?', help="Select this if you want to send email with HTML formatting."), 
+        'html': fields.boolean('HTML formatting?', help="Select this if you want to send email with HTML formatting."),
     }
 
+    def action_mass_send(self, cr, uid, ids, context=None):
+
+        if not context:
+            context = {}
+
+        context.update({'mail' : 'new'})
+        actives_ids = context.get('active_ids')
+        print "mass_mail", context.get('mass_mail')
+        model = context.get('active_model')
+        case_pool = self.pool.get(model)
+        for id in actives_ids:
+            context.update({'active_id' : id})
+            self.action_send(cr, uid, ids, context=context)
+
+        return {'type': 'ir.actions.act_window_close'}
+
     def action_send(self, cr, uid, ids, context=None):
         """ This sends an email to ALL the addresses of the selected partners.
         """
@@ -75,6 +91,8 @@
         case_pool = self.pool.get(model)
         res_id = context and context.get('active_id', False) or False
 
+
+
         for obj in self.browse(cr, uid, ids, context=context):
             attach = [
                 (x.name, base64.decodestring(x.binary)) for x in obj.attachment_ids
@@ -110,8 +128,15 @@
                 res_id = hist.res_id
                 ref_id = hist.ref_id
                 case = case_pool.browse(cr, uid, res_id, context=context)
-            emails = re.findall(r'([^ ,<@]+@[^> ,]+)', obj.email_to or '')
+
+            if context.get('mass_mail'):
+                email_temp = case.email_from and tools.ustr(case.email_from) or ''
+                emails = re.findall(r'([^ ,<@]+@[^> ,]+)', email_temp)
+            else:
+                emails = re.findall(r'([^ ,<@]+@[^> ,]+)', obj.email_to or '')
+
             email_cc = re.findall(r'([^ ,<@]+@[^> ,]+)', obj.email_cc or '')
+
             emails = filter(None, emails)
             body = obj.body
 
@@ -180,10 +205,11 @@
 
         user_obj = self.pool.get('res.users')
         user_mail_from = user_obj._get_email_from(cr, uid, [uid], context=context)[uid]
-
         for case in mod_obj.browse(cr, uid, res_id, context=context):
             if 'email_to' in fields:
                 res.update({'email_to': case.email_from and tools.ustr(case.email_from) or ''})
+                if context.get('mass_mail'):
+                    res.update({'email_to': ''})
             if 'email_from' in fields:
                 res.update({'email_from': user_mail_from and tools.ustr(user_mail_from) or ''})
             if 'reply_to' in fields:
@@ -191,8 +217,12 @@
                     res.update({'reply_to': case.section_id and case.section_id.reply_to or False})
             if 'subject' in fields:
                 res.update({'subject': tools.ustr(context.get('subject', case.name) or '')})
+                if context.get('mass_mail'):
+                    res.update({'subject': ''})
             if 'email_cc' in fields:
                 res.update({'email_cc': tools.ustr(case.email_cc or '')})
+                if context.get('mass_mail'):
+                    res.update({'email_cc': ''})
             if 'body' in fields:
                 res.update({'body': u'\n'+(tools.ustr(case.user_id.signature or ''))})
             if 'state' in fields:

=== modified file 'crm/wizard/crm_send_email_view.xml'
--- crm/wizard/crm_send_email_view.xml	2011-01-31 08:44:19 +0000
+++ crm/wizard/crm_send_email_view.xml	2011-04-11 12:48:30 +0000
@@ -44,6 +44,45 @@
             </field>
         </record>
 
+        <record model="ir.ui.view" id="crm_send_new_mass_mail_view">
+            <field name="name">crm.new.send.mass.mail.form</field>
+            <field name="model">crm.send.mail</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Send Mail" col="4">
+                    <group colspan="4" col="2">
+                    <field name="email_from"/>
+                    <field name="reply_to"/>
+                    <field name="email_cc"/>
+                    <field name="subject" />
+                    <field name="html"/>
+                    </group>
+                    <notebook colspan="6">
+                        <page string="Message">
+                            <field name="body" nolabel="1" colspan="4" default_focus="1"/>
+                        </page>
+                        <page string="Attachments">
+                            <field name="attachment_ids" colspan="4" nolabel="1">
+                                <form string="Attachment">
+                                    <field name="binary" filename="name" />
+                                    <field name="name" />
+                                </form>
+                                <tree string="Attachments">
+                                    <field name="name" />
+                                </tree>
+                            </field>
+                        </page>
+                    </notebook>
+                    <separator string="" colspan="6"/>
+                    <group colspan="6" col="4" >
+                        <field name="state" />
+                        <button string="_Cancel" icon="gtk-cancel" special="cancel" />
+                        <button name="action_mass_send" type="object" string="_Send to All" icon="gtk-go-forward" />
+                    </group>
+                </form>
+            </field>
+        </record>
+
 <!-- Send New Mail action -->
 
         <record model="ir.actions.act_window" id="action_crm_send_mail">
@@ -56,6 +95,14 @@
         </record>
 
 
+        <act_window id="action_crm_send_mass_mail"
+            multi="True"
+            key2="client_action_multi" name="Send emails"
+            res_model="crm.send.mail" src_model="crm.lead"
+            view_mode="form" target="new" view_type="form"
+            context="{'mass_mail' : True}"
+            view_id="crm_send_new_mass_mail_view"/>
+
     <!-- Reply to Mail view -->
 
         <record model="ir.ui.view" id="crm_reply_mail_view">
@@ -89,6 +136,9 @@
             </field>
         </record>
 
+
+
+
 <!-- Reply to Mail action -->
 
         <record model="ir.actions.act_window" id="action_crm_reply_mail">

=== modified file 'crm_partner_assign/__openerp__.py'
--- crm_partner_assign/__openerp__.py	2011-03-25 10:24:13 +0000
+++ crm_partner_assign/__openerp__.py	2011-04-11 12:48:30 +0000
@@ -43,6 +43,7 @@
         'wizard/crm_forward_to_partner_view.xml',
         'crm_lead_view.xml',
         'report/crm_lead_report_view.xml',
+        'report/crm_partner_report_view.xml',
     ],
     'test': ['test/test_crm_partner_assign.yml'],
     'installable': True,

=== modified file 'crm_partner_assign/partner_geo_assign.py'
--- crm_partner_assign/partner_geo_assign.py	2011-02-24 11:21:25 +0000
+++ crm_partner_assign/partner_geo_assign.py	2011-04-11 12:48:30 +0000
@@ -142,21 +142,6 @@
                         ('country', '=', part.country_id.id),
                     ], context=context)
 
-                # 3. third way: other countries, small area
-                if not part_ids:
-                    part_ids = self.pool.get('res.partner').search(cr, uid, [
-                        ('partner_weight','>',0),
-                        ('partner_latitude','>',result[0]-2), ('partner_latitude','<',result[0]+2),
-                        ('partner_longitude','>',result[1]-1.5), ('partner_longitude','<',result[1]+1.5)
-                    ], context=context)
-
-                # 4. fourth way: other countries, big area
-                if not part_ids:
-                    part_ids = self.pool.get('res.partner').search(cr, uid, [
-                        ('partner_weight','>',0),
-                        ('partner_latitude','>',result[0]-4), ('partner_latitude','<',result[0]+4),
-                        ('partner_longitude','>',result[1]-3), ('partner_longitude','<',result[1]+3)
-                    ], context=context)
 
                 # 5. fifth way: anywhere in same country
                 if not part_ids:

=== modified file 'crm_partner_assign/report/__init__.py'
--- crm_partner_assign/report/__init__.py	2011-01-14 00:11:01 +0000
+++ crm_partner_assign/report/__init__.py	2011-04-11 12:48:30 +0000
@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 ##############################################################################
-#    
+#
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
 #
@@ -15,9 +15,10 @@
 #    GNU Affero General Public License for more details.
 #
 #    You should have received a copy of the GNU Affero General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
 
 import crm_lead_report
+import crm_partner_report
 

=== modified file 'crm_partner_assign/report/crm_lead_report.py'
--- crm_partner_assign/report/crm_lead_report.py	2011-01-14 00:11:01 +0000
+++ crm_partner_assign/report/crm_lead_report.py	2011-04-11 12:48:30 +0000
@@ -37,7 +37,7 @@
     _auto = False
     _description = "CRM Lead Report"
     _columns = {
-        'name': fields.char('Year', size=64, required=False, readonly=True),
+        'year': fields.char('Year', size=64, required=False, readonly=True),
         'partner_assigned_id':fields.many2one('res.partner', 'Partner', readonly=True),
         'grade_id':fields.many2one('res.partner.grade', 'Grade', readonly=True),
         'user_id':fields.many2one('res.users', 'User', readonly=True),
@@ -51,7 +51,7 @@
                                   ('09', 'September'), ('10', 'October'),\
                                   ('11', 'November'), ('12', 'December')], 'Month', readonly=True),
         'company_id': fields.many2one('res.company', 'Company', readonly=True),
-        'partner_date': fields.date('Partner Date', readonly=True),
+        'date_assign': fields.date('Partner Date', readonly=True),
         'create_date': fields.datetime('Create Date', readonly=True),
         'day': fields.char('Day', size=128, readonly=True),
         'delay_open': fields.float('Delay to Open',digits=(16,2),readonly=True, group_operator="avg",help="Number of Days to open the case"),
@@ -82,18 +82,20 @@
             CRM Lead Report
             @param cr: the current row, from the database cursor
         """
+        print "WHATTT "
         tools.drop_view_if_exists(cr, 'crm_lead_report_assign')
         cr.execute("""
             CREATE OR REPLACE VIEW crm_lead_report_assign AS (
                 SELECT
                     c.id,
-                    to_char(c.create_date, 'YYYY') as name,
-                    to_char(c.create_date, 'MM') as month,
-                    to_char(c.create_date, 'YYYY-MM-DD') as day,
+                    to_char(c.date_assign, 'YYYY') as year,
+                    to_char(c.date_assign, 'MM') as month,
+                    to_char(c.date_assign, 'YYYY-MM-DD') as day,
                     to_char(c.create_date, 'YYYY-MM-DD') as creation_date,
                     to_char(c.date_open, 'YYYY-MM-DD') as opening_date,
                     to_char(c.date_closed, 'YYYY-mm-dd') as date_closed,
                     c.state,
+                    c.date_assign,
                     c.user_id,
                     c.probability,
                     c.probability as probability_max,

=== modified file 'crm_partner_assign/report/crm_lead_report_view.xml'
--- crm_partner_assign/report/crm_lead_report_view.xml	2011-01-14 00:11:01 +0000
+++ crm_partner_assign/report/crm_lead_report_view.xml	2011-04-11 12:48:30 +0000
@@ -69,17 +69,17 @@
                             domain="[]"
                             context="{'group_by':'company_id'}" />
                         <separator orientation="vertical" />
-                        <filter string="Partner Date" icon="terp-go-today"
+                        <filter string="Assign Date" icon="terp-go-today"
                             domain="[]"
                             name="group_partner_date"
-                            context="{'group_by':'partner_date'}"/>
+                            context="{'group_by':'date_assign'}"/>
                         <separator orientation="vertical"/>
                         <filter string="Day" icon="terp-go-today"
                             domain="[]" context="{'group_by':'day'}"/>
                         <filter string="Month" icon="terp-go-month"
                             domain="[]" context="{'group_by':'month'}" />
                         <filter string="Year" icon="terp-go-year"
-                            domain="[]" context="{'group_by':'name'}" />
+                            domain="[]" context="{'group_by':'year'}" />
                   </group>
 
                </search>
@@ -107,9 +107,9 @@
             <field name="type">tree</field>
             <field name="arch" type="xml">
             <tree string="Opportunities Assignment Analysis">
-                <field name="name" invisible="1"/>
+                <field name="year" invisible="1"/>
                 <field name="month" invisible="1"/>
-                <field name="partner_date" invisible="1"/>
+                <field name="date_assign" invisible="1"/>
                 <field name="section_id" invisible="1" groups="base.group_extended"/>
                 <field name="user_id" invisible="1"/>
                 <field name="grade_id" invisible="1" widget="selection"/>

=== added file 'crm_partner_assign/report/crm_partner_report.py'
--- crm_partner_assign/report/crm_partner_report.py	1970-01-01 00:00:00 +0000
+++ crm_partner_assign/report/crm_partner_report.py	2011-04-11 12:48:30 +0000
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+#    OpenERP, Open Source Management Solution
+#    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU Affero General Public License as
+#    published by the Free Software Foundation, either version 3 of the
+#    License, or (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU Affero General Public License for more details.
+#
+#    You should have received a copy of the GNU Affero General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+##############################################################################
+from osv import fields,osv
+import tools
+
+
+class crm_partner_report_assign(osv.osv):
+    """ CRM Lead Report """
+    _name = "crm.partner.report.assign"
+    _auto = False
+    _description = "CRM Partner Report"
+    _columns = {
+        'name': fields.char('Partner name', size=64, required=False, readonly=True),
+        'grade_id':fields.many2one('res.partner.grade', 'Grade', readonly=True),
+        'user_id':fields.many2one('res.users', 'User', readonly=True),
+        'country_id':fields.many2one('res.country', 'Country', readonly=True),
+        'section_id':fields.many2one('crm.case.section', 'Sales Team', readonly=True),
+        'nbr': fields.integer('# of Partner', readonly=True),
+        'opp': fields.integer('# of Opportunity', readonly=True),
+    }
+
+    def init(self, cr):
+
+        """
+            CRM Lead Report
+            @param cr: the current row, from the database cursor
+        """
+        tools.drop_view_if_exists(cr, 'crm_partner_report_assign')
+        cr.execute("""
+            CREATE OR REPLACE VIEW crm_partner_report_assign AS (
+                SELECT
+                    p.id,
+                    p.name,
+                    (SELECT country_id FROM res_partner_address a WHERE a.partner_id=p.id AND country_id is not null limit 1) as country_id,
+                    p.grade_id,
+                    p.user_id,
+                    p.section_id,
+                    1 as nbr,
+                    (SELECT count(id) FROM crm_lead WHERE partner_assigned_id=p.id) AS opp
+                FROM
+                    res_partner p
+
+            )""")
+
+crm_partner_report_assign()

=== added file 'crm_partner_assign/report/crm_partner_report_view.xml'
--- crm_partner_assign/report/crm_partner_report_view.xml	1970-01-01 00:00:00 +0000
+++ crm_partner_assign/report/crm_partner_report_view.xml	2011-04-11 12:48:30 +0000
@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="utf-8"?>
+<openerp>
+    <data>
+
+        <!--     Opportunity tree view  -->
+        <record id="view_report_crm_partner_assign_filter" model="ir.ui.view">
+            <field name="name">crm.partner.report.assign.select</field>
+            <field name="model">crm.partner.report.assign</field>
+            <field name="type">search</field>
+            <field name="arch" type="xml">
+                <search string="Partner assigned Analysis">
+                    <group col="20" colspan="8">
+                        <field name="country_id" />
+                        <field name="grade_id"/>
+                        <field name="user_id"/>
+                        <field name="section_id"/>
+                   </group>
+                   <newline/>
+                   <group  expand="1" string="Group By...">
+                       <filter string="Salesman" name="user" icon="terp-personal"
+                            domain="[]" context="{'group_by':'user_id'}" />
+                       <filter string="Country" icon="terp-go-home" name="group_country" context="{'group_by':'country_id'}" />
+                        <separator orientation="vertical" />
+                        <filter string="Section" icon="terp-personal+"
+                            domain="[]"
+                            context="{'group_by':'section_id'}" />
+                        <filter string="Grade" name="group_grade" icon="terp-stock_symbol-selection"
+                            domain="[]" context="{'group_by':'grade_id'}" />
+                        <filter string="Name" name="" icon="terp-stock_symbol-selection"
+                            domain="[]" context="{'group_by':'name'}" />
+                        <separator orientation="vertical" />
+                  </group>
+               </search>
+            </field>
+        </record>
+
+<!-- Crm Lead Assign report Graph View -->
+
+       <record id="view_report_crm_partner_assign_tree" model="ir.ui.view">
+            <field name="name">crm.partner.assign.report.tree</field>
+            <field name="model">crm.partner.report.assign</field>
+            <field name="type">tree</field>
+            <field name="arch" type="xml">
+            <tree string="Opportunities Assignment Analysis">
+                <field name="name" invisible="1"/>
+                <field name="country_id" invisible="1"/>
+                <field name="grade_id" invisible="1"/>
+                <field name="section_id" invisible="1" groups="base.group_extended"/>
+                <field name="user_id" invisible="1"/>
+                <field name="nbr" string="#Partner" sum="#Partner"/>
+                <field name="opp"/>
+             </tree>
+            </field>
+       </record>
+
+       <!-- Leads by user and section Action -->
+
+       <record id="action_report_crm_partner_assign" model="ir.actions.act_window">
+            <field name="name">Partnership Analysis</field>
+            <field name="res_model">crm.partner.report.assign</field>
+            <field name="context">{'search_default_group_country': 1, 'search_default_group_grade': 1, 'group_by_no_leaf':1,'group_by':[]}</field>
+            <field name="view_mode">tree</field>
+            <field name="domain">[('grade_id', '!=', False)]</field>
+        </record>
+
+       <menuitem id="menu_report_crm_partner_assign_tree"
+           groups="base.group_extended"
+           parent="base.next_id_64" action="action_report_crm_partner_assign" sequence="5"/>
+
+    </data>
+</openerp>

=== modified file 'crm_partner_assign/security/ir.model.access.csv'
--- crm_partner_assign/security/ir.model.access.csv	2010-12-31 06:26:50 +0000
+++ crm_partner_assign/security/ir.model.access.csv	2011-04-11 12:48:30 +0000
@@ -1,5 +1,6 @@
 "id","name","model_id:id","group_id:id","perm_read","perm_write","perm_create","perm_unlink"
-"access_ crm_lead_report_assign"," crm.lead.report.assign","model_crm_lead_report_assign","base.group_sale_salesman",1,1,1,0
+"access_ crm_lead_report_assign","crm.lead.report.assign","model_crm_lead_report_assign","base.group_sale_salesman",1,1,1,0
 "access_ crm_lead_report_assign_all","crm.lead.report.assign.all","model_crm_lead_report_assign","base.group_user",1,0,0,0
+"access_crm_partner_report","crm.partner.report.assign.all","model_crm_partner_report_assign","base.group_sale_salesman",1,0,0,0
 "access_res_partner_grade","res.partner.grade","model_res_partner_grade","base.group_sale_salesman",1,1,1,0
 "access_res_partner_grade_manager","res.partner.grade.manager","model_res_partner_grade","base.group_sale_manager",1,1,1,1

=== modified file 'crm_partner_assign/wizard/crm_forward_to_partner.py'
--- crm_partner_assign/wizard/crm_forward_to_partner.py	2011-02-24 15:03:09 +0000
+++ crm_partner_assign/wizard/crm_forward_to_partner.py	2011-04-11 12:48:30 +0000
@@ -44,9 +44,10 @@
     _defaults = {
         'name' : 'email',
         'history': 'latest',
-        'email_from': lambda self, cr, uid, *a: self.pool.get('res.users')._get_email_from(cr, uid, uid)[uid]
+        'email_from': lambda self, cr, uid, *a: self.pool.get('res.users')._get_email_from(cr, uid, uid)[uid],
     }
 
+
     def get_whole_history(self, cr, uid, ids, context=None):
         """This function gets whole communication history and returns as top posting style
         @param self: The object pointer
@@ -87,7 +88,7 @@
         @param uid: the current user’s ID for security checks,
         @param ids: List of Mail’s IDs
         @param user: Changed User id
-        @param partner: Changed Partner id  
+        @param partner: Changed Partner id
         """
         if not user:
             return {'value': {'email_to': False}}
@@ -146,7 +147,7 @@
         @param uid: the current user’s ID for security checks,
         @param ids: List of Mail’s IDs
         @param user: Changed User id
-        @param partner: Changed Partner id  
+        @param partner: Changed Partner id
         """
         if not partner_id:
             return {'value' : {'email_to' : False, 'address_id': False}}
@@ -155,13 +156,13 @@
         addr = partner_obj.address_get(cr, uid, [partner_id], ['contact'])
         data = {'address_id': addr['contact']}
         data.update(self.on_change_address(cr, uid, ids, addr['contact'])['value'])
-        
+
         partner = partner_obj.browse(cr, uid, [partner_id])
         user_id = partner and partner[0].user_id or False
         email = user_id and user_id.user_email or ''
         data.update({'email_cc' : email})
         return {
-            'value' : data, 
+            'value' : data,
             'domain' : {'address_id' : partner_id and "[('partner_id', '=', partner_id)]" or "[]"}
             }
 
@@ -236,23 +237,23 @@
                     body.append("%s: %s" % (field_definition.string, value or ''))
         elif lead.type == 'opportunity':
             pa = lead.partner_address_id
-            body = [
-                "Partner: %s" % (lead.partner_id and lead.partner_id.name_get()[0][1]), 
-                "Contact: %s" % (pa.name or ''), 
-                "Title: %s" % (pa.title or ''), 
-                "Function: %s" % (pa.function or ''), 
-                "Street: %s" % (pa.street or ''), 
-                "Street2: %s" % (pa.street2 or ''), 
-                "Zip: %s" % (pa.zip or ''), 
-                "City: %s" % (pa.city or ''), 
-                "Country: %s" % (pa.country_id and pa.country_id.name_get()[0][1] or ''), 
-                "State: %s" % (pa.state_id and pa.state_id.name_get()[0][1] or ''), 
-                "Email: %s" % (pa.email or ''), 
-                "Phone: %s" % (pa.phone or ''), 
-                "Fax: %s" % (pa.fax or ''), 
-                "Mobile: %s" % (pa.mobile or ''), 
-                "Lead Category: %s" % (lead.categ_id and lead.categ_id.name or ''), 
-                "Details: %s" % (lead.description or ''), 
+            body += [
+                "Partner: %s" % (lead.partner_id and lead.partner_id.name_get()[0][1]),
+                "Contact: %s" % (pa.name or ''),
+                "Title: %s" % (pa.title or ''),
+                "Function: %s" % (pa.function or ''),
+                "Street: %s" % (pa.street or ''),
+                "Street2: %s" % (pa.street2 or ''),
+                "Zip: %s" % (pa.zip or ''),
+                "City: %s" % (pa.city or ''),
+                "Country: %s" % (pa.country_id and pa.country_id.name_get()[0][1] or ''),
+                "State: %s" % (pa.state_id and pa.state_id.name_get()[0][1] or ''),
+                "Email: %s" % (pa.email or ''),
+                "Phone: %s" % (pa.phone or ''),
+                "Fax: %s" % (pa.fax or ''),
+                "Mobile: %s" % (pa.mobile or ''),
+                "Lead Category: %s" % (lead.categ_id and lead.categ_id.name or ''),
+                "Details: %s" % (lead.description or ''),
             ]
         return "\n".join(body + ['---'])
 
@@ -260,26 +261,66 @@
         """
         This function gets default values
         """
+
         if context is None:
             context = {}
 
         defaults = super(crm_lead_forward_to_partner, self).default_get(cr, uid, fields, context=context)
-
         active_id = context.get('active_id')
         if not active_id:
             return defaults
 
         lead_proxy = self.pool.get('crm.lead')
+        partner_obj = self.pool.get('res.partner')
         lead = lead_proxy.browse(cr, uid, active_id, context=context)
 
+        email_cc = ''
+        email = ''
+        if lead.partner_assigned_id:
+            partner = partner_obj.browse(cr, uid, [lead.partner_assigned_id.id])
+            user_id = partner and partner[0].user_id or False
+            email_cc = user_id and user_id.user_email or ''
+
+            addr = partner_obj.address_get(cr, uid, [partner[0].id], ['contact'])
+            email = self.pool.get('res.partner.address').browse(cr, uid, addr['contact']).email
+
         body = self._get_case_history(cr, uid, defaults.get('history', 'latest'), lead.id, context=context)
         defaults.update({
-            'subject' : '%s: %s' % (_('Fwd'), lead.name),
+            'subject' : '%s: %s - %s' % (_('Fwd'), 'Openerp lead forward', lead.name),
             'body' : body,
-            'email_cc' : ''
+            'email_cc' : email_cc,
+            'email_to' : email or 'dummy@xxxxxxxx'
         })
         return defaults
 
 crm_lead_forward_to_partner()
 
+class crm_lead_mass_forward_to_partner(osv.osv_memory):
+    _name = 'crm.lead.mass.forward.to.partner'
+    _inherit = 'crm.lead.forward.to.partner'
+
+    def action_mass_forward(self, cr, uid, ids, context=None):
+        if not context:
+            context = {}
+
+        active_ids = context.get('active_ids')
+        case_obj = self.pool.get('crm.lead')
+        for case in case_obj.browse(cr, uid, active_ids, context=context):
+            if not case.partner_assigned_id:
+                case_obj.assign_partner(cr,uid, [case.id], context=context)
+                case = case_obj.browse(cr, uid, case.id, context=context)
+
+            if not case.partner_assigned_id:
+                continue
+
+            context.update({'active_id' : case.id})
+            value = self.default_get(cr, uid, ['body', 'email_to', 'email_cc', 'subject', 'history'], context=context)
+            self.write(cr, uid, ids, value, context=context)
+            self.action_forward(cr,uid, ids, context=context)
+
+        return {'type': 'ir.actions.act_window_close'}
+
+
+crm_lead_mass_forward_to_partner()
+
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

=== modified file 'crm_partner_assign/wizard/crm_forward_to_partner_view.xml'
--- crm_partner_assign/wizard/crm_forward_to_partner_view.xml	2011-01-14 00:11:01 +0000
+++ crm_partner_assign/wizard/crm_forward_to_partner_view.xml	2011-04-11 12:48:30 +0000
@@ -38,14 +38,70 @@
                 </button>
             </field>
         </record>
+
         <record model="ir.actions.act_window" id="crm_lead_forward_to_partner_act">
             <field name="name">Forward to Partner</field>
             <field name="res_model">crm.lead.forward.to.partner</field>
             <field name="view_type">form</field>
             <field name="view_mode">form</field>
-            <field name="view_id" ref="crm_lead_forward_to_partner_form1"/>
+            <field name="view_id" ref="crm_lead_forward_to_partner_form"/>
             <field name="target">new</field>
         </record>
 
+
+        <record model="ir.ui.view" id="crm_forward_mass_mail_view">
+            <field name="name">crm.new.mass.forward.mail.form</field>
+            <field name="model">crm.lead.mass.forward.to.partner</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Send Mail" col="4">
+                    <group colspan="4" col="2">
+                    <separator string="Forward to Partner" colspan="4" />
+
+                    <field name="history" colspan="2" on_change="on_change_history(history, context)" invisible="1"/>
+                    <field name="email_from"/>
+                    <field name="reply_to"/>
+                    <field name="email_to" invisible="1" />
+                    <field name="email_cc" invisible="1" />
+                    <field name="subject"  invisible="1" />
+                    <field name="html"/>
+                    </group>
+                    <notebook colspan="6" >
+                        <page string="Message" >
+                            <field name="body" nolabel="1" colspan="4" default_focus="1" readonly="1"/>
+                        </page>
+                        <page string="Attachments" >
+                            <field name="attachment_ids" colspan="4" nolabel="1">
+                                <form string="Attachment">
+                                    <field name="binary" filename="name" />
+                                    <field name="name" />
+                                </form>
+                                <tree string="Attachments">
+                                    <field name="name" />
+                                </tree>
+                            </field>
+                        </page>
+                    </notebook>
+                    <separator string="" colspan="6"/>
+                    <group colspan="6" col="4" >
+                        <field name="state" />
+                        <button string="_Cancel" icon="gtk-cancel" special="cancel" />
+                        <button name="action_mass_forward" type="object" string="_Mass forward" icon="gtk-go-forward" />
+                    </group>
+                </form>
+            </field>
+        </record>
+
+        <act_window id="action_crm_send_mass_forward"
+            multi="True"
+            key2="client_action_multi" name="Mass forward to partner"
+            res_model="crm.lead.mass.forward.to.partner" src_model="crm.lead"
+            view_mode="form" target="new" view_type="form"
+            context="{'mass_forward' : True}"
+            view_id="crm_forward_mass_mail_view"
+        />
+
+
+
     </data>
 </openerp>

=== modified file 'document/document.py'
--- document/document.py	2011-02-28 10:49:36 +0000
+++ document/document.py	2011-04-11 12:48:30 +0000
@@ -279,10 +279,22 @@
         else:
             if vals.get('file_size'):
                 del vals['file_size']
-        if not self._check_duplication(cr, uid, vals):
-            raise osv.except_osv(_('ValidateError'), _('File name must be unique!'))
-        result = super(document_file, self).create(cr, uid, vals, context)
-        cr.commit() # ?
+        result = self._check_duplication(cr, uid, vals)
+        if not result:
+            domain = [
+                ('res_id', '=', vals['res_id']),
+                ('res_model', '=', vals['res_model']),
+                ('datas_fname', '=', vals['datas_fname']),
+            ]
+            attach_ids = self.search(cr, uid, domain, context=context)
+            super(document_file, self).write(cr, uid, attach_ids, 
+                                             {'datas' : vals['datas']},
+                                             context=context)
+            result = attach_ids[0]
+        else:
+            #raise osv.except_osv(_('ValidateError'), _('File name must be unique!'))
+            result = super(document_file, self).create(cr, uid, vals, context)
+            cr.commit() # ?
         return result
 
     def __get_partner_id(self, cr, uid, res_model, res_id, context=None):

=== modified file 'email_template/email_template_mailbox.py'
--- email_template/email_template_mailbox.py	2011-01-24 16:13:46 +0000
+++ email_template/email_template_mailbox.py	2011-04-11 12:48:30 +0000
@@ -92,6 +92,7 @@
                         self.unlink(cr, uid, [id], context=context)
                         # Remove attachments for this mail
                         attachment_pool.unlink(cr, uid, values['attachments_ids'], context=context)
+                        return result
                     else:
                         self.write(cr, uid, id, {'folder':'sent', 'state':'na', 'date_mail':time.strftime("%Y-%m-%d %H:%M:%S")}, context)
                         self.historise(cr, uid, [id], "Email sent successfully", context)

=== modified file 'email_template/email_template_view.xml'
--- email_template/email_template_view.xml	2011-01-14 00:11:01 +0000
+++ email_template/email_template_view.xml	2011-04-11 12:48:30 +0000
@@ -201,7 +201,7 @@
         </record>
 
         <menuitem name="Email Templates" id="menu_email_template_all"
-            parent="menu_email_template_configuration" action="action_email_template_tree_all" />
+            parent="menu_email_template" action="action_email_template_tree_all" />
 
         <!-- Email Template menu in Tools -->
         <menuitem name="Email Templates" id="menu_email_template_all_tools"

=== modified file 'hr/hr.py'
--- hr/hr.py	2011-03-11 17:29:52 +0000
+++ hr/hr.py	2011-04-11 12:48:30 +0000
@@ -151,7 +151,7 @@
         'work_email': fields.char('Work E-mail', size=240),
         'work_location': fields.char('Office Location', size=32),
         'notes': fields.text('Notes'),
-        'parent_id': fields.many2one('hr.employee', 'Manager'), 
+        'parent_id': fields.many2one('hr.employee', 'Manager'),
         'category_ids': fields.many2many('hr.employee.category', 'employee_category_rel', 'emp_id', 'category_id', 'Category'),
         'child_ids': fields.one2many('hr.employee', 'parent_id', 'Subordinates'),
         'resource_id': fields.many2one('resource.resource', 'Resource', ondelete='cascade', required=True),
@@ -240,7 +240,7 @@
         except:
             # Tolerate a missing shortcut. See product/product.py for similar code.
             logging.getLogger('orm').debug('Skipped meetings shortcut for user "%s"', data.get('name','<new'))
-            
+
         return user_id
 
 res_users()

=== modified file 'mail_gateway/mail_gateway.py'
--- mail_gateway/mail_gateway.py	2011-03-17 17:33:16 +0000
+++ mail_gateway/mail_gateway.py	2011-04-11 12:48:30 +0000
@@ -251,7 +251,7 @@
         for message in self.browse(cr, uid, ids, context=context):
             msg_txt = ''
             if message.history:
-                msg_txt += _('%s wrote on %s:\n\t') % (message.email_from or '/', format_date_tz(message.date, tz))
+                msg_txt += _('%s wrote on %s: \n Subject: %s \n\t') % (message.email_from or '/', format_date_tz(message.date, tz), message.name)
                 if message.description:
                     msg_txt += self.truncate_data(cr, uid, message.description, context=context)
             else:

=== modified file 'marketing_campaign/marketing_campaign.py'
--- marketing_campaign/marketing_campaign.py	2011-01-14 00:11:01 +0000
+++ marketing_campaign/marketing_campaign.py	2011-04-11 12:48:30 +0000
@@ -21,6 +21,7 @@
 
 import time
 import base64
+import itertools
 from datetime import datetime
 from dateutil.relativedelta import relativedelta
 from operator import itemgetter
@@ -92,7 +93,17 @@
 this campaign to be run"),
         'partner_field_id': fields.many2one('ir.model.fields', 'Partner Field',
                                             domain="[('model_id', '=', object_id), ('ttype', '=', 'many2one'), ('relation', '=', 'res.partner')]",
-                                            help="The generated workitems will be linked to the partner related to the record. If the record is the partner itself leave this field empty."),
+                                            help="The generated workitems will be linked to the partner related to the record. "\
+                                                  "If the record is the partner itself leave this field empty. "\
+                                                  "This is useful for reporting purposes, via the Campaign Analysis or Campaign Follow-up views."),
+        'unique_field_id': fields.many2one('ir.model.fields', 'Unique Field',
+                                            domain="[('model_id', '=', object_id), ('ttype', 'in', ['char','int','many2one','text','selection'])]",
+                                            help='If set, this field will help segments that work in "no duplicates" mode to avoid '\
+                                                 'selecting similar records twice. Similar records are records that have the same value for '\
+                                                 'this unique field. For example by choosing the "email_from" field for CRM Leads you would prevent '\
+                                                 'sending the same campaign to the same email address again. If not set, the "no duplicates" segments '\
+                                                 "will only avoid selecting the same record again if it entered the campaign previously. "\
+                                                 "Only easily comparable fields like textfields, integers, selections or single relationships may be used."),
         'mode': fields.selection([('test', 'Test Directly'),
                                 ('test_realtime', 'Test in Realtime'),
                                 ('manual', 'With Manual Confirmation'),
@@ -208,11 +219,36 @@
     def copy(self, cr, uid, id, default=None, context=None):
         raise osv.except_osv(_("Operation not supported"), _("Sorry, campaign duplication is not supported at the moment."))
 
+    def _find_duplicate_workitems(self, cr, uid, record, campaign_rec, context=None):
+        """Finds possible duplicates workitems for a record in this campaign, based on a uniqueness
+           field.
+
+           :param record: browse_record to find duplicates workitems for.
+           :param campaign_rec: browse_record of campaign
+        """
+        Workitems = self.pool.get('marketing.campaign.workitem')
+        duplicate_workitem_domain = [('res_id','=', record.id),
+                                     ('campaign_id','=', campaign_rec.id)]
+        unique_field = campaign_rec.unique_field_id
+        if unique_field:
+            unique_value = getattr(record, unique_field.name, None)
+            if unique_value:
+                if unique_field.ttype == 'many2one':
+                    unique_value = unique_value.id
+                similar_res_ids = self.pool.get(campaign_rec.object_id.model).search(cr, uid,
+                                    [(unique_field.name, '=', unique_value)], context=context)
+                if similar_res_ids:
+                    duplicate_workitem_domain = [('res_id','in', similar_res_ids),
+                                                 ('campaign_id','=', campaign_rec.id)]
+        return Workitems.search(cr, uid, duplicate_workitem_domain, context=context)
+
+
 marketing_campaign()
 
 class marketing_campaign_segment(osv.osv):
     _name = "marketing.campaign.segment"
     _description = "Campaign Segment"
+    _order = "name"
 
     def _get_next_sync(self, cr, uid, ids, fn, args, context=None):
         # next auto sync date is same for all segments
@@ -225,13 +261,19 @@
         'campaign_id': fields.many2one('marketing.campaign', 'Campaign', required=True, select=1, ondelete="cascade"),
         'object_id': fields.related('campaign_id','object_id', type='many2one', relation='ir.model', string='Resource'),
         'ir_filter_id': fields.many2one('ir.filters', 'Filter', ondelete="restrict",
-                            help="Filter to select the matching resource records that belong to this segment. New filters can be created and saved using the advanced search on the list view of the Resource. If no filter is set, all records are selected without filtering. The synchronization mode may also add a criterion to the filter."),
+                            help="Filter to select the matching resource records that belong to this segment. "\
+                                 "New filters can be created and saved using the advanced search on the list view of the Resource. "\
+                                 "If no filter is set, all records are selected without filtering. "\
+                                 "The synchronization mode may also add a criterion to the filter."),
         'sync_last_date': fields.datetime('Last Synchronization', help="Date on which this segment was synchronized last time (automatically or manually)"),
         'sync_mode': fields.selection([('create_date', 'Only records created after last sync'),
                                       ('write_date', 'Only records modified after last sync (no duplicates)'),
                                       ('all', 'All records (no duplicates)')],
                                       'Synchronization mode',
-                                      help="Determines an additional criterion to add to the filter when selecting new records to inject in the campaign."),
+                                      help="Determines an additional criterion to add to the filter when selecting new records to inject in the campaign. "\
+                                           '"No duplicates" prevents selecting records which have already entered the campaign previously.'\
+                                           'If the campaign has a "unique field" set, "no duplicates" will also prevent selecting records which have '\
+                                           'the same value for the unique field as other records that already entered the campaign.'),
         'state': fields.selection([('draft', 'Draft'),
                                    ('running', 'Running'),
                                    ('done', 'Done'),
@@ -300,6 +342,7 @@
 
     def process_segment(self, cr, uid, segment_ids=None, context=None):
         Workitems = self.pool.get('marketing.campaign.workitem')
+        Campaigns = self.pool.get('marketing.campaign')
         if not segment_ids:
             segment_ids = self.search(cr, uid, [('state', '=', 'running')], context=context)
 
@@ -322,21 +365,20 @@
             object_ids = model_obj.search(cr, uid, criteria, context=context)
 
             # XXX TODO: rewrite this loop more efficiently without doing 1 search per record!
-            for o_ids in model_obj.browse(cr, uid, object_ids, context=context):
-                # avoid duplicated workitem for the same resource
+            for record in model_obj.browse(cr, uid, object_ids, context=context):
+                # avoid duplicate workitem for the same resource
                 if segment.sync_mode in ('write_date','all'):
-                    wi_ids = Workitems.search(cr, uid, [('res_id','=',o_ids.id),('segment_id','=',segment.id)], context=context)
-                    if wi_ids:
+                    if Campaigns._find_duplicate_workitems(cr, uid, record, segment.campaign_id, context=context):
                         continue
 
                 wi_vals = {
                     'segment_id': segment.id,
                     'date': action_date,
                     'state': 'todo',
-                    'res_id': o_ids.id
+                    'res_id': record.id
                 }
 
-                partner = self.pool.get('marketing.campaign')._get_partner_for(segment.campaign_id, o_ids)
+                partner = self.pool.get('marketing.campaign')._get_partner_for(segment.campaign_id, record)
                 if partner:
                     wi_vals['partner_id'] = partner.id
 
@@ -352,6 +394,7 @@
 
 class marketing_campaign_activity(osv.osv):
     _name = "marketing.campaign.activity"
+    _order = "name"
     _description = "Campaign Activity"
 
     _action_types = [
@@ -453,10 +496,11 @@
         action_context = dict(context,
                               active_id=workitem.res_id,
                               active_ids=[workitem.res_id],
-                              active_model=workitem.object_id.model)
+                              active_model=workitem.object_id.model,
+                              workitem=workitem)
         res = server_obj.run(cr, uid, [activity.server_action_id.id],
                              context=action_context)
-        # server action return False if the action is perfomed
+        # server action return False if the action is performed
         # except client_action, other and python code
         return res == False and True or res
 
@@ -556,6 +600,8 @@
                 continue
 
             proxy = self.pool.get(wi.object_id.model)
+            if not proxy.exists(cr, uid, [wi.res_id]):
+                continue
             ng = proxy.name_get(cr, uid, [wi.res_id], context=context)
             if ng:
                 res[wi.id] = ng[0][1]
@@ -566,8 +612,14 @@
         if not len(args):
             return []
 
-        condition = []
-        final_ids = []
+        condition_name = None
+        for domain_item in args:
+            # we only use the first domain criterion and ignore all the rest including operators
+            if isinstance(domain_item, (list,tuple)) and len(domain_item) == 3 and domain_item[0] == 'res_name':
+                condition_name = [None, domain_item[1], domain_item[2]]
+                break
+
+        assert condition_name, "Invalid search domain for marketing_campaign_workitem.res_name. It should use 'res_name'"
 
         cr.execute("""select w.id, w.res_id, m.model  \
                                 from marketing_campaign_workitem w \
@@ -576,15 +628,17 @@
                                     left join ir_model m on (m.id=c.object_id)
                                     """)
         res = cr.fetchall()
+        workitem_map = {}
+        matching_workitems = []
         for id, res_id, model in res:
+            workitem_map.setdefault(model,{}).setdefault(res_id,set()).add(id)
+        for model, id_map in workitem_map.iteritems():
             model_pool = self.pool.get(model)
-            for arg in args:
-                if arg[1] == 'ilike':
-                    condition.append((model_pool._rec_name, 'ilike', arg[2]))
-            res_ids = model_pool.search(cr, uid, condition, context=context)
-            if res_id in res_ids:
-                final_ids.append(id)
-        return [('id', 'in', final_ids)]
+            condition_name[0] = model_pool._rec_name
+            condition = [('id', 'in', id_map.keys()), condition_name]
+            for res_id in model_pool.search(cr, uid, condition, context=context):
+                matching_workitems.extend(id_map[res_id])
+        return [('id', 'in', list(set(matching_workitems)))]
 
     _columns = {
         'segment_id': fields.many2one('marketing.campaign.segment', 'Segment', readonly=True),
@@ -722,7 +776,7 @@
                 # manual states are not processed automatically
                 continue
             while True:
-                domain = [('state', '=', 'todo'), ('date', '!=', False)]
+                domain = [('campaign_id', '=', camp.id), ('state', '=', 'todo'), ('date', '!=', False)]
                 if camp.mode in ('test_realtime', 'active'):
                     domain += [('date','<=', time.strftime('%Y-%m-%d %H:%M:%S'))]
 

=== modified file 'marketing_campaign/marketing_campaign_view.xml'
--- marketing_campaign/marketing_campaign_view.xml	2011-01-27 09:49:39 +0000
+++ marketing_campaign/marketing_campaign_view.xml	2011-04-11 12:48:30 +0000
@@ -26,6 +26,7 @@
                         <separator string="Resource" colspan="2" />
                         <field name="object_id"/>
                         <field name="partner_field_id"/>
+                        <field name="unique_field_id"/>
                     </group>
                     <group colspan="2" col="2">
                         <separator string="Cost" colspan="2" />

=== modified file 'project/project.py'
--- project/project.py	2011-04-05 06:02:10 +0000
+++ project/project.py	2011-04-11 12:48:30 +0000
@@ -26,6 +26,10 @@
 from tools.translate import _
 from osv import fields, osv
 
+class project_project(osv.osv):
+    _name = 'project.project'
+    
+project_project()    
 
 class project_task_type(osv.osv):
     _name = 'project.task.type'
@@ -35,6 +39,7 @@
         'name': fields.char('Stage Name', required=True, size=64, translate=True),
         'description': fields.text('Description'),
         'sequence': fields.integer('Sequence'),
+        'project_ids': fields.many2many('project.project', 'project_task_type_rel', 'type_id', 'project_id', 'Projects'),
     }
 
     _defaults = {

=== modified file 'project_issue/project_issue.py'
--- project_issue/project_issue.py	2011-03-09 04:36:22 +0000
+++ project_issue/project_issue.py	2011-04-11 12:48:30 +0000
@@ -43,9 +43,9 @@
 class project_issue(crm.crm_case, osv.osv):
     _name = "project.issue"
     _description = "Project Issue"
-    _order = "priority, id desc"
+    _order = "priority, create_date desc"
     _inherit = ['mailgate.thread']
-
+    
     def case_open(self, cr, uid, ids, *args):
         """
         @param self: The object pointer
@@ -56,7 +56,7 @@
         """
 
         res = super(project_issue, self).case_open(cr, uid, ids, *args)
-        self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S')})
+        self.write(cr, uid, ids, {'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), 'assigned_to' : uid})
         for (id, name) in self.name_get(cr, uid, ids):
             message = _("Issue '%s' has been opened.") % name
             self.log(cr, uid, id, message)
@@ -175,7 +175,7 @@
         'section_id': fields.many2one('crm.case.section', 'Sales Team', \
                         select=True, help='Sales team to which Case belongs to.\
                              Define Responsible user and Email account for mail gateway.'),
-        'user_id': fields.many2one('res.users', 'Responsible'),
+        'user_id': fields.related('project_id', 'user_id', type='many2one', relation='res.users', store=True, select=1, string='Responsible'),
         'partner_id': fields.many2one('res.partner', 'Partner'),
         'partner_address_id': fields.many2one('res.partner.address', 'Partner Contact', \
                                  domain="[('partner_id','=',partner_id)]"),
@@ -200,19 +200,19 @@
         'partner_name': fields.char("Employee's Name", size=64),
         'partner_mobile': fields.char('Mobile', size=32),
         'partner_phone': fields.char('Phone', size=32),
-        'type_id': fields.many2one ('project.task.type', 'Resolution'),
+        'type_id': fields.many2one ('project.task.type', 'Resolution', domain="[('project_ids', '=', project_id)]"),
         'project_id':fields.many2one('project.project', 'Project'),
         'duration': fields.float('Duration'),
         'task_id': fields.many2one('project.task', 'Task', domain="[('project_id','=',project_id)]"),
         'day_open': fields.function(_compute_day, string='Days to Open', \
-                                method=True, multi='day_open', type="float", store=True),
+                                method=True, multi='compute_day', type="float", store=True),
         'day_close': fields.function(_compute_day, string='Days to Close', \
-                                method=True, multi='day_close', type="float", store=True),
-        'assigned_to': fields.related('task_id', 'user_id', string = 'Assigned to', type="many2one", relation="res.users", store=True, help='This is the current user to whom the related task have been assigned'),
+                                method=True, multi='compute_day', type="float", store=True),
+        'assigned_to': fields.many2one('res.users', 'Assigned to', required=False, select=1),
         'working_hours_open': fields.function(_compute_day, string='Working Hours to Open the Issue', \
-                                method=True, multi='working_days_open', type="float", store=True),
+                                method=True, multi='compute_day', type="float", store=True),
         'working_hours_close': fields.function(_compute_day, string='Working Hours to Close the Issue', \
-                                method=True, multi='working_days_close', type="float", store=True),
+                                method=True, multi='compute_day', type="float", store=True),
         'message_ids': fields.one2many('mailgate.message', 'res_id', 'Messages', domain=[('model','=',_name)]),
         'date_action_last': fields.datetime('Last Action', readonly=1),
         'date_action_next': fields.datetime('Next Action', readonly=1),
@@ -230,17 +230,30 @@
             return user.context_project_id.id
         return False
 
+    def on_change_project(self, cr, uid, ids, project_id, context=None):
+        result = {}
+
+        if project_id:
+            project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
+            if project.user_id:
+                result['value'] = {'user_id' : project.user_id.id}
+
+        return result
+
+
     _defaults = {
         'active': 1,
-        'user_id': crm.crm_case._get_default_user,
+        #'user_id': crm.crm_case._get_default_user,
         'partner_id': crm.crm_case._get_default_partner,
         'partner_address_id': crm.crm_case._get_default_partner_address,
-        'email_from': crm.crm_case. _get_default_email,
+        'email_from': crm.crm_case._get_default_email,
         'state': 'draft',
-        'section_id': crm.crm_case. _get_section,
+        'section_id': crm.crm_case._get_section,
         'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
         'priority': crm.AVAILABLE_PRIORITIES[2][0],
         'project_id':_get_project,
+        'categ_id' : lambda *a: False,
+        #'assigned_to' : lambda obj, cr, uid, context: uid,
     }
 
     def convert_issue_task(self, cr, uid, ids, context=None):

=== modified file 'project_issue/project_issue_view.xml'
--- project_issue/project_issue_view.xml	2011-03-09 04:36:22 +0000
+++ project_issue/project_issue_view.xml	2011-04-11 12:48:30 +0000
@@ -52,13 +52,14 @@
             <field name="arch" type="xml">
                 <form string="Issue Tracker Form">
                     <group colspan="4" col="6">
-                    <field name="name"/>
-                    <field name="project_id" required="True"/>
-                    <field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'project.issue')]"/>
-                    <field name="user_id"/>
-                    <field name="assigned_to" />
+                        <field name="name"/>
+                        <field name="project_id" required="True" on_change="on_change_project(project_id)"/>
+                        <field name="categ_id" widget="selection" domain="[('object_id.model', '=', 'project.issue')]"/>
+                        <field name="user_id" invisible="1" />
+                        <field name="assigned_to"/>
+                        <field name="version_id" colspan="2" widget="selection"/>
                         <group colspan="2" col="4">
-                            <field name="type_id" readonly="1"/>
+                            <field name="type_id" string="Resolution" />
                             <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/>
                             <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/>
                         </group>
@@ -73,7 +74,6 @@
                             </group>
                             <group col="3" colspan="2">
                             <separator colspan="3" string="Status"/>
-                            <field name="version_id" colspan="3" widget="selection"/>
                             <field name="priority" colspan="3"/>
                             <field name="task_id" on_change="onchange_task_id(task_id)"/>
                             <button string="Convert To Task" name="convert_issue_task" icon="gtk-index" type="object"
@@ -184,12 +184,11 @@
                     <field name="partner_id" groups="base.group_extended"/>
                     <field name="project_id" />
                     <field name="priority" string="Priority"/>
-                    <field name="type_id" widget="selection" readonly="1"/>
+                    <field name="type_id" widget="selection" readonly="1" string="Resolution" />
                     <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/>
                     <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/>
                     <field name="version_id" widget="selection"/>
-                    <field name="user_id"/>
-                    <field name="assigned_to" attrs="{'readonly':[('task_id','=',False)]}"/>
+                    <field name="assigned_to"/>
                     <field name="progress" widget="progressbar" attrs="{'invisible':[('task_id','=',False)]}"/>
                     <field name="state"/>
                     <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/>
@@ -218,8 +217,8 @@
                         <separator orientation="vertical"/>
                         <field name="name"/>
                         <field name="partner_id" groups="base.group_extended"/>
-                        <field name="user_id">
-                            <filter domain="[('user_id','=',False)]"  help="Unassigned Issues" icon="terp-personal-" separator="1"/>
+                        <field name="assigned_to">
+                            <filter domain="[('assigned_to','=',False)]"  help="Unassigned Issues" icon="terp-personal-" separator="1"/>
                         </field>
                         <field name="project_id"/>
                     </group>
@@ -233,7 +232,7 @@
                     <newline/>
                     <group expand="0" string="Group By..."  groups="base.group_extended">
                         <filter string="Responsible" icon="terp-personal"
-                            domain="[]" context="{'group_by':'user_id'}" />
+                            domain="[]" context="{'group_by':'assigned_to'}" />
                         <filter string="Partner" icon="terp-partner" domain="[]"
                             context="{'group_by':'partner_id'}" />
                         <separator orientation="vertical"/>
@@ -264,7 +263,7 @@
             <field name="type">calendar</field>
             <field name="priority" eval="2"/>
             <field name="arch" type="xml">
-                <calendar string="Issues" date_start="date" color="user_id" date_delay="duration">
+                <calendar string="Issues" date_start="date" color="assigned_to" date_delay="duration">
                     <field name="name"/>
                     <field name="partner_id"/>
                 </calendar>
@@ -285,11 +284,11 @@
                     <field name="name" string="Feature description"/>
                     <field name="partner_id" groups="base.group_extended"/>
                     <field name="priority" string="Priority"/>
-                                <field name="type_id" widget="selection" readonly="1"/>
-                                <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/>
-                                <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/>
+                    <field name="type_id" widget="selection" readonly="1"/>
+                    <button name="prev_type" string="Previous" type="object" icon="gtk-go-back" help="Change to Previous Stage"/>
+                    <button name="next_type" string="Next" type="object" icon="gtk-go-forward" help="Change to Next Stage"/>
                     <field name="version_id"/>
-                    <field name="user_id"/>
+                    <field name="assigned_to"/>
                     <field name="state"/>
                     <button name="case_cancel" string="Cancel" states="draft,open,pending" type="object" icon="gtk-cancel"/>
                     <button name="case_close" string="Done" states="open,draft,pending" type="object" icon="gtk-jump-to"/>
@@ -312,7 +311,7 @@
                     <separator orientation="vertical"/>
                     <group>
                         <field name="name" select='1' string="Feature description"/>
-                        <field name="user_id" select="1"/>
+                        <field name="assigned_to" select="1"/>
                         <field name="state" select="1">
                             <filter icon="terp-check" domain="[('state','in',('open','draft'))]" help="Current Features" name="current_feature"/>
                             <filter icon="terp-camera_test" domain="[('state','=','open')]" help="Open Features"/>

=== modified file 'project_issue/report/project_issue_report.py'
--- project_issue/report/project_issue_report.py	2011-01-25 01:43:30 +0000
+++ project_issue/report/project_issue_report.py	2011-04-11 12:48:30 +0000
@@ -1,7 +1,7 @@
 
 # -*- coding: utf-8 -*-
 ##############################################################################
-#    
+#
 #    OpenERP, Open Source Management Solution
 #    Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
 #
@@ -16,7 +16,7 @@
 #    GNU Affero General Public License for more details.
 #
 #    You should have received a copy of the GNU Affero General Public License
-#    along with this program.  If not, see <http://www.gnu.org/licenses/>.     
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #
 ##############################################################################
 
@@ -101,10 +101,11 @@
                     c.task_id,
                     date_trunc('day',c.create_date) as create_date,
                     extract('epoch' from (c.date_open-c.create_date))/(3600*24) as  delay_open,
-                    extract('epoch' from (c.date_closed-c.create_date))/(3600*24) as  delay_close,
+                    extract('epoch' from (c.date_closed-c.date_open))/(3600*24) as  delay_close,
                     (SELECT count(id) FROM mailgate_message WHERE model='project.issue' AND res_id=c.id) AS email
                 FROM
                     project_issue c
+                WHERE c.categ_id IN (select id from crm_case_categ where object_id in (select id from ir_model where model = 'project.issue'))
             )""")
 
 project_issue_report()

=== modified file 'project_issue/report/project_issue_report_view.xml'
--- project_issue/report/project_issue_report_view.xml	2011-01-14 00:11:01 +0000
+++ project_issue/report/project_issue_report_view.xml	2011-04-11 12:48:30 +0000
@@ -28,8 +28,8 @@
                     <field name="nbr" string="#Project Issues" sum='#Number of Project Issues'/>
                     <field name="delay_open" avg='Avg Opening Delay'/>
                     <field name="delay_close" avg='Avg Closing Delay'/>
-                    <field name="working_hours_open" sum='Open Working Hours'/>
-                    <field name="working_hours_close" sum='Close Working hours'/>
+                    <field name="working_hours_open" avg='Open Working Hours'/>
+                    <field name="working_hours_close" avg='Close Working hours'/>
                     <field name="email" sum='# Emails'/>
                   </tree>
             </field>

=== modified file 'project_issue_sheet/project_issue_sheet.py'
--- project_issue_sheet/project_issue_sheet.py	2011-01-14 00:11:01 +0000
+++ project_issue_sheet/project_issue_sheet.py	2011-04-11 12:48:30 +0000
@@ -27,8 +27,36 @@
     _description = 'project issue'
     _columns = {
         'timesheet_ids': fields.one2many('hr.analytic.timesheet', 'issue_id', 'Timesheets'),
-        'analytic_account_id': fields.related('project_id', 'analytic_account_id', type='many2one', relation='account.analytic.account',string='Analytic Account')
+        'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account'), 
     }
+    
+    def on_change_project(self, cr, uid, ids, project_id, context=None):
+        if not project_id:
+            return {}
+
+        result = super(project_issue, self).on_change_project(cr, uid, ids, project_id, context=context)
+        
+        project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
+        if 'value' not in result:
+            result['value'] = {}
+
+        account = project.analytic_account_id
+        if account:
+            result['value']['analytic_account_id'] = account.id
+
+        return result
+
+    def on_change_account_id(self, cr, uid, ids, account_id, context=None):
+        if not account_id:
+            return {}
+
+        account = self.pool.get('account.analytic.account').browse(cr, uid, account_id, context=context)
+        result = {}
+
+        if account and account.state == 'pending':
+            result = {'warning' : {'title' : _('Analytic Account'), 'message' : _('The Analytic Account is in pending !')}}
+            
+        return result
 
 project_issue()
 

=== modified file 'project_issue_sheet/project_issue_sheet_view.xml'
--- project_issue_sheet/project_issue_sheet_view.xml	2011-01-14 00:11:01 +0000
+++ project_issue_sheet/project_issue_sheet_view.xml	2011-04-11 12:48:30 +0000
@@ -7,14 +7,22 @@
             <field name="inherit_id" ref="project_issue.project_issue_form_view" />
             <field name="type">form</field>
             <field name="arch" type="xml">
+                <field name="project_id" position="attributes">
+                    <attribute name="on_change">on_change_project(project_id)</attribute>
+                </field>
+                <xpath expr="/form/notebook" position="before">
+                    <field name="analytic_account_id"
+                        domain="[('parent_id','!=',False),('partner_id', '=', partner_id),('type', '!=', 'view')]"
+                        on_change='on_change_account_id(analytic_account_id)'
+                        groups="base.group_extended" />
+                </xpath>
                 <notebook colspan="4">
                     <page string="Worklogs">
-                        <field name="analytic_account_id" invisible="1" domain="[('parent_id','!=',False)]" groups="base.group_extended"/>
-                        <field name="timesheet_ids" colspan="4" nolabel="1" context="{'default_user_id' : user_id, 'default_account_id' : analytic_account_id}">
+                        <field name="timesheet_ids" colspan="4" nolabel="1" context="{'default_user_id' : assigned_to, 'default_account_id' : analytic_account_id}">
                             <tree editable="top" string="Timesheet">
-                              <field name="name"/>
-                              <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/>
-                              <field name="account_id" invisible="0" domain="[('partner_id', '=', parent.partner_id)]" on_change="on_change_account_id(account_id)"/>
+                                <field name="name"/>
+                                <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/>
+                                <field name="account_id" invisible="0" domain="[('partner_id', '=', parent.partner_id)]" on_change="on_change_account_id(account_id)"/>
                                 <field name="date"/>
                                 <field name="user_id"/>
                                 <field invisible="1" name="journal_id"/>


Follow ups