diff options
| author | Antony Lesuisse <al@openerp.com> | 2012-12-08 15:41:39 +0100 |
|---|---|---|
| committer | Antony Lesuisse <al@openerp.com> | 2012-12-08 15:41:39 +0100 |
| commit | e6efcaeabcff924d6e44f5c0f29edfec5570039d (patch) | |
| tree | fbf54c5e4d1cc4691b3e0d1a0eff69a17b93e566 | |
| parent | 85b0acf7000d0485e97f044f3fe4a7edcde25f3a (diff) | |
| parent | d30c3a32db5cf367eb0d464f3f61f37c41195a15 (diff) | |
[MERGE] improve_crm_leads_flux by abo
bzr revid: al@openerp.com-20121208144139-o9zr10b7qbij48d5
27 files changed, 699 insertions, 805 deletions
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 63d5d3da84b..76f65cc5bba 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -67,15 +67,11 @@ Dashboard for CRM will include: 'security/crm_security.xml', 'security/ir.model.access.csv', - 'wizard/crm_lead_to_partner_view.xml', 'wizard/crm_lead_to_opportunity_view.xml', 'wizard/crm_phonecall_to_phonecall_view.xml', - 'wizard/crm_phonecall_to_partner_view.xml', - 'wizard/crm_phonecall_to_opportunity_view.xml', 'wizard/crm_opportunity_to_phonecall_view.xml', - 'wizard/crm_partner_to_opportunity_view.xml', 'wizard/crm_merge_opportunities_view.xml', @@ -109,6 +105,7 @@ Dashboard for CRM will include: 'test': [ 'test/process/communication_with_customer.yml', 'test/process/lead2opportunity2win.yml', + 'test/process/lead2opportunity_assign_salesmen.yml', 'test/process/merge_opportunity.yml', 'test/process/cancel_lead.yml', 'test/process/action_rule.yml', diff --git a/addons/crm/crm_action_rule_demo.xml b/addons/crm/crm_action_rule_demo.xml index 14a9761e102..6885aba9bff 100644 --- a/addons/crm/crm_action_rule_demo.xml +++ b/addons/crm/crm_action_rule_demo.xml @@ -1,7 +1,13 @@ <?xml version="1.0"?> <openerp> <data noupdate="1"> - <!-- email server actions --> + <!-- demo automated actions add messge on new lead after 5 days --> + <record id="filter_draft_lead" model="ir.filters"> + <field name="name">Draft Leads</field> + <field name="model_id">crm.lead</field> + <field name="domain">[('state','=','draft')]</field> + <field name="user_id" eval="False"/> + </record> <record id="action_email_reminder_lead" model="ir.actions.server"> <field name="name">Reminder to User</field> <field name="model_id" ref="model_crm_lead"/> @@ -10,46 +16,12 @@ <field name="state">email</field> <field name="email">object.user_id.email</field> <field name="subject">Reminder on Lead: [[object.id ]] [[object.partner_id and 'of ' +object.partner_id.name or '']]</field> - <field name="message">Hello [[object.user_id.name]], -can you check following lead, it's not open since 5 days ? - -Lead: [[object.id ]] -Description: - - [[object.description]] - -Thanks, - </field> - </record> - - <record id="action_email_reminder_customer_lead" model="ir.actions.server"> - <field name="name">Reminder to Customer</field> - <field name="model_id" ref="model_crm_lead"/> - <field name="condition">True</field> - <field name="type">ir.actions.server</field> - <field name="state">email</field> - <field name="email">object.email_from</field> - <field name="subject">Reminder on Lead: [[object.id ]]</field> - <field name="message">Hello [[object.partner_id and object.partner_id.name or '']], -your following lead is not open since 5 days. - -Lead: [[object.id ]] -Description: - - [[object.description]] - -Thanks, + <field name="message">Warning unprocessed incoming lead is more than 5 day old. +Name: [[object.name ]] +ID: [[object.id ]] +Description: [[object.description]] </field> </record> - - <record id="filter_draft_lead" model="ir.filters"> - <field name="name">Draft Leads</field> - <field name="model_id">crm.lead</field> - <field name="domain">[('state','=','draft')]</field> - <field name="user_id" eval="False"/> - </record> - - <!-- automated actions --> <record id="rule_set_reminder_lead" model="base.action.rule"> <field name="name">Set Auto Reminder on leads which are not open since 5 days.</field> <field name="model_id" ref="model_crm_lead"/> @@ -58,16 +30,16 @@ Thanks, <field name="trg_date_type">create</field> <field name="trg_date_range">5</field> <field name="trg_date_range_type">day</field> - <field name="server_action_ids" eval="[(6,0,[ref('action_email_reminder_customer_lead'), ref('action_email_reminder_lead')])]"/> + <field name="server_action_ids" eval="[(6,0,[ref('action_email_reminder_lead')])]"/> </record> + <!-- demo automated actions add a followers on urgent USA leads --> <record id="filter_usa_lead" model="ir.filters"> <field name="name">Leads from USA</field> <field name="model_id">crm.lead</field> <field name="domain">[('country_id','=','United States')]</field> <field name="user_id" eval="False"/> </record> - <record id="rule_set_followers_lead" model="base.action.rule"> <field name="name">Set Auto Followers on leads which are urgent and come from USA.</field> <field name="model_id" ref="model_crm_lead"/> diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 9ee114d8c4d..d9701d63ba7 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -30,6 +30,36 @@ from tools import html2plaintext from base.res.res_partner import format_address +CRM_LEAD_FIELDS_TO_MERGE = ['name', + 'partner_id', + 'channel_id', + 'company_id', + 'country_id', + 'section_id', + 'stage_id', + 'state_id', + 'type_id', + 'user_id', + 'title', + 'city', + 'contact_name', + 'description', + 'email', + 'fax', + 'mobile', + 'partner_name', + 'phone', + 'probability', + 'planned_revenue', + 'street', + 'street2', + 'zip', + 'create_date', + 'date_action_last', + 'date_action_next', + 'email_from', + 'email_cc', + 'partner_name'] CRM_LEAD_PENDING_STATES = ( crm.AVAILABLE_STATES[2][0], # Cancelled crm.AVAILABLE_STATES[3][0], # Done @@ -120,11 +150,7 @@ class crm_lead(base_stage, format_address, osv.osv): def _compute_day(self, cr, uid, ids, fields, args, context=None): """ - @param cr: the current row, from the database cursor, - @param uid: the current user’s ID for security checks, - @param ids: List of Openday’s IDs - @return: difference between current date and log date - @param context: A standard dictionary for contextual values + :return dict: difference between current date and log date """ cal_obj = self.pool.get('resource.calendar') res_obj = self.pool.get('resource.resource') @@ -413,15 +439,46 @@ class crm_lead(base_stage, format_address, osv.osv): """ return self.set_priority(cr, uid, ids, '3') + def _merge_get_result_type(self, cr, uid, opps, context=None): + """ + Define the type of the result of the merge. If at least one of the + element to merge is an opp, the resulting new element will be an opp. + Otherwise it will be a lead. + + We'll directly use a list of browse records instead of a list of ids + for performances' sake: it will spare a second browse of the + leads/opps. + + :param list opps: list of browse records containing the leads/opps to process + :return string type: the type of the final element + """ + for opp in opps: + if (opp.type == 'opportunity'): + return 'opportunity' + + return 'lead' + def _merge_data(self, cr, uid, ids, oldest, fields, context=None): - # prepare opportunity data into dictionary for merging + """ + Prepare lead/opp data into a dictionary for merging. Different types + of fields are processed in different ways: + - text: all the values are concatenated + - m2m and o2m: those fields aren't processed + - m2o: the first not null value prevails (the other are dropped) + - any other type of field: same as m2o + + :param list ids: list of ids of the leads to process + :param list fields: list of leads' fields to process + :return dict data: contains the merged values + """ opportunities = self.browse(cr, uid, ids, context=context) + def _get_first_not_null(attr): if hasattr(oldest, attr): return getattr(oldest, attr) - for opportunity in opportunities: - if hasattr(opportunity, attr): - return getattr(opportunity, attr) + for opp in opportunities: + if hasattr(opp, attr): + return getattr(opp, attr) return False def _get_first_not_null_id(attr): @@ -429,8 +486,9 @@ class crm_lead(base_stage, format_address, osv.osv): return res and res.id or False def _concat_all(attr): - return ', '.join(filter(lambda x: x, [getattr(opportunity, attr) or '' for opportunity in opportunities if hasattr(opportunity, attr)])) + return ', '.join(filter(lambda x: x, [getattr(opp, attr) or '' for opp in opportunities if hasattr(opp, attr)])) + # Process the fields' values data = {} for field_name in fields: field_info = self._all_columns.get(field_name) @@ -445,24 +503,35 @@ class crm_lead(base_stage, format_address, osv.osv): data[field_name] = _concat_all(field_name) #not lost else: data[field_name] = _get_first_not_null(field_name) #not lost + + # Define the resulting type ('lead' or 'opportunity') + data['type'] = self._merge_get_result_type(cr, uid, opportunities, context) + return data def _merge_find_oldest(self, cr, uid, ids, context=None): + """ + Return the oldest lead found among ids. + + :param list ids: list of ids of the leads to inspect + :return object: browse record of the oldest of the leads + """ if context is None: context = {} - #TOCHECK: where pass 'convert' in context ? + if context.get('convert'): - ids = list(set(ids) - set(context.get('lead_ids', False)) ) + ids = list(set(ids) - set(context.get('lead_ids', []))) - #search opportunities order by create date - opportunity_ids = self.search(cr, uid, [('id', 'in', ids)], order='create_date' , context=context) - oldest_id = opportunity_ids[0] - return self.browse(cr, uid, oldest_id, context=context) + # Search opportunities order by create date + opportunity_ids = self.search(cr, uid, [('id', 'in', ids)], order='create_date', context=context) + oldest_opp_id = opportunity_ids[0] + return self.browse(cr, uid, oldest_opp_id, context=context) def _mail_body(self, cr, uid, lead, fields, title=False, context=None): body = [] if title: body.append("%s\n" % (title)) + for field_name in fields: field_info = self._all_columns.get(field_name) if field_info is None: @@ -483,20 +552,25 @@ class crm_lead(base_stage, format_address, osv.osv): value = lead[field_name] body.append("%s: %s" % (field.string, value or '')) + return "\n".join(body + ['---']) - def _merge_notification(self, cr, uid, opportunity_id, opportunities, context=None): + def _merge_notify(self, cr, uid, opportunity_id, opportunities, context=None): + """ + Create a message gathering merged leads/opps information. + """ #TOFIX: mail template should be used instead of fix body, subject text details = [] - merge_message = _('Merged opportunities') + result_type = self._merge_get_result_type(cr, uid, opportunities, context) + if result_type == 'lead': + merge_message = _('Merged leads') + else: + merge_message = _('Merged opportunities') subject = [merge_message] - fields = ['name', 'partner_id', 'stage_id', 'section_id', 'user_id', 'categ_ids', 'channel_id', 'company_id', 'contact_name', - 'email_from', 'phone', 'fax', 'mobile', 'state_id', 'description', 'probability', 'planned_revenue', - 'country_id', 'city', 'street', 'street2', 'zip'] for opportunity in opportunities: subject.append(opportunity.name) title = "%s : %s" % (merge_message, opportunity.name) - details.append(self._mail_body(cr, uid, opportunity, fields, title=title, context=context)) + details.append(self._mail_body(cr, uid, opportunity, CRM_LEAD_FIELDS_TO_MERGE, title=title, context=context)) # Chatter message's subject subject = subject[0] + ": " + ", ".join(subject[1:]) @@ -540,42 +614,41 @@ class crm_lead(base_stage, format_address, osv.osv): def merge_opportunity(self, cr, uid, ids, context=None): """ - To merge opportunities - :param ids: list of opportunities ids to merge + Different cases of merge: + - merge leads together = 1 new lead + - merge at least 1 opp with anything else (lead or opp) = 1 new opp + + :param list ids: leads/opportunities ids to merge + :return int id: id of the resulting lead/opp """ if context is None: context = {} - #TOCHECK: where pass lead_ids in context? - lead_ids = context and context.get('lead_ids', []) or [] - if len(ids) <= 1: - raise osv.except_osv(_('Warning!'),_('Please select more than one opportunity from the list view.')) + raise osv.except_osv(_('Warning!'),_('Please select more than one element (lead or opportunity) from the list view.')) + + lead_ids = context.get('lead_ids', []) ctx_opportunities = self.browse(cr, uid, lead_ids, context=context) opportunities = self.browse(cr, uid, ids, context=context) opportunities_list = list(set(opportunities) - set(ctx_opportunities)) oldest = self._merge_find_oldest(cr, uid, ids, context=context) - if ctx_opportunities : + if ctx_opportunities: first_opportunity = ctx_opportunities[0] tail_opportunities = opportunities_list + ctx_opportunities[1:] else: first_opportunity = opportunities_list[0] tail_opportunities = opportunities_list[1:] - fields = ['partner_id', 'title', 'name', 'categ_ids', 'channel_id', 'city', 'company_id', 'contact_name', 'country_id', 'type_id', 'user_id', 'section_id', 'state_id', 'description', 'email', 'fax', 'mobile', - 'partner_name', 'phone', 'probability', 'planned_revenue', 'street', 'street2', 'zip', 'create_date', 'date_action_last', - 'date_action_next', 'email_from', 'email_cc', 'partner_name'] - - data = self._merge_data(cr, uid, ids, oldest, fields, context=context) + merged_data = self._merge_data(cr, uid, ids, oldest, CRM_LEAD_FIELDS_TO_MERGE, context=context) # Merge messages and attachements into the first opportunity self._merge_opportunity_history(cr, uid, first_opportunity.id, tail_opportunities, context=context) self._merge_opportunity_attachments(cr, uid, first_opportunity.id, tail_opportunities, context=context) # Merge notifications about loss of information - self._merge_notification(cr, uid, first_opportunity, opportunities, context=context) + self._merge_notify(cr, uid, first_opportunity, opportunities, context=context) # Write merged data into first opportunity - self.write(cr, uid, [first_opportunity.id], data, context=context) + self.write(cr, uid, [first_opportunity.id], merged_data, context=context) # Delete tail opportunities self.unlink(cr, uid, [x.id for x in tail_opportunities], context=context) @@ -599,17 +672,17 @@ class crm_lead(base_stage, format_address, osv.osv): stage_id = stage_ids and stage_ids[0] or False return { - 'planned_revenue': lead.planned_revenue, - 'probability': lead.probability, - 'name': lead.name, - 'partner_id': customer and customer.id or False, - 'user_id': (lead.user_id and lead.user_id.id), - 'type': 'opportunity', - 'stage_id': stage_id or False, - 'date_action': time.strftime('%Y-%m-%d %H:%M:%S'), - 'date_open': time.strftime('%Y-%m-%d %H:%M:%S'), - 'email_from': customer and customer.email or lead.email_from, - 'phone': customer and customer.phone or lead.phone, + 'planned_revenue': lead.planned_revenue, + 'probability': lead.probability, + 'name': lead.name, + 'partner_id': customer and customer.id or False, + 'user_id': (lead.user_id and lead.user_id.id), + 'type': 'opportunity', + 'stage_id': stage_id or False, + 'date_action': fields.datetime.now(), + 'date_open': fields.datetime.now(), + 'email_from': customer and customer.email or lead.email_from, + 'phone': customer and customer.phone or lead.phone, } def convert_opportunity(self, cr, uid, ids, partner_id, user_ids=False, section_id=False, context=None): @@ -668,40 +741,67 @@ class crm_lead(base_stage, format_address, osv.osv): return partner_id def _lead_set_partner(self, cr, uid, lead, partner_id, context=None): + """ + Assign a partner to a lead. + + :param object lead: browse record of the lead to process + :param int partner_id: identifier of the partner to assign + :return bool: True if the partner has properly been assigned + """ res = False res_partner = self.pool.get('res.partner') if partner_id: res_partner.write(cr, uid, partner_id, {'section_id': lead.section_id.id or False}) contact_id = res_partner.address_get(cr, uid, [partner_id])['default'] - res = lead.write({'partner_id' : partner_id, }, context=context) + res = lead.write({'partner_id': partner_id}, context=context) self._lead_set_partner_send_note(cr, uid, [lead.id], context) return res - def convert_partner(self, cr, uid, ids, action='create', partner_id=False, context=None): + def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None): """ - Convert partner based on action. + Handle partner assignation during a lead conversion. if action is 'create', create new partner with contact and assign lead to new partner_id. - otherwise assign lead to specified partner_id + otherwise assign lead to the specified partner_id + + :param list ids: leads/opportunities ids to process + :param string action: what has to be done regarding partners (create it, assign an existing one, or nothing) + :param int partner_id: partner to assign if any + :return dict: dictionary organized as followed: {lead_id: partner_assigned_id} """ - if context is None: - context = {} + #TODO this is a duplication of the handle_partner_assignation method of crm_phonecall partner_ids = {} + # If a partner_id is given, force this partner for all elements force_partner_id = partner_id for lead in self.browse(cr, uid, ids, context=context): + # If the action is set to 'create' and no partner_id is set, create a new one if action == 'create': partner_id = force_partner_id or self._create_lead_partner(cr, uid, lead, context) self._lead_set_partner(cr, uid, lead, partner_id, context=context) partner_ids[lead.id] = partner_id return partner_ids - def allocate_salesman(self, cr, uid, ids, user_ids, team_id=False, context=None): + def allocate_salesman(self, cr, uid, ids, user_ids=None, team_id=False, context=None): + """ + Assign salesmen and salesteam to a batch of leads. If there are more + leads than salesmen, these salesmen will be assigned in round-robin. + E.g.: 4 salesmen (S1, S2, S3, S4) for 6 leads (L1, L2, ... L6). They + will be assigned as followed: L1 - S1, L2 - S2, L3 - S3, L4 - S4, + L5 - S1, L6 - S2. + + :param list ids: leads/opportunities ids to process + :param list user_ids: salesmen to assign + :param int team_id: salesteam to assign + :return bool + """ index = 0 + for lead_id in ids: value = {} if team_id: value['section_id'] = team_id if user_ids: value['user_id'] = user_ids[index] + # Cycle through user_ids index = (index + 1) % len(user_ids) if value: self.write(cr, uid, [lead_id], value, context=context) @@ -709,7 +809,7 @@ class crm_lead(base_stage, format_address, osv.osv): def schedule_phonecall(self, cr, uid, ids, schedule_time, call_summary, desc, phone, contact_name, user_id=False, section_id=False, categ_id=False, action='schedule', context=None): """ - action :('schedule','Schedule a call'), ('log','Log a call') + :param string action: ('schedule','Schedule a call'), ('log','Log a call') """ phonecall = self.pool.get('crm.phonecall') model_data = self.pool.get('ir.model.data') @@ -724,17 +824,17 @@ class crm_lead(base_stage, format_address, osv.osv): if not user_id: user_id = lead.user_id and lead.user_id.id or False vals = { - 'name' : call_summary, - 'opportunity_id' : lead.id, - 'user_id' : user_id or False, - 'categ_id' : categ_id or False, - 'description' : desc or '', - 'date' : schedule_time, - 'section_id' : section_id or False, - 'partner_id': lead.partner_id and lead.partner_id.id or False, - 'partner_phone' : phone or lead.phone or (lead.partner_id and lead.partner_id.phone or False), - 'partner_mobile' : lead.partner_id and lead.partner_id.mobile or False, - 'priority': lead.priority, + 'name': call_summary, + 'opportunity_id': lead.id, + 'user_id': user_id or False, + 'categ_id': categ_id or False, + 'description': desc or '', + 'date': schedule_time, + 'section_id': section_id or False, + 'partner_id': lead.partner_id and lead.partner_id.id or False, + 'partner_phone': phone or lead.phone or (lead.partner_id and lead.partner_id.phone or False), + 'partner_mobile': lead.partner_id and lead.partner_id.mobile or False, + 'priority': lead.priority, } new_id = phonecall.create(cr, uid, vals, context=context) phonecall.case_open(cr, uid, [new_id], context=context) @@ -744,30 +844,50 @@ class crm_lead(base_stage, format_address, osv.osv): self.schedule_phonecall_send_note(cr, uid, [lead.id], new_id, action, context=context) return phonecall_dict - def redirect_opportunity_view(self, cr, uid, opportunity_id, context=None): models_data = self.pool.get('ir.model.data') - # Get Opportunity views - form_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor') - tree_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_tree_view_oppor') + # Get opportunity views + dummy, form_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_form_view_oppor') + dummy, tree_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_tree_view_oppor') + return { + 'name': _('Opportunity'), + 'view_type': 'form', + 'view_mode': 'tree, form', + 'res_model': 'crm.lead', + 'domain': [('type', '=', 'opportunity')], + 'res_id': int(opportunity_id), + 'view_id': False, + 'views': [(form_view or False, 'form'), + (tree_view or False, 'tree'), + (False, 'calendar'), (False, 'graph')], + 'type': 'ir.actions.act_window', + } + + def redirect_lead_view(self, cr, uid, lead_id, context=None): + models_data = self.pool.get('ir.model.data') + + # Get lead views + dummy, form_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_form_view_leads') + dummy, tree_view = models_data.get_object_reference(cr, uid, 'crm', 'crm_case_tree_view_leads') return { - 'name': _('Opportunity'), - 'view_type': 'form', - 'view_mode': 'tree, form', - 'res_model': 'crm.lead', - 'domain': [('type', '=', 'opportunity')], - 'res_id': int(opportunity_id), - 'view_id': False, - 'views': [(form_view and form_view[1] or False, 'form'), - (tree_view and tree_view[1] or False, 'tree'), - (False, 'calendar'), (False, 'graph')], - 'type': 'ir.actions.act_window', + 'name': _('Lead'), + 'view_type': 'form', + 'view_mode': 'tree, form', + 'res_model': 'crm.lead', + 'domain': [('type', '=', 'lead')], + 'res_id': int(lead_id), + 'view_id': False, + 'views': [(form_view or False, 'form'), + (tree_view or False, 'tree'), + (False, 'calendar'), (False, 'graph')], + 'type': 'ir.actions.act_window', } def action_makeMeeting(self, cr, uid, ids, context=None): - """ This opens Meeting's calendar view to schedule meeting on current Opportunity - @return : Dictionary value for created Meeting view + """ + Open meeting's calendar view to schedule meeting on current opportunity. + :return dict: dictionary value for created Meeting view """ opportunity = self.browse(cr, uid, ids[0], context) res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context) diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index 913ce9fc612..6d392097d84 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -247,7 +247,7 @@ Andrew</field> <field name="contact_name">Bojing Hú</field> <field name="partner_name">Incom Corporation</field> <field name="email_from">bhu.a100@gmail.com</field> - <field name="partner_id" ref=""/> + <field name="partner_id" ref="base.res_partner_1"/> <field name="country_id" ref="base.cn"/> <field name="city">Shenzhen</field> <field name="type_id" ref="type_lead3"/> @@ -548,6 +548,31 @@ Andrew</field> <field eval="1" name="active"/> </record> + <record id="crm_case_26" model="crm.lead"> + <field name="type">opportunity</field> + <field name="name">Would appreciate more information about your products</field> + <field eval="11000" name="planned_revenue"/> + <field eval="45.0" name="probability"/> + <field name="partner_name">Agrolait</field> + <field name="email_from">info@agrolait.com</field> + <field name="partner_id" ref="base.res_partner_2"/> + <field name="street">69 rue de Chimay</field> + <field name="country_id" ref="base.be"/> + <field name="city">Wavre</field> + <field name="zip">1300</field> + <field name="type_id" ref="type_lead3"/> + <field name="categ_ids" eval="[(6, 0, [categ_oppor2])]"/> + <field name="channel_id" ref="crm_case_channel_website"/> + <field name="priority">3</field> + <field eval="time.strftime('%Y-%m-23')" name="date_deadline"/> + <field eval="time.strftime('%Y-%m-10')" name="date_action"/> + <field name="title_action">Send Catalogue by Email</field> + <field name="section_id" ref="crm_case_section_4"/> + <field name="user_id" ref="base.user_demo"/> + <field name="stage_id" ref="crm.stage_lead3"/> + <field eval="1" name="active"/> + </record> + <!-- Unsubscribe Admin from case15, subscribe Demo --> <record id="crm_case_15" model="crm.lead"> <field name="message_follower_ids" eval="[(3, ref('base.partner_root')), (4, ref('base.partner_demo'))]"/> diff --git a/addons/crm/crm_lead_menu.xml b/addons/crm/crm_lead_menu.xml index 208d92a6684..8c38ed41a1c 100644 --- a/addons/crm/crm_lead_menu.xml +++ b/addons/crm/crm_lead_menu.xml @@ -12,7 +12,7 @@ <field name="context">{'default_type':'lead', 'stage_type':'lead'}</field> <field name="help" type="html"> <p class="oe_view_nocontent_create"> - Click to create an unqualified lead. + Click to create an unqualified lead. </p><p> Use leads if you need a qualification step before creating an opportunity or a customer. It can be a business card you received, @@ -39,48 +39,48 @@ <field name="act_window_id" ref="crm_case_category_act_leads_all"/> </record> - <record model="ir.actions.act_window" id="crm_case_category_act_oppor11"> - <field name="name">Opportunities</field> - <field name="res_model">crm.lead</field> - <field name="view_mode">kanban,tree,graph,form,calendar</field> - <field name="domain">[('type','=','opportunity')]</field> - <field name="context">{'stage_type': 'opportunity', 'default_type': 'opportunity'}</field> - <field name="view_id" eval="False"/> - <field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/> - <field name="help" type="html"> - <p class="oe_view_nocontent_create"> - Click to create a new opportunity. - </p><p> - OpenERP helps you keep track of your sales pipeline to follow - up potential sales and better forecast your future revenues. - </p><p> - You will be able to plan meetings and phone calls from - opportunities, convert them into quotations, attach related - documents, track all discussions, and much more. - </p> - </field> - </record> + <record model="ir.actions.act_window" id="crm_case_category_act_oppor11"> + <field name="name">Opportunities</field> + <field name="res_model">crm.lead</field> + <field name="view_mode">kanban,tree,graph,form,calendar</field> + <field name="domain">[('type','=','opportunity')]</field> + <field name="context">{'stage_type': 'opportunity', 'default_type': 'opportunity', 'default_user_id': uid}</field> + <field name="view_id" eval="False"/> + <field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/> + <field name="help" type="html"> + <p class="oe_view_nocontent_create"> + Click to create a new opportunity. + </p><p> + OpenERP helps you keep track of your sales pipeline to follow + up potential sales and better forecast your future revenues. + </p><p> + You will be able to plan meetings and phone calls from + opportunities, convert them into quotations, attach related + documents, track all discussions, and much more. + </p> + </field> + </record> - <record model="ir.actions.act_window.view" id="action_crm_tag_kanban_view_oppor11"> - <field name="sequence" eval="0"/> - <field name="view_mode">kanban</field> - <field name="view_id" ref="crm_case_kanban_view_leads"/> - <field name="act_window_id" ref="crm_case_category_act_oppor11"/> - </record> + <record model="ir.actions.act_window.view" id="action_crm_tag_kanban_view_oppor11"> + <field name="sequence" eval="0"/> + <field name="view_mode">kanban</field> + <field name="view_id" ref="crm_case_kanban_view_leads"/> + <field name="act_window_id" ref="crm_case_category_act_oppor11"/> + </record> - <record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_oppor11"> - <field name="sequence" eval="1"/> - <field name="view_mode">tree</field> - <field name="view_id" ref="crm_case_tree_view_oppor"/> - <field name="act_window_id" ref="crm_case_category_act_oppor11"/> - </record> + <record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_oppor11"> + <field name="sequence" eval="1"/> + <field name="view_mode">tree</field> + <field name="view_id" ref="crm_case_tree_view_oppor"/> + <field name="act_window_id" ref="crm_case_category_act_oppor11"/> + </record> - <record model="ir.actions.act_window.view" id="action_crm_tag_form_view_oppor11"> - <field name="sequence" eval="2"/> - <field name="view_mode">form</field> - <field name="view_id" ref="crm_case_form_view_oppor"/> - <field name="act_window_id" ref="crm_case_category_act_oppor11"/> - </record> + <record model="ir.actions.act_window.view" id="action_crm_tag_form_view_oppor11"> + <field name="sequence" eval="2"/> + <field name="view_mode">form</field> + <field name="view_id" ref="crm_case_form_view_oppor"/> + <field name="act_window_id" ref="crm_case_category_act_oppor11"/> + </record> <menuitem name="Sales" id="base.menu_sales" parent="base.menu_base_partner" sequence="1" /> <menuitem name="Leads" id="menu_crm_leads" parent="base.menu_sales" action="crm_case_category_act_leads_all" sequence="2" /> diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 3710df3138f..c64dca542b0 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -116,8 +116,11 @@ <group> <group> <field name="partner_name" string="Company Name"/> + <!-- Preload all the partner's information --> <field name="partner_id" string="Customer" - on_change="on_change_partner(partner_id)" options='{"create_name_field": "name"}'/> + on_change="on_change_partner(partner_id)" + options='{"create_name_field": "name"}' + context="{'default_name': contact_name, 'default_street': street, 'default_city': city, 'default_state_id': state_id, 'default_zip': zip, 'default_country_id': country_id, 'default_function': function, 'default_phone': phone, 'default_mobile': mobile, 'default_fax': fax, 'default_email': email_from, 'default_user_id': user_id, 'default_section_id': section_id}"/> <label for="street" string="Address"/> <div> <field name="street" placeholder="Street..."/> @@ -133,7 +136,7 @@ <group> <label for="contact_name"/> <div> - <field name="contact_name" class="oe_inline"/>, + <field name="contact_name" class="oe_inline"/><span attrs="{'invisible': [('title', '=', '')]}">, </span> <field name="title" placeholder="Title" domain="[('domain', '=', 'contact')]" class="oe_inline" options='{"no_open": True}'/> </div> <field name="email_from" widget="email"/> @@ -206,7 +209,6 @@ </field> </record> - <!-- CRM Lead Tree View --> <record model="ir.ui.view" id="crm_case_tree_view_leads"> <field name="name">Leads</field> diff --git a/addons/crm/crm_phonecall.py b/addons/crm/crm_phonecall.py index 9b63f984b4a..0d17e0f3a68 100644 --- a/addons/crm/crm_phonecall.py +++ b/addons/crm/crm_phonecall.py @@ -182,17 +182,23 @@ class crm_phonecall(base_state, osv.osv): 'phone': phonecall.partner_phone, }) - def convert_partner(self, cr, uid, ids, action='create', partner_id=False, context=None): + def handle_partner_assignation(self, cr, uid, ids, action='create', partner_id=False, context=None): """ - This function convert partner based on action. + Handle partner assignation during a lead conversion. if action is 'create', create new partner with contact and assign lead to new partner_id. otherwise assign lead to specified partner_id + + :param list ids: phonecalls ids to process + :param string action: what has to be done regarding partners (create it, assign an existing one, or nothing) + :param int partner_id: partner to assign if any + :return dict: dictionary organized as followed: {lead_id: partner_assigned_id} """ - if context is None: - context = {} + #TODO this is a duplication of the handle_partner_assignation method of crm_lead partner_ids = {} + # If a partner_id is given, force this partner for all elements force_partner_id = partner_id for call in self.browse(cr, uid, ids, context=context): + # If the action is set to 'create' and no partner_id is set, create a new one if action == 'create': partner_id = force_partner_id or self._call_create_partner(cr, uid, call, context=context) self._call_create_partner_address(cr, uid, call, partner_id, context=context) @@ -219,7 +225,6 @@ class crm_phonecall(base_state, osv.osv): } return value - def convert_opportunity(self, cr, uid, ids, opportunity_summary=False, partner_id=False, planned_revenue=0.0, probability=0.0, context=None): partner = self.pool.get('res.partner') opportunity = self.pool.get('crm.lead') @@ -256,8 +261,9 @@ class crm_phonecall(base_state, osv.osv): return opportunity_dict def action_make_meeting(self, cr, uid, ids, context=None): - """ This opens Meeting's calendar view to schedule meeting on current Phonecall - @return : Dictionary value for created Meeting view + """ + Open meeting's calendar view to schedule a meeting on current phonecall. + :return dict: dictionary value for created meeting view """ phonecall = self.browse(cr, uid, ids[0], context) res = self.pool.get('ir.actions.act_window').for_xml_id(cr, uid, 'base_calendar', 'action_crm_meeting', context) @@ -270,14 +276,27 @@ class crm_phonecall(base_state, osv.osv): 'default_name': phonecall.name, } return res - + + def action_button_convert2opportunity(self, cr, uid, ids, context=None): + """ + Convert a phonecall into an opp and then redirect to the opp view. + + :param list ids: list of calls ids to convert (typically contains a single id) + :return dict: containing view information + """ + if len(ids) != 1: + raise osv.except_osv(_('Warning!'),_('It\'s only possible to convert one phonecall at a time.')) + + opportunity_dict = self.convert_opportunity(cr, uid, ids, context=context) + return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, opportunity_dict[ids[0]], context) + # ---------------------------------------- # OpenChatter # ---------------------------------------- def case_get_note_msg_prefix(self, cr, uid, id, context=None): return 'Phonecall' - + def case_reset_send_note(self, cr, uid, ids, context=None): message = _('Phonecall has been <b>reset and set as open</b>.') return self.message_post(cr, uid, ids, body=message, context=context) @@ -300,5 +319,4 @@ class crm_phonecall(base_state, osv.osv): def _call_set_partner_send_note(self, cr, uid, ids, context=None): return self.message_post(cr, uid, ids, body=_("Partner has been <b>created</b>."), context=context) - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/crm_phonecall_view.xml b/addons/crm/crm_phonecall_view.xml index 3dc93125ced..e070f8fd8d3 100644 --- a/addons/crm/crm_phonecall_view.xml +++ b/addons/crm/crm_phonecall_view.xml @@ -3,7 +3,7 @@ <data> -<!-- Phonecall Categories Form View --> + <!-- Phonecall Categories Form View --> <record id="crm_phonecall_categ_action" model="ir.actions.act_window"> <field name="name">Phonecall Categories</field> <field name="res_model">crm.case.categ</field> @@ -24,8 +24,7 @@ <menuitem action="crm_phonecall_categ_action" name="Categories" id="menu_crm_case_phonecall-act" parent="menu_crm_config_phonecall" groups="base.group_no_one"/> -<!-- PhoneCalls Tree View --> - + <!-- Phonecalls Tree View --> <record model="ir.ui.view" id="crm_case_phone_tree_view"> <field name="name">CRM - Phone Calls Tree</field> <field name="model">crm.phonecall</field> @@ -35,13 +34,14 @@ <field name="name"/> <field name="partner_id"/> <button string="Meeting" - states="open,pending" icon="gtk-redo" - name="action_make_meeting" type="object"/> + states="open,pending" + name="action_make_meeting" + type="object"/> <button string="Convert to Opportunity" - name="%(phonecall2opportunity_act)d" + type="object" + name="action_button_convert2opportunity" states="open,pending" - icon="gtk-index" - type="action" attrs="{'invisible':[('opportunity_id','!=',False)]}"/> + attrs="{'invisible':[('opportunity_id','!=',False)]}"/> <field name="partner_phone"/> <field name="user_id"/> <field name="categ_id" invisible="1"/> @@ -60,27 +60,26 @@ </field> </record> -<!-- Phonecalls Form View --> - + <!-- Phonecalls Form View --> <record model="ir.ui.view" id="crm_case_phone_form_view"> <field name="name">CRM - Phone Call Form</field> <field name="model">crm.phonecall</field> <field name="arch" type="xml"> <form string="Phone Call" version="7.0"> - <header> - <button name="case_close" string="Call Done" type="object" class="oe_highlight" - states="open,pending"/> - <button name="case_reset" string="Reset to Todo" type="object" - states="cancel"/> - <button name="case_cancel" string="Cancel" type="object" - states="draft,open,pending"/> - <field name="state" widget="statusbar" nolabel="1" statusbar_visible="open,done"/> - </header> + <header> + <button name="case_close" string="Call Done" type="object" class="oe_highlight" + states="open,pending"/> + <button name="case_reset" string="Reset to Todo" type="object" + states="cancel"/> + <button name="case_cancel" string="Cancel" type="object" + states="draft,open,pending"/> + <field name="state" widget="statusbar" nolabel="1" statusbar_visible="open,done"/> + </header> <sheet string="Phone Call"> - <div class="oe_right"> + <div class="oe_right"> <button string="Convert to Opportunity" - name="%(phonecall2opportunity_act)d" - type="action" + type="object" + name="action_button_convert2opportunity" attrs="{'invisible':[ '|', ('opportunity_id','!=',False), ('state', 'not in', ('open', 'done'))]}"/> <button string="Schedule Other Call" name="%(phonecall_to_phonecall_act)d" @@ -97,7 +96,7 @@ </div> <h2><field name="partner_phone"/></h2> </div> - + <group col="4"> <field name="date"/> <field name="user_id"/> @@ -122,7 +121,6 @@ </record> <!--Logged Phonecalls Tree View --> - <record model="ir.ui.view" id="crm_case_inbound_phone_tree_view"> <field name="name">CRM - Logged Phone Calls Tree</field> <field name="model">crm.phonecall</field> @@ -149,7 +147,7 @@ icon="gtk-redo" name="action_make_meeting" type="object"/> <button string="Convert to Opportunity" - name="%(phonecall2opportunity_act)d" + name="action_button_convert2opportunity" states="open,pending" icon="gtk-index" type="action" attrs="{'invisible':[('opportunity_id','!=',False)]}"/> @@ -158,7 +156,6 @@ </record> <!-- Phonecalls Calendar View --> - <record model="ir.ui.view" id="crm_case_phone_calendar_view"> <field name="name">CRM - Phone Calls Calendar</field> <field name="model">crm.phonecall</field> diff --git a/addons/crm/crm_view.xml b/addons/crm/crm_view.xml index 99d3752529e..529388c827d 100644 --- a/addons/crm/crm_view.xml +++ b/addons/crm/crm_view.xml @@ -293,7 +293,7 @@ <field name="view_id" ref="crm_case_resource_type_tree"/> </record> - <menuitem action="crm_case_resource_type_act" + <menuitem action="crm_case_resource_type_act" id="menu_crm_case_resource_type_act" sequence="4" groups="base.group_no_one" parent="base.menu_crm_config_lead"/> diff --git a/addons/crm/test/process/communication_with_customer.yml b/addons/crm/test/process/communication_with_customer.yml index 2137616ae3e..148d08dfc49 100644 --- a/addons/crm/test/process/communication_with_customer.yml +++ b/addons/crm/test/process/communication_with_customer.yml @@ -33,20 +33,4 @@ - !python {model: crm.lead}: | lead_ids = self.search(cr, uid, [('email_from','=', 'Mr. John Right <info@customer.com>')]) - self.convert_partner(cr, uid, lead_ids, context=context) -- - I convert one phonecall request to a customer and put him into regular customer list. -- - !python {model: crm.phonecall2partner}: | - context.update({'active_model': 'crm.phonecall', 'active_ids': [ref("crm.crm_phonecall_4")], 'active_id': ref("crm.crm_phonecall_4")}) - new_id = self.create(cr, uid, {}, context=context) - self.make_partner(cr, uid, [new_id], context=context) -- - I check converted phonecall to partner. -- - !python {model: res.partner}: | - partner_id = self.search(cr, uid, [('phonecall_ids', '=', ref('crm.crm_phonecall_4'))]) - assert partner_id, "Customer is not found in regular customer list." - data = self.browse(cr, uid, partner_id, context=context)[0] - assert data.user_id.id == ref("base.user_root"), "User not assigned properly" - assert data.name == "Wanted information about pricing of laptops", "Bad partner name" + self.handle_partner_assignation(cr, uid, lead_ids, context=context) diff --git a/addons/crm/test/process/lead2opportunity2win.yml b/addons/crm/test/process/lead2opportunity2win.yml index dc5aadbcf16..94e7149ef77 100644 --- a/addons/crm/test/process/lead2opportunity2win.yml +++ b/addons/crm/test/process/lead2opportunity2win.yml @@ -1,26 +1,16 @@ - - In order to test convert customer lead into opportunity, + In order to test the conversion of a lead into a opportunity, - - I open customer lead. + I open a lead. - !python {model: crm.lead}: | self.case_open(cr, uid, [ref("crm_case_4")]) - - I check lead state is "Open". + I check if the lead state is "Open". - - !assert {model: crm.lead, id: crm.crm_case_4, string: Lead in open state}: + !assert {model: crm.lead, id: crm.crm_case_4, string: Lead state is Open}: - state == "open" - - I fill in a lead2partner wizard. -- - !record {model: crm.lead2partner, id: crm_lead2partner_id1, context: '{"active_model": "crm.lead", "active_ids": [ref("crm_case_4")]}'}: -- - I create a partner from the lead2partner wizard. -- - !python {model: crm.lead2partner}: | - context.update({'active_model': 'crm.lead', 'active_ids': [ref('crm_case_4')], 'active_id': ref('crm_case_4')}) - self.make_partner(cr, uid ,[ref("crm_lead2partner_id1")], context=context) -- I convert lead into opportunity for exiting customer. - !python {model: crm.lead}: | @@ -76,7 +66,7 @@ - !python {model: crm.lead2opportunity.partner.mass}: | context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_11"), ref("crm_case_2")], 'active_id': ref("crm_case_11")}) - id = self.create(cr, uid, {'user_ids': [ref('base.user_root')], 'section_id': ref('crm.section_sales_department')}, context=context) + id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('base.user_root')])], 'section_id': ref('crm.section_sales_department')}, context=context) self.mass_convert(cr, uid, [id], context=context) - Now I check first lead converted on opportunity. diff --git a/addons/crm/test/process/lead2opportunity_assign_salesmen.yml b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml new file mode 100644 index 00000000000..b538a3a0185 --- /dev/null +++ b/addons/crm/test/process/lead2opportunity_assign_salesmen.yml @@ -0,0 +1,70 @@ +- + During a lead to opp conversion, salesmen should be assigned to leads following the round-robin method. Start by creating 6 leads (1 to 6) and 4 salesmen (A to D). +- + !record {model: crm.lead, id: test_crm_lead_01}: + type: 'lead' + name: 'Test lead 1' + stage_id: stage_lead1 +- + !record {model: crm.lead, id: test_crm_lead_02}: + type: 'lead' + name: 'Test lead 2' + stage_id: stage_lead1 +- + !record {model: crm.lead, id: test_crm_lead_03}: + type: 'lead' + name: 'Test lead 3' + stage_id: stage_lead1 +- + !record {model: crm.lead, id: test_crm_lead_04}: + type: 'lead' + name: 'Test lead 4' + stage_id: stage_lead1 +- + !record {model: crm.lead, id: test_crm_lead_05}: + type: 'lead' + name: 'Test lead 5' + stage_id: stage_lead1 +- + !record {model: crm.lead, id: test_crm_lead_06}: + type: 'lead' + name: 'Test lead 6' + stage_id: stage_lead1 +- + !record {model: res.users, id: test_res_user_01}: + name: 'Test user A' + login: 'tua' + new_password: 'tua' +- + !record {model: res.users, id: test_res_user_02}: + name: 'Test user B' + login: 'tub' + new_password: 'tub' +- + !record {model: res.users, id: test_res_user_03}: + name: 'Test user C' + login: 'tuc' + new_password: 'tuc' +- + !record {model: res.users, id: test_res_user_04}: + name: 'Test user D' + login: 'tud' + new_password: 'tud' +- + I create a mass convert wizard and convert all the leads. +- + !python {model: crm.lead2opportunity.partner.mass}: | + context.update({'active_model': 'crm.lead', 'active_ids': [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], 'active_id': ref("test_crm_lead_01")}) + id = self.create(cr, uid, {'user_ids': [(6, 0, [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')])], 'section_id': ref('crm.section_sales_department')}, context=context) + self.mass_convert(cr, uid, [id], context=context) +- + The leads should now be opps with a salesman and a salesteam. Also, salesmen should have been assigned following a round-robin method. +- + !python {model: crm.lead}: | + opps = self.browse(cr, uid, [ref("test_crm_lead_01"), ref("test_crm_lead_02"), ref("test_crm_lead_03"), ref("test_crm_lead_04"), ref("test_crm_lead_05"), ref("test_crm_lead_06")], context) + salesmen_ids = [ref('test_res_user_01'), ref('test_res_user_02'), ref('test_res_user_03'), ref('test_res_user_04')] + i = 0 + for opp in opps: + assert opp.type == 'opportunity', 'Type mismatch: this should be an opp, not a lead' + assert opp.user_id.id == salesmen_ids[i], 'Salesman mismatch: expected salesman %r, got %r' % (salesmen_ids[i], opp.user_id.id) + i = i+1 if (i < len(salesmen_ids) - 1) else 0
\ No newline at end of file diff --git a/addons/crm/test/process/merge_opportunity.yml b/addons/crm/test/process/merge_opportunity.yml index 155cd68274f..2f0b8e82956 100644 --- a/addons/crm/test/process/merge_opportunity.yml +++ b/addons/crm/test/process/merge_opportunity.yml @@ -1,34 +1,126 @@ + - - I make an opportunity from customer. + During a mixed merge (involving leads and opps), data should be handled a certain way following their type (m2o, m2m, text, ...) Start by creating two leads and an opp. - - !python {model: crm.partner2opportunity}: | - context.update({'active_model': 'res.partner', 'active_ids': [ref("base.res_partner_9")]}) - res_id = self.create(cr, uid, {'name': "Enquête pour l'achat de services"}, context=context) - self.make_opportunity(cr, uid, [res_id], context=context) + !record {model: crm.lead, id: test_crm_lead_01}: + type: 'lead' + name: 'Test lead 1' + partner_id: base.res_partner_1 + stage_id: stage_lead1 + description: This is the description of the test lead 1. - - I make another opportunity from a phone call with the same customer. + !record {model: crm.lead, id: test_crm_lead_02}: + type: 'lead' + name: 'Test lead 2' + partner_id: base.res_partner_3 + stage_id: stage_lead1 + description: This is the description of the test lead 2. - - !python {model: crm.phonecall2opportunity}: | - context.update({'active_model': 'crm.phonecall', 'active_ids': [ref("crm.crm_phonecall_6")]}) - res_id = self.create(cr, uid, {'name': "À propos du prix de votre autre service", 'partner_id': ref("base.res_partner_9")}, context=context) - self.make_opportunity(cr, uid, [res_id], context=context) + !record {model: crm.lead, id: test_crm_opp_01}: + type: 'opportunity' + name: 'Test opportunity 1' + partner_id: base.res_partner_5 + stage_id: stage_lead1 + description: This is the description of the test opp 1. - - Now I merge these opportunities. + !python {model: crm.lead}: | + lead_ids = [ref('test_crm_lead_01'), ref('test_crm_lead_02'), ref('test_crm_opp_01')] + context.update({'active_model': 'crm.lead', 'active_ids': lead_ids, 'active_id': lead_ids[0]}) +- + I create a merge wizard and merge the leads and opp together in the first item of the list. +- + !record {model: crm.merge.opportunity, id: merge_opp_wizard_01}: +- + !python {model: crm.merge.opportunity}: | + self.action_merge(cr, uid, [ref("merge_opp_wizard_01")], context=context) +- + I check for the resulting merged opp (based on name and partner). +- + !python {model: crm.lead}: | + merge_id = self.search(cr, uid, [('name', '=', 'Test lead 1'), ('partner_id','=', ref("base.res_partner_1"))]) + assert merge_id, 'Fail to create merge opportunity wizard' + merge_result = self.browse(cr, uid, merge_id)[0] + assert merge_result.partner_id.id == ref("base.res_partner_1"), 'Partner mismatch: when merging leads/opps with different m2o values, the first not null value prevails (the other are dropped)' + assert merge_result.description == 'This is the description of the test lead 1., This is the description of the test lead 2., This is the description of the test opp 1.', 'Description mismatch: when merging leads/opps with different text values, these values should get concatenated and separeted with ", "' + assert merge_result.type == 'opportunity', 'Type mismatch: when at least one opp in involved in the merge, the result should be a new opp (instead of %s)' % merge_result.type +- + The other (tailing) leads/opps shouldn't exist anymore. +- + !python {model: crm.lead}: | + tailing_lead = self.search(cr, uid, [('id', '=', ref('test_crm_lead_02'))]) + assert not tailing_lead, 'This tailing lead (id %s) should not exist anymore' % ref('test_crm_lead_02') + + tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_opp_01'))]) + assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_01') +- + I want to test leads merge. Start by creating two leads (with the same partner). +- + !record {model: crm.lead, id: test_crm_lead_03}: + type: 'lead' + name: 'Test lead 3' + partner_id: base.res_partner_1 + stage_id: stage_lead1 +- + !record {model: crm.lead, id: test_crm_lead_04}: + type: 'lead' + name: 'Test lead 4' + partner_id: base.res_partner_1 + stage_id: stage_lead1 - !python {model: crm.lead}: | - opportunity_ids = self.search(cr, uid, [('partner_id','=', ref("base.res_partner_9"))]) - context.update({'active_model': 'crm.lead', 'active_ids': opportunity_ids, 'active_id': opportunity_ids[0]}) + lead_ids = [ref('test_crm_lead_03'), ref('test_crm_lead_04')] + context.update({'active_model': 'crm.lead', 'active_ids': lead_ids, 'active_id': lead_ids[0]}) +- + I create a merge wizard and merge the leads together. +- + !record {model: crm.merge.opportunity, id: merge_opp_wizard_02}: +- + !python {model: crm.merge.opportunity}: | + self.action_merge(cr, uid, [ref("merge_opp_wizard_02")], context=context) +- + I check for the resulting merged lead (based on name and partner). +- + !python {model: crm.lead}: | + merge_id = self.search(cr, uid, [('name', '=', 'Test lead 3'), ('partner_id','=', ref("base.res_partner_1"))]) + assert merge_id, 'Fail to create merge opportunity wizard' + merge_result = self.browse(cr, uid, merge_id)[0] + assert merge_result.partner_id.id == ref("base.res_partner_1"), 'Partner mismatch' + assert merge_result.type == 'lead', 'Type mismatch: when leads get merged together, the result should be a new lead (instead of %s)' % merge_result.type + tailing_lead = self.search(cr, uid, [('id', '=', ref('test_crm_lead_04'))]) + assert not tailing_lead, 'This tailing lead (id %s) should not exist anymore' % ref('test_crm_lead_04') +- + I want to test opps merge. Start by creating two opportunities (with the same partner). +- + !record {model: crm.lead, id: test_crm_opp_02}: + type: 'opportunity' + name: 'Test opportunity 2' + partner_id: base.res_partner_5 + stage_id: stage_lead1 +- + !record {model: crm.lead, id: test_crm_opp_03}: + type: 'opportunity' + name: 'Test opportunity 3' + partner_id: base.res_partner_5 + stage_id: stage_lead1 +- + !python {model: crm.lead}: | + opp_ids = [ref('test_crm_opp_02'), ref('test_crm_opp_03')] + context.update({'active_model': 'crm.lead', 'active_ids': opp_ids, 'active_id': opp_ids[0]}) +- + I create a merge wizard and merge the opps together. - - !record {model: crm.merge.opportunity, id: opportunity_merge_id }: + !record {model: crm.merge.opportunity, id: merge_opp_wizard_03}: - !python {model: crm.merge.opportunity}: | - self.action_merge(cr, uid, [ref("opportunity_merge_id")], context=context) + self.action_merge(cr, uid, [ref("merge_opp_wizard_03")], context=context) - - I check for merged opportunities for customer. + I check for the resulting merged opp (based on name and partner). - !python {model: crm.lead}: | - merge_id = self.search(cr, uid, [('partner_id','=', ref("base.res_partner_9"))]) - assert merge_id, 'Fail to create Merge opportunity' - merge_data = self.browse(cr, uid, merge_id)[0] - assert merge_data.type == 'opportunity', 'Merged opportunity type not change!' - assert merge_data.partner_id.id == ref("base.res_partner_9"), 'Partner missmatch!' + merge_id = self.search(cr, uid, [('name', '=', 'Test opportunity 2'), ('partner_id','=', ref("base.res_partner_5"))]) + assert merge_id, 'Fail to create merge opportunity wizard' + merge_result = self.browse(cr, uid, merge_id)[0] + assert merge_result.partner_id.id == ref("base.res_partner_5"), 'Partner mismatch' + assert merge_result.type == 'opportunity', 'Type mismatch: when opps get merged together, the result should be a new opp (instead of %s)' % merge_result.type + tailing_opp = self.search(cr, uid, [('id', '=', ref('test_crm_opp_03'))]) + assert not tailing_opp, 'This tailing opp (id %s) should not exist anymore' % ref('test_crm_opp_03')
\ No newline at end of file diff --git a/addons/crm/test/ui/crm_demo.yml b/addons/crm/test/ui/crm_demo.yml index 842b3a4e887..9154ffdf513 100644 --- a/addons/crm/test/ui/crm_demo.yml +++ b/addons/crm/test/ui/crm_demo.yml @@ -1,5 +1,5 @@ - - I create lead record to call of partner onchange, stage onchange and Mailing opt-in onchange method. + I create a lead record to call a partner onchange, stage onchange and mailing opt-in onchange method. - !record {model: crm.lead, id: crm_case_25}: name: 'Need more info about your pc2' @@ -8,7 +8,7 @@ stage_id: crm.stage_lead1 state: draft - - I create lead record to call Mailing opt-out onchange method. + I create a lead record to call a mailing opt-out onchange method. - !record {model: crm.lead, id: crm_case_18}: name: 'Need 20 Days of Consultancy' @@ -16,13 +16,13 @@ state: draft opt_out: True - - I create phonecall record to call partner onchange method. + I create a phonecall record to call a partner onchange method. - !record {model: crm.phonecall, id: crm_phonecall_5}: name: 'Bad time' partner_id: base.res_partner_5 - - I setting next stage "New" for the lead. + I set the next stage to "New" for the lead. - !python {model: crm.lead}: | self.stage_next(cr, uid, [ref("crm_case_4")], context={'stage_type': 'lead'}) diff --git a/addons/crm/wizard/__init__.py b/addons/crm/wizard/__init__.py index 15ca8df670d..7bc2c97e5dd 100644 --- a/addons/crm/wizard/__init__.py +++ b/addons/crm/wizard/__init__.py @@ -19,16 +19,10 @@ # ############################################################################## -import crm_lead_to_partner -import crm_lead_to_opportunity +import crm_partner_binding import crm_phonecall_to_phonecall import crm_opportunity_to_phonecall -import crm_phonecall_to_partner -import crm_partner_to_opportunity -import crm_phonecall_to_opportunity - +import crm_lead_to_opportunity import crm_merge_opportunities - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/addons/crm/wizard/crm_lead_to_opportunity.py b/addons/crm/wizard/crm_lead_to_opportunity.py index ea96e1d16f1..431070ff8f8 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity.py +++ b/addons/crm/wizard/crm_lead_to_opportunity.py @@ -27,16 +27,13 @@ import re class crm_lead2opportunity_partner(osv.osv_memory): _name = 'crm.lead2opportunity.partner' _description = 'Lead To Opportunity Partner' - _inherit = 'crm.lead2partner' + _inherit = 'crm.partner.binding' _columns = { - 'action': fields.selection([('exist', 'Link to an existing customer'), \ - ('create', 'Create a new customer'), \ - ('nothing', 'Do not link to a customer')], \ - 'Related Customer', required=True), - 'name': fields.selection([('convert', 'Convert to opportunity'), \ - ('merge', 'Merge with existing opportunities')], \ - 'Conversion Action', required=True), + 'name': fields.selection([ + ('convert', 'Convert to opportunity'), + ('merge', 'Merge with existing opportunities') + ], 'Conversion Action', required=True), 'opportunity_ids': fields.many2many('crm.lead', string='Opportunities', domain=[('type', '=', 'opportunity')]), } @@ -157,8 +154,8 @@ class crm_lead2opportunity_mass_convert(osv.osv_memory): _inherit = 'crm.lead2opportunity.partner' _columns = { - 'user_ids': fields.many2many('res.users', string='Salesmen'), - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), + 'user_ids': fields.many2many('res.users', string='Salesmen'), + 'section_id': fields.many2one('crm.case.section', 'Sales Team'), } def default_get(self, cr, uid, fields, context=None): diff --git a/addons/crm/wizard/crm_lead_to_opportunity_view.xml b/addons/crm/wizard/crm_lead_to_opportunity_view.xml index 3390ad8f547..1a7bd687a94 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_view.xml +++ b/addons/crm/wizard/crm_lead_to_opportunity_view.xml @@ -7,7 +7,7 @@ <field name="model">crm.lead2opportunity.partner</field> <field name="arch" type="xml"> <form string="Convert to Opportunity" version="7.0"> - <group> + <group name="name"> <field name="name" class="oe_inline"/> <field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}"> <tree> @@ -18,7 +18,7 @@ </tree> </field> </group> - <group attrs="{'invisible': [('name', '!=', 'convert')]}"> + <group name="action" attrs="{'invisible': [('name', '!=', 'convert')]}"> <field name="action" class="oe_inline"/> <field name="partner_id" attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}" @@ -51,21 +51,17 @@ <group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}"> <field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}"> <tree> - <field name="name" /> - <field name="partner_id" /> - <field name="user_id" /> - <field name="section_id" /> + <field name="name"/> + <field name="partner_id"/> + <field name="user_id"/> + <field name="section_id"/> </tree> </field> </group> <group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}"> - <field name="section_id" /> - <field name="user_ids" colspan="4"> - <tree> - <field name="name" /> - </tree> - </field> + <field name="section_id"/> + <field name="user_ids" widget="many2many_tags"/> </group> <footer> diff --git a/addons/crm/wizard/crm_lead_to_partner.py b/addons/crm/wizard/crm_lead_to_partner.py deleted file mode 100644 index c48990f5866..00000000000 --- a/addons/crm/wizard/crm_lead_to_partner.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- 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 osv, fields -from tools.translate import _ - -class crm_lead2partner(osv.osv_memory): - """ Converts lead to partner """ - _name = 'crm.lead2partner' - _description = 'Lead to Partner' - - _columns = { - 'action': fields.selection([('exist', 'Link to an existing customer'), \ - ('create', 'Create a new customer')], \ - 'Action', required=True), - 'partner_id': fields.many2one('res.partner', 'Customer'), - } - def view_init(self, cr, uid, fields, context=None): - """ - Check for precondition before wizard executes. - """ - if context is None: - context = {} - model = context.get('active_model') - model = self.pool.get(model) - rec_ids = context and context.get('active_ids', []) - for this in model.browse(cr, uid, rec_ids, context=context): - if this.partner_id: - raise osv.except_osv(_('Warning!'), - _('A partner is already defined.')) - - def _select_partner(self, cr, uid, context=None): - if context is None: - context = {} - if not context.get('active_model') == 'crm.lead' or not context.get('active_id'): - return False - partner = self.pool.get('res.partner') - lead = self.pool.get('crm.lead') - this = lead.browse(cr, uid, context.get('active_id'), context=context) - if this.partner_id: - return this.partner_id.id - partner_id = False - if this.email_from: - partner_ids = partner.search(cr, uid, [('email', '=', this.email_from)], context=context) - if partner_ids: - partner_id = partner_ids[0] - if not this.partner_id and this.partner_name: - partner_ids = partner.search(cr, uid, [('name', '=', this.partner_name)], context=context) - if partner_ids: - partner_id = partner_ids[0] - return partner_id - - def default_get(self, cr, uid, fields, context=None): - res = super(crm_lead2partner, self).default_get(cr, uid, fields, context=context) - partner_id = self._select_partner(cr, uid, context=context) - - if 'partner_id' in fields: - res.update({'partner_id': partner_id}) - if 'action' in fields: - res.update({'action': partner_id and 'exist' or 'create'}) - - return res - - def open_create_partner(self, cr, uid, ids, context=None): - """ - Open form of create partner. - """ - view_obj = self.pool.get('ir.ui.view') - view_id = view_obj.search(cr, uid, [('model', '=', self._name), \ - ('name', '=', self._name+'.view')]) - return { - 'view_mode': 'form', - 'view_type': 'form', - 'view_id': view_id or False, - 'res_model': self._name, - 'context': context, - 'type': 'ir.actions.act_window', - 'target': 'new', - } - - def _create_partner(self, cr, uid, ids, context=None): - """ - Create partner based on action. - """ - if context is None: - context = {} - lead = self.pool.get('crm.lead') - lead_ids = context.get('active_ids', []) - data = self.browse(cr, uid, ids, context=context)[0] - partner_id = data.partner_id and data.partner_id.id or False - return lead.convert_partner(cr, uid, lead_ids, data.action, partner_id, context=context) - - def make_partner(self, cr, uid, ids, context=None): - """ - Make a partner based on action. - Only called from form view, so only meant to convert one lead at a time. - """ - lead_id = context and context.get('active_id') or False - partner_ids_map = self._create_partner(cr, uid, ids, context=context) - return self.pool.get('res.partner').redirect_partner_form(cr, uid, partner_ids_map.get(lead_id, False), context=context) - -crm_lead2partner() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/wizard/crm_lead_to_partner_view.xml b/addons/crm/wizard/crm_lead_to_partner_view.xml deleted file mode 100644 index 21a14dd3726..00000000000 --- a/addons/crm/wizard/crm_lead_to_partner_view.xml +++ /dev/null @@ -1,37 +0,0 @@ -<openerp> - <data> - - <!-- Lead to Partner form view --> - - <record id="view_crm_lead2partner" model="ir.ui.view"> - <field name="name">crm.lead2partner.view</field> - <field name="model">crm.lead2partner</field> - <field name="arch" type="xml"> - <form string="Create a Partner" version="7.0"> - <group col="4"> - <separator string="Create a Partner"/> - <field name="action"/> - <field name="partner_id" attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"/> - </group> - <footer> - <button name="make_partner" string="Continue" type="object" class="oe_highlight"/> - or - <button string="Cancel" class="oe_link" special="cancel" /> - </footer> - </form> - </field> - </record> - - - <!-- Lead to Partner wizard --> - <record id="action_crm_lead2partner" model="ir.actions.act_window"> - <field name="name">Create a Partner</field> - <field name="type">ir.actions.act_window</field> - <field name="res_model">crm.lead2partner</field> - <field name="view_type">form</field> - <field name="view_id" ref="view_crm_lead2partner"/> - <field name="target">new</field> - </record> - - </data> -</openerp> diff --git a/addons/crm/wizard/crm_merge_opportunities.py b/addons/crm/wizard/crm_merge_opportunities.py index edfefb5e5bc..38f88b1c083 100644 --- a/addons/crm/wizard/crm_merge_opportunities.py +++ b/addons/crm/wizard/crm_merge_opportunities.py @@ -21,33 +21,51 @@ from osv import osv, fields from tools.translate import _ class crm_merge_opportunity(osv.osv_memory): - """Merge two Opportunities""" + """ + Merge opportunities together. + If we're talking about opportunities, it's just because it makes more sense + to merge opps than leads, because the leads are more ephemeral objects. + But since opportunities are leads, it's also possible to merge leads + together (resulting in a new lead), or leads and opps together (resulting + in a new opp). + """ _name = 'crm.merge.opportunity' - _description = 'Merge two Opportunities' + _description = 'Merge opportunities' + _columns = { + 'opportunity_ids': fields.many2many('crm.lead', rel='merge_opportunity_rel', id1='merge_id', id2='opportunity_id', string='Leads/Opportunities'), + } def action_merge(self, cr, uid, ids, context=None): if context is None: context = {} - lead = self.pool.get('crm.lead') - record = self.browse(cr, uid, ids[0], context=context) - opportunities = record.opportunity_ids - #TOFIX: why need to check lead_ids here - lead_ids = [opportunities[0].id] - self.write(cr, uid, ids, {'opportunity_ids' : [(6,0, lead_ids)]}, context=context) - context['lead_ids'] = lead_ids - merge_id = lead.merge_opportunity(cr, uid, [x.id for x in opportunities], context=context) - return lead.redirect_opportunity_view(cr, uid, merge_id, context=context) - _columns = { - 'opportunity_ids': fields.many2many('crm.lead', rel='merge_opportunity_rel', id1='merge_id', id2='opportunity_id', string='Leads/Opportunities'), - } + lead_obj = self.pool.get('crm.lead') + wizard = self.browse(cr, uid, ids[0], context=context) + opportunity2merge_ids = wizard.opportunity_ids + + #TODO: why is this passed through the context ? + context['lead_ids'] = [opportunity2merge_ids[0].id] + + merge_id = lead_obj.merge_opportunity(cr, uid, [x.id for x in opportunity2merge_ids], context=context) + + # The newly created lead might be a lead or an opp: redirect toward the right view + merge_result = lead_obj.browse(cr, uid, merge_id, context=context) + + if merge_result.type == 'opportunity': + return lead_obj.redirect_opportunity_view(cr, uid, merge_id, context=context) + else: + return lead_obj.redirect_lead_view(cr, uid, merge_id, context=context) def default_get(self, cr, uid, fields, context=None): """ - This function gets default values + Use active_ids from the context to fetch the leads/opps to merge. + In order to get merged, these leads/opps can't be in 'Done' or + 'Cancel' state. """ - record_ids = context and context.get('active_ids', False) or False + if context is None: + context = {} + record_ids = context.get('active_ids', False) res = super(crm_merge_opportunity, self).default_get(cr, uid, fields, context=context) if record_ids: @@ -61,6 +79,4 @@ class crm_merge_opportunity(osv.osv_memory): return res -crm_merge_opportunity() - # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/wizard/crm_partner_binding.py b/addons/crm/wizard/crm_partner_binding.py new file mode 100644 index 00000000000..bc686db40f4 --- /dev/null +++ b/addons/crm/wizard/crm_partner_binding.py @@ -0,0 +1,115 @@ +# -*- 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 osv, fields +from tools.translate import _ + +class crm_partner_binding(osv.osv_memory): + """ + Handle the partner binding or generation in any CRM wizard that requires + such feature, like the lead2opportunity wizard, or the + phonecall2opportunity wizard. Try to find a matching partner from the + CRM model's information (name, email, phone number, etc) or create a new + one on the fly. + Use it like a mixin with the wizard of your choice. + """ + _name = 'crm.partner.binding' + _description = 'Handle partner binding or generation in CRM wizards.' + _columns = { + 'action': fields.selection([ + ('exist', 'Link to an existing customer'), + ('create', 'Create a new customer'), + ('nothing', 'Do not link to a customer') + ], 'Related Customer', required=True), + 'partner_id': fields.many2one('res.partner', 'Customer'), + } + + def _find_matching_partner(self, cr, uid, context=None): + """ + Try to find a matching partner regarding the active model data, like + the customer's name, email, phone number, etc. + + :return int partner_id if any, False otherwise + """ + if context is None: + context = {} + partner_id = False + partner_obj = self.pool.get('res.partner') + + # The active model has to be a lead or a phonecall + if (context.get('active_model') == 'crm.lead') and context.get('active_id'): + active_model = self.pool.get('crm.lead').browse(cr, uid, context.get('active_id'), context=context) + elif (context.get('active_model') == 'crm.phonecall') and context.get('active_id'): + active_model = self.pool.get('crm.phonecall').browse(cr, uid, context.get('active_id'), context=context) + + # Find the best matching partner for the active model + if (active_model): + partner_obj = self.pool.get('res.partner') + + # A partner is set already + if active_model.partner_id: + partner_id = active_model.partner_id.id + # Search through the existing partners based on the lead's email + elif active_model.email_from: + partner_ids = partner_obj.search(cr, uid, [('email', '=', active_model.email_from)], context=context) + if partner_ids: + partner_id = partner_ids[0] + # Search through the existing partners based on the lead's partner or contact name + elif active_model.partner_name: + partner_ids = partner_obj.search(cr, uid, [('name', 'ilike', '%'+active_model.partner_name+'%')], context=context) + if partner_ids: + partner_id = partner_ids[0] + elif active_model.contact_name: + partner_ids = partner_obj.search(cr, uid, [ + ('name', 'ilike', '%'+active_model.contact_name+'%')], context=context) + if partner_ids: + partner_id = partner_ids[0] + + return partner_id + + def default_get(self, cr, uid, fields, context=None): + res = super(crm_partner_binding, self).default_get(cr, uid, fields, context=context) + partner_id = self._find_matching_partner(cr, uid, context=context) + + if 'action' in fields: + res['action'] = partner_id and 'exist' or 'create' + if 'partner_id' in fields: + res['partner_id'] = partner_id + + return res + + def _create_partner(self, cr, uid, ids, context=None): + """ + Create partner based on action. + :return dict: dictionary organized as followed: {lead_id: partner_assigned_id} + """ + #TODO this method in only called by crm_lead2opportunity_partner + #wizard and would probably diserve to be refactored or at least + #moved to a better place + if context is None: + context = {} + lead = self.pool.get('crm.lead') + lead_ids = context.get('active_ids', []) + data = self.browse(cr, uid, ids, context=context)[0] + partner_id = data.partner_id and data.partner_id.id or False + return lead.handle_partner_assignation(cr, uid, lead_ids, data.action, partner_id, context=context) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/wizard/crm_partner_to_opportunity.py b/addons/crm/wizard/crm_partner_to_opportunity.py deleted file mode 100644 index aa8f4a2a036..00000000000 --- a/addons/crm/wizard/crm_partner_to_opportunity.py +++ /dev/null @@ -1,77 +0,0 @@ -# -*- 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 osv, fields -from tools.translate import _ - -class crm_partner2opportunity(osv.osv_memory): - """Converts Partner To Opportunity""" - - _name = 'crm.partner2opportunity' - _description = 'Partner To Opportunity' - - _columns = { - 'name' : fields.char('Opportunity Name', size=64, required=True), - 'planned_revenue': fields.float('Expected Revenue', digits=(16,2)), - 'probability': fields.float('Success Probability', digits=(16,2)), - 'partner_id': fields.many2one('res.partner', 'Customer'), - } - - def action_cancel(self, cr, uid, ids, context=None): - """ - Closes Partner 2 Opportunity - """ - return {'type':'ir.actions.act_window_close'} - - def default_get(self, cr, uid, fields, context=None): - """ - This function gets default values - """ - partner_obj = self.pool.get('res.partner') - data = context and context.get('active_ids', []) or [] - res = super(crm_partner2opportunity, self).default_get(cr, uid, fields, context=context) - - for partner in partner_obj.browse(cr, uid, data, []): - if 'name' in fields: - res.update({'name': partner.name}) - if 'partner_id' in fields: - res.update({'partner_id': data and data[0] or False}) - return res - - def make_opportunity(self, cr, uid, ids, context=None): - partner_ids = context and context.get('active_ids', []) or [] - partner_id = partner_ids[0] if partner_ids else None - partner = self.pool.get('res.partner') - lead = self.pool.get('crm.lead') - data = self.browse(cr, uid, ids, context=context)[0] - opportunity_ids = partner.make_opportunity(cr, uid, partner_ids, - data.name, - data.planned_revenue, - data.probability, - partner_id, - context=context, - ) - opportunity_id = opportunity_ids[partner_ids[0]] - return lead.redirect_opportunity_view(cr, uid, opportunity_id, context=context) - -crm_partner2opportunity() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/wizard/crm_partner_to_opportunity_view.xml b/addons/crm/wizard/crm_partner_to_opportunity_view.xml deleted file mode 100644 index 7f0b5957071..00000000000 --- a/addons/crm/wizard/crm_partner_to_opportunity_view.xml +++ /dev/null @@ -1,27 +0,0 @@ -<openerp> - <data> - - <!-- partner To Opportunity form --> - - <record id="view_crm_partner2opportunity" model="ir.ui.view"> - <field name="name">crm.crm.partner2opportunity</field> - <field name="model">crm.partner2opportunity</field> - <field name="arch" type="xml"> - <form string="Create Opportunity" version="7.0"> - <group col="4"> - <field name="name"/> - <field name="partner_id"/> - <field name="planned_revenue"/> - <field name="probability"/> - </group> - <footer> - <button name="make_opportunity" string="Create Opportunity" type="object" class="oe_highlight"/> - or - <button string="Cancel" class="oe_link" special="cancel" /> - </footer> - </form> - </field> - </record> - - </data> -</openerp> diff --git a/addons/crm/wizard/crm_phonecall_to_opportunity.py b/addons/crm/wizard/crm_phonecall_to_opportunity.py index 7305cbd8b5f..e69de29bb2d 100644 --- a/addons/crm/wizard/crm_phonecall_to_opportunity.py +++ b/addons/crm/wizard/crm_phonecall_to_opportunity.py @@ -1,65 +0,0 @@ -# -*- 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 osv, fields -from tools.translate import _ - -class crm_phonecall2opportunity(osv.osv_memory): - """ Converts Phonecall to Opportunity""" - - _name = 'crm.phonecall2opportunity' - _inherit = 'crm.partner2opportunity' - _description = 'Phonecall To Opportunity' - - def make_opportunity(self, cr, uid, ids, context=None): - """ - This converts Phonecall to Opportunity and opens Phonecall view - """ - if not len(ids): - return False - call_ids = context and context.get('active_ids', False) or False - this = self.browse(cr, uid, ids[0], context=context) - if not call_ids: - return {} - opportunity = self.pool.get('crm.lead') - phonecall = self.pool.get('crm.phonecall') - opportunity_ids = phonecall.convert_opportunity(cr, uid, call_ids, this.name, this.partner_id and this.partner_id.id or False, \ - this.planned_revenue, this.probability, context=context) - return opportunity.redirect_opportunity_view(cr, uid, opportunity_ids[call_ids[0]], context=context) - - def default_get(self, cr, uid, fields, context=None): - """ - This function gets default values - """ - record_id = context and context.get('active_id', False) or False - res = {} - - if record_id: - phonecall = self.pool.get('crm.phonecall').browse(cr, uid, record_id, context=context) - if 'name' in fields: - res.update({'name': phonecall.name}) - if 'partner_id' in fields: - res.update({'partner_id': phonecall.partner_id and phonecall.partner_id.id or False}) - return res - -crm_phonecall2opportunity() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/wizard/crm_phonecall_to_opportunity_view.xml b/addons/crm/wizard/crm_phonecall_to_opportunity_view.xml deleted file mode 100644 index 4b2ec725759..00000000000 --- a/addons/crm/wizard/crm_phonecall_to_opportunity_view.xml +++ /dev/null @@ -1,42 +0,0 @@ -<?xml version="1.0"?> -<openerp> - <data> - -<!-- Phonecall to Opportunity view --> - - <record model="ir.ui.view" id="phonecall2opportunity_view"> - <field name="name">crm.phonecall2opportunity.form</field> - <field name="model">crm.phonecall2opportunity</field> - <field name="arch" type="xml"> - <form string="Convert To Opportunity " version="7.0"> - <group> - <field name="name"/> - <field name="partner_id" /> - <label for="planned_revenue"/> - <div> - <field name="planned_revenue" class="oe_inline"/> at - <field name="probability" class="oe_inline"/> %% - </div> - </group> - <footer> - <button name="make_opportunity" type="object" string="_Convert" class="oe_highlight"/> - or - <button string="Cancel" class="oe_link" special="cancel" /> - </footer> - </form> - </field> - </record> - -<!-- Phonecall to Opportunity action --> - - <record model="ir.actions.act_window" id="phonecall2opportunity_act"> - <field name="name">Convert to opportunity</field> - <field name="res_model">crm.phonecall2opportunity</field> - <field name="view_type">form</field> - <field name="view_mode">form</field> - <field name="view_id" ref="phonecall2opportunity_view"/> - <field name="target">new</field> - </record> - - </data> -</openerp> diff --git a/addons/crm/wizard/crm_phonecall_to_partner.py b/addons/crm/wizard/crm_phonecall_to_partner.py deleted file mode 100644 index 715b7396c66..00000000000 --- a/addons/crm/wizard/crm_phonecall_to_partner.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- 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 osv, fields -from tools.translate import _ - -class crm_phonecall2partner(osv.osv_memory): - """ Converts phonecall to partner """ - - _name = 'crm.phonecall2partner' - _inherit = 'crm.lead2partner' - _description = 'Phonecall to Partner' - - def _select_partner(self, cr, uid, context=None): - """ - This function Searches for Partner from selected phonecall. - """ - if context is None: - context = {} - - phonecall_obj = self.pool.get('crm.phonecall') - partner_obj = self.pool.get('res.partner') - rec_ids = context and context.get('active_ids', []) - value = {} - partner_id = False - for phonecall in phonecall_obj.browse(cr, uid, rec_ids, context=context): - partner_ids = partner_obj.search(cr, uid, [('name', '=', phonecall.name or phonecall.name)]) - if not partner_ids and (phonecall.partner_phone or phonecall.partner_mobile): - partner_ids = partner_obj.search(cr, uid, ['|', ('phone', '=', phonecall.partner_phone), ('mobile','=',phonecall.partner_mobile)]) - - partner_id = partner_ids and partner_ids[0] or False - return partner_id - - - def _create_partner(self, cr, uid, ids, context=None): - """ - This function Creates partner based on action. - """ - if context is None: - context = {} - phonecall = self.pool.get('crm.phonecall') - data = self.browse(cr, uid, ids, context=context)[0] - call_ids = context and context.get('active_ids') or [] - partner_id = data.partner_id and data.partner_id.id or False - return phonecall.convert_partner(cr, uid, call_ids, data.action, partner_id, context=context) - -crm_phonecall2partner() - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/crm/wizard/crm_phonecall_to_partner_view.xml b/addons/crm/wizard/crm_phonecall_to_partner_view.xml deleted file mode 100644 index 7231876b0c1..00000000000 --- a/addons/crm/wizard/crm_phonecall_to_partner_view.xml +++ /dev/null @@ -1,54 +0,0 @@ -<openerp> - <data> - - <!-- Phonecall to partner confirmation form --> - - <record id="view_crm_phonecall2partner_create" model="ir.ui.view"> - <field name="name">crm.phonecall2partner.view.create</field> - <field name="model">crm.phonecall2partner</field> - <field name="arch" type="xml"> - <form string="Create a Partner" version="7.0"> - <label string="Are you sure you want to create a partner based on this Phonecall ?"/> - <newline/> - <label string="You may have to verify that this partner does not exist already."/> - <footer> - <button name="open_create_partner" string="Create Partner" type="object" groups="base.group_partner_manager" class="oe_highlight"/> - or - <button string="Cancel" class="oe_link" special="cancel" /> - </footer> - </form> - </field> - </record> - - <!-- Phonecall to partner form view --> - - <record id="view_crm_phonecall2partner" model="ir.ui.view"> - <field name="name">crm.phonecall2partner.view</field> - <field name="model">crm.phonecall2partner</field> - <field name="arch" type="xml"> - <form string="Create a Partner" version="7.0"> - <group> - <field name="action"/> - <field name="partner_id" attrs="{'required':[('action','=','exist')],'invisible':[('action','!=','exist')]}" /> - </group> - <footer> - <button name="make_partner" string="Continue" type="object" class="oe_highlight"/> - or - <button string="Cancel" class="oe_link" special="cancel" /> - </footer> - </form> - </field> - </record> - - <!-- Phonecall to Partner wizard --> - - <record id="action_crm_phonecall2partner" model="ir.actions.act_window"> - <field name="name">Create a Partner</field> - <field name="type">ir.actions.act_window</field> - <field name="res_model">crm.phonecall2partner</field> - <field name="view_type">form</field> - <field name="view_id" ref="view_crm_phonecall2partner_create"/> - <field name="target">new</field> - </record> - </data> -</openerp> |
