[MERGE]: Merged with addons

This commit is contained in:
Atul Patel (OpenERP)
2014-05-15 18:48:15 +05:30
262 changed files with 10054 additions and 9458 deletions
@@ -542,18 +542,17 @@ class account_analytic_account(osv.osv):
'nodestroy': True,
}
def on_change_template(self, cr, uid, ids, template_id, date_start=False, fix_price_invoices=False, invoice_on_timesheets=False, recurring_invoices=False, context=None):
def on_change_template(self, cr, uid, ids, template_id, date_start=False, context=None):
if not template_id:
return {}
obj_analytic_line = self.pool.get('account.analytic.invoice.line')
res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, date_start=date_start, context=context)
template = self.browse(cr, uid, template_id, context=context)
if not fix_price_invoices:
if not ids:
res['value']['fix_price_invoices'] = template.fix_price_invoices
res['value']['amount_max'] = template.amount_max
if not invoice_on_timesheets:
if not ids:
res['value']['invoice_on_timesheets'] = template.invoice_on_timesheets
res['value']['hours_qtt_est'] = template.hours_qtt_est
@@ -561,7 +560,7 @@ class account_analytic_account(osv.osv):
res['value']['to_invoice'] = template.to_invoice.id
if template.pricelist_id.id:
res['value']['pricelist_id'] = template.pricelist_id.id
if not recurring_invoices:
if not ids:
invoice_line_ids = []
for x in template.recurring_invoice_line_ids:
invoice_line_ids.append((0, 0, {
@@ -38,9 +38,6 @@
<field name="partner_id" position="attributes">
<attribute name="attrs">{'required': [('type','=','contract'),'|','|',('fix_price_invoices','=',True), ('invoice_on_timesheets', '=', True), ('recurring_invoices', '=', True)]}</attribute>
</field>
<field name="template_id" position="attributes">
<attribute name="on_change">on_change_template(template_id, date_start, fix_price_invoices, invoice_on_timesheets, recurring_invoices)</attribute>
</field>
<xpath expr='//group[@name="invoice_on_timesheets"]' position="replace">
</xpath>
<xpath expr='//separator[@name="description"]' position='before'>
+241
View File
@@ -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 <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <claudia.haida@initos.com>\n"
"Language-Team: German <de@li.org>\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 ""
"<p class=\"oe_view_nocontent_create\">\n"
" Click to create Accounting Test.\n"
" </p>\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 ""
+4 -4
View File
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-19 14:58+0000\n"
"Last-Translator: Ferdinand <Unknown>\n"
"PO-Revision-Date: 2014-05-14 11:25+0000\n"
"Last-Translator: Claudia Haida <claudia.haida@initos.com>\n"
"Language-Team: German <de@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:50+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-15 05:51+0000\n"
"X-Generator: Launchpad (build 17007)\n"
#. module: auth_signup
#: field:res.partner,signup_type:0
+4 -1
View File
@@ -363,7 +363,10 @@ openerp.base_import = function (instance) {
var fields = this.$('.oe_import_fields input.oe_import_match_field').map(function (index, el) {
return $(el).select2('val') || false;
}).get();
kwargs.context = this.parent_context;
kwargs.context = _.extend(
{}, this.parent_context,
{tracking_disable: !this.$('#oe_import_tracking').prop('checked')}
);
return this.Import.call('do', [this.id, fields, this.import_options()], kwargs)
.then(undefined, function (error, event) {
// In case of unexpected exception, convert
@@ -53,6 +53,15 @@
<div class="oe_import_with_file oe_padding">
<h2>Map your data to OpenERP</h2>
<div title="If the model uses openchatter, history tracking
will set up subscriptions and send notifications
during the import, but lead to a slower import.">
<input type="checkbox" id="oe_import_tracking"
checked="checked"/>
<label for="oe_import_tracking">
Track history during import
</label>
</div>
<input type="checkbox" class="oe_import_has_header"
id="oe_import_has_header" checked="checked"/>
<label for="oe_import_has_header">The first row of the
+16 -33
View File
@@ -1,23 +1,4 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2011-2014 OpenERP S.A. <http://openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
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:
@@ -16,7 +16,7 @@
<field name="arch" type="xml">
<xpath expr="/form/sheet/h1" position="before">
<div class="oe_right oe_button_box">
<button class="oe_inline oe_stat_button" type="action" attrs="{'invisible': [('picking_type_id.code', '!=', 'outgoing')]}"
<button class="oe_inline oe_stat_button" type="action" attrs="{'invisible': [('picking_type_code', '!=', 'outgoing')]}"
name="%(action_claim_from_delivery)d" icon="fa-comments" >
<field string="Claims" name="claim_count_out" widget="statinfo"/>
</button>
+1
View File
@@ -22,6 +22,7 @@
import crm
import crm_segmentation
import crm_lead
import sales_team
import calendar_event
import crm_phonecall
import report
+2 -3
View File
@@ -51,13 +51,13 @@ Dashboard for CRM will include:
'depends': [
'base_action_rule',
'base_setup',
'sales_team',
'mail',
'email_template',
'calendar',
'resource',
'board',
'fetchmail',
'web_kanban_sparkline',
],
'data': [
'crm_data.xml',
@@ -91,8 +91,7 @@ Dashboard for CRM will include:
'res_config_view.xml',
'base_partner_merge_view.xml',
'crm_case_section_view.xml',
'views/crm.xml',
'sales_team_view.xml',
],
'demo': [
'crm_demo.xml',
-148
View File
@@ -88,154 +88,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 = "mail.thread"
_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
monthly_open_leads: number of open lead during the last months
monthly_planned_revenue: planned revenu of opportunities during the last months
"""
obj = self.pool.get('crm.lead')
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
date_begin = month_begin - relativedelta.relativedelta(months=self._period_number - 1)
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1])
lead_pre_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('type', '=', 'lead')]
opp_pre_domain = [('date_deadline', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('date_deadline', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('type', '=', 'opportunity')]
for id in ids:
res[id] = dict()
lead_domain = lead_pre_domain + [('section_id', '=', id)]
opp_domain = opp_pre_domain + [('section_id', '=', id)]
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
_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."),
'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')
}
def _get_stage_common(self, cr, uid, context):
ids = self.pool.get('crm.case.stage').search(cr, uid, [('case_default', '=', 1)], context=context)
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"
+1 -1
View File
@@ -41,7 +41,7 @@
<field name="condition">True</field>
<field name="type">ir.actions.server</field>
<field name="state">code</field>
<field name="code">sales_team = self.pool.get('ir.model.data').get_object(cr, uid, 'crm', 'section_sales_department')
<field name="code">sales_team = self.pool.get('ir.model.data').get_object(cr, uid, 'sales_team', 'section_sales_department')
object.write({'section_id': sales_team.id})
</field>
</record>
-331
View File
@@ -1,331 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- CRM lead search by Salesteams -->
<record model="ir.actions.act_window" id="crm_case_form_view_salesteams_lead">
<field name="name">Leads</field>
<field name="res_model">crm.lead</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('type','=','lead'), ('type','=',False)]</field>
<field name="view_id" ref="crm_case_tree_view_leads"/>
<field name="search_view_id" ref="crm.view_crm_case_leads_filter"/>
<field name="context">{
'search_default_section_id': [active_id],
'default_section_id': active_id,
'default_type': 'lead',
'stage_type': 'lead',
}
</field>
<field name="help" type="html">
<p>
Use leads if you need a qualification step before creating an
opportunity or a customer. It can be a business card you received,
a contact form filled in your website, or a file of unqualified
prospects you import, etc.
</p><p>
Once qualified, the lead can be converted into a business
opportunity and/or a new customer in your address book.
</p>
</field>
</record>
<!-- CRM opportunity search by Salesteams -->
<record model="ir.actions.act_window" id="crm_case_form_view_salesteams_opportunity">
<field name="name">Opportunities</field>
<field name="res_model">crm.lead</field>
<field name="view_mode">kanban,tree,graph,form,calendar</field>
<field name="domain">[('type','=','opportunity')]</field>
<field name="view_id" ref="crm.crm_case_kanban_view_leads"/>
<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
<field name="context">{
'search_default_section_id': [active_id],
'default_section_id': active_id,
'stage_type': 'opportunity',
'default_type': 'opportunity',
'default_user_id': uid,
}
</field>
<field name="help" type="html">
<p>
OpenERP helps you keep track of your sales pipeline to follow
up potential sales and better forecast your future revenues.
</p><p>
You will be able to plan meetings and phone calls from
opportunities, convert them into quotations, attach related
documents, track all discussions, and much more.
</p>
</field>
</record>
<record id="action_report_crm_lead_salesteam" model="ir.actions.act_window">
<field name="name">Leads Analysis</field>
<field name="res_model">crm.lead.report</field>
<field name="context">{"search_default_month":1}</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="crm.view_report_crm_lead_graph_two"/>
<field name="domain">[('type','=', 'lead'),('section_id', '=', active_id)]</field>
<field name="help">Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis.</field>
</record>
<record id="action_report_crm_opportunity_salesteam" model="ir.actions.act_window">
<field name="name">Opportunities Analysis</field>
<field name="res_model">crm.lead.report</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="crm.view_report_crm_opportunity_graph"/>
<field name="domain">[('type','=', 'opportunity'), ('section_id', '=', active_id)]</field>
<field name="help">Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline.</field>
</record>
<!-- Case Sections Salesteams kanban view -->
<record model="ir.ui.view" id="crm_case_section_salesteams_view_kanban">
<field name="name">crm.case.section.kanban</field>
<field name="model">crm.case.section</field>
<field name="arch" type="xml">
<kanban version="7.0" class="oe_background_grey">
<field name="use_leads"/>
<field name="name"/>
<field name="user_id"/>
<field name="member_ids"/>
<field name="note"/>
<field name="alias_id"/>
<field name="color"/>
<field name="monthly_open_leads"/>
<field name="monthly_planned_revenue"/>
<templates>
<t t-name="kanban-box">
<div t-attf-class="oe_kanban_color_#{kanban_getcolor(record.color.raw_value)} oe_kanban_card oe_kanban_global_click oe_kanban_crm_salesteams">
<div class="oe_dropdown_toggle oe_dropdown_kanban" groups="base.group_sale_manager">
<span class="oe_e">í</span>
<ul class="oe_dropdown_menu">
<li t-if="widget.view.is_action_enabled('edit')"><a type="edit">Sales Teams Settings</a></li>
<li t-if="widget.view.is_action_enabled('delete')"><a type="delete">Delete</a></li>
<li t-if="widget.view.is_action_enabled('edit')"><ul class="oe_kanban_colorpicker" data-field="color"/></li>
</ul>
</div>
<div class="oe_kanban_content">
<h4 class="oe_center"><field name="name"/></h4>
<div class="oe_kanban_alias oe_center" t-if="record.use_leads.raw_value and record.alias_id.value">
<small><span class="oe_e oe_e_alias" style="float: none;">%%</span><t t-raw="record.alias_id.raw_value[1]"/></small>
</div>
<div class="oe_items_list">
<div class="oe_salesteams_leads" t-if="record.use_leads.raw_value">
<a name="%(crm_case_form_view_salesteams_lead)d" type="action">Leads</a>
<a name="%(action_report_crm_lead_salesteam)d" type="action" class="oe_sparkline_bar_link">
<field name="monthly_open_leads" widget="sparkline_bar"
options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1, 'delayIn': '3000', 'tooltip_suffix': ' Leads'}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field>
</a>
</div>
<div class="oe_salesteams_opportunities">
<a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">Opportunities</a>
<a name="%(action_report_crm_opportunity_salesteam)d" type="action">
<field name="monthly_planned_revenue" widget="sparkline_bar"
options="{'height': '20px', 'barWidth': '4', 'barSpacing': '1', 'delayIn': '3000', 'tooltip_suffix': ' (Planned Revenue)'}">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field>
</a>
</div>
</div>
<div class="oe_clear"></div>
<div class="oe_kanban_salesteams_avatars">
<t t-foreach="record.member_ids.raw_value.slice(0,10)" t-as="member">
<img t-att-src="kanban_image('res.users', 'image_small', member)" t-att-data-member_id="member"/>
</t>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Case Sections Search view -->
<record id="crm_case_section_salesteams_search" model="ir.ui.view">
<field name="name">Case Sections - Search</field>
<field name="model">crm.case.section</field>
<field name="arch" type="xml">
<search string="Salesteams Search">
<field name="name"/>
<field name="parent_id"/>
<field name="user_id"/>
<field name="note"/>
<field name="code"/>
<filter name="personal" string="My Sales Teams" domain="['|', ('member_ids', '=', uid), ('user_id', '=', uid)]"/>
<group expand="0" string="Group By...">
<filter string="Team Leader" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Parent Sales Teams" domain="[]" context="{'group_by':'parent_id'}"/>
</group>
</search>
</field>
</record>
<!-- Case Sections Action -->
<record id="crm_case_section_salesteams_act" model="ir.actions.act_window">
<field name="name">Sales Teams</field>
<field name="res_model">crm.case.section</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="context">{'search_default_personal': True}</field>
<field name="view_id" ref="crm_case_section_salesteams_view_kanban"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to define a new sales team.
</p><p>
Use sales team to organize your different salespersons or
departments into separate teams. Each team will work in
its own list of opportunities.
</p>
</field>
</record>
<!-- Case Sections Form View -->
<record id="crm_case_section_view_form" model="ir.ui.view">
<field name="name">crm.case.section.form</field>
<field name="model">crm.case.section</field>
<field name="arch" type="xml">
<form string="Sales Team" version="7.0">
<sheet>
<div class="oe_title">
<label for="name" class="oe_edit_only" string="Sales team"/>
<h1>
<field name="name" string="Salesteam"/>
</h1>
<div name="options_active">
<field name="use_leads" class="oe_inline"/><label for="use_leads"/>
</div>
</div>
<group>
<group>
<field name="user_id" context="{'default_groups_ref': ['base.group_user', 'base.group_partner_manager', 'base.group_sale_salesman_all_leads']}"/>
<field name="code"/>
<field name="parent_id"/>
<field name="change_responsible"/>
<field name="active"/>
</group>
<group>
<label for="alias_name" string="Email Alias"
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
<div name="alias_def"
attrs="{'invisible': [('alias_domain', '=', False)]}">
<field name="alias_id" class="oe_read_only oe_inline"
string="Email Alias" required="0"/>
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
</div>
</div>
<field name="alias_contact" class="oe_inline"
string="Accept Emails From"
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
</group>
</group>
<notebook colspan="4">
<page string="Team Members">
<field name="member_ids" widget="many2many_kanban">
<kanban quick_create="false" create="true">
<field name="name"/>
<templates>
<t t-name="kanban-box">
<div style="position: relative">
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
<div class="oe_module_vignette">
<div class="oe_module_desc">
<field name="name"/>
</div>
</div>
</div>
</t>
</templates>
</kanban>
</field>
</page>
<page string="Stages">
<separator string="Select Stages for this Sales Team"/>
<field name="stage_ids"/>
</page>
<page string="Notes">
<field name="note"/>
</page>
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_follower_ids" widget="mail_followers" help="Follow this salesteam to automatically track the events associated to users of this team."/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
</record>
<!-- Case Sections Tree View -->
<record id="crm_case_section_view_tree" model="ir.ui.view">
<field name="name">crm.case.section.tree</field>
<field name="model">crm.case.section</field>
<field name="field_parent">child_ids</field>
<field name="arch" type="xml">
<tree string="Sales Team">
<field name="name"/>
<field name="code"/>
<field name="user_id"/>
</tree>
</field>
</record>
<!-- Case Sections Action -->
<record model="ir.actions.act_window.view" id="action_crm_tag_kanban_view_salesteams_oppor11">
<field name="sequence" eval="0"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="crm_case_kanban_view_leads"/>
<field name="act_window_id" ref="crm_case_form_view_salesteams_opportunity"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_salesteams_oppor11">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_tree_view_oppor"/>
<field name="act_window_id" ref="crm_case_form_view_salesteams_opportunity"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form_view_salesteams_oppor11">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_case_form_view_oppor"/>
<field name="act_window_id" ref="crm_case_form_view_salesteams_opportunity"/>
</record>
<record id="crm_case_section_act_tree" model="ir.actions.act_window">
<field name="name">Cases by Sales Team</field>
<field name="res_model">crm.case.section</field>
<field name="domain">[('parent_id','=',False)]</field>
<field name="view_type">tree</field>
<field name="view_id" ref="crm_case_section_view_tree"/>
</record>
<record id="crm_case_section_act" model="ir.actions.act_window">
<field name="name">Sales Teams</field>
<field name="res_model">crm.case.section</field>
<field name="view_type">form</field>
<field name="view_id" ref="crm_case_section_view_tree"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click here to define a new sales team.
</p><p>
Use sales team to organize your different salespersons or
departments into separate teams. Each team will work in
its own list of opportunities.
</p>
</field>
</record>
<menuitem id="crm.menu_crm_case_section_act"
action="crm_case_section_salesteams_act"
sequence="1"
parent="base.menu_sales"
groups="base.group_multi_salesteams"/>
</data>
</openerp>
+5 -8
View File
@@ -26,34 +26,31 @@
<field name="name">Email</field>
</record>
<record model="crm.case.section" id="section_sales_department">
<field name="name">Direct Sales</field>
<field name="code">DM</field>
<record model="crm.case.section" id="sales_team.section_sales_department">
<field name="use_leads">True</field>
<field name="alias_name">sales</field>
<field name="member_ids" eval="[(4, ref('base.user_root'))]"/>
</record>
<!-- Payment Mode -->
<record model="crm.payment.mode" id="payment_mode_1">
<field name="name">Cash</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.payment.mode" id="payment_mode_2">
<field name="name">Check</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.payment.mode" id="payment_mode_3">
<field name="name">Credit Card</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.payment.mode" id="payment_mode_4">
<field name="name">Demand Draft</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<!--default alias for leads-->
-20
View File
@@ -2,26 +2,6 @@
<openerp>
<data noupdate="1">
<record id="base.user_demo" model="res.users">
<field name="groups_id" eval="[(4,ref('base.group_sale_salesman'))]"/>
</record>
<record id="crm.section_sales_department" model="crm.case.section">
<field name="member_ids" eval="[(4, ref('base.user_demo'))]"/>
</record>
<record model="crm.case.section" id="crm_case_section_1">
<field name="name">Indirect Sales</field>
<field name="code">IM</field>
<field name="member_ids" eval="[(4, ref('base.user_root')),(4, ref('base.user_demo'))]"/>
</record>
<record model="crm.case.section" id="crm_case_section_2">
<field name="name">Marketing</field>
<field name="code">SPD</field>
<field name="member_ids" eval="[(4, ref('base.user_root')),(4, ref('base.user_demo'))]"/>
</record>
<record model="crm.segmentation" id="crm_segmentation0">
<field name="name">OpenERP partners</field>
<field name="exclusif">True</field>
+4 -1
View File
@@ -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 """
+17 -17
View File
@@ -61,7 +61,7 @@
<field name="type">opportunity</field>
</record>
<record model="crm.case.section" id="section_sales_department">
<record model="crm.case.section" id="sales_team.section_sales_department">
<field name="stage_ids" eval="[ (4, ref('stage_lead1')), (4, ref('stage_lead2')),
(4, ref('stage_lead3')), (4, ref('stage_lead4')),
(4, ref('stage_lead5')), (4, ref('stage_lead6')),
@@ -71,76 +71,76 @@
<!-- Crm campain -->
<record model="crm.case.resource.type" id="type_lead1">
<field name="name">Telesales</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.case.resource.type" id="type_lead2">
<field name="name">Email Campaign - Services</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.case.resource.type" id="type_lead3">
<field name="name">Email Campaign - Products</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.case.resource.type" id="type_lead4">
<field name="name">Twitter Ads</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.case.resource.type" id="type_lead5">
<field name="name">Google Adwords</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.case.resource.type" id="type_lead6">
<field name="name">Banner Ads</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.case.resource.type" id="type_lead7">
<field name="name">Television</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.case.resource.type" id="type_lead8">
<field name="name">Newsletter</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<!-- crm categories -->
<record model="crm.case.categ" id="categ_oppor1">
<field name="name">Product</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor2">
<field name="name">Software</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor3">
<field name="name">Services</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor4">
<field name="name">Information</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor5">
<field name="name">Design</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor6">
<field name="name">Training</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor7">
<field name="name">Consulting</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_oppor8">
<field name="name">Other</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.lead')]" model="ir.model"/>
</record>
+26 -26
View File
@@ -20,7 +20,7 @@
<field name="categ_ids" eval="[(6, 0, [categ_oppor6])]"/>
<field name="channel_id" ref="crm_case_channel_email"/>
<field name="priority">1</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="stage_lead1"/>
<field name="description">Hello,
@@ -55,7 +55,7 @@ Could you please send me the details ?</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor2])]"/>
<field name="channel_id" ref="crm_case_channel_website"/>
<field name="priority">1</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="stage_lead1"/>
</record>
@@ -84,7 +84,7 @@ Could you please send me the details ?</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor4])]"/>
<field name="channel_id" ref=""/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="stage_lead1"/>
</record>
@@ -105,7 +105,7 @@ Could you please send me the details ?</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor5])]"/>
<field name="channel_id" ref=""/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="stage_lead6"/>
</record>
@@ -130,7 +130,7 @@ Could you please send me the details ?</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor1])]"/>
<field name="channel_id" ref="crm_case_channel_website"/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref=""/>
<field name="stage_id" ref="stage_lead1"/>
<field name="description">Hi,
@@ -157,7 +157,7 @@ Contact: +1 813 494 5005</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor3,categ_oppor4])]"/>
<field name="channel_id" ref=""/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field name="user_id" ref=""/>
<field name="stage_id" ref="stage_lead1"/>
</record>
@@ -175,7 +175,7 @@ Contact: +1 813 494 5005</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor4])]"/>
<field name="channel_id" ref=""/>
<field name="priority">0</field>
<field name="section_id" ref="crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="stage_lead1"/>
</record>
@@ -194,7 +194,7 @@ Contact: +1 813 494 5005</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor6,categ_oppor8])]"/>
<field name="channel_id" ref=""/>
<field name="priority">1</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="stage_lead1"/>
</record>
@@ -213,7 +213,7 @@ Contact: +1 813 494 5005</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor7])]"/>
<field name="channel_id" ref="crm_case_channel_phone"/>
<field name="priority">2</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="stage_lead1"/>
</record>
@@ -231,7 +231,7 @@ Contact: +1 813 494 5005</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor1])]"/>
<field name="channel_id" ref="crm_case_channel_email"/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="stage_lead1"/>
<field name="description">Hi,
@@ -254,7 +254,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor7])]"/>
<field name="channel_id" ref="crm_case_channel_direct"/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref=""/>
<field name="stage_id" ref="stage_lead1"/>
</record>
@@ -272,7 +272,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor1])]"/>
<field name="channel_id" ref="crm_case_channel_website"/>
<field name="priority">2</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref=""/>
<field name="stage_id" ref="stage_lead1"/>
</record>
@@ -310,7 +310,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-25')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-12')" name="date_action"/>
<field name="title_action">Meeting for pricing information.</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead1"/>
</record>
@@ -335,7 +335,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-23')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-10')" name="date_action"/>
<field name="title_action">Send Catalogue by Email</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead3"/>
</record>
@@ -356,7 +356,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-12')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-10')" name="date_action"/>
<field name="title_action">Call to ask system requirement</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead3"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=2)).strftime('%Y-%m-%d %H:%M')"/>
@@ -383,7 +383,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-28')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-1')" name="date_action"/>
<field name="title_action">Convert to quote</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead3"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/>
@@ -408,7 +408,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-8')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-3')" name="date_action"/>
<field name="title_action">Send price list regarding our interventions</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead4"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/>
@@ -435,7 +435,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-13')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-4')" name="date_action"/>
<field name="title_action">Call to define real needs about training</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead4"/>
<field eval="1" name="active"/>
@@ -462,7 +462,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-10')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-5')" name="date_action"/>
<field name="title_action">Ask for the good receprion of the proposition</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead4"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=3)).strftime('%Y-%m-%d %H:%M')"/>
@@ -479,7 +479,7 @@ Andrew</field>
<field name="type_id" ref="type_lead3"/>
<field name="categ_ids" eval="[(6, 0, [categ_oppor4,categ_oppor8])]"/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead4"/>
</record>
@@ -493,7 +493,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor7])]"/>
<field name="channel_id" ref="crm_case_channel_phone"/>
<field name="priority">2</field>
<field name="section_id" ref="crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead4"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(months=1)).strftime('%Y-%m-%d %H:%M')"/>
@@ -511,7 +511,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor3])]"/>
<field name="channel_id" ref="crm_case_channel_email"/>
<field name="priority">1</field>
<field name="section_id" ref="crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead5"/>
</record>
@@ -526,7 +526,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor3])]"/>
<field name="channel_id" ref="crm_case_channel_direct"/>
<field name="priority">0</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead5"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/>
@@ -545,7 +545,7 @@ Andrew</field>
<field name="channel_id" ref="crm_case_channel_website"/>
<field name="priority">0</field>
<field eval="time.strftime('%Y-%m-6')" name="date_deadline"/>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead5"/>
<field name="date_closed" eval="(DateTime.today() - relativedelta(month=1)).strftime('%Y-%m-%d %H:%M')"/>
@@ -568,7 +568,7 @@ Andrew</field>
<field name="categ_ids" eval="[(6, 0, [categ_oppor4])]"/>
<field name="priority">2</field>
<field name="title_action">Conf call with technical service</field>
<field name="section_id" ref="crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field name="user_id" ref="base.user_root"/>
<field name="stage_id" ref="crm.stage_lead5"/>
</record>
@@ -592,7 +592,7 @@ Andrew</field>
<field eval="time.strftime('%Y-%m-23')" name="date_deadline"/>
<field eval="time.strftime('%Y-%m-10')" name="date_action"/>
<field name="title_action">Send Catalogue by Email</field>
<field name="section_id" ref="crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field name="user_id" ref="base.user_demo"/>
<field name="stage_id" ref="crm.stage_lead5"/>
<!-- <field name="date_closed" eval="(DateTime.today() - relativedelta(hours=1)).strftime('%Y-%m-%d %H:%M')"/> -->
+1 -1
View File
@@ -81,7 +81,7 @@
action="crm_case_category_act_leads_all"/>
<menuitem name="Opportunities" id="menu_crm_opportunities" parent="base.menu_sales" sequence="4"
action="crm_case_category_act_oppor11"
groups="base.group_mono_salesteams,base.group_sale_manager"/>
groups="base.group_sale_salesman,base.group_sale_manager"/>
</data>
</openerp>
+2 -4
View File
@@ -1,11 +1,9 @@
<?xml version="1.0"?>
<openerp>
<data>
<!--
CRM CASE STAGE
-->
<!-- Stage Search view -->
<record id="crm_lead_stage_search" model="ir.ui.view">
<field name="name">Stage - Search</field>
@@ -570,12 +568,12 @@
help="Opportunities that are assigned to me"/>
<filter string="My Team(s)"
domain="[('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}"
help="Opportunities that are assigned to any sales teams I am member of"/>
help="Opportunities that are assigned to any sales teams I am member of" groups="base.group_multi_salesteams"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<group expand="0" string="Group By..." colspan="16">
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Team" domain="[]" context="{'group_by':'section_id'}"/>
<filter string="Team" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Customer" help="Partner" domain="[]" context="{'group_by':'partner_id'}"/>
<filter string="Country" domain="[]" context="{'group_by':'country_id'}"/>
+2 -2
View File
@@ -6,13 +6,13 @@
-->
<record model="crm.case.categ" id="categ_phone1">
<field name="name">Inbound</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.phonecall')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_phone2">
<field name="name">Outbound</field>
<field name="section_id" ref="section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.phonecall')]" model="ir.model"/>
</record>
+6 -6
View File
@@ -11,7 +11,7 @@
<field name="name">Left the message</field>
<field name="state">done</field>
<field name="partner_phone">+34 934 340 230</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_phone1"/>
<field eval="2.3" name="duration"/>
@@ -24,7 +24,7 @@
<field name="name">Need more information on the proposed deal</field>
<field name="state">done</field>
<field name="partner_phone">+44 121 690 4596</field>
<field name="section_id" ref="crm.crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_phone1"/>
<field eval="1.5" name="duration"/>
@@ -37,7 +37,7 @@
<field name="name">Ask for convenient time of meeting</field>
<field name="state">open</field>
<field name="partner_phone">+1 786 525 0724</field>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_phone2"/>
<field eval="5.0" name="duration"/>
@@ -50,7 +50,7 @@
<field name="state">done</field>
<field name="partner_phone">(077) 582-4035</field>
<field name="partner_mobile">(077) 341-3591</field>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_phone1"/>
<field eval="5.45" name="duration"/>
@@ -63,7 +63,7 @@
<field name="name">More information on the proposed deal</field>
<field name="state">pending</field>
<field name="partner_phone">+1 312 349 2324</field>
<field name="section_id" ref="crm.crm_case_section_1"/>
<field name="section_id" ref="sales_team.crm_case_section_1"/>
<field eval="1" name="active"/>
<field name="categ_id" ref="crm.categ_phone1"/>
<field eval="2.08" name="duration"/>
@@ -74,7 +74,7 @@
<field name="name">Proposal for discount offer</field>
<field name="state">open</field>
<field name="partner_phone">+34 230 953 485</field>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field eval="time.strftime('%Y-%m-28 14:15:30')" name="date"/>
<field name="categ_id" ref="crm.categ_phone2"/>
<field eval="8.56" name="duration"/>
+1 -1
View File
@@ -81,7 +81,7 @@
</group>
<group expand="1" string="Group By...">
<filter string="Salesperson" domain="[]" context="{'group_by':'user_id'}" />
<filter string="Sales Team" domain="[]" context="{'group_by':'section_id'}" />
<filter string="Sales Team" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Partner" context="{'group_by':'partner_id'}" />
<filter string="Country" context="{'group_by':'country_id'}" />
<filter string="Company" domain="[]" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
@@ -47,7 +47,7 @@
</group>
<group expand="1" string="Group By...">
<filter string="Salesperson" name="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Partner" icon="terp-partner" context="{'group_by':'partner_id'}" />
<filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}" />
<filter string="Category" icon="terp-stock_symbol-selection" domain="[]" context="{'group_by':'categ_id'}" />
-30
View File
@@ -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",
-11
View File
@@ -32,17 +32,6 @@
</div>
</div>
</group>
<separator string="Sales Teams"/>
<group>
<label for="id" string="Manage Sales Teams"/>
<div>
<div>
<field name="group_multi_salesteams" class="oe_inline"/>
<label for="group_multi_salesteams"/>
</div>
</div>
</group>
</div>
<xpath expr="//group[@name='On Mail Client']" position="before">
<group name="default_alias">
+82
View File
@@ -0,0 +1,82 @@
import calendar
from datetime import date
from dateutil import relativedelta
from openerp import tools
from openerp.osv import fields, osv
class crm_case_section(osv.Model):
_inherit = 'crm.case.section'
_inherits = {'mail.alias': 'alias_id'}
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
monthly_planned_revenue: planned revenu of opportunities during the last months
"""
obj = self.pool.get('crm.lead')
res = dict.fromkeys(ids, False)
month_begin = date.today().replace(day=1)
date_begin = month_begin - relativedelta.relativedelta(months=self._period_number - 1)
date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1])
lead_pre_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)),
('type', '=', 'lead')]
opp_pre_domain = [('date_deadline', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('date_deadline', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)),
('type', '=', 'opportunity')]
for id in ids:
res[id] = dict()
lead_domain = lead_pre_domain + [('section_id', '=', id)]
opp_domain = opp_pre_domain + [('section_id', '=', id)]
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
_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'),
'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."),
'use_opportunities': fields.boolean('Opportunities', help="Check this box to manage opportunities 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'),
'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):
"""Installation hook to create aliases for all lead and avoid constraint errors."""
return self.pool.get('mail.alias').migrate_to_alias(cr, self._name, self._table, super(crm_case_section, self)._auto_init,
'crm.lead', self._columns['alias_id'], 'name', alias_prefix='Lead+', alias_defaults={}, context=context)
def _get_stage_common(self, cr, uid, context):
ids = self.pool.get('crm.case.stage').search(cr, uid, [('case_default', '=', 1)], context=context)
return ids
_defaults = {
'stage_ids': _get_stage_common,
'use_leads': True,
'use_opportunities': 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
+185
View File
@@ -0,0 +1,185 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- CRM lead search by Salesteams -->
<record model="ir.actions.act_window" id="crm_case_form_view_salesteams_lead">
<field name="name">Leads</field>
<field name="res_model">crm.lead</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|', ('type','=','lead'), ('type','=',False)]</field>
<field name="view_id" ref="crm_case_tree_view_leads"/>
<field name="search_view_id" ref="crm.view_crm_case_leads_filter"/>
<field name="context">{
'search_default_section_id': [active_id],
'default_section_id': active_id,
'default_type': 'lead',
'stage_type': 'lead',
}
</field>
<field name="help" type="html">
<p>
Use leads if you need a qualification step before creating an
opportunity or a customer. It can be a business card you received,
a contact form filled in your website, or a file of unqualified
prospects you import, etc.
</p><p>
Once qualified, the lead can be converted into a business
opportunity and/or a new customer in your address book.
</p>
</field>
</record>
<!-- CRM opportunity search by Salesteams -->
<record model="ir.actions.act_window" id="crm_case_form_view_salesteams_opportunity">
<field name="name">Opportunities</field>
<field name="res_model">crm.lead</field>
<field name="view_mode">kanban,tree,graph,form,calendar</field>
<field name="domain">[('type','=','opportunity')]</field>
<field name="view_id" ref="crm.crm_case_kanban_view_leads"/>
<field name="search_view_id" ref="crm.view_crm_case_opportunities_filter"/>
<field name="context">{
'search_default_section_id': [active_id],
'default_section_id': active_id,
'stage_type': 'opportunity',
'default_type': 'opportunity',
'default_user_id': uid,
}
</field>
<field name="help" type="html">
<p>
OpenERP helps you keep track of your sales pipeline to follow
up potential sales and better forecast your future revenues.
</p><p>
You will be able to plan meetings and phone calls from
opportunities, convert them into quotations, attach related
documents, track all discussions, and much more.
</p>
</field>
</record>
<record id="action_report_crm_lead_salesteam" model="ir.actions.act_window">
<field name="name">Leads Analysis</field>
<field name="res_model">crm.lead.report</field>
<field name="context">{"search_default_month":1}</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="crm.view_report_crm_lead_graph_two"/>
<field name="domain">[('type','=', 'lead'),('section_id', '=', active_id)]</field>
<field name="help">Leads Analysis allows you to check different CRM related information like the treatment delays or number of leads per state. You can sort out your leads analysis by different groups to get accurate grained analysis.</field>
</record>
<record id="action_report_crm_opportunity_salesteam" model="ir.actions.act_window">
<field name="name">Opportunities Analysis</field>
<field name="res_model">crm.lead.report</field>
<field name="view_mode">graph</field>
<field name="view_id" ref="crm.view_report_crm_opportunity_graph"/>
<field name="domain">[('type','=', 'opportunity'), ('section_id', '=', active_id)]</field>
<field name="help">Opportunities Analysis gives you an instant access to your opportunities with information such as the expected revenue, planned cost, missed deadlines or the number of interactions per opportunity. This report is mainly used by the sales manager in order to do the periodic review with the teams of the sales pipeline.</field>
</record>
<record model="ir.ui.view" id="crm_case_section_salesteams_view_kanban">
<field name="name">crm.case.section.kanban</field>
<field name="model">crm.case.section</field>
<field name="inherit_id" ref="sales_team.crm_case_section_salesteams_view_kanban"/>
<field name="arch" type="xml">
<data>
<xpath expr="//templates" position="before">
<field name="alias_id"/>
<field name="use_leads"/>
<field name="use_opportunities"/>
<field name="monthly_open_leads"/>
<field name="monthly_planned_revenue"/>
</xpath>
<xpath expr="//h4[@name='name']" position="after">
<div class="oe_kanban_alias oe_center" t-if="record.use_leads.raw_value and record.alias_id.value">
<small><span class="oe_e oe_e_alias" style="float: none;">%%</span><t t-raw="record.alias_id.raw_value[1]"/></small>
</div>
<div class="oe_items_list">
<div class="oe_salesteams_leads" t-if="record.use_leads.raw_value">
<a name="%(crm_case_form_view_salesteams_lead)d" type="action">Leads</a>
<a name="%(action_report_crm_lead_salesteam)d" type="action" class="oe_sparkline_bar_link">
<field name="monthly_open_leads" widget="sparkline_bar"
options="{'height': '20px', 'barWidth': 4, 'barSpacing': 1, 'delayIn': '3000', 'tooltip_suffix': ' Leads'}">Open Leads per Month<br/>Click to see a detailed analysis of leads.</field>
</a>
</div>
<div class="oe_salesteams_opportunities" t-if="record.use_opportunities.raw_value">
<a name="%(crm_case_form_view_salesteams_opportunity)d" type="action">Opportunities</a>
<a name="%(action_report_crm_opportunity_salesteam)d" type="action">
<field name="monthly_planned_revenue" widget="sparkline_bar"
options="{'height': '20px', 'barWidth': '4', 'barSpacing': '1', 'delayIn': '3000', 'tooltip_suffix': ' (Planned Revenue)'}">Planned Revenue per Month<br/>Click to see a detailed analysis of opportunities.</field>
</a>
</div>
</div>
</xpath>
</data>
</field>
</record>
<record model="ir.ui.view" id="sales_team_form_view_in_crm">
<field name="name">crm.case.section.form</field>
<field name="model">crm.case.section</field>
<field name="inherit_id" ref="sales_team.crm_case_section_view_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='options_active']" position="inside">
<field name="use_leads"/><label for="use_leads" string="Leads"/>
<field name="use_opportunities" class="oe_inline"/><label for="use_opportunities"/>
</xpath>
<xpath expr="//group/field[@name='active']" position="inside">
<group>
<label for="alias_name" string="Email Alias"
attrs="p'invisible': [('alias_domain', '=', False)]}"/>
<div name="alias_def"
attrs="{'invisible': [('alias_domain', '=', False)]}">
<field name="alias_id" class="oe_read_only oe_inline"
string="Email Alias" required="0"/>
<div class="oe_edit_only oe_inline" name="edit_alias" style="display: inline;" >
<field name="alias_name" class="oe_inline"/>@<field name="alias_domain" class="oe_inline" readonly="1"/>
</div>
</div>
<field name="alias_contact" class="oe_inline"
string="Accept Emails From"
attrs="{'invisible': [('alias_domain', '=', False)]}"/>
</group>
</xpath>
<xpath expr="//page[@string='Team Members']" position="after">
<page string="Stages">
<separator string="Select Stages for this Sales Team"/>
<field name="stage_ids"/>
</page>
</xpath>
</field>
</record>
<!-- Case Sections Action -->
<record model="ir.actions.act_window.view" id="action_crm_tag_kanban_view_salesteams_oppor11">
<field name="sequence" eval="0"/>
<field name="view_mode">kanban</field>
<field name="view_id" ref="crm_case_kanban_view_leads"/>
<field name="act_window_id" ref="crm_case_form_view_salesteams_opportunity"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_tree_view_salesteams_oppor11">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="crm_case_tree_view_oppor"/>
<field name="act_window_id" ref="crm_case_form_view_salesteams_opportunity"/>
</record>
<record model="ir.actions.act_window.view" id="action_crm_tag_form_view_salesteams_oppor11">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="crm_case_form_view_oppor"/>
<field name="act_window_id" ref="crm_case_form_view_salesteams_opportunity"/>
</record>
<record id="crm_case_section_act_tree" model="ir.actions.act_window">
<field name="name">Cases by Sales Team</field>
<field name="res_model">crm.case.section</field>
<field name="domain">[('parent_id','=',False)]</field>
<field name="view_type">tree</field>
<field name="view_id" ref="sales_team.crm_case_section_view_tree"/>
</record>
</data>
</openerp>
-13
View File
@@ -24,19 +24,6 @@
<field name="users" eval="[(4, ref('base.user_root'))]"/>
</record>
<record id="base.group_mono_salesteams" model="res.groups">
<field name="name">Do Not Use Sales Teams</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="base.group_user" model="res.groups">
<field name="implied_ids" eval="[(4, ref('base.group_mono_salesteams'))]"/>
</record>
<record id="base.group_multi_salesteams" model="res.groups">
<field name="name">Manage Sales Teams</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_fund_raising" model="res.groups">
<field name="name">Manage Fund Raising</field>
<field name="category_id" ref="base.module_category_hidden"/>
-3
View File
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
5 access_crm_segmentation_line crm.segmentation.line model_crm_segmentation_line base.group_sale_manager 1 1 1 1
6 access_crm_case_channel_user crm.case.channel user model_crm_case_channel base.group_sale_salesman 1 0 0 0
7 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
8 access_crm_case_categ crm.case.categ model_crm_case_categ base.group_sale_salesman 1 1 1 0
9 access_crm_lead_manager crm.lead.manager model_crm_lead base.group_sale_manager 1 1 1 1
10 access_crm_phonecall_manager crm.phonecall.manager model_crm_phonecall base.group_sale_manager 1 1 1 1
11 access_crm_case_categ crm.case.categ model_crm_case_categ base.group_user 1 0 0 0
12 access_crm_lead crm.lead model_crm_lead base.group_sale_salesman 1 1 1 0
13 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
14 access_crm_case_stage crm.case.stage model_crm_case_stage 1 0 0 0
15 access_crm_case_stage_manager crm.case.stage model_crm_case_stage base.group_sale_manager 1 1 1 1
16 access_crm_case_resource_type_user crm_case_resource_type user model_crm_case_resource_type base.group_sale_salesman 1 1 1 0
-2
View File
@@ -1,2 +0,0 @@
crm.css: crm.sass
sass --trace -t expanded crm.sass:crm.css
+1 -1
View File
@@ -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("sales_team.crm_case_section_2")})
self.write(cr, uid, [ref("crm_case_1")], {'section_id': section_id})
-
Salesman check unqualified lead .
+1 -1
View File
@@ -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: sales_team.section_sales_department
-
I check default stage of lead.
-
+1 -1
View File
@@ -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('sales_team.section_sales_department')}, context=context)
self.mass_convert(cr, uid, [id], context=context)
-
Now I check first lead converted on opportunity.
@@ -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('sales_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.
-13
View File
@@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- vim:fdn=3:
-->
<openerp>
<data>
<template id="assets_backend" name="crm assets" inherit_id="web.assets_backend">
<xpath expr="." position="inside">
<link rel="stylesheet" href="/crm/static/src/css/crm.css"/>
<script type="text/javascript" src="/crm/static/src/js/crm_case_section.js"></script>
</xpath>
</template>
</data>
</openerp>
@@ -12,7 +12,7 @@
</group>
<group string="Assign opportunities to">
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, section_id, context)"/>
<field name="section_id" class="oe_inline"/>
<field name="section_id" class="oe_inline" groups="base.group_multi_salesteams"/>
</group>
<group string="Opportunities">
<field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}" nolabel="1">
@@ -10,7 +10,7 @@
<form string="Merge Leads/Opportunities" version="7.0">
<group string="Assign opportunities to">
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, section_id, context)"/>
<field name="section_id" class="oe_inline"/>
<field name="section_id" class="oe_inline" groups="base.group_multi_salesteams"/>
</group>
<group string="Select Leads/Opportunities">
<field name="opportunity_ids" nolabel="1">
+5 -5
View File
@@ -8,19 +8,19 @@
<record model="crm.case.categ" id="categ_claim1">
<field name="name">Factual Claims</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_claim2">
<field name="name">Value Claims</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
</record>
<record model="crm.case.categ" id="categ_claim3">
<field name="name">Policy Claims</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="object_id" search="[('model','=','crm.claim')]" model="ir.model"/>
</record>
@@ -30,12 +30,12 @@
<record model="crm.case.resource.type" id="type_claim1">
<field name="name">Corrective</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<record model="crm.case.resource.type" id="type_claim2">
<field name="name">Preventive</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
</record>
<!--
+7 -7
View File
@@ -12,7 +12,7 @@
<field eval="&quot;1&quot;" name="priority"/>
<field name="user_id" ref="base.user_root"/>
<field eval="&quot;Problem with the delivery of goods&quot;" name="name"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="categ_id" ref="crm_claim.categ_claim1"/>
<field name="stage_id" ref="crm_claim.stage_claim1"/>
<field eval="&quot;(769) 703-274&quot;" name="partner_phone"/>
@@ -24,7 +24,7 @@
<field eval="&quot;0&quot;" name="priority"/>
<field name="user_id" ref="base.user_root"/>
<field eval="&quot;Damaged Products&quot;" name="name"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="categ_id" ref="crm_claim.categ_claim2"/>
<field name="stage_id" ref="crm_claim.stage_claim5"/>
<field eval="&quot;(956) 293-2595&quot;" name="partner_phone"/>
@@ -36,7 +36,7 @@
<field eval="&quot;2&quot;" name="priority"/>
<field name="user_id" ref="base.user_demo"/>
<field eval="&quot;Document related problems&quot;" name="name"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="categ_id" ref="crm_claim.categ_claim3"/>
<field name="stage_id" ref="crm_claim.stage_claim2"/>
<field eval="&quot;(079) 681-2139&quot;" name="partner_phone"/>
@@ -49,7 +49,7 @@
<field eval="&quot;1&quot;" name="priority"/>
<field name="user_id" ref="base.user_root"/>
<field eval="&quot;Product quality not maintained&quot;" name="name"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="categ_id" ref="crm_claim.categ_claim1"/>
<field name="stage_id" ref="crm_claim.stage_claim5"/>
<field eval="&quot;(514) 698-4118&quot;" name="partner_phone"/>
@@ -61,7 +61,7 @@
<field eval="&quot;1&quot;" name="priority"/>
<field name="user_id" ref="base.user_root"/>
<field eval="&quot;Some products missing&quot;" name="name"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="categ_id" ref="crm_claim.categ_claim3"/>
<field name="stage_id" ref="crm_claim.stage_claim3"/>
<field eval="&quot;(855) 924-4364&quot;" name="partner_phone"/>
@@ -72,7 +72,7 @@
<field eval="&quot;1&quot;" name="priority"/>
<field name="user_id" ref="base.user_root"/>
<field eval="&quot;Problem with the delivery of assignments&quot;" name="name"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field eval="time.strftime('%Y-%m-28 14:15:30')" name="date"/>
<field name="categ_id" ref="crm_claim.categ_claim1"/>
<field name="stage_id" ref="crm_claim.stage_claim5"/>
@@ -85,7 +85,7 @@
<field eval="&quot;1&quot;" name="priority"/>
<field name="user_id" ref="base.user_root"/>
<field eval="&quot;Documents unclear&quot;" name="name"/>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field eval="time.strftime('%Y-%m-19 13:01:05')" name="date"/>
<field name="categ_id" ref="crm_claim.categ_claim3"/>
<field name="stage_id" ref="crm_claim.stage_claim2"/>
@@ -46,7 +46,7 @@
</group>
<group expand="1" string="Group By...">
<filter string="Salesperson" name="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" />
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Partner" name="partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}" />
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}" />
<filter string="Priority" icon="terp-rating-rated" domain="[]" context="{'group_by':'priority'}" />
+3 -3
View File
@@ -7,7 +7,7 @@
<field name="company_id" ref="base.main_company"/>
<field name="priority">1</field>
<field name="state">draft</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="date" eval="time.strftime('%Y-%m-04 11:10:36')"/>
<field name="name">Download old version of OpenERP? </field>
<field eval="&quot;Is there any link to download old versions of OpenERP?&quot;" name="description"/>
@@ -19,7 +19,7 @@
<field name="company_id" ref="base.main_company"/>
<field name="priority">1</field>
<field name="state">draft</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="date" eval="time.strftime('%Y-%m-12 11:12:09')"/>
<field name="name">Can not able to connect to Server</field>
<field eval="&quot;I am not able to connect Server, Can anyone help?&quot;" name="description"/>
@@ -31,7 +31,7 @@
<field name="company_id" ref="base.main_company"/>
<field name="priority">2</field>
<field name="state">draft</field>
<field name="section_id" ref="crm.section_sales_department"/>
<field name="section_id" ref="sales_team.section_sales_department"/>
<field name="date" eval="time.strftime('%Y-%m-12 11:13:10')"/>
<field name="name">Documentation for CRM?</field>
<field eval="&quot;Can anyone give link of document for CRM&quot;" name="description"/>
+1 -1
View File
@@ -16,7 +16,7 @@
<field name="categ_ids" eval="[(6, 0, [ref('crm.categ_oppor1')])]"/>
<field name="channel_id" ref="crm.crm_case_channel_email"/>
<field name="priority">2</field>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field name="section_id" ref="sales_team.crm_case_section_2"/>
<field name="user_id" ref=""/>
<field name="stage_id" ref="crm.stage_lead1"/>
<field name="description">Hi,
@@ -18,7 +18,7 @@
<field name="type_id" invisible="1"/>
<field name="referred" invisible="1"/>
<field name="channel_id" invisible="1"/>
<field name="section_id" invisible="context.get('invisible_section', True)" />
<field name="section_id" invisible="context.get('invisible_section', True)" groups="base.group_multi_salesteams"/>
<button string="I'm interested" name="case_interested" icon="gtk-index" type="object"/>
<button string="I'm not interested" name="case_disinterested" icon="gtk-close" type="object"/>
@@ -84,7 +84,7 @@
<field name="type_id" invisible="1"/>
<field name="planned_revenue" sum="Expected Revenues"/>
<field name="probability" widget="progressbar" avg="Avg. of Probability"/>
<field name="section_id" invisible="context.get('invisible_section', True)" />
<field name="section_id" groups="base.group_multi_salesteams" invisible="context.get('invisible_section', True)" />
<field name="priority" invisible="1"/>
</tree>
</field>
@@ -27,9 +27,9 @@
domain="[]" context="{'group_by':'user_id'}" />
<filter string="Partner" icon="terp-partner" context="{'group_by':'partner_assigned_id'}" />
<filter string="Country" icon="terp-go-home" context="{'group_by':'country_id'}" />
<filter string="Section" icon="terp-personal+"
<filter string="Sales Team" icon="terp-personal+"
domain="[]"
context="{'group_by':'section_id'}" />
context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Grade" name="group_grade" icon="terp-stock_symbol-selection"
domain="[]" context="{'group_by':'grade_id'}" />
<filter string="Stage" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
@@ -16,8 +16,8 @@
<filter string="Salesperson" name="user" icon="terp-personal"
domain="[]" context="{'group_by':'user_id'}" />
<filter string="Country" icon="terp-go-home" name="group_country" context="{'group_by':'country_id'}" />
<filter string="Section" icon="terp-personal+"
domain="[]" context="{'group_by':'section_id'}" />
<filter string="Sales Team" icon="terp-personal+"
domain="[]" context="{'group_by':'section_id'}" groups="base.group_multi_salesteams"/>
<filter string="Activation" name="group_activation" icon="terp-stock_symbol-selection"
domain="[]" context="{'group_by':'activation'}" />
<filter string="Grade" name="group_grade" icon="terp-stock_symbol-selection"
+1 -1
View File
@@ -243,7 +243,7 @@
<field name="type">ir.actions.act_window</field>
<field name="res_model">ir.attachment</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to create a new document.
+87
View File
@@ -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 <EMAIL@ADDRESS>, 2014.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\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 <EMAIL@ADDRESS>\n"
"Language-Team: Tamil <ta@li.org>\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 ""
+1 -1
View File
@@ -9,7 +9,7 @@
<field name="subject">Your registration at ${object.event_id.name}</field>
<field name="body_html"><![CDATA[
<p>Hello ${object.name},</p>
<p>The event ${object.event_id.name} that you registered for is confirmed and will be held from ${object.event_id.date_begin} to ${object.event_id.date_end}.
<p>The event ${object.event_id.name} that you registered for is confirmed and will be held from ${object.event_id.date_begin_located.strftime('%Y-%m-%d %H:%M:%S (%Z)')} to ${object.event_id.date_end_located.strftime('%Y-%m-%d %H:%M:%S (%Z)')}.
For any further information please contact our event department.</p>
<p>Thank you for your participation!</p>
<p>Best regards</p>]]></field>
+23 -2
View File
@@ -18,7 +18,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import pytz
from openerp.tools import DEFAULT_SERVER_DATETIME_FORMAT
from datetime import datetime, timedelta
from openerp.osv import fields, osv
from openerp.tools.translate import _
@@ -156,6 +157,22 @@ class event_event(osv.osv):
for event in self.browse(cr, uid, ids, context=context)
}
def _compute_date_tz(self, cr, uid, ids, fld, arg, context=None):
if context is None:
context = {}
res = {}
for event in self.browse(cr, uid, ids, context=context):
ctx = dict(context, tz=(event.date_tz or 'UTC'))
if fld == 'date_begin_located':
date_to_convert = event.date_begin
elif fld == 'date_end_located':
date_to_convert = event.date_end
res[event.id] = fields.datetime.context_timestamp(cr, uid, datetime.strptime(date_to_convert, DEFAULT_SERVER_DATETIME_FORMAT), context=ctx)
return res
def _tz_get(self, cr, uid, context=None):
return [(x, x) for x in pytz.all_timezones]
_columns = {
'name': fields.char('Event Name', size=64, required=True, translate=True, readonly=False, states={'done': [('readonly', True)]}),
'user_id': fields.many2one('res.users', 'Responsible User', readonly=False, states={'done': [('readonly', True)]}),
@@ -175,8 +192,11 @@ class event_event(osv.osv):
store={'event.registration': (_get_events_from_registrations, ['state'], 10),
'event.event': (lambda self, cr, uid, ids, c = {}: ids, ['seats_max', 'registration_ids'], 20)}),
'registration_ids': fields.one2many('event.registration', 'event_id', 'Registrations', readonly=False, states={'done': [('readonly', True)]}),
'date_tz': fields.selection(_tz_get, string='Timezone'),
'date_begin': fields.datetime('Start Date', required=True, readonly=True, states={'draft': [('readonly', False)]}),
'date_end': fields.datetime('End Date', required=True, readonly=True, states={'draft': [('readonly', False)]}),
'date_begin_located': fields.function(_compute_date_tz, string='Start Date Located', type="datetime"),
'date_end_located': fields.function(_compute_date_tz, string='End Date Located', type="datetime"),
'state': fields.selection([
('draft', 'Unconfirmed'),
('cancel', 'Cancelled'),
@@ -204,7 +224,8 @@ class event_event(osv.osv):
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'event.event', context=c),
'user_id': lambda obj, cr, uid, context: uid,
'organizer_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, context=c).company_id.partner_id.id,
'address_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, context=c).company_id.partner_id.id
'address_id': lambda self, cr, uid, c: self.pool.get('res.users').browse(cr, uid, uid, context=c).company_id.partner_id.id,
'date_tz': lambda self, cr, uid, ctx: ctx.get('tz', "UTC"),
}
def _check_seats_limit(self, cr, uid, ids, context=None):
+1
View File
@@ -89,6 +89,7 @@
<field name="type" on_change="onchange_event_type(type,context)" />
<field name="date_begin" on_change="onchange_start_date(date_begin,date_end)"/>
<field name="date_end"/>
<field name="date_tz" />
</group>
</group>
<notebook>
+1 -1
View File
@@ -155,7 +155,7 @@ class gamification_badge(osv.Model):
string='Authorized Users',
help="Only these people can give this badge"),
'rule_auth_badge_ids': fields.many2many('gamification.badge',
'rel_badge_badge', 'badge1_id', 'badge2_id',
'gamification_badge_rule_badge_rel', 'badge1_id', 'badge2_id',
string='Required Badges',
help="Only the people having these badges can give this badge"),
+2 -2
View File
@@ -129,7 +129,7 @@ class gamification_challenge(osv.Model):
'manager_id': fields.many2one('res.users',
string='Responsible', help="The user responsible for the challenge."),
'user_ids': fields.many2many('res.users', 'user_ids',
'user_ids': fields.many2many('res.users', 'gamification_challenge_users_rel',
string='Users',
help="List of users participating to the challenge"),
'user_domain': fields.char('User domain', help="Alternative to a list of users"),
@@ -149,7 +149,7 @@ class gamification_challenge(osv.Model):
'end_date': fields.date('End Date',
help="The day a new challenge will be automatically closed. If no periodicity is set, will use this date as the goal end date."),
'invited_user_ids': fields.many2many('res.users', 'invited_user_ids',
'invited_user_ids': fields.many2many('res.users', 'gamification_invited_user_ids_rel',
string="Suggest to users"),
'line_ids': fields.one2many('gamification.challenge.line', 'challenge_id',
+63 -59
View File
@@ -1,28 +1,12 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
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):
+3 -3
View File
@@ -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,
}
+41 -32
View File
@@ -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}
+322 -127
View File
@@ -1,31 +1,15 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# -*- 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!')]
@@ -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); });
}
});
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="view_users_form" model="ir.ui.view">
<field name="name">res.users.form</field>
<field name="model">res.users</field>
<field name="inherit_id" ref="base.view_users_form"/>
<field name="arch" type="xml">
<notebook colspan="4" position="inside">
<page string="Calendar">
<group>
<field name="google_calendar_rtoken"/>
<field name="google_calendar_token"/>
<field name="google_calendar_token_validity"/>
<field name="google_calendar_last_sync_date"/>
<field name="google_calendar_cal_id"/>
</group>
</page>
</notebook>
</field>
</record>
</data>
</openerp>
+44 -86
View File
@@ -5,11 +5,12 @@ How to use OpenChatter in my addon
Running example
++++++++++++++++
A small my_task model will be used as example to explain how to use the OpenChatter feature. Being simple, it has only the following fields :
A small my_task model will be used as example to explain how to use the
OpenChatter feature. Being simple, it has only the following fields:
- a name
- a task responsible
- a related project
- a name
- a task responsible
- a related project
::
@@ -28,9 +29,7 @@ A small my_task model will be used as example to explain how to use the OpenChat
Two-lines feature integration
++++++++++++++++++++++++++++++
Make your module inheriting from the ``mail.thread`` class.
::
Make your object inherit from :class:`mail.thread`::
class my_task(osv.osv):
_name = "my.task"
@@ -38,9 +37,8 @@ Make your module inheriting from the ``mail.thread`` class.
# inherit from mail.thread allows the use of OpenChatter
_inherit = ['mail.thread']
Use the thread viewer widget inside your form view by using the mail_thread widget on the message_ids field inherited from mail.thread.
::
Use the thread viewer widget inside your form view by using the mail_thread
widget on the message_ids field inherited from :class:`mail.thread`::
<record model="ir.ui.view" id="my_task_form_view">
<field name="name">My Task</field>
@@ -57,98 +55,58 @@ Use the thread viewer widget inside your form view by using the mail_thread widg
Send notifications
+++++++++++++++++++
When sending a notification is required in your workflow or business logic, use the ``message_post`` method. This method is a shortcut to the ``message_append`` method that takes all ``mail.message`` fields as arguments. This latter method calls ``message_create`` that
When sending a notification is required in your workflow or business logic,
use :meth:`mail.thread.message_post`. It will automatically take care of
subscriptions and notifications.
- creates the message
- parses the body to find users you want to push the message to (finding and parsing ``@login`` in the message body)
- pushes a notification to users following the document and requested users of the latetr step
You should therefore not worry about subscriptions or anything else than sending the notification. Here is a small example of sending a notification when the ``do_something`` method is called :
::
Here is a small example of sending a notification when the ``do_something``
method is called::
def do_something(self, cr, uid, ids, context=None):
[...]
self.do_something_send_note(cr, uid, ids, context=context)
[...]
return res
self.do_something_send_note(cr, uid, ids, context=context)
return res
def do_something_send_note(self, cr, uid, ids, context=None):
self.message_post(cr, uid, ids, _('My subject'),
_("has received a <b>notification</b> and is happy for it."), context=context)
self.message_post(
cr, uid, ids, _('My subject'),
_("has received a <b>notification</b> and is happy for it."),
context=context)
Notifications guidelines
+++++++++++++++++++++++++
Here are a few guidelines that you should keep in mind during the addition of system notifications :
- avoid unnecessary content; if a message has no interest, do not implement it
- use short sentences
- do not include the document name, as it is managed by the thread widget
- use a simple and clean style
- avoid unnecessary content, swamping users with irrelevant messages will lead
to them ignoring all messages
- use short sentences
- do not include the document name, this is done by the thread widget
- use a simple and clean style
- html tags are supported: use <b> or <em> mainly
- put main word(s) in bold
- put key word(s) in bold
- avoid fancy styles that will break the OpenERP look and feel
- create a separate method for sending your notification
- use a method name like ``original_method_name_send_note``, that allow to easily spot notification methods in the code
- create a separate method for sending your notification, use clear method
names allowing quickly spotting notification code e.g. name notification
methods by using the original method name postfixed by ``_send_note``
(``do_something`` -> ``do_something_send_note``)
Subscription management
++++++++++++++++++++++++
There are a few default subscription tricks that you should know before playing with subscription:
The default subscription behavior is the following:
- users that click on 'follow' follow the document. An entry in ``mail.subscription`` is created.
- users that click on 'unfollow' are no longer followers to the document. The related entry in ``mail.subscription`` is created.
- users that create or update a document automatically follow it. An entry in ``mail.subscription`` is created.
* Subscriptions are set up by creating a :class:`mail.followers`` entry
* If a user creates or updates a document, they automatically follow it. The
corresponding :class:`mail.followers` entry is created
* If a user explicitly cliks on the document's :guilabel:`Follow` button,
they follow the document. The corresponding :class:`mail.followers` entry
is created
* If a user explicitly clicks on the document's :guilabel:`Unfollow` button,
they stop following the document. The corresponding :class:`mail.followers`
entry is deleted
If you want to override this default behavior, you should avoid doing it manualle. You should instead override the ``message_get_subscribers`` method from mail.thread. The default implementation looks in the ``mail.suscription`` table for entries matching ``user_id=uid, res_model=self._name, res_id=current_record_id``. You can add subscribers by overriding the ``message_get_subscribers`` and adding user ids to the returned list. This means that they will be considered as followers even if they do not have an entry in the mail.subscription table.
You should not directly manipulate :class:`mail.followers` entry, if you need
to override the default subscription behavior you should override the relevant
:class:`mail.thread` methods.
As an exemple, let us say that you want to automatically add the my_task responsible along with the project manager to the list of followers. The method could look like:
::
def message_get_subscribers(self, cr, uid, ids, context=None):
# get the followers from the mail.subscription table
sub_ids = self.message_get_subscribers_ids(cr, uid, ids, context=context);
# add the employee and its manager if specified to the subscribed users
for obj in self.browse(cr, uid, ids, context=context):
if obj.user_id:
sub_ids.append(obj.user_id)
if obj.project_id and obj.project_id.user_id:
sub_ids.append(obj.project_id.user_id)
return self.pool.get('res.users').read(cr, uid, sub_ids, context=context)
This method has the advantage of being able to implement a particular behavior with as few code addition as possible. Moreover, when changing the task responsible of the project manager, the subscribers are always correct. This allows to avoid to implement complex corner cases that could obfuscate the code.
The drawback of this method is that it is no longer possible to those subscribers to unfollow a document. Indeed, as user ids are added directly in a list in ``message_get_subscribers``, it is not possible to unsubscribe to a document. However, this drawback is mitigated by
- only important users shoudl be added using this method. Important users should not unsubscribe from their documents.
- users can hide the notifications on their Wall
Messages display management
++++++++++++++++++++++++++++
By default, the mail_thread widget shows all messages related to the current document beside the document, in the History and comments section. However, you may want to display other messages in the widget. For example, the OpenChatter on res.users model shows
- messages related to the user, as usual (messages with ``model = res.users, res_id = current_document_id``)
- messages directly pushed to this user (containing @login)
The best way to direct the messages that will be displayed in the OpenChatter widget is to override the ``message_load`` method. For example, the following method fetches messages as usual, but also fetches messages linked to the task project that contain the task name. Please refer to the API for more details about the arguments.
::
def message_load(self, cr, uid, ids, limit=100, offset=0, domain=[], ascent=False, root_ids=[False], context=None):
msg_obj = self.pool.get('mail.message')
for my_task in self.browse(cr, uid, ids, context=context):
# search as usual messages related to the current document
msg_ids += msg_obj.search(cr, uid, ['|', '&', ('res_id', '=', my_task.id), ('model', '=', self._name),
# add: search in the current task project messages
'&', '&', ('res_id', '=', my_task.project_id.id), ('model', '=', 'project.project'),
# ... containing the task name
'|', ('body', 'like', '%s' % (my_task.name)), ('body_html', 'like', '%s' % (my_task.name))
] + domain, limit=limit, offset=offset, context=context)
# if asked: add ancestor ids to have complete threads
if (ascent): msg_ids = self._message_add_ancestor_ids(cr, uid, ids, msg_ids, root_ids, context=context)
return msg_obj.read(cr, uid, msg_ids, context=context)
.. TODO: wtf are the relevant mail.thread methds? message_get_subscribers
has disappeared and nothing looks like a replacement
+2 -1
View File
@@ -1,10 +1,11 @@
.. _mail_thread:
===========================
mail.thread and OpenChatter
===========================
API
+++
===
Writing messages and notifications
----------------------------------
+7 -4
View File
@@ -27,6 +27,7 @@ from openerp.osv import fields, osv
from openerp.tools import ustr
from openerp.modules.registry import RegistryManager
from openerp import SUPERUSER_ID
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
@@ -129,16 +130,18 @@ class mail_alias(osv.Model):
]
def name_get(self, cr, uid, ids, context=None):
"""Return the mail alias display alias_name, inclusing the implicit
mail catchall domain from config.
e.g. `jobs@openerp.my.openerp.com` or `sales@openerp.my.openerp.com`
"""Return the mail alias display alias_name, including the implicit
mail catchall domain if exists from config otherwise "New Alias".
e.g. `jobs@openerp.my.openerp.com` or `jobs` or 'New Alias'
"""
res = []
for record in self.browse(cr, uid, ids, context=context):
if record.alias_name and record.alias_domain:
res.append((record['id'], "%s@%s" % (record.alias_name, record.alias_domain)))
elif record.alias_name:
res.append((record['id'], "%s" % (record.alias_name)))
else:
res.append((record['id'], False))
res.append((record['id'], _("Inactive Alias")))
return res
def _find_unique(self, cr, uid, name, context=None):
+1
View File
@@ -41,6 +41,7 @@ class mail_mail(osv.Model):
_description = 'Outgoing Mails'
_inherits = {'mail.message': 'mail_message_id'}
_order = 'id desc'
_rec_name = 'subject'
_columns = {
'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'),
+2 -2
View File
@@ -10,13 +10,14 @@
<button name="send" string="Send Now" type="object" states='outgoing' class="oe_highlight"/>
<button name="mark_outgoing" string="Retry" type="object" states='exception,cancel'/>
<button name="cancel" string="Cancel" type="object" states='outgoing'/>
<field name="state" widget="statusbar" statusbar_visible="outgoing,sent,received,exception,cancel"/>
</header>
<sheet>
<field name="mail_message_id" required="0" invisible="1"/>
<label for="subject" class="oe_edit_only"/>
<h2><field name="subject"/></h2>
<div style="vertical-align: top;">
by <field name="author_id" class="oe_inline" string="User"/> on <field name="date" class="oe_inline"/>
by <field name="author_id" class="oe_inline" string="User"/> on <field name="date" readonly="1" class="oe_inline"/>
<button name="%(action_email_compose_message_wizard)d" string="Reply" type="action" icon="terp-mail-replied"
context="{'default_composition_mode':'reply', 'default_parent_id': mail_message_id}" states='received,sent,exception,cancel'/>
</div>
@@ -38,7 +39,6 @@
<field name="auto_delete"/>
<field name="notification"/>
<field name="type"/>
<field name="state"/>
<field name="mail_server_id"/>
<field name="model"/>
<field name="res_id"/>
+1 -1
View File
@@ -115,7 +115,7 @@
</field>
</record>
<record id="base.action_attachment" model="ir.actions.act_window">
<field name="view_mode">kanban,form</field>
<field name="view_mode">kanban,tree,form</field>
</record>
+7
View File
@@ -359,6 +359,10 @@ class mail_thread(osv.AbstractModel):
if context is None:
context = {}
if context.get('tracking_disable'):
return super(mail_thread, self).create(
cr, uid, values, context=context)
# subscribe uid unless asked not to
if not context.get('mail_create_nosubscribe'):
pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid).partner_id.id
@@ -394,6 +398,9 @@ class mail_thread(osv.AbstractModel):
context = {}
if isinstance(ids, (int, long)):
ids = [ids]
if context.get('tracking_disable'):
return super(mail_thread, self).write(
cr, uid, ids, values, context=context)
# Track initial values of tracked fields
track_ctx = dict(context)
if 'lang' not in track_ctx:
+2
View File
@@ -48,6 +48,8 @@ professional emails and reuse templates in a few clicks.
'views/res_config.xml',
'views/res_partner.xml',
'views/email_template.xml',
'views/website_mass_mailing.xml',
'views/snippets.xml',
'security/ir.model.access.csv',
'views/mass_mailing.xml',
],
+31
View File
@@ -40,3 +40,34 @@ class MassMailController(http.Controller):
if 'opt_out' in request.registry[mailing.mailing_model]._all_columns:
request.registry[mailing.mailing_model].write(cr, SUPERUSER_ID, record_ids, {'opt_out': True}, context=context)
return 'OK'
@http.route(['/website_mass_mailing/is_subscriber'], type='json', auth="public", website=True)
def is_subscriber(self, list_id, **post):
cr, uid, context = request.cr, request.uid, request.context
Contacts = request.registry['mail.mass_mailing.contact']
Users = request.registry['res.users']
is_subscriber = False
email = None
if uid != request.website.user_id.id:
email = Users.browse(cr, SUPERUSER_ID, uid, context).email
elif request.session.get('mass_mailing_email'):
email = request.session['mass_mailing_email']
if email:
contact_ids = Contacts.search(cr, SUPERUSER_ID, [('list_id', '=', int(list_id)), ('email', '=', email)], context=context)
is_subscriber = len(contact_ids) > 0
return {'is_subscriber': is_subscriber, 'email': email}
@http.route(['/website_mass_mailing/subscribe'], type='json', auth="public", website=True)
def subscribe(self, list_id, email, **post):
cr, uid, context = request.cr, request.uid, request.context
Contacts = request.registry['mail.mass_mailing.contact']
contact_ids = Contacts.search(cr, SUPERUSER_ID, [('list_id', '=', int(list_id)), ('email', '=', email)], context=context)
if not contact_ids:
Contacts.name_create(cr, SUPERUSER_ID, email, context=context)
# add email to session
request.session['mass_mailing_email'] = email
return True
@@ -33,5 +33,10 @@
<field name="sequence">30</field>
</record>
<!-- Create mailing lists -->
<record id="mass_mail_list_1" model="mail.mass_mailing.list">
<field name="name">Newsletter</field>
</record>
</data>
</openerp>
@@ -9,7 +9,7 @@
</record>
<!-- Create mailing lists -->
<record id="mass_mail_list_1" model="mail.mass_mailing.list">
<record id="mass_mail_list_2" model="mail.mass_mailing.list">
<field name="name">Imported Contacts</field>
</record>
@@ -17,17 +17,17 @@
<record id="mass_mail_contact_1" model="mail.mass_mailing.contact">
<field name="name">Aristide Antario</field>
<field name="email">aa@example.com</field>
<field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
<field name="list_id" ref="mass_mailing.mass_mail_list_2"/>
</record>
<record id="mass_mail_contact_2" model="mail.mass_mailing.contact">
<field name="name">Beverly Bridge</field>
<field name="email">bb@example.com</field>
<field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
<field name="list_id" ref="mass_mailing.mass_mail_list_2"/>
</record>
<record id="mass_mail_contact_3" model="mail.mass_mailing.contact">
<field name="name">Carol Cartridge</field>
<field name="email">cc@example.com</field>
<field name="list_id" ref="mass_mailing.mass_mail_list_1"/>
<field name="list_id" ref="mass_mailing.mass_mail_list_2"/>
<field name="opt_out" eval="True"/>
</record>
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

@@ -0,0 +1,40 @@
(function () {
'use strict';
var website = openerp.website;
var _t = openerp._t;
website.snippet.options.mailing_list_subscribe = website.snippet.Option.extend({
on_prompt: function () {
var self = this;
return website.prompt({
id: "editor_new_mailing_list_subscribe_button",
window_title: _t("Add a Newsletter Subscribe Button"),
select: _t("Newsletter"),
init: function (field) {
return website.session.model('mail.mass_mailing.list')
.call('name_search', ['', []], { context: website.get_context() });
},
}).then(function (mailing_list_id) {
self.$target.attr("data-list-id", mailing_list_id);
});
},
drop_and_build_snippet: function() {
var self = this;
this._super();
this.on_prompt().fail(function () {
self.editor.on_remove();
});
},
start : function () {
var self = this;
this.$el.find(".js_mailing_list").on("click", _.bind(this.on_prompt, this));
this._super();
},
clean_for_save: function () {
this.$target.addClass("hidden");
},
});
})();
@@ -0,0 +1,57 @@
(function () {
'use strict';
var website = openerp.website;
website.snippet.animationRegistry.subscribe = website.snippet.Animation.extend({
selector: ".js_subscribe",
start: function (editable_mode) {
var self = this;
// set value and display button
self.$target.find("input").removeClass("hidden");
openerp.jsonRpc('/website_mass_mailing/is_subscriber', 'call', {
list_id: this.$target.data('list-id'),
}).always(function (data) {
self.$target.find('input.js_subscribe_email')
.val(data.email ? data.email : "")
.attr("disabled", data.is_subscriber && data.email.length ? "disabled" : false);
self.$target.attr("data-subscribe", data.is_subscriber ? 'on' : 'off');
self.$target.find('a.js_subscribe_btn')
.val(data.email ? data.email : "")
.attr("disabled", data.is_subscriber && data.email.length ? "disabled" : false);
self.$target.removeClass("hidden");
});
// not if editable mode to allow designer to edit alert field
if (!editable_mode) {
$('.js_subscribe > .alert').addClass("hidden");
$('.js_subscribe > .input-group-btn.hidden').removeClass("hidden");
this.$target.find('.js_subscribe_btn').on('click', function (event) {
event.preventDefault();
self.on_click();
});
}
},
on_click: function () {
var self = this;
var $email = this.$target.find(".js_subscribe_email:visible");
if ($email.length && !$email.val().match(/.+@.+/)) {
this.$target.addClass('has-error');
return false;
}
this.$target.removeClass('has-error');
openerp.jsonRpc('/website_mass_mailing/subscribe', 'call', {
'list_id': this.$target.data('list-id'),
'email': $email.length ? $email.val() : false,
}).then(function (subscribe) {
self.$target.find(".js_subscribe_email, .input-group-btn").addClass("hidden");
self.$target.find(".alert").removeClass("hidden");
self.$target.find('input.js_subscribe_email').attr("disabled", subscribe ? "disabled" : false);
self.$target.attr("data-subscribe", subscribe ? 'on' : 'off');
});
},
});
})();
+44
View File
@@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="mailing_list_subscribe" inherit_id="website.snippets" name="Subscribe to Newsletter">
<xpath expr="//div[@id='snippet_content']" position="inside">
<div>
<div class="oe_snippet_thumbnail">
<img class="oe_snippet_thumbnail_img" src="/mass_mailing/static/src/img/blocks/button_newsletter.png"/>
<span class="oe_snippet_thumbnail_title">Newsletter</span>
</div>
<div class="oe_snippet_body input-group js_subscribe"
data-list-id="0">
<input
type="email"
name="email"
class="js_subscribe_email form-control"
placeholder="your email..."/>
<span class="input-group-btn">
<a href="#" class="btn btn-primary js_subscribe_btn">Subscribe</a>
</span>
<div class="alert alert-success hidden">Thanks for your subscription!</div>
</div>
</div>
</xpath>
<xpath expr="//div[@id='snippet_options']" position="inside">
<div data-snippet-option-id='mailing_list_subscribe'
data-selector=".js_subscribe"
data-selector-siblings="p, h1, h2, h3, blockquote, .well, .panel"
>
<li>
<a href="#" class="button js_mailing_list">Change Newsletter</a>
</li>
</div>
</xpath>
</template>
</data>
</openerp>
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<template id="head" inherit_id="website.layout" name="Mail customization">
<xpath expr="//head" position="inside">
<script type="text/javascript" src="/mass_mailing/static/src/js/website_mass_mailing.editor.js" groups="base.group_website_publisher"></script>
<script type="text/javascript" src="/mass_mailing/static/src/js/website_mass_mailing.js"></script>
<link rel='stylesheet' href='/website_mail/static/src/css/website_mail.css'/>
</xpath>
</template>
</data>
</openerp>
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
"X-Poedit-Country: GREECE\n"
"X-Poedit-Language: Greek\n"
"X-Poedit-SourceCharset: utf-8\n"
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:20+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:20+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:20+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
"Language: \n"
#. module: mrp_jit
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:20+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -14,8 +14,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information
+2 -2
View File
@@ -13,8 +13,8 @@ msgstr ""
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2014-04-22 07:19+0000\n"
"X-Generator: Launchpad (build 16985)\n"
"X-Launchpad-Export-Date: 2014-05-09 06:53+0000\n"
"X-Generator: Launchpad (build 16996)\n"
#. module: mrp_jit
#: model:ir.module.module,shortdesc:mrp_jit.module_meta_information

Some files were not shown because too many files have changed in this diff Show More