[MRG] merge with lp:openobject-addons
bzr revid: tpa@tinyerp.com-20131011061203-z13xuqw5fzznr6qc
|
@ -1416,14 +1416,17 @@ class account_move(osv.osv):
|
|||
l[2]['period_id'] = default_period
|
||||
context['period_id'] = default_period
|
||||
|
||||
if 'line_id' in vals:
|
||||
if vals.get('line_id', False):
|
||||
c = context.copy()
|
||||
c['novalidate'] = True
|
||||
c['period_id'] = vals['period_id'] if 'period_id' in vals else self._get_period(cr, uid, context)
|
||||
c['journal_id'] = vals['journal_id']
|
||||
if 'date' in vals: c['date'] = vals['date']
|
||||
result = super(account_move, self).create(cr, uid, vals, c)
|
||||
self.validate(cr, uid, [result], context)
|
||||
tmp = self.validate(cr, uid, [result], context)
|
||||
journal = self.pool.get('account.journal').browse(cr, uid, vals['journal_id'], context)
|
||||
if journal.entry_posted and tmp:
|
||||
self.button_validate(cr,uid, [result], context)
|
||||
else:
|
||||
result = super(account_move, self).create(cr, uid, vals, context)
|
||||
return result
|
||||
|
|
|
@ -349,6 +349,7 @@
|
|||
<page string="Invoice Lines">
|
||||
<field name="invoice_line" nolabel="1" widget="one2many_list" context="{'type': type}">
|
||||
<tree string="Invoice Lines" editable="bottom">
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="product_id"
|
||||
on_change="product_id_change(product_id, uos_id, quantity, name, parent.type, parent.partner_id, parent.fiscal_position, price_unit, parent.currency_id, context, parent.company_id)"/>
|
||||
<field name="name"/>
|
||||
|
|
|
@ -1283,7 +1283,7 @@ class account_move_line(osv.osv):
|
|||
self.create(cr, uid, data, context)
|
||||
del vals['account_tax_id']
|
||||
|
||||
if check and ((not context.get('no_store_function')) or journal.entry_posted):
|
||||
if check and not context.get('novalidate') and ((not context.get('no_store_function')) or journal.entry_posted):
|
||||
tmp = move_obj.validate(cr, uid, [vals['move_id']], context)
|
||||
if journal.entry_posted and tmp:
|
||||
move_obj.button_validate(cr,uid, [vals['move_id']], context)
|
||||
|
|
|
@ -81,31 +81,31 @@ class account_config_settings(osv.osv_memory):
|
|||
'purchase_refund_sequence_next': fields.related('purchase_refund_journal_id', 'sequence_id', 'number_next', type='integer', string='Next supplier credit note number'),
|
||||
|
||||
'module_account_check_writing': fields.boolean('Pay your suppliers by check',
|
||||
help="""This allows you to check writing and printing.
|
||||
This installs the module account_check_writing."""),
|
||||
help='This allows you to check writing and printing.\n'
|
||||
'-This installs the module account_check_writing.'),
|
||||
'module_account_accountant': fields.boolean('Full accounting features: journals, legal statements, chart of accounts, etc.',
|
||||
help="""If you do not check this box, you will be able to do invoicing & payments, but not accounting (Journal Items, Chart of Accounts, ...)"""),
|
||||
'module_account_asset': fields.boolean('Assets management',
|
||||
help="""This allows you to manage the assets owned by a company or a person.
|
||||
It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.
|
||||
This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments,
|
||||
but not accounting (Journal Items, Chart of Accounts, ...)"""),
|
||||
help='This allows you to manage the assets owned by a company or a person.\n'
|
||||
'It keeps track of the depreciation occurred on those assets, and creates account move for those depreciation lines.\n'
|
||||
'-This installs the module account_asset. If you do not check this box, you will be able to do invoicing & payments, '
|
||||
'but not accounting (Journal Items, Chart of Accounts, ...)'),
|
||||
'module_account_budget': fields.boolean('Budget management',
|
||||
help="""This allows accountants to manage analytic and crossovered budgets.
|
||||
Once the master budgets and the budgets are defined,
|
||||
the project managers can set the planned amount on each analytic account.
|
||||
This installs the module account_budget."""),
|
||||
help='This allows accountants to manage analytic and crossovered budgets. '
|
||||
'Once the master budgets and the budgets are defined, '
|
||||
'the project managers can set the planned amount on each analytic account.\n'
|
||||
'-This installs the module account_budget.'),
|
||||
'module_account_payment': fields.boolean('Manage payment orders',
|
||||
help="""This allows you to create and manage your payment orders, with purposes to
|
||||
* serve as base for an easy plug-in of various automated payment mechanisms, and
|
||||
* provide a more efficient way to manage invoice payments.
|
||||
This installs the module account_payment."""),
|
||||
help='This allows you to create and manage your payment orders, with purposes to \n'
|
||||
'* serve as base for an easy plug-in of various automated payment mechanisms, and \n'
|
||||
'* provide a more efficient way to manage invoice payments.\n'
|
||||
'-This installs the module account_payment.' ),
|
||||
'module_account_voucher': fields.boolean('Manage customer payments',
|
||||
help="""This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.
|
||||
This installs the module account_voucher."""),
|
||||
help='This includes all the basic requirements of voucher entries for bank, cash, sales, purchase, expense, contra, etc.\n'
|
||||
'-This installs the module account_voucher.'),
|
||||
'module_account_followup': fields.boolean('Manage customer payment follow-ups',
|
||||
help="""This allows to automate letters for unpaid invoices, with multi-level recalls.
|
||||
This installs the module account_followup."""),
|
||||
help='This allows to automate letters for unpaid invoices, with multi-level recalls.\n'
|
||||
'-This installs the module account_followup.'),
|
||||
'group_proforma_invoices': fields.boolean('Allow pro-forma invoices',
|
||||
implied_group='account.group_proforma_invoices',
|
||||
help="Allows you to put invoices in pro-forma state."),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Create a user as 'Accountant'
|
||||
-
|
||||
!record {model: res.users, id: res_users_account_user}:
|
||||
!record {model: res.users, id: res_users_account_user, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Accountant
|
||||
login: acc
|
||||
|
@ -17,7 +17,7 @@
|
|||
-
|
||||
Create a user as 'Financial Manager'
|
||||
-
|
||||
!record {model: res.users, id: res_users_account_manager}:
|
||||
!record {model: res.users, id: res_users_account_manager, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Financial Manager
|
||||
login: fm
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
<newline/>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='filter']" position="replace">
|
||||
<field name="filter" on_change="onchange_filter(filter, fiscalyear_id)" colspan="4"/>
|
||||
<field name="filter" on_change="onchange_filter(filter, fiscalyear_id)"/>
|
||||
<field name="initial_balance" attrs="{'readonly':[('filter', 'in', ('filter_no', 'unreconciled'))]}" />
|
||||
</xpath>
|
||||
</data>
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Spanish (Argentina) translation for openobject-addons
|
||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
|
||||
"PO-Revision-Date: 2013-10-07 21:16+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Spanish (Argentina) <es_AR@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: 2013-10-08 05:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16799)\n"
|
||||
|
||||
#. module: account_accountant
|
||||
#: model:ir.actions.client,name:account_accountant.action_client_account_menu
|
||||
msgid "Open Accounting Menu"
|
||||
msgstr ""
|
|
@ -655,7 +655,7 @@ class account_analytic_account(osv.osv):
|
|||
if not contract.partner_id:
|
||||
raise osv.except_osv(_('No Customer Defined!'),_("You must first select a Customer for Contract %s!") % contract.name )
|
||||
|
||||
fpos = contract.partner_id.property_account_position.id or False
|
||||
fpos = contract.partner_id.property_account_position or False
|
||||
journal_ids = journal_obj.search(cr, uid, [('type', '=','sale'),('company_id', '=', contract.company_id.id or False)], limit=1)
|
||||
if not journal_ids:
|
||||
raise osv.except_osv(_('Error!'),
|
||||
|
@ -673,7 +673,7 @@ class account_analytic_account(osv.osv):
|
|||
'journal_id': len(journal_ids) and journal_ids[0] or False,
|
||||
'date_invoice': contract.recurring_next_date,
|
||||
'origin': contract.name,
|
||||
'fiscal_position': fpos,
|
||||
'fiscal_position': fpos and fpos.id,
|
||||
'payment_term': partner_payment_term,
|
||||
'company_id': contract.company_id.id or False,
|
||||
}
|
||||
|
@ -687,7 +687,7 @@ class account_analytic_account(osv.osv):
|
|||
account_id = res.categ_id.property_account_income_categ.id
|
||||
account_id = fpos_obj.map_account(cr, uid, fpos, account_id)
|
||||
|
||||
taxes = res.taxes_id and res.taxes_id or False
|
||||
taxes = res.taxes_id or False
|
||||
tax_id = fpos_obj.map_tax(cr, uid, fpos, taxes)
|
||||
|
||||
invoice_line_vals = {
|
||||
|
|
|
@ -8,19 +8,19 @@ 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: 2011-11-08 10:42+0000\n"
|
||||
"Last-Translator: OpenERP Danmark / Mikhael Saxtorph <Unknown>\n"
|
||||
"PO-Revision-Date: 2013-09-26 21:19+0000\n"
|
||||
"Last-Translator: Morten Schou <ms@msteknik.dk>\n"
|
||||
"Language-Team: Danish <da@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: 2013-09-12 06:17+0000\n"
|
||||
"X-Generator: Launchpad (build 16761)\n"
|
||||
"X-Launchpad-Export-Date: 2013-09-27 05:49+0000\n"
|
||||
"X-Generator: Launchpad (build 16774)\n"
|
||||
|
||||
#. module: account_cancel
|
||||
#: view:account.invoice:0
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
msgstr "Annuller"
|
||||
|
||||
#~ msgid "Account Cancel"
|
||||
#~ msgstr "Annuller post"
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Estonian translation for openobject-addons
|
||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
||||
#
|
||||
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: 2013-10-09 14:39+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Estonian <et@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: 2013-10-10 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16799)\n"
|
||||
|
||||
#. module: account_cancel
|
||||
#: view:account.invoice:0
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
|
@ -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: 2013-03-21 19:22+0000\n"
|
||||
"Last-Translator: Liuming <gumdam20@me.com>\n"
|
||||
"PO-Revision-Date: 2013-09-24 02:46+0000\n"
|
||||
"Last-Translator: DWXXX <Unknown>\n"
|
||||
"Language-Team: Chinese (Simplified) <zh_CN@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: 2013-09-12 06:40+0000\n"
|
||||
"X-Generator: Launchpad (build 16761)\n"
|
||||
"X-Launchpad-Export-Date: 2013-09-25 05:28+0000\n"
|
||||
"X-Generator: Launchpad (build 16771)\n"
|
||||
|
||||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
|
@ -106,7 +106,7 @@ msgstr ""
|
|||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
msgstr "描述"
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_06_1
|
||||
|
@ -123,13 +123,13 @@ msgstr ""
|
|||
#: 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 ""
|
||||
msgstr "帐户测试"
|
||||
|
||||
#. module: account_test
|
||||
#: code:addons/account_test/report/account_test_report.py:74
|
||||
#, python-format
|
||||
msgid "The test was passed successfully"
|
||||
msgstr ""
|
||||
msgstr "测试通过"
|
||||
|
||||
#. module: account_test
|
||||
#: field:accounting.assert.test,active:0
|
||||
|
@ -155,7 +155,7 @@ msgstr ""
|
|||
#. module: account_test
|
||||
#: field:accounting.assert.test,code_exec:0
|
||||
msgid "Python code"
|
||||
msgstr ""
|
||||
msgstr "Python代码"
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_07
|
||||
|
@ -209,7 +209,7 @@ msgstr ""
|
|||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
msgid "Python Code"
|
||||
msgstr ""
|
||||
msgstr "Python代码"
|
||||
|
||||
#. module: account_test
|
||||
#: model:ir.actions.act_window,help:account_test.action_accounting_assert
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Create a user as 'Accountant for account voucher'
|
||||
-
|
||||
!record {model: res.users, id: res_users_account_voucher_user}:
|
||||
!record {model: res.users, id: res_users_account_voucher_user, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Voucher Accountant
|
||||
login: vacc
|
||||
|
@ -17,7 +17,7 @@
|
|||
-
|
||||
Create a user as 'Financial Manager for account voucher'
|
||||
-
|
||||
!record {model: res.users, id: res_users_account_voucher_manager}:
|
||||
!record {model: res.users, id: res_users_account_voucher_manager, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Financial Manager for voucher
|
||||
login: fmv
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
# Estonian translation for openobject-addons
|
||||
# Copyright (c) 2011 Rosetta Contributors and Canonical Ltd 2011
|
||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
||||
#
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: openobject-addons\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2012-12-03 16:03+0000\n"
|
||||
"PO-Revision-Date: 2011-10-10 19:33+0000\n"
|
||||
"Last-Translator: Aare Vesi <Unknown>\n"
|
||||
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
|
||||
"PO-Revision-Date: 2013-10-09 14:40+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Estonian <et@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: 2012-12-04 05:53+0000\n"
|
||||
"X-Generator: Launchpad (build 16335)\n"
|
||||
"X-Launchpad-Export-Date: 2013-10-10 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16799)\n"
|
||||
|
||||
#. module: base_crypt
|
||||
#: model:ir.model,name:base_crypt.model_res_users
|
||||
#. module: auth_crypt
|
||||
#: field:res.users,password_crypt:0
|
||||
msgid "Encrypted Password"
|
||||
msgstr "Krüpteeritud Parool"
|
||||
|
||||
#. module: auth_crypt
|
||||
#: model:ir.model,name:auth_crypt.model_res_users
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#, python-format
|
||||
#~ msgid "Error"
|
||||
#~ msgstr "Viga"
|
||||
|
||||
#~ msgid "res.users"
|
||||
#~ msgstr "res.users"
|
||||
msgstr "Kasutajad"
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
# Estonian translation for openobject-addons
|
||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
||||
#
|
||||
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: 2013-10-09 14:34+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Estonian <et@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: 2013-10-10 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16799)\n"
|
||||
|
||||
#. module: auth_oauth_signup
|
||||
#: model:ir.model,name:auth_oauth_signup.model_res_users
|
||||
msgid "Users"
|
||||
msgstr "Kasutajad"
|
|
@ -0,0 +1,279 @@
|
|||
# Chinese (Traditional) translation for openobject-addons
|
||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
||||
#
|
||||
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: 2013-09-19 09:40+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Chinese (Traditional) <zh_TW@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: 2013-09-20 05:38+0000\n"
|
||||
"X-Generator: Launchpad (build 16765)\n"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_type:0
|
||||
msgid "Signup Token Type"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:base.config.settings,auth_signup_uninvited:0
|
||||
msgid "Allow external users to sign up"
|
||||
msgstr "允許外部使用者註冊"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:19
|
||||
#, python-format
|
||||
msgid "Confirm Password"
|
||||
msgstr "確認密碼"
|
||||
|
||||
#. module: auth_signup
|
||||
#: help:base.config.settings,auth_signup_uninvited:0
|
||||
msgid "If unchecked, only invited users may sign up."
|
||||
msgstr "如果未勾選,只有被邀請的使用者可以註冊"
|
||||
|
||||
#. module: auth_signup
|
||||
#: model:ir.model,name:auth_signup.model_base_config_settings
|
||||
msgid "base.config.settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: code:addons/auth_signup/res_users.py:266
|
||||
#, python-format
|
||||
msgid "Cannot send email: user has no email address."
|
||||
msgstr "無法送出email:使用者沒有email 地址"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:27
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:31
|
||||
#, python-format
|
||||
msgid "Reset password"
|
||||
msgstr "重設密碼"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:base.config.settings,auth_signup_template_user_id:0
|
||||
msgid "Template user for new users created through signup"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: model:email.template,subject:auth_signup.reset_password_email
|
||||
msgid "Password reset"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:120
|
||||
#, python-format
|
||||
msgid "Please enter a password and confirm it."
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: view:res.users:0
|
||||
msgid "Send an email to the user to (re)set their password."
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:26
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:29
|
||||
#, python-format
|
||||
msgid "Sign Up"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: selection:res.users,state:0
|
||||
msgid "New"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: code:addons/auth_signup/res_users.py:258
|
||||
#, python-format
|
||||
msgid "Mail sent to:"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.users,state:0
|
||||
msgid "Status"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: model:email.template,body_html:auth_signup.reset_password_email
|
||||
msgid ""
|
||||
"\n"
|
||||
"<p>A password reset was requested for the OpenERP account linked to this "
|
||||
"email.</p>\n"
|
||||
"\n"
|
||||
"<p>You may change your password by following <a "
|
||||
"href=\"${object.signup_url}\">this link</a>.</p>\n"
|
||||
"\n"
|
||||
"<p>Note: If you do not expect this, you can safely ignore this email.</p>"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:114
|
||||
#, python-format
|
||||
msgid "Please enter a name."
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: model:ir.model,name:auth_signup.model_res_users
|
||||
msgid "Users"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_url:0
|
||||
msgid "Signup URL"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:117
|
||||
#, python-format
|
||||
msgid "Please enter a username."
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: selection:res.users,state:0
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: code:addons/auth_signup/res_users.py:270
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot send email: no outgoing email server configured.\n"
|
||||
"You can configure it under Settings/General Settings."
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:12
|
||||
#, python-format
|
||||
msgid "Username"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:8
|
||||
#, python-format
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:173
|
||||
#, python-format
|
||||
msgid "Please enter a username or email address."
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: selection:res.users,state:0
|
||||
msgid "Resetting Password"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:13
|
||||
#, python-format
|
||||
msgid "Username (Email)"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_expiration:0
|
||||
msgid "Signup Expiration"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: help:base.config.settings,auth_signup_reset_password:0
|
||||
msgid "This allows users to trigger a password reset from the Login page."
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:25
|
||||
#, python-format
|
||||
msgid "Log in"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_valid:0
|
||||
msgid "Signup Token is Valid"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:111
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:114
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:117
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:120
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:123
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:170
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:173
|
||||
#, python-format
|
||||
msgid "Login"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:97
|
||||
#, python-format
|
||||
msgid "Invalid signup token"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:123
|
||||
#, python-format
|
||||
msgid "Passwords do not match; please retype them."
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:111
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:170
|
||||
#, python-format
|
||||
msgid "No database selected !"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: view:res.users:0
|
||||
msgid "Reset Password"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:base.config.settings,auth_signup_reset_password:0
|
||||
msgid "Enable password reset from Login page"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:30
|
||||
#, python-format
|
||||
msgid "Back to Login"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:22
|
||||
#, python-format
|
||||
msgid "Sign up"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: model:ir.model,name:auth_signup.model_res_partner
|
||||
msgid "Partner"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_token:0
|
||||
msgid "Signup Token"
|
||||
msgstr ""
|
|
@ -114,19 +114,6 @@ def real_id2base_calendar_id(real_id, recurrent_date):
|
|||
return '%d-%s' % (real_id, recurrent_date)
|
||||
return real_id
|
||||
|
||||
def _links_get(self, cr, uid, context=None):
|
||||
"""
|
||||
Get request link.
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's ID for security checks
|
||||
@param context: a standard dictionary for contextual values
|
||||
@return: list of dictionary which contain object and name and id
|
||||
"""
|
||||
obj = self.pool.get('res.request.link')
|
||||
ids = obj.search(cr, uid, [])
|
||||
res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
|
||||
return [(r['object'], r['name']) for r in res]
|
||||
|
||||
html_invitation = """
|
||||
<html>
|
||||
<head>
|
||||
|
@ -307,19 +294,6 @@ class calendar_attendee(osv.osv):
|
|||
|
||||
return result
|
||||
|
||||
def _links_get(self, cr, uid, context=None):
|
||||
"""
|
||||
Get request link for ref field in calendar attendee.
|
||||
@param cr: the current row, from the database cursor
|
||||
@param uid: the current user's id for security checks
|
||||
@param context: A standard dictionary for contextual values
|
||||
@return: list of dictionary which contain object and name and id
|
||||
"""
|
||||
obj = self.pool.get('res.request.link')
|
||||
ids = obj.search(cr, uid, [])
|
||||
res = obj.read(cr, uid, ids, ['object', 'name'], context=context)
|
||||
return [(r['object'], r['name']) for r in res]
|
||||
|
||||
def _lang_get(self, cr, uid, context=None):
|
||||
"""
|
||||
Get language for language selection field.
|
||||
|
@ -385,7 +359,7 @@ property or property parameter."),
|
|||
'event_end_date': fields.function(_compute_data, \
|
||||
string='Event End Date', type="datetime", \
|
||||
multi='event_end_date'),
|
||||
'ref': fields.reference('Event Ref', selection=_links_get, size=128),
|
||||
'ref': fields.reference('Event Ref', selection=openerp.addons.base.res.res_request.referencable_models, size=128),
|
||||
'availability': fields.selection([('free', 'Free'), ('busy', 'Busy')], 'Free/Busy', readonly="True"),
|
||||
}
|
||||
_defaults = {
|
||||
|
@ -1208,20 +1182,44 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
new_rrule_str = ';'.join(new_rrule_str)
|
||||
rdates = get_recurrent_dates(str(new_rrule_str), exdate, event_date, data['exrule'])
|
||||
for r_date in rdates:
|
||||
ok = True
|
||||
# fix domain evaluation
|
||||
# step 1: check date and replace expression by True or False, replace other expressions by True
|
||||
# step 2: evaluation of & and |
|
||||
# check if there are one False
|
||||
pile = []
|
||||
for arg in domain:
|
||||
if arg[0] in ('date', 'date_deadline'):
|
||||
if (arg[1]=='='):
|
||||
ok = ok and r_date.strftime('%Y-%m-%d')==arg[2]
|
||||
if (arg[1]=='>'):
|
||||
ok = ok and r_date.strftime('%Y-%m-%d')>arg[2]
|
||||
if (arg[1]=='<'):
|
||||
ok = ok and r_date.strftime('%Y-%m-%d')<arg[2]
|
||||
if (arg[1]=='>='):
|
||||
ok = ok and r_date.strftime('%Y-%m-%d')>=arg[2]
|
||||
if (arg[1]=='<='):
|
||||
ok = ok and r_date.strftime('%Y-%m-%d')<=arg[2]
|
||||
if not ok:
|
||||
if str(arg[0]) in (str('date'), str('date_deadline')):
|
||||
if (arg[1] == '='):
|
||||
ok = r_date.strftime('%Y-%m-%d')==arg[2]
|
||||
if (arg[1] == '>'):
|
||||
ok = r_date.strftime('%Y-%m-%d')>arg[2]
|
||||
if (arg[1] == '<'):
|
||||
ok = r_date.strftime('%Y-%m-%d')<arg[2]
|
||||
if (arg[1] == '>='):
|
||||
ok = r_date.strftime('%Y-%m-%d')>=arg[2]
|
||||
if (arg[1] == '<='):
|
||||
ok = r_date.strftime('%Y-%m-%d')<=arg[2]
|
||||
pile.append(ok)
|
||||
elif str(arg) == str('&') or str(arg) == str('|'):
|
||||
pile.append(arg)
|
||||
else:
|
||||
pile.append(True)
|
||||
pile.reverse()
|
||||
new_pile = []
|
||||
for item in pile:
|
||||
if not isinstance(item, basestring):
|
||||
res = item
|
||||
elif str(item) == str('&'):
|
||||
first = new_pile.pop()
|
||||
second = new_pile.pop()
|
||||
res = first and second
|
||||
elif str(item) == str('|'):
|
||||
first = new_pile.pop()
|
||||
second = new_pile.pop()
|
||||
res = first or second
|
||||
new_pile.append(res)
|
||||
|
||||
if [True for item in new_pile if not item]:
|
||||
continue
|
||||
idval = real_id2base_calendar_id(data['id'], r_date.strftime("%Y-%m-%d %H:%M:%S"))
|
||||
result.append(idval)
|
||||
|
@ -1346,18 +1344,17 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
|
||||
for arg in args:
|
||||
new_arg = arg
|
||||
if arg[0] in ('date', unicode('date'), 'date_deadline', unicode('date_deadline')):
|
||||
if arg[0] in ('date_deadline', unicode('date_deadline')):
|
||||
if context.get('virtual_id', True):
|
||||
new_args += ['|','&',('recurrency','=',1),('recurrent_id_date', arg[1], arg[2])]
|
||||
new_args += ['|','&',('recurrency','=',1),('end_date', arg[1], arg[2])]
|
||||
elif arg[0] == "id":
|
||||
new_id = get_real_ids(arg[2])
|
||||
new_arg = (arg[0], arg[1], new_id)
|
||||
new_args.append(new_arg)
|
||||
|
||||
#offset, limit 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=order, context=context, count=False)
|
||||
if context.get('virtual_id', True):
|
||||
res = self.get_recurrent_ids(cr, uid, res, new_args, limit, context=context)
|
||||
res = self.get_recurrent_ids(cr, uid, res, args, limit, context=context)
|
||||
if count:
|
||||
return len(res)
|
||||
elif limit:
|
||||
|
@ -1436,6 +1433,14 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
vals['vtimezone'] = vals['vtimezone'][40:]
|
||||
|
||||
res = super(calendar_event, self).write(cr, uid, ids, vals, context=context)
|
||||
|
||||
# set end_date for calendar searching
|
||||
if vals.get('recurrency', True) and vals.get('end_type', 'count') in ('count', unicode('count')) and \
|
||||
(vals.get('rrule_type') or vals.get('count') or vals.get('date') or vals.get('date_deadline')):
|
||||
for data in self.read(cr, uid, ids, ['date', 'date_deadline', 'recurrency', 'rrule_type', 'count', 'end_type'], context=context):
|
||||
end_date = self._set_recurrency_end_date(data, context=context)
|
||||
super(calendar_event, self).write(cr, uid, [data['id']], {'end_date': end_date}, context=context)
|
||||
|
||||
if vals.get('partner_ids', False):
|
||||
self.create_attendees(cr, uid, ids, context)
|
||||
|
||||
|
@ -1554,6 +1559,21 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
self.unlink_events(cr, uid, ids, context=context)
|
||||
return res
|
||||
|
||||
def _set_recurrency_end_date(self, data, context=None):
|
||||
end_date = data.get('end_date')
|
||||
if data.get('recurrency') and data.get('end_type') in ('count', unicode('count')):
|
||||
data_date_deadline = datetime.strptime(data.get('date_deadline'), '%Y-%m-%d %H:%M:%S')
|
||||
if data.get('rrule_type') in ('daily', unicode('count')):
|
||||
rel_date = relativedelta(days=data.get('count')+1)
|
||||
elif data.get('rrule_type') in ('weekly', unicode('weekly')):
|
||||
rel_date = relativedelta(days=(data.get('count')+1)*7)
|
||||
elif data.get('rrule_type') in ('monthly', unicode('monthly')):
|
||||
rel_date = relativedelta(months=data.get('count')+1)
|
||||
elif data.get('rrule_type') in ('yearly', unicode('yearly')):
|
||||
rel_date = relativedelta(years=data.get('count')+1)
|
||||
end_date = data_date_deadline + rel_date
|
||||
return end_date
|
||||
|
||||
def create(self, cr, uid, vals, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
|
@ -1561,7 +1581,9 @@ rule or repeating pattern of time to exclude from the recurring rule."),
|
|||
if vals.get('vtimezone', '') and vals.get('vtimezone', '').startswith('/freeassociation.sourceforge.net/tzfile/'):
|
||||
vals['vtimezone'] = vals['vtimezone'][40:]
|
||||
|
||||
vals['end_date'] = self._set_recurrency_end_date(vals, context=context)
|
||||
res = super(calendar_event, self).create(cr, uid, vals, context)
|
||||
|
||||
alarm_obj = self.pool.get('res.alarm')
|
||||
alarm_obj.do_alarm_create(cr, uid, [res], self._name, 'date', context=context)
|
||||
self.create_attendees(cr, uid, [res], context)
|
||||
|
|
|
@ -0,0 +1,185 @@
|
|||
# Hindi translation for openobject-addons
|
||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
||||
#
|
||||
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: 2013-10-09 05:55+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Hindi <hi@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: 2013-10-10 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16799)\n"
|
||||
|
||||
#. module: base_report_designer
|
||||
#: model:ir.model,name:base_report_designer.model_base_report_sxw
|
||||
msgid "base.report.sxw"
|
||||
msgstr "base.report.sxw"
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base_report_designer.installer:0
|
||||
msgid "OpenERP Report Designer Configuration"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base_report_designer.installer:0
|
||||
msgid ""
|
||||
"This plug-in allows you to create/modify OpenERP Reports into OpenOffice "
|
||||
"Writer."
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.sxw:0
|
||||
msgid "Upload the modified report"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.file.sxw:0
|
||||
msgid "The .SXW report"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: model:ir.model,name:base_report_designer.model_base_report_designer_installer
|
||||
msgid "base_report_designer.installer"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: model:ir.model,name:base_report_designer.model_base_report_rml_save
|
||||
msgid "base.report.rml.save"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base_report_designer.installer:0
|
||||
msgid "Configure"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base_report_designer.installer:0
|
||||
msgid "title"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: field:base.report.file.sxw,report_id:0
|
||||
#: field:base.report.sxw,report_id:0
|
||||
msgid "Report"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.rml.save:0
|
||||
msgid "The RML Report"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: model:ir.ui.menu,name:base_report_designer.menu_action_report_designer_wizard
|
||||
msgid "Report Designer"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: field:base_report_designer.installer,name:0
|
||||
msgid "File name"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.file.sxw:0
|
||||
#: view:base.report.sxw:0
|
||||
msgid "Get a report"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base_report_designer.installer:0
|
||||
#: model:ir.actions.act_window,name:base_report_designer.action_report_designer_wizard
|
||||
msgid "OpenERP Report Designer"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.sxw:0
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: field:base.report.rml.save,file_rml:0
|
||||
msgid "Save As"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: help:base_report_designer.installer,plugin_file:0
|
||||
msgid ""
|
||||
"OpenObject Report Designer plug-in file. Save as this file and install this "
|
||||
"plug-in in OpenOffice."
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.rml.save:0
|
||||
msgid "Save RML FIle"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: field:base.report.file.sxw,file_sxw:0
|
||||
#: field:base.report.file.sxw,file_sxw_upload:0
|
||||
msgid "Your .SXW file"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base_report_designer.installer:0
|
||||
msgid "Installation and Configuration Steps"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: field:base_report_designer.installer,description:0
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.file.sxw:0
|
||||
msgid ""
|
||||
"This is the template of your requested report.\n"
|
||||
"Save it as a .SXW file and open it with OpenOffice.\n"
|
||||
"Don't forget to install the OpenERP SA OpenOffice package to modify it.\n"
|
||||
"Once it is modified, re-upload it in OpenERP using this wizard."
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: model:ir.actions.act_window,name:base_report_designer.action_view_base_report_sxw
|
||||
msgid "Base Report sxw"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: model:ir.model,name:base_report_designer.model_base_report_file_sxw
|
||||
msgid "base.report.file.sxw"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: field:base_report_designer.installer,plugin_file:0
|
||||
msgid "OpenObject Report Designer Plug-in"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: model:ir.actions.act_window,name:base_report_designer.action_report_designer_installer
|
||||
msgid "OpenERP Report Designer Installation"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.sxw:0
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.sxw:0
|
||||
msgid "or"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: model:ir.model,name:base_report_designer.model_ir_actions_report_xml
|
||||
msgid "ir.actions.report.xml"
|
||||
msgstr ""
|
||||
|
||||
#. module: base_report_designer
|
||||
#: view:base.report.sxw:0
|
||||
msgid "Select your report"
|
||||
msgstr ""
|
|
@ -20,14 +20,20 @@
|
|||
##############################################################################
|
||||
|
||||
from openerp.osv import fields, osv
|
||||
import re
|
||||
from openerp.report.render.rml2pdf import customfonts
|
||||
|
||||
class base_config_settings(osv.osv_memory):
|
||||
_name = 'base.config.settings'
|
||||
_inherit = 'res.config.settings'
|
||||
|
||||
def _get_font(self, cr, uid, context=None):
|
||||
return sorted(customfonts.RegisterCustomFonts())
|
||||
|
||||
_columns = {
|
||||
'module_multi_company': fields.boolean('Manage multiple companies',
|
||||
help="""Work in multi-company environments, with appropriate security access between companies.
|
||||
This installs the module multi_company."""),
|
||||
help='Work in multi-company environments, with appropriate security access between companies.\n'
|
||||
'-This installs the module multi_company.'),
|
||||
'module_share': fields.boolean('Allow documents sharing',
|
||||
help="""Share or embbed any screen of openerp."""),
|
||||
'module_portal': fields.boolean('Activate the customer portal',
|
||||
|
@ -38,8 +44,13 @@ class base_config_settings(osv.osv_memory):
|
|||
'module_base_import': fields.boolean("Allow users to import data from CSV files"),
|
||||
'module_google_drive': fields.boolean('Attach Google documents to any record',
|
||||
help="""This installs the module google_docs."""),
|
||||
'font': fields.selection(_get_font, "Select Font", help="Set the font into the report header, will be used for every RML report of the user company"),
|
||||
}
|
||||
|
||||
|
||||
_defaults= {
|
||||
'font': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.font or 'Helvetica',
|
||||
}
|
||||
|
||||
def open_company(self, cr, uid, ids, context=None):
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context)
|
||||
return {
|
||||
|
@ -52,6 +63,20 @@ class base_config_settings(osv.osv_memory):
|
|||
'target': 'current',
|
||||
}
|
||||
|
||||
def _change_header(self, header,font):
|
||||
""" Replace default fontname use in header and setfont tag """
|
||||
|
||||
default_para = re.sub('fontName.?=.?".*"', 'fontName="%s"'% font,header)
|
||||
return re.sub('(<setFont.?name.?=.?)(".*?")(.)', '\g<1>"%s"\g<3>'% font,default_para)
|
||||
|
||||
def set_base_defaults(self, cr, uid, ids, context=None):
|
||||
ir_model_data = self.pool.get('ir.model.data')
|
||||
wizard = self.browse(cr, uid, ids)[0]
|
||||
if wizard.font:
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context)
|
||||
user.company_id.write({'font':wizard.font,'rml_header': self._change_header(user.company_id.rml_header,wizard.font), 'rml_header2': self._change_header(user.company_id.rml_header2,wizard.font), 'rml_header3': self._change_header(user.company_id.rml_header3,wizard.font)})
|
||||
return {}
|
||||
|
||||
# Preferences wizard for Sales & CRM.
|
||||
# It is defined here because it is inherited independently in modules sale, crm,
|
||||
# plugin_outlook and plugin_thunderbird.
|
||||
|
@ -64,18 +89,20 @@ class sale_config_settings(osv.osv_memory):
|
|||
'module_crm': fields.boolean('CRM'),
|
||||
'module_sale' : fields.boolean('SALE'),
|
||||
'module_plugin_thunderbird': fields.boolean('Enable Thunderbird plug-in',
|
||||
help="""The plugin allows you archive email and its attachments to the selected
|
||||
OpenERP objects. You can select a partner, or a lead and
|
||||
attach the selected mail as a .eml file in
|
||||
the attachment of a selected record. You can create documents for CRM Lead,
|
||||
Partner from the selected emails.
|
||||
This installs the module plugin_thunderbird."""),
|
||||
help='The plugin allows you archive email and its attachments to the selected '
|
||||
'OpenERP objects. You can select a partner, or a lead and '
|
||||
'attach the selected mail as a .eml file in '
|
||||
'the attachment of a selected record. You can create documents for CRM Lead, '
|
||||
'Partner from the selected emails.\n'
|
||||
'-This installs the module plugin_thunderbird.'),
|
||||
'module_plugin_outlook': fields.boolean('Enable Outlook plug-in',
|
||||
help="""The Outlook plugin allows you to select an object that you would like to add
|
||||
to your email and its attachments from MS Outlook. You can select a partner,
|
||||
or a lead object and archive a selected
|
||||
email into an OpenERP mail message with attachments.
|
||||
This installs the module plugin_outlook."""),
|
||||
help='The Outlook plugin allows you to select an object that you would like to add '
|
||||
'to your email and its attachments from MS Outlook. You can select a partner, '
|
||||
'or a lead object and archive a selected email into an OpenERP mail message with attachments.\n'
|
||||
'-This installs the module plugin_outlook.'),
|
||||
'module_mass_mailing': fields.boolean(
|
||||
'Manage mass mailing campaigns',
|
||||
help='Get access to statistics with your mass mailing, manage campaigns.'),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -90,6 +90,14 @@
|
|||
</div>
|
||||
</div>
|
||||
</group>
|
||||
<group string="Report Settings">
|
||||
<label for="font" string="RML font (Header/footer)" />
|
||||
<div>
|
||||
<div>
|
||||
<field name="font" class="oe_inline"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
@ -123,6 +131,10 @@
|
|||
<field name="module_web_linkedin"/>
|
||||
<label for="module_web_linkedin"/>
|
||||
</div>
|
||||
<div>
|
||||
<field name="module_mass_mailing" class="oe_inline"/>
|
||||
<label for="module_mass_mailing"/>
|
||||
</div>
|
||||
</div>
|
||||
</group>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
# Estonian translation for openobject-addons
|
||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
||||
#
|
||||
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: 2013-10-09 14:42+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Estonian <et@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: 2013-10-10 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16799)\n"
|
||||
|
||||
#. module: claim_from_delivery
|
||||
#: view:stock.picking.out:0
|
||||
msgid "Claims"
|
||||
msgstr "Nõuded"
|
||||
|
||||
#. module: claim_from_delivery
|
||||
#: model:res.request.link,name:claim_from_delivery.request_link_claim_from_delivery
|
||||
msgid "Delivery Order"
|
||||
msgstr "Tarnetellimus"
|
||||
|
||||
#. module: claim_from_delivery
|
||||
#: model:ir.actions.act_window,name:claim_from_delivery.action_claim_from_delivery
|
||||
msgid "Claim From Delivery"
|
||||
msgstr ""
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import calendar
|
||||
from datetime import date, datetime
|
||||
from dateutil import relativedelta
|
||||
|
||||
|
@ -117,9 +118,9 @@ class crm_case_section(osv.osv):
|
|||
"""
|
||||
month_begin = date.today().replace(day=1)
|
||||
section_result = [{
|
||||
'value': 0,
|
||||
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
|
||||
} for i in range(self._period_number - 1, -1, -1)]
|
||||
'value': 0,
|
||||
'tooltip': (month_begin + relativedelta.relativedelta(months=-i)).strftime('%B'),
|
||||
} for i in range(self._period_number - 1, -1, -1)]
|
||||
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
||||
for group in group_obj:
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
|
@ -151,12 +152,14 @@ class crm_case_section(osv.osv):
|
|||
obj = self.pool.get('crm.lead')
|
||||
res = dict.fromkeys(ids, False)
|
||||
month_begin = date.today().replace(day=1)
|
||||
groupby_begin = (month_begin + relativedelta.relativedelta(months=-4)).strftime(tools.DEFAULT_SERVER_DATE_FORMAT)
|
||||
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])
|
||||
date_domain = [('create_date', '>=', date_begin.strftime(tools.DEFAULT_SERVER_DATE_FORMAT)), ('create_date', '<=', date_end.strftime(tools.DEFAULT_SERVER_DATE_FORMAT))]
|
||||
for id in ids:
|
||||
res[id] = dict()
|
||||
lead_domain = [('type', '=', 'lead'), ('section_id', '=', id), ('create_date', '>=', groupby_begin)]
|
||||
lead_domain = date_domain + [('type', '=', 'lead'), ('section_id', '=', id)]
|
||||
res[id]['monthly_open_leads'] = self.__get_bar_values(cr, uid, obj, lead_domain, ['create_date','company_id'], 'create_date_count', ['create_date', 'company_id'], type="Lead", context=context)
|
||||
opp_domain = [('type', '=', 'opportunity'), ('section_id', '=', id), ('create_date', '>=', groupby_begin)]
|
||||
opp_domain = date_domain + [('type', '=', 'opportunity'), ('section_id', '=', id)]
|
||||
res[id]['monthly_planned_revenue'] = self.__get_bar_values(cr, uid, obj, opp_domain, ['planned_revenue', 'create_date', 'company_id'], 'planned_revenue', ['create_date', 'company_id'], context=context)
|
||||
return res
|
||||
|
||||
|
@ -279,13 +282,6 @@ class crm_case_resource_type(osv.osv):
|
|||
'section_id': fields.many2one('crm.case.section', 'Sales Team'),
|
||||
}
|
||||
|
||||
def _links_get(self, cr, uid, context=None):
|
||||
"""Gets links value for reference field"""
|
||||
obj = self.pool.get('res.request.link')
|
||||
ids = obj.search(cr, uid, [])
|
||||
res = obj.read(cr, uid, ids, ['object', 'name'], context)
|
||||
return [(r['object'], r['name']) for r in res]
|
||||
|
||||
class crm_payment_mode(osv.osv):
|
||||
""" Payment Mode for Fund """
|
||||
_name = "crm.payment.mode"
|
||||
|
|
|
@ -23,6 +23,7 @@ import crm
|
|||
from datetime import datetime
|
||||
from operator import itemgetter
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp import tools
|
||||
from openerp.addons.base.res.res_partner import format_address
|
||||
|
@ -253,11 +254,13 @@ class crm_lead(format_address, osv.osv):
|
|||
multi='day_close', type="float", store=True),
|
||||
'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
|
||||
|
||||
# Messaging and marketing
|
||||
'message_bounce': fields.integer('Bounce'),
|
||||
# Only used for type opportunity
|
||||
'probability': fields.float('Success Rate (%)', group_operator="avg"),
|
||||
'planned_revenue': fields.float('Expected Revenue', track_visibility='always'),
|
||||
'ref': fields.reference('Reference', selection=crm._links_get, size=128),
|
||||
'ref2': fields.reference('Reference 2', selection=crm._links_get, size=128),
|
||||
'ref': fields.reference('Reference', selection=openerp.addons.base.res.res_request.referencable_models),
|
||||
'ref2': fields.reference('Reference 2', selection=openerp.addons.base.res.res_request.referencable_models),
|
||||
'phone': fields.char("Phone", size=64),
|
||||
'date_deadline': fields.date('Expected Closing', help="Estimate of the date on which the opportunity will be won."),
|
||||
'date_action': fields.date('Next Action Date', select=True),
|
||||
|
@ -296,7 +299,7 @@ class crm_lead(format_address, osv.osv):
|
|||
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.lead', context=c),
|
||||
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
|
||||
'color': 0,
|
||||
'date_last_stage_update': fields.datetime.now(),
|
||||
'date_last_stage_update': fields.datetime.now,
|
||||
}
|
||||
|
||||
_sql_constraints = [
|
||||
|
|
|
@ -169,16 +169,17 @@
|
|||
<field name="description"/>
|
||||
</page>
|
||||
<page string="Extra Info">
|
||||
<group string="Categorization" groups="base.group_multi_company,base.group_no_one" name="categorization">
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"
|
||||
widget="selection" colspan="2"/>
|
||||
</group>
|
||||
<group string="Mailings">
|
||||
<field name="opt_out"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<group>
|
||||
<group>
|
||||
<group string="Categorization" groups="base.group_multi_company,base.group_no_one" name="categorization">
|
||||
<field name="company_id"
|
||||
groups="base.group_multi_company"
|
||||
widget="selection"/>
|
||||
</group>
|
||||
<group string="Mailings">
|
||||
<field name="opt_out"/>
|
||||
<field name="message_bounce"/>
|
||||
</group>
|
||||
<group string="Misc">
|
||||
<field name="probability" groups="base.group_no_one"/>
|
||||
<field name="active"/>
|
||||
<field name="referred"/>
|
||||
|
|
|
@ -58,10 +58,11 @@ class crm_configuration(osv.TransientModel):
|
|||
implied_group='crm.group_fund_raising',
|
||||
help="""Allows you to trace and manage your activities for fund raising."""),
|
||||
'module_crm_claim': fields.boolean("Manage Customer Claims",
|
||||
help="""Allows you to track your customers/suppliers claims and grievances.
|
||||
This installs the module crm_claim."""),
|
||||
help='Allows you to track your customers/suppliers claims and grievances.\n'
|
||||
'-This installs the module crm_claim.'),
|
||||
'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. This installs the module crm_helpdesk."""),
|
||||
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."""),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Create a user as 'Crm Salesmanager'
|
||||
-
|
||||
!record {model: res.users, id: crm_res_users_salesmanager}:
|
||||
!record {model: res.users, id: crm_res_users_salesmanager, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Crm Sales manager
|
||||
login: csm
|
||||
|
@ -16,7 +16,7 @@
|
|||
-
|
||||
Create a user as 'Crm Salesman'
|
||||
-
|
||||
!record {model: res.users, id: crm_res_users_salesman}:
|
||||
!record {model: res.users, id: crm_res_users_salesman, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Crm Salesman
|
||||
login: csu
|
||||
|
|
|
@ -63,7 +63,7 @@ class crm_lead2opportunity_partner(osv.osv_memory):
|
|||
for id in ids:
|
||||
tomerge.add(id)
|
||||
if email:
|
||||
ids = lead_obj.search(cr, uid, [('email_from', 'ilike', email[0]), ('probability', '<', '100')])
|
||||
ids = lead_obj.search(cr, uid, [('email_from', '=ilike', email[0]), ('probability', '<', '100')])
|
||||
for id in ids:
|
||||
tomerge.add(id)
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import openerp
|
||||
from openerp.addons.crm import crm
|
||||
from openerp.osv import fields, osv
|
||||
from openerp import tools
|
||||
|
@ -83,7 +84,7 @@ class crm_claim(osv.osv):
|
|||
'date_deadline': fields.date('Deadline'),
|
||||
'date_closed': fields.datetime('Closed', readonly=True),
|
||||
'date': fields.datetime('Claim Date', select=True),
|
||||
'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
|
||||
'ref': fields.reference('Reference', selection=openerp.addons.base.res.res_request.referencable_models),
|
||||
'categ_id': fields.many2one('crm.case.categ', 'Category', \
|
||||
domain="[('section_id','=',section_id),\
|
||||
('object_id.model', '=', 'crm.claim')]"),
|
||||
|
@ -108,7 +109,7 @@ class crm_claim(osv.osv):
|
|||
_defaults = {
|
||||
'user_id': lambda s, cr, uid, c: uid,
|
||||
'section_id': lambda s, cr, uid, c: s._get_default_section_id(cr, uid, c),
|
||||
'date': fields.datetime.now(),
|
||||
'date': fields.datetime.now,
|
||||
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.case', context=c),
|
||||
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
|
||||
'active': lambda *a: 1,
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
import openerp
|
||||
from openerp.addons.crm import crm
|
||||
from openerp.osv import fields, osv
|
||||
from openerp import tools
|
||||
|
@ -53,8 +54,8 @@ class crm_helpdesk(osv.osv):
|
|||
'email_cc': fields.text('Watchers Emails', size=252 , help="These email addresses will be added to the CC field of all inbound and outbound emails for this record before being sent. Separate multiple email addresses with a comma"),
|
||||
'email_from': fields.char('Email', size=128, help="Destination email for email gateway"),
|
||||
'date': fields.datetime('Date'),
|
||||
'ref' : fields.reference('Reference', selection=crm._links_get, size=128),
|
||||
'ref2' : fields.reference('Reference 2', selection=crm._links_get, size=128),
|
||||
'ref': fields.reference('Reference', selection=openerp.addons.base.res.res_request.referencable_models),
|
||||
'ref2': fields.reference('Reference 2', selection=openerp.addons.base.res.res_request.referencable_models),
|
||||
'channel_id': fields.many2one('crm.case.channel', 'Channel', help="Communication channel."),
|
||||
'planned_revenue': fields.float('Planned Revenue'),
|
||||
'planned_cost': fields.float('Planned Costs'),
|
||||
|
@ -80,7 +81,7 @@ class crm_helpdesk(osv.osv):
|
|||
'active': lambda *a: 1,
|
||||
'user_id': lambda s, cr, uid, c: uid,
|
||||
'state': lambda *a: 'draft',
|
||||
'date': lambda *a: fields.datetime.now(),
|
||||
'date': fields.datetime.now,
|
||||
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'crm.helpdesk', context=c),
|
||||
'priority': lambda *a: crm.AVAILABLE_PRIORITIES[2][0],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
# Estonian translation for openobject-addons
|
||||
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
|
||||
# This file is distributed under the same license as the openobject-addons package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
|
||||
#
|
||||
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: 2013-10-09 15:39+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Estonian <et@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: 2013-10-10 04:41+0000\n"
|
||||
"X-Generator: Launchpad (build 16799)\n"
|
||||
|
||||
#. module: crm_todo
|
||||
#: model:ir.model,name:crm_todo.model_project_task
|
||||
msgid "Task"
|
||||
msgstr "Ülesanne"
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
msgid "Timebox"
|
||||
msgstr "Ajalahter"
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
msgid "Lead"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
msgid "For cancelling the task"
|
||||
msgstr "Ülesande katkestamiseks"
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
msgid "Next"
|
||||
msgstr "Järgmine"
|
||||
|
||||
#. module: crm_todo
|
||||
#: model:ir.actions.act_window,name:crm_todo.crm_todo_action
|
||||
#: model:ir.ui.menu,name:crm_todo.menu_crm_todo
|
||||
msgid "My Tasks"
|
||||
msgstr "Minu ülesanded"
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
#: field:crm.lead,task_ids:0
|
||||
msgid "Tasks"
|
||||
msgstr "Ülesanded"
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
msgid "Done"
|
||||
msgstr "Valmis"
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
msgid "Cancel"
|
||||
msgstr "Katkesta"
|
||||
|
||||
#. module: crm_todo
|
||||
#: model:ir.model,name:crm_todo.model_crm_lead
|
||||
msgid "Lead/Opportunity"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_todo
|
||||
#: field:project.task,lead_id:0
|
||||
msgid "Lead / Opportunity"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
msgid "For changing to done state"
|
||||
msgstr ""
|
||||
|
||||
#. module: crm_todo
|
||||
#: view:crm.lead:0
|
||||
msgid "Previous"
|
||||
msgstr "Eelmine"
|
|
@ -75,7 +75,7 @@ class email_template(osv.osv):
|
|||
_description = 'Email Templates'
|
||||
_order = 'name'
|
||||
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
"""Render the given template text, replace mako expressions ``${expr}``
|
||||
with the result of evaluating these expressions with
|
||||
an evaluation context containing:
|
||||
|
@ -87,46 +87,60 @@ class email_template(osv.osv):
|
|||
|
||||
:param str template: the template text to render
|
||||
:param str model: model name of the document record this mail is related to.
|
||||
:param int res_id: id of the document record this mail is related to.
|
||||
:param int res_ids: list of ids of document records those mails are related to.
|
||||
"""
|
||||
if not template:
|
||||
return u""
|
||||
if context is None:
|
||||
context = {}
|
||||
try:
|
||||
template = tools.ustr(template)
|
||||
record = None
|
||||
if res_id:
|
||||
record = self.pool[model].browse(cr, uid, res_id, context=context)
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
variables = {
|
||||
'object': record,
|
||||
'user': user,
|
||||
'ctx': context, # context kw would clash with mako internals
|
||||
}
|
||||
result = mako_template_env.from_string(template).render(variables)
|
||||
if result == u"False":
|
||||
result = u""
|
||||
return result
|
||||
except Exception:
|
||||
_logger.exception("failed to render mako template value %r", template)
|
||||
return u""
|
||||
results = dict.fromkeys(res_ids, u"")
|
||||
|
||||
def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
|
||||
# try to load the template
|
||||
try:
|
||||
template = mako_template_env.from_string(tools.ustr(template))
|
||||
except Exception:
|
||||
_logger.exception("Failed to load template %r", template)
|
||||
return results
|
||||
|
||||
# prepare template variables
|
||||
user = self.pool.get('res.users').browse(cr, uid, uid, context=context)
|
||||
records = self.pool[model].browse(cr, uid, res_ids, context=context) or [None]
|
||||
variables = {
|
||||
'user': user,
|
||||
'ctx': context, # context kw would clash with mako internals
|
||||
}
|
||||
for record in records:
|
||||
res_id = record.id if record else None
|
||||
variables['object'] = record
|
||||
try:
|
||||
render_result = template.render(variables)
|
||||
except Exception:
|
||||
_logger.exception("Failed to render template %r using values %r" % (template, variables))
|
||||
render_result = u""
|
||||
if render_result == u"False":
|
||||
render_result = u""
|
||||
results[res_id] = render_result
|
||||
return results
|
||||
|
||||
def get_email_template_batch(self, cr, uid, template_id=False, res_ids=None, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
if res_ids is None:
|
||||
res_ids = [None]
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
|
||||
if not template_id:
|
||||
return False
|
||||
return results
|
||||
template = self.browse(cr, uid, template_id, context)
|
||||
lang = self.render_template(cr, uid, template.lang, template.model, record_id, context)
|
||||
if lang:
|
||||
# Use translated template if necessary
|
||||
ctx = context.copy()
|
||||
ctx['lang'] = lang
|
||||
template = self.browse(cr, uid, template.id, ctx)
|
||||
else:
|
||||
template = self.browse(cr, uid, int(template_id), context)
|
||||
return template
|
||||
langs = self.render_template_batch(cr, uid, template.lang, template.model, res_ids, context)
|
||||
for res_id, lang in langs.iteritems():
|
||||
if lang:
|
||||
# Use translated template if necessary
|
||||
ctx = context.copy()
|
||||
ctx['lang'] = lang
|
||||
template = self.browse(cr, uid, template.id, ctx)
|
||||
else:
|
||||
template = self.browse(cr, uid, int(template_id), context)
|
||||
results[res_id] = template
|
||||
return results
|
||||
|
||||
def onchange_model_id(self, cr, uid, ids, model_id, context=None):
|
||||
mod_name = False
|
||||
|
@ -308,64 +322,75 @@ class email_template(osv.osv):
|
|||
})
|
||||
return {'value': result}
|
||||
|
||||
def generate_email(self, cr, uid, template_id, res_id, context=None):
|
||||
"""Generates an email from the template for given (model, res_id) pair.
|
||||
def generate_email_batch(self, cr, uid, template_id, res_ids, context=None, fields=None):
|
||||
"""Generates an email from the template for given the given model based on
|
||||
records given by res_ids.
|
||||
|
||||
:param template_id: id of the template to render.
|
||||
:param res_id: id of the record to use for rendering the template (model
|
||||
is taken from template definition)
|
||||
:returns: a dict containing all relevant fields for creating a new
|
||||
mail.mail entry, with one extra key ``attachments``, in the
|
||||
format expected by :py:meth:`mail_thread.message_post`.
|
||||
:param template_id: id of the template to render.
|
||||
:param res_id: id of the record to use for rendering the template (model
|
||||
is taken from template definition)
|
||||
:returns: a dict containing all relevant fields for creating a new
|
||||
mail.mail entry, with one extra key ``attachments``, in the
|
||||
format expected by :py:meth:`mail_thread.message_post`.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
if fields is None:
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']
|
||||
|
||||
report_xml_pool = self.pool.get('ir.actions.report.xml')
|
||||
template = self.get_email_template(cr, uid, template_id, res_id, context)
|
||||
values = {}
|
||||
for field in ['subject', 'body_html', 'email_from',
|
||||
'email_to', 'partner_to', 'email_cc', 'reply_to']:
|
||||
values[field] = self.render_template(cr, uid, getattr(template, field),
|
||||
template.model, res_id, context=context) \
|
||||
or False
|
||||
if template.user_signature:
|
||||
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
||||
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
|
||||
res_ids_to_templates = self.get_email_template_batch(cr, uid, template_id, res_ids, context)
|
||||
|
||||
if values['body_html']:
|
||||
values['body'] = tools.html_sanitize(values['body_html'])
|
||||
# templates: res_id -> template; template -> res_ids
|
||||
templates_to_res_ids = {}
|
||||
for res_id, template in res_ids_to_templates.iteritems():
|
||||
templates_to_res_ids.setdefault(template, []).append(res_id)
|
||||
|
||||
values.update(mail_server_id=template.mail_server_id.id or False,
|
||||
auto_delete=template.auto_delete,
|
||||
model=template.model,
|
||||
res_id=res_id or False)
|
||||
results = dict()
|
||||
for template, template_res_ids in templates_to_res_ids.iteritems():
|
||||
# generate fields value for all res_ids linked to the current template
|
||||
for field in ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to']:
|
||||
generated_field_values = self.render_template_batch(cr, uid, getattr(template, field), template.model, template_res_ids, context=context)
|
||||
for res_id, field_value in generated_field_values.iteritems():
|
||||
results.setdefault(res_id, dict())[field] = field_value
|
||||
# update values for all res_ids
|
||||
for res_id in template_res_ids:
|
||||
values = results[res_id]
|
||||
if template.user_signature:
|
||||
signature = self.pool.get('res.users').browse(cr, uid, uid, context).signature
|
||||
values['body_html'] = tools.append_content_to_html(values['body_html'], signature)
|
||||
if values['body_html']:
|
||||
values['body'] = tools.html_sanitize(values['body_html'])
|
||||
values.update(
|
||||
mail_server_id=template.mail_server_id.id or False,
|
||||
auto_delete=template.auto_delete,
|
||||
model=template.model,
|
||||
res_id=res_id or False,
|
||||
attachment_ids=[attach.id for attach in template.attachment_ids],
|
||||
)
|
||||
|
||||
attachments = []
|
||||
# Add report in attachments
|
||||
if template.report_template:
|
||||
report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
|
||||
report_service = report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
|
||||
# Ensure report is rendered using template's language
|
||||
ctx = context.copy()
|
||||
if template.lang:
|
||||
ctx['lang'] = self.render_template(cr, uid, template.lang, template.model, res_id, context)
|
||||
result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
|
||||
result = base64.b64encode(result)
|
||||
if not report_name:
|
||||
report_name = 'report.' + report_service
|
||||
ext = "." + format
|
||||
if not report_name.endswith(ext):
|
||||
report_name += ext
|
||||
attachments.append((report_name, result))
|
||||
# Add report in attachments
|
||||
if template.report_template:
|
||||
for res_id in template_res_ids:
|
||||
attachments = []
|
||||
report_name = self.render_template(cr, uid, template.report_name, template.model, res_id, context=context)
|
||||
report_service = report_xml_pool.browse(cr, uid, template.report_template.id, context).report_name
|
||||
# Ensure report is rendered using template's language
|
||||
ctx = context.copy()
|
||||
if template.lang:
|
||||
ctx['lang'] = self.render_template_batch(cr, uid, template.lang, template.model, res_id, context) # take 0 ?
|
||||
result, format = openerp.report.render_report(cr, uid, [res_id], report_service, {'model': template.model}, ctx)
|
||||
result = base64.b64encode(result)
|
||||
if not report_name:
|
||||
report_name = 'report.' + report_service
|
||||
ext = "." + format
|
||||
if not report_name.endswith(ext):
|
||||
report_name += ext
|
||||
attachments.append((report_name, result))
|
||||
|
||||
attachment_ids = []
|
||||
# Add template attachments
|
||||
for attach in template.attachment_ids:
|
||||
attachment_ids.append(attach.id)
|
||||
values['attachments'] = attachments
|
||||
|
||||
values['attachments'] = attachments
|
||||
values['attachment_ids'] = attachment_ids
|
||||
return values
|
||||
return results
|
||||
|
||||
def send_mail(self, cr, uid, template_id, res_id, force_send=False, raise_exception=False, context=None):
|
||||
"""Generates a new mail message for the given template and record,
|
||||
|
@ -412,4 +437,14 @@ class email_template(osv.osv):
|
|||
mail_mail.send(cr, uid, [msg_id], raise_exception=raise_exception, context=context)
|
||||
return msg_id
|
||||
|
||||
# Compatibility method
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
return self.render_template_batch(cr, uid, template, model, [res_id], context)[res_id]
|
||||
|
||||
def get_email_template(self, cr, uid, template_id=False, record_id=None, context=None):
|
||||
return self.get_email_template_batch(cr, uid, template_id, [record_id], context)[record_id]
|
||||
|
||||
def generate_email(self, cr, uid, template_id, res_id, context=None):
|
||||
return self.generate_email_batch(cr, uid, template_id, [res_id], context)[res_id]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -33,48 +33,45 @@ class actions_server(osv.Model):
|
|||
return res
|
||||
|
||||
_columns = {
|
||||
'email_from': fields.char('From',
|
||||
help="Sender address; define the template to see its value. If not set, the default "
|
||||
"value will be the author's email alias if configured, or email address."),
|
||||
'email_to': fields.char('To (Emails)',
|
||||
help="Comma-separated recipient addresses; define the template to see its value"),
|
||||
'partner_to': fields.char('To (Partners)',
|
||||
help="Comma-separated ids of recipient partners; define the template to see its value"),
|
||||
'subject': fields.char('Subject',
|
||||
help="Email subject; define the template to see its value"),
|
||||
'body_html': fields.text('Body',
|
||||
help="Rich-text/HTML version of the message; define the template to see its value"),
|
||||
'template_id': fields.many2one('email.template', 'Email Template', ondelete='set null',
|
||||
help="Define the email template to use for the email to send.")
|
||||
'email_from': fields.related(
|
||||
'template_id', 'email_from', type='char',
|
||||
readonly=True, string='From'
|
||||
),
|
||||
'email_to': fields.related(
|
||||
'template_id', 'email_to', type='char',
|
||||
readonly=True, string='To (Emails)'
|
||||
),
|
||||
'partner_to': fields.related(
|
||||
'template_id', 'partner_to', type='char',
|
||||
readonly=True, string='To (Partners)'
|
||||
),
|
||||
'subject': fields.related(
|
||||
'template_id', 'subject', type='char',
|
||||
readonly=True, string='Subject'
|
||||
),
|
||||
'body_html': fields.related(
|
||||
'template_id', 'body_html', type='text',
|
||||
readonly=True, string='Body'
|
||||
),
|
||||
'template_id': fields.many2one(
|
||||
'email.template', 'Email Template', ondelete='set null',
|
||||
domain="[('model_id', '=', model_id)]",
|
||||
),
|
||||
}
|
||||
|
||||
def on_change_template_id(self, cr, uid, ids, template_id, context=None):
|
||||
""" Render the raw template in the server action fields. """
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to']
|
||||
if template_id:
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
|
||||
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
|
||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||
if not values.get('email_from'):
|
||||
return {'warning': {'title': 'Incomplete template', 'message': 'Your template should define email_from'}, 'value': values}
|
||||
else:
|
||||
values = self.default_get(cr, uid, ['subject', 'body_html', 'email_from', 'email_to', 'partner_to'], context=context)
|
||||
values = dict.fromkeys(fields, False)
|
||||
|
||||
return {'value': values}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
if values.get('template_id'):
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
|
||||
template_values = self.pool.get('email.template').read(cr, uid, values.get('template_id'), fields, context)
|
||||
values.update(dict((field, template_values[field]) for field in fields if template_values.get(field)))
|
||||
return super(actions_server, self).create(cr, uid, values, context=context)
|
||||
|
||||
def write(self, cr, uid, ids, values, context=None):
|
||||
if values.get('template_id'):
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
|
||||
template_values = self.pool.get('email.template').read(cr, uid, values.get('template_id'), fields, context)
|
||||
values.update(dict((field, template_values[field]) for field in fields if template_values.get(field)))
|
||||
return super(actions_server, self).write(cr, uid, ids, values, context=context)
|
||||
|
||||
def run_action_email(self, cr, uid, action, eval_context=None, context=None):
|
||||
if not action.template_id or not context.get('active_id'):
|
||||
return False
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
<group attrs="{'invisible': [('model_id', '=', False)]}">
|
||||
<field name="template_id"
|
||||
on_change='on_change_template_id(template_id)'
|
||||
domain="[('model_id', '=', model_id)]"
|
||||
attrs="{'required': [('state', '=', 'email')]}"/>
|
||||
<p colspan="2" attrs="{'invisible': [('template_id', '!=', False)]}">
|
||||
Choose a template to display its values.
|
||||
|
|
|
@ -20,10 +20,10 @@
|
|||
##############################################################################
|
||||
|
||||
import base64
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
|
||||
class test_message_compose(TestMailBase):
|
||||
class test_message_compose(TestMail):
|
||||
|
||||
def setUp(self):
|
||||
super(test_message_compose, self).setUp()
|
||||
|
@ -73,7 +73,7 @@ class test_message_compose(TestMailBase):
|
|||
|
||||
# 1. Comment on pigs
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': 'Forget me subject', 'body': '<p>Dummy body</p>'},
|
||||
{'subject': 'Forget me subject', 'body': '<p>Dummy body</p>', 'post': True},
|
||||
{'default_composition_mode': 'comment',
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
|
@ -101,7 +101,7 @@ class test_message_compose(TestMailBase):
|
|||
'default_template_id': email_template_id,
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
||||
}
|
||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
|
||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body', 'post': True}, context)
|
||||
compose = mail_compose.browse(cr, uid, compose_id, context)
|
||||
onchange_res = compose.onchange_template_id(email_template_id, 'comment', 'mail.group', self.group_pigs_id)['value']
|
||||
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||
|
@ -145,7 +145,7 @@ class test_message_compose(TestMailBase):
|
|||
'default_partner_ids': [p_a_id],
|
||||
'active_ids': [self.group_pigs_id, self.group_bird_id]
|
||||
}
|
||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body'}, context)
|
||||
compose_id = mail_compose.create(cr, uid, {'subject': 'Forget me subject', 'body': 'Dummy body', 'post': True}, context)
|
||||
compose = mail_compose.browse(cr, uid, compose_id, context)
|
||||
onchange_res = compose.onchange_template_id(email_template_id, 'mass_mail', 'mail.group', self.group_pigs_id)['value']
|
||||
onchange_res['partner_ids'] = [(4, partner_id) for partner_id in onchange_res.pop('partner_ids', [])]
|
||||
|
|
|
@ -62,6 +62,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
for wizard in self.browse(cr, uid, ids, context=context):
|
||||
if wizard.template_id:
|
||||
wizard_context['mail_notify_user_signature'] = False # template user_signature is added when generating body_html
|
||||
wizard_context['mail_auto_delete'] = wizard.template_id.auto_delete # mass mailing: use template auto_delete value -> note, for emails mass mailing only
|
||||
if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id:
|
||||
continue
|
||||
new_attachment_ids = []
|
||||
|
@ -81,7 +82,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
|
||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||
elif template_id:
|
||||
values = self.generate_email_for_composer(cr, uid, template_id, res_id, context=context)
|
||||
values = self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context=context)[res_id]
|
||||
# transform attachments into attachment_ids; not attached to the document because this will
|
||||
# be done further in the posting process, allowing to clean database if email not send
|
||||
values['attachment_ids'] = values.pop('attachment_ids', [])
|
||||
|
@ -147,45 +148,55 @@ class mail_compose_message(osv.TransientModel):
|
|||
partner_ids.append(int(partner_id))
|
||||
return partner_ids
|
||||
|
||||
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
|
||||
def generate_email_for_composer_batch(self, cr, uid, template_id, res_ids, context=None):
|
||||
""" Call email_template.generate_email(), get fields relevant for
|
||||
mail.compose.message, transform email_cc and email_to into partner_ids """
|
||||
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
|
||||
# filter template values
|
||||
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
|
||||
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
|
||||
values['body'] = values.pop('body_html', '')
|
||||
values = dict.fromkeys(res_ids, False)
|
||||
|
||||
# transform email_to, email_cc into partner_ids
|
||||
ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
|
||||
partner_ids = self._get_or_create_partners_from_values(cr, uid, values, context=ctx)
|
||||
# legacy template behavior: void values do not erase existing values and the
|
||||
# related key is removed from the values dict
|
||||
if partner_ids:
|
||||
values['partner_ids'] = list(partner_ids)
|
||||
template_values = self.pool.get('email.template').generate_email_batch(cr, uid, template_id, res_ids, context=context)
|
||||
for res_id in res_ids:
|
||||
res_id_values = dict((field, template_values[res_id][field]) for field in fields if template_values[res_id].get(field))
|
||||
res_id_values['body'] = res_id_values.pop('body_html', '')
|
||||
|
||||
# transform email_to, email_cc into partner_ids
|
||||
ctx = dict((k, v) for k, v in (context or {}).items() if not k.startswith('default_'))
|
||||
partner_ids = self._get_or_create_partners_from_values(cr, uid, res_id_values, context=ctx)
|
||||
# legacy template behavior: void values do not erase existing values and the
|
||||
# related key is removed from the values dict
|
||||
if partner_ids:
|
||||
res_id_values['partner_ids'] = list(partner_ids)
|
||||
|
||||
values[res_id] = res_id_values
|
||||
return values
|
||||
|
||||
def render_message(self, cr, uid, wizard, res_id, context=None):
|
||||
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
||||
""" Override to handle templates. """
|
||||
# generate the composer email
|
||||
# generate template-based values
|
||||
if wizard.template_id:
|
||||
values = self.generate_email_for_composer(cr, uid, wizard.template_id.id, res_id, context=context)
|
||||
template_values = self.generate_email_for_composer_batch(cr, uid, wizard.template_id.id, res_ids, context=context)
|
||||
else:
|
||||
values = {}
|
||||
# remove attachments as they should not be rendered
|
||||
values.pop('attachment_ids', None)
|
||||
# get values to return
|
||||
email_dict = super(mail_compose_message, self).render_message(cr, uid, wizard, res_id, context)
|
||||
# those values are not managed; they are readonly
|
||||
email_dict.pop('email_to', None)
|
||||
email_dict.pop('email_cc', None)
|
||||
email_dict.pop('partner_to', None)
|
||||
# update template values by wizard values
|
||||
values.update(email_dict)
|
||||
return values
|
||||
template_values = dict.fromkeys(res_ids, dict())
|
||||
# generate composer values
|
||||
composer_values = super(mail_compose_message, self).render_message_batch(cr, uid, wizard, res_ids, context)
|
||||
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
return self.pool.get('email.template').render_template(cr, uid, template, model, res_id, context=context)
|
||||
for res_id in res_ids:
|
||||
# remove attachments from template values as they should not be rendered
|
||||
template_values[res_id].pop('attachment_ids', None)
|
||||
# remove some keys from composer that are readonly
|
||||
composer_values[res_id].pop('email_to', None)
|
||||
composer_values[res_id].pop('email_cc', None)
|
||||
composer_values[res_id].pop('partner_to', None)
|
||||
# update template values by composer values
|
||||
template_values[res_id].update(composer_values[res_id])
|
||||
return template_values
|
||||
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
return self.pool.get('email.template').render_template_batch(cr, uid, template, model, res_ids, context=context)
|
||||
|
||||
# Compatibility methods
|
||||
def generate_email_for_composer(self, cr, uid, template_id, res_id, context=None):
|
||||
return self.generate_email_for_composer_batch(cr, uid, template_id, [res_id], context)[res_id]
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<label string="Template Recipients" for="partner_to"
|
||||
groups="base.group_no_one"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<div groups="base.group_no_one"
|
||||
<div groups="base.group_no_one" name="template_recipients"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}">
|
||||
<group class="oe_grey">
|
||||
<!-- <label string="Partners" for="partner_to"/> -->
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Create a user as 'Event manager'
|
||||
-
|
||||
!record {model: res.users, id: res_users_eventmanager}:
|
||||
!record {model: res.users, id: res_users_eventmanager, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Event manager
|
||||
login: em
|
||||
|
@ -16,7 +16,7 @@
|
|||
-
|
||||
Create a user as 'Event user'
|
||||
-
|
||||
!record {model: res.users, id: res_users_eventuser}:
|
||||
!record {model: res.users, id: res_users_eventuser, view: False}:
|
||||
company_id: base.main_company
|
||||
name: User
|
||||
login: eu
|
||||
|
|
|
@ -346,26 +346,9 @@ class res_users(osv.osv):
|
|||
_name = 'res.users'
|
||||
_inherit = 'res.users'
|
||||
|
||||
def create(self, cr, uid, data, context=None):
|
||||
user_id = super(res_users, self).create(cr, uid, data, context=context)
|
||||
|
||||
# add shortcut unless 'noshortcut' is True in context
|
||||
if not(context and context.get('noshortcut', False)):
|
||||
data_obj = self.pool.get('ir.model.data')
|
||||
try:
|
||||
data_id = data_obj._get_id(cr, uid, 'hr', 'ir_ui_view_sc_employee')
|
||||
view_id = data_obj.browse(cr, uid, data_id, context=context).res_id
|
||||
self.pool.get('ir.ui.view_sc').copy(cr, uid, view_id, default = {
|
||||
'user_id': user_id}, context=context)
|
||||
except:
|
||||
# Tolerate a missing shortcut. See product/product.py for similar code.
|
||||
_logger.debug('Skipped meetings shortcut for user "%s".', data.get('name','<new'))
|
||||
|
||||
return user_id
|
||||
|
||||
_columns = {
|
||||
'employee_ids': fields.one2many('hr.employee', 'user_id', 'Related employees'),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -224,13 +224,6 @@
|
|||
|
||||
<menuitem action="open_view_employee_list_my" id="menu_open_view_employee_list_my" sequence="3" parent="menu_hr_main"/>
|
||||
|
||||
<record id="ir_ui_view_sc_employee" model="ir.ui.view_sc">
|
||||
<field name="name">Employees</field>
|
||||
<field name="resource">ir.ui.menu</field>
|
||||
<field name="user_id" ref="base.user_root"/>
|
||||
<field name="res_id" ref="hr.menu_open_view_employee_list_my"/>
|
||||
</record>
|
||||
|
||||
<!-- Employee architecture -->
|
||||
<record id="view_partner_tree2" model="ir.ui.view">
|
||||
<field name="name">hr.employee.tree</field>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Create a user as 'HR Manager'
|
||||
-
|
||||
!record {model: res.users, id: res_users_hr_manager}:
|
||||
!record {model: res.users, id: res_users_hr_manager, view: False}:
|
||||
company_id: base.main_company
|
||||
name: HR manager
|
||||
login: hrm
|
||||
|
@ -15,7 +15,7 @@
|
|||
-
|
||||
Create a user as 'HR Officer'
|
||||
-
|
||||
!record {model: res.users, id: res_users_hr_officer}:
|
||||
!record {model: res.users, id: res_users_hr_officer, view: False}:
|
||||
company_id: base.main_company
|
||||
name: HR Officer
|
||||
login: hro
|
||||
|
@ -29,7 +29,7 @@
|
|||
-
|
||||
Create a user as 'Employee'
|
||||
-
|
||||
!record {model: res.users, id: res_users_employee}:
|
||||
!record {model: res.users, id: res_users_employee, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Employee
|
||||
login: emp
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Create a user as 'HR Attendance Officer'
|
||||
-
|
||||
!record {model: res.users, id: res_users_attendance_officer}:
|
||||
!record {model: res.users, id: res_users_attendance_officer, view: False}:
|
||||
company_id: base.main_company
|
||||
name: HR Officer
|
||||
login: ao
|
||||
|
|
|
@ -225,7 +225,7 @@ class hr_applicant(osv.Model):
|
|||
'department_id': lambda s, cr, uid, c: s._get_default_department_id(cr, uid, c),
|
||||
'company_id': lambda s, cr, uid, c: s.pool.get('res.company')._company_default_get(cr, uid, 'hr.applicant', context=c),
|
||||
'color': 0,
|
||||
'date_last_stage_update': fields.datetime.now(),
|
||||
'date_last_stage_update': fields.datetime.now,
|
||||
}
|
||||
|
||||
_group_by_full = {
|
||||
|
|
|
@ -27,11 +27,11 @@ class hr_applicant_settings(osv.osv_memory):
|
|||
|
||||
_columns = {
|
||||
'module_document_ftp': fields.boolean('Allow the automatic indexation of resumes',
|
||||
help="""Manage your CV's and motivation letter related to all applicants.
|
||||
This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)"""),
|
||||
help='Manage your CV\'s and motivation letter related to all applicants.\n'
|
||||
'-This installs the module document_ftp. This will install the knowledge management module in order to allow you to search using specific keywords through the content of all documents (PDF, .DOCx...)'),
|
||||
'fetchmail_applicants': fields.boolean('Create applicants from an incoming email account',
|
||||
fetchmail_model='hr.applicant', fetchmail_name='Incoming HR Applications',
|
||||
help ="""Allow applicants to send their job application to an email address (jobs@mycompany.com),
|
||||
and create automatically application documents in the system."""),
|
||||
help='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
|
||||
'and create automatically application documents in the system.'),
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Create a user as 'HR Recruitment Officer'
|
||||
-
|
||||
!record {model: res.users, id: res_users_hr_recruitment_officer}:
|
||||
!record {model: res.users, id: res_users_hr_recruitment_officer, view: False}:
|
||||
company_id: base.main_company
|
||||
name: HR Recruitment Officer
|
||||
login: hrro
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
-
|
||||
Create a user as 'HR timesheet Manager'
|
||||
-
|
||||
!record {model: res.users, id: res_hr_timesheet_manager}:
|
||||
!record {model: res.users, id: res_hr_timesheet_manager, view: False}:
|
||||
company_id: base.main_company
|
||||
name: HR timesheet manager
|
||||
login: hrtm
|
||||
|
@ -15,7 +15,7 @@
|
|||
-
|
||||
Create a user as 'HR timesheet Officer'
|
||||
-
|
||||
!record {model: res.users, id: res_hr_timesheet_officer}:
|
||||
!record {model: res.users, id: res_hr_timesheet_officer, view: False}:
|
||||
company_id: base.main_company
|
||||
name: HR timesheet Officer
|
||||
login: hrto
|
||||
|
@ -29,7 +29,7 @@
|
|||
-
|
||||
Create a user as 'Timesheet Employee'
|
||||
-
|
||||
!record {model: res.users, id: res_hr_timesheet_employee}:
|
||||
!record {model: res.users, id: res_hr_timesheet_employee, view: False}:
|
||||
company_id: base.main_company
|
||||
name: Timesheet Employee
|
||||
login: empt
|
||||
|
|
|
@ -133,7 +133,7 @@ class hr_si_project(osv.osv_memory):
|
|||
|
||||
def check_state(self, cr, uid, ids, context=None):
|
||||
obj_model = self.pool.get('ir.model.data')
|
||||
emp_id = self.default_get(cr, uid, context)['emp_id']
|
||||
emp_id = self.default_get(cr, uid, ['emp_id'], context)['emp_id']
|
||||
# get the latest action (sign_in or out) for this employee
|
||||
cr.execute('select action from hr_attendance where employee_id=%s and action in (\'sign_in\',\'sign_out\') order by name desc limit 1', (emp_id,))
|
||||
res = (cr.fetchone() or ('sign_out',))[0]
|
||||
|
|
|
@ -458,7 +458,7 @@ class hr_timesheet_sheet_sheet_day(osv.osv):
|
|||
THEN (SUM(total_attendance) +
|
||||
CASE WHEN current_date <> name
|
||||
THEN 1440
|
||||
ELSE (EXTRACT(hour FROM current_time) * 60) + EXTRACT(minute FROM current_time)
|
||||
ELSE (EXTRACT(hour FROM current_time AT TIME ZONE 'UTC') * 60) + EXTRACT(minute FROM current_time AT TIME ZONE 'UTC')
|
||||
END
|
||||
)
|
||||
ELSE SUM(total_attendance)
|
||||
|
|
|
@ -28,8 +28,8 @@ class hr_timesheet_settings(osv.osv_memory):
|
|||
'timesheet_range': fields.selection([('day','Day'),('week','Week'),('month','Month')],
|
||||
'Validate timesheets every', help="Periodicity on which you validate your timesheets."),
|
||||
'timesheet_max_difference': fields.float('Allow a difference of time between timesheets and attendances of (in hours)',
|
||||
help="""Allowed difference in hours between the sign in/out and the timesheet
|
||||
computation for one sheet. Set this to 0 if you do not want any control."""),
|
||||
help='Allowed difference in hours between the sign in/out and the timesheet '
|
||||
'computation for one sheet. Set this to 0 if you do not want any control.'),
|
||||
}
|
||||
|
||||
def get_default_timesheet(self, cr, uid, fields, context=None):
|
||||
|
|
|
@ -303,7 +303,7 @@ class im_user(osv.osv):
|
|||
'name': fields.function(_get_name, type='char', size=200, string="Name", store=True, readonly=True),
|
||||
'assigned_name': fields.char(string="Assigned Name", size=200, required=False),
|
||||
'image': fields.related('user_id', 'image_small', type='binary', string="Image", readonly=True),
|
||||
'user_id': fields.many2one("res.users", string="User", select=True, ondelete='cascade'),
|
||||
'user_id': fields.many2one("res.users", string="User", select=True, ondelete='cascade', oldname='user'),
|
||||
'uuid': fields.char(string="UUID", size=50, select=True),
|
||||
'im_last_received': fields.integer(string="Instant Messaging Last Received Message"),
|
||||
'im_last_status': fields.boolean(strint="Instant Messaging Last Status"),
|
||||
|
|
|
@ -28,15 +28,15 @@ class knowledge_config_settings(osv.osv_memory):
|
|||
'module_document_page': fields.boolean('Create static web pages',
|
||||
help="""This installs the module document_page."""),
|
||||
'module_document': fields.boolean('Manage documents',
|
||||
help="""This is a complete document management system, with: user authentication,
|
||||
full document search (but pptx and docx are not supported), and a document dashboard.
|
||||
This installs the module document."""),
|
||||
help='This is a complete document management system, with: user authentication, '
|
||||
'full document search (but pptx and docx are not supported), and a document dashboard.\n'
|
||||
'-This installs the module document.'),
|
||||
'module_document_ftp': fields.boolean('Share repositories (FTP)',
|
||||
help="""Access your documents in OpenERP through an FTP interface.
|
||||
This installs the module document_ftp."""),
|
||||
help='Access your documents in OpenERP through an FTP interface.\n'
|
||||
'-This installs the module document_ftp.'),
|
||||
'module_document_webdav': fields.boolean('Share repositories (WebDAV)',
|
||||
help="""Access your documents in OpenERP through WebDAV.
|
||||
This installs the module document_webdav."""),
|
||||
help='Access your documents in OpenERP through WebDAV.\n'
|
||||
'-This installs the module document_webdav.'),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import base64
|
||||
import psycopg2
|
||||
|
||||
import openerp
|
||||
from openerp import SUPERUSER_ID
|
||||
import openerp.addons.web.http as oeweb
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.controllers.main import content_disposition
|
||||
|
||||
#----------------------------------------------------------
|
||||
# Controller
|
||||
#----------------------------------------------------------
|
||||
class MailController(oeweb.Controller):
|
||||
|
||||
class MailController(http.Controller):
|
||||
_cp_path = '/mail'
|
||||
|
||||
@oeweb.httprequest
|
||||
@http.httprequest
|
||||
def download_attachment(self, req, model, id, method, attachment_id, **kw):
|
||||
Model = req.session.model(model)
|
||||
res = getattr(Model, method)(int(id), int(attachment_id))
|
||||
|
@ -24,7 +23,7 @@ class MailController(oeweb.Controller):
|
|||
('Content-Disposition', content_disposition(filename, req))])
|
||||
return req.not_found()
|
||||
|
||||
@oeweb.jsonrequest
|
||||
@http.jsonrequest
|
||||
def receive(self, req):
|
||||
""" End-point to receive mail from an external SMTP server. """
|
||||
dbs = req.jsonrequest.get('databases')
|
||||
|
|
|
@ -6,6 +6,15 @@ Changelog
|
|||
`trunk (saas-2)`
|
||||
----------------
|
||||
|
||||
- ``mass_mailing_campaign`` update
|
||||
|
||||
- ``mail_mail`: moved ``reply_to`` computation from ``mail_mail`` to ``mail_message``
|
||||
where it belongs, as the field is located onto the ``mail_message`` model.
|
||||
- ``mail_compose_message``: template rendering is now done in batch. Each template
|
||||
is rendered for all res_ids, instead of all templates one id at a time.
|
||||
- ``mail_thread``: to ease inheritance, processing of routes is now done in
|
||||
message_route_process, called in message_route
|
||||
|
||||
- added support of ``active_domain`` form context, coming from the list view.
|
||||
When checking the header hook, the mass mailing will be done on all records
|
||||
matching the ``active_domain``.
|
||||
|
|
|
@ -77,7 +77,7 @@ class mail_notification(osv.Model):
|
|||
if not cr.fetchone():
|
||||
cr.execute('CREATE INDEX mail_notification_partner_id_read_starred_message_id ON mail_notification (partner_id, read, starred, message_id)')
|
||||
|
||||
def get_partners_to_notify(self, cr, uid, message, partners_to_notify=None, context=None):
|
||||
def get_partners_to_email(self, cr, uid, ids, message, context=None):
|
||||
""" Return the list of partners to notify, based on their preferences.
|
||||
|
||||
:param browse_record message: mail.message to notify
|
||||
|
@ -85,13 +85,10 @@ class mail_notification(osv.Model):
|
|||
the notifications to process
|
||||
"""
|
||||
notify_pids = []
|
||||
for notification in message.notification_ids:
|
||||
for notification in self.browse(cr, uid, ids, context=context):
|
||||
if notification.read:
|
||||
continue
|
||||
partner = notification.partner_id
|
||||
# If partners_to_notify specified: restrict to them
|
||||
if partners_to_notify is not None and partner.id not in partners_to_notify:
|
||||
continue
|
||||
# Do not send to partners without email address defined
|
||||
if not partner.email:
|
||||
continue
|
||||
|
@ -143,14 +140,62 @@ class mail_notification(osv.Model):
|
|||
company = user.company_id.name
|
||||
sent_by = _('Sent by %(company)s using %(openerp)s.')
|
||||
signature_company = '<small>%s</small>' % (sent_by % {
|
||||
'company': company,
|
||||
'openerp': "<a style='color:inherit' href='https://www.openerp.com/'>OpenERP</a>"
|
||||
})
|
||||
'company': company,
|
||||
'openerp': "<a style='color:inherit' href='https://www.openerp.com/'>OpenERP</a>"
|
||||
})
|
||||
footer = tools.append_content_to_html(footer, signature_company, plaintext=False, container_tag='div')
|
||||
|
||||
return footer
|
||||
|
||||
def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None,
|
||||
def update_message_notification(self, cr, uid, ids, message_id, partner_ids, context=None):
|
||||
existing_pids = set()
|
||||
new_pids = set()
|
||||
new_notif_ids = []
|
||||
|
||||
for notification in self.browse(cr, uid, ids, context=context):
|
||||
existing_pids.add(notification.partner_id.id)
|
||||
|
||||
# update existing notifications
|
||||
self.write(cr, uid, ids, {'read': False}, context=context)
|
||||
|
||||
# create new notifications
|
||||
new_pids = set(partner_ids) - existing_pids
|
||||
for new_pid in new_pids:
|
||||
new_notif_ids.append(self.create(cr, uid, {'message_id': message_id, 'partner_id': new_pid, 'read': False}, context=context))
|
||||
return new_notif_ids
|
||||
|
||||
def _notify_email(self, cr, uid, ids, message_id, force_send=False, user_signature=True, context=None):
|
||||
message = self.pool['mail.message'].browse(cr, SUPERUSER_ID, message_id, context=context)
|
||||
|
||||
# compute partners
|
||||
email_pids = self.get_partners_to_email(cr, uid, ids, message, context=None)
|
||||
if not email_pids:
|
||||
return True
|
||||
|
||||
# compute email body (signature, company data)
|
||||
body_html = message.body
|
||||
user_id = message.author_id and message.author_id.user_ids and message.author_id.user_ids[0] and message.author_id.user_ids[0].id or None
|
||||
if user_signature:
|
||||
signature_company = self.get_signature_footer(cr, uid, user_id, res_model=message.model, res_id=message.res_id, context=context)
|
||||
body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
|
||||
|
||||
# compute email references
|
||||
references = message.parent_id.message_id if message.parent_id else False
|
||||
|
||||
# create email values
|
||||
mail_values = {
|
||||
'mail_message_id': message.id,
|
||||
'auto_delete': True,
|
||||
'body_html': body_html,
|
||||
'recipient_ids': [(4, id) for id in email_pids],
|
||||
'references': references,
|
||||
}
|
||||
email_notif_id = self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
|
||||
if force_send:
|
||||
self.pool.get('mail.mail').send(cr, uid, [email_notif_id], context=context)
|
||||
return True
|
||||
|
||||
def _notify(self, cr, uid, message_id, partners_to_notify=None, context=None,
|
||||
force_send=False, user_signature=True):
|
||||
""" Send by email the notification depending on the user preferences
|
||||
|
||||
|
@ -162,57 +207,14 @@ class mail_notification(osv.Model):
|
|||
:param bool user_signature: if True, the generated mail.mail body is
|
||||
the body of the related mail.message with the author's signature
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
mail_message_obj = self.pool.get('mail.message')
|
||||
notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', message_id), ('partner_id', 'in', partners_to_notify)], context=context)
|
||||
|
||||
# optional list of partners to notify: subscribe them if not already done or update the notification
|
||||
if partners_to_notify:
|
||||
notifications_to_update = []
|
||||
notified_partners = []
|
||||
notif_ids = self.search(cr, SUPERUSER_ID, [('message_id', '=', msg_id), ('partner_id', 'in', partners_to_notify)], context=context)
|
||||
for notification in self.browse(cr, SUPERUSER_ID, notif_ids, context=context):
|
||||
notified_partners.append(notification.partner_id.id)
|
||||
notifications_to_update.append(notification.id)
|
||||
partners_to_notify = filter(lambda item: item not in notified_partners, partners_to_notify)
|
||||
if notifications_to_update:
|
||||
self.write(cr, SUPERUSER_ID, notifications_to_update, {'read': False}, context=context)
|
||||
mail_message_obj.write(cr, uid, msg_id, {'notified_partner_ids': [(4, id) for id in partners_to_notify]}, context=context)
|
||||
# update or create notifications
|
||||
new_notif_ids = self.update_message_notification(cr, SUPERUSER_ID, notif_ids, message_id, partners_to_notify, context=context)
|
||||
|
||||
# mail_notify_noemail (do not send email) or no partner_ids: do not send, return
|
||||
if context.get('mail_notify_noemail'):
|
||||
if context and context.get('mail_notify_noemail'):
|
||||
return True
|
||||
|
||||
# browse as SUPERUSER_ID because of access to res_partner not necessarily allowed
|
||||
msg = self.pool.get('mail.message').browse(cr, SUPERUSER_ID, msg_id, context=context)
|
||||
notify_partner_ids = self.get_partners_to_notify(cr, uid, msg, partners_to_notify=partners_to_notify, context=context)
|
||||
if not notify_partner_ids:
|
||||
return True
|
||||
|
||||
# add the context in the email
|
||||
# TDE FIXME: commented, to be improved in a future branch
|
||||
# quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
|
||||
|
||||
# add signature
|
||||
body_html = msg.body
|
||||
user_id = msg.author_id and msg.author_id.user_ids and msg.author_id.user_ids[0] and msg.author_id.user_ids[0].id or None
|
||||
if user_signature:
|
||||
signature_company = self.get_signature_footer(cr, uid, user_id, res_model=msg.model, res_id=msg.res_id, context=context)
|
||||
body_html = tools.append_content_to_html(body_html, signature_company, plaintext=False, container_tag='div')
|
||||
|
||||
references = False
|
||||
if msg.parent_id:
|
||||
references = msg.parent_id.message_id
|
||||
|
||||
mail_values = {
|
||||
'mail_message_id': msg.id,
|
||||
'auto_delete': True,
|
||||
'body_html': body_html,
|
||||
'recipient_ids': [(4, id) for id in notify_partner_ids],
|
||||
'references': references,
|
||||
}
|
||||
mail_mail = self.pool.get('mail.mail')
|
||||
email_notif_id = mail_mail.create(cr, uid, mail_values, context=context)
|
||||
|
||||
if force_send:
|
||||
mail_mail.send(cr, uid, [email_notif_id], context=context)
|
||||
return True
|
||||
self._notify_email(cr, SUPERUSER_ID, new_notif_ids, message_id, force_send, user_signature, context=context)
|
||||
|
|
|
@ -61,15 +61,9 @@ class mail_mail(osv.Model):
|
|||
# Auto-detected based on create() - if 'mail_message_id' was passed then this mail is a notification
|
||||
# and during unlink() we will not cascade delete the parent and its attachments
|
||||
'notification': fields.boolean('Is Notification',
|
||||
help='Mail has been created to notify people of an existing mail.message')
|
||||
help='Mail has been created to notify people of an existing mail.message'),
|
||||
}
|
||||
|
||||
def _get_default_from(self, cr, uid, context=None):
|
||||
""" Kept for compatibility
|
||||
TDE TODO: remove me in 8.0
|
||||
"""
|
||||
return self.pool['mail.message']._get_default_from(cr, uid, context=context)
|
||||
|
||||
_defaults = {
|
||||
'state': 'outgoing',
|
||||
}
|
||||
|
@ -81,74 +75,11 @@ class mail_mail(osv.Model):
|
|||
context = dict(context, default_type=None)
|
||||
return super(mail_mail, self).default_get(cr, uid, fields, context=context)
|
||||
|
||||
def _get_reply_to(self, cr, uid, values, context=None):
|
||||
""" Return a specific reply_to: alias of the document through message_get_reply_to
|
||||
or take the email_from
|
||||
"""
|
||||
# if value specified: directly return it
|
||||
if values.get('reply_to'):
|
||||
return values.get('reply_to')
|
||||
format_name = True # whether to use a 'Followers of Pigs <pigs@openerp.com' format
|
||||
email_reply_to = None
|
||||
|
||||
ir_config_parameter = self.pool.get("ir.config_parameter")
|
||||
catchall_domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context)
|
||||
|
||||
# model, res_id, email_from: comes from values OR related message
|
||||
model, res_id, email_from = values.get('model'), values.get('res_id'), values.get('email_from')
|
||||
if values.get('mail_message_id'):
|
||||
message = self.pool.get('mail.message').browse(cr, uid, values.get('mail_message_id'), context=context)
|
||||
if message.reply_to:
|
||||
email_reply_to = message.reply_to
|
||||
format_name = False
|
||||
if not model:
|
||||
model = message.model
|
||||
if not res_id:
|
||||
res_id = message.res_id
|
||||
if not email_from:
|
||||
email_from = message.email_from
|
||||
|
||||
# if model and res_id: try to use ``message_get_reply_to`` that returns the document alias
|
||||
if not email_reply_to and model and res_id and hasattr(self.pool[model], 'message_get_reply_to'):
|
||||
email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0]
|
||||
# no alias reply_to -> catchall alias
|
||||
if not email_reply_to:
|
||||
catchall_alias = ir_config_parameter.get_param(cr, uid, "mail.catchall.alias", context=context)
|
||||
if catchall_domain and catchall_alias:
|
||||
email_reply_to = '%s@%s' % (catchall_alias, catchall_domain)
|
||||
|
||||
# still no reply_to -> reply_to will be the email_from
|
||||
if not email_reply_to and email_from:
|
||||
email_reply_to = email_from
|
||||
|
||||
# format 'Document name <email_address>'
|
||||
if email_reply_to and model and res_id and format_name:
|
||||
emails = tools.email_split(email_reply_to)
|
||||
if emails:
|
||||
email_reply_to = emails[0]
|
||||
document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0]
|
||||
if document_name:
|
||||
# sanitize document name
|
||||
sanitized_doc_name = re.sub(r'[^\w+.]+', '-', document_name[1])
|
||||
# generate reply to
|
||||
email_reply_to = _('"Followers of %s" <%s>') % (sanitized_doc_name, email_reply_to)
|
||||
|
||||
return email_reply_to
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
# notification field: if not set, set if mail comes from an existing mail.message
|
||||
if 'notification' not in values and values.get('mail_message_id'):
|
||||
values['notification'] = True
|
||||
mail_id = super(mail_mail, self).create(cr, uid, values, context=context)
|
||||
|
||||
# reply_to: if not set, set with default values that require creation values
|
||||
# but delegate after creation because of mail_message.message_id automatic
|
||||
# creation using existence of reply_to
|
||||
if not values.get('reply_to'):
|
||||
reply_to = self._get_reply_to(cr, uid, values, context=context)
|
||||
if reply_to:
|
||||
self.write(cr, uid, [mail_id], {'reply_to': reply_to}, context=context)
|
||||
return mail_id
|
||||
return super(mail_mail, self).create(cr, uid, values, context=context)
|
||||
|
||||
def unlink(self, cr, uid, ids, context=None):
|
||||
# cascade-delete the parent message for all mails that are not created for a notification
|
||||
|
@ -213,11 +144,6 @@ class mail_mail(osv.Model):
|
|||
# mail_mail formatting, tools and send mechanism
|
||||
#------------------------------------------------------
|
||||
|
||||
# TODO in 8.0(+): maybe factorize this to enable in modules link generation
|
||||
# independently of mail_mail model
|
||||
# TODO in 8.0(+): factorize doc name sanitized and 'Followers of ...' formatting
|
||||
# because it begins to appear everywhere
|
||||
|
||||
def _get_partner_access_link(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Generate URLs for links in mails:
|
||||
- partner is an user and has read access to the document: direct link to document with model, res_id
|
||||
|
@ -232,8 +158,8 @@ class mail_mail(osv.Model):
|
|||
}
|
||||
if mail.notification:
|
||||
fragment.update({
|
||||
'message_id': mail.mail_message_id.id,
|
||||
})
|
||||
'message_id': mail.mail_message_id.id,
|
||||
})
|
||||
url = urljoin(base_url, "?%s#%s" % (urlencode(query), urlencode(fragment)))
|
||||
return _("""<small>Access your messages and documents <a style='color:inherit' href="%s">in OpenERP</a></small>""") % url
|
||||
else:
|
||||
|
@ -325,26 +251,37 @@ class mail_mail(osv.Model):
|
|||
email_list.append(self.send_get_email_dict(cr, uid, mail, context=context))
|
||||
for partner in mail.recipient_ids:
|
||||
email_list.append(self.send_get_email_dict(cr, uid, mail, partner=partner, context=context))
|
||||
# headers
|
||||
headers = {}
|
||||
bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
|
||||
catchall_domain = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.catchall.domain", context=context)
|
||||
if bounce_alias and catchall_domain:
|
||||
if mail.model and mail.res_id:
|
||||
headers['Return-Path'] = '%s-%d-%s-%d@%s' % (bounce_alias, mail.id, mail.model, mail.res_id, catchall_domain)
|
||||
else:
|
||||
headers['Return-Path'] = '%s-%d@%s' % (bounce_alias, mail.id, catchall_domain)
|
||||
|
||||
# build an RFC2822 email.message.Message object and send it without queuing
|
||||
res = None
|
||||
for email in email_list:
|
||||
msg = ir_mail_server.build_email(
|
||||
email_from = mail.email_from,
|
||||
email_to = email.get('email_to'),
|
||||
subject = email.get('subject'),
|
||||
body = email.get('body'),
|
||||
body_alternative = email.get('body_alternative'),
|
||||
email_cc = tools.email_split(mail.email_cc),
|
||||
reply_to = mail.reply_to,
|
||||
attachments = attachments,
|
||||
message_id = mail.message_id,
|
||||
references = mail.references,
|
||||
object_id = mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
|
||||
subtype = 'html',
|
||||
subtype_alternative = 'plain')
|
||||
email_from=mail.email_from,
|
||||
email_to=email.get('email_to'),
|
||||
subject=email.get('subject'),
|
||||
body=email.get('body'),
|
||||
body_alternative=email.get('body_alternative'),
|
||||
email_cc=tools.email_split(mail.email_cc),
|
||||
reply_to=mail.reply_to,
|
||||
attachments=attachments,
|
||||
message_id=mail.message_id,
|
||||
references=mail.references,
|
||||
object_id=mail.res_id and ('%s-%s' % (mail.res_id, mail.model)),
|
||||
subtype='html',
|
||||
subtype_alternative='plain',
|
||||
headers=headers)
|
||||
res = ir_mail_server.send_email(cr, uid, msg,
|
||||
mail_server_id=mail.mail_server_id.id, context=context)
|
||||
mail_server_id=mail.mail_server_id.id,
|
||||
context=context)
|
||||
if res:
|
||||
mail.write({'state': 'sent', 'message_id': res})
|
||||
mail_sent = True
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<sheet>
|
||||
<label for="subject" class="oe_edit_only"/>
|
||||
<h2><field name="subject"/></h2>
|
||||
<div>
|
||||
<div style="vertical-align: top;">
|
||||
by <field name="author_id" class="oe_inline" string="User"/> on <field name="date" 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': active_id}" states='received,sent,exception,cancel'/>
|
||||
|
@ -32,20 +32,26 @@
|
|||
</page>
|
||||
<page string="Advanced" groups="base.group_no_one">
|
||||
<group>
|
||||
<group>
|
||||
<field name="auto_delete"/>
|
||||
<field name="type"/>
|
||||
<field name="state"/>
|
||||
<field name="mail_server_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="message_id"/>
|
||||
<field name="references"/>
|
||||
<field name="partner_ids" widget="many2many_tags"/>
|
||||
<field name="notified_partner_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
<div>
|
||||
<group string="Status">
|
||||
<field name="auto_delete"/>
|
||||
<field name="type"/>
|
||||
<field name="state"/>
|
||||
<field name="mail_server_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
</div>
|
||||
<div>
|
||||
<group string="Headers">
|
||||
<field name="message_id"/>
|
||||
<field name="references"/>
|
||||
</group>
|
||||
<group string="Recipients">
|
||||
<field name="partner_ids" widget="many2many_tags"/>
|
||||
<field name="notified_partner_ids" widget="many2many_tags"/>
|
||||
</group>
|
||||
</div>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Attachments">
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
##############################################################################
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from openerp import tools
|
||||
|
||||
from email.header import decode_header
|
||||
|
@ -98,7 +100,7 @@ class mail_message(osv.Model):
|
|||
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute if the message is unread by the current user. """
|
||||
res = dict((id, False) for id in ids)
|
||||
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
notif_obj = self.pool.get('mail.notification')
|
||||
notif_ids = notif_obj.search(cr, uid, [
|
||||
('partner_id', 'in', [partner_id]),
|
||||
|
@ -117,7 +119,7 @@ class mail_message(osv.Model):
|
|||
def _get_starred(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute if the message is unread by the current user. """
|
||||
res = dict((id, False) for id in ids)
|
||||
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
notif_obj = self.pool.get('mail.notification')
|
||||
notif_ids = notif_obj.search(cr, uid, [
|
||||
('partner_id', 'in', [partner_id]),
|
||||
|
@ -206,11 +208,11 @@ class mail_message(osv.Model):
|
|||
raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias."))
|
||||
|
||||
def _get_default_author(self, cr, uid, context=None):
|
||||
return self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
return self.pool.get('res.users').browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
|
||||
_defaults = {
|
||||
'type': 'email',
|
||||
'date': lambda *a: fields.datetime.now(),
|
||||
'date': fields.datetime.now,
|
||||
'author_id': lambda self, cr, uid, ctx=None: self._get_default_author(cr, uid, ctx),
|
||||
'body': '',
|
||||
'email_from': lambda self, cr, uid, ctx=None: self._get_default_from(cr, uid, ctx),
|
||||
|
@ -264,7 +266,7 @@ class mail_message(osv.Model):
|
|||
:return number of message mark as read
|
||||
"""
|
||||
notification_obj = self.pool.get('mail.notification')
|
||||
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
user_pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)]
|
||||
if not create_missing:
|
||||
domain += [('read', '=', not read)]
|
||||
|
@ -292,7 +294,7 @@ class mail_message(osv.Model):
|
|||
(i.e. when acting on displayed messages not notified)
|
||||
"""
|
||||
notification_obj = self.pool.get('mail.notification')
|
||||
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
|
||||
user_pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
domain = [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)]
|
||||
if not create_missing:
|
||||
domain += [('starred', '=', not starred)]
|
||||
|
@ -330,7 +332,7 @@ class mail_message(osv.Model):
|
|||
"""
|
||||
res_partner_obj = self.pool.get('res.partner')
|
||||
ir_attachment_obj = self.pool.get('ir.attachment')
|
||||
pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
|
||||
pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
|
||||
# 1. Aggregate partners (author_id and partner_ids) and attachments
|
||||
partner_ids = set()
|
||||
|
@ -646,7 +648,7 @@ class mail_message(osv.Model):
|
|||
elif not ids:
|
||||
return ids
|
||||
|
||||
pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
|
||||
pid = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=context).partner_id.id
|
||||
author_ids, partner_ids, allowed_ids = set([]), set([]), set([])
|
||||
model_ids = {}
|
||||
|
||||
|
@ -706,7 +708,7 @@ class mail_message(osv.Model):
|
|||
ids = [ids]
|
||||
not_obj = self.pool.get('mail.notification')
|
||||
fol_obj = self.pool.get('mail.followers')
|
||||
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
|
||||
partner_id = self.pool['res.users'].browse(cr, SUPERUSER_ID, uid, context=None).partner_id.id
|
||||
|
||||
# Read mail_message.ids to have their values
|
||||
message_values = dict.fromkeys(ids)
|
||||
|
@ -775,17 +777,66 @@ class mail_message(osv.Model):
|
|||
_('The requested operation cannot be completed due to security restrictions. Please contact your system administrator.\n\n(Document type: %s, Operation: %s)') % \
|
||||
(self._description, operation))
|
||||
|
||||
def _get_reply_to(self, cr, uid, values, context=None):
|
||||
""" Return a specific reply_to: alias of the document through message_get_reply_to
|
||||
or take the email_from
|
||||
"""
|
||||
email_reply_to = None
|
||||
|
||||
ir_config_parameter = self.pool.get("ir.config_parameter")
|
||||
catchall_domain = ir_config_parameter.get_param(cr, uid, "mail.catchall.domain", context=context)
|
||||
|
||||
# model, res_id, email_from: comes from values OR related message
|
||||
model, res_id, email_from = values.get('model'), values.get('res_id'), values.get('email_from')
|
||||
|
||||
# if model and res_id: try to use ``message_get_reply_to`` that returns the document alias
|
||||
if not email_reply_to and model and res_id and catchall_domain and hasattr(self.pool[model], 'message_get_reply_to'):
|
||||
email_reply_to = self.pool[model].message_get_reply_to(cr, uid, [res_id], context=context)[0]
|
||||
# no alias reply_to -> catchall alias
|
||||
if not email_reply_to and catchall_domain:
|
||||
catchall_alias = ir_config_parameter.get_param(cr, uid, "mail.catchall.alias", context=context)
|
||||
if catchall_alias:
|
||||
email_reply_to = '%s@%s' % (catchall_alias, catchall_domain)
|
||||
# still no reply_to -> reply_to will be the email_from
|
||||
if not email_reply_to and email_from:
|
||||
email_reply_to = email_from
|
||||
|
||||
# format 'Document name <email_address>'
|
||||
if email_reply_to and model and res_id:
|
||||
emails = tools.email_split(email_reply_to)
|
||||
if emails:
|
||||
email_reply_to = emails[0]
|
||||
document_name = self.pool[model].name_get(cr, SUPERUSER_ID, [res_id], context=context)[0]
|
||||
if document_name:
|
||||
# sanitize document name
|
||||
sanitized_doc_name = re.sub(r'[^\w+.]+', '-', document_name[1])
|
||||
# generate reply to
|
||||
email_reply_to = _('"Followers of %s" <%s>') % (sanitized_doc_name, email_reply_to)
|
||||
|
||||
return email_reply_to
|
||||
|
||||
def _get_message_id(self, cr, uid, values, context=None):
|
||||
message_id = None
|
||||
if not values.get('message_id') and values.get('reply_to'):
|
||||
message_id = tools.generate_tracking_message_id('reply_to')
|
||||
elif not values.get('message_id') and values.get('res_id') and values.get('model'):
|
||||
message_id = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
|
||||
elif not values.get('message_id'):
|
||||
message_id = tools.generate_tracking_message_id('private')
|
||||
return message_id
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
if context is None:
|
||||
context = {}
|
||||
default_starred = context.pop('default_starred', False)
|
||||
# generate message_id, to redirect answers to the right discussion thread
|
||||
if not values.get('message_id') and values.get('reply_to'):
|
||||
values['message_id'] = tools.generate_tracking_message_id('reply_to')
|
||||
elif not values.get('message_id') and values.get('res_id') and values.get('model'):
|
||||
values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values)
|
||||
elif not values.get('message_id'):
|
||||
values['message_id'] = tools.generate_tracking_message_id('private')
|
||||
|
||||
if 'email_from' not in values: # needed to compute reply_to
|
||||
values['email_from'] = self._get_default_from(cr, uid, context=context)
|
||||
if not values.get('message_id'):
|
||||
values['message_id'] = self._get_message_id(cr, uid, values, context=context)
|
||||
if 'reply_to' not in values:
|
||||
values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
|
||||
|
||||
newid = super(mail_message, self).create(cr, uid, values, context)
|
||||
self._notify(cr, uid, newid, context=context,
|
||||
force_send=context.get('mail_notify_force_send', True),
|
||||
|
@ -915,26 +966,28 @@ class mail_message(osv.Model):
|
|||
if message.subtype_id and message.model and message.res_id:
|
||||
fol_obj = self.pool.get("mail.followers")
|
||||
# browse as SUPERUSER because rules could restrict the search results
|
||||
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
|
||||
('res_model', '=', message.model),
|
||||
('res_id', '=', message.res_id),
|
||||
('subtype_ids', 'in', message.subtype_id.id)
|
||||
fol_ids = fol_obj.search(
|
||||
cr, SUPERUSER_ID, [
|
||||
('res_model', '=', message.model),
|
||||
('res_id', '=', message.res_id),
|
||||
('subtype_ids', 'in', message.subtype_id.id)
|
||||
], context=context)
|
||||
partners_to_notify |= set(fo.partner_id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context))
|
||||
partners_to_notify |= set(fo.partner_id.id for fo in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context))
|
||||
# remove me from notified partners, unless the message is written on my own wall
|
||||
if message.subtype_id and message.author_id and message.model == "res.partner" and message.res_id == message.author_id.id:
|
||||
partners_to_notify |= set([message.author_id])
|
||||
partners_to_notify |= set([message.author_id.id])
|
||||
elif message.author_id:
|
||||
partners_to_notify -= set([message.author_id])
|
||||
partners_to_notify -= set([message.author_id.id])
|
||||
|
||||
# all partner_ids of the mail.message have to be notified regardless of the above (even the author if explicitly added!)
|
||||
if message.partner_ids:
|
||||
partners_to_notify |= set(message.partner_ids)
|
||||
partners_to_notify |= set([p.id for p in message.partner_ids])
|
||||
|
||||
# notify
|
||||
if partners_to_notify:
|
||||
notification_obj._notify(cr, uid, newid, partners_to_notify=[p.id for p in partners_to_notify], context=context,
|
||||
force_send=force_send, user_signature=user_signature)
|
||||
notification_obj._notify(
|
||||
cr, uid, newid, partners_to_notify=list(partners_to_notify), context=context,
|
||||
force_send=force_send, user_signature=user_signature
|
||||
)
|
||||
message.refresh()
|
||||
|
||||
# An error appear when a user receive a notification without notifying
|
||||
|
|
|
@ -56,7 +56,8 @@
|
|||
<field name="priority">25</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Messages Search">
|
||||
<field name="subject" string="Content" filter_domain="['|', ('subject', 'ilike', self), ('body', 'ilike', self)]" />
|
||||
<field name="body" string="Content" filter_domain="['|', ('subject', 'ilike', self), ('body', 'ilike', self)]" />
|
||||
<field name="subject"/>
|
||||
<field name="type"/>
|
||||
<field name="author_id"/>
|
||||
<field name="partner_ids"/>
|
||||
|
@ -66,23 +67,13 @@
|
|||
<filter string="To Read"
|
||||
name="message_unread" help="Show messages to read"
|
||||
domain="[('to_read', '=', True)]"/>
|
||||
<filter string="Read"
|
||||
name="message_read" help="Show already read messages"
|
||||
domain="[('to_read', '=', False)]"/>
|
||||
<separator/>
|
||||
<filter string="Comments"
|
||||
name="comments" help="Comments"
|
||||
domain="[('type', '=', 'comment')]"/>
|
||||
<filter string="Notifications"
|
||||
name="notifications" help="Notifications"
|
||||
domain="[('type', '=', 'notification')]"/>
|
||||
<filter string="Emails"
|
||||
name="emails" help="Emails"
|
||||
domain="[('type', '=', 'email')]"/>
|
||||
<separator/>
|
||||
<filter string="Has attachments"
|
||||
name="attachments"
|
||||
domain="[('attachment_ids', '!=', False)]"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Type" name="thread" domain="[]" context="{'group_by':'type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
|
|
@ -874,6 +874,40 @@ class mail_thread(osv.AbstractModel):
|
|||
"No possible route found for incoming message from %s to %s (Message-Id %s:)." \
|
||||
"Create an appropriate mail.alias or force the destination model." % (email_from, email_to, message_id)
|
||||
|
||||
def message_route_process(self, cr, uid, message, message_dict, routes, context=None):
|
||||
# postpone setting message_dict.partner_ids after message_post, to avoid double notifications
|
||||
partner_ids = message_dict.pop('partner_ids', [])
|
||||
thread_id = False
|
||||
for model, thread_id, custom_values, user_id, alias in routes:
|
||||
if self._name == 'mail.thread':
|
||||
context.update({'thread_model': model})
|
||||
if model:
|
||||
model_pool = self.pool[model]
|
||||
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
||||
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
|
||||
(message_dict['message_id'], model)
|
||||
|
||||
# disabled subscriptions during message_new/update to avoid having the system user running the
|
||||
# email gateway become a follower of all inbound messages
|
||||
nosub_ctx = dict(context, mail_create_nosubscribe=True, mail_create_nolog=True)
|
||||
if thread_id and hasattr(model_pool, 'message_update'):
|
||||
model_pool.message_update(cr, user_id, [thread_id], message_dict, context=nosub_ctx)
|
||||
else:
|
||||
thread_id = model_pool.message_new(cr, user_id, message_dict, custom_values, context=nosub_ctx)
|
||||
else:
|
||||
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
|
||||
model_pool = self.pool.get('mail.thread')
|
||||
if not hasattr(model_pool, 'message_post'):
|
||||
context['thread_model'] = model
|
||||
model_pool = self.pool['mail.thread']
|
||||
new_msg_id = model_pool.message_post(cr, uid, [thread_id], context=context, subtype='mail.mt_comment', **message_dict)
|
||||
|
||||
if partner_ids:
|
||||
# postponed after message_post, because this is an external message and we don't want to create
|
||||
# duplicate emails due to notifications
|
||||
self.pool.get('mail.message').write(cr, uid, [new_msg_id], {'partner_ids': partner_ids}, context=context)
|
||||
return thread_id
|
||||
|
||||
def message_process(self, cr, uid, model, message, custom_values=None,
|
||||
save_original=False, strip_attachments=False,
|
||||
thread_id=None, context=None):
|
||||
|
@ -926,8 +960,7 @@ class mail_thread(osv.AbstractModel):
|
|||
msg = self.message_parse(cr, uid, msg_txt, save_original=save_original, context=context)
|
||||
if strip_attachments:
|
||||
msg.pop('attachments', None)
|
||||
# postpone setting msg.partner_ids after message_post, to avoid double notifications
|
||||
partner_ids = msg.pop('partner_ids', [])
|
||||
|
||||
if msg.get('message_id'): # should always be True as message_parse generate one if missing
|
||||
existing_msg_ids = self.pool.get('mail.message').search(cr, SUPERUSER_ID, [
|
||||
('message_id', '=', msg.get('message_id')),
|
||||
|
@ -939,36 +972,7 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
# find possible routes for the message
|
||||
routes = self.message_route(cr, uid, msg_txt, msg, model, thread_id, custom_values, context=context)
|
||||
thread_id = False
|
||||
for model, thread_id, custom_values, user_id, alias in routes:
|
||||
if self._name == 'mail.thread':
|
||||
context.update({'thread_model': model})
|
||||
if model:
|
||||
model_pool = self.pool[model]
|
||||
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
||||
"Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \
|
||||
(msg['message_id'], model)
|
||||
|
||||
# disabled subscriptions during message_new/update to avoid having the system user running the
|
||||
# email gateway become a follower of all inbound messages
|
||||
nosub_ctx = dict(context, mail_create_nosubscribe=True, mail_create_nolog=True)
|
||||
if thread_id and hasattr(model_pool, 'message_update'):
|
||||
model_pool.message_update(cr, user_id, [thread_id], msg, context=nosub_ctx)
|
||||
else:
|
||||
thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=nosub_ctx)
|
||||
else:
|
||||
assert thread_id == 0, "Posting a message without model should be with a null res_id, to create a private message."
|
||||
model_pool = self.pool.get('mail.thread')
|
||||
if not hasattr(model_pool, 'message_post'):
|
||||
context['thread_model'] = model
|
||||
model_pool = self.pool['mail.thread']
|
||||
new_msg_id = model_pool.message_post(cr, uid, [thread_id], context=context, subtype='mail.mt_comment', **msg)
|
||||
|
||||
if partner_ids:
|
||||
# postponed after message_post, because this is an external message and we don't want to create
|
||||
# duplicate emails due to notifications
|
||||
self.pool.get('mail.message').write(cr, uid, [new_msg_id], {'partner_ids': partner_ids}, context=context)
|
||||
|
||||
thread_id = self.message_route_process(cr, uid, msg_txt, msg, routes, context=context)
|
||||
return thread_id
|
||||
|
||||
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
|
||||
|
@ -1274,8 +1278,8 @@ class mail_thread(osv.AbstractModel):
|
|||
return result
|
||||
|
||||
def message_post(self, cr, uid, thread_id, body='', subject=None, type='notification',
|
||||
subtype=None, parent_id=False, attachments=None, context=None,
|
||||
content_subtype='html', **kwargs):
|
||||
subtype=None, parent_id=False, attachments=None, context=None,
|
||||
content_subtype='html', **kwargs):
|
||||
""" Post a new message in an existing thread, returning the new
|
||||
mail.message ID.
|
||||
|
||||
|
|
|
@ -1218,7 +1218,7 @@ openerp.mail = function (session) {
|
|||
init: function (parent, datasets, options) {
|
||||
var self = this;
|
||||
this._super(parent, options);
|
||||
this.MailWidget = parent.__proto__ == mail.Widget.prototype ? parent : false;
|
||||
this.MailWidget = parent instanceof mail.Widget ? parent : false;
|
||||
this.domain = options.domain || [];
|
||||
this.context = _.extend(options.context || {});
|
||||
|
||||
|
|
|
@ -48,7 +48,8 @@ openerp.mail.suggestions = function(session, mail) {
|
|||
|
||||
join_group: function (event) {
|
||||
var self = this;
|
||||
return this.mail_group.call('message_subscribe_users', [[$(event.currentTarget).attr('id')],[this.session.uid]]).then(function(res) {
|
||||
var group_id = parseInt($(event.currentTarget).attr('id'), 10);
|
||||
return this.mail_group.call('message_subscribe_users', [[group_id], [this.session.uid]]).then(function(res) {
|
||||
self.fetch_suggested_groups();
|
||||
});
|
||||
},
|
||||
|
|
|
@ -19,9 +19,10 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from . import test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
|
||||
from . import test_mail_group, test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite
|
||||
|
||||
checks = [
|
||||
test_mail_group,
|
||||
test_mail_message,
|
||||
test_mail_features,
|
||||
test_mail_gateway,
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
from openerp.tests import common
|
||||
|
||||
|
||||
class TestMailBase(common.TransactionCase):
|
||||
class TestMail(common.TransactionCase):
|
||||
|
||||
def _mock_smtp_gateway(self, *args, **kwargs):
|
||||
return args[2]['Message-Id']
|
||||
|
@ -39,7 +39,7 @@ class TestMailBase(common.TransactionCase):
|
|||
return self._build_email(*args, **kwargs)
|
||||
|
||||
def setUp(self):
|
||||
super(TestMailBase, self).setUp()
|
||||
super(TestMail, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
# Install mock SMTP gateway
|
||||
|
@ -68,12 +68,46 @@ class TestMailBase(common.TransactionCase):
|
|||
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
|
||||
self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
|
||||
|
||||
# Partner Data
|
||||
|
||||
# User Data: employee, noone
|
||||
self.user_employee_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Ernest Employee',
|
||||
'login': 'ernest',
|
||||
'alias_name': 'ernest',
|
||||
'email': 'e.e@example.com',
|
||||
'signature': '--\nErnest',
|
||||
'notification_email_send': 'comment',
|
||||
'groups_id': [(6, 0, [self.group_employee_id])]
|
||||
}, {'no_reset_password': True})
|
||||
self.user_noone_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Noemie NoOne',
|
||||
'login': 'noemie',
|
||||
'alias_name': 'noemie',
|
||||
'email': 'n.n@example.com',
|
||||
'signature': '--\nNoemie',
|
||||
'notification_email_send': 'comment',
|
||||
'groups_id': [(6, 0, [])]
|
||||
}, {'no_reset_password': True})
|
||||
|
||||
# Test users to use through the various tests
|
||||
self.res_users.write(cr, uid, uid, {'name': 'Administrator'})
|
||||
self.user_raoul_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Raoul Grosbedon', 'signature': 'SignRaoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'alias_name': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
|
||||
self.user_bert_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Bert Tartignole', 'signature': 'SignBert', 'email': 'bert@bert.fr', 'login': 'bert', 'alias_name': 'bert', 'groups_id': [(6, 0, [])]})
|
||||
self.user_raoul_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Raoul Grosbedon',
|
||||
'signature': 'SignRaoul',
|
||||
'email': 'raoul@raoul.fr',
|
||||
'login': 'raoul',
|
||||
'alias_name': 'raoul',
|
||||
'groups_id': [(6, 0, [self.group_employee_id])]
|
||||
})
|
||||
self.user_bert_id = self.res_users.create(cr, uid, {
|
||||
'name': 'Bert Tartignole',
|
||||
'signature': 'SignBert',
|
||||
'email': 'bert@bert.fr',
|
||||
'login': 'bert',
|
||||
'alias_name': 'bert',
|
||||
'groups_id': [(6, 0, [])]
|
||||
})
|
||||
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
|
||||
self.user_bert = self.res_users.browse(cr, uid, self.user_bert_id)
|
||||
self.user_admin = self.res_users.browse(cr, uid, uid)
|
||||
|
@ -82,13 +116,19 @@ class TestMailBase(common.TransactionCase):
|
|||
self.partner_bert_id = self.user_bert.partner_id.id
|
||||
|
||||
# Test 'pigs' group to use through the various tests
|
||||
self.group_pigs_id = self.mail_group.create(cr, uid,
|
||||
self.group_pigs_id = self.mail_group.create(
|
||||
cr, uid,
|
||||
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !', 'alias_name': 'group+pigs'},
|
||||
{'mail_create_nolog': True})
|
||||
{'mail_create_nolog': True}
|
||||
)
|
||||
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
# Test mail.group: public to provide access to everyone
|
||||
self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
|
||||
# Test mail.group: private to restrict access
|
||||
self.group_priv_id = self.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
|
||||
|
||||
def tearDown(self):
|
||||
# Remove mocks
|
||||
self.registry('ir.mail_server').build_email = self._build_email
|
||||
self.registry('ir.mail_server').send_email = self._send_email
|
||||
super(TestMailBase, self).tearDown()
|
||||
super(TestMail, self).tearDown()
|
|
@ -19,10 +19,10 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
|
||||
class test_invite(TestMailBase):
|
||||
class test_invite(TestMail):
|
||||
|
||||
def test_00_basic_invite(self):
|
||||
cr, uid = self.cr, self.uid
|
||||
|
|
|
@ -21,12 +21,12 @@
|
|||
|
||||
from openerp.addons.mail.mail_mail import mail_mail
|
||||
from openerp.addons.mail.mail_thread import mail_thread
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.tools import mute_logger, email_split
|
||||
from openerp.tools.mail import html_sanitize
|
||||
|
||||
|
||||
class test_mail(TestMailBase):
|
||||
class test_mail(TestMail):
|
||||
|
||||
def test_000_alias_setup(self):
|
||||
""" Test basic mail.alias setup works, before trying to use them for routing """
|
||||
|
@ -631,6 +631,7 @@ class test_mail(TestMailBase):
|
|||
{
|
||||
'subject': _subject,
|
||||
'body': '${object.description}',
|
||||
'post': True,
|
||||
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
||||
}, context={
|
||||
'default_composition_mode': 'mass_mail',
|
||||
|
@ -684,6 +685,7 @@ class test_mail(TestMailBase):
|
|||
{
|
||||
'subject': _subject,
|
||||
'body': '${object.description}',
|
||||
'post': True,
|
||||
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
||||
}, context={
|
||||
'default_composition_mode': 'mass_mail',
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
||||
|
@ -143,173 +143,9 @@ dGVzdAo=
|
|||
--089e01536c4ed4d17204e49b8e96--"""
|
||||
|
||||
|
||||
class TestMailgateway(TestMailBase):
|
||||
class TestMailgateway(TestMail):
|
||||
|
||||
def test_00_partner_find_from_email(self):
|
||||
""" Tests designed for partner fetch based on emails. """
|
||||
cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
|
||||
|
||||
# --------------------------------------------------
|
||||
# Data creation
|
||||
# --------------------------------------------------
|
||||
# 1 - Partner ARaoul
|
||||
p_a_id = self.res_partner.create(cr, uid, {'name': 'ARaoul', 'email': 'test@test.fr'})
|
||||
|
||||
# --------------------------------------------------
|
||||
# CASE1: without object
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: find partner with email -> first partner should be found
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['full_name'], 'Maybe Raoul <test@test.fr>',
|
||||
'mail_thread: message_partner_info_from_emails did not handle email')
|
||||
self.assertEqual(partner_info['partner_id'], p_a_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# Data: add some data about partners
|
||||
# 2 - User BRaoul
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'BRaoul', 'email': 'test@test.fr', 'user_ids': [(4, user_raoul.id)]})
|
||||
|
||||
# Do: find partner with email -> first user should be found
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# --------------------------------------------------
|
||||
# CASE1: with object
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: find partner in group where there is a follower with the email -> should be taken
|
||||
self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
|
||||
partner_info = self.mail_group.message_partner_info_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
def test_05_mail_message_mail_mail(self):
|
||||
""" Tests designed for testing email values based on mail.message, aliases, ... """
|
||||
cr, uid, user_raoul_id = self.cr, self.uid, self.user_raoul_id
|
||||
|
||||
# Data: update + generic variables
|
||||
reply_to1 = '_reply_to1@example.com'
|
||||
reply_to2 = '_reply_to2@example.com'
|
||||
email_from1 = 'from@example.com'
|
||||
alias_domain = 'schlouby.fr'
|
||||
raoul_from = 'Raoul Grosbedon <raoul@raoul.fr>'
|
||||
raoul_from_alias = 'Raoul Grosbedon <raoul@schlouby.fr>'
|
||||
raoul_reply = '"Followers of Pigs" <raoul@raoul.fr>'
|
||||
raoul_reply_alias = '"Followers of Pigs" <group+pigs@schlouby.fr>'
|
||||
# Data: remove alias_domain to see emails with alias
|
||||
param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
|
||||
|
||||
# Do: free message; specified values > default values
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'reply_to': reply_to1, 'email_from': email_from1})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('reply_to', msg.message_id,
|
||||
'mail_message: message_id should be specific to a mail_message with a given reply_to')
|
||||
self.assertEqual(msg.reply_to, reply_to1,
|
||||
'mail_message: incorrect reply_to: should come from values')
|
||||
self.assertEqual(msg.email_from, email_from1,
|
||||
'mail_message: incorrect email_from: should come from values')
|
||||
# Do: create a mail_mail with the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, reply_to1,
|
||||
'mail_mail: incorrect reply_to: should come from mail.message')
|
||||
self.assertEqual(mail.email_from, email_from1,
|
||||
'mail_mail: incorrect email_from: should come from mail.message')
|
||||
# Do: create a mail_mail with the previous mail_message + specified reply_to
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel', 'reply_to': reply_to2})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, reply_to2,
|
||||
'mail_mail: incorrect reply_to: should come from values')
|
||||
self.assertEqual(mail.email_from, email_from1,
|
||||
'mail_mail: incorrect email_from: should come from mail.message')
|
||||
|
||||
# Do: mail_message attached to a document
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('mail.group', msg.message_id,
|
||||
'mail_message: message_id should contain model')
|
||||
self.assertIn('%s' % self.group_pigs_id, msg.message_id,
|
||||
'mail_message: message_id should contain res_id')
|
||||
self.assertFalse(msg.reply_to,
|
||||
'mail_message: incorrect reply_to: should not be generated if not specified')
|
||||
self.assertEqual(msg.email_from, raoul_from,
|
||||
'mail_message: incorrect email_from: should be Raoul')
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, raoul_reply,
|
||||
'mail_mail: incorrect reply_to: should be Raoul')
|
||||
|
||||
# Data: set catchall domain
|
||||
self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain)
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, user_raoul_id, [msg_id], {'email_from': False, 'reply_to': False})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, raoul_reply_alias,
|
||||
'mail_mail: incorrect reply_to: should be Pigs alias')
|
||||
|
||||
# Update message: test alias on email_from
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, raoul_from_alias,
|
||||
'mail_mail: incorrect reply_to: should be message email_from using Raoul alias')
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, user_raoul_id, [msg_id], {'res_id': False, 'email_from': 'someone@schlouby.fr', 'reply_to': False})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, msg.email_from,
|
||||
'mail_mail: incorrect reply_to: should be message email_from')
|
||||
|
||||
# Data: set catchall alias
|
||||
self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
|
||||
|
||||
# Update message
|
||||
self.mail_message.write(cr, uid, [msg_id], {'email_from': False, 'reply_to': False})
|
||||
msg.refresh()
|
||||
# Do: create a mail_mail based on the previous mail_message
|
||||
mail_id = self.mail_mail.create(cr, uid, {'mail_message_id': msg_id, 'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail Content-Type
|
||||
self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
|
||||
'mail_mail: reply_to should equal the catchall email alias')
|
||||
|
||||
# Do: create a mail_mail
|
||||
mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
|
||||
'mail_mail: reply_to should equal the catchall email alias')
|
||||
|
||||
# Do: create a mail_mail
|
||||
mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel', 'reply_to': 'someone@example.com'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, 'someone@example.com',
|
||||
'mail_mail: reply_to should equal the rpely_to given to create')
|
||||
|
||||
def test_09_message_parse(self):
|
||||
def test_00_message_parse(self):
|
||||
""" Testing incoming emails parsing """
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
|
@ -738,9 +574,7 @@ class TestMailgateway(TestMailBase):
|
|||
'message_post: private discussion: incorrect notified recipients')
|
||||
self.assertEqual(msg.model, False,
|
||||
'message_post: private discussion: context key "thread_model" not correctly ignored when having no res_id')
|
||||
# Test: message reply_to and message-id
|
||||
self.assertFalse(msg.reply_to,
|
||||
'message_post: private discussion: initial message should not have any reply_to specified')
|
||||
# Test: message-id
|
||||
self.assertIn('openerp-private', msg.message_id,
|
||||
'message_post: private discussion: message-id should contain the private keyword')
|
||||
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (c) 2012-TODAY 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/>.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
||||
class TestMailGroup(TestMail):
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
|
||||
def test_00_mail_group_access_rights(self):
|
||||
""" Testing mail_group access rights and basic mail_thread features """
|
||||
cr, uid, user_noone_id, user_employee_id = self.cr, self.uid, self.user_noone_id, self.user_employee_id
|
||||
|
||||
# Do: Bert reads Jobs -> ok, public
|
||||
self.mail_group.read(cr, user_noone_id, [self.group_jobs_id])
|
||||
# Do: Bert read Pigs -> ko, restricted to employees
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.read(cr, user_noone_id, [self.group_pigs_id])
|
||||
# Do: Raoul read Pigs -> ok, belong to employees
|
||||
self.mail_group.read(cr, user_employee_id, [self.group_pigs_id])
|
||||
|
||||
# Do: Bert creates a group -> ko, no access rights
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.create(cr, user_noone_id, {'name': 'Test'})
|
||||
# Do: Raoul creates a restricted group -> ok
|
||||
new_group_id = self.mail_group.create(cr, user_employee_id, {'name': 'Test'})
|
||||
# Do: Bert added in followers, read -> ok, in followers
|
||||
self.mail_group.message_subscribe_users(cr, uid, [new_group_id], [user_noone_id])
|
||||
self.mail_group.read(cr, user_noone_id, [new_group_id])
|
||||
|
||||
# Do: Raoul reads Priv -> ko, private
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.read(cr, user_employee_id, [self.group_priv_id])
|
||||
# Do: Raoul added in follower, read -> ok, in followers
|
||||
self.mail_group.message_subscribe_users(cr, uid, [self.group_priv_id], [user_employee_id])
|
||||
self.mail_group.read(cr, user_employee_id, [self.group_priv_id])
|
||||
|
||||
# Do: Raoul write on Jobs -> ok
|
||||
self.mail_group.write(cr, user_employee_id, [self.group_priv_id], {'name': 'modified'})
|
||||
# Do: Bert cannot write on Private -> ko (read but no write)
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.write(cr, user_noone_id, [self.group_priv_id], {'name': 're-modified'})
|
||||
# Test: Bert cannot unlink the group
|
||||
with self.assertRaises(except_orm):
|
||||
self.mail_group.unlink(cr, user_noone_id, [self.group_priv_id])
|
||||
# Do: Raoul unlinks the group, there are no followers and messages left
|
||||
self.mail_group.unlink(cr, user_employee_id, [self.group_priv_id])
|
||||
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
|
||||
self.assertFalse(fol_ids, 'unlinked document should not have any followers left')
|
||||
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
|
||||
self.assertFalse(msg_ids, 'unlinked document should not have any followers left')
|
|
@ -19,66 +19,147 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
from openerp.osv.orm import except_orm
|
||||
from openerp.tools import mute_logger
|
||||
|
||||
|
||||
class test_mail_access_rights(TestMailBase):
|
||||
class TestMailMail(TestMail):
|
||||
|
||||
def setUp(self):
|
||||
super(test_mail_access_rights, self).setUp()
|
||||
cr, uid = self.cr, self.uid
|
||||
def test_00_partner_find_from_email(self):
|
||||
""" Tests designed for partner fetch based on emails. """
|
||||
cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
|
||||
|
||||
# Test mail.group: public to provide access to everyone
|
||||
self.group_jobs_id = self.mail_group.create(cr, uid, {'name': 'Jobs', 'public': 'public'})
|
||||
# Test mail.group: private to restrict access
|
||||
self.group_priv_id = self.mail_group.create(cr, uid, {'name': 'Private', 'public': 'private'})
|
||||
# --------------------------------------------------
|
||||
# Data creation
|
||||
# --------------------------------------------------
|
||||
# 1 - Partner ARaoul
|
||||
p_a_id = self.res_partner.create(cr, uid, {'name': 'ARaoul', 'email': 'test@test.fr'})
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
|
||||
def test_00_mail_group_access_rights(self):
|
||||
""" Testing mail_group access rights and basic mail_thread features """
|
||||
cr, uid, user_bert_id, user_raoul_id = self.cr, self.uid, self.user_bert_id, self.user_raoul_id
|
||||
# --------------------------------------------------
|
||||
# CASE1: without object
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: Bert reads Jobs -> ok, public
|
||||
self.mail_group.read(cr, user_bert_id, [self.group_jobs_id])
|
||||
# Do: Bert read Pigs -> ko, restricted to employees
|
||||
self.assertRaises(except_orm, self.mail_group.read,
|
||||
cr, user_bert_id, [self.group_pigs_id])
|
||||
# Do: Raoul read Pigs -> ok, belong to employees
|
||||
self.mail_group.read(cr, user_raoul_id, [self.group_pigs_id])
|
||||
# Do: find partner with email -> first partner should be found
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['full_name'], 'Maybe Raoul <test@test.fr>',
|
||||
'mail_thread: message_partner_info_from_emails did not handle email')
|
||||
self.assertEqual(partner_info['partner_id'], p_a_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# Do: Bert creates a group -> ko, no access rights
|
||||
self.assertRaises(except_orm, self.mail_group.create,
|
||||
cr, user_bert_id, {'name': 'Test'})
|
||||
# Do: Raoul creates a restricted group -> ok
|
||||
new_group_id = self.mail_group.create(cr, user_raoul_id, {'name': 'Test'})
|
||||
# Do: Bert added in followers, read -> ok, in followers
|
||||
self.mail_group.message_subscribe_users(cr, uid, [new_group_id], [user_bert_id])
|
||||
self.mail_group.read(cr, user_bert_id, [new_group_id])
|
||||
# Data: add some data about partners
|
||||
# 2 - User BRaoul
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'BRaoul', 'email': 'test@test.fr', 'user_ids': [(4, user_raoul.id)]})
|
||||
|
||||
# Do: Raoul reads Priv -> ko, private
|
||||
self.assertRaises(except_orm, self.mail_group.read,
|
||||
cr, user_raoul_id, [self.group_priv_id])
|
||||
# Do: Raoul added in follower, read -> ok, in followers
|
||||
self.mail_group.message_subscribe_users(cr, uid, [self.group_priv_id], [user_raoul_id])
|
||||
self.mail_group.read(cr, user_raoul_id, [self.group_priv_id])
|
||||
# Do: find partner with email -> first user should be found
|
||||
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
# Do: Raoul write on Jobs -> ok
|
||||
self.mail_group.write(cr, user_raoul_id, [self.group_priv_id], {'name': 'modified'})
|
||||
# Do: Bert cannot write on Private -> ko (read but no write)
|
||||
self.assertRaises(except_orm, self.mail_group.write,
|
||||
cr, user_bert_id, [self.group_priv_id], {'name': 're-modified'})
|
||||
# Test: Bert cannot unlink the group
|
||||
self.assertRaises(except_orm,
|
||||
self.mail_group.unlink,
|
||||
cr, user_bert_id, [self.group_priv_id])
|
||||
# Do: Raoul unlinks the group, there are no followers and messages left
|
||||
self.mail_group.unlink(cr, user_raoul_id, [self.group_priv_id])
|
||||
fol_ids = self.mail_followers.search(cr, uid, [('res_model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
|
||||
self.assertFalse(fol_ids, 'unlinked document should not have any followers left')
|
||||
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_priv_id)])
|
||||
self.assertFalse(msg_ids, 'unlinked document should not have any followers left')
|
||||
# --------------------------------------------------
|
||||
# CASE1: with object
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: find partner in group where there is a follower with the email -> should be taken
|
||||
self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
|
||||
partner_info = self.mail_group.message_partner_info_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
|
||||
self.assertEqual(partner_info['partner_id'], p_b_id,
|
||||
'mail_thread: message_partner_info_from_emails wrong partner found')
|
||||
|
||||
|
||||
class TestMailMessage(TestMail):
|
||||
|
||||
def test_00_mail_message_values(self):
|
||||
""" Tests designed for testing email values based on mail.message, aliases, ... """
|
||||
cr, uid, user_raoul_id = self.cr, self.uid, self.user_raoul_id
|
||||
|
||||
# Data: update + generic variables
|
||||
reply_to1 = '_reply_to1@example.com'
|
||||
reply_to2 = '_reply_to2@example.com'
|
||||
email_from1 = 'from@example.com'
|
||||
alias_domain = 'schlouby.fr'
|
||||
raoul_from = 'Raoul Grosbedon <raoul@raoul.fr>'
|
||||
raoul_from_alias = 'Raoul Grosbedon <raoul@schlouby.fr>'
|
||||
raoul_reply = '"Followers of Pigs" <raoul@raoul.fr>'
|
||||
raoul_reply_alias = '"Followers of Pigs" <group+pigs@schlouby.fr>'
|
||||
|
||||
# --------------------------------------------------
|
||||
# Case1: without alias_domain
|
||||
# --------------------------------------------------
|
||||
param_ids = self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.domain')])
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, param_ids)
|
||||
|
||||
# Do: free message; specified values > default values
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'reply_to': reply_to1, 'email_from': email_from1})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('reply_to', msg.message_id,
|
||||
'mail_message: message_id should be specific to a mail_message with a given reply_to')
|
||||
self.assertEqual(msg.reply_to, reply_to1,
|
||||
'mail_message: incorrect reply_to: should come from values')
|
||||
self.assertEqual(msg.email_from, email_from1,
|
||||
'mail_message: incorrect email_from: should come from values')
|
||||
|
||||
# Do: create a mail_mail with the previous mail_message + specified reply_to
|
||||
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel', 'reply_to': reply_to2})
|
||||
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, reply_to2,
|
||||
'mail_mail: incorrect reply_to: should come from values')
|
||||
self.assertEqual(mail.email_from, email_from1,
|
||||
'mail_mail: incorrect email_from: should come from mail.message')
|
||||
|
||||
# Do: mail_message attached to a document
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: message content
|
||||
self.assertIn('mail.group', msg.message_id,
|
||||
'mail_message: message_id should contain model')
|
||||
self.assertIn('%s' % self.group_pigs_id, msg.message_id,
|
||||
'mail_message: message_id should contain res_id')
|
||||
self.assertEqual(msg.reply_to, raoul_reply,
|
||||
'mail_message: incorrect reply_to: should be Raoul')
|
||||
self.assertEqual(msg.email_from, raoul_from,
|
||||
'mail_message: incorrect email_from: should be Raoul')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Case2: with alias_domain, without catchall alias
|
||||
# --------------------------------------------------
|
||||
self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain)
|
||||
self.registry('ir.config_parameter').unlink(cr, uid, self.registry('ir.config_parameter').search(cr, uid, [('key', '=', 'mail.catchall.alias')]))
|
||||
|
||||
# Update message
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: generated reply_to
|
||||
self.assertEqual(msg.reply_to, raoul_reply_alias,
|
||||
'mail_mail: incorrect reply_to: should be Pigs alias')
|
||||
|
||||
# Update message: test alias on email_from
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: generated reply_to
|
||||
self.assertEqual(msg.reply_to, raoul_from_alias,
|
||||
'mail_mail: incorrect reply_to: should be message email_from using Raoul alias')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Case2: with alias_domain and catchall alias
|
||||
# --------------------------------------------------
|
||||
self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
|
||||
|
||||
# Update message
|
||||
msg_id = self.mail_message.create(cr, user_raoul_id, {})
|
||||
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
|
||||
# Test: generated reply_to
|
||||
self.assertEqual(msg.reply_to, 'gateway@schlouby.fr',
|
||||
'mail_mail: reply_to should equal the catchall email alias')
|
||||
|
||||
# Do: create a mail_mail
|
||||
mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel', 'reply_to': 'someone@example.com'})
|
||||
mail = self.mail_mail.browse(cr, uid, mail_id)
|
||||
# Test: mail_mail content
|
||||
self.assertEqual(mail.reply_to, 'someone@example.com',
|
||||
'mail_mail: reply_to should equal the rpely_to given to create')
|
||||
|
||||
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
|
||||
def test_10_mail_message_search_access_rights(self):
|
||||
|
|
|
@ -19,10 +19,10 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
|
||||
class test_mail_access_rights(TestMailBase):
|
||||
class test_mail_access_rights(TestMail):
|
||||
|
||||
def test_00_message_read(self):
|
||||
""" Tests for message_read and expandables. """
|
||||
|
|
|
@ -75,7 +75,7 @@ class mail_compose_message(osv.TransientModel):
|
|||
if 'active_domain' in context: # not context.get() because we want to keep global [] domains
|
||||
result['use_active_domain'] = True
|
||||
result['active_domain'] = '%s' % context.get('active_domain')
|
||||
else:
|
||||
elif not result.get('active_domain'):
|
||||
result['active_domain'] = ''
|
||||
# get default values according to the composition mode
|
||||
if composition_mode == 'reply':
|
||||
|
@ -134,8 +134,9 @@ class mail_compose_message(osv.TransientModel):
|
|||
'body': lambda self, cr, uid, ctx={}: '',
|
||||
'subject': lambda self, cr, uid, ctx={}: False,
|
||||
'partner_ids': lambda self, cr, uid, ctx={}: [],
|
||||
'post': lambda self, cr, uid, ctx={}: True,
|
||||
'same_thread': lambda self, cr, uid, ctx={}: True,
|
||||
'post': False,
|
||||
'notify': False,
|
||||
'same_thread': True,
|
||||
}
|
||||
|
||||
def check_access_rule(self, cr, uid, ids, operation, context=None):
|
||||
|
@ -232,7 +233,11 @@ class mail_compose_message(osv.TransientModel):
|
|||
email(s), rendering any template patterns on the fly if needed. """
|
||||
if context is None:
|
||||
context = {}
|
||||
ir_attachment_obj = self.pool.get('ir.attachment')
|
||||
# clean the context (hint: mass mailing sets some default values that
|
||||
# could be wrongly interpreted by mail_mail)
|
||||
context.pop('default_email_to', None)
|
||||
context.pop('default_partner_ids', None)
|
||||
|
||||
active_ids = context.get('active_ids')
|
||||
is_log = context.get('mail_compose_log', False)
|
||||
|
||||
|
@ -251,43 +256,11 @@ class mail_compose_message(osv.TransientModel):
|
|||
else:
|
||||
res_ids = [wizard.res_id]
|
||||
|
||||
for res_id in res_ids:
|
||||
# mail.message values, according to the wizard options
|
||||
post_values = {
|
||||
'subject': wizard.subject,
|
||||
'body': wizard.body,
|
||||
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
||||
'partner_ids': [partner.id for partner in wizard.partner_ids],
|
||||
'attachment_ids': [attach.id for attach in wizard.attachment_ids],
|
||||
}
|
||||
# mass mailing: render and override default values
|
||||
if mass_mail_mode and wizard.model:
|
||||
email_dict = self.render_message(cr, uid, wizard, res_id, context=context)
|
||||
post_values['partner_ids'] += email_dict.pop('partner_ids', [])
|
||||
post_values['attachments'] = email_dict.pop('attachments', [])
|
||||
attachment_ids = []
|
||||
for attach_id in post_values.pop('attachment_ids'):
|
||||
new_attach_id = ir_attachment_obj.copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
|
||||
attachment_ids.append(new_attach_id)
|
||||
post_values['attachment_ids'] = attachment_ids
|
||||
# email_from: mass mailing only can specify another email_from
|
||||
if email_dict.get('email_from'):
|
||||
post_values['email_from'] = email_dict.pop('email_from')
|
||||
# replies redirection: mass mailing only
|
||||
if not wizard.same_thread:
|
||||
post_values['reply_to'] = email_dict.pop('reply_to')
|
||||
else:
|
||||
email_dict.pop('reply_to')
|
||||
post_values.update(email_dict)
|
||||
# clean the context (hint: mass mailing sets some default values that
|
||||
# could be wrongly interpreted by mail_mail)
|
||||
context.pop('default_email_to', None)
|
||||
context.pop('default_partner_ids', None)
|
||||
# post the message
|
||||
all_mail_values = self.get_mail_values(cr, uid, wizard, res_ids, context=context)
|
||||
|
||||
for res_id, mail_values in all_mail_values.iteritems():
|
||||
if mass_mail_mode and not wizard.post:
|
||||
post_values['body_html'] = post_values.get('body', '')
|
||||
post_values['recipient_ids'] = [(4, id) for id in post_values.pop('partner_ids', [])]
|
||||
self.pool.get('mail.mail').create(cr, uid, post_values, context=context)
|
||||
self.pool.get('mail.mail').create(cr, uid, mail_values, context=context)
|
||||
else:
|
||||
subtype = 'mail.mt_comment'
|
||||
if is_log: # log a note: subtype is False
|
||||
|
@ -296,46 +269,122 @@ class mail_compose_message(osv.TransientModel):
|
|||
if not wizard.notify:
|
||||
subtype = False
|
||||
context = dict(context,
|
||||
mail_notify_force_send=False, # do not send emails directly but use the queue instead
|
||||
mail_create_nosubscribe=True) # add context key to avoid subscribing the author
|
||||
active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values)
|
||||
mail_notify_force_send=False, # do not send emails directly but use the queue instead
|
||||
mail_create_nosubscribe=True) # add context key to avoid subscribing the author
|
||||
active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **mail_values)
|
||||
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
def render_message(self, cr, uid, wizard, res_id, context=None):
|
||||
""" Generate an email from the template for given (wizard.model, res_id)
|
||||
pair. This method is meant to be inherited by email_template that
|
||||
will produce a more complete dictionary. """
|
||||
return {
|
||||
'subject': self.render_template(cr, uid, wizard.subject, wizard.model, res_id, context),
|
||||
'body': self.render_template(cr, uid, wizard.body, wizard.model, res_id, context),
|
||||
'email_from': self.render_template(cr, uid, wizard.email_from, wizard.model, res_id, context),
|
||||
'reply_to': self.render_template(cr, uid, wizard.reply_to, wizard.model, res_id, context),
|
||||
}
|
||||
def get_mail_values(self, cr, uid, wizard, res_ids, context=None):
|
||||
"""Generate the values that will be used by send_mail to create mail_messages
|
||||
or mail_mails. """
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
mass_mail_mode = wizard.composition_mode == 'mass_mail'
|
||||
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
# render all template-based value at once
|
||||
if mass_mail_mode and wizard.model:
|
||||
rendered_values = self.render_message_batch(cr, uid, wizard, res_ids, context=context)
|
||||
|
||||
for res_id in res_ids:
|
||||
# static wizard (mail.message) values
|
||||
mail_values = {
|
||||
'subject': wizard.subject,
|
||||
'body': wizard.body,
|
||||
'parent_id': wizard.parent_id and wizard.parent_id.id,
|
||||
'partner_ids': [partner.id for partner in wizard.partner_ids],
|
||||
'attachment_ids': [attach.id for attach in wizard.attachment_ids],
|
||||
}
|
||||
# mass mailing: rendering override wizard static values
|
||||
if mass_mail_mode and wizard.model:
|
||||
email_dict = rendered_values[res_id]
|
||||
mail_values['partner_ids'] += email_dict.pop('partner_ids', [])
|
||||
mail_values['attachments'] = email_dict.pop('attachments', [])
|
||||
attachment_ids = []
|
||||
for attach_id in mail_values.pop('attachment_ids'):
|
||||
new_attach_id = self.pool.get('ir.attachment').copy(cr, uid, attach_id, {'res_model': self._name, 'res_id': wizard.id}, context=context)
|
||||
attachment_ids.append(new_attach_id)
|
||||
mail_values['attachment_ids'] = attachment_ids
|
||||
# email_from: mass mailing only can specify another email_from
|
||||
if email_dict.get('email_from'):
|
||||
mail_values['email_from'] = email_dict.pop('email_from')
|
||||
# replies redirection: mass mailing only
|
||||
if not wizard.same_thread:
|
||||
mail_values['reply_to'] = email_dict.pop('reply_to')
|
||||
else:
|
||||
email_dict.pop('reply_to')
|
||||
mail_values.update(email_dict)
|
||||
# mass mailing without post: mail_mail values
|
||||
if mass_mail_mode and not wizard.post:
|
||||
if 'mail_auto_delete' in context:
|
||||
mail_values['auto_delete'] = context.get('mail_auto_delete')
|
||||
mail_values['body_html'] = mail_values.get('body', '')
|
||||
mail_values['recipient_ids'] = [(4, id) for id in mail_values.pop('partner_ids', [])]
|
||||
results[res_id] = mail_values
|
||||
return results
|
||||
|
||||
def render_message_batch(self, cr, uid, wizard, res_ids, context=None):
|
||||
"""Generate template-based values of wizard, for the document records given
|
||||
by res_ids. This method is meant to be inherited by email_template that
|
||||
will produce a more complete dictionary, using Jinja2 templates.
|
||||
|
||||
Each template is generated for all res_ids, allowing to parse the template
|
||||
once, and render it multiple times. This is useful for mass mailing where
|
||||
template rendering represent a significant part of the process.
|
||||
|
||||
:param browse wizard: current mail.compose.message browse record
|
||||
:param list res_ids: list of record ids
|
||||
|
||||
:return dict results: for each res_id, the generated template values for
|
||||
subject, body, email_from and reply_to
|
||||
"""
|
||||
subjects = self.render_template_batch(cr, uid, wizard.subject, wizard.model, res_ids, context)
|
||||
bodies = self.render_template_batch(cr, uid, wizard.body, wizard.model, res_ids, context)
|
||||
emails_from = self.render_template_batch(cr, uid, wizard.email_from, wizard.model, res_ids, context)
|
||||
replies_to = self.render_template_batch(cr, uid, wizard.reply_to, wizard.model, res_ids, context)
|
||||
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
for res_id in res_ids:
|
||||
results[res_id] = {
|
||||
'subject': subjects[res_id],
|
||||
'body': bodies[res_id],
|
||||
'email_from': emails_from[res_id],
|
||||
'reply_to': replies_to[res_id],
|
||||
}
|
||||
return results
|
||||
|
||||
def render_template_batch(self, cr, uid, template, model, res_ids, context=None):
|
||||
""" Render the given template text, replace mako-like expressions ``${expr}``
|
||||
with the result of evaluating these expressions with an evaluation context
|
||||
containing:
|
||||
with the result of evaluating these expressions with an evaluation context
|
||||
containing:
|
||||
|
||||
* ``user``: browse_record of the current user
|
||||
* ``object``: browse_record of the document record this mail is
|
||||
related to
|
||||
* ``context``: the context passed to the mail composition wizard
|
||||
* ``user``: browse_record of the current user
|
||||
* ``object``: browse_record of the document record this mail is
|
||||
related to
|
||||
* ``context``: the context passed to the mail composition wizard
|
||||
|
||||
:param str template: the template text to render
|
||||
:param str model: model name of the document record this mail is related to.
|
||||
:param int res_id: id of the document record this mail is related to.
|
||||
:param str template: the template text to render
|
||||
:param str model: model name of the document record this mail is related to
|
||||
:param list res_ids: list of record ids
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
results = dict.fromkeys(res_ids, False)
|
||||
|
||||
def merge(match):
|
||||
exp = str(match.group()[2:-1]).strip()
|
||||
result = eval(exp, {
|
||||
'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
|
||||
'object': self.pool[model].browse(cr, uid, res_id, context=context),
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
for res_id in res_ids:
|
||||
def merge(match):
|
||||
exp = str(match.group()[2:-1]).strip()
|
||||
result = eval(exp, {
|
||||
'user': self.pool.get('res.users').browse(cr, uid, uid, context=context),
|
||||
'object': self.pool[model].browse(cr, uid, res_id, context=context),
|
||||
'context': dict(context), # copy context to prevent side-effects of eval
|
||||
})
|
||||
return result and tools.ustr(result) or ''
|
||||
return template and EXPRESSION_PATTERN.sub(merge, template)
|
||||
return result and tools.ustr(result) or ''
|
||||
results[res_id] = template and EXPRESSION_PATTERN.sub(merge, template)
|
||||
return results
|
||||
|
||||
# Compatibility methods
|
||||
def render_template(self, cr, uid, template, model, res_id, context=None):
|
||||
return self.render_template_batch(cr, uid, template, model, [res_id], context)[res_id]
|
||||
|
||||
def render_message(self, cr, uid, wizard, res_id, context=None):
|
||||
return self.render_message_batch(cr, uid, wizard, [res_id], context)[res_id]
|
||||
|
|
|
@ -19,15 +19,7 @@
|
|||
<field name="email_from"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="subject" placeholder="Subject..." required="True"/>
|
||||
<field name="post"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="notify"
|
||||
attrs="{'invisible':['|', ('post', '!=', True), ('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="same_thread"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="reply_to" placeholder="Email address te redirect replies..."
|
||||
attrs="{'invisible':['|', ('same_thread', '=', True), ('composition_mode', '!=', 'mass_mail')],
|
||||
'required':[('same_thread', '!=', True)]}"/>
|
||||
<!-- classic message composer -->
|
||||
<label for="partner_ids" string="Recipients"
|
||||
attrs="{'invisible':[('composition_mode', '=', 'mass_mail')]}"/>
|
||||
<div groups="base.group_user"
|
||||
|
@ -40,6 +32,16 @@
|
|||
<field name="partner_ids" widget="many2many_tags_email" placeholder="Add contacts to notify..."
|
||||
context="{'force_email':True, 'show_email':True}"/>
|
||||
</div>
|
||||
<!-- mass post / mass mailing -->
|
||||
<field name="post"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="notify"
|
||||
attrs="{'invisible':['|', ('post', '!=', True), ('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="same_thread"
|
||||
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
<field name="reply_to" placeholder="Email address te redirect replies..."
|
||||
attrs="{'invisible':['|', ('same_thread', '=', True), ('composition_mode', '!=', 'mass_mail')],
|
||||
'required':[('same_thread', '!=', True)]}"/>
|
||||
</group>
|
||||
<field name="body"/>
|
||||
<field name="attachment_ids" widget="many2many_binary" string="Attach a file"/>
|
||||
|
|
|
@ -26,15 +26,15 @@ class marketing_config_settings(osv.osv_memory):
|
|||
_inherit = 'res.config.settings'
|
||||
_columns = {
|
||||
'module_marketing_campaign': fields.boolean('Marketing campaigns',
|
||||
help="""Provides leads automation through marketing campaigns.
|
||||
Campaigns can in fact be defined on any resource, not just CRM leads.
|
||||
This installs the module marketing_campaign."""),
|
||||
help='Provides leads automation through marketing campaigns. '
|
||||
'Campaigns can in fact be defined on any resource, not just CRM leads.\n'
|
||||
'-This installs the module marketing_campaign.'),
|
||||
'module_marketing_campaign_crm_demo': fields.boolean('Demo data for marketing campaigns',
|
||||
help="""Installs demo data like leads, campaigns and segments for Marketing Campaigns.
|
||||
This installs the module marketing_campaign_crm_demo."""),
|
||||
help='Installs demo data like leads, campaigns and segments for Marketing Campaigns.\n'
|
||||
'-This installs the module marketing_campaign_crm_demo.'),
|
||||
'module_crm_profiling': fields.boolean('Track customer profile to focus your campaigns',
|
||||
help="""Allows users to perform segmentation within partners.
|
||||
This installs the module crm_profiling."""),
|
||||
help='Allows users to perform segmentation within partners.\n'
|
||||
'-This installs the module crm_profiling.'),
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-today 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import mass_mailing
|
||||
import mail_mail
|
||||
import mail_thread
|
||||
import wizard
|
||||
import controllers
|
|
@ -0,0 +1,58 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-Today 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
{
|
||||
'name': 'Mass Mailing Campaigns',
|
||||
'description': """
|
||||
Easily send mass mailing to your leads, opportunities or customers. Track
|
||||
marketing campaigns performance to improve conversion rates. Design
|
||||
professional emails and reuse templates in a few clicks.
|
||||
""",
|
||||
'version': '1.0',
|
||||
'author': 'OpenERP',
|
||||
'website': 'http://www.openerp.com',
|
||||
'category': 'Marketing',
|
||||
'depends': [
|
||||
'mail',
|
||||
'email_template',
|
||||
'web_kanban_gauge',
|
||||
'web_kanban_sparkline',
|
||||
],
|
||||
'data': [
|
||||
'mail_data.xml',
|
||||
'mass_mailing_view.xml',
|
||||
'wizard/mail_compose_message_view.xml',
|
||||
'wizard/mail_mass_mailing_create_segment.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'js': [
|
||||
'static/src/js/mass_mailing.js',
|
||||
],
|
||||
'qweb': [],
|
||||
'css': [
|
||||
'static/src/css/mass_mailing.css'
|
||||
],
|
||||
'demo': [
|
||||
'mass_mailing_demo.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
import main
|
||||
|
||||
# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,12 @@
|
|||
|
||||
import openerp.addons.web.http as http
|
||||
from openerp.addons.web.http import request
|
||||
|
||||
|
||||
class MassMailController(http.Controller):
|
||||
@http.route('/mail/track/<int:mail_id>/blank.gif', type='http', auth='admin')
|
||||
def track_mail_open(self, mail_id):
|
||||
""" Email tracking. """
|
||||
mail_mail_stats = request.registry.get('mail.mail.statistics')
|
||||
mail_mail_stats.set_opened(request.cr, request.uid, mail_mail_ids=[mail_id])
|
||||
return "data:image/gif;base64,R0lGODlhAQABAIAAANvf7wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw=="
|
|
@ -0,0 +1,9 @@
|
|||
.. _changelog:
|
||||
|
||||
Changelog
|
||||
=========
|
||||
|
||||
`trunk (saas-2)`
|
||||
----------------
|
||||
|
||||
- added module
|
|
@ -0,0 +1,13 @@
|
|||
Mass Mailing module documentation
|
||||
=================================
|
||||
|
||||
Mass Mailing documentation topics
|
||||
'''''''''''''''''''''''''''''''''
|
||||
|
||||
Changelog
|
||||
'''''''''
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
changelog.rst
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data noupdate="1">
|
||||
|
||||
<!-- Bounce Email Alias -->
|
||||
<record id="icp_mail_bounce_alias" model="ir.config_parameter">
|
||||
<field name="key">mail.bounce.alias</field>
|
||||
<field name="value">bounce</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,65 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-Today 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from urlparse import urljoin
|
||||
|
||||
from openerp import tools
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
|
||||
class MailMail(osv.Model):
|
||||
"""Add the mass mailing campaign data to mail"""
|
||||
_name = 'mail.mail'
|
||||
_inherit = ['mail.mail']
|
||||
|
||||
_columns = {
|
||||
'statistics_ids': fields.one2many(
|
||||
'mail.mail.statistics', 'mail_mail_id',
|
||||
string='Statistics',
|
||||
),
|
||||
}
|
||||
|
||||
def create(self, cr, uid, values, context=None):
|
||||
""" Override mail_mail creation to create an entry in mail.mail.statistics """
|
||||
# TDE note: should be after 'all values computed', to have values (FIXME after merging other branch holding create refactoring)
|
||||
mail_id = super(MailMail, self).create(cr, uid, values, context=context)
|
||||
if values.get('statistics_ids'):
|
||||
mail = self.browse(cr, SUPERUSER_ID, mail_id)
|
||||
for stat in mail.statistics_ids:
|
||||
self.pool['mail.mail.statistics'].write(cr, uid, [stat.id], {'message_id': mail.message_id}, context=context)
|
||||
return mail_id
|
||||
|
||||
def _get_tracking_url(self, cr, uid, mail, partner=None, context=None):
|
||||
base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
|
||||
track_url = urljoin(base_url, 'mail/track/%d/blank.gif' % mail.id)
|
||||
return '<img src="%s" alt=""/>' % track_url
|
||||
|
||||
def send_get_mail_body(self, cr, uid, mail, partner=None, context=None):
|
||||
""" Override to add the tracking URL to the body. """
|
||||
body = super(MailMail, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||
|
||||
# generate tracking URL
|
||||
if mail.statistics_ids:
|
||||
tracking_url = self._get_tracking_url(cr, uid, mail, partner, context=context)
|
||||
if tracking_url:
|
||||
body = tools.append_content_to_html(body, tracking_url, plaintext=False, container_tag='div')
|
||||
return body
|
|
@ -0,0 +1,86 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-Today 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
import logging
|
||||
|
||||
from openerp import tools
|
||||
from openerp.addons.mail.mail_message import decode
|
||||
from openerp.addons.mail.mail_thread import decode_header
|
||||
from openerp.osv import osv
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class MailThread(osv.Model):
|
||||
""" Update MailThread to add the feature of bounced emails and replied emails
|
||||
in message_process. """
|
||||
_name = 'mail.thread'
|
||||
_inherit = ['mail.thread']
|
||||
|
||||
def message_route_check_bounce(self, cr, uid, message, context=None):
|
||||
""" Override to verify that the email_to is the bounce alias. If it is the
|
||||
case, log the bounce, set the parent and related document as bounced and
|
||||
return False to end the routing process. """
|
||||
bounce_alias = self.pool['ir.config_parameter'].get_param(cr, uid, "mail.bounce.alias", context=context)
|
||||
message_id = message.get('Message-Id')
|
||||
email_from = decode_header(message, 'From')
|
||||
email_to = decode_header(message, 'To')
|
||||
|
||||
# 0. Verify whether this is a bounced email (wrong destination,...) -> use it to collect data, such as dead leads
|
||||
if bounce_alias in email_to:
|
||||
bounce_match = tools.bounce_re.search(email_to)
|
||||
if bounce_match:
|
||||
bounced_mail_id = bounce_match.group(1)
|
||||
stat_ids = self.pool['mail.mail.statistics'].set_bounced(cr, uid, mail_mail_ids=[bounced_mail_id], context=context)
|
||||
for stat in self.pool['mail.mail.statistics'].browse(cr, uid, stat_ids, context=context):
|
||||
bounced_model = stat.model
|
||||
bounced_thread_id = stat.res_id
|
||||
_logger.info('Routing mail from %s to %s with Message-Id %s: bounced mail from mail %s, model: %s, thread_id: %s',
|
||||
email_from, email_to, message_id, bounced_mail_id, bounced_model, bounced_thread_id)
|
||||
if bounced_model and bounced_model in self.pool and hasattr(self.pool[bounced_model], 'message_receive_bounce'):
|
||||
self.pool[bounced_model].message_receive_bounce(cr, uid, [bounced_thread_id], mail_id=bounced_mail_id, context=context)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def message_route(self, cr, uid, message, message_dict, model=None, thread_id=None,
|
||||
custom_values=None, context=None):
|
||||
if not self.message_route_check_bounce(cr, uid, message, context=context):
|
||||
return []
|
||||
return super(MailThread, self).message_route(cr, uid, message, message_dict, model, thread_id, custom_values, context)
|
||||
|
||||
def message_receive_bounce(self, cr, uid, ids, mail_id=None, context=None):
|
||||
"""Called by ``message_process`` when a bounce email (such as Undelivered
|
||||
Mail Returned to Sender) is received for an existing thread. The default
|
||||
behavior is to check is an integer ``message_bounce`` column exists.
|
||||
If it is the case, its content is incremented. """
|
||||
if self._all_columns.get('message_bounce'):
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
self.write(cr, uid, [obj.id], {'message_bounce': obj.message_bounce + 1}, context=context)
|
||||
|
||||
def message_route_process(self, cr, uid, message, message_dict, routes, context=None):
|
||||
""" Override to update the parent mail statistics. The parent is found
|
||||
by using the References header of the incoming message and looking for
|
||||
matching message_id in mail.mail.statistics. """
|
||||
if message.get('References'):
|
||||
message_ids = [x.strip() for x in decode(message['References']).split()]
|
||||
self.pool['mail.mail.statistics'].set_replied(cr, uid, mail_message_ids=message_ids, context=context)
|
||||
return super(MailThread, self).message_route_process(cr, uid, message, message_dict, routes, context=context)
|
|
@ -0,0 +1,379 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2013-today 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from datetime import datetime
|
||||
from dateutil import relativedelta
|
||||
|
||||
from openerp import tools
|
||||
from openerp.tools.translate import _
|
||||
from openerp.osv import osv, fields
|
||||
|
||||
|
||||
class MassMailingCampaign(osv.Model):
|
||||
"""Model of mass mailing campaigns.
|
||||
"""
|
||||
_name = "mail.mass_mailing.campaign"
|
||||
_description = 'Mass Mailing Campaign'
|
||||
# number of embedded mailings in kanban view
|
||||
_kanban_mailing_nbr = 4
|
||||
|
||||
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute statistics of the mass mailing campaign """
|
||||
results = dict.fromkeys(ids, False)
|
||||
for campaign in self.browse(cr, uid, ids, context=context):
|
||||
results[campaign.id] = {
|
||||
'sent': len(campaign.statistics_ids),
|
||||
# delivered: shouldn't be: all mails - (failed + bounced) ?
|
||||
'delivered': len([stat for stat in campaign.statistics_ids if not stat.bounced]), # stat.state == 'sent' and
|
||||
'opened': len([stat for stat in campaign.statistics_ids if stat.opened]),
|
||||
'replied': len([stat for stat in campaign.statistics_ids if stat.replied]),
|
||||
'bounced': len([stat for stat in campaign.statistics_ids if stat.bounced]),
|
||||
}
|
||||
return results
|
||||
|
||||
def _get_mass_mailing_kanban_ids(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Gather data about mass mailings to display them in kanban view as
|
||||
nested kanban views is not possible currently. """
|
||||
results = dict.fromkeys(ids, '')
|
||||
for campaign in self.browse(cr, uid, ids, context=context):
|
||||
mass_mailing_results = []
|
||||
for mass_mailing in campaign.mass_mailing_ids[:self._kanban_mailing_nbr]:
|
||||
mass_mailing_object = {}
|
||||
for attr in ['name', 'sent', 'delivered', 'opened', 'replied', 'bounced']:
|
||||
mass_mailing_object[attr] = getattr(mass_mailing, attr)
|
||||
mass_mailing_results.append(mass_mailing_object)
|
||||
results[campaign.id] = mass_mailing_results
|
||||
return results
|
||||
|
||||
_columns = {
|
||||
'name': fields.char(
|
||||
'Campaign Name', required=True,
|
||||
),
|
||||
'user_id': fields.many2one(
|
||||
'res.users', 'Responsible',
|
||||
required=True,
|
||||
),
|
||||
'mass_mailing_ids': fields.one2many(
|
||||
'mail.mass_mailing', 'mass_mailing_campaign_id',
|
||||
'Mass Mailings',
|
||||
),
|
||||
'mass_mailing_kanban_ids': fields.function(
|
||||
_get_mass_mailing_kanban_ids,
|
||||
type='text', string='Mass Mailings (kanban data)',
|
||||
help='This field has for purpose to gather data about mass mailings '
|
||||
'to display them in kanban view as nested kanban views is not '
|
||||
'possible currently',
|
||||
),
|
||||
'statistics_ids': fields.one2many(
|
||||
'mail.mail.statistics', 'mass_mailing_campaign_id',
|
||||
'Sent Emails',
|
||||
),
|
||||
'color': fields.integer('Color Index'),
|
||||
# stat fields
|
||||
'sent': fields.function(
|
||||
_get_statistics,
|
||||
string='Sent Emails',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
'delivered': fields.function(
|
||||
_get_statistics,
|
||||
string='Delivered',
|
||||
type='integer', multi='_get_statistics',
|
||||
),
|
||||
'opened': fields.function(
|
||||
_get_statistics,
|
||||
string='Opened',
|
||||
type='integer', multi='_get_statistics',
|
||||
),
|
||||
'replied': fields.function(
|
||||
_get_statistics,
|
||||
string='Replied',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
'bounced': fields.function(
|
||||
_get_statistics,
|
||||
string='Bounced',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'user_id': lambda self, cr, uid, ctx=None: uid,
|
||||
}
|
||||
|
||||
def launch_mass_mailing_create_wizard(self, cr, uid, ids, context=None):
|
||||
ctx = dict(context)
|
||||
ctx.update({
|
||||
'default_mass_mailing_campaign_id': ids[0],
|
||||
})
|
||||
return {
|
||||
'name': _('Create a Mass Mailing for the Campaign'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.mass_mailing.create',
|
||||
'views': [(False, 'form')],
|
||||
'view_id': False,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
}
|
||||
|
||||
|
||||
class MassMailing(osv.Model):
|
||||
""" MassMailing models a wave of emails for a mass mailign campaign.
|
||||
A mass mailing is an occurence of sending emails. """
|
||||
|
||||
_name = 'mail.mass_mailing'
|
||||
_description = 'Wave of sending emails'
|
||||
# number of periods for tracking mail_mail statistics
|
||||
_period_number = 6
|
||||
_order = 'date DESC'
|
||||
|
||||
def __get_bar_values(self, cr, uid, id, 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,
|
||||
}
|
||||
]
|
||||
"""
|
||||
date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT).date()
|
||||
section_result = [{'value': 0,
|
||||
'tooltip': (date_begin + relativedelta.relativedelta(days=i)).strftime('%d %B %Y'),
|
||||
} for i in range(0, self._period_number)]
|
||||
group_obj = obj.read_group(cr, uid, domain, read_fields, groupby_field, context=context)
|
||||
for group in group_obj:
|
||||
group_begin_date = datetime.strptime(group['__domain'][0][2], tools.DEFAULT_SERVER_DATE_FORMAT).date()
|
||||
timedelta = relativedelta.relativedelta(group_begin_date, date_begin)
|
||||
section_result[timedelta.days] = {'value': group.get(value_field, 0), 'tooltip': group.get(groupby_field)}
|
||||
return section_result
|
||||
|
||||
def _get_daily_statistics(self, cr, uid, ids, field_name, arg, context=None):
|
||||
""" Get the daily statistics of the mass mailing. This is done by a grouping
|
||||
on opened and replied fields. Using custom format in context, we obtain
|
||||
results for the next 6 days following the mass mailing date. """
|
||||
obj = self.pool['mail.mail.statistics']
|
||||
res = {}
|
||||
context['datetime_format'] = {
|
||||
'opened': {
|
||||
'interval': 'day',
|
||||
'groupby_format': 'yyyy-mm-dd',
|
||||
'display_format': 'dd MMMM YYYY'
|
||||
},
|
||||
'replied': {
|
||||
'interval': 'day',
|
||||
'groupby_format': 'yyyy-mm-dd',
|
||||
'display_format': 'dd MMMM YYYY'
|
||||
},
|
||||
}
|
||||
for id in ids:
|
||||
res[id] = {}
|
||||
date_begin = datetime.strptime(self.browse(cr, uid, id, context=context).date, tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
date_end = date_begin + relativedelta.relativedelta(days=self._period_number - 1)
|
||||
date_begin_str = date_begin.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
date_end_str = date_end.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
domain = [('mass_mailing_id', '=', id), ('opened', '>=', date_begin_str), ('opened', '<=', date_end_str)]
|
||||
res[id]['opened_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['opened'], 'opened_count', 'opened', context=context)
|
||||
domain = [('mass_mailing_id', '=', id), ('replied', '>=', date_begin_str), ('replied', '<=', date_end_str)]
|
||||
res[id]['replied_monthly'] = self.__get_bar_values(cr, uid, id, obj, domain, ['replied'], 'replied_count', 'replied', context=context)
|
||||
return res
|
||||
|
||||
def _get_statistics(self, cr, uid, ids, name, arg, context=None):
|
||||
""" Compute statistics of the mass mailing campaign """
|
||||
results = dict.fromkeys(ids, False)
|
||||
for mass_mailing in self.browse(cr, uid, ids, context=context):
|
||||
results[mass_mailing.id] = {
|
||||
'sent': len(mass_mailing.statistics_ids),
|
||||
'delivered': len([stat for stat in mass_mailing.statistics_ids if not stat.bounced]), # mail.state == 'sent' and
|
||||
'opened': len([stat for stat in mass_mailing.statistics_ids if stat.opened]),
|
||||
'replied': len([stat for stat in mass_mailing.statistics_ids if stat.replied]),
|
||||
'bounced': len([stat for stat in mass_mailing.statistics_ids if stat.bounced]),
|
||||
}
|
||||
return results
|
||||
|
||||
_columns = {
|
||||
'name': fields.char('Name', required=True),
|
||||
'mass_mailing_campaign_id': fields.many2one(
|
||||
'mail.mass_mailing.campaign', 'Mass Mailing Campaign',
|
||||
ondelete='cascade', required=True,
|
||||
),
|
||||
'template_id': fields.many2one(
|
||||
'email.template', 'Email Template',
|
||||
ondelete='set null',
|
||||
),
|
||||
'domain': fields.char('Domain'),
|
||||
'date': fields.datetime('Date'),
|
||||
'color': fields.related(
|
||||
'mass_mailing_campaign_id', 'color',
|
||||
type='integer', string='Color Index',
|
||||
),
|
||||
# statistics data
|
||||
'statistics_ids': fields.one2many(
|
||||
'mail.mail.statistics', 'mass_mailing_id',
|
||||
'Emails Statistics',
|
||||
),
|
||||
'sent': fields.function(
|
||||
_get_statistics,
|
||||
string='Sent Emails',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
'delivered': fields.function(
|
||||
_get_statistics,
|
||||
string='Delivered',
|
||||
type='integer', multi='_get_statistics',
|
||||
),
|
||||
'opened': fields.function(
|
||||
_get_statistics,
|
||||
string='Opened',
|
||||
type='integer', multi='_get_statistics',
|
||||
),
|
||||
'replied': fields.function(
|
||||
_get_statistics,
|
||||
string='Replied',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
'bounced': fields.function(
|
||||
_get_statistics,
|
||||
string='Bounce',
|
||||
type='integer', multi='_get_statistics'
|
||||
),
|
||||
# monthly ratio
|
||||
'opened_monthly': fields.function(
|
||||
_get_daily_statistics,
|
||||
string='Opened',
|
||||
type='char', multi='_get_daily_statistics',
|
||||
),
|
||||
'replied_monthly': fields.function(
|
||||
_get_daily_statistics,
|
||||
string='Replied',
|
||||
type='char', multi='_get_daily_statistics',
|
||||
),
|
||||
}
|
||||
|
||||
_defaults = {
|
||||
'date': fields.datetime.now,
|
||||
}
|
||||
|
||||
|
||||
class MailMailStats(osv.Model):
|
||||
""" MailMailStats models the statistics collected about emails. Those statistics
|
||||
are stored in a separated model and table to avoid bloating the mail_mail table
|
||||
with statistics values. This also allows to delete emails send with mass mailing
|
||||
without loosing the statistics about them. """
|
||||
|
||||
_name = 'mail.mail.statistics'
|
||||
_description = 'Email Statistics'
|
||||
_rec_name = 'message_id'
|
||||
_order = 'message_id'
|
||||
|
||||
_columns = {
|
||||
'mail_mail_id': fields.integer(
|
||||
'Mail ID',
|
||||
help='ID of the related mail_mail. This field is an integer field because'
|
||||
'the related mail_mail can be deleted separately from its statistics.'
|
||||
),
|
||||
'message_id': fields.char(
|
||||
'Message-ID',
|
||||
),
|
||||
'model': fields.char(
|
||||
'Document model',
|
||||
),
|
||||
'res_id': fields.integer(
|
||||
'Document ID',
|
||||
),
|
||||
# campaign / wave data
|
||||
'mass_mailing_id': fields.many2one(
|
||||
'mail.mass_mailing', 'Mass Mailing',
|
||||
ondelete='set null',
|
||||
),
|
||||
'mass_mailing_campaign_id': fields.related(
|
||||
'mass_mailing_id', 'mass_mailing_campaign_id',
|
||||
type='many2one', ondelete='set null',
|
||||
relation='mail.mass_mailing.campaign',
|
||||
string='Mass Mailing Campaign',
|
||||
store=True, readonly=True,
|
||||
),
|
||||
'template_id': fields.related(
|
||||
'mass_mailing_id', 'template_id',
|
||||
type='many2one', ondelete='set null',
|
||||
relation='email.template',
|
||||
string='Email Template',
|
||||
store=True, readonly=True,
|
||||
),
|
||||
# Bounce and tracking
|
||||
'opened': fields.datetime(
|
||||
'Opened',
|
||||
help='Date when this email has been opened for the first time.'),
|
||||
'replied': fields.datetime(
|
||||
'Replied',
|
||||
help='Date when this email has been replied for the first time.'),
|
||||
'bounced': fields.datetime(
|
||||
'Bounced',
|
||||
help='Date when this email has bounced.'
|
||||
),
|
||||
}
|
||||
|
||||
def set_opened(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||
""" Set as opened """
|
||||
if not ids and mail_mail_ids:
|
||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||
elif not ids and mail_message_ids:
|
||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||
else:
|
||||
ids = []
|
||||
for stat in self.browse(cr, uid, ids, context=context):
|
||||
if not stat.opened:
|
||||
self.write(cr, uid, [stat.id], {'opened': fields.datetime.now()}, context=context)
|
||||
return ids
|
||||
|
||||
def set_replied(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||
""" Set as replied """
|
||||
if not ids and mail_mail_ids:
|
||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||
elif not ids and mail_message_ids:
|
||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||
else:
|
||||
ids = []
|
||||
for stat in self.browse(cr, uid, ids, context=context):
|
||||
if not stat.replied:
|
||||
self.write(cr, uid, [stat.id], {'replied': fields.datetime.now()}, context=context)
|
||||
return ids
|
||||
|
||||
def set_bounced(self, cr, uid, ids=None, mail_mail_ids=None, mail_message_ids=None, context=None):
|
||||
""" Set as bounced """
|
||||
if not ids and mail_mail_ids:
|
||||
ids = self.search(cr, uid, [('mail_mail_id', 'in', mail_mail_ids)], context=context)
|
||||
elif not ids and mail_message_ids:
|
||||
ids = self.search(cr, uid, [('message_id', 'in', mail_message_ids)], context=context)
|
||||
else:
|
||||
ids = []
|
||||
for stat in self.browse(cr, uid, ids, context=context):
|
||||
if not stat.bounced:
|
||||
self.write(cr, uid, [stat.id], {'bounced': fields.datetime.now()}, context=context)
|
||||
return ids
|
|
@ -0,0 +1,90 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<!-- <data noupdate="1"> -->
|
||||
<data>
|
||||
|
||||
<record id="mass_mail_template_1" model="email.template">
|
||||
<field name="name">Partner Newsletter 1</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="auto_delete" eval="False"/>
|
||||
<field name="partner_to">${object.id}</field>
|
||||
<field name="body_html"><![CDATA[<p>Hello</p>]]></field>
|
||||
</record>
|
||||
<record id="mass_mail_template_2" model="email.template">
|
||||
<field name="name">Partner Newsletter 2</field>
|
||||
<field name="model_id" ref="base.model_res_partner"/>
|
||||
<field name="auto_delete" eval="False"/>
|
||||
<field name="partner_to">${object.id}</field>
|
||||
<field name="body_html"><![CDATA[<p>Hello</p>]]></field>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_campaign_1" model="mail.mass_mailing.campaign">
|
||||
<field name="name">Partners Newsletter</field>
|
||||
<field name="user_id" eval="ref('base.user_root')"/>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_1" model="mail.mass_mailing">
|
||||
<field name="name">First Newsletter</field>
|
||||
<field name="template_id" eval="ref('mass_mail_template_1')"/>
|
||||
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||
</record>
|
||||
<record id="mass_mail_2" model="mail.mass_mailing">
|
||||
<field name="name">Second Newsletter</field>
|
||||
<field name="template_id" eval="ref('mass_mail_template_2')"/>
|
||||
<field name="date" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="mass_mailing_campaign_id" eval="ref('mass_mail_campaign_1')"/>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_email_1" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111000@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111001@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="replied" eval="(DateTime.today() - relativedelta(days=0)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_3" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111002@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=1)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_4" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111003@OpenERP.com</field>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_5" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_1')"/>
|
||||
<field name="message_id">1111004@OpenERP.com</field>
|
||||
<field name="bounced" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
|
||||
<record id="mass_mail_email_2_1" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||
<field name="message_id">1111005@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=3)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2_2" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||
<field name="message_id">1111006@OpenERP.com</field>
|
||||
<field name="opened" eval="(DateTime.today() - relativedelta(days=2)).strftime('%Y-%m-%d %H:%M:%S')"/>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
<record id="mass_mail_email_2_3" model="mail.mail.statistics">
|
||||
<field name="mass_mailing_id" eval="ref('mass_mail_2')"/>
|
||||
<field name="message_id">1111007@OpenERP.com</field>
|
||||
<field name="state">sent</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,376 @@
|
|||
<?xml version="1.0"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- MASS MAILING !-->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_search">
|
||||
<field name="name">mail.mass_mailing.search</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mass Mailings">
|
||||
<field name="name" string="Mailings"/>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
<field name="template_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Campaign" name="group_mass_mailing_campaign_id"
|
||||
context="{'group_by': 'mass_mailing_campaign_id'}"/>
|
||||
<filter string="Template" name="group_template_id"
|
||||
context="{'group_by': 'template_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_tree">
|
||||
<field name="name">mail.mass_mailing.tree</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mass Mailings">
|
||||
<field name="name"/>
|
||||
<field name="sent"/>
|
||||
<field name="delivered"/>
|
||||
<field name="opened"/>
|
||||
<field name="replied"/>
|
||||
<field name="mass_mailing_campaign_id" invisible="1"/>
|
||||
<field name="template_id" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_form">
|
||||
<field name="name">mail.mass_mailing.form</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mass Mailing" version="7.0">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="mass_mailing_campaign_id" readonly="True"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="template_id"/>
|
||||
<field name="domain"/>
|
||||
<field name="date"/>
|
||||
</group>
|
||||
</group>
|
||||
<group string="Email Statistics">
|
||||
<field name="statistics_ids" nolabel="1" colspan="2"/>
|
||||
<group>
|
||||
<field name="sent"/>
|
||||
<field name="opened"/>
|
||||
<field name="bounced"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="delivered"/>
|
||||
<field name="replied"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_kanban">
|
||||
<field name="name">mail.mass_mailing.kanban</field>
|
||||
<field name="model">mail.mass_mailing</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name='color'/>
|
||||
<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_mass_mailing oe_kanban_mass_mailing_segment">
|
||||
<div class="oe_kanban_content">
|
||||
<div>
|
||||
<h3>
|
||||
<field name="name"/>
|
||||
</h3>
|
||||
<p style="margin-left: 10px; margin-top: 8px;">
|
||||
Sent: <field name="date"/><br />
|
||||
Campaign: <field name="mass_mailing_campaign_id"/>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><field name="sent"/></span><br />
|
||||
Sent
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><field name="delivered"/></span><br />
|
||||
Delivered
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><field name="opened"/></span><br />
|
||||
Opened
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><field name="replied"/></span><br />
|
||||
Replied
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="oe_sparkline_container">
|
||||
<h4 class="oe_sparkline_bar_title">Opened</h4><br />
|
||||
<field name="opened_monthly" widget="sparkline_bar" options="{'height': '50px', 'barWidth': 10, 'barSpacing': 5}"/>
|
||||
</div>
|
||||
<div class="oe_sparkline_container">
|
||||
<h4 class="oe_sparkline_bar_title">Replied</h4><br />
|
||||
<field name="replied_monthly" widget="sparkline_bar" options="{'height': '50px', 'barWidth': 10, 'barSpacing': 5}"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mass_mailings" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mass_mailings_from_campaign" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailings</field>
|
||||
<field name="res_model">mail.mass_mailing</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="context">{
|
||||
'search_default_mass_mailing_campaign_id': [active_id],
|
||||
'default_mass_mailing_campaign_id': active_id,
|
||||
}
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- MASS MAILING CAMPAIGNS !-->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_search">
|
||||
<field name="name">mail.mass_mailing.campaign.search</field>
|
||||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mass Mailing Campaigns">
|
||||
<field name="name" string="Campaigns"/>
|
||||
<field name="user_id"/>
|
||||
<group expand="0" string="Group By...">
|
||||
<filter string="Responsibles" name="group_user_id"
|
||||
context="{'group_by': 'user_id'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_tree">
|
||||
<field name="name">mail.mass_mailing.campaign.tree</field>
|
||||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mass Mailing Campaigns">
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_form">
|
||||
<field name="name">mail.mass_mailing.campaign.form</field>
|
||||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mass Mailing Campaign" version="7.0">
|
||||
<header>
|
||||
<button name="launch_mass_mailing_create_wizard" type="object"
|
||||
class="oe_highlight" string="Create a New Mailing"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="user_id"/>
|
||||
</group>
|
||||
<group>
|
||||
<group>
|
||||
<field name="sent"/>
|
||||
<field name="opened"/>
|
||||
<field name="bounced"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="delivered"/>
|
||||
<field name="replied"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mass_mailing_ids" readonly="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_campaign_kanban">
|
||||
<field name="name">mail.mass_mailing.campaign.kanban</field>
|
||||
<field name="model">mail.mass_mailing.campaign</field>
|
||||
<field name="arch" type="xml">
|
||||
<kanban>
|
||||
<field name="mass_mailing_kanban_ids"/>
|
||||
<field name='sent'/>
|
||||
<field name='color'/>
|
||||
<templates>
|
||||
<t t-name="mass_mailing.mass_mailing">
|
||||
<div class="oe_mass_mailings">
|
||||
<div>
|
||||
<a name="%(action_view_mass_mailings_from_campaign)d" type="action">
|
||||
<h4><t t-raw="mass_mailing.name"/></h4>
|
||||
</a>
|
||||
</div>
|
||||
<div>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><t t-raw="mass_mailing.sent"/></span><br />
|
||||
Sent
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><t t-raw="mass_mailing.delivered"/></span><br />
|
||||
Delivered
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><t t-raw="mass_mailing.opened"/></span><br />
|
||||
Opened
|
||||
</p>
|
||||
<p class="oe_mail_stats">
|
||||
<span class="oe_mail_result"><t t-raw="mass_mailing.replied"/></span><br />
|
||||
Replied
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<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_mass_mailing oe_kanban_mass_mailing_campaign">
|
||||
<div class="oe_dropdown_toggle oe_dropdown_kanban">
|
||||
<span class="oe_e">i</span>
|
||||
<ul class="oe_dropdown_menu">
|
||||
<t t-if="widget.view.is_action_enabled('edit')">
|
||||
<li><a type="edit">Settings</a></li>
|
||||
</t>
|
||||
<t t-if="widget.view.is_action_enabled('delete')">
|
||||
<li><a type="delete">Delete</a></li>
|
||||
</t>
|
||||
<li><ul class="oe_kanban_colorpicker" data-field="color"/></li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="oe_kanban_content">
|
||||
<h3>
|
||||
<field name="name"/>
|
||||
</h3>
|
||||
<div>
|
||||
<field name="delivered" widget="gauge" style="width:160px; height: 120px;"
|
||||
options="{'max_field': 'sent'}"/>
|
||||
<field name="opened" widget="gauge" style="width:160px; height: 120px;"
|
||||
options="{'max_field': 'sent'}"/>
|
||||
<field name="replied" widget="gauge" style="width:160px; height: 120px;"
|
||||
options="{'max_field': 'sent'}"/>
|
||||
</div>
|
||||
<t t-foreach='record.mass_mailing_kanban_ids.value' t-as='mass_mailing'>
|
||||
<t t-call="mass_mailing.mass_mailing"/>
|
||||
</t>
|
||||
</div>
|
||||
<div class="oe_clear"></div>
|
||||
</div>
|
||||
</t>
|
||||
</templates>
|
||||
</kanban>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mass_mailing_campaigns" model="ir.actions.act_window">
|
||||
<field name="name">Mass Mailing Campaigns</field>
|
||||
<field name="res_model">mail.mass_mailing.campaign</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">kanban,tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="oe_view_nocontent_create">
|
||||
Click to define a new mass mailing campaign.
|
||||
</p><p>
|
||||
Create a campaign to structure mass mailing and get analysis from email status.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- MAIL MAIL STATISTICS !-->
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_search">
|
||||
<field name="name">mail.mail.statistics.search</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Mail Statistics">
|
||||
<field name="mail_mail_id"/>
|
||||
<field name="message_id"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_tree">
|
||||
<field name="name">mail.mail.statistics.tree</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Mail Statistics">
|
||||
<field name="mail_mail_id"/>
|
||||
<field name="message_id"/>
|
||||
<field name="opened"/>
|
||||
<field name="replied"/>
|
||||
<field name="bounced"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.ui.view" id="view_mail_mail_statistics_form">
|
||||
<field name="name">mail.mail.statistics.form</field>
|
||||
<field name="model">mail.mail.statistics</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Mail Statistics" version="7.0">
|
||||
<group>
|
||||
<group>
|
||||
<field name="mail_mail_id"/>
|
||||
<field name="message_id"/>
|
||||
<field name="opened"/>
|
||||
<field name="replied"/>
|
||||
<field name="bounced"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="mass_mailing_id"/>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
<field name="template_id"/>
|
||||
<field name="model"/>
|
||||
<field name="res_id"/>
|
||||
</group>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_view_mail_mail_statistics" model="ir.actions.act_window">
|
||||
<field name="name">Mail Statistics</field>
|
||||
<field name="res_model">mail.mail.statistics</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Top menu item -->
|
||||
<menuitem name="Marketing" id="base.marketing_menu" sequence="85"/>
|
||||
|
||||
<!-- Add in marketing -->
|
||||
<menuitem name="Mass Mailing" id="mass_mailing_campaign"
|
||||
parent="base.marketing_menu" sequence="1"/>
|
||||
<menuitem name="Campaigns" id="menu_email_campaigns"
|
||||
parent="mass_mailing_campaign" sequence="1"
|
||||
action="action_view_mass_mailing_campaigns"/>
|
||||
<menuitem name="Mass Mailings" id="menu_email_mass_mailings"
|
||||
parent="mass_mailing_campaign" sequence="2"
|
||||
action="action_view_mass_mailings"/>
|
||||
|
||||
<!-- Add in Technical/Email -->
|
||||
<menuitem name="Mail Statistics" id="menu_email_statistics"
|
||||
parent="base.menu_email" sequence="50"
|
||||
action="action_view_mail_mail_statistics"/>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,6 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_mass_mailing_campaign,mail.mass_mailing.campaign,model_mail_mass_mailing_campaign,,1,1,1,0
|
||||
access_mass_mailing_campaign_system,mail.mass_mailing.campaign.system,model_mail_mass_mailing_campaign,base.group_system,1,1,1,1
|
||||
access_mass_mailing,mail.mass_mailing,model_mail_mass_mailing,,1,1,1,0
|
||||
access_mass_mailing_system,mail.mass_mailing.system,model_mail_mass_mailing,base.group_system,1,1,1,1
|
||||
access_mail_mail_statistics,mail.mail.statistics,model_mail_mail_statistics,,1,1,1,1
|
|
After Width: | Height: | Size: 63 KiB |
After Width: | Height: | Size: 34 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 9.3 KiB |
|
@ -0,0 +1,227 @@
|
|||
<section class="oe_container">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Mass Mailing Made Easy</h2>
|
||||
<h3 class="oe_slogan">Design, Send, Track Emails</h3>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_bg_img">
|
||||
<img src="mass_mailing_campaign.png" class="oe_picture oe_screenshot">
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32'>
|
||||
Easily send mass mailing to your leads, opportunities or customers. Track
|
||||
marketing campaigns performance to improve conversion rates. Design
|
||||
professional emails and reuse templates in a few clicks.
|
||||
</p>
|
||||
<div class="oe_centeralign oe_websiteonly">
|
||||
<a href="http://www.openerp.com/start" class="oe_button oe_big oe_tacky">Start your <span class="oe_emph">free</span> trial</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row">
|
||||
<h2 class="oe_slogan">Send Professional Emails</h2>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32'>
|
||||
Import database of prospects or filter on
|
||||
existing leads, opportunities and customers in just a few clicks.
|
||||
</p><p>
|
||||
Define email templates to reuse content or specific design for your newsletter.
|
||||
Setup several email servers with their own IP/domain to optimise opening rates.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_bg_img">
|
||||
<img class="oe_picture oe_screenshot" src="mass_mailing_composer.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row">
|
||||
<h2 class="oe_slogan">Organize Marketing Campaigns</h2>
|
||||
<h3 class="oe_slogan">Design, Send, Track by Campaigns</h3>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_bg_img">
|
||||
<img class="oe_picture oe_screenshot" src="mass_mailing_campaign.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32'>
|
||||
Get real time statistics on campaigns performance to improve your
|
||||
conversion rate. Track mails sent, received, opened and
|
||||
answered.
|
||||
</p><p>
|
||||
Easily manage your marketing campaigns, discussion groups,
|
||||
leads and opportunities in one simple and powerful platform.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row">
|
||||
<h2 class="oe_slogan">Integrated with OpenERP Apps</h2>
|
||||
<h2 class="oe_slogan"></h2>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32'>
|
||||
Get access to mass mailing features from every OpenERP app to improve
|
||||
the way your users communicate.
|
||||
</p><p>
|
||||
Send template of emails from <a href="/apps/crm">CRM</a> opportunities, select leads based on
|
||||
marketing segments, send <a href="/apps/hr_recruitment">jobs offers</a> and automate answers to applicants, reuse
|
||||
email template in the <a href="/apps/marketing_campaign">lead automation marketing campaigns</a>.
|
||||
</p><p>
|
||||
Answers to your emails appears automatically in the history of every document with the
|
||||
<a href="/apps/mail">social network</a> module.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_bg_img">
|
||||
<img class="oe_picture oe_screenshot" src="mass_mailing_tasks.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row">
|
||||
<h2 class="oe_slogan">Clean Your Lead Database</h2>
|
||||
<h3 class="oe_slogan">Handle bounce and conversion rates</h3>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_bg_img">
|
||||
<img class="oe_picture oe_screenshot" src="mass_mailing_mailing.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32'>
|
||||
Get a clean lead database that improves over the time using the
|
||||
performance of your mails. OpenERP handle bounce mails
|
||||
efficiently, flag erroneous leads accordingly and gives you
|
||||
statistics on the quality of your leads.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row">
|
||||
<h2 class="oe_slogan">One click emails send</h2>
|
||||
<h3 class="oe_slogan">Select any documents, send emails</h3>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32'>
|
||||
The marketing department will love working on campaigns. But you can also
|
||||
give a one click mass mailing facility to all others users on their own
|
||||
prospects or documents.
|
||||
</p><p>
|
||||
Select a few documents (e.g. leads, support tickets, suppliers, applicants,
|
||||
...) and send emails to their contacts in one click, reusing existing emails
|
||||
templates.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_bg_img">
|
||||
<img class="oe_picture oe_screenshot" src="mass_mailing_composer.png">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row">
|
||||
<h2 class="oe_slogan">Follow-up On Answers</h2>
|
||||
<h3 class="oe_slogan">Communicate efficiently with prospects</h3>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_bg_img">
|
||||
<img class="oe_picture oe_screenshot" src="mass_mailing_history.png">
|
||||
</div>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<p class='oe_mt32'>
|
||||
The chatter feature enables you to communicate faster and more efficiently with
|
||||
your customer. Get documents created automatically (leads, opportunities,
|
||||
tasks, ...) based on answers to your mass mailing campaigns Follow the
|
||||
discussion directly on the business documents within OpenERP or via email.
|
||||
</p><p>
|
||||
Get all the negotiations and discussions attached to the right document
|
||||
and relevent managers notified on specific events.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container oe_dark">
|
||||
<div class="oe_row oe_spaced">
|
||||
<h2 class="oe_slogan">Campaigns Dashboard</h2>
|
||||
<h3 class="oe_slogan">Analyse the performance of your campaigns</h3>
|
||||
<div class="oe_span6">
|
||||
<p class="oe_mt32">
|
||||
Get the insights you need to make smarter marketing campaign. Track statistics
|
||||
per campaign: bounce rates, sent mails, best content, etc. The clear dashboards
|
||||
gives you a direct overview of your campaign performance.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span6">
|
||||
<div class="oe_bg_img">
|
||||
<img class="oe_picture oe_screenshot" src="mass_mailing_dashboard.png">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="oe_container">
|
||||
<div class="oe_row">
|
||||
<h2 class="oe_slogan">Fully Integrated With Others Apps</h2>
|
||||
<h3 class="oe_slogan">Efficient mailing for every users</h3>
|
||||
</div>
|
||||
<div class="oe_row">
|
||||
<div class="oe_span4">
|
||||
<a href="/apps/marketing_campaign">
|
||||
<h3>Lead Automation</h3>
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture oe_screenshot" src="app_marketing_campaign.png">
|
||||
</div>
|
||||
</a>
|
||||
<p>
|
||||
Define automated actions (e.g. ask a salesperson to call, send
|
||||
an email, ...) based on triggers (no activity since 20 days,
|
||||
answered a promotional email, etc.)
|
||||
</p><p>
|
||||
Optimize campaigns from lead to close, on every channel. Make
|
||||
smarter decisions about where to invest and show the impact of
|
||||
your marketing activities on your company's bottom line.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span4">
|
||||
<a href="/apps/website_crm">
|
||||
<h3>Website Forms</h3>
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture oe_screenshot" src="app_website_crm.png">
|
||||
</div>
|
||||
</a>
|
||||
<p>
|
||||
Integrate a contact form in your website easily. Forms
|
||||
submissions create leads automatically in OpenERP CRM. Leads
|
||||
can be used in marketing campaigns.
|
||||
</p>
|
||||
</div>
|
||||
<div class="oe_span4">
|
||||
<a href="/apps/crm">
|
||||
<h3>CRM</h3>
|
||||
<div class="oe_row_img oe_centered">
|
||||
<img class="oe_picture oe_screenshot" src="app_crm.png">
|
||||
</div>
|
||||
</a>
|
||||
<p>
|
||||
Manage your sales funnel with no effort. Attract leads, follow-up on phone
|
||||
calls and meetings. Analyse the quality of your leads to make informed
|
||||
decisions and save time by integrating emails directly into the application.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
After Width: | Height: | Size: 65 KiB |
After Width: | Height: | Size: 103 KiB |
After Width: | Height: | Size: 29 KiB |
After Width: | Height: | Size: 66 KiB |
After Width: | Height: | Size: 80 KiB |
After Width: | Height: | Size: 56 KiB |
|
@ -0,0 +1,55 @@
|
|||
.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_campaign {
|
||||
width: 540px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing.oe_kanban_mass_mailing_segment {
|
||||
width: 270px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_stats {
|
||||
width: 120px;
|
||||
display: inline-block;
|
||||
margin: 2px 5px 0px 5px;
|
||||
text-align: center;
|
||||
border: 1px solid rgba(0, 0, 0, 0.16);
|
||||
-webkit-border-radius: 2px;
|
||||
border-radius: 2px;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_mail_result {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_gauge {
|
||||
width: 120px;
|
||||
height: 120px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_kanban_content div.oe_sparkline_container {
|
||||
height: 60px;
|
||||
width: 120px;
|
||||
display: inline-block;
|
||||
margin: 8px 5px 0px 5px;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar_title {
|
||||
text-align: center;
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.openerp .oe_kanban_view .oe_kanban_mass_mailing .oe_sparkline_bar {
|
||||
width: 100px;
|
||||
height: 60px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/*
|
||||
* Campaign related CSS
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Segment related CSS
|
||||
*/
|
|
@ -0,0 +1,13 @@
|
|||
openerp.mass_mailing = function(openerp) {
|
||||
|
||||
openerp.web_kanban.KanbanRecord.include({
|
||||
on_card_clicked: function (event) {
|
||||
if (this.view.dataset.model === 'mail.mass_mailing.campaign') {
|
||||
this.$('.oe_mass_mailings a').first().click();
|
||||
} else {
|
||||
this._super.apply(this, arguments);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2013-Today 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/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
from openerp.addons.mass_mailing.tests import test_mail
|
||||
|
||||
checks = [
|
||||
test_mail,
|
||||
]
|
|
@ -1,8 +1,8 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010-2011 OpenERP s.a. (<http://openerp.com>).
|
||||
# OpenERP, Open Source Business Applications
|
||||
# Copyright (C) 2013-Today 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
|
||||
|
@ -18,27 +18,12 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
##############################################################################
|
||||
{
|
||||
'name': 'Web Shortcuts',
|
||||
'version': '1.0',
|
||||
'category': 'Tools',
|
||||
'description': """
|
||||
Enable shortcuts feature in the web client.
|
||||
===========================================
|
||||
|
||||
Add a Shortcut icon in the systray in order to access the user's shortcuts (if any).
|
||||
from openerp.addons.mail.tests.common import TestMail
|
||||
|
||||
Add a Shortcut icon besides the views title in order to add/remove a shortcut.
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'website': 'http://openerp.com',
|
||||
'depends': ['base'],
|
||||
'data': [],
|
||||
'js' : ['static/src/js/web_shortcuts.js'],
|
||||
'css' : ['static/src/css/web_shortcuts.css'],
|
||||
'qweb' : ['static/src/xml/*.xml'],
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
class test_message_compose(TestMail):
|
||||
|
||||
def test_OO_mail_mail_tracking(self):
|
||||
""" Tests designed for mail_mail tracking (opened, replied, bounced) """
|
||||
pass
|