[MRG] merge with lp:openobject-addons
bzr revid: tpa@tinyerp.com-20131009093540-y9ueph05ah04gixg
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
|
@ -135,12 +136,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'], 'create_date_count', 'create_date', 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'], 'planned_revenue', 'create_date', context=context)
|
||||
return res
|
||||
|
||||
|
@ -255,13 +258,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],
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -356,26 +356,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,14 +27,12 @@ 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."""),
|
||||
'alias_prefix': fields.char('Default Alias Name for Jobs'),
|
||||
'alias_domain' : fields.char('Alias Domain'),
|
||||
help='Allow applicants to send their job application to an email address (jobs@mycompany.com), '
|
||||
'and create automatically application documents in the system.'),
|
||||
}
|
||||
|
||||
def get_default_alias_prefix(self, cr, uid, ids, context=None):
|
||||
|
@ -74,4 +72,4 @@ class hr_applicant_settings(osv.osv_memory):
|
|||
alias_domain = urlparse.urlsplit(domain).netloc.split(':')[0]
|
||||
except Exception:
|
||||
pass
|
||||
return {'alias_domain': alias_domain}
|
||||
return {'alias_domain': alias_domain}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -875,6 +875,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):
|
||||
|
@ -927,8 +961,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')),
|
||||
|
@ -940,36 +973,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):
|
||||
|
@ -1275,8 +1279,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
|
|
@ -1,21 +1,23 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010 OpenERP s.a. (<http://openerp.com>).
|
||||
# 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.
|
||||
# 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.
|
||||
# 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/>.
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
import mail_compose_message
|
||||
import mail_mass_mailing_create_segment
|
|
@ -0,0 +1,61 @@
|
|||
# -*- 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 openerp.osv import osv, fields
|
||||
|
||||
|
||||
class MailComposeMessage(osv.TransientModel):
|
||||
"""Add concept of mass mailing campaign to the mail.compose.message wizard
|
||||
"""
|
||||
_inherit = 'mail.compose.message'
|
||||
|
||||
_columns = {
|
||||
'mass_mailing_campaign_id': fields.many2one(
|
||||
'mail.mass_mailing.campaign', 'Mass mailing campaign',
|
||||
),
|
||||
'mass_mailing_id': fields.many2one(
|
||||
'mail.mass_mailing', 'Mass mailing',
|
||||
domain="[('mass_mailing_campaign_id', '=', mass_mailing_campaign_id)]",
|
||||
),
|
||||
}
|
||||
|
||||
def get_mail_values(self, cr, uid, wizard, res_ids, context=None):
|
||||
""" Override method that generated the mail content by creating the
|
||||
mail.mail.statistics values in the o2m of mail_mail, when doing pure
|
||||
email mass mailing. """
|
||||
res = super(MailComposeMessage, self).get_mail_values(cr, uid, wizard, res_ids, context=context)
|
||||
if wizard.composition_mode == 'mass_mail' and wizard.mass_mailing_campaign_id: # TODO: which kind of mass mailing ?
|
||||
current_date = fields.datetime.now()
|
||||
mass_mailing_id = self.pool['mail.mass_mailing'].create(
|
||||
cr, uid, {
|
||||
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||
'name': '%s-%s' % (wizard.mass_mailing_campaign_id.name, current_date),
|
||||
'date': current_date,
|
||||
'domain': wizard.active_domain,
|
||||
'template_id': wizard.template_id and wizard.template_id.id or False,
|
||||
}, context=context)
|
||||
for res_id in res_ids:
|
||||
res[res_id]['statistics_ids'] = [(0, 0, {
|
||||
'model': wizard.model,
|
||||
'res_id': res_id,
|
||||
'mass_mailing_id': mass_mailing_id,
|
||||
})]
|
||||
return res
|
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Add mass mail campaign to the mail.compose.message form view -->
|
||||
<record model="ir.ui.view" id="email_compose_form_mass_mailing">
|
||||
<field name="name">mail.compose.message.form.mass_mailing</field>
|
||||
<field name="model">mail.compose.message</field>
|
||||
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//field[@name='notify']" position="after">
|
||||
<field name="mass_mailing_campaign_id"
|
||||
attrs="{'invisible': [('composition_mode', '!=', 'mass_mail')]}"/>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,133 @@
|
|||
# -*- 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 openerp.osv import osv, fields
|
||||
|
||||
from openerp.tools.translate import _
|
||||
|
||||
|
||||
class MailMassMailingCreate(osv.TransientModel):
|
||||
"""Wizard to help creating mass mailing waves for a campaign. """
|
||||
|
||||
_name = 'mail.mass_mailing.create'
|
||||
_description = 'Mass mailing creation'
|
||||
|
||||
_columns = {
|
||||
'mass_mailing_campaign_id': fields.many2one(
|
||||
'mail.mass_mailing.campaign', 'Mass mailing campaign',
|
||||
required=True,
|
||||
),
|
||||
'model_id': fields.many2one(
|
||||
'ir.model', 'Document',
|
||||
required=True,
|
||||
help='Document on which the mass mailing will run. This must be a '
|
||||
'valid OpenERP model.',
|
||||
),
|
||||
'model_model': fields.related(
|
||||
'model_id', 'name',
|
||||
type='char', string='Model Name'
|
||||
),
|
||||
'filter_id': fields.many2one(
|
||||
'ir.filters', 'Filter',
|
||||
required=True,
|
||||
domain="[('model_id', '=', model_model)]",
|
||||
help='Filter to be applied on the document to find the records to be '
|
||||
'mailed.',
|
||||
),
|
||||
'domain': fields.related(
|
||||
'filter_id', 'domain',
|
||||
type='char', string='Domain',
|
||||
),
|
||||
'template_id': fields.many2one(
|
||||
'email.template', 'Template', required=True,
|
||||
domain="[('model_id', '=', model_id)]",
|
||||
),
|
||||
'name': fields.char(
|
||||
'Mailing Name', required=True,
|
||||
help='Name of the mass mailing.',
|
||||
),
|
||||
'mass_mailing_id': fields.many2one(
|
||||
'mail.mass_mailing', 'Mass Mailing',
|
||||
),
|
||||
}
|
||||
|
||||
def _get_default_model_id(self, cr, uid, context=None):
|
||||
model_ids = self.pool['ir.model'].search(cr, uid, [('model', '=', 'res.partner')], context=context)
|
||||
return model_ids and model_ids[0] or False
|
||||
|
||||
_defaults = {
|
||||
'model_id': lambda self, cr, uid, ctx=None: self._get_default_model_id(cr, uid, context=ctx),
|
||||
}
|
||||
|
||||
def on_change_model_id(self, cr, uid, ids, model_id, context=None):
|
||||
if model_id:
|
||||
model_model = self.pool['ir.model'].browse(cr, uid, model_id, context=context).model
|
||||
else:
|
||||
model_model = False
|
||||
return {'value': {'model_model': model_model}}
|
||||
|
||||
def on_change_filter_id(self, cr, uid, ids, filter_id, context=None):
|
||||
if filter_id:
|
||||
domain = self.pool['ir.filters'].browse(cr, uid, filter_id, context=context).domain
|
||||
else:
|
||||
domain = False
|
||||
return {'value': {'domain': domain}}
|
||||
|
||||
def create_mass_mailing(self, cr, uid, ids, context=None):
|
||||
""" Create a mass mailing based on wizard data, and update the wizard """
|
||||
for wizard in self.browse(cr, uid, ids, context=context):
|
||||
mass_mailing_values = {
|
||||
'name': wizard.name,
|
||||
'mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||
'domain': wizard.domain,
|
||||
'template_id': wizard.template_id.id,
|
||||
}
|
||||
mass_mailing_id = self.pool['mail.mass_mailing'].create(cr, uid, mass_mailing_values, context=context)
|
||||
self.write(cr, uid, [wizard.id], {'mass_mailing_id': mass_mailing_id}, context=context)
|
||||
return True
|
||||
|
||||
def launch_composer(self, cr, uid, ids, context=None):
|
||||
""" Main wizard action: create a new mailing and launch the mail.compose.message
|
||||
email composer with wizard data. """
|
||||
self.create_mass_mailing(cr, uid, ids, context=context)
|
||||
|
||||
wizard = self.browse(cr, uid, ids[0], context=context)
|
||||
ctx = dict(context)
|
||||
ctx.update({
|
||||
'default_composition_mode': 'mass_mail',
|
||||
'default_template_id': wizard.template_id.id,
|
||||
'default_use_mass_mailing_campaign': True,
|
||||
'default_use_active_domain': True,
|
||||
'default_active_domain': wizard.domain,
|
||||
'default_mass_mailing_campaign_id': wizard.mass_mailing_campaign_id.id,
|
||||
'default_mass_mailing_id': wizard.mass_mailing_id.id,
|
||||
})
|
||||
return {
|
||||
'name': _('Compose Email for Mass Mailing'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'view_type': 'form',
|
||||
'view_mode': 'form',
|
||||
'res_model': 'mail.compose.message',
|
||||
'views': [(False, 'form')],
|
||||
'view_id': False,
|
||||
'target': 'new',
|
||||
'context': ctx,
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<!-- Wizard form view -->
|
||||
<record model="ir.ui.view" id="view_mail_mass_mailing_create_form">
|
||||
<field name="name">mail.mass_mailing.create.form</field>
|
||||
<field name="model">mail.mass_mailing.create</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Create a Mass Mailing" version="7.0">
|
||||
<group>
|
||||
<field name="model_model" invisible="1"/>
|
||||
<field name="domain" invisible="1"/>
|
||||
|
||||
<label for="mass_mailing_campaign_id"/>
|
||||
<div>
|
||||
<field name="mass_mailing_campaign_id"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('mass_mailing_campaign_id', '!=', False)]}">
|
||||
Please choose a mass mailing campaign that will hold the new mailing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="model_id"/>
|
||||
<div>
|
||||
<field name="model_id"
|
||||
on_change="on_change_model_id(model_id, context)"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('model_id', '!=', False)]}">
|
||||
Please choose a model on which you will run the mass mailing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="filter_id"/>
|
||||
<div>
|
||||
<field name="filter_id"
|
||||
on_change="on_change_filter_id(filter_id, context)"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('filter_id', '!=', False)]}">
|
||||
Please choose a filter that will be applied on the model
|
||||
to find the records on which you will run the mass mailing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="model_id"/>
|
||||
<div>
|
||||
<field name="template_id"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('template_id', '!=', False)]}">
|
||||
Please choose the template to use to render the emails
|
||||
to send.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label for="name"/>
|
||||
<div>
|
||||
<field name="name"/>
|
||||
<p class="oe_grey"
|
||||
attrs="{'invisible': [('name', '!=', False)]}">
|
||||
Please choose the name of the mailing.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<button name="launch_composer" type="object"
|
||||
string="Create mailing and launch email composer"/>
|
||||
</group>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record model="ir.actions.act_window" id="action_mail_mass_mailing_create">
|
||||
<field name="name">Create Mass Mailing</field>
|
||||
<field name="res_model">mail.mass_mailing.create</field>
|
||||
<field name="src_model">mail.mass_mailing.campaign</field>
|
||||
<field name="type">ir.actions.act_window</field>
|
||||
<field name="view_type">form</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -77,6 +77,7 @@ Dashboard / Reports for MRP will include:
|
|||
#TODO: This yml tests are needed to be completely reviewed again because the product wood panel is removed in product demo as it does not suit for new demo context of computer and consultant company
|
||||
# so the ymls are too complex to change at this stage
|
||||
'test': [
|
||||
'test/bom_with_service_type_product.yml',
|
||||
'test/mrp_users.yml',
|
||||
'test/order_demo.yml',
|
||||
'test/order_process.yml',
|
||||
|
|