From 7699504d62f33dd0b124ff66850485add6641637 Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Fri, 28 Mar 2014 15:28:35 +0530 Subject: [PATCH 01/51] [Add new sale_team module] --- addons/crm/__openerp__.py | 1 + addons/crm/crm.py | 100 +------ addons/crm/crm_case_section_view.xml | 244 +++------------ addons/crm/crm_lead.py | 13 +- addons/crm/res_config_view.xml | 11 - addons/crm/security/crm_security.xml | 14 +- addons/crm/security/ir.model.access.csv | 3 - addons/sale/__openerp__.py | 3 +- addons/sale/report/__init__.py | 3 +- .../report/sale_analysis_report.py} | 0 .../report/sale_analysis_report_view.xml} | 0 .../sales_crm_account_invoice_report.py | 0 addons/sale/sale.py | 81 ++++- addons/sale/sale_view.xml | 237 +++++++++++++++ addons/sale_crm/report/__init__.py | 4 +- addons/sale_crm/sale_crm.py | 108 +------ addons/sale_crm/sale_crm_view.xml | 278 +----------------- addons/sale_team/__init__.py | 25 ++ addons/sale_team/__openerp__.py | 36 +++ addons/sale_team/ir.model.access.csv | 5 + addons/sale_team/res_config_view.xml | 27 ++ addons/sale_team/sale_team.py | 153 ++++++++++ addons/sale_team/sale_team.xml | 232 +++++++++++++++ addons/sale_team/sale_team_config.py | 45 +++ addons/sale_team/sale_team_security.xml | 18 ++ 25 files changed, 917 insertions(+), 724 deletions(-) rename addons/{sale_crm/report/sale_report.py => sale/report/sale_analysis_report.py} (100%) rename addons/{sale_crm/report/sale_report_view.xml => sale/report/sale_analysis_report_view.xml} (100%) rename addons/{sale_crm => sale}/report/sales_crm_account_invoice_report.py (100%) create mode 100644 addons/sale_team/__init__.py create mode 100644 addons/sale_team/__openerp__.py create mode 100644 addons/sale_team/ir.model.access.csv create mode 100644 addons/sale_team/res_config_view.xml create mode 100644 addons/sale_team/sale_team.py create mode 100644 addons/sale_team/sale_team.xml create mode 100644 addons/sale_team/sale_team_config.py create mode 100644 addons/sale_team/sale_team_security.xml diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 1b0cbed7937..7d5052c1c4e 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -51,6 +51,7 @@ Dashboard for CRM will include: 'depends': [ 'base_action_rule', 'base_setup', + 'sale_team', 'process', 'mail', 'email_template', diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 20e2a6e8aff..eb10e468602 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -90,45 +90,8 @@ class crm_case_stage(osv.osv): class crm_case_section(osv.osv): """ Model for sales teams. """ - _name = "crm.case.section" - _inherits = {'mail.alias': 'alias_id'} - _inherit = "mail.thread" + _inherit = 'crm.case.section' _description = "Sales Teams" - _order = "complete_name" - # number of periods for lead/opportunities/... tracking in salesteam kanban dashboard/kanban view - _period_number = 5 - - def get_full_name(self, cr, uid, ids, field_name, arg, context=None): - return dict(self.name_get(cr, uid, ids, context=context)) - - def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): - """ Generic method to generate data for bar chart values using SparklineBarWidget. - This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). - - :param obj: the target model (i.e. crm_lead) - :param domain: the domain applied to the read_group - :param list read_fields: the list of fields to read in the read_group - :param str value_field: the field used to compute the value of the bar slice - :param str groupby_field: the fields used to group - - :return list section_result: a list of dicts: [ - { 'value': (int) bar_column_value, - 'tootip': (str) bar_column_tooltip, - } - ] - """ - month_begin = date.today().replace(day=1) - section_result = [{ - 'value': 0, - 'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B %Y'), - } for i in range(self._period_number - 1, -1, -1)] - group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) - pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT - for group in group_obj: - group_begin_date = datetime.strptime(group['__domain'][0][2], pattern) - month_delta = relativedelta.relativedelta(month_begin, group_begin_date) - section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field, 0)} - return section_result def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): """ Get opportunities-related data for salesteam kanban view @@ -155,25 +118,7 @@ class crm_case_section(osv.osv): return res _columns = { - 'name': fields.char('Sales Team', size=64, required=True, translate=True), - 'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True), - 'code': fields.char('Code', size=8), - 'active': fields.boolean('Active', help="If the active field is set to "\ - "true, it will allow you to hide the sales team without removing it."), - 'change_responsible': fields.boolean('Reassign Escalated', help="When escalating to this team override the salesman with the team leader."), - 'user_id': fields.many2one('res.users', 'Team Leader'), - 'member_ids': fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'), - 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by OpenERP about cases in this sales team"), - 'parent_id': fields.many2one('crm.case.section', 'Parent Team'), - 'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Teams'), - 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), - 'note': fields.text('Description'), - 'working_hours': fields.float('Working Hours', digits=(16, 2)), 'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'), - 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, - help="The email address associated with this team. New emails received will automatically " - "create new leads assigned to the team."), - 'color': fields.integer('Color Index'), 'use_leads': fields.boolean('Leads', help="The first contact you get with a potential customer is a lead you qualify before converting it into a real business opportunity. Check this box to manage leads in this sales team."), @@ -190,52 +135,11 @@ class crm_case_section(osv.osv): return ids _defaults = { - 'active': 1, 'stage_ids': _get_stage_common, 'use_leads': True, } - _sql_constraints = [ - ('code_uniq', 'unique (code)', 'The code of the sales team must be unique !') - ] - - _constraints = [ - (osv.osv._check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id']) - ] - - def name_get(self, cr, uid, ids, context=None): - """Overrides orm name_get method""" - if not isinstance(ids, list): - ids = [ids] - res = [] - if not ids: - return res - reads = self.read(cr, uid, ids, ['name', 'parent_id'], context) - - for record in reads: - name = record['name'] - if record['parent_id']: - name = record['parent_id'][1] + ' / ' + name - res.append((record['id'], name)) - return res - - def create(self, cr, uid, vals, context=None): - if context is None: - context = {} - create_context = dict(context, alias_model_name='crm.lead', alias_parent_model_name=self._name) - section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) - section = self.browse(cr, uid, section_id, context=context) - self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) - return section_id - - def unlink(self, cr, uid, ids, context=None): - # Cascade-delete mail aliases as well, as they should not exist without the sales team. - mail_alias = self.pool.get('mail.alias') - alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id] - res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) - mail_alias.unlink(cr, uid, alias_ids, context=context) - return res - + class crm_case_categ(osv.osv): """ Category of Case """ _name = "crm.case.categ" diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index d3ad226dbea..a56459a7065 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -84,195 +84,34 @@ crm.case.section.kanban crm.case.section + - + + - - - - - - - - - - - - - - - - - - - Case Sections - Search - crm.case.section - - - - - - - - - - - - - - - - - - - - Sales Teams - crm.case.section - form - kanban,tree,form - {'search_default_personal': True} - - -

- Click here to define a new sales team. -

- Use sales team to organize your different salespersons or - departments into separate teams. Each team will work in - its own list of opportunities. -

-
-
- - - - - crm.case.section.form - crm.case.section - -
- -
-
- - - - - - - - - - - - - - - - - - -
- X -
-
- -
-
-
-
-
-
-
-
- - - - - - - -
-
-
- - + + + + + + @@ -303,29 +142,26 @@ crm.case.section [('parent_id','=',False)] tree - + - - Sales Teams - crm.case.section - form - - -

- Click here to define a new sales team. -

- Use sales team to organize your different salespersons or - departments into separate teams. Each team will work in - its own list of opportunities. -

+ + crm.case.section.form + crm.case.section + + + +
+
+
+ + + + + +
- - \ No newline at end of file diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 4da0a509ed1..f97b7d0adf8 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -346,7 +346,18 @@ class crm_lead(format_address, osv.osv): if section_ids: section_id = section_ids[0] return {'value': {'section_id': section_id}} - + + def on_change_user(self, cr, uid, ids, user_id, context=None): + """ Override of on change user_id on lead/opportunity; when having sale + the new logic is : + - use user.default_section_id + - or fallback on previous behavior """ + if user_id: + user = self.pool.get('res.users').browse(cr, uid, user_id, context=context) + if user.default_section_id and user.default_section_id.id: + return {'value': {'section_id': user.default_section_id.id}} + return super(sale_crm_lead, self).on_change_user(cr, uid, ids, user_id, context=context) + def stage_find(self, cr, uid, cases, section_id, domain=None, order='sequence', context=None): """ Override of the base.stage method Parameter of the stage search taken from the lead: diff --git a/addons/crm/res_config_view.xml b/addons/crm/res_config_view.xml index b964cca5c7f..0ef4cfa6f1e 100644 --- a/addons/crm/res_config_view.xml +++ b/addons/crm/res_config_view.xml @@ -23,17 +23,6 @@
- - - diff --git a/addons/crm/security/crm_security.xml b/addons/crm/security/crm_security.xml index dfc3a56858e..bd2c3cdb3a4 100644 --- a/addons/crm/security/crm_security.xml +++ b/addons/crm/security/crm_security.xml @@ -24,19 +24,7 @@
- - Do Not Use Sales Teams - - - - - - - - Manage Sales Teams - - - + Manage Fund Raising diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv index f3c6bdf5c1f..fde894e4c31 100644 --- a/addons/crm/security/ir.model.access.csv +++ b/addons/crm/security/ir.model.access.csv @@ -5,15 +5,12 @@ access_crm_segmentation,crm.segmentation,model_crm_segmentation,base.group_sale_ access_crm_segmentation_line,crm.segmentation.line,model_crm_segmentation_line,base.group_sale_manager,1,1,1,1 access_crm_case_channel_user,crm.case.channel user,model_crm_case_channel,base.group_sale_salesman,1,0,0,0 access_crm_case_channel_manager,crm.case.channel manager,model_crm_case_channel,base.group_sale_manager,1,1,1,1 -access_crm_case_section,crm.case.section,model_crm_case_section,base.group_user,1,0,0,0 access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_sale_salesman,1,1,1,0 access_crm_lead_manager,crm.lead.manager,model_crm_lead,base.group_sale_manager,1,1,1,1 access_crm_phonecall_manager,crm.phonecall.manager,model_crm_phonecall,base.group_sale_manager,1,1,1,1 access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_user,1,0,0,0 access_crm_lead,crm.lead,model_crm_lead,base.group_sale_salesman,1,1,1,0 access_crm_phonecall,crm.phonecall,model_crm_phonecall,base.group_sale_salesman,1,1,1,0 -access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,0,0,0 -access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 access_crm_case_stage,crm.case.stage,model_crm_case_stage,,1,0,0,0 access_crm_case_stage_manager,crm.case.stage,model_crm_case_stage,base.group_sale_manager,1,1,1,1 access_crm_case_resource_type_user,crm_case_resource_type user,model_crm_case_resource_type,base.group_sale_salesman,1,1,1,0 diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py index bb5bac89e46..44cbb3227ca 100644 --- a/addons/sale/__openerp__.py +++ b/addons/sale/__openerp__.py @@ -59,7 +59,7 @@ The Dashboard for the Sales Manager will include 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', 'images': ['images/sale_dashboard.jpeg','images/Sale_order_line_to_invoice.jpeg','images/sale_order.jpeg','images/sales_analysis.jpeg'], - 'depends': ['account_voucher'], + 'depends': ['sale_team','account_voucher'], 'data': [ 'wizard/sale_make_invoice_advance.xml', 'wizard/sale_line_invoice.xml', @@ -73,6 +73,7 @@ The Dashboard for the Sales Manager will include 'sale_view.xml', 'res_partner_view.xml', 'report/sale_report_view.xml', + 'report/sale_analysis_report_view.xml', 'process/sale_process.xml', 'board_sale_view.xml', 'edi/sale_order_action_data.xml', diff --git a/addons/sale/report/__init__.py b/addons/sale/report/__init__.py index 12d484c3e25..f1d48dd38e9 100644 --- a/addons/sale/report/__init__.py +++ b/addons/sale/report/__init__.py @@ -18,7 +18,8 @@ # along with this program. If not, see . # ############################################################################## - +import sales_crm_account_invoice_report import sale_report +import sale_analysis_report # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_crm/report/sale_report.py b/addons/sale/report/sale_analysis_report.py similarity index 100% rename from addons/sale_crm/report/sale_report.py rename to addons/sale/report/sale_analysis_report.py diff --git a/addons/sale_crm/report/sale_report_view.xml b/addons/sale/report/sale_analysis_report_view.xml similarity index 100% rename from addons/sale_crm/report/sale_report_view.xml rename to addons/sale/report/sale_analysis_report_view.xml diff --git a/addons/sale_crm/report/sales_crm_account_invoice_report.py b/addons/sale/report/sales_crm_account_invoice_report.py similarity index 100% rename from addons/sale_crm/report/sales_crm_account_invoice_report.py rename to addons/sale/report/sales_crm_account_invoice_report.py diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 667f15a375d..93e4d90f612 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -18,9 +18,11 @@ # along with this program. If not, see . # ############################################################################## - -from datetime import datetime, timedelta +import calendar +from openerp import tools +from datetime import date, datetime, timedelta from dateutil.relativedelta import relativedelta +from dateutil import relativedelta import time from openerp.osv import fields, osv from openerp.tools.translate import _ @@ -162,6 +164,11 @@ class sale_order(osv.osv): if not company_id: raise osv.except_osv(_('Error!'), _('There is no default company for the current user!')) return company_id + + def _get_default_section_id(self, cr, uid, context=None): + """ Gives default section by checking if present in the context """ + section_id = self.pool.get('res.users').browse(cr, uid, uid, context).default_section_id.id or False + return section_id _columns = { 'name': fields.char('Order Reference', size=64, required=True, @@ -226,6 +233,7 @@ class sale_order(osv.osv): 'payment_term': fields.many2one('account.payment.term', 'Payment Term'), 'fiscal_position': fields.many2one('account.fiscal.position', 'Fiscal Position'), 'company_id': fields.many2one('res.company', 'Company'), + 'section_id': fields.many2one('crm.case.section', 'Sales Team'), } _defaults = { 'date_order': fields.date.context_today, @@ -237,7 +245,9 @@ class sale_order(osv.osv): 'invoice_quantity': 'order', 'partner_invoice_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['invoice'])['invoice'], 'partner_shipping_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['delivery'])['delivery'], - 'note': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.sale_note + 'note': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.sale_note, + 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), + } _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'), @@ -372,12 +382,14 @@ class sale_order(osv.osv): 'fiscal_position': order.fiscal_position.id or order.partner_id.property_account_position.id, 'date_invoice': context.get('date_invoice', False), 'company_id': order.company_id.id, - 'user_id': order.user_id and order.user_id.id or False + 'user_id': order.user_id and order.user_id.id or False, + 'section_id' : order.section_id.id } # Care for deprecated _inv_get() hook - FIXME: to be removed after 6.1 invoice_vals.update(self._inv_get(cr, uid, order, context=context)) return invoice_vals + def _make_invoice(self, cr, uid, order, lines, context=None): inv_obj = self.pool.get('account.invoice') @@ -1000,11 +1012,14 @@ class mail_compose_message(osv.Model): context = dict(context, mail_post_autofollow=True) self.pool.get('sale.order').signal_quotation_sent(cr, uid, [context['default_res_id']]) return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context) - + class account_invoice(osv.Model): _inherit = 'account.invoice' - + _columns = { + 'section_id': fields.many2one('crm.case.section', 'Sales Team'), + } + def confirm_paid(self, cr, uid, ids, context=None): sale_order_obj = self.pool.get('sale.order') res = super(account_invoice, self).confirm_paid(cr, uid, ids, context=context) @@ -1023,5 +1038,59 @@ class account_invoice(osv.Model): for id in ids: workflow.trg_validate(uid, 'account.invoice', id, 'invoice_cancel', cr) return super(account_invoice, self).unlink(cr, uid, ids, context=context) + + _defaults = { + 'section_id': lambda self, cr, uid, c=None: self.pool.get('res.users').browse(cr, uid, uid, c).default_section_id.id or False, + } + +class crm_case_section(osv.osv): + _inherit = 'crm.case.section' + + def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None): + obj = self.pool.get('sale.order') + res = dict.fromkeys(ids, False) + month_begin = date.today().replace(day=1) + date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + for id in ids: + res[id] = dict() + created_domain = [('section_id', '=', id), ('state', '=', ['draft']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] + res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) + validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent', 'cancel']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] + res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) + return res + + def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None): + obj = self.pool.get('account.invoice.report') + res = dict.fromkeys(ids, False) + month_begin = date.today().replace(day=1) + date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + for id in ids: + created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)] + res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context) + return res + + _columns = { + 'invoiced_forecast': fields.integer(string='Invoice Forecast', + help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" + "team should invoice this month. It is used to compute the progression ratio \n" + " of the current and forecast revenue on the kanban view."), + 'invoiced_target': fields.integer(string='Invoice Target', + help="Target of invoice revenue for the current month. This is the amount the sales \n" + "team estimates to be able to invoice this month."), + 'monthly_quoted': fields.function(_get_sale_orders_data, + type='string', readonly=True, multi='_get_sale_orders_data', + string='Rate of created quotation per duration'), + 'monthly_confirmed': fields.function(_get_sale_orders_data, + type='string', readonly=True, multi='_get_sale_orders_data', + string='Rate of validate sales orders per duration'), + 'monthly_invoiced': fields.function(_get_invoices_data, + type='string', readonly=True, + string='Rate of sent invoices per duration'), + } + + def action_forecast(self, cr, uid, id, value, context=None): + return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context) # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index c9ffb94b985..9a6ef2a2e32 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -202,6 +202,7 @@ + @@ -255,12 +256,14 @@ + + @@ -518,5 +521,239 @@ + + + + Account Invoice + account.invoice + + + + + + + + + + + + + account.invoice.groupby + account.invoice + + + + + + + + + + + + + + Account Invoice + account.invoice + + + + + + + + + + + + + + + Sales Orders + ir.actions.act_window + sale.order + form + tree,form,calendar,graph + + [('state','not in',('draft','sent','cancel'))] + { + 'search_default_section_id': [active_id], + 'default_section_id': active_id, + } + + +

+ Click to create a quotation that can be converted into a sales + order. +

+ OpenERP will help you efficiently handle the complete sales flow: + quotation, sales order, delivery, invoicing and payment. +

+
+
+ + + Quotations + ir.actions.act_window + sale.order + form + + tree,form,calendar,graph + { + 'search_default_section_id': [active_id], + 'default_section_id': active_id, + 'show_address': 1, + } + + [('state','in',('draft','sent','cancel'))] + + +

+ Click to create a quotation, the first step of a new sale. +

+ OpenERP will help you handle efficiently the complete sale flow: + from the quotation to the sales order, the + delivery, the invoicing and the payment collection. +

+ The social feature helps you organize discussions on each sales + order, and allow your customers to keep track of the evolution + of the sales order. +

+
+
+ + + Invoices + account.invoice + form + tree,form,calendar,graph + + [ + ('state', 'not in', ['draft', 'cancel']), + ('type', '=', 'out_invoice')] + { + 'search_default_section_id': [active_id], + 'default_section_id': active_id, + 'default_type':'out_invoice', + 'type':'out_invoice', + 'journal_type': 'sale', + } + + + + + + 1 + tree + + + + + 2 + form + + + + + + Quotations Analysis + sale.report + graph + [('state','=','draft'),('section_id', '=', active_id)] + {'search_default_order_month':1} + This report performs analysis on your quotations. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. + + + + Sales Analysis + sale.report + graph + [('state','not in',('draft','sent','cancel')),('section_id', '=', active_id)] + {'search_default_order_month':1} + This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. + + + + Invoices Analysis + account.invoice.report + graph + [('section_id', '=', active_id),('state', 'not in', ['draft', 'cancel'])] + {'search_default_month':1} + From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. + + + + crm.case.section.form + crm.case.section + + + + + + + + + + + + + crm.case.section.kanban + crm.case.section + + + + + + + + + + + + + + + + +
+ Invoiced + Forecast +
+
+
Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance. +
+
+
+
+
diff --git a/addons/sale_crm/report/__init__.py b/addons/sale_crm/report/__init__.py index d86b3e66de9..3fd45ac3f8f 100644 --- a/addons/sale_crm/report/__init__.py +++ b/addons/sale_crm/report/__init__.py @@ -19,8 +19,8 @@ # ############################################################################## -import sales_crm_account_invoice_report -import sale_report + + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 9bcb5adf6ae..510daf30c3b 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -26,117 +26,11 @@ from dateutil import relativedelta from openerp import tools from openerp.osv import osv, fields -class res_users(osv.Model): - _inherit = 'res.users' - _columns = { - 'default_section_id': fields.many2one('crm.case.section', 'Default Sales Team'), - } - - def __init__(self, pool, cr): - init_res = super(res_users, self).__init__(pool, cr) - # duplicate list to avoid modifying the original reference - self.SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS) - self.SELF_WRITEABLE_FIELDS.extend(['default_section_id']) - return init_res - class sale_order(osv.osv): _inherit = 'sale.order' _columns = { - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), 'categ_ids': fields.many2many('crm.case.categ', 'sale_order_category_rel', 'order_id', 'category_id', 'Categories', \ domain="['|',('section_id','=',section_id),('section_id','=',False), ('object_id.model', '=', 'crm.lead')]", context="{'object_name': 'crm.lead'}") } - - def _get_default_section_id(self, cr, uid, context=None): - """ Gives default section by checking if present in the context """ - section_id = self.pool.get('crm.lead')._resolve_section_id_from_context(cr, uid, context=context) or False - if not section_id: - section_id = self.pool.get('res.users').browse(cr, uid, uid, context).default_section_id.id or False - return section_id - - _defaults = { - 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), - } - - def _prepare_invoice(self, cr, uid, order, lines, context=None): - invoice_vals = super(sale_order, self)._prepare_invoice(cr, uid, order, lines, context=context) - if order.section_id and order.section_id.id: - invoice_vals['section_id'] = order.section_id.id - return invoice_vals - - -class crm_case_section(osv.osv): - _inherit = 'crm.case.section' - - def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None): - obj = self.pool.get('sale.order') - res = dict.fromkeys(ids, False) - month_begin = date.today().replace(day=1) - date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - for id in ids: - res[id] = dict() - created_domain = [('section_id', '=', id), ('state', '=', ['draft']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] - res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) - validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent', 'cancel']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] - res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) - return res - - def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None): - obj = self.pool.get('account.invoice.report') - res = dict.fromkeys(ids, False) - month_begin = date.today().replace(day=1) - date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - for id in ids: - created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)] - res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context) - return res - - _columns = { - 'invoiced_forecast': fields.integer(string='Invoice Forecast', - help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" - "team should invoice this month. It is used to compute the progression ratio \n" - " of the current and forecast revenue on the kanban view."), - 'invoiced_target': fields.integer(string='Invoice Target', - help="Target of invoice revenue for the current month. This is the amount the sales \n" - "team estimates to be able to invoice this month."), - 'monthly_quoted': fields.function(_get_sale_orders_data, - type='string', readonly=True, multi='_get_sale_orders_data', - string='Rate of created quotation per duration'), - 'monthly_confirmed': fields.function(_get_sale_orders_data, - type='string', readonly=True, multi='_get_sale_orders_data', - string='Rate of validate sales orders per duration'), - 'monthly_invoiced': fields.function(_get_invoices_data, - type='string', readonly=True, - string='Rate of sent invoices per duration'), - } - - def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context) - -class sale_crm_lead(osv.Model): - _inherit = 'crm.lead' - - def on_change_user(self, cr, uid, ids, user_id, context=None): - """ Override of on change user_id on lead/opportunity; when having sale - the new logic is : - - use user.default_section_id - - or fallback on previous behavior """ - if user_id: - user = self.pool.get('res.users').browse(cr, uid, user_id, context=context) - if user.default_section_id and user.default_section_id.id: - return {'value': {'section_id': user.default_section_id.id}} - return super(sale_crm_lead, self).on_change_user(cr, uid, ids, user_id, context=context) - - -class account_invoice(osv.osv): - _inherit = 'account.invoice' - _columns = { - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), - } - _defaults = { - 'section_id': lambda self, cr, uid, c=None: self.pool.get('res.users').browse(cr, uid, uid, c).default_section_id.id or False, - } - + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 88d7fe8be6c..102eb9462fa 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -27,285 +27,9 @@ - - - - sale.order.list.select - sale.order - - - - - - - - - - - - - - - Account Invoice - account.invoice - - - - - - - - - - - - - account.invoice.groupby - account.invoice - - - - - - - - - - - - - - Account Invoice - account.invoice - - - - - - - - - - - - - Users Preferences - res.users - - - - - - - - - - - - - res.users.preferences.form - res.users - - - - - - - - - - - - - - Sales Orders - ir.actions.act_window - sale.order - form - tree,form,calendar,graph - - [('state','not in',('draft','sent','cancel'))] - { - 'search_default_section_id': [active_id], - 'default_section_id': active_id, - } - - -

- Click to create a quotation that can be converted into a sales - order. -

- OpenERP will help you efficiently handle the complete sales flow: - quotation, sales order, delivery, invoicing and payment. -

-
-
- - - Quotations - ir.actions.act_window - sale.order - form - - tree,form,calendar,graph - { - 'search_default_section_id': [active_id], - 'default_section_id': active_id, - 'show_address': 1, - } - - [('state','in',('draft','sent','cancel'))] - - -

- Click to create a quotation, the first step of a new sale. -

- OpenERP will help you handle efficiently the complete sale flow: - from the quotation to the sales order, the - delivery, the invoicing and the payment collection. -

- The social feature helps you organize discussions on each sales - order, and allow your customers to keep track of the evolution - of the sales order. -

-
-
- - - Invoices - account.invoice - form - tree,form,calendar,graph - - [ - ('state', 'not in', ['draft', 'cancel']), - ('type', '=', 'out_invoice')] - { - 'search_default_section_id': [active_id], - 'default_section_id': active_id, - 'default_type':'out_invoice', - 'type':'out_invoice', - 'journal_type': 'sale', - } - - - - - - 1 - tree - - - - - 2 - form - - - - - - Quotations Analysis - sale.report - graph - [('state','=','draft'),('section_id', '=', active_id)] - {'search_default_order_month':1} - This report performs analysis on your quotations. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. - - - - Sales Analysis - sale.report - graph - [('state','not in',('draft','sent','cancel')),('section_id', '=', active_id)] - {'search_default_order_month':1} - This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application. - - - - Invoices Analysis - account.invoice.report - graph - [('section_id', '=', active_id),('state', 'not in', ['draft', 'cancel'])] - {'search_default_month':1} - From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. - - - - crm.case.section.form - crm.case.section - - - - - - - - - - - - - crm.case.section.kanban - crm.case.section - - - - - - - - - - - - - - - - - - -
- Invoiced - Forecast -
-
-
Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance. -
-
-
-
-
- + diff --git a/addons/sale_team/__init__.py b/addons/sale_team/__init__.py new file mode 100644 index 00000000000..6bac200f0db --- /dev/null +++ b/addons/sale_team/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## +import sale_team +import sale_team_config + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/sale_team/__openerp__.py b/addons/sale_team/__openerp__.py new file mode 100644 index 00000000000..9c303ae3ae3 --- /dev/null +++ b/addons/sale_team/__openerp__.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## +{ + 'name' : 'Sale Team', + 'version' : '1.0', + 'author' : 'OpenERP SA', + 'category': 'hidden', + 'sequence': 10, + 'summary': 'Sales Team', + 'description': """ """, + 'website': 'http://www.openerp.com', + 'depends' : ['base','web_kanban','calendar'], + 'data': ['sale_team.xml','sale_team_security.xml','res_config_view.xml','ir.model.access.csv'], + 'demo': [], + 'installable': True, + 'auto_install': True, +} +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_team/ir.model.access.csv b/addons/sale_team/ir.model.access.csv new file mode 100644 index 00000000000..9817ad834f6 --- /dev/null +++ b/addons/sale_team/ir.model.access.csv @@ -0,0 +1,5 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_crm_case_section,crm.case.section,model_crm_case_section,base.group_user,1,0,0,0 +access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,0,0,0 +access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 + diff --git a/addons/sale_team/res_config_view.xml b/addons/sale_team/res_config_view.xml new file mode 100644 index 00000000000..7723fda48fc --- /dev/null +++ b/addons/sale_team/res_config_view.xml @@ -0,0 +1,27 @@ + + + + + + crm settings + sale.team.config.settings + + + +
+ + + +
+
+
+
+
diff --git a/addons/sale_team/sale_team.py b/addons/sale_team/sale_team.py new file mode 100644 index 00000000000..3643a6dc26c --- /dev/null +++ b/addons/sale_team/sale_team.py @@ -0,0 +1,153 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-today OpenERP SA () +# +# 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 . +# +############################################################################## + + +import calendar +from datetime import date, datetime +from dateutil import relativedelta + +from openerp import tools +from openerp.osv import fields +from openerp.osv import osv + + + +class crm_case_section(osv.osv): + _name = "crm.case.section" + _inherits = {'mail.alias': 'alias_id'} + _inherit = ['mail.thread', 'ir.needaction_mixin'] + _description = "Sales Teams" + _order = "complete_name" + # number of periods for lead/opportunities/... tracking in salesteam kanban dashboard/kanban view + _period_number = 5 + + def get_full_name(self, cr, uid, ids, field_name, arg, context=None): + return dict(self.name_get(cr, uid, ids, context=context)) + + def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None): + + """ Generic method to generate data for bar chart values using SparklineBarWidget. + This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field). + + :param obj: the target model (i.e. crm_lead) + :param domain: the domain applied to the read_group + :param list read_fields: the list of fields to read in the read_group + :param str value_field: the field used to compute the value of the bar slice + :param str groupby_field: the fields used to group + + :return list section_result: a list of dicts: [ + { 'value': (int) bar_column_value, + 'tootip': (str) bar_column_tooltip, + } + ] + """ + month_begin = date.today().replace(day=1) + section_result = [{ + 'value': 0, + 'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B %Y'), + } for i in range(self._period_number - 1, -1, -1)] + group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context) + pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT + for group in group_obj: + group_begin_date = datetime.strptime(group['__domain'][0][2], pattern) + month_delta = relativedelta.relativedelta(month_begin, group_begin_date) + section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field, 0)} + return section_result + + _columns = { + 'name': fields.char('Sales Team', size=64, required=True, translate=True), + 'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True), + 'code': fields.char('Code', size=8), + 'active': fields.boolean('Active', help="If the active field is set to "\ + "true, it will allow you to hide the sales team without removing it."), + 'change_responsible': fields.boolean('Reassign Escalated', help="When escalating to this team override the salesman with the team leader."), + 'user_id': fields.many2one('res.users', 'Team Leader'), + 'member_ids': fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'), + 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by OpenERP about cases in this sales team"), + 'parent_id': fields.many2one('crm.case.section', 'Parent Team'), + 'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Teams'), + 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), + 'note': fields.text('Description'), + 'working_hours': fields.float('Working Hours', digits=(16, 2)), + 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."), + 'color': fields.integer('Color Index'), + } + + _defaults = { + 'active': 1, + } + + _sql_constraints = [ + ('code_uniq', 'unique (code)', 'The code of the sales team must be unique !') + ] + + _constraints = [ + (osv.osv._check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id']) + ] + + def name_get(self, cr, uid, ids, context=None): + """Overrides orm name_get method""" + if not isinstance(ids, list): + ids = [ids] + res = [] + if not ids: + return res + reads = self.read(cr, uid, ids, ['name', 'parent_id'], context) + + for record in reads: + name = record['name'] + if record['parent_id']: + name = record['parent_id'][1] + ' / ' + name + res.append((record['id'], name)) + return res + + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + create_context = dict(context, alias_model_name=self._name, alias_parent_model_name=self._name) + section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) + section = self.browse(cr, uid, section_id, context=context) + self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) + return section_id + + def unlink(self, cr, uid, ids, context=None): + # Cascade-delete mail aliases as well, as they should not exist without the sales team. + mail_alias = self.pool.get('mail.alias') + alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id] + res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) + mail_alias.unlink(cr, uid, alias_ids, context=context) + return res + +class res_users(osv.Model): + _inherit = 'res.users' + _columns = { + 'default_section_id': fields.many2one('crm.case.section', 'Default Sales Team'), + } + + def __init__(self, pool, cr): + init_res = super(res_users, self).__init__(pool, cr) + # duplicate list to avoid modifying the original reference + self.SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS) + self.SELF_WRITEABLE_FIELDS.extend(['default_section_id']) + return init_res + + + \ No newline at end of file diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml new file mode 100644 index 00000000000..687919ff66a --- /dev/null +++ b/addons/sale_team/sale_team.xml @@ -0,0 +1,232 @@ + + + + + + + Users Preferences + res.users + + + + + + + + + + + + + res.users.preferences.form + res.users + + + + + + + + + + + + + + crm.case.section.kanban + crm.case.section + + + + + + + + + + +
+
+ í +
+
+

+
+ %% +
+
+
+ + + +
+
+
+
+
+
+
+
+ + + + + + + Case Sections - Search + crm.case.section + + + + + + + + + + + + + + + + + + + + Sales Teams + crm.case.section + form + kanban,tree,form + {} + + +

+ Click here to define a new sales team. +

+ Use sales team to organize your different salespersons or + departments into separate teams. Each team will work in + its own list of opportunities. +

+
+
+ + + + + crm.case.section.form + crm.case.section + +
+ +
+
+ + + + + + + + + + + + + + + + + + +
+ X +
+
+ +
+
+
+
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+ + + + + crm.case.section.tree + crm.case.section + child_ids + + + + + + + + + + + + + + + Sales Teams + crm.case.section + form + + +

+ Click here to define a new sales team. +

+ Use sales team to organize your different salespersons or + departments into separate teams. Each team will work in + its own list of opportunities. +

+
+
+ + +
+
\ No newline at end of file diff --git a/addons/sale_team/sale_team_config.py b/addons/sale_team/sale_team_config.py new file mode 100644 index 00000000000..33a5e84b8de --- /dev/null +++ b/addons/sale_team/sale_team_config.py @@ -0,0 +1,45 @@ + +from openerp import SUPERUSER_ID +from openerp.osv import fields, osv + + +class sala_team_configuration(osv.TransientModel): + _name = 'sale.team.config.settings' + _inherit = ['sale.config.settings', 'fetchmail.config.settings'] + + def set_group_multi_salesteams(self, cr, uid, ids, context=None): + """ This method is automatically called by res_config as it begins + with set. It is used to implement the 'one group or another' + behavior. We have to perform some group manipulation by hand + because in res_config.execute(), set_* methods are called + after group_*; therefore writing on an hidden res_config file + could not work. + If group_multi_salesteams is checked: remove group_mono_salesteams + from group_user, remove the users. Otherwise, just add + group_mono_salesteams in group_user. + The inverse logic about group_multi_salesteams is managed by the + normal behavior of 'group_multi_salesteams' field. + """ + def ref(xml_id): + mod, xml = xml_id.split('.', 1) + return self.pool['ir.model.data'].get_object(cr, uid, mod, xml, context) + + for obj in self.browse(cr, uid, ids, context=context): + config_group = ref('base.group_mono_salesteams') + base_group = ref('base.group_user') + if obj.group_multi_salesteams: + base_group.write({'implied_ids': [(3, config_group.id)]}) + config_group.write({'users': [(3, u.id) for u in base_group.users]}) + else: + base_group.write({'implied_ids': [(4, config_group.id)]}) + return True + + _columns = { + + 'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams", + implied_group='base.group_multi_salesteams', + help="""Allows you to use Sales Teams to manage your leads and opportunities."""), + } + + + diff --git a/addons/sale_team/sale_team_security.xml b/addons/sale_team/sale_team_security.xml new file mode 100644 index 00000000000..ff8eef01678 --- /dev/null +++ b/addons/sale_team/sale_team_security.xml @@ -0,0 +1,18 @@ + + + + + Do Not Use Sales Teams + + + + + + + + Manage Sales Teams + + + + + From 3676ec8291fa3ccbc6e6ae69123db38f5f6239b6 Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Fri, 28 Mar 2014 16:17:12 +0530 Subject: [PATCH 02/51] [update in code] --- addons/sale_crm/__openerp__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/sale_crm/__openerp__.py b/addons/sale_crm/__openerp__.py index ab94069c173..859a67b3e56 100644 --- a/addons/sale_crm/__openerp__.py +++ b/addons/sale_crm/__openerp__.py @@ -45,7 +45,6 @@ modules. 'process/sale_crm_process.xml', 'security/sale_crm_security.xml', 'security/ir.model.access.csv', - 'report/sale_report_view.xml', ], 'js': [ 'static/src/js/sale_crm.js', From 69c060639af2ada321cb7198a1e92fa785f94586 Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Tue, 1 Apr 2014 15:14:34 +0530 Subject: [PATCH 03/51] [update] update in code --- addons/crm/security/ir.model.access.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv index fde894e4c31..639c5928894 100644 --- a/addons/crm/security/ir.model.access.csv +++ b/addons/crm/security/ir.model.access.csv @@ -11,6 +11,7 @@ access_crm_phonecall_manager,crm.phonecall.manager,model_crm_phonecall,base.grou access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_user,1,0,0,0 access_crm_lead,crm.lead,model_crm_lead,base.group_sale_salesman,1,1,1,0 access_crm_phonecall,crm.phonecall,model_crm_phonecall,base.group_sale_salesman,1,1,1,0 +access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 access_crm_case_stage,crm.case.stage,model_crm_case_stage,,1,0,0,0 access_crm_case_stage_manager,crm.case.stage,model_crm_case_stage,base.group_sale_manager,1,1,1,1 access_crm_case_resource_type_user,crm_case_resource_type user,model_crm_case_resource_type,base.group_sale_salesman,1,1,1,0 From 44d3f263cca585b4ff391d41811a5faa3cc913ce Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Tue, 1 Apr 2014 18:34:23 +0530 Subject: [PATCH 04/51] [IMP] update on code --- addons/crm/crm_lead.py | 13 +++---------- addons/crm/security/ir.model.access.csv | 2 ++ 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index f97b7d0adf8..b1a0effaf63 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -345,19 +345,12 @@ class crm_lead(format_address, osv.osv): section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context) if section_ids: section_id = section_ids[0] - return {'value': {'section_id': section_id}} - - def on_change_user(self, cr, uid, ids, user_id, context=None): - """ Override of on change user_id on lead/opportunity; when having sale - the new logic is : - - use user.default_section_id - - or fallback on previous behavior """ - if user_id: + else: user = self.pool.get('res.users').browse(cr, uid, user_id, context=context) if user.default_section_id and user.default_section_id.id: return {'value': {'section_id': user.default_section_id.id}} - return super(sale_crm_lead, self).on_change_user(cr, uid, ids, user_id, context=context) - + return {'value': {'section_id': section_id}} + def stage_find(self, cr, uid, cases, section_id, domain=None, order='sequence', context=None): """ Override of the base.stage method Parameter of the stage search taken from the lead: diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv index 639c5928894..52648c8e347 100644 --- a/addons/crm/security/ir.model.access.csv +++ b/addons/crm/security/ir.model.access.csv @@ -5,12 +5,14 @@ access_crm_segmentation,crm.segmentation,model_crm_segmentation,base.group_sale_ access_crm_segmentation_line,crm.segmentation.line,model_crm_segmentation_line,base.group_sale_manager,1,1,1,1 access_crm_case_channel_user,crm.case.channel user,model_crm_case_channel,base.group_sale_salesman,1,0,0,0 access_crm_case_channel_manager,crm.case.channel manager,model_crm_case_channel,base.group_sale_manager,1,1,1,1 +access_crm_case_section,crm.case.section,model_crm_case_section,base.group_user,1,0,0,0 access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_sale_salesman,1,1,1,0 access_crm_lead_manager,crm.lead.manager,model_crm_lead,base.group_sale_manager,1,1,1,1 access_crm_phonecall_manager,crm.phonecall.manager,model_crm_phonecall,base.group_sale_manager,1,1,1,1 access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_user,1,0,0,0 access_crm_lead,crm.lead,model_crm_lead,base.group_sale_salesman,1,1,1,0 access_crm_phonecall,crm.phonecall,model_crm_phonecall,base.group_sale_salesman,1,1,1,0 +access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,1,1,0 access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 access_crm_case_stage,crm.case.stage,model_crm_case_stage,,1,0,0,0 access_crm_case_stage_manager,crm.case.stage,model_crm_case_stage,base.group_sale_manager,1,1,1,1 From 0ee42746df8c8e688c0eefbcc0faed0b9d795194 Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Wed, 2 Apr 2014 12:41:23 +0530 Subject: [PATCH 05/51] [update] move alias for default lead in to crm --- addons/crm/crm.py | 20 ++++++++++++++++++++ addons/crm/security/ir.model.access.csv | 3 --- addons/sale_team/sale_team.py | 19 ------------------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index eb10e468602..166cfc7f435 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -90,6 +90,8 @@ class crm_case_stage(osv.osv): class crm_case_section(osv.osv): """ Model for sales teams. """ + _name = "crm.case.section" + _inherits = {'mail.alias': 'alias_id'} _inherit = 'crm.case.section' _description = "Sales Teams" @@ -118,6 +120,8 @@ class crm_case_section(osv.osv): return res _columns = { + + 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."), 'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'), 'use_leads': fields.boolean('Leads', help="The first contact you get with a potential customer is a lead you qualify before converting it into a real business opportunity. Check this box to manage leads in this sales team."), @@ -138,7 +142,23 @@ class crm_case_section(osv.osv): 'stage_ids': _get_stage_common, 'use_leads': True, } + + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + create_context = dict(context, alias_model_name='crm.lead', alias_parent_model_name=self._name) + section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) + section = self.browse(cr, uid, section_id, context=context) + self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) + return section_id + def unlink(self, cr, uid, ids, context=None): + # Cascade-delete mail aliases as well, as they should not exist without the sales team. + mail_alias = self.pool.get('mail.alias') + alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id] + res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) + mail_alias.unlink(cr, uid, alias_ids, context=context) + return res class crm_case_categ(osv.osv): """ Category of Case """ diff --git a/addons/crm/security/ir.model.access.csv b/addons/crm/security/ir.model.access.csv index 52648c8e347..fde894e4c31 100644 --- a/addons/crm/security/ir.model.access.csv +++ b/addons/crm/security/ir.model.access.csv @@ -5,15 +5,12 @@ access_crm_segmentation,crm.segmentation,model_crm_segmentation,base.group_sale_ access_crm_segmentation_line,crm.segmentation.line,model_crm_segmentation_line,base.group_sale_manager,1,1,1,1 access_crm_case_channel_user,crm.case.channel user,model_crm_case_channel,base.group_sale_salesman,1,0,0,0 access_crm_case_channel_manager,crm.case.channel manager,model_crm_case_channel,base.group_sale_manager,1,1,1,1 -access_crm_case_section,crm.case.section,model_crm_case_section,base.group_user,1,0,0,0 access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_sale_salesman,1,1,1,0 access_crm_lead_manager,crm.lead.manager,model_crm_lead,base.group_sale_manager,1,1,1,1 access_crm_phonecall_manager,crm.phonecall.manager,model_crm_phonecall,base.group_sale_manager,1,1,1,1 access_crm_case_categ,crm.case.categ,model_crm_case_categ,base.group_user,1,0,0,0 access_crm_lead,crm.lead,model_crm_lead,base.group_sale_salesman,1,1,1,0 access_crm_phonecall,crm.phonecall,model_crm_phonecall,base.group_sale_salesman,1,1,1,0 -access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,1,1,0 -access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 access_crm_case_stage,crm.case.stage,model_crm_case_stage,,1,0,0,0 access_crm_case_stage_manager,crm.case.stage,model_crm_case_stage,base.group_sale_manager,1,1,1,1 access_crm_case_resource_type_user,crm_case_resource_type user,model_crm_case_resource_type,base.group_sale_salesman,1,1,1,0 diff --git a/addons/sale_team/sale_team.py b/addons/sale_team/sale_team.py index 3643a6dc26c..fd898bb248c 100644 --- a/addons/sale_team/sale_team.py +++ b/addons/sale_team/sale_team.py @@ -36,7 +36,6 @@ class crm_case_section(osv.osv): _inherit = ['mail.thread', 'ir.needaction_mixin'] _description = "Sales Teams" _order = "complete_name" - # number of periods for lead/opportunities/... tracking in salesteam kanban dashboard/kanban view _period_number = 5 def get_full_name(self, cr, uid, ids, field_name, arg, context=None): @@ -87,7 +86,6 @@ class crm_case_section(osv.osv): 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), 'note': fields.text('Description'), 'working_hours': fields.float('Working Hours', digits=(16, 2)), - 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."), 'color': fields.integer('Color Index'), } @@ -119,23 +117,6 @@ class crm_case_section(osv.osv): res.append((record['id'], name)) return res - def create(self, cr, uid, vals, context=None): - if context is None: - context = {} - create_context = dict(context, alias_model_name=self._name, alias_parent_model_name=self._name) - section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) - section = self.browse(cr, uid, section_id, context=context) - self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) - return section_id - - def unlink(self, cr, uid, ids, context=None): - # Cascade-delete mail aliases as well, as they should not exist without the sales team. - mail_alias = self.pool.get('mail.alias') - alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id] - res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) - mail_alias.unlink(cr, uid, alias_ids, context=context) - return res - class res_users(osv.Model): _inherit = 'res.users' _columns = { From bc90e644a9f3d2636992ad2265e76838eaeb702c Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Fri, 4 Apr 2014 17:18:07 +0530 Subject: [PATCH 06/51] [IMP]done the require changes --- addons/crm/crm_case_section_view.xml | 23 ++++++++++++++++++++++- addons/sale_team/sale_team.py | 1 - addons/sale_team/sale_team.xml | 19 ------------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index a56459a7065..caa282c4e7f 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -89,10 +89,14 @@ + +
+ %% +
Leads @@ -152,9 +156,26 @@
-
+ + + + diff --git a/addons/sale_team/sale_team.py b/addons/sale_team/sale_team.py index fd898bb248c..423bb21391c 100644 --- a/addons/sale_team/sale_team.py +++ b/addons/sale_team/sale_team.py @@ -32,7 +32,6 @@ from openerp.osv import osv class crm_case_section(osv.osv): _name = "crm.case.section" - _inherits = {'mail.alias': 'alias_id'} _inherit = ['mail.thread', 'ir.needaction_mixin'] _description = "Sales Teams" _order = "complete_name" diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index 687919ff66a..0120dbd21b5 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -41,7 +41,6 @@ - @@ -56,9 +55,6 @@

-
- %% -
@@ -138,21 +134,6 @@ - - From cfc26235349087748c58e9b4ce39b775fc4c709a Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Wed, 9 Apr 2014 15:35:21 +0530 Subject: [PATCH 07/51] [IMP] improve related yml bug --- addons/crm/__openerp__.py | 1 - addons/sale_team/static/src/css/sale_team.css | 62 +++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 addons/sale_team/static/src/css/sale_team.css diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 7d5052c1c4e..b72fa3e5ae2 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -109,7 +109,6 @@ Dashboard for CRM will include: 'test': [ 'test/crm_access_group_users.yml', 'test/crm_lead_message.yml', - 'test/lead2opportunity2win.yml', 'test/lead2opportunity_assign_salesmen.yml', 'test/crm_lead_merge.yml', 'test/crm_lead_cancel.yml', diff --git a/addons/sale_team/static/src/css/sale_team.css b/addons/sale_team/static/src/css/sale_team.css new file mode 100644 index 00000000000..8999f758e41 --- /dev/null +++ b/addons/sale_team/static/src/css/sale_team.css @@ -0,0 +1,62 @@ +@charset "utf-8"; +.openerp .oe_kanban_view .oe_kanban_crm_salesteams { + /* Customize width and height of kanban according bootstrap3 */ + width: 357px; + min-height: 254px !important; + /* End of customize */ + cursor: default; +} +.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list { + position: relative; + /* Improved margin to set alignment of list items according bootstrap3 */ + margin: 10px 0 10px 9px; + min-height: 10px; +} +.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div { + width: 160px; + height: 22px; + margin: 0 !important; + position: relative; + display: inline-block; + float: left; +} +.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div a:nth-child(2n) { + position: absolute; + left: 90px; + top: 0; +} +.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div:nth-child(2n) a:nth-child(2n) { + left: 110px; +} +.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list a:hover { + text-decoration: underline !important; +} +.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center { + text-align: center; + margin: 3px 0; +} +.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center .oe_sum { + margin: 0; + font-size: 40px; +} +.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center .oe_subsum { + font-size: 10px; +} +.openerp .oe_kanban_view .oe_salesteams_help { + display: inline-block; +} +.openerp .oe_kanban_view .oe_kanban_salesteams_avatars { + margin-top: 20px; +} +.openerp .oe_kanban_view .oe_kanban_salesteams_avatars img { + width: 30px; + height: 30px; + padding-left: 0px; + margin-top: 3px; + -moz-border-radius: 2px; + -webkit-border-radius: 2px; + border-radius: 2px; + -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); + -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} From 8a576d5d9470762c95cb82173c61316112ffd988 Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Wed, 9 Apr 2014 17:20:31 +0530 Subject: [PATCH 08/51] [IMP] fix the issue --- addons/gamification_sale_crm/sale_crm_goals_demo.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/gamification_sale_crm/sale_crm_goals_demo.xml b/addons/gamification_sale_crm/sale_crm_goals_demo.xml index d8e26551c32..5c0466f34ed 100644 --- a/addons/gamification_sale_crm/sale_crm_goals_demo.xml +++ b/addons/gamification_sale_crm/sale_crm_goals_demo.xml @@ -3,10 +3,10 @@ - + From 31c7c33260f977ff2bb5c4783b4a37fcd5a0f717 Mon Sep 17 00:00:00 2001 From: "Ravish (Open ERP)" Date: Wed, 9 Apr 2014 19:10:28 +0530 Subject: [PATCH 09/51] [imp]improve the code --- addons/crm/__openerp__.py | 3 --- addons/crm/crm_case_section_view.xml | 4 +--- addons/sale/sale_view.xml | 8 ++------ addons/sale_team/__openerp__.py | 3 ++- 4 files changed, 5 insertions(+), 13 deletions(-) diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index b72fa3e5ae2..63dc175a0dd 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -119,9 +119,6 @@ Dashboard for CRM will include: 'test/crm_lead_unlink.yml', 'test/crm_lead_find_stage.yml', ], - 'css': [ - 'static/src/css/crm.css' - ], 'js': [ 'static/src/js/crm_case_section.js', ], diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index caa282c4e7f..8cc9c0bcd56 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -155,9 +155,7 @@ -
-
-
+
diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 9a6ef2a2e32..fafca287f2e 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -711,7 +711,7 @@
diff --git a/addons/sale_team/__openerp__.py b/addons/sale_team/__openerp__.py index 9c303ae3ae3..35965f4609d 100644 --- a/addons/sale_team/__openerp__.py +++ b/addons/sale_team/__openerp__.py @@ -22,7 +22,7 @@ 'name' : 'Sale Team', 'version' : '1.0', 'author' : 'OpenERP SA', - 'category': 'hidden', + 'category': 'hidden', 'sequence': 10, 'summary': 'Sales Team', 'description': """ """, @@ -30,6 +30,7 @@ 'depends' : ['base','web_kanban','calendar'], 'data': ['sale_team.xml','sale_team_security.xml','res_config_view.xml','ir.model.access.csv'], 'demo': [], + 'css': ['static/src/css/sale_team.css'], 'installable': True, 'auto_install': True, } From 458b95eacb568ae6bd1346a5a10f997df01d3a25 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Thu, 10 Apr 2014 18:47:17 +0530 Subject: [PATCH 10/51] [IMP] imrove yml testcases --- addons/crm/__openerp__.py | 1 + addons/gamification/models/goal.py | 4 ++-- addons/gamification_sale_crm/sale_crm_goals_demo.xml | 5 ++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 63dc175a0dd..1b777ab3525 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -109,6 +109,7 @@ Dashboard for CRM will include: 'test': [ 'test/crm_access_group_users.yml', 'test/crm_lead_message.yml', + 'test/lead2opportunity2win.yml', 'test/lead2opportunity_assign_salesmen.yml', 'test/crm_lead_merge.yml', 'test/crm_lead_cancel.yml', diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index 8e79c43c8a6..62b4e6a9585 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -279,8 +279,8 @@ class gamification_goal(osv.Model): if goal.definition_id.computation_mode == 'sum': field_name = goal.definition_id.field_id.name - res = obj.read_group(cr, uid, domain, [field_name], [''], context=context) - new_value = res and res[0][field_name] or 0.0 + res = obj.read_group(cr, uid, domain, [field_name], [], context=context) + new_value = res and res[res.keys()[0]][field_name] or 0.0 else: # computation mode = count new_value = obj.search(cr, uid, domain, context=context, count=True) diff --git a/addons/gamification_sale_crm/sale_crm_goals_demo.xml b/addons/gamification_sale_crm/sale_crm_goals_demo.xml index 5c0466f34ed..064d2cbdc20 100644 --- a/addons/gamification_sale_crm/sale_crm_goals_demo.xml +++ b/addons/gamification_sale_crm/sale_crm_goals_demo.xml @@ -3,10 +3,10 @@ - + @@ -16,7 +16,6 @@ 2000 - inprogress From 53f71ba3d45c327badbae77cba68be26f02e06a2 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Fri, 11 Apr 2014 18:59:26 +0530 Subject: [PATCH 11/51] [IMP]imrpove sale_team sequence and depends --- addons/sale_team/__openerp__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/sale_team/__openerp__.py b/addons/sale_team/__openerp__.py index 35965f4609d..f2de8fb432e 100644 --- a/addons/sale_team/__openerp__.py +++ b/addons/sale_team/__openerp__.py @@ -23,11 +23,10 @@ 'version' : '1.0', 'author' : 'OpenERP SA', 'category': 'hidden', - 'sequence': 10, 'summary': 'Sales Team', 'description': """ """, 'website': 'http://www.openerp.com', - 'depends' : ['base','web_kanban','calendar'], + 'depends' : ['base','web_kanban','mail'], 'data': ['sale_team.xml','sale_team_security.xml','res_config_view.xml','ir.model.access.csv'], 'demo': [], 'css': ['static/src/css/sale_team.css'], From e25229a6da7c5dbe4736cdbecb7aeb44f323af31 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Tue, 15 Apr 2014 16:08:37 +0530 Subject: [PATCH 12/51] [IMP] update depends on sale_team and sale --- addons/sale/__openerp__.py | 2 +- addons/sale_team/__openerp__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py index 44cbb3227ca..ea6f0f187bd 100644 --- a/addons/sale/__openerp__.py +++ b/addons/sale/__openerp__.py @@ -59,7 +59,7 @@ The Dashboard for the Sales Manager will include 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', 'images': ['images/sale_dashboard.jpeg','images/Sale_order_line_to_invoice.jpeg','images/sale_order.jpeg','images/sales_analysis.jpeg'], - 'depends': ['sale_team','account_voucher'], + 'depends': ['sale_team','account_voucher','report'], 'data': [ 'wizard/sale_make_invoice_advance.xml', 'wizard/sale_line_invoice.xml', diff --git a/addons/sale_team/__openerp__.py b/addons/sale_team/__openerp__.py index f2de8fb432e..bb1e8ab5830 100644 --- a/addons/sale_team/__openerp__.py +++ b/addons/sale_team/__openerp__.py @@ -26,7 +26,7 @@ 'summary': 'Sales Team', 'description': """ """, 'website': 'http://www.openerp.com', - 'depends' : ['base','web_kanban','mail'], + 'depends' : ['base','web_kanban','mail', 'resource'], 'data': ['sale_team.xml','sale_team_security.xml','res_config_view.xml','ir.model.access.csv'], 'demo': [], 'css': ['static/src/css/sale_team.css'], From 9bb0e6f30ec3a2ef1055e167c2fae8ec0005ba2e Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Mon, 21 Apr 2014 15:32:12 +0530 Subject: [PATCH 13/51] [IMP] improve demo data and menu items --- addons/crm/crm.py | 17 ++------ addons/crm/crm_data.xml | 14 +++---- addons/crm/crm_demo.xml | 16 -------- addons/crm/crm_lead.py | 1 + addons/crm/crm_lead_data.xml | 34 ++++++++-------- addons/crm/crm_lead_demo.xml | 52 ++++++++++++------------- addons/crm/crm_phonecall_data.xml | 4 +- addons/crm/crm_phonecall_demo.xml | 12 +++--- addons/sale_team/__openerp__.py | 12 ++++-- addons/sale_team/sale_team.py | 14 ++++++- addons/sale_team/sale_team.xml | 4 +- addons/sale_team/sale_team_security.xml | 18 --------- 12 files changed, 83 insertions(+), 115 deletions(-) delete mode 100644 addons/sale_team/sale_team_security.xml diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 166cfc7f435..da57ae628b3 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -91,7 +91,6 @@ class crm_case_stage(osv.osv): class crm_case_section(osv.osv): """ Model for sales teams. """ _name = "crm.case.section" - _inherits = {'mail.alias': 'alias_id'} _inherit = 'crm.case.section' _description = "Sales Teams" @@ -120,18 +119,17 @@ class crm_case_section(osv.osv): return res _columns = { - - 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."), 'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'), 'use_leads': fields.boolean('Leads', help="The first contact you get with a potential customer is a lead you qualify before converting it into a real business opportunity. Check this box to manage leads in this sales team."), - + 'monthly_open_leads': fields.function(_get_opportunities_data, type="string", readonly=True, multi='_get_opportunities_data', string='Open Leads per Month'), 'monthly_planned_revenue': fields.function(_get_opportunities_data, type="string", readonly=True, multi='_get_opportunities_data', - string='Planned Revenue per Month') + string='Planned Revenue per Month'), + 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), } def _get_stage_common(self, cr, uid, context): @@ -142,15 +140,6 @@ class crm_case_section(osv.osv): 'stage_ids': _get_stage_common, 'use_leads': True, } - - def create(self, cr, uid, vals, context=None): - if context is None: - context = {} - create_context = dict(context, alias_model_name='crm.lead', alias_parent_model_name=self._name) - section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) - section = self.browse(cr, uid, section_id, context=context) - self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) - return section_id def unlink(self, cr, uid, ids, context=None): # Cascade-delete mail aliases as well, as they should not exist without the sales team. diff --git a/addons/crm/crm_data.xml b/addons/crm/crm_data.xml index 231a7124824..41f4819e500 100644 --- a/addons/crm/crm_data.xml +++ b/addons/crm/crm_data.xml @@ -26,34 +26,30 @@ Email - - Direct Sales - DM + True - sales - Cash - + Check - + Credit Card - + Demand Draft - + diff --git a/addons/crm/crm_demo.xml b/addons/crm/crm_demo.xml index 82bdcd8fcd9..1541753523a 100644 --- a/addons/crm/crm_demo.xml +++ b/addons/crm/crm_demo.xml @@ -6,22 +6,6 @@ - - - - - - Indirect Sales - IM - - - - - Marketing - SPD - - - OpenERP partners True diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 32891c21981..e0149269243 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -195,6 +195,7 @@ class crm_lead(format_address, osv.osv): resource_id = resource_ids[0] duration = float(ans.days) + if lead.section_id and lead.section_id.resource_calendar_id: duration = float(ans.days) * 24 new_dates = cal_obj.interval_get(cr, diff --git a/addons/crm/crm_lead_data.xml b/addons/crm/crm_lead_data.xml index 1abcdac9712..6dbe9b2ba88 100644 --- a/addons/crm/crm_lead_data.xml +++ b/addons/crm/crm_lead_data.xml @@ -60,7 +60,7 @@ opportunity - + Telesales - + Email Campaign - Services - + Email Campaign - Products - + Twitter Ads - + Google Adwords - + Banner Ads - + Television - + Newsletter - + Product - + Software - + Services - + Information - + Design - + Training - + Consulting - + Other - + diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index dab17888d7a..0187f14555f 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -20,7 +20,7 @@ 1 - + Hello, @@ -55,7 +55,7 @@ Could you please send me the details ? 4 - + @@ -84,7 +84,7 @@ Could you please send me the details ? 2 - + @@ -105,7 +105,7 @@ Could you please send me the details ? 3 - + @@ -130,7 +130,7 @@ Could you please send me the details ? 3 - + Hi, @@ -157,7 +157,7 @@ Contact: +1 813 494 5005 3 - + @@ -175,7 +175,7 @@ Contact: +1 813 494 5005 5 - + @@ -194,7 +194,7 @@ Contact: +1 813 494 5005 4 - + @@ -213,7 +213,7 @@ Contact: +1 813 494 5005 2 - + @@ -231,7 +231,7 @@ Contact: +1 813 494 5005 2 - + Hi, @@ -254,7 +254,7 @@ Andrew 3 - + @@ -272,7 +272,7 @@ Andrew 2 - + @@ -310,7 +310,7 @@ Andrew Meeting for pricing information. - + @@ -335,7 +335,7 @@ Andrew Send Catalogue by Email - + @@ -356,7 +356,7 @@ Andrew Call to ask system requirement - + @@ -383,7 +383,7 @@ Andrew Convert to quote - + @@ -408,7 +408,7 @@ Andrew Send price list regarding our interventions - + @@ -435,7 +435,7 @@ Andrew Call to define real needs about training - + @@ -462,7 +462,7 @@ Andrew Ask for the good receprion of the proposition - + @@ -479,7 +479,7 @@ Andrew 2 - + @@ -493,7 +493,7 @@ Andrew 3 - + @@ -511,7 +511,7 @@ Andrew 3 - + @@ -526,7 +526,7 @@ Andrew 5 - + @@ -545,7 +545,7 @@ Andrew 5 - + @@ -568,7 +568,7 @@ Andrew 2 Conf call with technical service - + @@ -592,7 +592,7 @@ Andrew Send Catalogue by Email - + diff --git a/addons/crm/crm_phonecall_data.xml b/addons/crm/crm_phonecall_data.xml index 0e2bb6511c5..4e613e7f72a 100644 --- a/addons/crm/crm_phonecall_data.xml +++ b/addons/crm/crm_phonecall_data.xml @@ -6,13 +6,13 @@ --> Inbound - + Outbound - + diff --git a/addons/crm/crm_phonecall_demo.xml b/addons/crm/crm_phonecall_demo.xml index 4002e18815e..c6026c8d894 100644 --- a/addons/crm/crm_phonecall_demo.xml +++ b/addons/crm/crm_phonecall_demo.xml @@ -11,7 +11,7 @@ Left the message done +34 934 340 230 - + @@ -24,7 +24,7 @@ Need more information on the proposed deal done +44 121 690 4596 - + @@ -37,7 +37,7 @@ Ask for convenient time of meeting open +1 786 525 0724 - + @@ -50,7 +50,7 @@ done (077) 582-4035 (077) 341-3591 - + @@ -63,7 +63,7 @@ More information on the proposed deal pending +1 312 349 2324 - + @@ -74,7 +74,7 @@ Proposal for discount offer open +34 230 953 485 - + diff --git a/addons/sale_team/__openerp__.py b/addons/sale_team/__openerp__.py index bb1e8ab5830..9bbdc73e876 100644 --- a/addons/sale_team/__openerp__.py +++ b/addons/sale_team/__openerp__.py @@ -22,13 +22,17 @@ 'name' : 'Sale Team', 'version' : '1.0', 'author' : 'OpenERP SA', - 'category': 'hidden', + 'category': 'Sales Management', 'summary': 'Sales Team', 'description': """ """, 'website': 'http://www.openerp.com', - 'depends' : ['base','web_kanban','mail', 'resource'], - 'data': ['sale_team.xml','sale_team_security.xml','res_config_view.xml','ir.model.access.csv'], - 'demo': [], + 'depends' : ['base','mail'], + 'data': ['security/sale_team_security.xml', + 'res_config_view.xml', + 'ir.model.access.csv', + 'sale_team_data.xml', + 'sale_team.xml',], + 'demo': ['sale_team_demo.xml'], 'css': ['static/src/css/sale_team.css'], 'installable': True, 'auto_install': True, diff --git a/addons/sale_team/sale_team.py b/addons/sale_team/sale_team.py index 423bb21391c..eb6c892c2e9 100644 --- a/addons/sale_team/sale_team.py +++ b/addons/sale_team/sale_team.py @@ -34,6 +34,7 @@ class crm_case_section(osv.osv): _name = "crm.case.section" _inherit = ['mail.thread', 'ir.needaction_mixin'] _description = "Sales Teams" + _inherits = {'mail.alias': 'alias_id'} _order = "complete_name" _period_number = 5 @@ -71,6 +72,7 @@ class crm_case_section(osv.osv): return section_result _columns = { + 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."), 'name': fields.char('Sales Team', size=64, required=True, translate=True), 'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True), 'code': fields.char('Code', size=8), @@ -82,7 +84,6 @@ class crm_case_section(osv.osv): 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by OpenERP about cases in this sales team"), 'parent_id': fields.many2one('crm.case.section', 'Parent Team'), 'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Teams'), - 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), 'note': fields.text('Description'), 'working_hours': fields.float('Working Hours', digits=(16, 2)), 'color': fields.integer('Color Index'), @@ -115,7 +116,16 @@ class crm_case_section(osv.osv): name = record['parent_id'][1] + ' / ' + name res.append((record['id'], name)) return res - + + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + create_context = dict(context, alias_model_name='res.user', alias_parent_model_name=self._name) + section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) + section = self.browse(cr, uid, section_id, context=context) + self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) + return section_id + class res_users(osv.Model): _inherit = 'res.users' _columns = { diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index 0120dbd21b5..f2fea79a933 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -1,7 +1,9 @@ - + Users Preferences diff --git a/addons/sale_team/sale_team_security.xml b/addons/sale_team/sale_team_security.xml deleted file mode 100644 index ff8eef01678..00000000000 --- a/addons/sale_team/sale_team_security.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - Do Not Use Sales Teams - - - - - - - - Manage Sales Teams - - - - - From 3bc6bf2d99d000d0c47b19a07cd39e65e878e43f Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Mon, 21 Apr 2014 16:40:57 +0530 Subject: [PATCH 14/51] [IMP] add demo data files --- addons/crm/crm.py | 9 +---- addons/crm/crm_case_section_view.xml | 1 - addons/crm/crm_lead_menu.xml | 2 +- addons/sale_crm/sale_crm_demo.xml | 24 +++++------ addons/sale_team/sale_team.py | 10 ++++- addons/sale_team/sale_team.xml | 1 + addons/sale_team/sale_team_data.xml | 11 +++++ addons/sale_team/sale_team_demo.xml | 20 ++++++++++ .../sale_team/security/sale_team_security.xml | 40 +++++++++++++++++++ 9 files changed, 95 insertions(+), 23 deletions(-) create mode 100644 addons/sale_team/sale_team_data.xml create mode 100644 addons/sale_team/sale_team_demo.xml create mode 100644 addons/sale_team/security/sale_team_security.xml diff --git a/addons/crm/crm.py b/addons/crm/crm.py index da57ae628b3..c43a3feabb8 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -93,6 +93,7 @@ class crm_case_section(osv.osv): _name = "crm.case.section" _inherit = 'crm.case.section' _description = "Sales Teams" + _alias_model_name = 'crm.lead' def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): """ Get opportunities-related data for salesteam kanban view @@ -140,14 +141,6 @@ class crm_case_section(osv.osv): 'stage_ids': _get_stage_common, 'use_leads': True, } - - def unlink(self, cr, uid, ids, context=None): - # Cascade-delete mail aliases as well, as they should not exist without the sales team. - mail_alias = self.pool.get('mail.alias') - alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id] - res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) - mail_alias.unlink(cr, uid, alias_ids, context=context) - return res class crm_case_categ(osv.osv): """ Category of Case """ diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index 8cc9c0bcd56..10888377050 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -89,7 +89,6 @@ - diff --git a/addons/crm/crm_lead_menu.xml b/addons/crm/crm_lead_menu.xml index dffe03dd4b3..60dc307cc0f 100644 --- a/addons/crm/crm_lead_menu.xml +++ b/addons/crm/crm_lead_menu.xml @@ -81,7 +81,7 @@ action="crm_case_category_act_leads_all"/> + groups="base.group_sale_salesman,base.group_sale_manager"/> diff --git a/addons/sale_crm/sale_crm_demo.xml b/addons/sale_crm/sale_crm_demo.xml index 25f653548d1..9af5ae7ebce 100644 --- a/addons/sale_crm/sale_crm_demo.xml +++ b/addons/sale_crm/sale_crm_demo.xml @@ -2,12 +2,12 @@ - + 52700 60000 - + Indirect Sales IM 36000 @@ -21,7 +21,7 @@ - + draft in_invoice @@ -69,7 +69,7 @@ - + draft out_invoice @@ -98,32 +98,32 @@ - + - + - + - + - + - + - + - + diff --git a/addons/sale_team/sale_team.py b/addons/sale_team/sale_team.py index eb6c892c2e9..3c13514bd11 100644 --- a/addons/sale_team/sale_team.py +++ b/addons/sale_team/sale_team.py @@ -37,6 +37,7 @@ class crm_case_section(osv.osv): _inherits = {'mail.alias': 'alias_id'} _order = "complete_name" _period_number = 5 + _alias_model_name = 'mail.message' def get_full_name(self, cr, uid, ids, field_name, arg, context=None): return dict(self.name_get(cr, uid, ids, context=context)) @@ -120,11 +121,18 @@ class crm_case_section(osv.osv): def create(self, cr, uid, vals, context=None): if context is None: context = {} - create_context = dict(context, alias_model_name='res.user', alias_parent_model_name=self._name) + create_context = dict(context, alias_model_name=self._alias_model_name, alias_parent_model_name=self._name) section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) section = self.browse(cr, uid, section_id, context=context) self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) return section_id + def unlink(self, cr, uid, ids, context=None): + # Cascade-delete mail aliases as well, as they should not exist without the sales team. + mail_alias = self.pool.get('mail.alias') + alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id] + res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) + mail_alias.unlink(cr, uid, alias_ids, context=context) + return res class res_users(osv.Model): _inherit = 'res.users' diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index f2fea79a933..b3972e71342 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -42,6 +42,7 @@ + diff --git a/addons/sale_team/sale_team_data.xml b/addons/sale_team/sale_team_data.xml new file mode 100644 index 00000000000..eef5e5809a1 --- /dev/null +++ b/addons/sale_team/sale_team_data.xml @@ -0,0 +1,11 @@ + + + + + Direct Sales + DM + sales + + + + diff --git a/addons/sale_team/sale_team_demo.xml b/addons/sale_team/sale_team_demo.xml new file mode 100644 index 00000000000..b24c047fba1 --- /dev/null +++ b/addons/sale_team/sale_team_demo.xml @@ -0,0 +1,20 @@ + + + + + + + + Indirect Sales + IM + + + + + Marketing + SPD + + + + + \ No newline at end of file diff --git a/addons/sale_team/security/sale_team_security.xml b/addons/sale_team/security/sale_team_security.xml new file mode 100644 index 00000000000..53fb560e332 --- /dev/null +++ b/addons/sale_team/security/sale_team_security.xml @@ -0,0 +1,40 @@ + + + + + + User: Own Leads Only + + + the user will have access to his own data in the sales application. + + + + User: All Leads + + + the user will have access to all records of everyone in the sales application. + + + + Manager + the user will have an access to the sales configuration as well as statistic reports. + + + + + + + + + + + + + + + + + + + From 2d4ebc9edcf1f53f24f0e99e13753c5649360a50 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Mon, 21 Apr 2014 17:54:27 +0530 Subject: [PATCH 15/51] [IMP] improve kanban view and dependent demo data record --- addons/crm/crm_case_section_view.xml | 36 ++++++++++++++-------------- addons/crm_claim/crm_claim_data.xml | 10 ++++---- addons/crm_claim/crm_claim_demo.xml | 14 +++++------ addons/sale_team/sale_team.xml | 5 +++- 4 files changed, 34 insertions(+), 31 deletions(-) diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index 10888377050..4c313d488b2 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -93,26 +93,26 @@ -
- %% -
-
-
- Leads - - Open Leads per Month
Click to see a detailed analysis of leads.
-
+
+ %%
- - + diff --git a/addons/crm_claim/crm_claim_data.xml b/addons/crm_claim/crm_claim_data.xml index d897fe2bcb3..8a1bae29a2b 100644 --- a/addons/crm_claim/crm_claim_data.xml +++ b/addons/crm_claim/crm_claim_data.xml @@ -8,19 +8,19 @@ Factual Claims - + Value Claims - + Policy Claims - + @@ -30,12 +30,12 @@ Corrective - + Preventive - + - kanban diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index e0149269243..cc2488f9589 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -195,7 +195,6 @@ class crm_lead(format_address, osv.osv): resource_id = resource_ids[0] duration = float(ans.days) - if lead.section_id and lead.section_id.resource_calendar_id: duration = float(ans.days) * 24 new_dates = cal_obj.interval_get(cr, @@ -351,7 +350,7 @@ class crm_lead(format_address, osv.osv): if user.default_section_id and user.default_section_id.id: return {'value': {'section_id': user.default_section_id.id}} return {'value': {'section_id': section_id}} - + def stage_find(self, cr, uid, cases, section_id, domain=None, order='sequence', context=None): """ Override of the base.stage method Parameter of the stage search taken from the lead: diff --git a/addons/crm/security/crm_security.xml b/addons/crm/security/crm_security.xml index bd2c3cdb3a4..7539b699614 100644 --- a/addons/crm/security/crm_security.xml +++ b/addons/crm/security/crm_security.xml @@ -24,7 +24,6 @@ - Manage Fund Raising diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index 2a9b6788360..f6f7165c41b 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -282,6 +282,7 @@ class gamification_goal(osv.Model): _logger.exception(_('Invalid return content from the evaluation of code for definition %s' % definition.name)) else: # count or sum + obj = self.pool.get(definition.model_id.model) field_date_name = definition.field_date_id and definition.field_date_id.name or False @@ -345,6 +346,7 @@ class gamification_goal(osv.Model): if not value: continue goal = all_goals[goal_id] + # check goal target reached if (goal.definition_condition == 'higher' and value.get('current', goal.current) >= goal.target_goal) \ or (goal.definition_condition == 'lower' and value.get('current', goal.current) <= goal.target_goal): diff --git a/addons/sale/report/__init__.py b/addons/sale/report/__init__.py index f1d48dd38e9..0ce593424d3 100644 --- a/addons/sale/report/__init__.py +++ b/addons/sale/report/__init__.py @@ -18,6 +18,7 @@ # along with this program. If not, see . # ############################################################################## + import sales_crm_account_invoice_report import sale_report import sale_analysis_report diff --git a/addons/sale/report/sale_analysis_report_view.xml b/addons/sale/report/sale_analysis_report_view.xml index fb62468ec91..83613219046 100644 --- a/addons/sale/report/sale_analysis_report_view.xml +++ b/addons/sale/report/sale_analysis_report_view.xml @@ -1,6 +1,6 @@ - + sale.report.search.sale.crm sale.report @@ -12,5 +12,5 @@ - + \ No newline at end of file diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 09008e1dc81..d597d0d1743 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -18,6 +18,7 @@ # along with this program. If not, see . # ############################################################################## + import calendar from openerp import tools from datetime import date, datetime, timedelta @@ -389,7 +390,6 @@ class sale_order(osv.osv): # Care for deprecated _inv_get() hook - FIXME: to be removed after 6.1 invoice_vals.update(self._inv_get(cr, uid, order, context=context)) return invoice_vals - def _make_invoice(self, cr, uid, order, lines, context=None): inv_obj = self.pool.get('account.invoice') @@ -1012,10 +1012,10 @@ class mail_compose_message(osv.Model): context = dict(context, mail_post_autofollow=True) self.pool.get('sale.order').signal_quotation_sent(cr, uid, [context['default_res_id']]) return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context) - class account_invoice(osv.Model): _inherit = 'account.invoice' + _columns = { 'section_id': fields.many2one('crm.case.section', 'Sales Team'), } diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index fafca287f2e..790e71ee37b 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -565,9 +565,7 @@ - - Sales Orders ir.actions.act_window diff --git a/addons/sale_crm/__init__.py b/addons/sale_crm/__init__.py index cda0cd795a2..ac44586e7db 100644 --- a/addons/sale_crm/__init__.py +++ b/addons/sale_crm/__init__.py @@ -21,6 +21,5 @@ import wizard import sale_crm -import report # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale_crm/report/__init__.py b/addons/sale_crm/report/__init__.py deleted file mode 100644 index 3fd45ac3f8f..00000000000 --- a/addons/sale_crm/report/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -# -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# 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 . -# -############################################################################## - - - - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - diff --git a/addons/sale_crm/sale_crm_view.xml b/addons/sale_crm/sale_crm_view.xml index 102eb9462fa..070b8ef4fa7 100644 --- a/addons/sale_crm/sale_crm_view.xml +++ b/addons/sale_crm/sale_crm_view.xml @@ -31,5 +31,5 @@ - + diff --git a/addons/sale_team/__init__.py b/addons/sale_team/__init__.py index 6bac200f0db..f6973d5f845 100644 --- a/addons/sale_team/__init__.py +++ b/addons/sale_team/__init__.py @@ -21,5 +21,4 @@ import sale_team import sale_team_config -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: - +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file diff --git a/addons/sale_team/__openerp__.py b/addons/sale_team/__openerp__.py index 2ea88cc6a92..ea45d3c1e68 100644 --- a/addons/sale_team/__openerp__.py +++ b/addons/sale_team/__openerp__.py @@ -28,8 +28,8 @@ 'website': 'http://www.openerp.com', 'depends' : ['base','mail','web_kanban_sparkline',], 'data': ['security/sale_team_security.xml', + 'security/ir.model.access.csv', 'res_config_view.xml', - 'ir.model.access.csv', 'sale_team_data.xml', 'sale_team.xml',], 'demo': ['sale_team_demo.xml'], diff --git a/addons/sale_team/res_config_view.xml b/addons/sale_team/res_config_view.xml index 7723fda48fc..ddcc72b5375 100644 --- a/addons/sale_team/res_config_view.xml +++ b/addons/sale_team/res_config_view.xml @@ -1,27 +1,26 @@ - crm settings sale.team.config.settings -
- - - +
+
+
-
+
diff --git a/addons/sale_team/sale_team.py b/addons/sale_team/sale_team.py index 3c13514bd11..db0e4638048 100644 --- a/addons/sale_team/sale_team.py +++ b/addons/sale_team/sale_team.py @@ -19,17 +19,13 @@ # ############################################################################## - import calendar from datetime import date, datetime from dateutil import relativedelta - from openerp import tools from openerp.osv import fields from openerp.osv import osv - - class crm_case_section(osv.osv): _name = "crm.case.section" _inherit = ['mail.thread', 'ir.needaction_mixin'] @@ -145,7 +141,4 @@ class res_users(osv.Model): # duplicate list to avoid modifying the original reference self.SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS) self.SELF_WRITEABLE_FIELDS.extend(['default_section_id']) - return init_res - - - \ No newline at end of file + return init_res \ No newline at end of file diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index e3bea558a68..b2b98be643a 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -24,7 +24,7 @@ res.users - + @@ -75,10 +75,7 @@ - - - Case Sections - Search crm.case.section @@ -99,7 +96,6 @@ - Sales Teams crm.case.section @@ -175,7 +171,6 @@ - crm.case.section.tree crm.case.section @@ -189,15 +184,6 @@ - - - - Sales Teams crm.case.section @@ -213,7 +199,6 @@

- - + \ No newline at end of file diff --git a/addons/sale_team/sale_team_config.py b/addons/sale_team/sale_team_config.py index 33a5e84b8de..2ae861e34b5 100644 --- a/addons/sale_team/sale_team_config.py +++ b/addons/sale_team/sale_team_config.py @@ -1,8 +1,27 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-today OpenERP SA () +# +# 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 . +# +############################################################################## from openerp import SUPERUSER_ID from openerp.osv import fields, osv - class sala_team_configuration(osv.TransientModel): _name = 'sale.team.config.settings' _inherit = ['sale.config.settings', 'fetchmail.config.settings'] @@ -35,11 +54,7 @@ class sala_team_configuration(osv.TransientModel): return True _columns = { - 'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams", implied_group='base.group_multi_salesteams', help="""Allows you to use Sales Teams to manage your leads and opportunities."""), - } - - - + } \ No newline at end of file diff --git a/addons/sale_team/sale_team_demo.xml b/addons/sale_team/sale_team_demo.xml index b24c047fba1..58f5ac66b69 100644 --- a/addons/sale_team/sale_team_demo.xml +++ b/addons/sale_team/sale_team_demo.xml @@ -15,6 +15,5 @@ SPD - \ No newline at end of file diff --git a/addons/sale_team/ir.model.access.csv b/addons/sale_team/security/ir.model.access.csv similarity index 87% rename from addons/sale_team/ir.model.access.csv rename to addons/sale_team/security/ir.model.access.csv index 9817ad834f6..e3c55739176 100644 --- a/addons/sale_team/ir.model.access.csv +++ b/addons/sale_team/security/ir.model.access.csv @@ -1,5 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_crm_case_section,crm.case.section,model_crm_case_section,base.group_user,1,0,0,0 access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,0,0,0 -access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 - +access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 \ No newline at end of file diff --git a/addons/sale_team/security/sale_team_security.xml b/addons/sale_team/security/sale_team_security.xml index 53fb560e332..11e4f583dda 100644 --- a/addons/sale_team/security/sale_team_security.xml +++ b/addons/sale_team/security/sale_team_security.xml @@ -35,6 +35,5 @@ - From d4826eb6d776dbc80b2099915bcc149a3892c027 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Tue, 22 Apr 2014 14:12:07 +0530 Subject: [PATCH 19/51] [IMP] improve dependancy for section_sales_department --- addons/crm_helpdesk/crm_helpdesk_demo.xml | 6 ++--- addons/project_issue/project_issue_demo.xml | 30 ++++++++++----------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/addons/crm_helpdesk/crm_helpdesk_demo.xml b/addons/crm_helpdesk/crm_helpdesk_demo.xml index 1370bf6d906..c76949f78f8 100644 --- a/addons/crm_helpdesk/crm_helpdesk_demo.xml +++ b/addons/crm_helpdesk/crm_helpdesk_demo.xml @@ -7,7 +7,7 @@ 3 draft - + Download old version of OpenERP? @@ -19,7 +19,7 @@ 3 draft - + Can not able to connect to Server @@ -31,7 +31,7 @@ 2 draft - + Documentation for CRM? diff --git a/addons/project_issue/project_issue_demo.xml b/addons/project_issue/project_issue_demo.xml index 63fa37c84c3..e16b8702551 100644 --- a/addons/project_issue/project_issue_demo.xml +++ b/addons/project_issue/project_issue_demo.xml @@ -38,7 +38,7 @@ 5 - + @@ -54,7 +54,7 @@ 3 - + Program not giving proper output @@ -66,7 +66,7 @@ 4 - + @@ -82,7 +82,7 @@ 3 - + @@ -95,7 +95,7 @@ 3 - + @@ -108,7 +108,7 @@ 3 - + @@ -124,7 +124,7 @@ 2 - + @@ -137,7 +137,7 @@ 2 - + @@ -162,7 +162,7 @@ 2 - + @@ -175,7 +175,7 @@ 4 - + @@ -188,7 +188,7 @@ 1 - + @@ -202,7 +202,7 @@ 3 - + @@ -216,7 +216,7 @@ 3 - + @@ -229,7 +229,7 @@ 4 - + @@ -242,7 +242,7 @@ 2 - + From 5d7e462046d237f0cff14910eb63a6a5b4a96638 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Tue, 22 Apr 2014 15:17:25 +0530 Subject: [PATCH 20/51] [IMP] improve crm_partner_assing demo file --- addons/crm_partner_assign/crm_lead_demo.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm_partner_assign/crm_lead_demo.xml b/addons/crm_partner_assign/crm_lead_demo.xml index 6631131d054..3338b392048 100644 --- a/addons/crm_partner_assign/crm_lead_demo.xml +++ b/addons/crm_partner_assign/crm_lead_demo.xml @@ -16,7 +16,7 @@ 2 - + Hi, From 579d261468900fc9e89303deab5ed609fdaa8eeb Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Tue, 22 Apr 2014 17:20:49 +0530 Subject: [PATCH 21/51] [IMP] solve yml issues --- addons/crm/crm.py | 3 +++ addons/crm/crm_action_rule_demo.xml | 2 +- addons/crm/test/crm_lead_cancel.yml | 2 +- addons/crm/test/crm_lead_find_stage.yml | 2 +- addons/crm/test/lead2opportunity2win.yml | 2 +- addons/crm/test/lead2opportunity_assign_salesmen.yml | 2 +- 6 files changed, 8 insertions(+), 5 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index 86350b472a7..f746893c2d6 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -118,6 +118,9 @@ class crm_case_section(osv.osv): res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context) res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'date_deadline'], 'planned_revenue', 'date_deadline', context=context) return res + def create(self, cr, uid, vals, context=None): + self._alias_model_name = 'crm.lead' + return super(crm_case_section, self).create(cr, uid, vals, context={'alias_model_name': self._alias_model_name}) _columns = { 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), diff --git a/addons/crm/crm_action_rule_demo.xml b/addons/crm/crm_action_rule_demo.xml index 8eac91c06e2..2524132c4d4 100644 --- a/addons/crm/crm_action_rule_demo.xml +++ b/addons/crm/crm_action_rule_demo.xml @@ -41,7 +41,7 @@ True ir.actions.server code - sales_team = self.pool.get('ir.model.data').get_object(cr, uid, 'crm', 'section_sales_department') + sales_team = self.pool.get('ir.model.data').get_object(cr, uid, 'sale_team', 'section_sales_department') object.write({'section_id': sales_team.id}) diff --git a/addons/crm/test/crm_lead_cancel.yml b/addons/crm/test/crm_lead_cancel.yml index dd475d54919..fa98f47e6ea 100644 --- a/addons/crm/test/crm_lead_cancel.yml +++ b/addons/crm/test/crm_lead_cancel.yml @@ -5,7 +5,7 @@ uid: 'crm_res_users_salesmanager' - !python {model: crm.lead}: | - section_id = self.pool.get('crm.case.section').create(cr, uid, {'name': "Phone Marketing", 'parent_id': ref("crm.crm_case_section_2")}) + section_id = self.pool.get('crm.case.section').create(cr, uid, {'name': "Phone Marketing", 'parent_id': ref("sale_team.crm_case_section_2")}) self.write(cr, uid, [ref("crm_case_1")], {'section_id': section_id}) - Salesman check unqualified lead . diff --git a/addons/crm/test/crm_lead_find_stage.yml b/addons/crm/test/crm_lead_find_stage.yml index b8a11a4af9e..9fcff6ab512 100644 --- a/addons/crm/test/crm_lead_find_stage.yml +++ b/addons/crm/test/crm_lead_find_stage.yml @@ -6,7 +6,7 @@ name: 'Test lead new' partner_id: base.res_partner_1 description: This is the description of the test new lead. - section_id: crm.section_sales_department + section_id: sale_team.section_sales_department - I check default stage of lead. - diff --git a/addons/crm/test/lead2opportunity2win.yml b/addons/crm/test/lead2opportunity2win.yml index 0650838e4cc..c91768bcc07 100644 --- a/addons/crm/test/lead2opportunity2win.yml +++ b/addons/crm/test/lead2opportunity2win.yml @@ -65,7 +65,7 @@ - !python {model: crm.lead2opportunity.partner.mass}: | context.update({'active_model': 'crm.lead', 'active_ids': [ref("crm_case_13"), ref("crm_case_2")], 'active_id': ref("crm_case_13")}) - id = self.create(cr, uid, {'user_ids': [(6, 0, [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('sale_team.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/lead2opportunity_assign_salesmen.yml b/addons/crm/test/lead2opportunity_assign_salesmen.yml index 76b35be531d..6206db33050 100644 --- a/addons/crm/test/lead2opportunity_assign_salesmen.yml +++ b/addons/crm/test/lead2opportunity_assign_salesmen.yml @@ -66,7 +66,7 @@ - !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'), 'deduplicate': False, 'force_assignation': True}, context=context) + 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('sale_team.section_sales_department'), 'deduplicate': False, 'force_assignation': True}, 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. From 2f5250edfa5315b323deb23b3436606f3effc636 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Wed, 23 Apr 2014 15:17:33 +0530 Subject: [PATCH 22/51] [IMP] improve inherited crm_case_section --- addons/crm/crm.py | 40 ++++++++++++++----- addons/crm/crm_case_section_view.xml | 3 +- addons/sale_team/sale_team.py | 19 +-------- addons/sale_team/sale_team.xml | 3 +- .../sale_team/security/sale_team_security.xml | 12 +++++- 5 files changed, 44 insertions(+), 33 deletions(-) diff --git a/addons/crm/crm.py b/addons/crm/crm.py index f746893c2d6..ef9cb89d5c2 100644 --- a/addons/crm/crm.py +++ b/addons/crm/crm.py @@ -88,13 +88,9 @@ class crm_case_stage(osv.osv): } -class crm_case_section(osv.osv): - """ Model for sales teams. """ - _name = "crm.case.section" +class crm_case_section(osv.Model): _inherit = 'crm.case.section' - _description = "Sales Teams" - _alias_model_name = 'crm.lead' - + _name = 'crm.case.section' def _get_opportunities_data(self, cr, uid, ids, field_name, arg, context=None): """ Get opportunities-related data for salesteam kanban view monthly_open_leads: number of open lead during the last months @@ -118,10 +114,7 @@ class crm_case_section(osv.osv): res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date'], 'create_date_count', 'create_date', context=context) res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'date_deadline'], 'planned_revenue', 'date_deadline', context=context) return res - def create(self, cr, uid, vals, context=None): - self._alias_model_name = 'crm.lead' - return super(crm_case_section, self).create(cr, uid, vals, context={'alias_model_name': self._alias_model_name}) - + _columns = { 'resource_calendar_id': fields.many2one('resource.calendar', "Working Time", help="Used to compute open days"), 'stage_ids': fields.many2many('crm.case.stage', 'section_stage_rel', 'section_id', 'stage_id', 'Stages'), @@ -143,7 +136,32 @@ class crm_case_section(osv.osv): 'stage_ids': _get_stage_common, 'use_leads': True, } - + +class crm_case_section_inherit(osv.Model): + """ Model for sales teams. """ + _inherit = 'crm.case.section' + _name = 'crm.case.section.inherit' + _description = "Sales Teams" + _inherits = {'mail.alias': 'alias_id'} + _columns = { + 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."), + } + def create(self, cr, uid, vals, context=None): + if context is None: + context = {} + create_context = dict(context, alias_model_name='crm.lead', alias_parent_model_name=self._name) + section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) + section = self.browse(cr, uid, section_id, context=context) + self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) + return section_id + + def unlink(self, cr, uid, ids, context=None): + # Cascade-delete mail aliases as well, as they should not exist without the sales team. + mail_alias = self.pool.get('mail.alias') + alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id] + res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) + mail_alias.unlink(cr, uid, alias_ids, context=context) + return res class crm_case_categ(osv.osv): """ Category of Case """ _name = "crm.case.categ" diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index 57f5b70bfe8..eb86842e3bd 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -83,7 +83,7 @@ crm.case.section.kanban - crm.case.section + crm.case.section.inherit @@ -91,6 +91,7 @@ +
diff --git a/addons/sale_team/sale_team.py b/addons/sale_team/sale_team.py index db0e4638048..299e6564f56 100644 --- a/addons/sale_team/sale_team.py +++ b/addons/sale_team/sale_team.py @@ -30,10 +30,8 @@ class crm_case_section(osv.osv): _name = "crm.case.section" _inherit = ['mail.thread', 'ir.needaction_mixin'] _description = "Sales Teams" - _inherits = {'mail.alias': 'alias_id'} _order = "complete_name" _period_number = 5 - _alias_model_name = 'mail.message' def get_full_name(self, cr, uid, ids, field_name, arg, context=None): return dict(self.name_get(cr, uid, ids, context=context)) @@ -69,7 +67,6 @@ class crm_case_section(osv.osv): return section_result _columns = { - 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."), 'name': fields.char('Sales Team', size=64, required=True, translate=True), 'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True), 'code': fields.char('Code', size=8), @@ -114,21 +111,7 @@ class crm_case_section(osv.osv): res.append((record['id'], name)) return res - def create(self, cr, uid, vals, context=None): - if context is None: - context = {} - create_context = dict(context, alias_model_name=self._alias_model_name, alias_parent_model_name=self._name) - section_id = super(crm_case_section, self).create(cr, uid, vals, context=create_context) - section = self.browse(cr, uid, section_id, context=context) - self.pool.get('mail.alias').write(cr, uid, [section.alias_id.id], {'alias_parent_thread_id': section_id, 'alias_defaults': {'section_id': section_id, 'type': 'lead'}}, context=context) - return section_id - def unlink(self, cr, uid, ids, context=None): - # Cascade-delete mail aliases as well, as they should not exist without the sales team. - mail_alias = self.pool.get('mail.alias') - alias_ids = [team.alias_id.id for team in self.browse(cr, uid, ids, context=context) if team.alias_id] - res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) - mail_alias.unlink(cr, uid, alias_ids, context=context) - return res + class res_users(osv.Model): _inherit = 'res.users' diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index b2b98be643a..a044c455b4a 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -42,7 +42,6 @@ - @@ -58,7 +57,7 @@

-
+
%%
diff --git a/addons/sale_team/security/sale_team_security.xml b/addons/sale_team/security/sale_team_security.xml index 11e4f583dda..fe999715a7a 100644 --- a/addons/sale_team/security/sale_team_security.xml +++ b/addons/sale_team/security/sale_team_security.xml @@ -23,7 +23,17 @@ - + + + Do Not Use Sales Teams + + + + + Manage Sales Teams + + + From a26436c57e7fd8408d91f44b3ab11c18600a5a0b Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Wed, 23 Apr 2014 17:51:52 +0530 Subject: [PATCH 23/51] [IMP] improve kanban view --- addons/crm/crm_case_section_view.xml | 22 +++++++++++++++++----- addons/crm/crm_demo.xml | 4 ---- addons/sale_team/sale_team.xml | 3 --- addons/sale_team/sale_team_demo.xml | 4 ++++ 4 files changed, 21 insertions(+), 12 deletions(-) diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index eb86842e3bd..6de1a8b6094 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -80,7 +80,6 @@ - crm.case.section.kanban crm.case.section.inherit @@ -88,15 +87,28 @@ - - - - +
%%
+
+
+
+
+ + crm.case.section.kanban + crm.case.section + + + + + + + + +
Leads diff --git a/addons/crm/crm_demo.xml b/addons/crm/crm_demo.xml index 1541753523a..df776f92350 100644 --- a/addons/crm/crm_demo.xml +++ b/addons/crm/crm_demo.xml @@ -2,10 +2,6 @@ - - - - OpenERP partners True diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index a044c455b4a..a00bbe6665b 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -57,9 +57,6 @@

-
- %% -
diff --git a/addons/sale_team/sale_team_demo.xml b/addons/sale_team/sale_team_demo.xml index 58f5ac66b69..0e698d8f1fa 100644 --- a/addons/sale_team/sale_team_demo.xml +++ b/addons/sale_team/sale_team_demo.xml @@ -1,6 +1,10 @@ + + + + From 5cf064805867e7de0f33f03d5efbc02a34020432 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Wed, 23 Apr 2014 18:21:33 +0530 Subject: [PATCH 24/51] [IMP] improve alias_name --- addons/crm/crm_case_section_view.xml | 2 +- addons/sale_team/sale_team_data.xml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index 5053cc5c308..950f017a473 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -163,7 +163,7 @@ crm.case.section.form - crm.case.section + crm.case.section.inherit diff --git a/addons/sale_team/sale_team_data.xml b/addons/sale_team/sale_team_data.xml index eef5e5809a1..2f296143ec0 100644 --- a/addons/sale_team/sale_team_data.xml +++ b/addons/sale_team/sale_team_data.xml @@ -4,7 +4,6 @@ Direct Sales DM - sales From e8bed3e41fb97c601f8de2f53b16c3854459ee78 Mon Sep 17 00:00:00 2001 From: Randhir Mayatra rma-openerp Date: Wed, 23 Apr 2014 18:45:33 +0530 Subject: [PATCH 25/51] [IMP] improve alias_name --- addons/website_sale_crm/data/website_sale_crm.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/addons/website_sale_crm/data/website_sale_crm.xml b/addons/website_sale_crm/data/website_sale_crm.xml index dad22228f01..716bce75766 100644 --- a/addons/website_sale_crm/data/website_sale_crm.xml +++ b/addons/website_sale_crm/data/website_sale_crm.xml @@ -5,7 +5,6 @@ Website Website - Website From 74f60e621227aea1736b3d3a337cf8e09e41f15e Mon Sep 17 00:00:00 2001 From: Ravi Gadhia Date: Thu, 1 May 2014 14:15:42 +0530 Subject: [PATCH 26/51] [REM] crm.case.section.inherit use crm.case.section for get aliase_id --- addons/crm/crm_case_section_view.xml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml index 950f017a473..94f59217065 100644 --- a/addons/crm/crm_case_section_view.xml +++ b/addons/crm/crm_case_section_view.xml @@ -82,7 +82,7 @@ crm.case.section.kanban - crm.case.section.inherit + crm.case.section @@ -163,7 +163,7 @@ crm.case.section.form - crm.case.section.inherit + crm.case.section @@ -195,4 +195,4 @@ - \ No newline at end of file + From ca008ef74be82fa786b2388d870687f103b8dcb4 Mon Sep 17 00:00:00 2001 From: Ravi Gadhia Date: Fri, 2 May 2014 14:53:09 +0530 Subject: [PATCH 27/51] [IMP] remove print + innecessary diff on merge --- addons/gamification/models/goal.py | 4 ++-- addons/gamification_sale_crm/sale_crm_goals_demo.xml | 10 ---------- addons/mail/mail_alias.py | 1 - 3 files changed, 2 insertions(+), 13 deletions(-) diff --git a/addons/gamification/models/goal.py b/addons/gamification/models/goal.py index 1a7c489f575..7bbecbfa052 100644 --- a/addons/gamification/models/goal.py +++ b/addons/gamification/models/goal.py @@ -282,7 +282,7 @@ class gamification_goal(osv.Model): _logger.exception(_('Invalid return content from the evaluation of code for definition %s' % definition.name)) else: # count or sum - + obj = self.pool.get(definition.model_id.model) field_date_name = definition.field_date_id and definition.field_date_id.name or False @@ -349,7 +349,7 @@ class gamification_goal(osv.Model): if not value: continue goal = all_goals[goal_id] - + # # check goal target reached if (goal.definition_condition == 'higher' and value.get('current', goal.current) >= goal.target_goal) \ or (goal.definition_condition == 'lower' and value.get('current', goal.current) <= goal.target_goal): diff --git a/addons/gamification_sale_crm/sale_crm_goals_demo.xml b/addons/gamification_sale_crm/sale_crm_goals_demo.xml index dc3cc27135d..a4720b1e566 100644 --- a/addons/gamification_sale_crm/sale_crm_goals_demo.xml +++ b/addons/gamification_sale_crm/sale_crm_goals_demo.xml @@ -7,15 +7,5 @@ inprogress - - - - - - - - 2000 - - diff --git a/addons/mail/mail_alias.py b/addons/mail/mail_alias.py index 54200283131..d8a79dd98e0 100644 --- a/addons/mail/mail_alias.py +++ b/addons/mail/mail_alias.py @@ -204,7 +204,6 @@ class mail_alias(osv.Model): alias_vals['alias_defaults'] = dict((k, obj_data[v]) for k, v in alias_defaults.iteritems()) alias_vals['alias_parent_thread_id'] = obj_data['id'] alias_create_ctx = dict(context, alias_model_name=alias_model_name, alias_parent_model_name=child_model_name) - print 'EEEEEEEEEEEe',alias_vals, alias_create_ctx alias_id = mail_alias.create(cr, SUPERUSER_ID, alias_vals, context=alias_create_ctx) child_class_model.write(cr, SUPERUSER_ID, obj_data['id'], {'alias_id': alias_id}, context={'mail_notrack': True}) _logger.info('Mail alias created for %s %s (id %s)', child_model_name, obj_data[alias_key], obj_data['id']) From 6ccbd2cd8282b8301bba675efa705ca3de09dc73 Mon Sep 17 00:00:00 2001 From: Ravi Gadhia Date: Fri, 2 May 2014 17:23:18 +0530 Subject: [PATCH 28/51] [IMP] no need to inherit sale.order report we are already in sale/report +change file name for invoice report --- addons/sale/report/__init__.py | 4 +--- ...unt_invoice_report.py => invoice_report.py} | 0 addons/sale/report/sale_analysis_report.py | 18 ------------------ .../sale/report/sale_analysis_report_view.xml | 16 ---------------- addons/sale/report/sale_report.py | 7 +++++-- addons/sale/report/sale_report_view.xml | 1 + 6 files changed, 7 insertions(+), 39 deletions(-) rename addons/sale/report/{sales_crm_account_invoice_report.py => invoice_report.py} (100%) delete mode 100644 addons/sale/report/sale_analysis_report.py delete mode 100644 addons/sale/report/sale_analysis_report_view.xml diff --git a/addons/sale/report/__init__.py b/addons/sale/report/__init__.py index 0ce593424d3..be1073205c3 100644 --- a/addons/sale/report/__init__.py +++ b/addons/sale/report/__init__.py @@ -19,8 +19,6 @@ # ############################################################################## -import sales_crm_account_invoice_report import sale_report -import sale_analysis_report - +import invoice_report # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale/report/sales_crm_account_invoice_report.py b/addons/sale/report/invoice_report.py similarity index 100% rename from addons/sale/report/sales_crm_account_invoice_report.py rename to addons/sale/report/invoice_report.py diff --git a/addons/sale/report/sale_analysis_report.py b/addons/sale/report/sale_analysis_report.py deleted file mode 100644 index eca020f82ad..00000000000 --- a/addons/sale/report/sale_analysis_report.py +++ /dev/null @@ -1,18 +0,0 @@ - # -*- coding: utf-8 -*- - -from openerp.osv import fields, osv - - -class sale_report(osv.osv): - _inherit = "sale.report" - _columns = { - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), - } - - def _select(self): - return super(sale_report, self)._select() + ", s.section_id as section_id" - - def _group_by(self): - return super(sale_report, self)._group_by() + ", s.section_id" - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale/report/sale_analysis_report_view.xml b/addons/sale/report/sale_analysis_report_view.xml deleted file mode 100644 index 83613219046..00000000000 --- a/addons/sale/report/sale_analysis_report_view.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - sale.report.search.sale.crm - sale.report - - - - - - - - - - \ No newline at end of file diff --git a/addons/sale/report/sale_report.py b/addons/sale/report/sale_report.py index 5d6a1cd759c..8a54da5901e 100644 --- a/addons/sale/report/sale_report.py +++ b/addons/sale/report/sale_report.py @@ -53,6 +53,7 @@ class sale_report(osv.osv): ], 'Order Status', readonly=True), 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', readonly=True), 'analytic_account_id': fields.many2one('account.analytic.account', 'Analytic Account', readonly=True), + 'section_id': fields.many2one('crm.case.section', 'Sales Team'), } _order = 'date desc' @@ -73,7 +74,8 @@ class sale_report(osv.osv): s.state, t.categ_id as categ_id, s.pricelist_id as pricelist_id, - s.project_id as analytic_account_id + s.project_id as analytic_account_id, + s.section_id as section_id """ return select_str @@ -101,7 +103,8 @@ class sale_report(osv.osv): s.company_id, s.state, s.pricelist_id, - s.project_id + s.project_id, + s.section_id """ return group_by_str diff --git a/addons/sale/report/sale_report_view.xml b/addons/sale/report/sale_report_view.xml index 14b719ebd7a..b8ce36efc2b 100644 --- a/addons/sale/report/sale_report_view.xml +++ b/addons/sale/report/sale_report_view.xml @@ -34,6 +34,7 @@ + From adf1480f7104df47a80228459bca4539d399046f Mon Sep 17 00:00:00 2001 From: Ravi Gadhia Date: Fri, 2 May 2014 18:18:40 +0530 Subject: [PATCH 29/51] [IMP] add alias name --- addons/crm/crm_data.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/addons/crm/crm_data.xml b/addons/crm/crm_data.xml index 41f4819e500..cec54a683bd 100644 --- a/addons/crm/crm_data.xml +++ b/addons/crm/crm_data.xml @@ -28,6 +28,7 @@ True + sales From 6ddbf83c1a4c7d590b5dabe788ba57f6389fdf47 Mon Sep 17 00:00:00 2001 From: "Prashant Panchal (OpenERP)" Date: Tue, 6 May 2014 12:34:38 +0530 Subject: [PATCH 30/51] [IMP]fix type + chaneg class name from sala_team_configuration to sale_team_configuration & change model name sale.config.sattings --- addons/sale_team/res_config_view.xml | 28 +++++++++---------- addons/sale_team/sale_team_config.py | 4 +-- addons/sale_team/security/ir.model.access.csv | 2 +- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/addons/sale_team/res_config_view.xml b/addons/sale_team/res_config_view.xml index ddcc72b5375..a9d9f08fdd4 100644 --- a/addons/sale_team/res_config_view.xml +++ b/addons/sale_team/res_config_view.xml @@ -1,26 +1,26 @@ - + crm settings - sale.team.config.settings + sale.config.settings -
- - - +
+
+
diff --git a/addons/sale_team/sale_team_config.py b/addons/sale_team/sale_team_config.py index 344c38313c9..c6adeb73a55 100644 --- a/addons/sale_team/sale_team_config.py +++ b/addons/sale_team/sale_team_config.py @@ -22,8 +22,8 @@ from openerp.osv import fields, osv -class sala_team_configuration(osv.TransientModel): - _name = 'sale.team.config.settings' +class sale_team_configuration(osv.TransientModel): + _name = 'sale.config.settings' _inherit = ['sale.config.settings', 'fetchmail.config.settings'] def set_group_multi_salesteams(self, cr, uid, ids, context=None): diff --git a/addons/sale_team/security/ir.model.access.csv b/addons/sale_team/security/ir.model.access.csv index e3c55739176..f983a2abb9d 100644 --- a/addons/sale_team/security/ir.model.access.csv +++ b/addons/sale_team/security/ir.model.access.csv @@ -1,4 +1,4 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_crm_case_section,crm.case.section,model_crm_case_section,base.group_user,1,0,0,0 access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,0,0,0 -access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 \ No newline at end of file +access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1 From 14cf5e1631520fa4c0793220c62165439f30f84c Mon Sep 17 00:00:00 2001 From: "Prashant Panchal (OpenERP)" Date: Tue, 6 May 2014 12:42:08 +0530 Subject: [PATCH 31/51] [IMP] change record id in res_config_view.xml --- addons/sale_team/res_config_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale_team/res_config_view.xml b/addons/sale_team/res_config_view.xml index a9d9f08fdd4..dd5d0ffca66 100644 --- a/addons/sale_team/res_config_view.xml +++ b/addons/sale_team/res_config_view.xml @@ -1,7 +1,7 @@ - + crm settings sale.config.settings From 10beeb53ede95800f649f05449acc47f06b78454 Mon Sep 17 00:00:00 2001 From: "Prashant Panchal (OpenERP)" Date: Tue, 6 May 2014 15:38:12 +0530 Subject: [PATCH 32/51] [MERGE]merge with trunk --- addons/sale_team/res_config_view.xml | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/addons/sale_team/res_config_view.xml b/addons/sale_team/res_config_view.xml index dd5d0ffca66..e8f2265f1e3 100644 --- a/addons/sale_team/res_config_view.xml +++ b/addons/sale_team/res_config_view.xml @@ -7,20 +7,23 @@ -
- - - -
+
+ + + +
+ +
From e511de45cbe46f5f93358aa23d405588ed485cac Mon Sep 17 00:00:00 2001 From: "Prashant Panchal (OpenERP)" Date: Wed, 7 May 2014 11:50:06 +0530 Subject: [PATCH 33/51] [ADD]crm_case_section (view & py file) add in sale module, that file split from sale.py & sale_view.xml --- addons/sale/__init__.py | 1 + addons/sale/__openerp__.py | 1 + addons/sale/crm_case_section.py | 85 +++++++++++++++++++++++++++ addons/sale/crm_case_section_view.xml | 74 +++++++++++++++++++++++ addons/sale/sale.py | 50 ---------------- addons/sale/sale_view.xml | 70 ---------------------- addons/sale_team/res_config_view.xml | 27 ++++----- addons/sale_team/sale_team.xml | 4 +- 8 files changed, 176 insertions(+), 136 deletions(-) create mode 100644 addons/sale/crm_case_section.py create mode 100644 addons/sale/crm_case_section_view.xml diff --git a/addons/sale/__init__.py b/addons/sale/__init__.py index 5a7b4b499df..509364643ae 100644 --- a/addons/sale/__init__.py +++ b/addons/sale/__init__.py @@ -24,6 +24,7 @@ #---------------------------------------------------------- import sale +import crm_case_section import res_partner import wizard import report diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py index c7348e5b37d..2faeb5b3693 100644 --- a/addons/sale/__openerp__.py +++ b/addons/sale/__openerp__.py @@ -71,6 +71,7 @@ The Dashboard for the Sales Manager will include 'sale_report.xml', 'sale_data.xml', 'sale_view.xml', + 'crm_case_section_view.xml', 'res_partner_view.xml', 'report/sale_report_view.xml', 'edi/sale_order_action_data.xml', diff --git a/addons/sale/crm_case_section.py b/addons/sale/crm_case_section.py new file mode 100644 index 00000000000..11c8cdc8bb4 --- /dev/null +++ b/addons/sale/crm_case_section.py @@ -0,0 +1,85 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# 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 . +# +############################################################################## + +import calendar +from openerp import tools +from datetime import date, datetime, timedelta +from dateutil.relativedelta import relativedelta +from dateutil import relativedelta +import time +from openerp.osv import fields, osv +from openerp.tools.translate import _ +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP, float_compare +import openerp.addons.decimal_precision as dp +from openerp import workflow + + +class crm_case_section(osv.osv): + _inherit = 'crm.case.section' + + def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None): + obj = self.pool.get('sale.order') + res = dict.fromkeys(ids, False) + month_begin = date.today().replace(day=1) + date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + for id in ids: + res[id] = dict() + created_domain = [('section_id', '=', id), ('state', '=', ['draft']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] + res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) + validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent', 'cancel']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] + res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) + return res + + def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None): + obj = self.pool.get('account.invoice.report') + res = dict.fromkeys(ids, False) + month_begin = date.today().replace(day=1) + date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) + for id in ids: + created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)] + res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context) + return res + + _columns = { + 'invoiced_forecast': fields.integer(string='Invoice Forecast', + help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" + "team should invoice this month. It is used to compute the progression ratio \n" + " of the current and forecast revenue on the kanban view."), + 'invoiced_target': fields.integer(string='Invoice Target', + help="Target of invoice revenue for the current month. This is the amount the sales \n" + "team estimates to be able to invoice this month."), + 'monthly_quoted': fields.function(_get_sale_orders_data, + type='string', readonly=True, multi='_get_sale_orders_data', + string='Rate of created quotation per duration'), + 'monthly_confirmed': fields.function(_get_sale_orders_data, + type='string', readonly=True, multi='_get_sale_orders_data', + string='Rate of validate sales orders per duration'), + 'monthly_invoiced': fields.function(_get_invoices_data, + type='string', readonly=True, + string='Rate of sent invoices per duration'), + } + + def action_forecast(self, cr, uid, id, value, context=None): + return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context) + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale/crm_case_section_view.xml b/addons/sale/crm_case_section_view.xml new file mode 100644 index 00000000000..e4d36f23918 --- /dev/null +++ b/addons/sale/crm_case_section_view.xml @@ -0,0 +1,74 @@ + + + + + crm.case.section.form + crm.case.section + + + + + + + + + + + + + crm.case.section.kanban + crm.case.section + + + + + + + + + + + + + + +
+ Invoiced + Forecast +
+
+
Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance. +
+
+
+
+
+
+
diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 1270534ae6d..53ab1c38a8b 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -1042,56 +1042,6 @@ class account_invoice(osv.Model): _defaults = { 'section_id': lambda self, cr, uid, c=None: self.pool.get('res.users').browse(cr, uid, uid, c).default_section_id.id or False, } - -class crm_case_section(osv.osv): - _inherit = 'crm.case.section' - - def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None): - obj = self.pool.get('sale.order') - res = dict.fromkeys(ids, False) - month_begin = date.today().replace(day=1) - date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - for id in ids: - res[id] = dict() - created_domain = [('section_id', '=', id), ('state', '=', ['draft']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] - res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) - validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent', 'cancel']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)] - res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context) - return res - - def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None): - obj = self.pool.get('account.invoice.report') - res = dict.fromkeys(ids, False) - month_begin = date.today().replace(day=1) - date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT) - for id in ids: - created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)] - res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context) - return res - - _columns = { - 'invoiced_forecast': fields.integer(string='Invoice Forecast', - help="Forecast of the invoice revenue for the current month. This is the amount the sales \n" - "team should invoice this month. It is used to compute the progression ratio \n" - " of the current and forecast revenue on the kanban view."), - 'invoiced_target': fields.integer(string='Invoice Target', - help="Target of invoice revenue for the current month. This is the amount the sales \n" - "team estimates to be able to invoice this month."), - 'monthly_quoted': fields.function(_get_sale_orders_data, - type='string', readonly=True, multi='_get_sale_orders_data', - string='Rate of created quotation per duration'), - 'monthly_confirmed': fields.function(_get_sale_orders_data, - type='string', readonly=True, multi='_get_sale_orders_data', - string='Rate of validate sales orders per duration'), - 'monthly_invoiced': fields.function(_get_invoices_data, - type='string', readonly=True, - string='Rate of sent invoices per duration'), - } - - def action_forecast(self, cr, uid, id, value, context=None): - return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context) class product_product(osv.Model): _inherit = 'product.product' diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 223bc5fe1f0..8aabe31fc93 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -682,75 +682,5 @@ {'search_default_month':1} From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs. - - - crm.case.section.form - crm.case.section - - - - - - - - - - - - - crm.case.section.kanban - crm.case.section - - - - - - - - - - - - - - -
- Invoiced - Forecast -
-
-
Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance. -
-
-
-
-
diff --git a/addons/sale_team/res_config_view.xml b/addons/sale_team/res_config_view.xml index e8f2265f1e3..88a51e78cda 100644 --- a/addons/sale_team/res_config_view.xml +++ b/addons/sale_team/res_config_view.xml @@ -7,23 +7,22 @@ -
- - - -
+
+ + + +
- - diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index a00bbe6665b..2512c20efb6 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -34,7 +34,7 @@ - + crm.case.section.kanban crm.case.section @@ -197,4 +197,4 @@ - \ No newline at end of file + From ec267fc210e00761a42129fd81eb9ba5985bb440 Mon Sep 17 00:00:00 2001 From: "Prashant Panchal (OpenERP)" Date: Thu, 8 May 2014 18:59:16 +0530 Subject: [PATCH 34/51] [IMP]add dicription --- addons/sale_team/__openerp__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/addons/sale_team/__openerp__.py b/addons/sale_team/__openerp__.py index f141bdc76d0..b380df08313 100644 --- a/addons/sale_team/__openerp__.py +++ b/addons/sale_team/__openerp__.py @@ -24,7 +24,9 @@ 'author': 'OpenERP SA', 'category': 'Sales Management', 'summary': 'Sales Team', - 'description': """ """, + 'description': """ +Using this application you can manage Sales Team with CRM and/or Sales + """, 'website': 'http://www.openerp.com', 'depends': ['base','mail','web_kanban_sparkline',], 'data': ['security/sale_team_security.xml', From 0e08ceddb19b7e5ccde12a4b032c6833b8c57bdd Mon Sep 17 00:00:00 2001 From: Mantavya Gajjar Date: Fri, 9 May 2014 13:08:26 +0530 Subject: [PATCH 35/51] [FIX]: review sale_team to add groups on section_id field --- addons/crm/crm_lead_view.xml | 1 + addons/sale_team/sale_team.xml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index dc297492020..88d0349b939 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -2,6 +2,7 @@ + diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index 2512c20efb6..8155fdd8ce0 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -77,12 +77,12 @@ crm.case.section + - From 6c0389279a7275e07f4b6d8751b79dfb8086d3ba Mon Sep 17 00:00:00 2001 From: "Prashant Panchal (OpenERP)" Date: Fri, 9 May 2014 13:13:13 +0530 Subject: [PATCH 36/51] [IMP]add groups 'base.group_multi_salesteams' on sales team dependent module --- addons/crm/crm_lead_view.xml | 2 +- addons/crm/report/crm_lead_report_view.xml | 2 +- addons/crm/report/crm_phonecall_report_view.xml | 2 +- addons/crm_partner_assign/report/crm_lead_report_view.xml | 4 ++-- addons/crm_partner_assign/report/crm_partner_report_view.xml | 4 ++-- addons/sale/report/sale_report_view.xml | 2 +- addons/sale_team/__openerp__.py | 1 + addons/sale_team/sale_team.xml | 2 +- 8 files changed, 10 insertions(+), 9 deletions(-) diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 88d0349b939..25af392d998 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -575,7 +575,7 @@ - + diff --git a/addons/crm/report/crm_lead_report_view.xml b/addons/crm/report/crm_lead_report_view.xml index 1dda471c2f2..239579a70c2 100644 --- a/addons/crm/report/crm_lead_report_view.xml +++ b/addons/crm/report/crm_lead_report_view.xml @@ -81,7 +81,7 @@ - + diff --git a/addons/crm/report/crm_phonecall_report_view.xml b/addons/crm/report/crm_phonecall_report_view.xml index 32ae9704064..98f4a58d7c4 100644 --- a/addons/crm/report/crm_phonecall_report_view.xml +++ b/addons/crm/report/crm_phonecall_report_view.xml @@ -47,7 +47,7 @@ - + diff --git a/addons/crm_partner_assign/report/crm_lead_report_view.xml b/addons/crm_partner_assign/report/crm_lead_report_view.xml index 271c9b2016f..43997ac5609 100644 --- a/addons/crm_partner_assign/report/crm_lead_report_view.xml +++ b/addons/crm_partner_assign/report/crm_lead_report_view.xml @@ -27,9 +27,9 @@ domain="[]" context="{'group_by':'user_id'}" /> - + context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/> diff --git a/addons/crm_partner_assign/report/crm_partner_report_view.xml b/addons/crm_partner_assign/report/crm_partner_report_view.xml index 02320219218..62e54260285 100644 --- a/addons/crm_partner_assign/report/crm_partner_report_view.xml +++ b/addons/crm_partner_assign/report/crm_partner_report_view.xml @@ -16,8 +16,8 @@ - + - + diff --git a/addons/sale_team/__openerp__.py b/addons/sale_team/__openerp__.py index b380df08313..ebca1e7100e 100644 --- a/addons/sale_team/__openerp__.py +++ b/addons/sale_team/__openerp__.py @@ -26,6 +26,7 @@ 'summary': 'Sales Team', 'description': """ Using this application you can manage Sales Team with CRM and/or Sales +======================================================================= """, 'website': 'http://www.openerp.com', 'depends': ['base','mail','web_kanban_sparkline',], diff --git a/addons/sale_team/sale_team.xml b/addons/sale_team/sale_team.xml index 8155fdd8ce0..56d709efce5 100644 --- a/addons/sale_team/sale_team.xml +++ b/addons/sale_team/sale_team.xml @@ -195,6 +195,6 @@

- + From 146051db29cee16b5e93f74d4080bdc1831fc7d5 Mon Sep 17 00:00:00 2001 From: Mantavya Gajjar Date: Fri, 9 May 2014 13:14:38 +0530 Subject: [PATCH 37/51] [FIX]: review, add default to get the current state --- addons/sale_team/sale_team_config.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/addons/sale_team/sale_team_config.py b/addons/sale_team/sale_team_config.py index c6adeb73a55..0c36ac31084 100644 --- a/addons/sale_team/sale_team_config.py +++ b/addons/sale_team/sale_team_config.py @@ -58,3 +58,12 @@ class sale_team_configuration(osv.TransientModel): implied_group='base.group_multi_salesteams', help="""Allows you to use Sales Teams to manage your leads and opportunities."""), } + + def _get_group_multi_salesteams(self, cr, uid, ids, context=None): + pass + # Todo: return the current state for the group_multi_salesteams field + + _defaults = { + 'group_multi_salesteams': _get_group_multi_salesteams + } + From ca2bee0865c086dbbc4b1a2effa46fce382a98a5 Mon Sep 17 00:00:00 2001 From: "Prashant Panchal (OpenERP)" Date: Fri, 9 May 2014 16:16:07 +0530 Subject: [PATCH 38/51] [IMP]add groups 'base.group_multi_salesteams' --- addons/crm/crm_lead_view.xml | 5 +---- addons/crm/wizard/crm_lead_to_opportunity_view.xml | 2 +- addons/crm/wizard/crm_merge_opportunities_view.xml | 2 +- addons/crm_claim/report/crm_claim_report_view.xml | 2 +- addons/crm_partner_assign/crm_portal_view.xml | 4 ++-- addons/project_issue/report/project_issue_report_view.xml | 2 +- addons/sale/sale_view.xml | 2 +- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 25af392d998..5a7aac6e569 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -1,12 +1,9 @@ - - Stage - Search @@ -570,7 +567,7 @@ help="Opportunities that are assigned to me"/> + help="Opportunities that are assigned to any sales teams I am member of" groups="base.group_multi_salesteams"/> diff --git a/addons/crm/wizard/crm_lead_to_opportunity_view.xml b/addons/crm/wizard/crm_lead_to_opportunity_view.xml index 0f8f676d92d..58bbcf4d934 100644 --- a/addons/crm/wizard/crm_lead_to_opportunity_view.xml +++ b/addons/crm/wizard/crm_lead_to_opportunity_view.xml @@ -12,7 +12,7 @@ - + diff --git a/addons/crm/wizard/crm_merge_opportunities_view.xml b/addons/crm/wizard/crm_merge_opportunities_view.xml index f88a6fe6f17..92007d52e42 100644 --- a/addons/crm/wizard/crm_merge_opportunities_view.xml +++ b/addons/crm/wizard/crm_merge_opportunities_view.xml @@ -10,7 +10,7 @@
- + diff --git a/addons/crm_claim/report/crm_claim_report_view.xml b/addons/crm_claim/report/crm_claim_report_view.xml index 710603af10a..2068ed8ea27 100644 --- a/addons/crm_claim/report/crm_claim_report_view.xml +++ b/addons/crm_claim/report/crm_claim_report_view.xml @@ -46,7 +46,7 @@ - + diff --git a/addons/crm_partner_assign/crm_portal_view.xml b/addons/crm_partner_assign/crm_portal_view.xml index dab831dcb4b..1ad0659d217 100644 --- a/addons/crm_partner_assign/crm_portal_view.xml +++ b/addons/crm_partner_assign/crm_portal_view.xml @@ -18,7 +18,7 @@ - +
- - - - - crm.case.section.kanban - crm.case.section - - - @@ -130,8 +120,41 @@ - + + crm.case.section.form + crm.case.section + + + +
+
+ + + + + + + + + + +
+
+ kanban @@ -160,39 +183,5 @@ tree - - - crm.case.section.form - crm.case.section - - - -
-
- - - - - - - - - - -
-
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 6be094c2227..7f5ae17dc33 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -90,7 +90,10 @@ class crm_lead(format_address, osv.osv): def _get_default_section_id(self, cr, uid, context=None): """ Gives default section by checking if present in the context """ - return self._resolve_section_id_from_context(cr, uid, context=context) or False + section_id = self._resolve_section_id_from_context(cr, uid, context=context) or False + if not section_id: + section_id = self.pool.get('res.users').browse(cr, uid, uid, context).default_section_id.id or False + return section_id def _get_default_stage_id(self, cr, uid, context=None): """ Gives default stage_id """ @@ -352,12 +355,8 @@ class crm_lead(format_address, osv.osv): section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context) if section_ids: section_id = section_ids[0] - else: - user = self.pool.get('res.users').browse(cr, uid, user_id, context=context) - if user.default_section_id and user.default_section_id.id: - return {'value': {'section_id': user.default_section_id.id}} return {'value': {'section_id': section_id}} - + def stage_find(self, cr, uid, cases, section_id, domain=None, order='sequence', context=None): """ Override of the base.stage method Parameter of the stage search taken from the lead: diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml index ef105d3989e..8acefd5c610 100644 --- a/addons/crm/crm_lead_demo.xml +++ b/addons/crm/crm_lead_demo.xml @@ -54,7 +54,7 @@ Could you please send me the details ? - 4 + 1 @@ -104,7 +104,7 @@ Could you please send me the details ? - 3 + 2 @@ -129,7 +129,7 @@ Could you please send me the details ? - 3 + 2 diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index 3481e29baf5..995a1ddea43 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -1,7 +1,7 @@ - diff --git a/addons/crm/res_config.py b/addons/crm/res_config.py index 1cd31cd0dd0..d48e66e286c 100644 --- a/addons/crm/res_config.py +++ b/addons/crm/res_config.py @@ -27,33 +27,6 @@ class crm_configuration(osv.TransientModel): _name = 'sale.config.settings' _inherit = ['sale.config.settings', 'fetchmail.config.settings'] - def set_group_multi_salesteams(self, cr, uid, ids, context=None): - """ This method is automatically called by res_config as it begins - with set. It is used to implement the 'one group or another' - behavior. We have to perform some group manipulation by hand - because in res_config.execute(), set_* methods are called - after group_*; therefore writing on an hidden res_config file - could not work. - If group_multi_salesteams is checked: remove group_mono_salesteams - from group_user, remove the users. Otherwise, just add - group_mono_salesteams in group_user. - The inverse logic about group_multi_salesteams is managed by the - normal behavior of 'group_multi_salesteams' field. - """ - def ref(xml_id): - mod, xml = xml_id.split('.', 1) - return self.pool['ir.model.data'].get_object(cr, uid, mod, xml, context) - - for obj in self.browse(cr, uid, ids, context=context): - config_group = ref('base.group_mono_salesteams') - base_group = ref('base.group_user') - if obj.group_multi_salesteams: - base_group.write({'implied_ids': [(3, config_group.id)]}) - config_group.write({'users': [(3, u.id) for u in base_group.users]}) - else: - base_group.write({'implied_ids': [(4, config_group.id)]}) - return True - _columns = { 'group_fund_raising': fields.boolean("Manage Fund Raising", implied_group='crm.group_fund_raising', @@ -64,9 +37,6 @@ class crm_configuration(osv.TransientModel): 'module_crm_helpdesk': fields.boolean("Manage Helpdesk and Support", help='Allows you to communicate with Customer, process Customer query, and provide better help and support.\n' '-This installs the module crm_helpdesk.'), - 'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams", - implied_group='base.group_multi_salesteams', - help="""Allows you to use Sales Teams to manage your leads and opportunities."""), 'alias_prefix': fields.char('Default Alias Name for Leads'), 'alias_domain' : fields.char('Alias Domain'), 'group_scheduled_calls': fields.boolean("Schedule calls to manage call center", diff --git a/addons/crm/crm_case_section.py b/addons/crm/sales_team.py similarity index 99% rename from addons/crm/crm_case_section.py rename to addons/crm/sales_team.py index 7a8baca5a44..ac626563f36 100644 --- a/addons/crm/crm_case_section.py +++ b/addons/crm/sales_team.py @@ -78,4 +78,3 @@ class crm_case_section(osv.Model): res = super(crm_case_section, self).unlink(cr, uid, ids, context=context) mail_alias.unlink(cr, uid, alias_ids, context=context) return res - diff --git a/addons/sales_team/__init__.py b/addons/sales_team/__init__.py index 075d782f564..3b2a8f41267 100644 --- a/addons/sales_team/__init__.py +++ b/addons/sales_team/__init__.py @@ -19,6 +19,6 @@ # ############################################################################## import sales_team -import sales_team_config +import res_config # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sales_team/sales_team_config.py b/addons/sales_team/res_config.py similarity index 59% rename from addons/sales_team/sales_team_config.py rename to addons/sales_team/res_config.py index 1e2a193acf7..3081a2e918d 100644 --- a/addons/sales_team/sales_team_config.py +++ b/addons/sales_team/res_config.py @@ -1,30 +1,11 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-today OpenERP SA () -# -# 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 . -# -############################################################################## from openerp.osv import fields, osv class sales_team_configuration(osv.TransientModel): _name = 'sale.config.settings' - _inherit = ['sale.config.settings', 'fetchmail.config.settings'] + _inherit = ['sale.config.settings'] def set_group_multi_salesteams(self, cr, uid, ids, context=None): """ This method is automatically called by res_config as it begins @@ -58,12 +39,3 @@ class sales_team_configuration(osv.TransientModel): implied_group='base.group_multi_salesteams', help="""Allows you to use Sales Teams to manage your leads and opportunities."""), } - - def _get_group_multi_salesteams(self, cr, uid, ids, context=None): - pass - # Todo: return the current state for the group_multi_salesteams field - - _defaults = { - 'group_multi_salesteams': _get_group_multi_salesteams - } - diff --git a/addons/sales_team/security/sales_team_security.xml b/addons/sales_team/security/sales_team_security.xml index fe999715a7a..d9002f8332f 100644 --- a/addons/sales_team/security/sales_team_security.xml +++ b/addons/sales_team/security/sales_team_security.xml @@ -1,49 +1,17 @@ - - - User: Own Leads Only - - - the user will have access to his own data in the sales application. - - - - User: All Leads - - - the user will have access to all records of everyone in the sales application. - - - - Manager - the user will have an access to the sales configuration as well as statistic reports. - - - - - Do Not Use Sales Teams - + + + + Manage Sales Teams - - - - - - - - - - - - From 3406b6db397506c3fedb974f0fe470e2d99b9a92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 12 May 2014 14:56:01 +0200 Subject: [PATCH 41/51] [CLEAN] sale, sale_crm: cleaning before merging. Cleaned calculation of default section id + removed duplicated code. --- addons/crm/__openerp__.py | 2 +- ...e_section_view.xml => sales_team_view.xml} | 0 .../sale_crm_goals_demo.xml | 2 +- addons/sale/__init__.py | 8 +-- addons/sale/__openerp__.py | 2 +- addons/sale/sale.py | 63 ++++++++++++++----- .../{crm_case_section.py => sales_team.py} | 30 +-------- ...e_section_view.xml => sales_team_view.xml} | 0 addons/sale_crm/sale_crm.py | 1 - 9 files changed, 56 insertions(+), 52 deletions(-) rename addons/crm/{crm_case_section_view.xml => sales_team_view.xml} (100%) rename addons/sale/{crm_case_section.py => sales_team.py} (74%) rename addons/sale/{crm_case_section_view.xml => sales_team_view.xml} (100%) diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index ecdefb9e051..1bf4c611be6 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -91,7 +91,7 @@ Dashboard for CRM will include: 'res_config_view.xml', 'base_partner_merge_view.xml', - 'crm_case_section_view.xml', + 'sales_team_view.xml', 'views/crm.xml', ], 'demo': [ diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/sales_team_view.xml similarity index 100% rename from addons/crm/crm_case_section_view.xml rename to addons/crm/sales_team_view.xml diff --git a/addons/gamification_sale_crm/sale_crm_goals_demo.xml b/addons/gamification_sale_crm/sale_crm_goals_demo.xml index a4720b1e566..b149af31f48 100644 --- a/addons/gamification_sale_crm/sale_crm_goals_demo.xml +++ b/addons/gamification_sale_crm/sale_crm_goals_demo.xml @@ -6,6 +6,6 @@ inprogress - + diff --git a/addons/sale/__init__.py b/addons/sale/__init__.py index 509364643ae..c99fc936467 100644 --- a/addons/sale/__init__.py +++ b/addons/sale/__init__.py @@ -19,16 +19,10 @@ # ############################################################################## -#---------------------------------------------------------- -# Init Sales -#---------------------------------------------------------- - import sale -import crm_case_section +import sales_team import res_partner import wizard import report import edi import res_config - -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/sale/__openerp__.py b/addons/sale/__openerp__.py index 52baff32ab7..039e765cd71 100644 --- a/addons/sale/__openerp__.py +++ b/addons/sale/__openerp__.py @@ -71,7 +71,7 @@ The Dashboard for the Sales Manager will include 'sale_report.xml', 'sale_data.xml', 'sale_view.xml', - 'crm_case_section_view.xml', + 'sales_team_view.xml', 'res_partner_view.xml', 'report/sale_report_view.xml', 'edi/sale_order_action_data.xml', diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 34886a22991..e3b59c00015 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -19,11 +19,7 @@ # ############################################################################## -import calendar -from openerp import tools -from datetime import date, datetime, timedelta -from dateutil.relativedelta import relativedelta -from dateutil import relativedelta +from datetime import datetime, timedelta import time from openerp.osv import fields, osv from openerp.tools.translate import _ @@ -166,12 +162,29 @@ class sale_order(osv.osv): if not company_id: raise osv.except_osv(_('Error!'), _('There is no default company for the current user!')) return company_id - + def _get_default_section_id(self, cr, uid, context=None): """ Gives default section by checking if present in the context """ - section_id = self.pool.get('res.users').browse(cr, uid, uid, context).default_section_id.id or False + section_id = self._resolve_section_id_from_context(cr, uid, context=context) or False + if not section_id: + section_id = self.pool.get('res.users').browse(cr, uid, uid, context).default_section_id.id or False return section_id + def _resolve_section_id_from_context(self, cr, uid, context=None): + """ Returns ID of section based on the value of 'section_id' + context key, or None if it cannot be resolved to a single + Sales Team. + """ + if context is None: + context = {} + if type(context.get('default_section_id')) in (int, long): + return context.get('default_section_id') + if isinstance(context.get('default_section_id'), basestring): + section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=context['default_section_id'], context=context) + if len(section_ids) == 1: + return int(section_ids[0][0]) + return None + _columns = { 'name': fields.char('Order Reference', size=64, required=True, readonly=True, states={'draft': [('readonly', False)], 'sent': [('readonly', False)]}, select=True), @@ -252,7 +265,6 @@ class sale_order(osv.osv): 'partner_shipping_id': lambda self, cr, uid, context: context.get('partner_id', False) and self.pool.get('res.partner').address_get(cr, uid, [context['partner_id']], ['delivery'])['delivery'], 'note': lambda self, cr, uid, context: self.pool.get('res.users').browse(cr, uid, uid, context=context).company_id.sale_note, 'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c), - } _sql_constraints = [ ('name_uniq', 'unique(name, company_id)', 'Order Reference must be unique per Company!'), @@ -1199,13 +1211,40 @@ class mail_compose_message(osv.Model): self.pool.get('sale.order').signal_quotation_sent(cr, uid, [context['default_res_id']]) return super(mail_compose_message, self).send_mail(cr, uid, ids, context=context) + class account_invoice(osv.Model): _inherit = 'account.invoice' - + + def _get_default_section_id(self, cr, uid, context=None): + """ Gives default section by checking if present in the context """ + section_id = self._resolve_section_id_from_context(cr, uid, context=context) or False + if not section_id: + section_id = self.pool.get('res.users').browse(cr, uid, uid, context).default_section_id.id or False + return section_id + + def _resolve_section_id_from_context(self, cr, uid, context=None): + """ Returns ID of section based on the value of 'section_id' + context key, or None if it cannot be resolved to a single + Sales Team. + """ + if context is None: + context = {} + if type(context.get('default_section_id')) in (int, long): + return context.get('default_section_id') + if isinstance(context.get('default_section_id'), basestring): + section_ids = self.pool.get('crm.case.section').name_search(cr, uid, name=context['default_section_id'], context=context) + if len(section_ids) == 1: + return int(section_ids[0][0]) + return None + _columns = { 'section_id': fields.many2one('crm.case.section', 'Sales Team'), } - + + _defaults = { + 'section_id': lambda self, cr, uid, c=None: self._get_default_section_id(cr, uid, context=c) + } + def confirm_paid(self, cr, uid, ids, context=None): sale_order_obj = self.pool.get('sale.order') res = super(account_invoice, self).confirm_paid(cr, uid, ids, context=context) @@ -1224,10 +1263,6 @@ class account_invoice(osv.Model): for id in ids: workflow.trg_validate(uid, 'account.invoice', id, 'invoice_cancel', cr) return super(account_invoice, self).unlink(cr, uid, ids, context=context) - - _defaults = { - 'section_id': lambda self, cr, uid, c=None: self.pool.get('res.users').browse(cr, uid, uid, c).default_section_id.id or False, - } class procurement_order(osv.osv): diff --git a/addons/sale/crm_case_section.py b/addons/sale/sales_team.py similarity index 74% rename from addons/sale/crm_case_section.py rename to addons/sale/sales_team.py index 11c8cdc8bb4..e8161d7325c 100644 --- a/addons/sale/crm_case_section.py +++ b/addons/sale/sales_team.py @@ -1,35 +1,11 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# 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 . -# -############################################################################## import calendar -from openerp import tools -from datetime import date, datetime, timedelta +from datetime import date from dateutil.relativedelta import relativedelta -from dateutil import relativedelta -import time + +from openerp import tools from openerp.osv import fields, osv -from openerp.tools.translate import _ -from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT, DATETIME_FORMATS_MAP, float_compare -import openerp.addons.decimal_precision as dp -from openerp import workflow class crm_case_section(osv.osv): diff --git a/addons/sale/crm_case_section_view.xml b/addons/sale/sales_team_view.xml similarity index 100% rename from addons/sale/crm_case_section_view.xml rename to addons/sale/sales_team_view.xml diff --git a/addons/sale_crm/sale_crm.py b/addons/sale_crm/sale_crm.py index 1b91ef4ef49..92c0abf2df0 100644 --- a/addons/sale_crm/sale_crm.py +++ b/addons/sale_crm/sale_crm.py @@ -29,7 +29,6 @@ from openerp.osv import osv, fields class sale_order(osv.osv): _inherit = 'sale.order' _columns = { - 'section_id': fields.many2one('crm.case.section', 'Sales Team'), 'categ_ids': fields.many2many('crm.case.categ', 'sale_order_category_rel', 'order_id', 'category_id', 'Tags', \ domain="['|', ('section_id', '=', section_id), ('section_id', '=', False), ('object_id.model', '=', 'crm.lead')]", context="{'object_name': 'crm.lead'}") } From e53cadcf2a16d5bad3c04b56a2e9f7016c00bdd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 12 May 2014 15:47:12 +0200 Subject: [PATCH 42/51] [FIX] sale: fixed import error about relativedelta --- addons/sale/sales_team.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sale/sales_team.py b/addons/sale/sales_team.py index e8161d7325c..10acac1dddc 100644 --- a/addons/sale/sales_team.py +++ b/addons/sale/sales_team.py @@ -2,7 +2,7 @@ import calendar from datetime import date -from dateutil.relativedelta import relativedelta +from dateutil import relativedelta from openerp import tools from openerp.osv import fields, osv From 03ee7ed00d15132377ed5586872dc4e3e28b25ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 12 May 2014 15:58:25 +0200 Subject: [PATCH 43/51] [FIX] sales_team: indentation issue --- addons/sales_team/sales_team.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/sales_team/sales_team.py b/addons/sales_team/sales_team.py index b356fa55900..ed3a3876231 100644 --- a/addons/sales_team/sales_team.py +++ b/addons/sales_team/sales_team.py @@ -78,7 +78,7 @@ class crm_case_section(osv.osv): 'note': fields.text('Description'), 'working_hours': fields.float('Working Hours', digits=(16, 2)), 'color': fields.integer('Color Index'), - } + } _defaults = { 'active': 1, From 3af0824fa585217853b297d2fd88df5a99b3375d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 12 May 2014 15:58:39 +0200 Subject: [PATCH 44/51] [FIX] crm: typo --- addons/crm/sales_team.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/crm/sales_team.py b/addons/crm/sales_team.py index ac626563f36..aeaf2cd8ec6 100644 --- a/addons/crm/sales_team.py +++ b/addons/crm/sales_team.py @@ -45,7 +45,7 @@ class crm_case_section(osv.Model): 'monthly_planned_revenue': fields.function(_get_opportunities_data, type="string", readonly=True, multi='_get_opportunities_data', string='Planned Revenue per Month'), - 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically ""create new leads assigned to the team."), + 'alias_id': fields.many2one('mail.alias', 'Alias', ondelete="restrict", required=True, help="The email address associated with this team. New emails received will automatically create new leads assigned to the team."), } def _auto_init(self, cr, context=None): From 1101c618a5113623dcb40190935c0b5f0b3cbc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Mon, 12 May 2014 15:58:47 +0200 Subject: [PATCH 45/51] [FIX] sales_team: fixed kanban view inheritance --- addons/crm/sales_team_view.xml | 14 +++++--------- addons/sales_team/sales_team.xml | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/addons/crm/sales_team_view.xml b/addons/crm/sales_team_view.xml index 8be2d888fb6..0a1b1933385 100644 --- a/addons/crm/sales_team_view.xml +++ b/addons/crm/sales_team_view.xml @@ -85,20 +85,16 @@ - + - - -
- %% -
-
- - + +
+ %% +
Leads diff --git a/addons/sales_team/sales_team.xml b/addons/sales_team/sales_team.xml index 5913565c98b..95fcb6f3078 100644 --- a/addons/sales_team/sales_team.xml +++ b/addons/sales_team/sales_team.xml @@ -56,7 +56,7 @@
-

+

From 28b20d312d837a8ff67012093180217592ad9548 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 13 May 2014 12:32:53 +0200 Subject: [PATCH 46/51] [FIX] Re-added code removed by al at rev 9421: website quotation email was not sent anymore. --- addons/website_sale/controllers/main.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/addons/website_sale/controllers/main.py b/addons/website_sale/controllers/main.py index 1b96f8e96e9..c31cd970344 100644 --- a/addons/website_sale/controllers/main.py +++ b/addons/website_sale/controllers/main.py @@ -634,6 +634,16 @@ class website_sale(http.Controller): # cancel the quotation sale_order_obj.action_cancel(cr, SUPERUSER_ID, [order.id], context=request.context) + # send the email + if email_act and email_act.get('context'): + composer_values = {} + email_ctx = email_act['context'] + public_id = request.website.user_id.id + if uid == public_id: + composer_values['email_from'] = request.website.user_id.company_id.email + composer_id = request.registry['mail.compose.message'].create(cr, SUPERUSER_ID, composer_values, context=email_ctx) + request.registry['mail.compose.message'].send_mail(cr, SUPERUSER_ID, [composer_id], context=email_ctx) + # clean context and session, then redirect to the confirmation page request.website.sale_reset(context=context) From aa08287d08f837ca5d8dcb397e60aa35286db966 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Tue, 13 May 2014 13:18:37 +0200 Subject: [PATCH 47/51] [FIX] document, mail, project: re-add tree view for attachments removed at revision 9279 and 9280 when adding a kanban view for documents. --- addons/document/document_view.xml | 2 +- addons/mail/mail_message_view.xml | 2 +- addons/project/project.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/addons/document/document_view.xml b/addons/document/document_view.xml index b735d2ce4a4..3376655d29d 100644 --- a/addons/document/document_view.xml +++ b/addons/document/document_view.xml @@ -243,7 +243,7 @@ ir.actions.act_window ir.attachment form - kanban,form + kanban,tree,form

Click to create a new document. diff --git a/addons/mail/mail_message_view.xml b/addons/mail/mail_message_view.xml index 1c4a7f55c10..6c15114cde6 100644 --- a/addons/mail/mail_message_view.xml +++ b/addons/mail/mail_message_view.xml @@ -115,7 +115,7 @@ - kanban,form + kanban,tree,form diff --git a/addons/project/project.py b/addons/project/project.py index 883db8fd0be..2249cf07dd5 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -214,7 +214,7 @@ class project(osv.osv): 'res_model': 'ir.attachment', 'type': 'ir.actions.act_window', 'view_id': False, - 'view_mode': 'kanban,form', + 'view_mode': 'kanban,tree,form', 'view_type': 'form', 'limit': 80, 'context': "{'default_res_model': '%s','default_res_id': %d}" % (self._name, res_id) From 59effb89e301aedefbfd3a5292dfe78c9064cb78 Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Tue, 13 May 2014 15:31:24 +0200 Subject: [PATCH 48/51] [FIX] broken domain in route --- addons/website_forum_doc/controllers/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/addons/website_forum_doc/controllers/main.py b/addons/website_forum_doc/controllers/main.py index 25fdf9aa2e4..f31e41759db 100644 --- a/addons/website_forum_doc/controllers/main.py +++ b/addons/website_forum_doc/controllers/main.py @@ -26,7 +26,7 @@ class WebsiteDoc(http.Controller): } return request.website.render("website_forum_doc.documentation", value) - @http.route(['''/forum/how-to/// Date: Wed, 14 May 2014 05:45:54 +0000 Subject: [PATCH 49/51] Launchpad automatic translations update. --- addons/account_test/i18n/de.po | 241 +++++++++++++++++++++++++++++++++ addons/edi/i18n/ta.po | 87 ++++++++++++ addons/sale/i18n/fr_CA.po | 2 +- 3 files changed, 329 insertions(+), 1 deletion(-) create mode 100644 addons/account_test/i18n/de.po create mode 100644 addons/edi/i18n/ta.po diff --git a/addons/account_test/i18n/de.po b/addons/account_test/i18n/de.po new file mode 100644 index 00000000000..157223be184 --- /dev/null +++ b/addons/account_test/i18n/de.po @@ -0,0 +1,241 @@ +# German translation for openobject-addons +# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:05+0000\n" +"PO-Revision-Date: 2014-05-13 09:39+0000\n" +"Last-Translator: Claudia Haida \n" +"Language-Team: German \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2014-05-14 05:45+0000\n" +"X-Generator: Launchpad (build 17002)\n" + +#. module: account_test +#: view:accounting.assert.test:0 +msgid "" +"Code should always set a variable named `result` with the result of your " +"test, that can be a list or\n" +"a dictionary. If `result` is an empty list, it means that the test was " +"succesful. Otherwise it will\n" +"try to translate and print what is inside `result`.\n" +"\n" +"If the result of your test is a dictionary, you can set a variable named " +"`column_order` to choose in\n" +"what order you want to print `result`'s content.\n" +"\n" +"Should you need them, you can also use the following variables into your " +"code:\n" +" * cr: cursor to the database\n" +" * uid: ID of the current user\n" +"\n" +"In any ways, the code must be legal python statements with correct " +"indentation (if needed).\n" +"\n" +"Example: \n" +" sql = '''SELECT id, name, ref, date\n" +" FROM account_move_line \n" +" WHERE account_id IN (SELECT id FROM account_account WHERE type " +"= 'view')\n" +" '''\n" +" cr.execute(sql)\n" +" result = cr.dictfetchall()" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_02 +msgid "Test 2: Opening a fiscal year" +msgstr "Test2: Eröffnung eines Geschäftsjahres" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_05 +msgid "" +"Check that reconciled invoice for Sales/Purchases has reconciled entries for " +"Payable and Receivable Accounts" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_03 +msgid "" +"Check if movement lines are balanced and have the same date and period" +msgstr "" + +#. module: account_test +#: field:accounting.assert.test,name:0 +msgid "Test Name" +msgstr "" + +#. module: account_test +#: report:account.test.assert.print:0 +msgid "Accouting tests on" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_01 +msgid "Test 1: General balance" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_06 +msgid "Check that paid/reconciled invoices are not in 'Open' state" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_05_2 +msgid "" +"Check that reconciled account moves, that define Payable and Receivable " +"accounts, are belonging to reconciled invoices" +msgstr "" + +#. module: account_test +#: view:accounting.assert.test:0 +msgid "Tests" +msgstr "" + +#. module: account_test +#: field:accounting.assert.test,desc:0 +msgid "Test Description" +msgstr "" + +#. module: account_test +#: view:accounting.assert.test:0 +msgid "Description" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_06_1 +msgid "Check that there's no move for any account with « View » account type" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_08 +msgid "Test 9 : Accounts and partners on account moves" +msgstr "" + +#. module: account_test +#: model:ir.actions.act_window,name:account_test.action_accounting_assert +#: model:ir.actions.report.xml,name:account_test.account_assert_test_report +#: model:ir.ui.menu,name:account_test.menu_action_license +msgid "Accounting Tests" +msgstr "" + +#. module: account_test +#: code:addons/account_test/report/account_test_report.py:74 +#, python-format +msgid "The test was passed successfully" +msgstr "" + +#. module: account_test +#: field:accounting.assert.test,active:0 +msgid "Active" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_06 +msgid "Test 6 : Invoices status" +msgstr "" + +#. module: account_test +#: model:ir.model,name:account_test.model_accounting_assert_test +msgid "accounting.assert.test" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_05 +msgid "" +"Test 5.1 : Payable and Receivable accountant lines of reconciled invoices" +msgstr "" + +#. module: account_test +#: field:accounting.assert.test,code_exec:0 +msgid "Python code" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_07 +msgid "" +"Check on bank statement that the Closing Balance = Starting Balance + sum of " +"statement lines" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_07 +msgid "Test 8 : Closing balance on bank statements" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_03 +msgid "Test 3: Movement lines" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_05_2 +msgid "Test 5.2 : Reconcilied invoices and Payable/Receivable accounts" +msgstr "" + +#. module: account_test +#: view:accounting.assert.test:0 +msgid "Expression" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_04 +msgid "Test 4: Totally reconciled mouvements" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_04 +msgid "Check if the totally reconciled movements are balanced" +msgstr "" + +#. module: account_test +#: field:accounting.assert.test,sequence:0 +msgid "Sequence" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_02 +msgid "" +"Check if the balance of the new opened fiscal year matches with last year's " +"balance" +msgstr "" + +#. module: account_test +#: view:accounting.assert.test:0 +msgid "Python Code" +msgstr "" + +#. module: account_test +#: model:ir.actions.act_window,help:account_test.action_accounting_assert +msgid "" +"

\n" +" Click to create Accounting Test.\n" +"

\n" +" " +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_01 +msgid "Check the balance: Debit sum = Credit sum" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,desc:account_test.account_test_08 +msgid "Check that general accounts and partners on account moves are active" +msgstr "" + +#. module: account_test +#: model:accounting.assert.test,name:account_test.account_test_06_1 +msgid "Test 7: « View  » account type" +msgstr "" + +#. module: account_test +#: view:accounting.assert.test:0 +msgid "Code Help" +msgstr "" diff --git a/addons/edi/i18n/ta.po b/addons/edi/i18n/ta.po new file mode 100644 index 00000000000..7f0570794ac --- /dev/null +++ b/addons/edi/i18n/ta.po @@ -0,0 +1,87 @@ +# Tamil translation for openobject-addons +# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR , 2014. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME \n" +"POT-Creation-Date: 2012-12-21 17:05+0000\n" +"PO-Revision-Date: 2014-05-13 08:27+0000\n" +"Last-Translator: FULL NAME \n" +"Language-Team: Tamil \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2014-05-14 05:45+0000\n" +"X-Generator: Launchpad (build 17002)\n" + +#. module: edi +#. openerp-web +#: code:addons/edi/static/src/js/edi.js:67 +#, python-format +msgid "Reason:" +msgstr "" + +#. module: edi +#. openerp-web +#: code:addons/edi/static/src/js/edi.js:60 +#, python-format +msgid "The document has been successfully imported!" +msgstr "" + +#. module: edi +#. openerp-web +#: code:addons/edi/static/src/js/edi.js:65 +#, python-format +msgid "Sorry, the document could not be imported." +msgstr "" + +#. module: edi +#: model:ir.model,name:edi.model_res_company +msgid "Companies" +msgstr "" + +#. module: edi +#: model:ir.model,name:edi.model_res_currency +msgid "Currency" +msgstr "" + +#. module: edi +#. openerp-web +#: code:addons/edi/static/src/js/edi.js:71 +#, python-format +msgid "Document Import Notification" +msgstr "" + +#. module: edi +#: code:addons/edi/models/edi.py:130 +#, python-format +msgid "Missing application." +msgstr "" + +#. module: edi +#: code:addons/edi/models/edi.py:131 +#, python-format +msgid "" +"The document you are trying to import requires the OpenERP `%s` application. " +"You can install it by connecting as the administrator and opening the " +"configuration assistant." +msgstr "" + +#. module: edi +#: code:addons/edi/models/edi.py:47 +#, python-format +msgid "'%s' is an invalid external ID" +msgstr "" + +#. module: edi +#: model:ir.model,name:edi.model_res_partner +msgid "Partner" +msgstr "" + +#. module: edi +#: model:ir.model,name:edi.model_edi_edi +msgid "EDI Subsystem" +msgstr "" diff --git a/addons/sale/i18n/fr_CA.po b/addons/sale/i18n/fr_CA.po index 56acd05eb3f..867db031b73 100644 --- a/addons/sale/i18n/fr_CA.po +++ b/addons/sale/i18n/fr_CA.po @@ -15,7 +15,7 @@ msgstr "" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2014-05-13 05:59+0000\n" +"X-Launchpad-Export-Date: 2014-05-14 05:45+0000\n" "X-Generator: Launchpad (build 17002)\n" #. module: sale From 02be7aa65cb2fa06ab2d1fcd250a419015f3c674 Mon Sep 17 00:00:00 2001 From: Kersten Jeremy Date: Wed, 14 May 2014 10:28:10 +0200 Subject: [PATCH 50/51] [IMP] Improve Calendar synchronization - Use the new fields from calendar (renamed in 9398.1.7 trunk-cal-v8-jke) - Manage some google excpetions about token expiration - Manage some google excpetions about token revokation - Manage some google excpetions about 410 - Gone from google - Detect / Allow user to change the calendar and remove all old reference. - Use by default the since_modified for synchronization and not always a full (updatedTime in google) - Add some key for ir_parameters : 'calendar.week_synchro' : number of week to synchornize when we are in full mode 'calendar.block_synchro_attendee' : need to synchronize the attendees between google and openerp (creation of partner) 'calendar.block_since_synchro' : Always use the full synchro. (Can be forced in "one time" removing the last synchro from res_users form) 'calendar.debug_print' : _logger is more verbose - Pep 8 - Change behaviour of do_request to return a tuple with status raquest and response - Add field from token and synchro date in res_users view form --- addons/calendar/calendar.py | 49 +- addons/google_account/google_account.py | 122 ++--- addons/google_calendar/__openerp__.py | 6 +- addons/google_calendar/controllers/main.py | 73 +-- addons/google_calendar/google_calendar.py | 449 +++++++++++++----- .../static/src/js/calendar_sync.js | 42 +- addons/google_calendar/views/res_users.xml | 24 + 7 files changed, 498 insertions(+), 267 deletions(-) create mode 100644 addons/google_calendar/views/res_users.xml diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py index c4b584100b6..46e8b024dd6 100644 --- a/addons/calendar/calendar.py +++ b/addons/calendar/calendar.py @@ -1,23 +1,4 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Business Applications -# Copyright (c) 2011-2014 OpenERP S.A. -# -# 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 . -# -############################################################################## import pytz import re @@ -188,7 +169,8 @@ class calendar_attendee(osv.Model): res = cal.serialize() return res - def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='calendar_template_meeting_invitation', context=None): + def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), + template_xmlid='calendar_template_meeting_invitation', context=None): """ Send mail for event invitation to event attendees. @param email_from: email address for user sending the mail @@ -273,7 +255,8 @@ class calendar_attendee(osv.Model): meeting_obj = self.pool['calendar.event'] res = self.write(cr, uid, ids, {'state': 'accepted'}, context) for attendee in self.browse(cr, uid, ids, context=context): - meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)), subtype="calendar.subtype_invitation", context=context) + meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)), + subtype="calendar.subtype_invitation", context=context) return res @@ -1410,7 +1393,7 @@ class calendar_event(osv.Model): new_args.append(new_arg) if not context.get('virtual_id', True): - return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, context=context, count=count) + return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, count=count, context=context) # offset, limit, order and count must be treated separately as we may need to deal with virtual ids res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False) @@ -1535,17 +1518,17 @@ class calendar_event(osv.Model): if (values.get('start_date') or values.get('start_datetime', False)) and values.get('active', True): the_id = new_id or (ids and int(ids[0])) + if the_id: + if attendees_create: + attendees_create = attendees_create[the_id] + mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids'])) + else: + mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids] - if attendees_create: - attendees_create = attendees_create[the_id] - mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids'])) - else: - mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids] - - if mail_to_ids: - current_user = self.pool['res.users'].browse(cr, uid, uid, context=context) - if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context): - self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context) + if mail_to_ids: + current_user = self.pool['res.users'].browse(cr, uid, uid, context=context) + if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context): + self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context) return res or True and False def create(self, cr, uid, vals, context=None): @@ -1624,7 +1607,7 @@ class calendar_event(osv.Model): continue if r['class'] == 'private': for f in r.keys(): - if f not in ('id', 'allday', 'start', 'stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date'): + if f not in ('id', 'allday', 'start', 'stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date', 'rrule'): if isinstance(r[f], list): r[f] = [] else: diff --git a/addons/google_account/google_account.py b/addons/google_account/google_account.py index d42b0b67e59..7ffa4f95f48 100644 --- a/addons/google_account/google_account.py +++ b/addons/google_account/google_account.py @@ -1,28 +1,12 @@ # -*- coding: utf-8 -*- -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2010 Tiny SPRL (). -# -# 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 . -# -############################################################################## +import openerp +from openerp.http import request from openerp.osv import osv from openerp import SUPERUSER_ID from openerp.tools.translate import _ -from openerp.addons.web.http import request +from datetime import datetime +from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT import werkzeug.urls import urllib2 @@ -31,6 +15,7 @@ import simplejson import logging _logger = logging.getLogger(__name__) + class google_service(osv.osv_memory): _name = 'google.service' @@ -48,7 +33,8 @@ class google_service(osv.osv_memory): req = urllib2.Request("https://accounts.google.com/o/oauth2/token", data, headers) content = urllib2.urlopen(req).read() except urllib2.HTTPError: - raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"), context=context) + error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired" + raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context) content = simplejson.loads(content) return content.get('refresh_token') @@ -65,8 +51,8 @@ class google_service(osv.osv_memory): uri = 'https://accounts.google.com/o/oauth2/auth?%s' % werkzeug.url_encode(params) return uri - #If no scope is passed, we use service by default to get a default scope - def _get_authorize_uri(self, cr, uid, from_url, service, scope = False, context=None): + # If no scope is passed, we use service by default to get a default scope + def _get_authorize_uri(self, cr, uid, from_url, service, scope=False, context=None): """ This method return the url needed to allow this instance of OpenErp to access to the scope of gmail specified as parameters """ state_obj = dict(d=cr.dbname, s=service, f=from_url) @@ -76,11 +62,11 @@ class google_service(osv.osv_memory): params = { 'response_type': 'code', 'client_id': client_id, - 'state' : simplejson.dumps(state_obj), + 'state': simplejson.dumps(state_obj), 'scope': scope or 'https://www.googleapis.com/auth/%s' % (service,), 'redirect_uri': base_url + '/google_account/authentication', - 'approval_prompt':'force', - 'access_type':'offline' + 'approval_prompt': 'force', + 'access_type': 'offline' } uri = self.get_uri_oauth(a='auth') + "?%s" % werkzeug.url_encode(params) @@ -96,25 +82,24 @@ class google_service(osv.osv_memory): 'code': authorize_code, 'client_id': client_id, 'client_secret': client_secret, - 'grant_type' : 'authorization_code', + 'grant_type': 'authorization_code', 'redirect_uri': base_url + '/google_account/authentication' } headers = {"content-type": "application/x-www-form-urlencoded"} try: + uri = self.get_uri_oauth(a='token') data = werkzeug.url_encode(params) - req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers) - content = urllib2.urlopen(req).read() - res = simplejson.loads(content) - except urllib2.HTTPError,e: - raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid"), context=context) + st, res = self._do_request(cr, uid, uri, params=data, headers=headers, type='POST', preuri='', context=context) + except urllib2.HTTPError: + error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid" + raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context) return res - def _refresh_google_token_json(self, cr, uid, refresh_token, service, context=None): #exchange_AUTHORIZATION vs Token (service = calendar) + def _refresh_google_token_json(self, cr, uid, refresh_token, service, context=None): # exchange_AUTHORIZATION vs Token (service = calendar) res = False - base_url = self.get_base_url(cr, uid, context) client_id = self.get_client_id(cr, uid, service, context) client_secret = self.get_client_secret(cr, uid, service, context) @@ -122,60 +107,79 @@ class google_service(osv.osv_memory): 'refresh_token': refresh_token, 'client_id': client_id, 'client_secret': client_secret, - 'grant_type' : 'refresh_token' + 'grant_type': 'refresh_token', } headers = {"content-type": "application/x-www-form-urlencoded"} try: - data = werkzeug.url_encode(params) - req = urllib2.Request(self.get_uri_oauth(a='token'), data, headers) - content = urllib2.urlopen(req).read() - res = simplejson.loads(content) - except urllib2.HTTPError: - raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired"), context=context) + uri = self.get_uri_oauth(a='token') + data = werkzeug.url_encode(params) + st, res = self._do_request(cr, uid, uri, params=data, headers=headers, type='POST', preuri='', context=context) + except urllib2.HTTPError, e: + if e.code == 400: # invalid grant + registry = openerp.modules.registry.RegistryManager.get(request.session.db) + with registry.cursor() as cur: + self.pool['res.users'].write(cur, uid, [uid], {'google_%s_rtoken' % service: False}, context=context) + error_key = simplejson.loads(e.read()).get("error", "nc") + _logger.exception("Bad google request : %s !" % error_key) + error_msg = "Something went wrong during your token generation. Maybe your Authorization Code is invalid or already expired [%s]" % error_key + raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context) return res + def _do_request(self, cr, uid, uri, params={}, headers={}, type='POST', preuri="https://www.googleapis.com", context=None): + if context is None: + context = {} - def _do_request(self,cr,uid,uri,params={},headers={},type='POST', context=None): - _logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri,type,headers,werkzeug.url_encode(params) if type =='GET' else params)) - res = False + """ Return a tuple ('HTTP_CODE', 'HTTP_RESPONSE') """ + _logger.debug("Uri: %s - Type : %s - Headers: %s - Params : %s !" % (uri, type, headers, werkzeug.url_encode(params) if type == 'GET' else params)) + status = 418 + response = "" try: if type.upper() == 'GET' or type.upper() == 'DELETE': data = werkzeug.url_encode(params) - req = urllib2.Request(self.get_uri_api() + uri + "?" + data) - elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT': - req = urllib2.Request(self.get_uri_api() + uri, params, headers) + req = urllib2.Request(preuri + uri + "?" + data) + elif type.upper() == 'POST' or type.upper() == 'PATCH' or type.upper() == 'PUT': + req = urllib2.Request(preuri + uri, params, headers) else: raise ('Method not supported [%s] not in [GET, POST, PUT, PATCH or DELETE]!' % (type)) req.get_method = lambda: type.upper() request = urllib2.urlopen(req) + status = request.getcode() - if request.getcode() == 204: #No content returned, (ex: POST calendar/event/clear) - res = True - elif request.getcode() == 404: #Page not found - res = False + if int(status) in (204, 404): # Page not found, no response + response = False else: - content=request.read() - res = simplejson.loads(content) - except urllib2.HTTPError,e: + content = request.read() + response = simplejson.loads(content) + + if context.get('ask_time'): + try: + date = datetime.strptime(request.headers.get('date'), "%a, %d %b %Y %H:%M:%S %Z") + except: + date = datetime.now().strftime(DEFAULT_SERVER_DATETIME_FORMAT) + context['ask_time'] = date + except urllib2.HTTPError, e: + if e.code in (400, 401, 410): + raise e + _logger.exception("Bad google request : %s !" % e.read()) raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong with your request to google"), context=context) - return res + return (status, response) def get_base_url(self, cr, uid, context=None): - return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url',default='http://www.openerp.com?NoBaseUrl',context=context) + return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='http://www.openerp.com?NoBaseUrl', context=context) def get_client_id(self, cr, uid, service, context=None): - return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_id' % (service,),default=False,context=context) + return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_id' % (service,), default=False, context=context) def get_client_secret(self, cr, uid, service, context=None): - return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_secret' % (service,),default=False,context=context) + return self.pool.get('ir.config_parameter').get_param(cr, uid, 'google_%s_client_secret' % (service,), default=False, context=context) - def get_uri_oauth(self,a=''): #a = optional action + def get_uri_oauth(self, a=''): # a = optional action return "https://accounts.google.com/o/oauth2/%s" % (a,) def get_uri_api(self): diff --git a/addons/google_calendar/__openerp__.py b/addons/google_calendar/__openerp__.py index 18f2806976b..7e7a34362f6 100644 --- a/addons/google_calendar/__openerp__.py +++ b/addons/google_calendar/__openerp__.py @@ -25,20 +25,20 @@ 'version': '1.0', 'category': 'Tools', 'description': """ -The module adds the possibility to synchronize Google Calendar with OpenERP +The module adds the possibility to synchronize Google Calendar with OpenERP ======================================== """, 'author': 'OpenERP SA', 'website': 'http://www.openerp.com', - 'depends': ['google_account','calendar'], + 'depends': ['google_account', 'calendar'], 'qweb': ['static/src/xml/*.xml'], 'data': [ 'res_config_view.xml', 'security/ir.model.access.csv', 'views/google_calendar.xml', + 'views/res_users.xml', ], 'demo': [], 'installable': True, 'auto_install': False, } - diff --git a/addons/google_calendar/controllers/main.py b/addons/google_calendar/controllers/main.py index 49083a79bfc..1f307133ea0 100644 --- a/addons/google_calendar/controllers/main.py +++ b/addons/google_calendar/controllers/main.py @@ -1,51 +1,60 @@ -import simplejson -import urllib -import openerp import openerp.addons.web.http as http from openerp.addons.web.http import request -import openerp.addons.web.controllers.main as webmain -from openerp.addons.web.http import SessionExpiredException -from werkzeug.exceptions import BadRequest -import werkzeug.utils + class google_calendar_controller(http.Controller): - + @http.route('/google_calendar/sync_data', type='json', auth='user') - def sync_data(self, arch, fields, model,**kw): - """ + def sync_data(self, arch, fields, model, **kw): + """ This route/function is called when we want to synchronize openERP calendar with Google Calendar Function return a dictionary with the status : need_config_from_admin, need_auth, need_refresh, success if not calendar_event - The dictionary may contains an url, to allow OpenERP Client to redirect user on this URL for authorization for example + The dictionary may contains an url, to allow OpenERP Client to redirect user on this URL for authorization for example """ - + if model == 'calendar.event': gs_obj = request.registry['google.service'] gc_obj = request.registry['google.calendar'] - + # Checking that admin have already configured Google API for google synchronization ! - client_id = gs_obj.get_client_id(request.cr, request.uid,'calendar',context=kw.get('local_context')) + client_id = gs_obj.get_client_id(request.cr, request.uid, 'calendar', context=kw.get('local_context')) if not client_id or client_id == '': action = '' - if gc_obj.can_authorize_google(request.cr,request.uid): - dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid, 'google_calendar', 'action_config_settings_google_calendar') - + if gc_obj.can_authorize_google(request.cr, request.uid): + dummy, action = request.registry.get('ir.model.data').get_object_reference(request.cr, request.uid, + 'google_calendar', 'action_config_settings_google_calendar') + return { - "status" : "need_config_from_admin", - "url" : '', - "action" : action - } - + "status": "need_config_from_admin", + "url": '', + "action": action + } + # Checking that user have already accepted OpenERP to access his calendar ! - if gc_obj.need_authorize(request.cr, request.uid,context=kw.get('local_context')): - url = gc_obj.authorize_google_uri(request.cr, request.uid, from_url=kw.get('fromurl'), context=kw.get('local_context')) + if gc_obj.need_authorize(request.cr, request.uid, context=kw.get('local_context')): + url = gc_obj.authorize_google_uri(request.cr, request.uid, from_url=kw.get('fromurl'), context=kw.get('local_context')) return { - "status" : "need_auth", - "url" : url - } - + "status": "need_auth", + "url": url + } + # If App authorized, and user access accepted, We launch the synchronization - return gc_obj.synchronize_events(request.cr, request.uid, [], kw.get('local_context')) - - return { "status" : "success" } - + return gc_obj.synchronize_events(request.cr, request.uid, [], context=kw.get('local_context')) + + return {"status": "success"} + + @http.route('/google_calendar/remove_references', type='json', auth='user') + def remove_references(self, model, **kw): + """ + This route/function is called when we want to remove all the references between one calendar OpenERP and one Google Calendar + """ + status = "NOP" + if model == 'calendar.event': + gc_obj = request.registry['google.calendar'] + # Checking that user have already accepted OpenERP to access his calendar ! + if gc_obj.remove_references(request.cr, request.uid, context=kw.get('local_context')): + status = "OK" + else: + status = "KO" + return {"status": status} diff --git a/addons/google_calendar/google_calendar.py b/addons/google_calendar/google_calendar.py index 177642f091d..543c81977aa 100644 --- a/addons/google_calendar/google_calendar.py +++ b/addons/google_calendar/google_calendar.py @@ -1,31 +1,15 @@ -############################################################################## -# -# OpenERP, Open Source Management Solution -# Copyright (C) 2004-2012 OpenERP SA (). -# -# 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 . -# -############################################################################## +# -*- coding: utf-8 -*- import operator import simplejson -import urllib +import urllib2 +import openerp from openerp import tools from openerp import SUPERUSER_ID - +from openerp.tools import DEFAULT_SERVER_DATE_FORMAT, DEFAULT_SERVER_DATETIME_FORMAT +from openerp.tools.translate import _ +from openerp.http import request from datetime import datetime, timedelta from dateutil import parser import pytz @@ -35,6 +19,13 @@ import logging _logger = logging.getLogger(__name__) +def status_response(status, substr=False): + if substr: + return int(str(status)[0]) + else: + return status_response(status, substr=True) == 2 + + class Meta(type): """ This Meta class allow to define class as a structure, and so instancied variable in __init__ to avoid to have side effect alike 'static' variable """ @@ -90,7 +81,7 @@ class SyncEvent(object): def __getitem__(self, key): return getattr(self, key) - def compute_OP(self): + def compute_OP(self, modeFull=True): #If event are already in Gmail and in OpenERP if self.OE.found and self.GG.found: #If the event has been deleted from one side, we delete on other side ! @@ -120,7 +111,6 @@ class SyncEvent(object): else: if not self.OE.synchro or self.OE.synchro.split('.')[0] < self.OE.update.split('.')[0]: self.OP = Update('OE', 'Event already updated by another user, but not synchro with my google calendar') - #import ipdb; ipdb.set_trace(); else: self.OP = NothingToDo("", 'Not update needed') else: @@ -128,11 +118,13 @@ class SyncEvent(object): # New in openERP... Create on create_events of synchronize function elif self.OE.found and not self.GG.found: - #Has been deleted from gmail if self.OE.status: - self.OP = Delete('OE', 'Removed from GOOGLE') + self.OP = Delete('OE', 'Update or delete from GOOGLE') else: - self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP") + if not modeFull: + self.OP = Delete('GG', 'Deleted from OpenERP, need to delete it from Gmail if already created') + else: + self.OP = NothingToDo("", "Already Deleted in gmail and unlinked in OpenERP") elif self.GG.found and not self.OE.found: tmpSrc = 'GG' if not self.GG.status and not self.GG.isInstance: @@ -151,11 +143,11 @@ class SyncEvent(object): return self.__repr__() def __repr__(self): - myPrint = "---- A SYNC EVENT ---" + myPrint = "\n\n---- A SYNC EVENT ---" myPrint += "\n ID OE: %s " % (self.OE.event and self.OE.event.id) myPrint += "\n ID GG: %s " % (self.GG.event and self.GG.event.get('id', False)) - myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name) - myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', False)) + myPrint += "\n Name OE: %s " % (self.OE.event and self.OE.event.name.encode('utf8')) + myPrint += "\n Name GG: %s " % (self.GG.event and self.GG.event.get('summary', '').encode('utf8')) myPrint += "\n Found OE:%5s vs GG: %5s" % (self.OE.found, self.GG.found) myPrint += "\n Recurrence OE:%5s vs GG: %5s" % (self.OE.isRecurrence, self.GG.isRecurrence) myPrint += "\n Instance OE:%5s vs GG: %5s" % (self.OE.isInstance, self.GG.isInstance) @@ -207,15 +199,15 @@ class google_calendar(osv.AbstractModel): STR_SERVICE = 'calendar' _name = 'google.%s' % STR_SERVICE - def generate_data(self, cr, uid, event, context=None): + def generate_data(self, cr, uid, event, isCreating=False, context=None): if event.allday: - start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0] - end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration), context=context).isoformat('T').split('T')[0] + start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T').split('T')[0] + final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(hours=event.duration) + timedelta(days=isCreating and 1 or 0), context=context).isoformat('T').split('T')[0] type = 'date' vstype = 'dateTime' else: - start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T') - end_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.date_deadline, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T') + start_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.start, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T') + final_date = fields.datetime.context_timestamp(cr, uid, datetime.strptime(event.stop, tools.DEFAULT_SERVER_DATETIME_FORMAT), context=context).isoformat('T') type = 'dateTime' vstype = 'date' attendee_list = [] @@ -235,7 +227,7 @@ class google_calendar(osv.AbstractModel): 'timeZone': 'UTC' }, "end": { - type: end_date, + type: final_date, vstype: None, 'timeZone': 'UTC' }, @@ -256,10 +248,9 @@ class google_calendar(osv.AbstractModel): def create_an_event(self, cr, uid, event, context=None): gs_pool = self.pool['google.service'] + data = self.generate_data(cr, uid, event, isCreating=True, context=context) - data = self.generate_data(cr, uid, event, context=context) - - url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib.quote('id,updated'), self.get_token(cr, uid, context)) + url = "/calendar/v3/calendars/%s/events?fields=%s&access_token=%s" % ('primary', urllib2.quote('id,updated'), self.get_token(cr, uid, context)) headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} data_json = simplejson.dumps(data) @@ -276,38 +267,89 @@ class google_calendar(osv.AbstractModel): return gs_pool._do_request(cr, uid, url, params, headers, type='DELETE', context=context) - def get_event_dict(self, cr, uid, token=False, nextPageToken=False, context=None): + def get_calendar_primary_id(self, cr, uid, context=None): + params = { + 'fields': 'id', + 'access_token': self.get_token(cr, uid, context) + } + headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} + + url = "/calendar/v3/calendars/primary" + + try: + st, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context) + except Exception, e: + + if (e.code == 401): # Token invalid / Acces unauthorized + error_msg = "Your token is invalid or has been revoked !" + + registry = openerp.modules.registry.RegistryManager.get(request.session.db) + with registry.cursor() as cur: + self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_token': False, 'google_calendar_token_validity': False}, context=context) + + raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context) + raise + + return status_response(st) and content['id'] or False + + def get_event_synchro_dict(self, cr, uid, lastSync=False, token=False, nextPageToken=False, context=None): if not token: token = self.get_token(cr, uid, context) - gs_pool = self.pool['google.service'] - params = { 'fields': 'items,nextPageToken', 'access_token': token, 'maxResults': 1000, - 'timeMin': self.get_start_time_to_synchro(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"), + #'timeMin': self.get_minTime(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz"), } + + if lastSync: + params['updatedMin'] = lastSync.strftime("%Y-%m-%dT%H:%M:%S.%fz") + params['showDeleted'] = True + else: + params['timeMin'] = self.get_minTime(cr, uid, context=context).strftime("%Y-%m-%dT%H:%M:%S.%fz") + headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} url = "/calendar/v3/calendars/%s/events" % 'primary' if nextPageToken: params['pageToken'] = nextPageToken - content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context) + status, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context) google_events_dict = {} - for google_event in content['items']: google_events_dict[google_event['id']] = google_event - if content.get('nextPageToken', False): - google_events_dict.update(self.get_event_dict(cr, uid, token, content['nextPageToken'], context=context)) + if content.get('nextPageToken'): + google_events_dict.update( + self.get_event_synchro_dict(cr, uid, lastSync=lastSync, token=token, nextPageToken=content['nextPageToken'], context=context) + ) + return google_events_dict + def get_one_event_synchro(self, cr, uid, google_id, context=None): + token = self.get_token(cr, uid, context) + + params = { + 'access_token': token, + 'maxResults': 1000, + 'showDeleted': True, + } + + headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} + + url = "/calendar/v3/calendars/%s/events/%s" % ('primary', google_id) + try: + status, content = self.pool['google.service']._do_request(cr, uid, url, params, headers, type='GET', context=context) + except: + _logger.info("Calendar Synchro - In except of get_one_event_synchro") + pass + + return status_response(status) and content or False + def update_to_google(self, cr, uid, oe_event, google_event, context): calendar_event = self.pool['calendar.event'] - gs_pool = self.pool['google.service'] url = "/calendar/v3/calendars/%s/events/%s?fields=%s&access_token=%s" % ('primary', google_event['id'], 'id,updated', self.get_token(cr, uid, context)) headers = {'Content-type': 'application/json', 'Accept': 'text/plain'} @@ -315,7 +357,7 @@ class google_calendar(osv.AbstractModel): data['sequence'] = google_event.get('sequence', 0) data_json = simplejson.dumps(data) - content = gs_pool._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context) + status, content = self.pool['google.service']._do_request(cr, uid, url, data_json, headers, type='PATCH', context=context) update_date = datetime.strptime(content['updated'], "%Y-%m-%dT%H:%M:%S.%fz") calendar_event.write(cr, uid, [oe_event.id], {'oe_update_date': update_date}) @@ -324,15 +366,13 @@ class google_calendar(osv.AbstractModel): self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date}, context) def update_an_event(self, cr, uid, event, context=None): - gs_pool = self.pool['google.service'] - data = self.generate_data(cr, uid, event, context=context) url = "/calendar/v3/calendars/%s/events/%s" % ('primary', event.google_internal_event_id) headers = {} data['access_token'] = self.get_token(cr, uid, context) - response = gs_pool._do_request(cr, uid, url, data, headers, type='GET', context=context) + status, response = self.pool['google.service']._do_request(cr, uid, url, data, headers, type='GET', context=context) #TO_CHECK : , if http fail, no event, do DELETE ? return response @@ -379,7 +419,12 @@ class google_calendar(osv.AbstractModel): if self.get_need_synchro_attendee(cr, uid, context=context): attendee_id = res_partner_obj.search(cr, uid, [('email', '=', google_attendee['email'])], context=context) if not attendee_id: - attendee_id = [res_partner_obj.create(cr, uid, {'email': google_attendee['email'], 'customer': False, 'name': google_attendee.get("displayName", False) or google_attendee['email']}, context=context)] + data = { + 'email': google_attendee['email'], + 'customer': False, + 'name': google_attendee.get("displayName", False) or google_attendee['email'] + } + attendee_id = [res_partner_obj.create(cr, uid, data, context=context)] attendee = res_partner_obj.read(cr, uid, attendee_id[0], ['email'], context=context) partner_record.append((4, attendee.get('id'))) attendee['partner_id'] = attendee.pop('id') @@ -388,26 +433,25 @@ class google_calendar(osv.AbstractModel): UTC = pytz.timezone('UTC') if single_event_dict.get('start') and single_event_dict.get('end'): # If not cancelled + if single_event_dict['start'].get('dateTime', False) and single_event_dict['end'].get('dateTime', False): date = parser.parse(single_event_dict['start']['dateTime']) - date_deadline = parser.parse(single_event_dict['end']['dateTime']) - delta = date_deadline.astimezone(UTC) - date.astimezone(UTC) + stop = parser.parse(single_event_dict['end']['dateTime']) date = str(date.astimezone(UTC))[:-6] - date_deadline = str(date_deadline.astimezone(UTC))[:-6] + stop = str(stop.astimezone(UTC))[:-6] allday = False else: - date = (single_event_dict['start']['date'] + ' 00:00:00') - date_deadline = (single_event_dict['end']['date'] + ' 00:00:00') - d_start = datetime.strptime(date, "%Y-%m-%d %H:%M:%S") - d_end = datetime.strptime(date_deadline, "%Y-%m-%d %H:%M:%S") - delta = (d_end - d_start) + date = (single_event_dict['start']['date']) + stop = (single_event_dict['end']['date']) + d_end = datetime.strptime(stop, DEFAULT_SERVER_DATE_FORMAT) allday = True + d_end = d_end + timedelta(days=-1) + stop = d_end.strftime(DEFAULT_SERVER_DATE_FORMAT) - result['duration'] = (delta.seconds / 60) / 60.0 + delta.days * 24 update_date = datetime.strptime(single_event_dict['updated'], "%Y-%m-%dT%H:%M:%S.%fz") result.update({ - 'date': date, - 'date_deadline': date_deadline, + 'start': date, + 'stop': stop, 'allday': allday }) result.update({ @@ -419,7 +463,6 @@ class google_calendar(osv.AbstractModel): 'location': single_event_dict.get('location', False), 'class': single_event_dict.get('visibility', 'public'), 'oe_update_date': update_date, - # 'google_internal_event_id': single_event_dict.get('id',False), }) if single_event_dict.get("recurrence", False): @@ -431,7 +474,6 @@ class google_calendar(osv.AbstractModel): elif type == "copy": result['recurrence'] = True res = calendar_event.write(cr, uid, [event['id']], result, context=context) - elif type == "create": res = calendar_event.create(cr, uid, result, context=context) @@ -439,18 +481,81 @@ class google_calendar(osv.AbstractModel): self.pool['calendar.attendee'].write(cr, uid, [context['curr_attendee']], {'oe_synchro_date': update_date, 'google_internal_event_id': single_event_dict.get('id', False)}, context) return res - def synchronize_events(self, cr, uid, ids, context=None): - # Create all new events from OpenERP into Gmail, if that is not recurrent event - self.create_new_events(cr, uid, context=context) - self.bind_recurring_events_to_google(cr, uid, context) - res = self.update_events(cr, uid, context) + def remove_references(self, cr, uid, context=None): + current_user = self.pool['res.users'].browse(cr, uid, uid, context=context) + reset_data = { + 'google_calendar_rtoken': False, + 'google_calendar_token': False, + 'google_calendar_token_validity': False, + 'google_calendar_last_sync_date': False, + 'google_calendar_cal_id': False, + } + all_my_attendees = self.pool['calendar.attendee'].search(cr, uid, [('partner_id', '=', current_user.partner_id.id)], context=context) + self.pool['calendar.attendee'].write(cr, uid, all_my_attendees, {'oe_synchro_date': False, 'google_internal_event_id': False}, context=context) + current_user.write(reset_data, context=context) + return True + + def synchronize_events(self, cr, uid, ids, lastSync=True, context=None): + if context is None: + context = {} + + # def isValidSync(syncToken): + # gs_pool = self.pool['google.service'] + # params = { + # 'maxResults': 1, + # 'fields': 'id', + # 'access_token': self.get_token(cr, uid, context), + # 'syncToken': syncToken, + # } + # url = "/calendar/v3/calendars/primary/events" + # status, response = gs_pool._do_request(cr, uid, url, params, type='GET', context=context) + # return int(status) != 410 + + current_user = self.pool['res.users'].browse(cr, uid, uid, context=context) + + context_with_time = dict(context.copy(), ask_time=True) + current_google = self.get_calendar_primary_id(cr, uid, context=context_with_time) + + if current_user.google_calendar_cal_id: + if current_google != current_user.google_calendar_cal_id: + return { + "status": "need_reset", + "info": { + "old_name": current_user.google_calendar_cal_id, + "new_name": current_google + }, + "url": '' + } + + if lastSync and self.get_last_sync_date(cr, uid, context=context) and not self.get_disable_since_synchro(cr, uid, context=context): + lastSync = self.get_last_sync_date(cr, uid, context) + _logger.info("Calendar Synchro - MODE SINCE_MODIFIED : %s !" % lastSync.strftime(DEFAULT_SERVER_DATETIME_FORMAT)) + else: + lastSync = False + _logger.info("Calendar Synchro - MODE FULL SYNCHRO FORCED") + else: + current_user.write({'google_calendar_cal_id': current_google}, context=context) + lastSync = False + _logger.info("Calendar Synchro - MODE FULL SYNCHRO - NEW CAL ID") + + new_ids = [] + new_ids += self.create_new_events(cr, uid, context=context) + new_ids += self.bind_recurring_events_to_google(cr, uid, context) + + res = self.update_events(cr, uid, lastSync, context) + + current_user.write({'google_calendar_last_sync_date': context_with_time.get('ask_time')}, context=context) return { "status": res and "need_refresh" or "no_new_event_form_google", "url": '' } def create_new_events(self, cr, uid, context=None): + if context is None: + context = {} + + new_ids = [] ev_obj = self.pool['calendar.event'] att_obj = self.pool['calendar.attendee'] user_obj = self.pool['res.users'] @@ -458,32 +563,43 @@ class google_calendar(osv.AbstractModel): context_norecurrent = context.copy() context_norecurrent['virtual_id'] = False - my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('google_internal_event_id', '=', False), '|', - ('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")), - ('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")), + ('event_id.stop', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)), + ('event_id.final_date', '>', self.get_minTime(cr, uid, context=context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)), ], context=context_norecurrent) - for att in att_obj.browse(cr, uid, my_att_ids, context=context): if not att.event_id.recurrent_id or att.event_id.recurrent_id == 0: - response = self.create_an_event(cr, uid, att.event_id, context=context) - update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz") - ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date}) - att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date}) - cr.commit() + st, response = self.create_an_event(cr, uid, att.event_id, context=context) + if status_response(st): + update_date = datetime.strptime(response['updated'], "%Y-%m-%dT%H:%M:%S.%fz") + ev_obj.write(cr, uid, att.event_id.id, {'oe_update_date': update_date}) + new_ids.append(response['id']) + att_obj.write(cr, uid, [att.id], {'google_internal_event_id': response['id'], 'oe_synchro_date': update_date}) + cr.commit() + else: + _logger.warning("Impossible to create event %s. [%s]" % (att.event_id.id, st)) + _logger.warning("Response : %s" % response) + return new_ids - def bind_recurring_events_to_google(self, cr, uid, context): + def get_context_no_virtual(self, context): + context_norecurrent = context.copy() + context_norecurrent['virtual_id'] = False + context_norecurrent['active_test'] = False + return context_norecurrent + + def bind_recurring_events_to_google(self, cr, uid, context=None): + if context is None: + context = {} + + new_ids = [] ev_obj = self.pool['calendar.event'] att_obj = self.pool['calendar.attendee'] user_obj = self.pool['res.users'] myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id - context_norecurrent = context.copy() - context_norecurrent['virtual_id'] = False - context_norecurrent['active_test'] = False - + context_norecurrent = self.get_context_no_virtual(context) my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), ('google_internal_event_id', '=', False)], context=context_norecurrent) for att in att_obj.browse(cr, uid, my_att_ids, context=context): @@ -500,11 +616,20 @@ class google_calendar(osv.AbstractModel): if new_google_internal_event_id: #TODO WARNING, NEED TO CHECK THAT EVENT and ALL instance NOT DELETE IN GMAIL BEFORE ! - self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context) - att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context) - cr.commit() + try: + st, response = self.update_recurrent_event_exclu(cr, uid, new_google_internal_event_id, source_attendee_record.google_internal_event_id, att.event_id, context=context) + if status_response(st): + att_obj.write(cr, uid, [att.id], {'google_internal_event_id': new_google_internal_event_id}, context=context) + new_ids.append(new_google_internal_event_id) + cr.commit() + else: + _logger.warning("Impossible to create event %s. [%s]" % (att.event_id.id, st)) + _logger.warning("Response : %s" % response) + except: + pass + return new_ids - def update_events(self, cr, uid, context=None): + def update_events(self, cr, uid, lastSync=False, context=None): if context is None: context = {} @@ -512,20 +637,64 @@ class google_calendar(osv.AbstractModel): user_obj = self.pool['res.users'] att_obj = self.pool['calendar.attendee'] myPartnerID = user_obj.browse(cr, uid, uid, context=context).partner_id.id + context_novirtual = self.get_context_no_virtual(context) - context_novirtual = context.copy() - context_novirtual['virtual_id'] = False - context_novirtual['active_test'] = False + if lastSync: + try: + all_event_from_google = self.get_event_synchro_dict(cr, uid, lastSync=lastSync, context=context) + except urllib2.HTTPError, e: + if e.code == 410: # GONE, Google is lost. + # we need to force the rollback from this cursor, because it locks my res_users but I need to write in this tuple before to raise. + cr.rollback() + registry = openerp.modules.registry.RegistryManager.get(request.session.db) + with registry.cursor() as cur: + self.pool['res.users'].write(cur, uid, [uid], {'google_calendar_last_sync_date': False}, context=context) + error_key = simplejson.loads(e.read()) + error_key = error_key.get('error', {}).get('message', 'nc') + error_msg = "Google are lost... the next synchro will be a full synchro. \n\n %s" % error_key + raise self.pool.get('res.config.settings').get_config_warning(cr, _(error_msg), context=context) - all_event_from_google = self.get_event_dict(cr, uid, context=context) + my_google_att_ids = att_obj.search(cr, uid, [ + ('partner_id', '=', myPartnerID), + ('google_internal_event_id', 'in', all_event_from_google.keys()) + ], context=context_novirtual) + + my_openerp_att_ids = att_obj.search(cr, uid, [ + ('partner_id', '=', myPartnerID), + ('event_id.oe_update_date', '>', lastSync and lastSync.strftime(DEFAULT_SERVER_DATETIME_FORMAT) or self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)), + ('google_internal_event_id', '!=', False), + ], context=context_novirtual) + + my_openerp_googleinternal_ids = att_obj.read(cr, uid, my_openerp_att_ids, ['google_internal_event_id', 'event_id'], context=context_novirtual) + + if self.get_print_log(cr, uid, context=context): + _logger.info("Calendar Synchro - \n\nUPDATE IN GOOGLE\n%s\n\nRETRIEVE FROM OE\n%s\n\nUPDATE IN OE\n%s\n\nRETRIEVE FROM GG\n%s\n\n" % (all_event_from_google, my_google_att_ids, my_openerp_att_ids, my_openerp_googleinternal_ids)) + + for giid in my_openerp_googleinternal_ids: + active = True # if not sure, we request google + if giid.get('event_id'): + active = calendar_event.browse(cr, uid, int(giid.get('event_id')[0]), context=context_novirtual).active + + if giid.get('google_internal_event_id') and not all_event_from_google.get(giid.get('google_internal_event_id')) and active: + one_event = self.get_one_event_synchro(cr, uid, giid.get('google_internal_event_id'), context=context) + if one_event: + all_event_from_google[one_event['id']] = one_event + + my_att_ids = list(set(my_google_att_ids + my_openerp_att_ids)) + + else: + domain = [ + ('partner_id', '=', myPartnerID), + ('google_internal_event_id', '!=', False), + '|', + ('event_id.stop', '>', self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)), + ('event_id.final_date', '>', self.get_minTime(cr, uid, context).strftime(DEFAULT_SERVER_DATETIME_FORMAT)), + ] + + # Select all events from OpenERP which have been already synchronized in gmail + my_att_ids = att_obj.search(cr, uid, domain, context=context_novirtual) + all_event_from_google = self.get_event_synchro_dict(cr, uid, lastSync=False, context=context) - # Select all events from OpenERP which have been already synchronized in gmail - my_att_ids = att_obj.search(cr, uid, [('partner_id', '=', myPartnerID), - ('google_internal_event_id', '!=', False), - '|', - ('event_id.date_deadline', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")), - ('event_id.end_date', '>', self.get_start_time_to_synchro(cr, uid, context).strftime("%Y-%m-%d %H:%M:%S")), - ], context=context_novirtual) event_to_synchronize = {} for att in att_obj.browse(cr, uid, my_att_ids, context=context): event = att.event_id @@ -576,8 +745,10 @@ class google_calendar(osv.AbstractModel): ###################### for base_event in event_to_synchronize: for current_event in event_to_synchronize[base_event]: - event_to_synchronize[base_event][current_event].compute_OP() - #print event_to_synchronize[base_event] + event_to_synchronize[base_event][current_event].compute_OP(modeFull=not lastSync) + if self.get_print_log(cr, uid, context=context): + if not isinstance(event_to_synchronize[base_event][current_event].OP, NothingToDo): + _logger.info(event_to_synchronize[base_event]) ###################### # DO ACTION # @@ -615,24 +786,35 @@ class google_calendar(osv.AbstractModel): if actSrc == 'OE': self.delete_an_event(cr, uid, current_event[0], context=context) elif actSrc == 'GG': - new_google_event_id = event.GG.event['id'].split('_')[1] - if 'T' in new_google_event_id: - new_google_event_id = new_google_event_id.replace('T', '')[:-1] - else: - new_google_event_id = new_google_event_id + "000000" + new_google_event_id = event.GG.event['id'].split('_')[1] + if 'T' in new_google_event_id: + new_google_event_id = new_google_event_id.replace('T', '')[:-1] + else: + new_google_event_id = new_google_event_id + "000000" - if event.GG.status: - parent_event = {} - parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id) - res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context) - else: - if event_to_synchronize[base_event][0][1].OE.event_id: - parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id - calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), can_be_deleted=True, context=context) + if event.GG.status: + parent_event = {} + if not event_to_synchronize[base_event][0][1].OE.event_id: + event_to_synchronize[base_event][0][1].OE.event_id = att_obj.search_read(cr, uid, [('google_internal_event_id', '=', event.GG.event['id'].split('_')[0])], ['event_id'], context=context_novirtual)[0].get('event_id')[0] + + parent_event['id'] = "%s-%s" % (event_to_synchronize[base_event][0][1].OE.event_id, new_google_event_id) + res = self.update_from_google(cr, uid, parent_event, event.GG.event, "copy", context) + else: + parent_oe_id = event_to_synchronize[base_event][0][1].OE.event_id + calendar_event.unlink(cr, uid, "%s-%s" % (parent_oe_id, new_google_event_id), can_be_deleted=True, context=context) elif isinstance(actToDo, Delete): if actSrc == 'GG': - self.delete_an_event(cr, uid, current_event[0], context=context) + try: + self.delete_an_event(cr, uid, current_event[0], context=context) + except Exception, e: + error = simplejson.loads(e.read()) + error_nr = error.get('error', {}).get('code') + # if already deleted from gmail or never created + if error_nr in (404, 410,): + pass + else: + raise e elif actSrc == 'OE': calendar_event.unlink(cr, uid, event.OE.event_id, can_be_deleted=False, context=context) return True @@ -655,7 +837,7 @@ class google_calendar(osv.AbstractModel): url = "/calendar/v3/calendars/%s/events/%s" % ('primary', instance_id) - content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context) + st, content = gs_pool._do_request(cr, uid, url, params, headers, type='GET', context=context) return content.get('sequence', 0) ################################# ## MANAGE CONNEXION TO GMAIL ## @@ -663,13 +845,16 @@ class google_calendar(osv.AbstractModel): def get_token(self, cr, uid, context=None): current_user = self.pool['res.users'].browse(cr, uid, uid, context=context) - - if datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], "%Y-%m-%d %H:%M:%S") < (datetime.now() + timedelta(minutes=1)): + if not current_user.google_calendar_token_validity or \ + datetime.strptime(current_user.google_calendar_token_validity.split('.')[0], DEFAULT_SERVER_DATETIME_FORMAT) < (datetime.now() + timedelta(minutes=1)): self.do_refresh_token(cr, uid, context=context) current_user.refresh() - return current_user.google_calendar_token + def get_last_sync_date(self, cr, uid, context=None): + current_user = self.pool['res.users'].browse(cr, uid, uid, context=context) + return current_user.google_calendar_last_sync_date and datetime.strptime(current_user.google_calendar_last_sync_date, DEFAULT_SERVER_DATETIME_FORMAT) + timedelta(minutes=0) or False + def do_refresh_token(self, cr, uid, context=None): current_user = self.pool['res.users'].browse(cr, uid, uid, context=context) gs_pool = self.pool['google.service'] @@ -707,14 +892,18 @@ class google_calendar(osv.AbstractModel): vals['google_%s_token' % self.STR_SERVICE] = all_token.get('access_token') self.pool['res.users'].write(cr, SUPERUSER_ID, uid, vals, context=context) - def get_start_time_to_synchro(self, cr, uid, context=None): - # WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4 - number_of_week = 13 + def get_minTime(self, cr, uid, context=None): + number_of_week = self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.week_synchro', default=13) return datetime.now() - timedelta(weeks=number_of_week) def get_need_synchro_attendee(self, cr, uid, context=None): - # WILL BE AN IR CONFIG PARAMETER - beginning from SAAS4 - return True + return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_synchro_attendee', default=True) + + def get_disable_since_synchro(self, cr, uid, context=None): + return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.block_since_synchro', default=False) + + def get_print_log(self, cr, uid, context=None): + return self.pool['ir.config_parameter'].get_param(cr, uid, 'calendar.debug_print', default=False) class res_users(osv.Model): @@ -724,16 +913,22 @@ class res_users(osv.Model): 'google_calendar_rtoken': fields.char('Refresh Token'), 'google_calendar_token': fields.char('User token'), 'google_calendar_token_validity': fields.datetime('Token Validity'), + 'google_calendar_last_sync_date': fields.datetime('Last synchro date'), + 'google_calendar_cal_id': fields.char('Calendar ID', help='Last Calendar ID who has been synchronized. If it is changed, we remove \ +all links between GoogleID and OpenERP Google Internal ID') } class calendar_event(osv.Model): _inherit = "calendar.event" + def get_fields_need_update_google(self, cr, uid, context=None): + return ['name', 'description', 'allday', 'date', 'date_end', 'stop', 'attendee_ids', 'location', 'class', 'active'] + def write(self, cr, uid, ids, vals, context=None): if context is None: context = {} - sync_fields = set(['name', 'description', 'date', 'date_closed', 'date_deadline', 'attendee_ids', 'location', 'class']) + sync_fields = set(self.get_fields_need_update_google(cr, uid, context)) if (set(vals.keys()) & sync_fields) and 'oe_update_date' not in vals.keys() and 'NewMeeting' not in context: vals['oe_update_date'] = datetime.now() @@ -762,7 +957,7 @@ class calendar_attendee(osv.Model): _inherit = 'calendar.attendee' _columns = { - 'google_internal_event_id': fields.char('Google Calendar Event Id', size=256), + 'google_internal_event_id': fields.char('Google Calendar Event Id'), 'oe_synchro_date': fields.datetime('OpenERP Synchro Date'), } _sql_constraints = [('google_id_uniq', 'unique(google_internal_event_id,partner_id,event_id)', 'Google ID should be unique!')] diff --git a/addons/google_calendar/static/src/js/calendar_sync.js b/addons/google_calendar/static/src/js/calendar_sync.js index fdc4aeffb77..fc05ca05ab0 100644 --- a/addons/google_calendar/static/src/js/calendar_sync.js +++ b/addons/google_calendar/static/src/js/calendar_sync.js @@ -11,38 +11,54 @@ openerp.google_calendar = function(instance) { }); return this._super(r); }, - sync_calendar: function(res,button) { + sync_calendar: function(res, button) { var self = this; var context = instance.web.pyeval.eval('context'); //$('div.oe_cal_sync_button').hide(); - $('div.oe_cal_sync_button').prop('disabled',true); - + $('div.oe_cal_sync_button').prop('disabled', true); + self.rpc('/google_calendar/sync_data', { arch: res.arch, fields: res.fields, - model:res.model, + model: res.model, fromurl: window.location.href, - local_context:context + local_context: context }).done(function(o) { - if (o.status == "need_auth") { + if (o.status === "need_auth") { alert(_t("You will be redirected on gmail to authorize your OpenErp to access your calendar !")); instance.web.redirect(o.url); } - else if (o.status == "need_config_from_admin") { - - if (!_.isUndefined(o.action) && parseInt(o.action)) { - if (confirm(_t("An admin need to configure Google Synchronization before to use it, do you want to configure it now ? !"))) { - self.do_action(o.action); + else if (o.status === "need_config_from_admin"){ + if (!_.isUndefined(o.action) && parseInt(o.action)){ + if (confirm(_t("An admin need to configure Google Synchronization before to use it, do you want to configure it now ? !"))){ + self.do_action(o.action); } } - else { + else{ alert(_t("An admin need to configure Google Synchronization before to use it !")); } } - else if (o.status == "need_refresh"){ + else if (o.status === "need_refresh"){ self.$calendar.fullCalendar('refetchEvents'); } + else if (o.status === "need_reset"){ + if (confirm(_t("The account that you are trying to synchronize (" + o.info.new_name + "), is not the same that the last one used \ +(" + o.info.old_name + "! )" + "\r\n\r\nDo you want remove all references from the old account ?"))){ + self.rpc('/google_calendar/remove_references', { + model:res.model, + local_context:context + }).done(function(o) { + if (o.status === "OK") { + alert(_t("All old references have been deleted. You can now restart the synchronization")); + } + else if (o.status === "KO") { + alert(_t("An error has occured when we was removing all old references. Please retry or contact your administrator.")); + } + //else NOP + }); + } + } }).always(function(o) { $('div.oe_cal_sync_button').prop('disabled',false); }); } }); diff --git a/addons/google_calendar/views/res_users.xml b/addons/google_calendar/views/res_users.xml new file mode 100644 index 00000000000..8adc3429090 --- /dev/null +++ b/addons/google_calendar/views/res_users.xml @@ -0,0 +1,24 @@ + + + + + res.users.form + res.users + + + + + + + + + + + + + + + + + + From a5311e690000d371e886a30ea1557d24ee0186b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thibault=20Delavall=C3=A9e?= Date: Wed, 14 May 2014 14:30:26 +0200 Subject: [PATCH 51/51] [MOV] crm: moved css / js related to salesteam to sales_team module; sale_crm: moved data and demo data into sale and sales_team module. --- addons/crm/__openerp__.py | 1 - addons/crm/static/src/css/Makefile | 2 - addons/crm/static/src/css/crm.css | 62 --------- addons/crm/views/crm.xml | 13 -- addons/sale/sale_data.xml | 18 ++- addons/sale/sale_demo.xml | 103 ++++++++++++++ addons/sale_crm/__openerp__.py | 3 +- addons/sale_crm/sale_crm_data.xml | 22 --- addons/sale_crm/sale_crm_demo.xml | 130 ------------------ addons/sales_team/sales_team.xml | 12 +- addons/sales_team/static/src/css/Makefile | 2 + .../static/src/css/sales_team.sass} | 0 .../static/src/js/sales_team.js} | 0 13 files changed, 134 insertions(+), 234 deletions(-) delete mode 100644 addons/crm/static/src/css/Makefile delete mode 100644 addons/crm/static/src/css/crm.css delete mode 100644 addons/crm/views/crm.xml delete mode 100644 addons/sale_crm/sale_crm_data.xml delete mode 100644 addons/sale_crm/sale_crm_demo.xml create mode 100644 addons/sales_team/static/src/css/Makefile rename addons/{crm/static/src/css/crm.sass => sales_team/static/src/css/sales_team.sass} (100%) rename addons/{crm/static/src/js/crm_case_section.js => sales_team/static/src/js/sales_team.js} (100%) diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py index 1bf4c611be6..15544dc3dff 100644 --- a/addons/crm/__openerp__.py +++ b/addons/crm/__openerp__.py @@ -92,7 +92,6 @@ Dashboard for CRM will include: 'base_partner_merge_view.xml', 'sales_team_view.xml', - 'views/crm.xml', ], 'demo': [ 'crm_demo.xml', diff --git a/addons/crm/static/src/css/Makefile b/addons/crm/static/src/css/Makefile deleted file mode 100644 index a2ced24a13d..00000000000 --- a/addons/crm/static/src/css/Makefile +++ /dev/null @@ -1,2 +0,0 @@ -crm.css: crm.sass - sass --trace -t expanded crm.sass:crm.css diff --git a/addons/crm/static/src/css/crm.css b/addons/crm/static/src/css/crm.css deleted file mode 100644 index 8999f758e41..00000000000 --- a/addons/crm/static/src/css/crm.css +++ /dev/null @@ -1,62 +0,0 @@ -@charset "utf-8"; -.openerp .oe_kanban_view .oe_kanban_crm_salesteams { - /* Customize width and height of kanban according bootstrap3 */ - width: 357px; - min-height: 254px !important; - /* End of customize */ - cursor: default; -} -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list { - position: relative; - /* Improved margin to set alignment of list items according bootstrap3 */ - margin: 10px 0 10px 9px; - min-height: 10px; -} -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div { - width: 160px; - height: 22px; - margin: 0 !important; - position: relative; - display: inline-block; - float: left; -} -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div a:nth-child(2n) { - position: absolute; - left: 90px; - top: 0; -} -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list div:nth-child(2n) a:nth-child(2n) { - left: 110px; -} -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_items_list a:hover { - text-decoration: underline !important; -} -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center { - text-align: center; - margin: 3px 0; -} -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center .oe_sum { - margin: 0; - font-size: 40px; -} -.openerp .oe_kanban_view .oe_kanban_crm_salesteams .oe_center .oe_subsum { - font-size: 10px; -} -.openerp .oe_kanban_view .oe_salesteams_help { - display: inline-block; -} -.openerp .oe_kanban_view .oe_kanban_salesteams_avatars { - margin-top: 20px; -} -.openerp .oe_kanban_view .oe_kanban_salesteams_avatars img { - width: 30px; - height: 30px; - padding-left: 0px; - margin-top: 3px; - -moz-border-radius: 2px; - -webkit-border-radius: 2px; - border-radius: 2px; - -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); - -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); - -box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); -} diff --git a/addons/crm/views/crm.xml b/addons/crm/views/crm.xml deleted file mode 100644 index c28ba723d24..00000000000 --- a/addons/crm/views/crm.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - diff --git a/addons/sale/sale_data.xml b/addons/sale/sale_data.xml index 443895631a9..4c5347cf1d0 100644 --- a/addons/sale/sale_data.xml +++ b/addons/sale/sale_data.xml @@ -29,7 +29,7 @@ This application lets you create and send quotations and process your sales orders; from delivery to invoicing.

If you need to manage your sales pipeline (leads, opportunities, phonecalls), the CRM application may be useful. Use the Settings menu to install it.

]]>
- + Quotation sent @@ -44,5 +44,21 @@ Quotation confirmed + + + Quotation Send + 20 + crm.case.section + + section_id + + + Sales Order Confirmed + 21 + crm.case.section + + section_id + + diff --git a/addons/sale/sale_demo.xml b/addons/sale/sale_demo.xml index 2e0019598f7..bcb69f9a7a3 100644 --- a/addons/sale/sale_demo.xml +++ b/addons/sale/sale_demo.xml @@ -2,12 +2,24 @@ + + 52700 + 60000 + + + + 36000 + 40000 + + + + @@ -47,6 +59,8 @@ manual + + @@ -76,6 +90,7 @@ manual + @@ -104,6 +119,7 @@ + @@ -152,6 +168,8 @@ + + @@ -190,6 +208,7 @@ + @@ -209,6 +228,8 @@ manual + + @@ -259,6 +280,7 @@ manual + @@ -335,5 +357,86 @@ Thanks!
+ + + + + + + + draft + in_invoice + + Test invoice 1 + + + Basic computer with Dvorak keyboard and left-handed mouse + + 250 + 1 + + + + Little server with raid 1 and 512ECC ram + + 800 + 2 + + + + Server with raid 10 and 2048ECC ram + + 4800 + 4 + + + + + + + + + + + + + + + + + + + + + + + + + draft + out_invoice + + Test invoice 1 + + + Basic formation with Dvorak + + 500 + 1 + + + + + + + + + + + + + + + + diff --git a/addons/sale_crm/__openerp__.py b/addons/sale_crm/__openerp__.py index 37059458acd..251c4746a86 100644 --- a/addons/sale_crm/__openerp__.py +++ b/addons/sale_crm/__openerp__.py @@ -41,11 +41,10 @@ modules. 'data': [ 'wizard/crm_make_sale_view.xml', 'sale_crm_view.xml', - 'sale_crm_data.xml', 'security/sale_crm_security.xml', 'security/ir.model.access.csv', ], - 'demo': ['sale_crm_demo.xml'], + 'demo': [], 'test': ['test/sale_crm.yml'], 'installable': True, 'auto_install': True, diff --git a/addons/sale_crm/sale_crm_data.xml b/addons/sale_crm/sale_crm_data.xml deleted file mode 100644 index 1996f367202..00000000000 --- a/addons/sale_crm/sale_crm_data.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - Quotation Send - 20 - crm.case.section - - section_id - - - Sales Order Confirmed - 21 - crm.case.section - - section_id - - - - diff --git a/addons/sale_crm/sale_crm_demo.xml b/addons/sale_crm/sale_crm_demo.xml deleted file mode 100644 index 4af287b0587..00000000000 --- a/addons/sale_crm/sale_crm_demo.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - 52700 - 60000 - - - - Indirect Sales - IM - 36000 - 40000 - - - - - - - - - - - draft - in_invoice - - Test invoice 1 - - - Basic computer with Dvorak keyboard and left-handed mouse - - 250 - 1 - - - - Little server with raid 1 and 512ECC ram - - 800 - 2 - - - - Server with raid 10 and 2048ECC ram - - 4800 - 4 - - - - - - - - - - - - - - - - - - - - - - - - - draft - out_invoice - - Test invoice 1 - - - Basic formation with Dvorak - - 500 - 1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/addons/sales_team/sales_team.xml b/addons/sales_team/sales_team.xml index 95fcb6f3078..67907c38c7e 100644 --- a/addons/sales_team/sales_team.xml +++ b/addons/sales_team/sales_team.xml @@ -195,6 +195,16 @@

- + + + + + + diff --git a/addons/sales_team/static/src/css/Makefile b/addons/sales_team/static/src/css/Makefile new file mode 100644 index 00000000000..523f4a1e81e --- /dev/null +++ b/addons/sales_team/static/src/css/Makefile @@ -0,0 +1,2 @@ +sales_team.css: sales_team.sass + sass --trace -t expanded sales_team.sass:sales_team.css diff --git a/addons/crm/static/src/css/crm.sass b/addons/sales_team/static/src/css/sales_team.sass similarity index 100% rename from addons/crm/static/src/css/crm.sass rename to addons/sales_team/static/src/css/sales_team.sass diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/sales_team/static/src/js/sales_team.js similarity index 100% rename from addons/crm/static/src/js/crm_case_section.js rename to addons/sales_team/static/src/js/sales_team.js