diff --git a/addons/account_test/i18n/de.po b/addons/account_test/i18n/de.po
new file mode 100644
index 00000000000..157223be184
--- /dev/null
+++ b/addons/account_test/i18n/de.po
@@ -0,0 +1,241 @@
+# German translation for openobject-addons
+# Copyright (c) 2014 Rosetta Contributors and Canonical Ltd 2014
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2014.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-12-21 17:05+0000\n"
+"PO-Revision-Date: 2014-05-13 09:39+0000\n"
+"Last-Translator: Claudia Haida \n"
+"Language-Team: German \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2014-05-14 05:45+0000\n"
+"X-Generator: Launchpad (build 17002)\n"
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid ""
+"Code should always set a variable named `result` with the result of your "
+"test, that can be a list or\n"
+"a dictionary. If `result` is an empty list, it means that the test was "
+"succesful. Otherwise it will\n"
+"try to translate and print what is inside `result`.\n"
+"\n"
+"If the result of your test is a dictionary, you can set a variable named "
+"`column_order` to choose in\n"
+"what order you want to print `result`'s content.\n"
+"\n"
+"Should you need them, you can also use the following variables into your "
+"code:\n"
+" * cr: cursor to the database\n"
+" * uid: ID of the current user\n"
+"\n"
+"In any ways, the code must be legal python statements with correct "
+"indentation (if needed).\n"
+"\n"
+"Example: \n"
+" sql = '''SELECT id, name, ref, date\n"
+" FROM account_move_line \n"
+" WHERE account_id IN (SELECT id FROM account_account WHERE type "
+"= 'view')\n"
+" '''\n"
+" cr.execute(sql)\n"
+" result = cr.dictfetchall()"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_02
+msgid "Test 2: Opening a fiscal year"
+msgstr "Test2: Eröffnung eines Geschäftsjahres"
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_05
+msgid ""
+"Check that reconciled invoice for Sales/Purchases has reconciled entries for "
+"Payable and Receivable Accounts"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_03
+msgid ""
+"Check if movement lines are balanced and have the same date and period"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,name:0
+msgid "Test Name"
+msgstr ""
+
+#. module: account_test
+#: report:account.test.assert.print:0
+msgid "Accouting tests on"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_01
+msgid "Test 1: General balance"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_06
+msgid "Check that paid/reconciled invoices are not in 'Open' state"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_05_2
+msgid ""
+"Check that reconciled account moves, that define Payable and Receivable "
+"accounts, are belonging to reconciled invoices"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Tests"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,desc:0
+msgid "Test Description"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Description"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_06_1
+msgid "Check that there's no move for any account with « View » account type"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_08
+msgid "Test 9 : Accounts and partners on account moves"
+msgstr ""
+
+#. module: account_test
+#: model:ir.actions.act_window,name:account_test.action_accounting_assert
+#: model:ir.actions.report.xml,name:account_test.account_assert_test_report
+#: model:ir.ui.menu,name:account_test.menu_action_license
+msgid "Accounting Tests"
+msgstr ""
+
+#. module: account_test
+#: code:addons/account_test/report/account_test_report.py:74
+#, python-format
+msgid "The test was passed successfully"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,active:0
+msgid "Active"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_06
+msgid "Test 6 : Invoices status"
+msgstr ""
+
+#. module: account_test
+#: model:ir.model,name:account_test.model_accounting_assert_test
+msgid "accounting.assert.test"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_05
+msgid ""
+"Test 5.1 : Payable and Receivable accountant lines of reconciled invoices"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,code_exec:0
+msgid "Python code"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_07
+msgid ""
+"Check on bank statement that the Closing Balance = Starting Balance + sum of "
+"statement lines"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_07
+msgid "Test 8 : Closing balance on bank statements"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_03
+msgid "Test 3: Movement lines"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_05_2
+msgid "Test 5.2 : Reconcilied invoices and Payable/Receivable accounts"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Expression"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_04
+msgid "Test 4: Totally reconciled mouvements"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_04
+msgid "Check if the totally reconciled movements are balanced"
+msgstr ""
+
+#. module: account_test
+#: field:accounting.assert.test,sequence:0
+msgid "Sequence"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_02
+msgid ""
+"Check if the balance of the new opened fiscal year matches with last year's "
+"balance"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Python Code"
+msgstr ""
+
+#. module: account_test
+#: model:ir.actions.act_window,help:account_test.action_accounting_assert
+msgid ""
+"
\n"
+" Click to create Accounting Test.\n"
+"
\n"
+" "
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_01
+msgid "Check the balance: Debit sum = Credit sum"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,desc:account_test.account_test_08
+msgid "Check that general accounts and partners on account moves are active"
+msgstr ""
+
+#. module: account_test
+#: model:accounting.assert.test,name:account_test.account_test_06_1
+msgid "Test 7: « View » account type"
+msgstr ""
+
+#. module: account_test
+#: view:accounting.assert.test:0
+msgid "Code Help"
+msgstr ""
diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py
index 8a66d3ab075..3040fafaf2b 100644
--- a/addons/auth_signup/controllers/main.py
+++ b/addons/auth_signup/controllers/main.py
@@ -42,7 +42,7 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
return http.redirect_with_hash(request.params.get('redirect'))
return response
- @http.route('/web/signup', type='http', auth='public', website=True, multilang=True)
+ @http.route('/web/signup', type='http', auth='public', website=True)
def web_auth_signup(self, *args, **kw):
qcontext = self.get_auth_signup_qcontext()
@@ -58,7 +58,7 @@ class AuthSignupHome(openerp.addons.web.controllers.main.Home):
return request.render('auth_signup.signup', qcontext)
- @http.route('/web/reset_password', type='http', auth='public', website=True, multilang=True)
+ @http.route('/web/reset_password', type='http', auth='public', website=True)
def web_auth_reset_password(self, *args, **kw):
qcontext = self.get_auth_signup_qcontext()
diff --git a/addons/auth_signup/i18n/de.po b/addons/auth_signup/i18n/de.po
index 756a19cdf8f..c6e8f777d80 100644
--- a/addons/auth_signup/i18n/de.po
+++ b/addons/auth_signup/i18n/de.po
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
-"PO-Revision-Date: 2012-12-19 14:58+0000\n"
-"Last-Translator: Ferdinand \n"
+"PO-Revision-Date: 2014-05-14 11:25+0000\n"
+"Last-Translator: Claudia Haida \n"
"Language-Team: German \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2014-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
diff --git a/addons/calendar/calendar.py b/addons/calendar/calendar.py
index c4b584100b6..46e8b024dd6 100644
--- a/addons/calendar/calendar.py
+++ b/addons/calendar/calendar.py
@@ -1,23 +1,4 @@
# -*- coding: utf-8 -*-
-##############################################################################
-#
-# OpenERP, Open Source Business Applications
-# Copyright (c) 2011-2014 OpenERP S.A.
-#
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as
-# published by the Free Software Foundation, either version 3 of the
-# License, or (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU Affero General Public License for more details.
-#
-# You should have received a copy of the GNU Affero General Public License
-# along with this program. If not, see .
-#
-##############################################################################
import pytz
import re
@@ -188,7 +169,8 @@ class calendar_attendee(osv.Model):
res = cal.serialize()
return res
- def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False), template_xmlid='calendar_template_meeting_invitation', context=None):
+ def _send_mail_to_attendees(self, cr, uid, ids, email_from=tools.config.get('email_from', False),
+ template_xmlid='calendar_template_meeting_invitation', context=None):
"""
Send mail for event invitation to event attendees.
@param email_from: email address for user sending the mail
@@ -273,7 +255,8 @@ class calendar_attendee(osv.Model):
meeting_obj = self.pool['calendar.event']
res = self.write(cr, uid, ids, {'state': 'accepted'}, context)
for attendee in self.browse(cr, uid, ids, context=context):
- meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)), subtype="calendar.subtype_invitation", context=context)
+ meeting_obj.message_post(cr, uid, attendee.event_id.id, body=_(("%s has accepted invitation") % (attendee.cn)),
+ subtype="calendar.subtype_invitation", context=context)
return res
@@ -1410,7 +1393,7 @@ class calendar_event(osv.Model):
new_args.append(new_arg)
if not context.get('virtual_id', True):
- return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, context=context, count=count)
+ return super(calendar_event, self).search(cr, uid, new_args, offset=offset, limit=limit, order=order, count=count, context=context)
# offset, limit, order and count must be treated separately as we may need to deal with virtual ids
res = super(calendar_event, self).search(cr, uid, new_args, offset=0, limit=0, order=None, context=context, count=False)
@@ -1535,17 +1518,17 @@ class calendar_event(osv.Model):
if (values.get('start_date') or values.get('start_datetime', False)) and values.get('active', True):
the_id = new_id or (ids and int(ids[0]))
+ if the_id:
+ if attendees_create:
+ attendees_create = attendees_create[the_id]
+ mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
+ else:
+ mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
- if attendees_create:
- attendees_create = attendees_create[the_id]
- mail_to_ids = list(set(attendees_create['old_attendee_ids']) - set(attendees_create['removed_attendee_ids']))
- else:
- mail_to_ids = [att.id for att in self.browse(cr, uid, the_id, context=context).attendee_ids]
-
- if mail_to_ids:
- current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
- if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context):
- self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context)
+ if mail_to_ids:
+ current_user = self.pool['res.users'].browse(cr, uid, uid, context=context)
+ if self.pool['calendar.attendee']._send_mail_to_attendees(cr, uid, mail_to_ids, template_xmlid='calendar_template_meeting_changedate', email_from=current_user.email, context=context):
+ self.message_post(cr, uid, the_id, body=_("A email has been send to specify that the date has been changed !"), subtype="calendar.subtype_invitation", context=context)
return res or True and False
def create(self, cr, uid, vals, context=None):
@@ -1624,7 +1607,7 @@ class calendar_event(osv.Model):
continue
if r['class'] == 'private':
for f in r.keys():
- if f not in ('id', 'allday', 'start', 'stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date'):
+ if f not in ('id', 'allday', 'start', 'stop', 'duration', 'user_id', 'state', 'interval', 'count', 'recurrent_id_date', 'rrule'):
if isinstance(r[f], list):
r[f] = []
else:
diff --git a/addons/crm/__init__.py b/addons/crm/__init__.py
index e5c06c53d97..872d0390303 100644
--- a/addons/crm/__init__.py
+++ b/addons/crm/__init__.py
@@ -22,6 +22,7 @@
import crm
import crm_segmentation
import crm_lead
+import sales_team
import calendar_event
import crm_phonecall
import report
diff --git a/addons/crm/__openerp__.py b/addons/crm/__openerp__.py
index d53f785d174..15544dc3dff 100644
--- a/addons/crm/__openerp__.py
+++ b/addons/crm/__openerp__.py
@@ -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',
diff --git a/addons/crm/crm.py b/addons/crm/crm.py
index 29aa900c16d..b114a6e24fe 100644
--- a/addons/crm/crm.py
+++ b/addons/crm/crm.py
@@ -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"
diff --git a/addons/crm/crm_action_rule_demo.xml b/addons/crm/crm_action_rule_demo.xml
index 8eac91c06e2..d77211a0f12 100644
--- a/addons/crm/crm_action_rule_demo.xml
+++ b/addons/crm/crm_action_rule_demo.xml
@@ -41,7 +41,7 @@
Trueir.actions.servercode
- sales_team = self.pool.get('ir.model.data').get_object(cr, uid, 'crm', 'section_sales_department')
+ sales_team = self.pool.get('ir.model.data').get_object(cr, uid, 'sales_team', 'section_sales_department')
object.write({'section_id': sales_team.id})
diff --git a/addons/crm/crm_case_section_view.xml b/addons/crm/crm_case_section_view.xml
deleted file mode 100644
index 04ad124f652..00000000000
--- a/addons/crm/crm_case_section_view.xml
+++ /dev/null
@@ -1,331 +0,0 @@
-
-
-
-
-
-
-
- Leads
- crm.lead
- tree,form
- ['|', ('type','=','lead'), ('type','=',False)]
-
-
- {
- 'search_default_section_id': [active_id],
- 'default_section_id': active_id,
- 'default_type': 'lead',
- 'stage_type': 'lead',
- }
-
-
-
- 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.
-
- Once qualified, the lead can be converted into a business
- opportunity and/or a new customer in your address book.
-
- OpenERP helps you keep track of your sales pipeline to follow
- up potential sales and better forecast your future revenues.
-
- 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.
-
-
-
-
-
- Leads Analysis
- crm.lead.report
- {"search_default_month":1}
- graph
-
- [('type','=', 'lead'),('section_id', '=', active_id)]
- 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.
-
-
-
- Opportunities Analysis
- crm.lead.report
- graph
-
- [('type','=', 'opportunity'), ('section_id', '=', active_id)]
- 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.
-
-
-
-
-
- crm.case.section.kanban
- crm.case.section
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Use sales team to organize your different salespersons or
- departments into separate teams. Each team will work in
- its own list of opportunities.
-
- Use sales team to organize your different salespersons or
- departments into separate teams. Each team will work in
- its own list of opportunities.
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/addons/crm/crm_data.xml b/addons/crm/crm_data.xml
index 231a7124824..a5a732747ab 100644
--- a/addons/crm/crm_data.xml
+++ b/addons/crm/crm_data.xml
@@ -26,34 +26,31 @@
Email
-
- Direct Sales
- DM
+ Truesales
- Cash
-
+ Check
-
+ Credit Card
-
+ Demand Draft
-
+
diff --git a/addons/crm/crm_demo.xml b/addons/crm/crm_demo.xml
index 82bdcd8fcd9..df776f92350 100644
--- a/addons/crm/crm_demo.xml
+++ b/addons/crm/crm_demo.xml
@@ -2,26 +2,6 @@
-
-
-
-
-
-
-
-
-
- Indirect Sales
- IM
-
-
-
-
- Marketing
- SPD
-
-
-
OpenERP partnersTrue
diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py
index cfec1a3d931..7f5ae17dc33 100644
--- a/addons/crm/crm_lead.py
+++ b/addons/crm/crm_lead.py
@@ -90,7 +90,10 @@ class crm_lead(format_address, osv.osv):
def _get_default_section_id(self, cr, uid, context=None):
""" Gives default section by checking if present in the context """
- return self._resolve_section_id_from_context(cr, uid, context=context) or False
+ section_id = self._resolve_section_id_from_context(cr, uid, context=context) or False
+ if not section_id:
+ section_id = self.pool.get('res.users').browse(cr, uid, uid, context).default_section_id.id or False
+ return section_id
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
diff --git a/addons/crm/crm_lead_data.xml b/addons/crm/crm_lead_data.xml
index fef80bb8a53..9ea859937a2 100644
--- a/addons/crm/crm_lead_data.xml
+++ b/addons/crm/crm_lead_data.xml
@@ -61,7 +61,7 @@
opportunity
-
+ Telesales
-
+ Email Campaign - Services
-
+ Email Campaign - Products
-
+ Twitter Ads
-
+ Google Adwords
-
+ Banner Ads
-
+ Television
-
+ Newsletter
-
+ Product
-
+ Software
-
+ Services
-
+ Information
-
+ Design
-
+ Training
-
+ Consulting
-
+ Other
-
+
diff --git a/addons/crm/crm_lead_demo.xml b/addons/crm/crm_lead_demo.xml
index 50a5a369850..8acefd5c610 100644
--- a/addons/crm/crm_lead_demo.xml
+++ b/addons/crm/crm_lead_demo.xml
@@ -20,7 +20,7 @@
1
-
+ Hello,
@@ -55,7 +55,7 @@ Could you please send me the details ?1
-
+
@@ -84,7 +84,7 @@ Could you please send me the details ?2
-
+
@@ -105,7 +105,7 @@ Could you please send me the details ?
2
-
+
@@ -130,7 +130,7 @@ Could you please send me the details ?
2
-
+ Hi,
@@ -157,7 +157,7 @@ Contact: +1 813 494 50052
-
+
@@ -175,7 +175,7 @@ Contact: +1 813 494 5005
0
-
+
@@ -194,7 +194,7 @@ Contact: +1 813 494 5005
1
-
+
@@ -213,7 +213,7 @@ Contact: +1 813 494 5005
2
-
+
@@ -231,7 +231,7 @@ Contact: +1 813 494 5005
2
-
+ Hi,
@@ -254,7 +254,7 @@ Andrew2
-
+
@@ -272,7 +272,7 @@ Andrew
2
-
+
@@ -310,7 +310,7 @@ Andrew
Meeting for pricing information.
-
+
@@ -335,7 +335,7 @@ Andrew
Send Catalogue by Email
-
+
@@ -356,7 +356,7 @@ Andrew
Call to ask system requirement
-
+
@@ -383,7 +383,7 @@ Andrew
Convert to quote
-
+
@@ -408,7 +408,7 @@ Andrew
Send price list regarding our interventions
-
+
@@ -435,7 +435,7 @@ Andrew
Call to define real needs about training
-
+
@@ -462,7 +462,7 @@ Andrew
Ask for the good receprion of the proposition
-
+
@@ -479,7 +479,7 @@ Andrew
2
-
+
@@ -493,7 +493,7 @@ Andrew
2
-
+
@@ -511,7 +511,7 @@ Andrew
1
-
+
@@ -526,7 +526,7 @@ Andrew
0
-
+
@@ -545,7 +545,7 @@ Andrew
0
-
+
@@ -568,7 +568,7 @@ Andrew
2Conf call with technical service
-
+
@@ -592,7 +592,7 @@ Andrew
Send Catalogue by Email
-
+
diff --git a/addons/crm/crm_lead_menu.xml b/addons/crm/crm_lead_menu.xml
index dffe03dd4b3..60dc307cc0f 100644
--- a/addons/crm/crm_lead_menu.xml
+++ b/addons/crm/crm_lead_menu.xml
@@ -81,7 +81,7 @@
action="crm_case_category_act_leads_all"/>
+ groups="base.group_sale_salesman,base.group_sale_manager"/>
diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml
index 2441e4197c4..995a1ddea43 100644
--- a/addons/crm/crm_lead_view.xml
+++ b/addons/crm/crm_lead_view.xml
@@ -1,11 +1,9 @@
-
-
Stage - Search
@@ -570,12 +568,12 @@
help="Opportunities that are assigned to me"/>
+ help="Opportunities that are assigned to any sales teams I am member of" groups="base.group_multi_salesteams"/>
-
+
diff --git a/addons/crm/crm_phonecall_data.xml b/addons/crm/crm_phonecall_data.xml
index 0e2bb6511c5..ce82a9a7b51 100644
--- a/addons/crm/crm_phonecall_data.xml
+++ b/addons/crm/crm_phonecall_data.xml
@@ -6,13 +6,13 @@
-->
Inbound
-
+ Outbound
-
+
diff --git a/addons/crm/crm_phonecall_demo.xml b/addons/crm/crm_phonecall_demo.xml
index 90cda34cc01..8563a38b075 100644
--- a/addons/crm/crm_phonecall_demo.xml
+++ b/addons/crm/crm_phonecall_demo.xml
@@ -11,7 +11,7 @@
Left the messagedone+34 934 340 230
-
+
@@ -24,7 +24,7 @@
Need more information on the proposed dealdone+44 121 690 4596
-
+
@@ -37,7 +37,7 @@
Ask for convenient time of meetingopen+1 786 525 0724
-
+
@@ -50,7 +50,7 @@
done(077) 582-4035(077) 341-3591
-
+
@@ -63,7 +63,7 @@
More information on the proposed dealpending+1 312 349 2324
-
+
@@ -74,7 +74,7 @@
Proposal for discount offeropen+34 230 953 485
-
+
diff --git a/addons/crm/report/crm_lead_report_view.xml b/addons/crm/report/crm_lead_report_view.xml
index 1dda471c2f2..239579a70c2 100644
--- a/addons/crm/report/crm_lead_report_view.xml
+++ b/addons/crm/report/crm_lead_report_view.xml
@@ -81,7 +81,7 @@
-
+
diff --git a/addons/crm/report/crm_phonecall_report_view.xml b/addons/crm/report/crm_phonecall_report_view.xml
index 32ae9704064..98f4a58d7c4 100644
--- a/addons/crm/report/crm_phonecall_report_view.xml
+++ b/addons/crm/report/crm_phonecall_report_view.xml
@@ -47,7 +47,7 @@
-
+
diff --git a/addons/crm/res_config.py b/addons/crm/res_config.py
index 1cd31cd0dd0..d48e66e286c 100644
--- a/addons/crm/res_config.py
+++ b/addons/crm/res_config.py
@@ -27,33 +27,6 @@ class crm_configuration(osv.TransientModel):
_name = 'sale.config.settings'
_inherit = ['sale.config.settings', 'fetchmail.config.settings']
- def set_group_multi_salesteams(self, cr, uid, ids, context=None):
- """ This method is automatically called by res_config as it begins
- with set. It is used to implement the 'one group or another'
- behavior. We have to perform some group manipulation by hand
- because in res_config.execute(), set_* methods are called
- after group_*; therefore writing on an hidden res_config file
- could not work.
- If group_multi_salesteams is checked: remove group_mono_salesteams
- from group_user, remove the users. Otherwise, just add
- group_mono_salesteams in group_user.
- The inverse logic about group_multi_salesteams is managed by the
- normal behavior of 'group_multi_salesteams' field.
- """
- def ref(xml_id):
- mod, xml = xml_id.split('.', 1)
- return self.pool['ir.model.data'].get_object(cr, uid, mod, xml, context)
-
- for obj in self.browse(cr, uid, ids, context=context):
- config_group = ref('base.group_mono_salesteams')
- base_group = ref('base.group_user')
- if obj.group_multi_salesteams:
- base_group.write({'implied_ids': [(3, config_group.id)]})
- config_group.write({'users': [(3, u.id) for u in base_group.users]})
- else:
- base_group.write({'implied_ids': [(4, config_group.id)]})
- return True
-
_columns = {
'group_fund_raising': fields.boolean("Manage Fund Raising",
implied_group='crm.group_fund_raising',
@@ -64,9 +37,6 @@ class crm_configuration(osv.TransientModel):
'module_crm_helpdesk': fields.boolean("Manage Helpdesk and Support",
help='Allows you to communicate with Customer, process Customer query, and provide better help and support.\n'
'-This installs the module crm_helpdesk.'),
- 'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams",
- implied_group='base.group_multi_salesteams',
- help="""Allows you to use Sales Teams to manage your leads and opportunities."""),
'alias_prefix': fields.char('Default Alias Name for Leads'),
'alias_domain' : fields.char('Alias Domain'),
'group_scheduled_calls': fields.boolean("Schedule calls to manage call center",
diff --git a/addons/crm/res_config_view.xml b/addons/crm/res_config_view.xml
index ce3c8242f99..cc7bb6b2e4a 100644
--- a/addons/crm/res_config_view.xml
+++ b/addons/crm/res_config_view.xml
@@ -32,17 +32,6 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/addons/crm/sales_team.py b/addons/crm/sales_team.py
new file mode 100644
index 00000000000..8a1e9f938af
--- /dev/null
+++ b/addons/crm/sales_team.py
@@ -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
diff --git a/addons/crm/sales_team_view.xml b/addons/crm/sales_team_view.xml
new file mode 100644
index 00000000000..7e0eb2e8ad6
--- /dev/null
+++ b/addons/crm/sales_team_view.xml
@@ -0,0 +1,185 @@
+
+
+
+
+
+
+
+ Leads
+ crm.lead
+ tree,form
+ ['|', ('type','=','lead'), ('type','=',False)]
+
+
+ {
+ 'search_default_section_id': [active_id],
+ 'default_section_id': active_id,
+ 'default_type': 'lead',
+ 'stage_type': 'lead',
+ }
+
+
+
+ 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.
+
+ Once qualified, the lead can be converted into a business
+ opportunity and/or a new customer in your address book.
+
+ OpenERP helps you keep track of your sales pipeline to follow
+ up potential sales and better forecast your future revenues.
+
+ 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.
+
+
+
+
+
+ Leads Analysis
+ crm.lead.report
+ {"search_default_month":1}
+ graph
+
+ [('type','=', 'lead'),('section_id', '=', active_id)]
+ 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.
+
+
+
+ Opportunities Analysis
+ crm.lead.report
+ graph
+
+ [('type','=', 'opportunity'), ('section_id', '=', active_id)]
+ 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.
+
+
+
+ crm.case.section.kanban
+ crm.case.section
+
+
+
+
+
+
+
+
+
+
+
+
+ Click to create a quotation, the first step of a new sale.
+
+ OpenERP will help you handle efficiently the complete sale flow:
+ from the quotation to the sales order, the
+ delivery, the invoicing and the payment collection.
+
+ The social feature helps you organize discussions on each sales
+ order, and allow your customers to keep track of the evolution
+ of the sales order.
+
+
+
+
+
+ Invoices
+ account.invoice
+ form
+ tree,form,calendar,graph
+
+ [
+ ('state', 'not in', ['draft', 'cancel']),
+ ('type', '=', 'out_invoice')]
+ {
+ 'search_default_section_id': [active_id],
+ 'default_section_id': active_id,
+ 'default_type':'out_invoice',
+ 'type':'out_invoice',
+ 'journal_type': 'sale',
+ }
+
+
+
+
+
+ 1
+ tree
+
+
+
+
+ 2
+ form
+
+
+
+
+
+ Quotations Analysis
+ sale.report
+ graph
+ [('state','=','draft'),('section_id', '=', active_id)]
+ {'search_default_order_month':1}
+ This report performs analysis on your quotations. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application.
+
+
+
+ Sales Analysis
+ sale.report
+ graph
+ [('state','not in',('draft','sent','cancel')),('section_id', '=', active_id)]
+ {'search_default_order_month':1}
+ This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application.
+
+
+
+ Invoices Analysis
+ account.invoice.report
+ graph
+ [('section_id', '=', active_id),('state', 'not in', ['draft', 'cancel'])]
+ {'search_default_month':1}
+ From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs.
+
diff --git a/addons/sale/sales_team.py b/addons/sale/sales_team.py
new file mode 100644
index 00000000000..ba9f3e3f5e1
--- /dev/null
+++ b/addons/sale/sales_team.py
@@ -0,0 +1,66 @@
+# -*- coding: utf-8 -*-
+
+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.osv):
+ _inherit = 'crm.case.section'
+
+ def _get_sale_orders_data(self, cr, uid, ids, field_name, arg, context=None):
+ obj = self.pool.get('sale.order')
+ res = dict.fromkeys(ids, False)
+ month_begin = date.today().replace(day=1)
+ date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ for id in ids:
+ res[id] = dict()
+ created_domain = [('section_id', '=', id), ('state', '=', 'draft'), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
+ res[id]['monthly_quoted'] = self.__get_bar_values(cr, uid, obj, created_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
+ validated_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'sent', 'cancel']), ('date_order', '>=', date_begin), ('date_order', '<=', date_end)]
+ res[id]['monthly_confirmed'] = self.__get_bar_values(cr, uid, obj, validated_domain, ['amount_total', 'date_order'], 'amount_total', 'date_order', context=context)
+ return res
+
+ def _get_invoices_data(self, cr, uid, ids, field_name, arg, context=None):
+ obj = self.pool.get('account.invoice.report')
+ res = dict.fromkeys(ids, False)
+ month_begin = date.today().replace(day=1)
+ date_begin = (month_begin - relativedelta.relativedelta(months=self._period_number - 1)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ date_end = month_begin.replace(day=calendar.monthrange(month_begin.year, month_begin.month)[1]).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
+ for id in ids:
+ created_domain = [('section_id', '=', id), ('state', 'not in', ['draft', 'cancel']), ('date', '>=', date_begin), ('date', '<=', date_end)]
+ res[id] = self.__get_bar_values(cr, uid, obj, created_domain, ['price_total', 'date'], 'price_total', 'date', context=context)
+ return res
+
+ _columns = {
+ 'use_quotations': fields.boolean('Opportunities', help="Check this box to manage quotations in this sales team."),
+ 'invoiced_forecast': fields.integer(string='Invoice Forecast',
+ help="Forecast of the invoice revenue for the current month. This is the amount the sales \n"
+ "team should invoice this month. It is used to compute the progression ratio \n"
+ " of the current and forecast revenue on the kanban view."),
+ 'invoiced_target': fields.integer(string='Invoice Target',
+ help="Target of invoice revenue for the current month. This is the amount the sales \n"
+ "team estimates to be able to invoice this month."),
+ 'monthly_quoted': fields.function(_get_sale_orders_data,
+ type='string', readonly=True, multi='_get_sale_orders_data',
+ string='Rate of created quotation per duration'),
+ 'monthly_confirmed': fields.function(_get_sale_orders_data,
+ type='string', readonly=True, multi='_get_sale_orders_data',
+ string='Rate of validate sales orders per duration'),
+ 'monthly_invoiced': fields.function(_get_invoices_data,
+ type='string', readonly=True,
+ string='Rate of sent invoices per duration'),
+ }
+
+ _defaults = {
+ 'use_quotations': True,
+ }
+
+ def action_forecast(self, cr, uid, id, value, context=None):
+ return self.write(cr, uid, [id], {'invoiced_forecast': round(float(value))}, context=context)
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/sale/sales_team_view.xml b/addons/sale/sales_team_view.xml
new file mode 100644
index 00000000000..922caa9f033
--- /dev/null
+++ b/addons/sale/sales_team_view.xml
@@ -0,0 +1,78 @@
+
+
+
+
+ crm.case.section.form
+ crm.case.section
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ crm.case.section.kanban
+ crm.case.section
+
+
+
+
+
+
+
+
+
+
+
+
+
- Click to create a quotation, the first step of a new sale.
-
- OpenERP will help you handle efficiently the complete sale flow:
- from the quotation to the sales order, the
- delivery, the invoicing and the payment collection.
-
- The social feature helps you organize discussions on each sales
- order, and allow your customers to keep track of the evolution
- of the sales order.
-
-
-
-
-
- Invoices
- account.invoice
- form
- tree,form,calendar,graph
-
- [
- ('state', 'not in', ['draft', 'cancel']),
- ('type', '=', 'out_invoice')]
- {
- 'search_default_section_id': [active_id],
- 'default_section_id': active_id,
- 'default_type':'out_invoice',
- 'type':'out_invoice',
- 'journal_type': 'sale',
- }
-
-
-
-
-
- 1
- tree
-
-
-
-
- 2
- form
-
-
-
-
-
- Quotations Analysis
- sale.report
- graph
- [('state','=','draft'),('section_id', '=', active_id)]
- {'search_default_order_month':1}
- This report performs analysis on your quotations. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application.
-
-
-
- Sales Analysis
- sale.report
- graph
- [('state','not in',('draft','sent','cancel')),('section_id', '=', active_id)]
- {'search_default_order_month':1}
- This report performs analysis on your sales orders. Analysis check your sales revenues and sort it by different group criteria (salesman, partner, product, etc.) Use this report to perform analysis on sales not having invoiced yet. If you want to analyse your turnover, you should use the Invoice Analysis report in the Accounting application.
-
-
-
- Invoices Analysis
- account.invoice.report
- graph
- [('section_id', '=', active_id),('state', 'not in', ['draft', 'cancel'])]
- {'search_default_month':1}
- From this report, you can have an overview of the amount invoiced to your customer. The tool search can also be used to personalise your Invoices reports and so, match this analysis to your needs.
-
-
-
- crm.case.section.form
- crm.case.section
-
-
-
-
-
-
-
-
-
-
-
-
- crm.case.section.kanban
- crm.case.section
-
-
-
-
-
-
-
-
-
-
-
-
- Define an invoicing target in the sales team settings to see the period's achievement and forecast at a glance.
-
-
-
-
-
diff --git a/addons/sale_crm/report/__init__.py b/addons/sales_team/__init__.py
similarity index 94%
rename from addons/sale_crm/report/__init__.py
rename to addons/sales_team/__init__.py
index d86b3e66de9..3b2a8f41267 100644
--- a/addons/sale_crm/report/__init__.py
+++ b/addons/sales_team/__init__.py
@@ -18,9 +18,7 @@
# along with this program. If not, see .
#
##############################################################################
-
-import sales_crm_account_invoice_report
-import sale_report
+import sales_team
+import res_config
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-
diff --git a/addons/sales_team/__openerp__.py b/addons/sales_team/__openerp__.py
new file mode 100644
index 00000000000..c9cb20cd401
--- /dev/null
+++ b/addons/sales_team/__openerp__.py
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-2010 Tiny SPRL ().
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+{
+ 'name': 'Sale Team',
+ 'version': '1.0',
+ 'author': 'OpenERP SA',
+ 'category': 'Sales Management',
+ 'summary': 'Sales Team',
+ 'description': """
+Using this application you can manage Sales Team with CRM and/or Sales
+=======================================================================
+ """,
+ 'website': 'http://www.openerp.com',
+ 'depends': ['base','mail','web_kanban_sparkline',],
+ 'data': ['security/sales_team_security.xml',
+ 'security/ir.model.access.csv',
+ 'res_config_view.xml',
+ 'sales_team_data.xml',
+ 'sales_team.xml',],
+ 'demo': ['sales_team_demo.xml'],
+ 'css': ['static/src/css/sales_team.css'],
+ 'installable': True,
+ 'auto_install': True,
+}
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
diff --git a/addons/sales_team/res_config.py b/addons/sales_team/res_config.py
new file mode 100644
index 00000000000..3081a2e918d
--- /dev/null
+++ b/addons/sales_team/res_config.py
@@ -0,0 +1,41 @@
+# -*- coding: utf-8 -*-
+
+from openerp.osv import fields, osv
+
+
+class sales_team_configuration(osv.TransientModel):
+ _name = 'sale.config.settings'
+ _inherit = ['sale.config.settings']
+
+ def set_group_multi_salesteams(self, cr, uid, ids, context=None):
+ """ This method is automatically called by res_config as it begins
+ with set. It is used to implement the 'one group or another'
+ behavior. We have to perform some group manipulation by hand
+ because in res_config.execute(), set_* methods are called
+ after group_*; therefore writing on an hidden res_config file
+ could not work.
+ If group_multi_salesteams is checked: remove group_mono_salesteams
+ from group_user, remove the users. Otherwise, just add
+ group_mono_salesteams in group_user.
+ The inverse logic about group_multi_salesteams is managed by the
+ normal behavior of 'group_multi_salesteams' field.
+ """
+ def ref(xml_id):
+ mod, xml = xml_id.split('.', 1)
+ return self.pool['ir.model.data'].get_object(cr, uid, mod, xml, context)
+
+ for obj in self.browse(cr, uid, ids, context=context):
+ config_group = ref('base.group_mono_salesteams')
+ base_group = ref('base.group_user')
+ if obj.group_multi_salesteams:
+ base_group.write({'implied_ids': [(3, config_group.id)]})
+ config_group.write({'users': [(3, u.id) for u in base_group.users]})
+ else:
+ base_group.write({'implied_ids': [(4, config_group.id)]})
+ return True
+
+ _columns = {
+ 'group_multi_salesteams': fields.boolean("Organize Sales activities into multiple Sales Teams",
+ implied_group='base.group_multi_salesteams',
+ help="""Allows you to use Sales Teams to manage your leads and opportunities."""),
+ }
diff --git a/addons/sales_team/res_config_view.xml b/addons/sales_team/res_config_view.xml
new file mode 100644
index 00000000000..88a51e78cda
--- /dev/null
+++ b/addons/sales_team/res_config_view.xml
@@ -0,0 +1,28 @@
+
+
+
+
+ crm settings
+ sale.config.settings
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sales_team/sales_team.py b/addons/sales_team/sales_team.py
new file mode 100644
index 00000000000..ed3a3876231
--- /dev/null
+++ b/addons/sales_team/sales_team.py
@@ -0,0 +1,130 @@
+# -*- coding: utf-8 -*-
+##############################################################################
+#
+# OpenERP, Open Source Management Solution
+# Copyright (C) 2004-today OpenERP SA ()
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as
+# published by the Free Software Foundation, either version 3 of the
+# License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+##############################################################################
+from datetime import date, datetime
+from dateutil import relativedelta
+from openerp import tools
+from openerp.osv import fields, osv
+
+
+class crm_case_section(osv.osv):
+ _name = "crm.case.section"
+ _inherit = ['mail.thread', 'ir.needaction_mixin']
+ _description = "Sales Teams"
+ _order = "complete_name"
+ _period_number = 5
+
+ def get_full_name(self, cr, uid, ids, field_name, arg, context=None):
+ return dict(self.name_get(cr, uid, ids, context=context))
+
+ def __get_bar_values(self, cr, uid, obj, domain, read_fields, value_field, groupby_field, context=None):
+ """ Generic method to generate data for bar chart values using SparklineBarWidget.
+ This method performs obj.read_group(cr, uid, domain, read_fields, groupby_field).
+
+ :param obj: the target model (i.e. crm_lead)
+ :param domain: the domain applied to the read_group
+ :param list read_fields: the list of fields to read in the read_group
+ :param str value_field: the field used to compute the value of the bar slice
+ :param str groupby_field: the fields used to group
+
+ :return list section_result: a list of dicts: [
+ { 'value': (int) bar_column_value,
+ 'tootip': (str) bar_column_tooltip,
+ }
+ ]
+ """
+ month_begin = date.today().replace(day=1)
+ section_result = [{
+ 'value': 0,
+ 'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B %Y'),
+ } for i in range(self._period_number - 1, -1, -1)]
+ group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
+ pattern = tools.DEFAULT_SERVER_DATE_FORMAT if obj.fields_get(cr, uid, groupby_field)[groupby_field]['type'] == 'date' else tools.DEFAULT_SERVER_DATETIME_FORMAT
+ for group in group_obj:
+ group_begin_date = datetime.strptime(group['__domain'][0][2], pattern)
+ month_delta = relativedelta.relativedelta(month_begin, group_begin_date)
+ section_result[self._period_number - (month_delta.months + 1)] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field, 0)}
+ return section_result
+
+ _columns = {
+ 'name': fields.char('Sales Team', size=64, required=True, translate=True),
+ 'complete_name': fields.function(get_full_name, type='char', size=256, readonly=True, store=True),
+ 'code': fields.char('Code', size=8),
+ 'active': fields.boolean('Active', help="If the active field is set to "\
+ "true, it will allow you to hide the sales team without removing it."),
+ 'change_responsible': fields.boolean('Reassign Escalated', help="When escalating to this team override the salesman with the team leader."),
+ 'user_id': fields.many2one('res.users', 'Team Leader'),
+ 'member_ids': fields.many2many('res.users', 'sale_member_rel', 'section_id', 'member_id', 'Team Members'),
+ 'reply_to': fields.char('Reply-To', size=64, help="The email address put in the 'Reply-To' of all emails sent by OpenERP about cases in this sales team"),
+ 'parent_id': fields.many2one('crm.case.section', 'Parent Team'),
+ 'child_ids': fields.one2many('crm.case.section', 'parent_id', 'Child Teams'),
+ 'note': fields.text('Description'),
+ 'working_hours': fields.float('Working Hours', digits=(16, 2)),
+ 'color': fields.integer('Color Index'),
+ }
+
+ _defaults = {
+ 'active': 1,
+ }
+
+ _sql_constraints = [
+ ('code_uniq', 'unique (code)', 'The code of the sales team must be unique !')
+ ]
+
+ _constraints = [
+ (osv.osv._check_recursion, 'Error ! You cannot create recursive Sales team.', ['parent_id'])
+ ]
+
+ def name_get(self, cr, uid, ids, context=None):
+ """Overrides orm name_get method"""
+ if not isinstance(ids, list):
+ ids = [ids]
+ res = []
+ if not ids:
+ return res
+ reads = self.read(cr, uid, ids, ['name', 'parent_id'], context)
+
+ for record in reads:
+ name = record['name']
+ if record['parent_id']:
+ name = record['parent_id'][1] + ' / ' + name
+ res.append((record['id'], name))
+ return res
+
+
+class res_partner(osv.Model):
+ _inherit = 'res.partner'
+ _columns = {
+ 'section_id': fields.many2one('crm.case.section', 'Sales Team'),
+ }
+
+
+class res_users(osv.Model):
+ _inherit = 'res.users'
+ _columns = {
+ 'default_section_id': fields.many2one('crm.case.section', 'Default Sales Team'),
+ }
+
+ def __init__(self, pool, cr):
+ init_res = super(res_users, self).__init__(pool, cr)
+ # duplicate list to avoid modifying the original reference
+ self.SELF_WRITEABLE_FIELDS = list(self.SELF_WRITEABLE_FIELDS)
+ self.SELF_WRITEABLE_FIELDS.extend(['default_section_id'])
+ return init_res
diff --git a/addons/sales_team/sales_team.xml b/addons/sales_team/sales_team.xml
new file mode 100644
index 00000000000..5ac8e24d8bb
--- /dev/null
+++ b/addons/sales_team/sales_team.xml
@@ -0,0 +1,211 @@
+
+
+
+
+
+
+ Users Preferences
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+
+ res.users.preferences.form
+ res.users
+
+
+
+
+
+
+
+
+
+
+
+
+
+ crm.case.section.kanban
+ crm.case.section
+
+
+
+
+
+
+
+
+
+
+ Use sales team to organize your different salespersons or
+ departments into separate teams. Each team will work in
+ its own list of opportunities.
+
+ Use sales team to organize your different salespersons or
+ departments into separate teams. Each team will work in
+ its own list of opportunities.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons/sales_team/sales_team_data.xml b/addons/sales_team/sales_team_data.xml
new file mode 100644
index 00000000000..2f296143ec0
--- /dev/null
+++ b/addons/sales_team/sales_team_data.xml
@@ -0,0 +1,10 @@
+
+
+
+
+ Direct Sales
+ DM
+
+
+
+
diff --git a/addons/sales_team/sales_team_demo.xml b/addons/sales_team/sales_team_demo.xml
new file mode 100644
index 00000000000..0e698d8f1fa
--- /dev/null
+++ b/addons/sales_team/sales_team_demo.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
+
+ Indirect Sales
+ IM
+
+
+
+
+ Marketing
+ SPD
+
+
+
+
\ No newline at end of file
diff --git a/addons/sales_team/security/ir.model.access.csv b/addons/sales_team/security/ir.model.access.csv
new file mode 100644
index 00000000000..f983a2abb9d
--- /dev/null
+++ b/addons/sales_team/security/ir.model.access.csv
@@ -0,0 +1,4 @@
+id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
+access_crm_case_section,crm.case.section,model_crm_case_section,base.group_user,1,0,0,0
+access_crm_case_section_user,crm.case.section.user,model_crm_case_section,base.group_sale_salesman,1,0,0,0
+access_crm_case_section_manager,crm.case.section.manager,model_crm_case_section,base.group_sale_manager,1,1,1,1
diff --git a/addons/sales_team/security/sales_team_security.xml b/addons/sales_team/security/sales_team_security.xml
new file mode 100644
index 00000000000..d9002f8332f
--- /dev/null
+++ b/addons/sales_team/security/sales_team_security.xml
@@ -0,0 +1,17 @@
+
+
+
+
+ Do Not Use Sales Teams
+
+
+
+
+
+
+
+ Manage Sales Teams
+
+
+
+
diff --git a/addons/sales_team/static/src/css/Makefile b/addons/sales_team/static/src/css/Makefile
new file mode 100644
index 00000000000..523f4a1e81e
--- /dev/null
+++ b/addons/sales_team/static/src/css/Makefile
@@ -0,0 +1,2 @@
+sales_team.css: sales_team.sass
+ sass --trace -t expanded sales_team.sass:sales_team.css
diff --git a/addons/crm/static/src/css/crm.css b/addons/sales_team/static/src/css/sales_team.css
similarity index 100%
rename from addons/crm/static/src/css/crm.css
rename to addons/sales_team/static/src/css/sales_team.css
diff --git a/addons/crm/static/src/css/crm.sass b/addons/sales_team/static/src/css/sales_team.sass
similarity index 100%
rename from addons/crm/static/src/css/crm.sass
rename to addons/sales_team/static/src/css/sales_team.sass
diff --git a/addons/crm/static/src/js/crm_case_section.js b/addons/sales_team/static/src/js/sales_team.js
similarity index 100%
rename from addons/crm/static/src/js/crm_case_section.js
rename to addons/sales_team/static/src/js/sales_team.js
diff --git a/addons/stock_picking_wave/controllers/main.py b/addons/stock_picking_wave/controllers/main.py
index 9de14934828..8aab4905933 100644
--- a/addons/stock_picking_wave/controllers/main.py
+++ b/addons/stock_picking_wave/controllers/main.py
@@ -4,7 +4,7 @@ from openerp.addons.web.http import request
class picking_wave_report(http.Controller):
@http.route('/report/stock_picking_wave.report_pickingwave/', type='http', auth='user',
- website=True, multilang=True)
+ website=True)
def report_picking_wave(self, ids):
self.cr, self.uid, self.pool = request.cr, request.uid, request.registry
ids = [int(i) for i in ids.split(',')]
diff --git a/addons/survey/controllers/main.py b/addons/survey/controllers/main.py
index 69fa12222c5..ef4fd05302a 100644
--- a/addons/survey/controllers/main.py
+++ b/addons/survey/controllers/main.py
@@ -76,7 +76,7 @@ class WebsiteSurvey(http.Controller):
# Survey start
@http.route(['/survey/start/',
'/survey/start//'],
- type='http', auth='public', multilang=True, website=True)
+ type='http', auth='public', website=True)
def start_survey(self, survey, token=None, **post):
cr, uid, context = request.cr, request.uid, request.context
survey_obj = request.registry['survey.survey']
@@ -123,7 +123,7 @@ class WebsiteSurvey(http.Controller):
# Survey displaying
@http.route(['/survey/fill//',
'/survey/fill///'],
- type='http', auth='public', multilang=True, website=True)
+ type='http', auth='public', website=True)
def fill_survey(self, survey, token, prev=None, **post):
'''Display and validates a survey'''
cr, uid, context = request.cr, request.uid, request.context
@@ -173,7 +173,7 @@ class WebsiteSurvey(http.Controller):
# AJAX prefilling of a survey
@http.route(['/survey/prefill//',
'/survey/prefill///'],
- type='http', auth='public', multilang=True, website=True)
+ type='http', auth='public', website=True)
def prefill(self, survey, token, page=None, **post):
cr, uid, context = request.cr, request.uid, request.context
user_input_line_obj = request.registry['survey.user_input_line']
@@ -216,7 +216,7 @@ class WebsiteSurvey(http.Controller):
# AJAX scores loading for quiz correction mode
@http.route(['/survey/scores//'],
- type='http', auth='public', multilang=True, website=True)
+ type='http', auth='public', website=True)
def get_scores(self, survey, token, page=None, **post):
cr, uid, context = request.cr, request.uid, request.context
user_input_line_obj = request.registry['survey.user_input_line']
@@ -234,7 +234,7 @@ class WebsiteSurvey(http.Controller):
# AJAX submission of a page
@http.route(['/survey/submit/'],
- type='http', auth='public', multilang=True, website=True)
+ type='http', auth='public', website=True)
def submit(self, survey, **post):
_logger.debug('Incoming data: %s', post)
page_id = int(post['page_id'])
@@ -284,7 +284,7 @@ class WebsiteSurvey(http.Controller):
# Printing routes
@http.route(['/survey/print/',
'/survey/print//'],
- type='http', auth='public', multilang=True, website=True)
+ type='http', auth='public', website=True)
def print_survey(self, survey, token=None, **post):
'''Display an survey in printable view; if is set, it will
grab the answers of the user_input_id that has .'''
@@ -295,7 +295,7 @@ class WebsiteSurvey(http.Controller):
'quizz_correction': True if survey.quizz_mode and token else False})
@http.route(['/survey/results/'],
- type='http', auth='user', multilang=True, website=True)
+ type='http', auth='user', website=True)
def survey_reporting(self, survey, token=None, **post):
'''Display survey Results & Statistics for given survey.'''
result_template, current_filters, filter_display_data, filter_finish = 'survey.result', [], [], False
diff --git a/addons/website/controllers/main.py b/addons/website/controllers/main.py
index 4180ecf4c69..a4faa9c361f 100644
--- a/addons/website/controllers/main.py
+++ b/addons/website/controllers/main.py
@@ -32,7 +32,7 @@ class Website(openerp.addons.web.controllers.main.Home):
#------------------------------------------------------
# View
#------------------------------------------------------
- @http.route('/', type='http', auth="public", website=True, multilang=True)
+ @http.route('/', type='http', auth="public", website=True)
def index(self, **kw):
page = 'homepage'
try:
@@ -47,12 +47,12 @@ class Website(openerp.addons.web.controllers.main.Home):
pass
return self.page(page)
- @http.route(website=True, auth="public", multilang=True)
+ @http.route(website=True, auth="public")
def web_login(self, *args, **kw):
# TODO: can't we just put auth=public, ... in web client ?
return super(Website, self).web_login(*args, **kw)
- @http.route('/page/', type='http', auth="public", website=True, multilang=True)
+ @http.route('/page/', type='http', auth="public", website=True)
def page(self, page, **opt):
values = {
'path': page,
diff --git a/addons/website/models/ir_http.py b/addons/website/models/ir_http.py
index d2b5c437ea6..57e7d284922 100644
--- a/addons/website/models/ir_http.py
+++ b/addons/website/models/ir_http.py
@@ -51,6 +51,8 @@ class ir_http(orm.AbstractModel):
# in all cases, website processes them
request.website_enabled = True
+ request.website_multilang = request.website_enabled and func and func.routing.get('multilang', True)
+
if request.website_enabled:
if func:
self._authenticate(func.routing['auth'])
@@ -67,6 +69,10 @@ class ir_http(orm.AbstractModel):
if path[1] in langs:
request.lang = request.context['lang'] = path.pop(1)
path = '/'.join(path) or '/'
+ if request.lang == request.website.default_lang_code:
+ # If language is in the url and it is the default language, redirect
+ # to url without language so google doesn't see duplicate content
+ return request.redirect(path + request.httprequest.query_string)
return self.reroute(path)
return self._handle_exception(code=404)
return super(ir_http, self)._dispatch()
diff --git a/addons/website/models/website.py b/addons/website/models/website.py
index 5e339a21d03..35bae23a7da 100644
--- a/addons/website/models/website.py
+++ b/addons/website/models/website.py
@@ -72,7 +72,7 @@ def is_multilang_url(local_url, langs=None):
query_string = url[1] if len(url) > 1 else None
router = request.httprequest.app.get_db_router(request.db).bind('')
func = router.match(path, query_args=query_string)[0]
- return func.routing.get('multilang', False)
+ return func.routing.get('website', False) and func.routing.get('multilang', True)
except Exception:
return False
diff --git a/addons/website/views/website_templates.xml b/addons/website/views/website_templates.xml
index 890d6080039..751a37b3455 100644
--- a/addons/website/views/website_templates.xml
+++ b/addons/website/views/website_templates.xml
@@ -16,8 +16,6 @@
-
-
@@ -83,10 +81,12 @@
and main_object.website_meta_keywords or website_meta_keywords"/>
-
-
-
-
+
+
+
+
+
+
@@ -177,7 +177,7 @@
their performance.