[MERGE] Sync with trunk.
bzr revid: tde@openerp.com-20130322090933-k1jdo40wer42to1e
This commit is contained in:
commit
4e90cbe28e
|
@ -97,6 +97,8 @@ class account_invoice(osv.osv):
|
|||
for m in invoice.move_id.line_id:
|
||||
if m.account_id.type in ('receivable','payable'):
|
||||
result[invoice.id] += m.amount_residual_currency
|
||||
#prevent the residual amount on the invoice to be less than 0
|
||||
result[invoice.id] = max(result[invoice.id], 0.0)
|
||||
return result
|
||||
|
||||
# Give Journal Items related to the payment reconciled to this invoice
|
||||
|
|
|
@ -741,20 +741,17 @@ class account_move_line(osv.osv):
|
|||
|
||||
def list_partners_to_reconcile(self, cr, uid, context=None):
|
||||
cr.execute(
|
||||
"""
|
||||
SELECT partner_id
|
||||
FROM (
|
||||
SELECT l.partner_id, p.last_reconciliation_date, SUM(l.debit) AS debit, SUM(l.credit) AS credit
|
||||
"""SELECT partner_id FROM (
|
||||
SELECT l.partner_id, p.last_reconciliation_date, SUM(l.debit) AS debit, SUM(l.credit) AS credit, MAX(l.date) AS max_date
|
||||
FROM account_move_line l
|
||||
RIGHT JOIN account_account a ON (a.id = l.account_id)
|
||||
RIGHT JOIN res_partner p ON (l.partner_id = p.id)
|
||||
WHERE a.reconcile IS TRUE
|
||||
AND l.reconcile_id IS NULL
|
||||
AND (p.last_reconciliation_date IS NULL OR l.date > p.last_reconciliation_date)
|
||||
AND l.state <> 'draft'
|
||||
GROUP BY l.partner_id, p.last_reconciliation_date
|
||||
) AS s
|
||||
WHERE debit > 0 AND credit > 0
|
||||
WHERE debit > 0 AND credit > 0 AND (last_reconciliation_date IS NULL OR max_date > last_reconciliation_date)
|
||||
ORDER BY last_reconciliation_date""")
|
||||
ids = cr.fetchall()
|
||||
ids = len(ids) and [x[0] for x in ids] or []
|
||||
|
|
|
@ -233,7 +233,7 @@ class res_partner(osv.osv):
|
|||
help="This payment term will be used instead of the default one for purchase orders and supplier invoices"),
|
||||
'ref_companies': fields.one2many('res.company', 'partner_id',
|
||||
'Companies that refers to partner'),
|
||||
'last_reconciliation_date': fields.datetime('Latest Reconciliation Date', help='Date on which the partner accounting entries were fully reconciled last time. It differs from the date of the last reconciliation made for this partner, as here we depict the fact that nothing more was to be reconciled at this date. This can be achieved in 2 ways: either the last debit/credit entry was reconciled, either the user pressed the button "Fully Reconciled" in the manual reconciliation process')
|
||||
'last_reconciliation_date': fields.datetime('Latest Full Reconciliation Date', help='Date on which the partner accounting entries were fully reconciled last time. It differs from the last date where a reconciliation has been made for this partner, as here we depict the fact that nothing more was to be reconciled at this date. This can be achieved in 2 different ways: either the last unreconciled debit/credit entry of this partner was reconciled, either the user pressed the button "Nothing more to reconcile" during the manual reconciliation process.')
|
||||
}
|
||||
|
||||
res_partner()
|
||||
|
|
|
@ -22,13 +22,13 @@
|
|||
<button class="oe_account_recon_next oe_button" href="javascript:void(0)">></button>
|
||||
</div>
|
||||
<div>
|
||||
Last Reconciliation: <t t-esc="widget.last_reconciliation_date" />
|
||||
Latest Manual Reconciliation Processed: <t t-esc="widget.last_reconciliation_date" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<button class="oe_account_recon_reconcile oe_button oe_highlight" href="javascript:void(0)"
|
||||
disabled="">Reconcile</button>
|
||||
<button class="oe_account_recom_mark_as_reconciled oe_button" href="javascript:void(0)">Nothing to reconcile</button>
|
||||
<button class="oe_account_recom_mark_as_reconciled oe_button" href="javascript:void(0)">Nothing more to reconcile</button>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import time
|
||||
journal = self._default_journal_id(cr, uid, {'lang': u'en_US', 'tz': False, 'active_model': 'ir.ui.menu',
|
||||
'journal_type': 'bank', 'period_id': time.strftime('%m'), 'active_ids': [ref('menu_bank_statement_tree')], 'active_id': ref('menu_bank_statement_tree')})
|
||||
assert journal, _('Journal has not been selected')
|
||||
assert journal, 'Journal has not been selected'
|
||||
-
|
||||
I create a bank statement with Opening and Closing balance 0.
|
||||
-
|
||||
|
|
|
@ -41,7 +41,7 @@
|
|||
ids = self.search(cr, uid, [('ref', '=', 'My Test Model')])
|
||||
self.button_validate(cr, uid, ids, {})
|
||||
moves = self.browse(cr, uid, ids)[0]
|
||||
assert(moves.state == 'posted'), _('Journal Entries are not in posted state')
|
||||
assert(moves.state == 'posted'), 'Journal Entries are not in posted state'
|
||||
-
|
||||
Then I create Recurring Lines
|
||||
-
|
||||
|
@ -57,7 +57,7 @@
|
|||
self.compute(cr, uid, [ref('test_recurring_lines')], {'lang': u'en_US', 'active_model': 'ir.ui.menu',
|
||||
'active_ids': [ref('menu_action_subscription_form')], 'tz': False, 'active_id': ref('menu_action_subscription_form')})
|
||||
subscription_lines = subscription_line_obj.search(cr, uid, [('subscription_id', '=', ref('test_recurring_lines'))])
|
||||
assert subscription_lines, _('Subscription lines has not been created')
|
||||
assert subscription_lines, 'Subscription lines has not been created'
|
||||
-
|
||||
I provide date in 'Generate Entries' wizard
|
||||
-
|
||||
|
@ -69,5 +69,5 @@
|
|||
!python {model: account.subscription.generate}: |
|
||||
res = self.action_generate(cr, uid, [ref('account_subscription_generate')], {'lang': u'en_US', 'active_model': 'ir.ui.menu',
|
||||
'active_ids': [ref('menu_generate_subscription')], 'tz': False, 'active_id': ref('menu_generate_subscription')})
|
||||
assert res, _('Move for subscription lines has not been created')
|
||||
assert res, 'Move for subscription lines has not been created'
|
||||
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
# Chinese (Simplified) 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-03-21 19:22+0000\n"
|
||||
"Last-Translator: Liuming <gumdam20@me.com>\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-03-22 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
msgid ""
|
||||
"Code should always set a variable named `result` with the result of your "
|
||||
"test, that can be a list or\n"
|
||||
"a dictionary. If `result` is an empty list, it means that the test was "
|
||||
"succesful. Otherwise it will\n"
|
||||
"try to translate and print what is inside `result`.\n"
|
||||
"\n"
|
||||
"If the result of your test is a dictionary, you can set a variable named "
|
||||
"`column_order` to choose in\n"
|
||||
"what order you want to print `result`'s content.\n"
|
||||
"\n"
|
||||
"Should you need them, you can also use the following variables into your "
|
||||
"code:\n"
|
||||
" * cr: cursor to the database\n"
|
||||
" * uid: ID of the current user\n"
|
||||
"\n"
|
||||
"In any ways, the code must be legal python statements with correct "
|
||||
"indentation (if needed).\n"
|
||||
"\n"
|
||||
"Example: \n"
|
||||
" sql = '''SELECT id, name, ref, date\n"
|
||||
" FROM account_move_line \n"
|
||||
" WHERE account_id IN (SELECT id FROM account_account WHERE type "
|
||||
"= 'view')\n"
|
||||
" '''\n"
|
||||
" cr.execute(sql)\n"
|
||||
" result = cr.dictfetchall()"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_02
|
||||
msgid "Test 2: Opening a fiscal year"
|
||||
msgstr "测试2: 打开一个会计年度"
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_05
|
||||
msgid ""
|
||||
"Check that reconciled invoice for Sales/Purchases has reconciled entries for "
|
||||
"Payable and Receivable Accounts"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_03
|
||||
msgid ""
|
||||
"Check if movement lines are balanced and have the same date and period"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: field:accounting.assert.test,name:0
|
||||
msgid "Test Name"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: report:account.test.assert.print:0
|
||||
msgid "Accouting tests on"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_01
|
||||
msgid "Test 1: General balance"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_06
|
||||
msgid "Check that paid/reconciled invoices are not in 'Open' state"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_05_2
|
||||
msgid ""
|
||||
"Check that reconciled account moves, that define Payable and Receivable "
|
||||
"accounts, are belonging to reconciled invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
msgid "Tests"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: field:accounting.assert.test,desc:0
|
||||
msgid "Test Description"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_06_1
|
||||
msgid "Check that there's no move for any account with « View » account type"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_08
|
||||
msgid "Test 9 : Accounts and partners on account moves"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:ir.actions.act_window,name:account_test.action_accounting_assert
|
||||
#: model:ir.actions.report.xml,name:account_test.account_assert_test_report
|
||||
#: model:ir.ui.menu,name:account_test.menu_action_license
|
||||
msgid "Accounting Tests"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: code:addons/account_test/report/account_test_report.py:74
|
||||
#, python-format
|
||||
msgid "The test was passed successfully"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: field:accounting.assert.test,active:0
|
||||
msgid "Active"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_06
|
||||
msgid "Test 6 : Invoices status"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:ir.model,name:account_test.model_accounting_assert_test
|
||||
msgid "accounting.assert.test"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_05
|
||||
msgid ""
|
||||
"Test 5.1 : Payable and Receivable accountant lines of reconciled invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: field:accounting.assert.test,code_exec:0
|
||||
msgid "Python code"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_07
|
||||
msgid ""
|
||||
"Check on bank statement that the Closing Balance = Starting Balance + sum of "
|
||||
"statement lines"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_07
|
||||
msgid "Test 8 : Closing balance on bank statements"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_03
|
||||
msgid "Test 3: Movement lines"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_05_2
|
||||
msgid "Test 5.2 : Reconcilied invoices and Payable/Receivable accounts"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
msgid "Expression"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_04
|
||||
msgid "Test 4: Totally reconciled mouvements"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_04
|
||||
msgid "Check if the totally reconciled movements are balanced"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: field:accounting.assert.test,sequence:0
|
||||
msgid "Sequence"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_02
|
||||
msgid ""
|
||||
"Check if the balance of the new opened fiscal year matches with last year's "
|
||||
"balance"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
msgid "Python Code"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:ir.actions.act_window,help:account_test.action_accounting_assert
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to create Accounting Test.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_01
|
||||
msgid "Check the balance: Debit sum = Credit sum"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,desc:account_test.account_test_08
|
||||
msgid "Check that general accounts and partners on account moves are active"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: model:accounting.assert.test,name:account_test.account_test_06_1
|
||||
msgid "Test 7: « View » account type"
|
||||
msgstr ""
|
||||
|
||||
#. module: account_test
|
||||
#: view:accounting.assert.test:0
|
||||
msgid "Code Help"
|
||||
msgstr ""
|
|
@ -76,11 +76,11 @@ class account_analytic_account(osv.osv):
|
|||
GROUP BY product_id, user_id, to_invoice, product_uom_id, line.name""", (account.id,))
|
||||
|
||||
res[account.id] = 0.0
|
||||
for product_id, price, user_id, factor_id, qty, uom, line_name in cr.fetchall():
|
||||
for product_id, total_amount, user_id, factor_id, qty, uom, line_name in cr.fetchall():
|
||||
#the amount to reinvoice is the real cost. We don't use the pricelist
|
||||
price = -price
|
||||
total_amount = -total_amount
|
||||
factor = self.pool.get('hr_timesheet_invoice.factor').browse(cr, uid, factor_id, context=context)
|
||||
res[account.id] += price * qty * (100 - factor.factor or 0.0) / 100.0
|
||||
res[account.id] += total_amount * (100 - factor.factor or 0.0) / 100.0
|
||||
return res
|
||||
|
||||
def _expense_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
|
||||
|
@ -89,8 +89,13 @@ class account_analytic_account(osv.osv):
|
|||
for account in self.browse(cr, uid, ids, context=context):
|
||||
res[account.id] = 0.0
|
||||
line_ids = lines_obj.search(cr, uid, [('account_id','=', account.id), ('invoice_id','!=',False), ('to_invoice','!=', False), ('journal_id.type', '=', 'purchase')], context=context)
|
||||
#Put invoices in separate array in order not to calculate them double
|
||||
invoices = []
|
||||
for line in lines_obj.browse(cr, uid, line_ids, context=context):
|
||||
res[account.id] += line.invoice_id.amount_untaxed
|
||||
if line.invoice_id not in invoices:
|
||||
invoices.append(line.invoice_id)
|
||||
for invoice in invoices:
|
||||
res[account.id] += invoice.amount_untaxed
|
||||
return res
|
||||
|
||||
def _ca_invoiced_calc(self, cr, uid, ids, name, arg, context=None):
|
||||
|
|
|
@ -64,8 +64,13 @@ class OAuthController(oeweb.Controller):
|
|||
u = registry.get('res.users')
|
||||
credentials = u.auth_oauth(cr, SUPERUSER_ID, provider, kw, context=context)
|
||||
cr.commit()
|
||||
action = state.get('a', None)
|
||||
url = '/#action=' + action if action else '/'
|
||||
action = state.get('a')
|
||||
menu = state.get('m')
|
||||
url = '/'
|
||||
if action:
|
||||
url = '/#action=%s' % action
|
||||
elif menu:
|
||||
url = '/#menu_id=%s' % menu
|
||||
return login_and_redirect(req, *credentials, redirect_url=url)
|
||||
except AttributeError:
|
||||
# auth_signup is not installed
|
||||
|
|
|
@ -0,0 +1,135 @@
|
|||
# Turkish 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-03-21 19:39+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Turkish <tr@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-03-22 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,validation_endpoint:0
|
||||
msgid "Validation URL"
|
||||
msgstr "Onaylama URL"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,auth_endpoint:0
|
||||
msgid "Authentication URL"
|
||||
msgstr "Kimlik Doğrulama URL"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: model:ir.model,name:auth_oauth.model_base_config_settings
|
||||
msgid "base.config.settings"
|
||||
msgstr "base.config.settings"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,name:0
|
||||
msgid "Provider name"
|
||||
msgstr "Sağlayıcı adı"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,scope:0
|
||||
msgid "Scope"
|
||||
msgstr "Kapsam"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:res.users,oauth_provider_id:0
|
||||
msgid "OAuth Provider"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,css_class:0
|
||||
msgid "CSS class"
|
||||
msgstr "CSS class"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,body:0
|
||||
msgid "Body"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: model:ir.model,name:auth_oauth.model_res_users
|
||||
msgid "Users"
|
||||
msgstr "Kullanıcılar"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,sequence:0
|
||||
msgid "unknown"
|
||||
msgstr "bilinmeyen"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:res.users,oauth_access_token:0
|
||||
msgid "OAuth Access Token"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,client_id:0
|
||||
#: field:base.config.settings,auth_oauth_facebook_client_id:0
|
||||
#: field:base.config.settings,auth_oauth_google_client_id:0
|
||||
msgid "Client ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: model:ir.ui.menu,name:auth_oauth.menu_oauth_providers
|
||||
msgid "OAuth Providers"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: model:ir.model,name:auth_oauth.model_auth_oauth_provider
|
||||
msgid "OAuth2 provider"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:res.users,oauth_uid:0
|
||||
msgid "OAuth User ID"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:base.config.settings,auth_oauth_facebook_enabled:0
|
||||
msgid "Allow users to sign in with Facebook"
|
||||
msgstr "Kullanıcılara Facebook ile oturum için izin ver"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: sql_constraint:res.users:0
|
||||
msgid "OAuth UID must be unique per provider"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: help:res.users,oauth_uid:0
|
||||
msgid "Oauth Provider user_id"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,data_endpoint:0
|
||||
msgid "Data URL"
|
||||
msgstr "Data URL"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: view:auth.oauth.provider:0
|
||||
msgid "arch"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: model:ir.actions.act_window,name:auth_oauth.action_oauth_provider
|
||||
msgid "Providers"
|
||||
msgstr "Sağlayıcılar"
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:base.config.settings,auth_oauth_google_enabled:0
|
||||
msgid "Allow users to sign in with Google"
|
||||
msgstr ""
|
||||
|
||||
#. module: auth_oauth
|
||||
#: field:auth.oauth.provider,enabled:0
|
||||
msgid "Allowed"
|
||||
msgstr "İzinVerildi"
|
|
@ -0,0 +1,298 @@
|
|||
# Spanish (Colombia) 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-03-21 21:49+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Spanish (Colombia) <es_CO@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-03-22 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_type:0
|
||||
msgid "Signup Token Type"
|
||||
msgstr "Tipo de palabra de ingreso"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:base.config.settings,auth_signup_uninvited:0
|
||||
msgid "Allow external users to sign up"
|
||||
msgstr "Permitir ingreso a usuarios externos"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:16
|
||||
#, python-format
|
||||
msgid "Confirm Password"
|
||||
msgstr "Confirmar Contraseña"
|
||||
|
||||
#. module: auth_signup
|
||||
#: help:base.config.settings,auth_signup_uninvited:0
|
||||
msgid "If unchecked, only invited users may sign up."
|
||||
msgstr "Si no está marcado, sólo los usuarios invitados pueden ingresar."
|
||||
|
||||
#. module: auth_signup
|
||||
#: model:ir.model,name:auth_signup.model_base_config_settings
|
||||
msgid "base.config.settings"
|
||||
msgstr "base.config.settings"
|
||||
|
||||
#. module: auth_signup
|
||||
#: code:addons/auth_signup/res_users.py:265
|
||||
#, python-format
|
||||
msgid "Cannot send email: user has no email address."
|
||||
msgstr ""
|
||||
"No se pudo enviar el correo eléctronico: el usuario no tiene dirección de "
|
||||
"correo."
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:24
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:28
|
||||
#, python-format
|
||||
msgid "Reset password"
|
||||
msgstr "Restablecer contraseña"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:base.config.settings,auth_signup_template_user_id:0
|
||||
msgid "Template user for new users created through signup"
|
||||
msgstr ""
|
||||
"Plantilla de usuario para los nuevos usuarios creados a través del ingreso"
|
||||
|
||||
#. module: auth_signup
|
||||
#: model:email.template,subject:auth_signup.reset_password_email
|
||||
msgid "Password reset"
|
||||
msgstr "Restablecer contraseña"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:117
|
||||
#, python-format
|
||||
msgid "Please enter a password and confirm it."
|
||||
msgstr "Por favor introduzca una contraseña y confírmela."
|
||||
|
||||
#. 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:23
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:26
|
||||
#, python-format
|
||||
msgid "Sign Up"
|
||||
msgstr "Registro"
|
||||
|
||||
#. module: auth_signup
|
||||
#: selection:res.users,state:0
|
||||
msgid "New"
|
||||
msgstr "Nuevo(a)"
|
||||
|
||||
#. 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 "Estado"
|
||||
|
||||
#. 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 ""
|
||||
"\n"
|
||||
"<p>Se ha solicitado un cambio de contraseña dedse la cuenta de OpenERP "
|
||||
"asociada con este correo eléctronico.</p>\n"
|
||||
"\n"
|
||||
"<p>Debería cambiar su contraseña siguiendo <a "
|
||||
"href=\"${object.signup_url}\">este enlace</a>.</p>\n"
|
||||
"\n"
|
||||
"<p>Nota: Si no espera estanotificación, puede ignorarla de forma segura.</p>"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:111
|
||||
#, python-format
|
||||
msgid "Please enter a name."
|
||||
msgstr "Por favor, introduzca un nombre."
|
||||
|
||||
#. module: auth_signup
|
||||
#: model:ir.model,name:auth_signup.model_res_users
|
||||
msgid "Users"
|
||||
msgstr "Usuarios"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_url:0
|
||||
msgid "Signup URL"
|
||||
msgstr "URL de Ingreso"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:114
|
||||
#, python-format
|
||||
msgid "Please enter a username."
|
||||
msgstr "Por favor, introduzca un nombre de usuario."
|
||||
|
||||
#. module: auth_signup
|
||||
#: selection:res.users,state:0
|
||||
msgid "Active"
|
||||
msgstr "Activo(a)"
|
||||
|
||||
#. module: auth_signup
|
||||
#: code:addons/auth_signup/res_users.py:269
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Cannot send email: no outgoing email server configured.\n"
|
||||
"You can configure it under Settings/General Settings."
|
||||
msgstr ""
|
||||
"No se puede enviar el correo electrónico: no se ha configurado un servidor "
|
||||
"de correo saliente.\n"
|
||||
"Puede configurarlo en Configuración / Configuraciones Generales."
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:12
|
||||
#, python-format
|
||||
msgid "Username"
|
||||
msgstr "Nombre de Usuario"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:8
|
||||
#, python-format
|
||||
msgid "Name"
|
||||
msgstr "Nombre"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:170
|
||||
#, python-format
|
||||
msgid "Please enter a username or email address."
|
||||
msgstr ""
|
||||
"Por favor ingresea un Nombre de Usuario o dirección de correo electrónico."
|
||||
|
||||
#. module: auth_signup
|
||||
#: selection:res.users,state:0
|
||||
msgid "Resetting Password"
|
||||
msgstr "Restableciendo la Contraseña"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:13
|
||||
#, python-format
|
||||
msgid "Username (Email)"
|
||||
msgstr "Nombre de Usuario (Dirección de correo electrónico)"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_expiration:0
|
||||
msgid "Signup Expiration"
|
||||
msgstr "Vencimiento del Ingreso"
|
||||
|
||||
#. 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 ""
|
||||
"Esto permite a los usuarios lanzar un restablecimiento de la contraseña "
|
||||
"desde la página de Inicio de Sesión."
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:22
|
||||
#, python-format
|
||||
msgid "Log in"
|
||||
msgstr "Iniciar Sesión"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_valid:0
|
||||
msgid "Signup Token is Valid"
|
||||
msgstr "La Palabra de Ingreso es Válida"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:108
|
||||
#: 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:167
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:170
|
||||
#, python-format
|
||||
msgid "Login"
|
||||
msgstr "Inicio de Sesión"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:94
|
||||
#, python-format
|
||||
msgid "Invalid signup token"
|
||||
msgstr "Palabra de ingreso no válida"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:120
|
||||
#, python-format
|
||||
msgid "Passwords do not match; please retype them."
|
||||
msgstr "Las contraseñas no coinciden. Por favor vuelva a escribirlas."
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:108
|
||||
#: code:addons/auth_signup/static/src/js/auth_signup.js:167
|
||||
#, python-format
|
||||
msgid "No database selected !"
|
||||
msgstr "No se ha seleccionado ninguna base de datos!"
|
||||
|
||||
#. 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 ""
|
||||
"Habilitar restablecimiento de la contraseña desde la página de Inicio de "
|
||||
"Sesión"
|
||||
|
||||
#. module: auth_signup
|
||||
#. openerp-web
|
||||
#: code:addons/auth_signup/static/src/xml/auth_signup.xml:27
|
||||
#, python-format
|
||||
msgid "Back to Login"
|
||||
msgstr "Volver al Inicio de Sesión"
|
||||
|
||||
#. 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 "Empresa/Cliente"
|
||||
|
||||
#. module: auth_signup
|
||||
#: field:res.partner,signup_token:0
|
||||
msgid "Signup Token"
|
||||
msgstr "Palabra de Ingreso"
|
|
@ -27,8 +27,8 @@
|
|||
<page string="Conditions">
|
||||
<group>
|
||||
<group name="filter" string="Filter Condition">
|
||||
<field name="filter_pre_id" domain="[('model_id','=',model), ('user_id', '=', False)]" context="{'default_model_id': model}"/>
|
||||
<field name="filter_id" domain="[('model_id','=',model), ('user_id', '=', False)]" context="{'default_model_id': model}"/>
|
||||
<field name="filter_pre_id" domain="[('model_id','=',model)]" context="{'default_model_id': model}"/>
|
||||
<field name="filter_id" domain="[('model_id','=',model)]" context="{'default_model_id': model}"/>
|
||||
</group>
|
||||
<group name="timing" string="Timer">
|
||||
<field name="trg_date_id"/>
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
# Spanish (Colombia) 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-03-21 21:43+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Spanish (Colombia) <es_CO@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-03-22 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: contacts
|
||||
#: model:ir.actions.act_window,help:contacts.action_contacts
|
||||
msgid ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Click to add a contact in your address book.\n"
|
||||
" </p><p>\n"
|
||||
" OpenERP helps you easily track all activities related to\n"
|
||||
" a customer; discussions, history of business opportunities,\n"
|
||||
" documents, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"<p class=\"oe_view_nocontent_create\">\n"
|
||||
" Pulse para añadir un contacto a su libreta de direcciones.\n"
|
||||
" </p><p>\n"
|
||||
" OpenERP le ayuda a seguir el rastro a todas las actividades "
|
||||
"relativas a\n"
|
||||
" un cliente: discusiones, histórico de oportunidades de negocio,\n"
|
||||
" documentos, etc.\n"
|
||||
" </p>\n"
|
||||
" "
|
||||
|
||||
#. module: contacts
|
||||
#: model:ir.actions.act_window,name:contacts.action_contacts
|
||||
#: model:ir.ui.menu,name:contacts.menu_contacts
|
||||
msgid "Contacts"
|
||||
msgstr "Contactos"
|
|
@ -68,9 +68,8 @@
|
|||
I verify that the payslip is in done state
|
||||
-
|
||||
!python {model: hr.payslip}: |
|
||||
from openerp.tools.translate import _
|
||||
payslip_brw=self.browse(cr, uid, ref("hr_payslip_0"))
|
||||
assert(payslip_brw.state == 'done'), _('State not changed!')
|
||||
assert(payslip_brw.state == 'done'), 'State not changed!'
|
||||
-
|
||||
I want to check refund payslip so I click on refund button.
|
||||
-
|
||||
|
|
|
@ -89,9 +89,8 @@
|
|||
I verify the payslip is in draft state.
|
||||
-
|
||||
!python {model: hr.payslip}: |
|
||||
from openerp.tools.translate import _
|
||||
payslip_brw=self.browse(cr, uid, ref("hr_payslip_0"))
|
||||
assert(payslip_brw.state == 'draft'), _('State not changed!')
|
||||
assert(payslip_brw.state == 'draft'), 'State not changed!'
|
||||
-
|
||||
I click on "Compute Sheet" button.
|
||||
-
|
||||
|
@ -120,7 +119,6 @@
|
|||
I verify that the payslip is in done state.
|
||||
-
|
||||
!python {model: hr.payslip}: |
|
||||
from openerp.tools.translate import _
|
||||
payslip_brw=self.browse(cr, uid, ref("hr_payslip_0"))
|
||||
assert(payslip_brw.state == 'done'), _('State not changed!')
|
||||
assert(payslip_brw.state == 'done'), 'State not changed!'
|
||||
|
||||
|
|
|
@ -74,8 +74,9 @@ class hr_analytic_timesheet(osv.osv):
|
|||
toremove = {}
|
||||
for obj in self.browse(cr, uid, ids, context=context):
|
||||
toremove[obj.line_id.id] = True
|
||||
super(hr_analytic_timesheet, self).unlink(cr, uid, ids, context=context)
|
||||
self.pool.get('account.analytic.line').unlink(cr, uid, toremove.keys(), context=context)
|
||||
return super(hr_analytic_timesheet, self).unlink(cr, uid, ids, context=context)
|
||||
return True
|
||||
|
||||
|
||||
def on_change_unit_amount(self, cr, uid, id, prod_id, unit_amount, company_id, unit=False, journal_id=False, context=None):
|
||||
|
|
|
@ -9,16 +9,17 @@
|
|||
<form string="Create Invoice" version="7.0">
|
||||
<notebook>
|
||||
<page string="Billing Data">
|
||||
<group>
|
||||
<group string="Do you want to show details of work in invoice?">
|
||||
<field name="date"/>
|
||||
<field name="time"/>
|
||||
<field name="name"/>
|
||||
<field name="price"/>
|
||||
</group>
|
||||
<group string="Force to use a specific product">
|
||||
<field name="product"/>
|
||||
</group>
|
||||
<group cols="2">
|
||||
<group string="Do you want to show details of work in invoice?" colspan="1" cols="2">
|
||||
<field name="date"/>
|
||||
<field name="time"/>
|
||||
<field name="name"/>
|
||||
<field name="price"/>
|
||||
</group>
|
||||
<group string="Force to use a specific product" colspan="1" cols="2">
|
||||
<p class="oe_grey" colspan="2">When reinvoicing costs, the amount on the invoice lines is given by the sale price of the corresponding product (if any, and if its sale price is not 0). You can use the following field to enforce the use of a single product for all the chosen lines in the future invoices.</p>
|
||||
<field name="product"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
<field name="priority">10</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree editable="bottom" string="Statement lines">
|
||||
<field name="statement_id" readonly="1" invisible="1"/>
|
||||
<field name="sequence" readonly="1" invisible="1"/>
|
||||
<field name="date"/>
|
||||
<field name="name"/>
|
||||
|
|
|
@ -192,10 +192,13 @@ class mail_alias(osv.Model):
|
|||
def create_unique_alias(self, cr, uid, vals, model_name=None, context=None):
|
||||
"""Creates an email.alias record according to the values provided in ``vals``,
|
||||
with 2 alterations: the ``alias_name`` value may be suffixed in order to
|
||||
make it unique, and the ``alias_model_id`` value will set to the
|
||||
model ID of the ``model_name`` value, if provided,
|
||||
make it unique (and certain unsafe characters replaced), and
|
||||
he ``alias_model_id`` value will set to the model ID of the ``model_name``
|
||||
value, if provided,
|
||||
"""
|
||||
alias_name = re.sub(r'[^\w+]', '-', remove_accents(vals['alias_name'])).lower()
|
||||
# when an alias name appears to already be an email, we keep the local part only
|
||||
alias_name = remove_accents(vals['alias_name']).lower().split('@')[0]
|
||||
alias_name = re.sub(r'[^\w+.]+', '-', alias_name)
|
||||
alias_name = self._find_unique(cr, uid, alias_name, context=context)
|
||||
vals['alias_name'] = alias_name
|
||||
if model_name:
|
||||
|
|
|
@ -95,6 +95,9 @@ class mail_notification(osv.Model):
|
|||
# Do not send to partners without email address defined
|
||||
if not partner.email:
|
||||
continue
|
||||
# Do not send to partners having same email address than the author (can cause loops or bounce effect due to messy database)
|
||||
if message.author_id and message.author_id.email == partner.email:
|
||||
continue
|
||||
# Partner does not want to receive any emails or is opt-out
|
||||
if partner.notification_email_send == 'none':
|
||||
continue
|
||||
|
@ -186,11 +189,16 @@ class mail_notification(osv.Model):
|
|||
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]
|
||||
'recipient_ids': [(4, id) for id in notify_partner_ids],
|
||||
'references': references,
|
||||
}
|
||||
if msg.email_from:
|
||||
mail_values['email_from'] = msg.email_from
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
import base64
|
||||
import logging
|
||||
import re
|
||||
from urllib import urlencode
|
||||
from urlparse import urljoin
|
||||
|
||||
|
@ -96,9 +97,19 @@ class mail_mail(osv.Model):
|
|||
return values.get('reply_to')
|
||||
email_reply_to = False
|
||||
|
||||
# model, res_id: comes from values OR related message
|
||||
model = values.get('model')
|
||||
res_id = values.get('res_id')
|
||||
if values.get('mail_message_id') and (not model or not res_id):
|
||||
message = self.pool.get('mail.message').browse(cr, uid, values.get('mail_message_id'), context=context)
|
||||
if not model:
|
||||
model = message.model
|
||||
if not res_id:
|
||||
res_id = message.res_id
|
||||
|
||||
# if model and res_id: try to use ``message_get_reply_to`` that returns the document alias
|
||||
if values.get('model') and values.get('res_id') and hasattr(self.pool.get(values.get('model')), 'message_get_reply_to'):
|
||||
email_reply_to = self.pool.get(values.get('model')).message_get_reply_to(cr, uid, [values.get('res_id')], context=context)[0]
|
||||
if model and res_id and hasattr(self.pool.get(model), 'message_get_reply_to'):
|
||||
email_reply_to = self.pool.get(model).message_get_reply_to(cr, uid, [res_id], context=context)[0]
|
||||
# no alias reply_to -> reply_to will be the email_from, only the email part
|
||||
if not email_reply_to and values.get('email_from'):
|
||||
emails = tools.email_split(values.get('email_from'))
|
||||
|
@ -106,10 +117,13 @@ class mail_mail(osv.Model):
|
|||
email_reply_to = emails[0]
|
||||
|
||||
# format 'Document name <email_address>'
|
||||
if email_reply_to and values.get('model') and values.get('res_id'):
|
||||
document_name = self.pool.get(values.get('model')).name_get(cr, SUPERUSER_ID, [values.get('res_id')], context=context)[0]
|
||||
if email_reply_to and model and res_id:
|
||||
document_name = self.pool.get(model).name_get(cr, SUPERUSER_ID, [res_id], context=context)[0]
|
||||
if document_name:
|
||||
email_reply_to = _('Followers of %s <%s>') % (document_name[1], email_reply_to)
|
||||
# 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
|
||||
|
||||
|
@ -242,7 +256,7 @@ class mail_mail(osv.Model):
|
|||
body = self.send_get_mail_body(cr, uid, mail, partner=partner, context=context)
|
||||
subject = self.send_get_mail_subject(cr, uid, mail, partner=partner, context=context)
|
||||
body_alternative = tools.html2plaintext(body)
|
||||
email_to = [partner.email] if partner else tools.email_split(mail.email_to)
|
||||
email_to = ['%s <%s>' % (partner.name, partner.email)] if partner else tools.email_split(mail.email_to)
|
||||
return {
|
||||
'body': body,
|
||||
'body_alternative': body_alternative,
|
||||
|
|
|
@ -438,9 +438,15 @@ class mail_thread(osv.AbstractModel):
|
|||
|
||||
def _message_find_partners(self, cr, uid, message, header_fields=['From'], context=None):
|
||||
""" Find partners related to some header fields of the message. """
|
||||
partner_obj = self.pool.get('res.partner')
|
||||
partner_ids = []
|
||||
s = ', '.join([decode(message.get(h)) for h in header_fields if message.get(h)])
|
||||
return [partner_id for email in tools.email_split(s)
|
||||
for partner_id in self.pool.get('res.partner').search(cr, uid, [('email', 'ilike', email)], limit=1, context=context)]
|
||||
for email_address in tools.email_split(s):
|
||||
related_partners = partner_obj.search(cr, uid, [('email', 'ilike', email_address), ('user_ids', '!=', False)], limit=1, context=context)
|
||||
if not related_partners:
|
||||
related_partners = partner_obj.search(cr, uid, [('email', 'ilike', email_address)], limit=1, context=context)
|
||||
partner_ids += related_partners
|
||||
return partner_ids
|
||||
|
||||
def _message_find_user_id(self, cr, uid, message, context=None):
|
||||
from_local_part = tools.email_split(decode(message.get('From')))[0]
|
||||
|
@ -549,6 +555,11 @@ class mail_thread(osv.AbstractModel):
|
|||
# Legacy: fallback to matching [ID] in the Subject
|
||||
match = tools.res_re.search(decode_header(message, 'Subject'))
|
||||
thread_id = match and match.group(1)
|
||||
# Convert into int (bug spotted in 7.0 because of str)
|
||||
try:
|
||||
thread_id = int(thread_id)
|
||||
except:
|
||||
thread_id = False
|
||||
assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \
|
||||
"No possible route found for incoming message with Message-Id %s. " \
|
||||
"Create an appropriate mail.alias or force the destination model." % message_id
|
||||
|
@ -951,6 +962,22 @@ class mail_thread(osv.AbstractModel):
|
|||
model = False
|
||||
if thread_id:
|
||||
model = context.get('thread_model', self._name) if self._name == 'mail.thread' else self._name
|
||||
if model != self._name:
|
||||
del context['thread_model']
|
||||
return self.pool.get(model).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs)
|
||||
|
||||
# 0: Parse email-from, try to find a better author_id based on document's followers for incoming emails
|
||||
email_from = kwargs.get('email_from')
|
||||
if email_from and thread_id and type == 'email' and kwargs.get('author_id'):
|
||||
email_list = tools.email_split(email_from)
|
||||
doc = self.browse(cr, uid, thread_id, context=context)
|
||||
if email_list and doc:
|
||||
author_ids = self.pool.get('res.partner').search(cr, uid, [
|
||||
('email', 'ilike', email_list[0]),
|
||||
('id', 'in', [f.id for f in doc.message_follower_ids])
|
||||
], limit=1, context=context)
|
||||
if author_ids:
|
||||
kwargs['author_id'] = author_ids[0]
|
||||
|
||||
# 1: Handle content subtype: if plaintext, converto into HTML
|
||||
if content_subtype == 'plaintext':
|
||||
|
|
|
@ -688,6 +688,10 @@ openerp.mail = function (session) {
|
|||
|
||||
on_message_post: function (event) {
|
||||
var self = this;
|
||||
if (self.flag_post) {
|
||||
return;
|
||||
}
|
||||
self.flag_post = true;
|
||||
if (this.do_check_attachment_upload() && (this.attachment_ids.length || this.$('textarea').val().match(/\S+/))) {
|
||||
if (this.is_log) {
|
||||
this.do_send_message_post([], this.is_log);
|
||||
|
@ -735,6 +739,7 @@ openerp.mail = function (session) {
|
|||
thread.insert_message( message, root ? undefined : self.$el, root );
|
||||
});
|
||||
self.on_cancel();
|
||||
self.flag_post = false;
|
||||
});
|
||||
},
|
||||
|
||||
|
@ -910,9 +915,6 @@ openerp.mail = function (session) {
|
|||
|
||||
// read messages
|
||||
self.parent_thread.message_fetch(this.domain, this.context, false, function (arg, data) {
|
||||
if (self.options.root_thread == self.parent_thread) {
|
||||
data.reverse();
|
||||
}
|
||||
self.id = false;
|
||||
// insert the message on dom after this message
|
||||
self.parent_thread.switch_new_message( data, self.$el );
|
||||
|
@ -1212,7 +1214,7 @@ openerp.mail = function (session) {
|
|||
this.partner_ids = datasets.partner_ids;
|
||||
this.messages = [];
|
||||
|
||||
this.options.flat_mode = !!(this.options.display_indented_thread > this.thread_level ? this.options.display_indented_thread - this.thread_level : 0);
|
||||
this.options.flat_mode = (this.options.display_indented_thread - this.thread_level > 0);
|
||||
|
||||
// object compose message
|
||||
this.compose_message = false;
|
||||
|
@ -1434,8 +1436,8 @@ openerp.mail = function (session) {
|
|||
create_message_object: function (data) {
|
||||
var self = this;
|
||||
|
||||
var data = _.extend(data, {'thread_level': data.thread_level ? data.thread_level : self.thread_level});
|
||||
data.options = _.extend(self.options, data.options);
|
||||
data.thread_level = self.thread_level || 0;
|
||||
data.options = _.extend(data.options || {}, self.options);
|
||||
|
||||
if (data.type=='expandable') {
|
||||
var message = new mail.ThreadExpandable(self, data, {'context':{
|
||||
|
@ -1480,8 +1482,7 @@ openerp.mail = function (session) {
|
|||
}
|
||||
|
||||
this.$('.oe_view_nocontent').remove();
|
||||
|
||||
if (dom_insert_after) {
|
||||
if (dom_insert_after && dom_insert_after.parent()[0] == self.$el[0]) {
|
||||
message.insertAfter(dom_insert_after);
|
||||
} else if (prepend) {
|
||||
message.prependTo(self.$el);
|
||||
|
@ -1500,6 +1501,7 @@ openerp.mail = function (session) {
|
|||
*/
|
||||
switch_new_message: function (records, dom_insert_after) {
|
||||
var self=this;
|
||||
var dom_insert_after = typeof dom_insert_after == 'object' ? dom_insert_after : false;
|
||||
_(records).each(function (record) {
|
||||
var thread = self.browse_thread({
|
||||
'id': record.parent_id,
|
||||
|
@ -1508,7 +1510,7 @@ openerp.mail = function (session) {
|
|||
// create object and attach to the thread object
|
||||
var message = thread.create_message_object( record );
|
||||
// insert the message on dom
|
||||
thread.insert_message( message, typeof dom_insert_after == 'object' ? dom_insert_after : false);
|
||||
thread.insert_message( message, dom_insert_after);
|
||||
});
|
||||
if (!records.length && this.options.root_thread == this) {
|
||||
this.no_message();
|
||||
|
|
|
@ -19,12 +19,13 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from . import test_mail_message, test_mail_features, test_message_read, test_invite, test_mail_alias
|
||||
from . import test_mail_message, test_mail_features, test_mail_gateway, test_message_read, test_invite, test_mail_alias
|
||||
|
||||
checks = [
|
||||
test_mail_message,
|
||||
test_mail_alias,
|
||||
test_mail_features,
|
||||
test_mail_gateway,
|
||||
test_message_read,
|
||||
test_invite,
|
||||
]
|
||||
|
|
|
@ -70,9 +70,9 @@ class TestMailBase(common.TransactionCase):
|
|||
|
||||
# Test users to use through the various tests
|
||||
self.user_raoul_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Raoul Grosbedon', 'signature': 'Raoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
|
||||
{'name': 'Raoul Grosbedon', 'signature': 'SignRaoul', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [self.group_employee_id])]})
|
||||
self.user_bert_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Bert Tartignole', 'signature': 'Bert', 'email': 'bert@bert.fr', 'login': 'bert', 'groups_id': [(6, 0, [])]})
|
||||
{'name': 'Bert Tartignole', 'signature': 'SignBert', 'email': 'bert@bert.fr', 'login': '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)
|
||||
|
|
|
@ -22,164 +22,29 @@
|
|||
from openerp.addons.mail.tests.test_mail_base import TestMailBase
|
||||
from openerp.tools.mail import html_sanitize
|
||||
|
||||
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
||||
To: {to}
|
||||
Received: by mail1.openerp.com (Postfix, from userid 10002)
|
||||
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
|
||||
From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
|
||||
Subject: {subject}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="----=_Part_4200734_24778174.1344608186754"
|
||||
Date: Fri, 10 Aug 2012 14:16:26 +0000
|
||||
Message-ID: <1198923581.41972151344608186760.JavaMail@agrolait.com>
|
||||
{extra}
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Please call me as soon as possible this afternoon!
|
||||
|
||||
--
|
||||
Sylvie
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>=20
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" />
|
||||
</head>=20
|
||||
<body style=3D"margin: 0; padding: 0; background: #ffffff;-webkit-text-size-adjust: 100%;">=20
|
||||
|
||||
<p>Please call me as soon as possible this afternoon!</p>
|
||||
|
||||
<p>--<br/>
|
||||
Sylvie
|
||||
<p>
|
||||
</body>
|
||||
</html>
|
||||
------=_Part_4200734_24778174.1344608186754--
|
||||
"""
|
||||
|
||||
MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
||||
To: {to}
|
||||
Received: by mail1.openerp.com (Postfix, from userid 10002)
|
||||
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
|
||||
From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
|
||||
Subject: {subject}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain
|
||||
Date: Fri, 10 Aug 2012 14:16:26 +0000
|
||||
Message-ID: {msg_id}
|
||||
{extra}
|
||||
|
||||
Please call me as soon as possible this afternoon!
|
||||
|
||||
--
|
||||
Sylvie
|
||||
"""
|
||||
|
||||
|
||||
class test_mail(TestMailBase):
|
||||
|
||||
def test_00_message_process(self):
|
||||
""" Testing incoming emails processing. """
|
||||
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
|
||||
|
||||
# groups@.. will cause the creation of new mail groups
|
||||
self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
|
||||
self.mail_alias.create(cr, uid, {'alias_name': 'groups', 'alias_model_id': self.mail_group_model_id})
|
||||
|
||||
# Incoming mail creates a new mail_group "frogs"
|
||||
self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', 'frogs')]), [])
|
||||
mail_frogs = MAIL_TEMPLATE.format(to='groups@example.com, other@gmail.com', subject='frogs', extra='')
|
||||
self.mail_thread.message_process(cr, uid, None, mail_frogs)
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'frogs')])
|
||||
self.assertTrue(len(frog_groups) == 1)
|
||||
|
||||
# Previously-created group can be emailed now - it should have an implicit alias group+frogs@...
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
group_messages = frog_group.message_ids
|
||||
self.assertTrue(len(group_messages) == 1, 'New group should only have the original message')
|
||||
mail_frog_news = MAIL_TEMPLATE.format(to='Friendly Frogs <group+frogs@example.com>', subject='news', extra='')
|
||||
self.mail_thread.message_process(cr, uid, None, mail_frog_news)
|
||||
frog_group.refresh()
|
||||
self.assertTrue(len(frog_group.message_ids) == 2, 'Group should contain 2 messages now')
|
||||
|
||||
# Even with a wrong destination, a reply should end up in the correct thread
|
||||
mail_reply = MAIL_TEMPLATE.format(to='erroneous@example.com>', subject='Re: news',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
self.mail_thread.message_process(cr, uid, None, mail_reply)
|
||||
frog_group.refresh()
|
||||
self.assertTrue(len(frog_group.message_ids) == 3, 'Group should contain 3 messages now')
|
||||
|
||||
# No model passed and no matching alias must raise
|
||||
mail_spam = MAIL_TEMPLATE.format(to='noone@example.com', subject='spam', extra='')
|
||||
self.assertRaises(Exception,
|
||||
self.mail_thread.message_process,
|
||||
cr, uid, None, mail_spam)
|
||||
|
||||
# plain text content should be wrapped and stored as html
|
||||
test_msg_id = '<deadcafe.1337@smtp.agrolait.com>'
|
||||
mail_text = MAIL_TEMPLATE_PLAINTEXT.format(to='groups@example.com', subject='frogs', extra='', msg_id=test_msg_id)
|
||||
self.mail_thread.message_process(cr, uid, None, mail_text)
|
||||
new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
|
||||
self.assertEqual(new_mail.body, '<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>',
|
||||
'plaintext mail incorrectly parsed')
|
||||
|
||||
# Do: post a new message, with a known partner
|
||||
test_msg_id = '<deadcafe.1337-2@smtp.agrolait.com>'
|
||||
TEMPLATE_MOD = MAIL_TEMPLATE_PLAINTEXT.replace('Sylvie Lelitre <sylvie.lelitre@agrolait.com>', user_raoul.email)
|
||||
mail_new = TEMPLATE_MOD.format(to='Friendly Frogs <group+frogs@example.com>', subject='extra news', extra='', msg_id=test_msg_id)
|
||||
self.mail_thread.message_process(cr, uid, None, mail_new)
|
||||
new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
|
||||
# Test: author_id set, not email_from
|
||||
self.assertEqual(new_mail.author_id, user_raoul.partner_id, 'message process wrong author found')
|
||||
self.assertEqual(new_mail.email_from, user_raoul.email, 'message process wrong email_from')
|
||||
|
||||
# Do: post a new message, with a unknown partner
|
||||
test_msg_id = '<deadcafe.1337-3@smtp.agrolait.com>'
|
||||
TEMPLATE_MOD = MAIL_TEMPLATE_PLAINTEXT.replace('Sylvie Lelitre <sylvie.lelitre@agrolait.com>', '_abcd_')
|
||||
mail_new = TEMPLATE_MOD.format(to='Friendly Frogs <group+frogs@example.com>', subject='super news', extra='', msg_id=test_msg_id)
|
||||
self.mail_thread.message_process(cr, uid, None, mail_new)
|
||||
new_mail = self.mail_message.browse(cr, uid, self.mail_message.search(cr, uid, [('message_id', '=', test_msg_id)])[0])
|
||||
# Test: author_id set, not email_from
|
||||
self.assertFalse(new_mail.author_id, 'message process shnould not have found a partner for _abcd_ email address')
|
||||
self.assertIn('_abcd_', new_mail.email_from, 'message process should set en email_from when not finding a partner_id')
|
||||
|
||||
def test_05_thread_parent_resolution(self):
|
||||
"""Verify parent/child relationships are correctly established when processing incoming mails"""
|
||||
|
||||
def test_000_alias_setup(self):
|
||||
""" Test basic mail.alias setup works, before trying to use them for routing """
|
||||
cr, uid = self.cr, self.uid
|
||||
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
msg1 = group_pigs.message_post(body='My Body', subject='1')
|
||||
msg2 = group_pigs.message_post(body='My Body', subject='2')
|
||||
msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
|
||||
self.assertTrue(msg1.message_id, "New message should have a proper message_id")
|
||||
self.user_valentin_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Valentin Cognito', 'email': 'valentin.cognito@gmail.com', 'login': 'valentin.cognito'})
|
||||
self.user_valentin = self.res_users.browse(cr, uid, self.user_valentin_id)
|
||||
self.assertEquals(self.user_valentin.alias_name, self.user_valentin.login, "Login should be used as alias")
|
||||
|
||||
# Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
|
||||
# 1. In-Reply-To header
|
||||
reply_msg = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
|
||||
extra='In-Reply-To: %s' % msg1.message_id)
|
||||
self.mail_group.message_process(cr, uid, None, reply_msg)
|
||||
self.user_pagan_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Pagan Le Marchant', 'email': 'plmarchant@gmail.com', 'login': 'plmarchant@gmail.com'})
|
||||
self.user_pagan = self.res_users.browse(cr, uid, self.user_pagan_id)
|
||||
self.assertEquals(self.user_pagan.alias_name, 'plmarchant', "If login is an email, the alias should keep only the local part")
|
||||
|
||||
# 2. References header
|
||||
reply_msg2 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: Re: 1',
|
||||
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
|
||||
self.mail_group.message_process(cr, uid, None, reply_msg2)
|
||||
self.user_barty_id = self.res_users.create(cr, uid,
|
||||
{'name': 'Bartholomew Ironside', 'email': 'barty@gmail.com', 'login': 'b4r+_#_R3wl$$'})
|
||||
self.user_barty = self.res_users.browse(cr, uid, self.user_barty_id)
|
||||
self.assertEquals(self.user_barty.alias_name, 'b4r+_-_r3wl-', 'Disallowed chars should be replaced by hyphens')
|
||||
|
||||
# 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, not to mail
|
||||
reply_msg3 = MAIL_TEMPLATE.format(to='Pretty Pigs <group+pigs@example.com>, other@gmail.com',
|
||||
extra='', subject='Re: [%s] 1' % self.group_pigs_id)
|
||||
self.mail_group.message_process(cr, uid, 'mail.group', reply_msg3)
|
||||
|
||||
group_pigs.refresh()
|
||||
msg1.refresh()
|
||||
self.assertEqual(5, len(group_pigs.message_ids), 'group should contain 5 messages')
|
||||
self.assertEqual(2, len(msg1.child_ids), 'msg1 should have 2 children now')
|
||||
|
||||
def test_10_followers_function_field(self):
|
||||
def test_00_followers_function_field(self):
|
||||
""" Tests designed for the many2many function field 'follower_ids'.
|
||||
We will test to perform writes using the many2many commands 0, 3, 4,
|
||||
5 and 6. """
|
||||
|
@ -236,7 +101,7 @@ class test_mail(TestMailBase):
|
|||
follower_ids = set([follower.partner_id.id for follower in self.mail_followers.browse(cr, uid, fol_obj_ids)])
|
||||
self.assertEqual(follower_ids, set([partner_bert_id, user_admin.partner_id.id]), 'Bert and Admin should be the followers of dummy mail.group data')
|
||||
|
||||
def test_11_message_followers_and_subtypes(self):
|
||||
def test_05_message_followers_and_subtypes(self):
|
||||
""" Tests designed for the subscriber API as well as message subtypes """
|
||||
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
|
||||
# Data: message subtypes
|
||||
|
@ -288,7 +153,7 @@ class test_mail(TestMailBase):
|
|||
self.assertTrue(subtype_data['mt_mg_nodef']['followed'], 'Admin should follow mt_mg_nodef in pigs')
|
||||
self.assertTrue(subtype_data['mt_all_nodef']['followed'], 'Admin should follow mt_all_nodef in pigs')
|
||||
|
||||
def test_20_message_quote_context(self):
|
||||
def test_10_message_quote_context(self):
|
||||
""" Tests designed for message_post. """
|
||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
||||
|
||||
|
@ -306,239 +171,396 @@ class test_mail(TestMailBase):
|
|||
self.assertNotIn('First answer, should not be displayed', result, 'Old answer should not be in quote.')
|
||||
self.assertNotIn('My answer I am propagating', result, 'Thread header content should be in quote.')
|
||||
|
||||
def test_21_message_post(self):
|
||||
def test_20_message_post(self):
|
||||
""" Tests designed for message_post. """
|
||||
cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
|
||||
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
|
||||
|
||||
# --------------------------------------------------
|
||||
# Data creation
|
||||
# --------------------------------------------------
|
||||
# 0 - Update existing users-partners
|
||||
self.res_users.write(cr, uid, [uid], {'email': 'a@a'})
|
||||
self.res_users.write(cr, uid, [self.user_raoul_id], {'email': 'r@r'})
|
||||
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
|
||||
# 2 - Carine Poilvache, with email, should receive emails for emails
|
||||
p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
|
||||
# 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
|
||||
p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'notification_email_send': 'all'})
|
||||
|
||||
# Subscribe Raoul, #1, #2
|
||||
group_pigs.message_subscribe([self.partner_raoul_id, p_b_id, p_c_id])
|
||||
|
||||
# Mail data
|
||||
p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'email': 'd@d', 'notification_email_send': 'all'})
|
||||
# 4 - Attachments
|
||||
attach1_id = self.ir_attachment.create(cr, user_raoul.id, {
|
||||
'name': 'Attach1', 'datas_fname': 'Attach1',
|
||||
'datas': 'bWlncmF0aW9uIHRlc3Q=',
|
||||
'res_model': 'mail.compose.message', 'res_id': 0})
|
||||
attach2_id = self.ir_attachment.create(cr, user_raoul.id, {
|
||||
'name': 'Attach2', 'datas_fname': 'Attach2',
|
||||
'datas': 'bWlncmF0aW9uIHRlc3Q=',
|
||||
'res_model': 'mail.compose.message', 'res_id': 0})
|
||||
attach3_id = self.ir_attachment.create(cr, user_raoul.id, {
|
||||
'name': 'Attach3', 'datas_fname': 'Attach3',
|
||||
'datas': 'bWlncmF0aW9uIHRlc3Q=',
|
||||
'res_model': 'mail.compose.message', 'res_id': 0})
|
||||
# 5 - Mail data
|
||||
_subject = 'Pigs'
|
||||
_mail_subject = 'Re: %s' % (group_pigs.name)
|
||||
_body1 = '<p>Pigs rules</p>'
|
||||
_mail_body1 = '<p>Pigs rules</p>'
|
||||
_mail_signature1 = '<p>Raoul</p>'
|
||||
_mail_bodyalt1 = 'Pigs rules\n'
|
||||
_mail_signaturealt1 = '\nRaoul\n'
|
||||
_body2 = '<html>Pigs rules</html>'
|
||||
_mail_body2 = '<div><p>Pigs rules</p></div>'
|
||||
_mail_signature2 = '<p>Raoul</p>'
|
||||
_mail_bodyalt2 = 'Pigs rules\n'
|
||||
_mail_signaturealt2 = '\nRaoul\n'
|
||||
_attachments = [('First', 'My first attachment'), ('Second', 'My second attachment')]
|
||||
_body2 = '<html>Pigs rocks</html>'
|
||||
_attachments = [
|
||||
('List1', 'My first attachment'),
|
||||
('List2', 'My second attachment')
|
||||
]
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE1: post comment, body and subject specified
|
||||
# ----------------------------------------
|
||||
# --------------------------------------------------
|
||||
# CASE1: post comment + partners + attachments
|
||||
# --------------------------------------------------
|
||||
|
||||
# 1. Post a new comment on Pigs
|
||||
# Data: set alias_domain to see emails with alias
|
||||
self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.domain', 'schlouby.fr')
|
||||
# Data: change Pigs name to test reply_to
|
||||
self.mail_group.write(cr, uid, [self.group_pigs_id], {'name': '"Pigs" !ù $%-'})
|
||||
|
||||
# Do: subscribe Raoul
|
||||
new_follower_ids = [self.partner_raoul_id]
|
||||
group_pigs.message_subscribe(new_follower_ids)
|
||||
# Test: group followers = Raoul + uid
|
||||
group_fids = [follower.id for follower in group_pigs.message_follower_ids]
|
||||
test_fids = new_follower_ids + [self.partner_admin_id]
|
||||
self.assertEqual(set(test_fids), set(group_fids),
|
||||
'message_subscribe: incorrect followers after subscribe')
|
||||
|
||||
# Do: Raoul message_post on Pigs
|
||||
self._init_mock_build_email()
|
||||
msg1_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body=_body1, subject=_subject, type='comment', subtype='mt_comment')
|
||||
message1 = self.mail_message.browse(cr, uid, msg1_id)
|
||||
msg1_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id,
|
||||
body=_body1, subject=_subject, partner_ids=[p_b_id, p_c_id],
|
||||
attachment_ids=[attach1_id, attach2_id], attachments=_attachments,
|
||||
type='comment', subtype='mt_comment')
|
||||
msg = self.mail_message.browse(cr, uid, msg1_id)
|
||||
msg_message_id = msg.message_id
|
||||
msg_pids = [partner.id for partner in msg.notified_partner_ids]
|
||||
msg_aids = [attach.id for attach in msg.attachment_ids]
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
# Test: mail.mail notifications have been deleted
|
||||
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg1_id)]), 'mail.mail notifications should have been auto-deleted!')
|
||||
# Test: mail_message: subject is _subject, body is _body1 (no formatting done)
|
||||
self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect')
|
||||
self.assertEqual(message1.body, _body1, 'mail.message body incorrect')
|
||||
# Test: sent_email: email send by server: correct subject, body, body_alternative
|
||||
self.assertEqual(len(sent_emails), 2, 'sent_email number of sent emails incorrect')
|
||||
for sent_email in sent_emails:
|
||||
self.assertEqual(sent_email['subject'], _subject, 'sent_email subject incorrect')
|
||||
self.assertIn(_mail_body1, sent_email['body'], 'sent_email body incorrect')
|
||||
self.assertIn(_mail_signature1, sent_email['body'], 'sent_email body incorrect (no signature)')
|
||||
self.assertIn("OpenERP", sent_email['body'], 'sent_email body incorrect (no OpenERP company)')
|
||||
# the html2plaintext uses etree or beautiful soup, so the result may be slighly different
|
||||
# depending if you have installed beautiful soup.
|
||||
self.assertIn(_mail_bodyalt1, sent_email['body_alternative'], 'sent_email body_alternative is incorrect')
|
||||
self.assertIn(_mail_signaturealt1, sent_email['body_alternative'], 'sent_email body_alternative is incorrect (no signature)')
|
||||
self.assertIn("OpenERP", sent_email['body_alternative'], 'sent_email body incorrect (no OpenERP company)')
|
||||
# Test: mail_message: notified_partner_ids = group followers
|
||||
message_pids = set([partner.id for partner in message1.notified_partner_ids])
|
||||
|
||||
# Test: mail_message: subject and body not modified
|
||||
self.assertEqual(_subject, msg.subject, 'message_post: mail.message subject incorrect')
|
||||
self.assertEqual(_body1, msg.body, 'message_post: mail.message body incorrect')
|
||||
# Test: mail_message: notified_partner_ids = group followers + partner_ids - author
|
||||
test_pids = set([self.partner_admin_id, p_b_id, p_c_id])
|
||||
self.assertEqual(test_pids, message_pids, 'mail.message notified partners incorrect')
|
||||
self.assertEqual(test_pids, set(msg_pids), 'message_post: mail.message notified partners incorrect')
|
||||
# Test: mail_message: attachments (4, attachment_ids + attachments)
|
||||
test_aids = set([attach1_id, attach2_id])
|
||||
msg_attach_names = set([attach.name for attach in msg.attachment_ids])
|
||||
test_attach_names = set(['Attach1', 'Attach2', 'List1', 'List2'])
|
||||
self.assertEqual(len(msg_aids), 4,
|
||||
'message_post: mail.message wrong number of attachments')
|
||||
self.assertEqual(msg_attach_names, test_attach_names,
|
||||
'message_post: mail.message attachments incorrectly added')
|
||||
self.assertTrue(test_aids.issubset(set(msg_aids)),
|
||||
'message_post: mail.message attachments duplicated')
|
||||
for attach in msg.attachment_ids:
|
||||
self.assertEqual(attach.res_model, 'mail.group',
|
||||
'message_post: mail.message attachments were not linked to the document')
|
||||
self.assertEqual(attach.res_id, group_pigs.id,
|
||||
'message_post: mail.message attachments were not linked to the document')
|
||||
if 'List' in attach.name:
|
||||
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
|
||||
'message_post: mail.message attachment name / data incorrect')
|
||||
dl_attach = self.mail_message.download_attachment(cr, user_raoul.id, id_message=msg.id, attachment_id=attach.id)
|
||||
self.assertIn((dl_attach['filename'], dl_attach['base64'].decode('base64')), _attachments,
|
||||
'message_post: mail.message download_attachment is incorrect')
|
||||
|
||||
# Test: followers: same as before (author was already subscribed)
|
||||
group_pigs.refresh()
|
||||
group_fids = [follower.id for follower in group_pigs.message_follower_ids]
|
||||
test_fids = new_follower_ids + [self.partner_admin_id]
|
||||
self.assertEqual(set(test_fids), set(group_fids),
|
||||
'message_post: wrong followers after posting')
|
||||
|
||||
# Test: mail_mail: notifications have been deleted
|
||||
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg1_id)]),
|
||||
'message_post: mail.mail notifications should have been auto-deleted!')
|
||||
|
||||
# Test: notifications emails: to a and b, c is email only, r is author
|
||||
test_emailto = ['Administrator <a@a>', 'Bert Tartopoils <b@b>']
|
||||
self.assertEqual(len(sent_emails), 2,
|
||||
'message_post: notification emails wrong number of send emails')
|
||||
self.assertEqual(set([m['email_to'][0] for m in sent_emails]), set(test_emailto),
|
||||
'message_post: notification emails wrong recipients (email_to)')
|
||||
for sent_email in sent_emails:
|
||||
self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon <raoul@schlouby.fr>',
|
||||
'message_post: notification email wrong email_from: should use alias of sender')
|
||||
self.assertEqual(len(sent_email['email_to']), 1,
|
||||
'message_post: notification email sent to more than one email address instead of a precise partner')
|
||||
self.assertIn(sent_email['email_to'][0], test_emailto,
|
||||
'message_post: notification email email_to incorrect')
|
||||
self.assertEqual(sent_email['reply_to'], '"Followers of -Pigs-" <group+pigs@schlouby.fr>',
|
||||
'message_post: notification email reply_to incorrect')
|
||||
self.assertEqual(_subject, sent_email['subject'],
|
||||
'message_post: notification email subject incorrect')
|
||||
self.assertIn(_body1, sent_email['body'],
|
||||
'message_post: notification email body incorrect')
|
||||
self.assertIn(user_raoul.signature, sent_email['body'],
|
||||
'message_post: notification email body should contain the sender signature')
|
||||
self.assertIn('Pigs rules', sent_email['body_alternative'],
|
||||
'message_post: notification email body alternative should contain the body')
|
||||
self.assertNotIn('<p>', sent_email['body_alternative'],
|
||||
'message_post: notification email body alternative still contains html')
|
||||
self.assertIn(user_raoul.signature, sent_email['body_alternative'],
|
||||
'message_post: notification email body alternative should contain the sender signature')
|
||||
self.assertFalse(sent_email['references'],
|
||||
'message_post: references should be False when sending a message that is not a reply')
|
||||
|
||||
# Test: notification linked to this message = group followers = notified_partner_ids
|
||||
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg1_id)])
|
||||
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
|
||||
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
|
||||
# Test: sent_email: email_to should contain b@b, not c@c (pref email), not a@a (writer)
|
||||
for sent_email in sent_emails:
|
||||
self.assertTrue(set(sent_email['email_to']).issubset(set(['a@a', 'b@b'])), 'sent_email email_to is incorrect')
|
||||
self.assertEqual(notif_pids, test_pids,
|
||||
'message_post: mail.message created mail.notification incorrect')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE2: post an email with attachments, parent_id, partner_ids, parent notification
|
||||
# ----------------------------------------
|
||||
# Data: Pigs name back to normal
|
||||
self.mail_group.write(cr, uid, [self.group_pigs_id], {'name': 'Pigs'})
|
||||
|
||||
# 1. Post a new email comment on Pigs
|
||||
# --------------------------------------------------
|
||||
# CASE2: reply + parent_id + parent notification
|
||||
# --------------------------------------------------
|
||||
|
||||
# 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: Raoul message_post on Pigs
|
||||
self._init_mock_build_email()
|
||||
msg2_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body=_body2, type='email', subtype='mt_comment',
|
||||
partner_ids=[p_d_id], parent_id=msg1_id, attachments=_attachments,
|
||||
context={'mail_post_autofollow': True})
|
||||
message2 = self.mail_message.browse(cr, uid, msg2_id)
|
||||
msg2_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id,
|
||||
body=_body2, type='email', subtype='mt_comment',
|
||||
partner_ids=[p_d_id], parent_id=msg1_id, attachment_ids=[attach3_id],
|
||||
context={'mail_post_autofollow': True})
|
||||
msg = self.mail_message.browse(cr, uid, msg2_id)
|
||||
msg_pids = [partner.id for partner in msg.notified_partner_ids]
|
||||
msg_aids = [attach.id for attach in msg.attachment_ids]
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!')
|
||||
# Test: mail_message: subject is False, body is _body2 (no formatting done), parent_id is msg_id
|
||||
self.assertEqual(message2.subject, False, 'mail.message subject incorrect')
|
||||
self.assertEqual(message2.body, html_sanitize(_body2), 'mail.message body incorrect')
|
||||
self.assertEqual(message2.parent_id.id, msg1_id, 'mail.message parent_id incorrect')
|
||||
# Test: sent_email: email send by server: correct automatic subject, body, body_alternative
|
||||
self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect')
|
||||
for sent_email in sent_emails:
|
||||
self.assertEqual(sent_email['subject'], _mail_subject, 'sent_email subject incorrect')
|
||||
self.assertIn(_mail_body2, sent_email['body'], 'sent_email body incorrect')
|
||||
self.assertIn(_mail_signature2, sent_email['body'], 'sent_email body incorrect (no signature)')
|
||||
self.assertIn("OpenERP", sent_email['body'], 'sent_email body incorrect (no OpenERP company)')
|
||||
# body_alternative
|
||||
self.assertIn(_mail_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative is incorrect')
|
||||
self.assertIn(_mail_signaturealt2, sent_email['body_alternative'], 'sent_email body_alternative is incorrect (no signature)')
|
||||
self.assertIn("OpenERP", sent_email['body'], 'sent_email body incorrect (no OpenERP company)')
|
||||
|
||||
# Test: mail_message: subject is False, body, parent_id is msg_id
|
||||
self.assertEqual(msg.subject, False, 'message_post: mail.message subject incorrect')
|
||||
self.assertEqual(msg.body, html_sanitize(_body2), 'message_post: mail.message body incorrect')
|
||||
self.assertEqual(msg.parent_id.id, msg1_id, 'message_post: mail.message parent_id incorrect')
|
||||
# Test: mail_message: notified_partner_ids = group followers
|
||||
message_pids = set([partner.id for partner in message2.notified_partner_ids])
|
||||
test_pids = set([self.partner_admin_id, p_b_id, p_c_id, p_d_id])
|
||||
self.assertEqual(message_pids, test_pids, 'mail.message partners incorrect')
|
||||
# Test: notifications linked to this message = group followers = notified_partner_ids
|
||||
test_pids = [self.partner_admin_id, p_d_id]
|
||||
self.assertEqual(set(test_pids), set(msg_pids), 'message_post: mail.message partners incorrect')
|
||||
# Test: mail_message: notifications linked to this message = group followers = notified_partner_ids
|
||||
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', msg2_id)])
|
||||
notif_pids = set([notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)])
|
||||
self.assertEqual(notif_pids, test_pids, 'mail.message notification partners incorrect')
|
||||
# Test: sent_email: email_to should contain b@b, c@c, not a@a (writer)
|
||||
notif_pids = [notif.partner_id.id for notif in self.mail_notification.browse(cr, uid, notif_ids)]
|
||||
self.assertEqual(set(test_pids), set(notif_pids), 'message_post: mail.message notification partners incorrect')
|
||||
|
||||
# Test: mail_mail: notifications deleted
|
||||
self.assertFalse(self.mail_mail.search(cr, uid, [('mail_message_id', '=', msg2_id)]), 'mail.mail notifications should have been auto-deleted!')
|
||||
|
||||
# Test: emails send by server (to a, b, c, d)
|
||||
test_emailto = [u'Administrator <a@a>', u'Bert Tartopoils <b@b>', u'Carine Poilvache <c@c>', u'D\xe9d\xe9 Grosbedon <d@d>']
|
||||
# self.assertEqual(len(sent_emails), 3, 'sent_email number of sent emails incorrect')
|
||||
for sent_email in sent_emails:
|
||||
self.assertTrue(set(sent_email['email_to']).issubset(set(['a@a', 'b@b', 'c@c'])), 'sent_email email_to incorrect')
|
||||
# Test: attachments
|
||||
for attach in message2.attachment_ids:
|
||||
self.assertEqual(attach.res_model, 'mail.group', 'mail.message attachment res_model incorrect')
|
||||
self.assertEqual(attach.res_id, self.group_pigs_id, 'mail.message attachment res_id incorrect')
|
||||
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
|
||||
'mail.message attachment name / data incorrect')
|
||||
# Test: download attachments
|
||||
for attach in message2.attachment_ids:
|
||||
dl_attach = self.mail_message.download_attachment(cr, user_raoul.id, id_message=message2.id, attachment_id=attach.id)
|
||||
self.assertIn((dl_attach['filename'], dl_attach['base64'].decode('base64')), _attachments, 'mail.message download_attachment is incorrect')
|
||||
self.assertEqual(sent_email['email_from'], 'Raoul Grosbedon <r@r>',
|
||||
'message_post: notification email wrong email_from: should use email of sender when no alias domain set')
|
||||
self.assertEqual(len(sent_email['email_to']), 1,
|
||||
'message_post: notification email sent to more than one email address instead of a precise partner')
|
||||
self.assertIn(sent_email['email_to'][0], test_emailto,
|
||||
'message_post: notification email email_to incorrect')
|
||||
self.assertEqual(sent_email['reply_to'], '"Followers of Pigs" <r@r>',
|
||||
'message_post: notification email reply_to incorrect: should name Followers of Pigs, and have raoul email')
|
||||
self.assertEqual(_mail_subject, sent_email['subject'],
|
||||
'message_post: notification email subject incorrect')
|
||||
self.assertIn(html_sanitize(_body2), sent_email['body'],
|
||||
'message_post: notification email does not contain the body')
|
||||
self.assertIn(user_raoul.signature, sent_email['body'],
|
||||
'message_post: notification email body should contain the sender signature')
|
||||
self.assertIn('Pigs rocks', sent_email['body_alternative'],
|
||||
'message_post: notification email body alternative should contain the body')
|
||||
self.assertNotIn('<p>', sent_email['body_alternative'],
|
||||
'message_post: notification email body alternative still contains html')
|
||||
self.assertIn(user_raoul.signature, sent_email['body_alternative'],
|
||||
'message_post: notification email body alternative should contain the sender signature')
|
||||
self.assertIn(msg_message_id, sent_email['references'],
|
||||
'message_post: notification email references lacks parent message message_id')
|
||||
# Test: attachments + download
|
||||
for attach in msg.attachment_ids:
|
||||
self.assertEqual(attach.res_model, 'mail.group',
|
||||
'message_post: mail.message attachment res_model incorrect')
|
||||
self.assertEqual(attach.res_id, self.group_pigs_id,
|
||||
'message_post: mail.message attachment res_id incorrect')
|
||||
|
||||
# 2. Dédé has been notified -> should also have been notified of the parent message
|
||||
message1.refresh()
|
||||
message_pids = set([partner.id for partner in message1.notified_partner_ids])
|
||||
# Test: Dédé has been notified -> should also have been notified of the parent message
|
||||
msg = self.mail_message.browse(cr, uid, msg1_id)
|
||||
msg_pids = set([partner.id for partner in msg.notified_partner_ids])
|
||||
test_pids = set([self.partner_admin_id, p_b_id, p_c_id, p_d_id])
|
||||
self.assertEqual(test_pids, message_pids, 'mail.message parent notification not created')
|
||||
self.assertEqual(test_pids, msg_pids, 'message_post: mail.message parent notification not created')
|
||||
|
||||
# 3. Reply to the last message, check that its parent will be the first message
|
||||
# Do: reply to last message
|
||||
msg3_id = self.mail_group.message_post(cr, user_raoul.id, self.group_pigs_id, body='Test', parent_id=msg2_id)
|
||||
message = self.mail_message.browse(cr, uid, msg3_id)
|
||||
self.assertEqual(message.parent_id.id, msg1_id, 'message_post did not flatten the thread structure')
|
||||
msg = self.mail_message.browse(cr, uid, msg3_id)
|
||||
# Test: check that its parent will be the first message
|
||||
self.assertEqual(msg.parent_id.id, msg1_id, 'message_post did not flatten the thread structure')
|
||||
|
||||
def test_25_message_compose_wizard(self):
|
||||
""" Tests designed for the mail.compose.message wizard. """
|
||||
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
|
||||
cr, uid, user_raoul, group_pigs = self.cr, self.uid, self.user_raoul, self.group_pigs
|
||||
mail_compose = self.registry('mail.compose.message')
|
||||
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
|
||||
group_bird_id = self.mail_group.create(cr, uid, {'name': 'Bird', 'description': 'Bird resistance'}, {'mail_create_nolog': True})
|
||||
group_bird = self.mail_group.browse(cr, uid, group_bird_id)
|
||||
|
||||
# Mail data
|
||||
# --------------------------------------------------
|
||||
# Data creation
|
||||
# --------------------------------------------------
|
||||
# 0 - Update existing users-partners
|
||||
self.res_users.write(cr, uid, [uid], {'email': 'a@a'})
|
||||
self.res_users.write(cr, uid, [self.user_raoul_id], {'email': 'r@r'})
|
||||
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
|
||||
# 2 - Carine Poilvache, with email, should receive emails for emails
|
||||
p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
|
||||
# 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
|
||||
p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'email': 'd@d', 'notification_email_send': 'all'})
|
||||
# 4 - Create a Bird mail.group, that will be used to test mass mailing
|
||||
group_bird_id = self.mail_group.create(cr, uid,
|
||||
{
|
||||
'name': 'Bird',
|
||||
'description': 'Bird resistance',
|
||||
}, context={'mail_create_nolog': True})
|
||||
group_bird = self.mail_group.browse(cr, uid, group_bird_id)
|
||||
# 5 - Mail data
|
||||
_subject = 'Pigs'
|
||||
_body = 'Pigs <b>rule</b>'
|
||||
_reply_subject = 'Re: Pigs'
|
||||
_reply_subject = 'Re: %s' % _subject
|
||||
_attachments = [
|
||||
{'name': 'First', 'datas_fname': 'first.txt', 'datas': 'My first attachment'.encode('base64')},
|
||||
{'name': 'Second', 'datas_fname': 'second.txt', 'datas': 'My second attachment'.encode('base64')}
|
||||
]
|
||||
_attachments_test = [('first.txt', 'My first attachment'), ('second.txt', 'My second attachment')]
|
||||
|
||||
# 1 - Bert Tartopoils, with email, should receive emails for comments and emails
|
||||
p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'})
|
||||
# 2 - Carine Poilvache, with email, should never receive emails
|
||||
p_c_id = self.res_partner.create(cr, uid, {'name': 'Carine Poilvache', 'email': 'c@c', 'notification_email_send': 'email'})
|
||||
# 3 - Dédé Grosbedon, without email, to test email verification; should receive emails for every message
|
||||
p_d_id = self.res_partner.create(cr, uid, {'name': 'Dédé Grosbedon', 'email': 'd@d', 'notification_email_send': 'all'})
|
||||
|
||||
# Subscribe #1
|
||||
# 6 - Subscribe Bert to Pigs
|
||||
group_pigs.message_subscribe([p_b_id])
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE1: comment on group_pigs
|
||||
# ----------------------------------------
|
||||
# --------------------------------------------------
|
||||
# CASE1: wizard + partners + context keys
|
||||
# --------------------------------------------------
|
||||
|
||||
# 1. Comment group_pigs with body_text and subject
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': _subject, 'body': _body, 'partner_ids': [(4, p_c_id), (4, p_d_id)]},
|
||||
{'default_composition_mode': 'comment', 'default_model': 'mail.group', 'default_res_id': self.group_pigs_id,
|
||||
'default_content_subtype': 'plaintext'})
|
||||
# Do: Raoul wizard-composes on Pigs with auto-follow for partners, not for author
|
||||
compose_id = mail_compose.create(cr, user_raoul.id,
|
||||
{
|
||||
'subject': _subject,
|
||||
'body': _body,
|
||||
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
||||
}, context={
|
||||
'default_composition_mode': 'comment',
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
# Test: mail.compose.message: composition_mode, model, res_id
|
||||
self.assertEqual(compose.composition_mode, 'comment', 'mail.compose.message incorrect composition_mode')
|
||||
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
|
||||
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
|
||||
|
||||
# 2. Post the comment, get created message
|
||||
mail_compose.send_mail(cr, uid, [compose_id], {'mail_post_autofollow': True})
|
||||
# Test: mail.compose.message: composition_mode, model, res_id
|
||||
self.assertEqual(compose.composition_mode, 'comment', 'compose wizard: mail.compose.message incorrect composition_mode')
|
||||
self.assertEqual(compose.model, 'mail.group', 'compose wizard: mail.compose.message incorrect model')
|
||||
self.assertEqual(compose.res_id, self.group_pigs_id, 'compose wizard: mail.compose.message incorrect res_id')
|
||||
|
||||
# Do: Post the comment
|
||||
mail_compose.send_mail(cr, user_raoul.id, [compose_id], {'mail_post_autofollow': True, 'mail_create_nosubscribe': True})
|
||||
group_pigs.refresh()
|
||||
message = group_pigs.message_ids[0]
|
||||
# Test: mail.message: subject, body inside pre
|
||||
self.assertEqual(message.subject, _subject, 'mail.message incorrect subject')
|
||||
self.assertEqual(message.body, '<p>%s</p>' % _body, 'mail.message incorrect body')
|
||||
# Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
|
||||
|
||||
# Test: mail.group: followers (c and d added by auto follow key; raoul not added by nosubscribe key)
|
||||
pigs_pids = [p.id for p in group_pigs.message_follower_ids]
|
||||
test_pids = [self.partner_admin_id, p_b_id, p_c_id, p_d_id]
|
||||
self.assertEqual(set(pigs_pids), set(test_pids),
|
||||
'compose wizard: mail_post_autofollow and mail_create_nosubscribe context keys not correctly taken into account')
|
||||
|
||||
# Test: mail.message: subject, body inside p
|
||||
self.assertEqual(message.subject, _subject, 'compose wizard: mail.message incorrect subject')
|
||||
self.assertEqual(message.body, '<p>%s</p>' % _body, 'compose wizard: mail.message incorrect body')
|
||||
# Test: mail.message: notified_partner_ids = admin + bert (followers) + c + d (recipients)
|
||||
msg_pids = [partner.id for partner in message.notified_partner_ids]
|
||||
test_pids = [p_b_id, p_c_id, p_d_id]
|
||||
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.id)])
|
||||
self.assertEqual(len(notif_ids), 3, 'mail.message: too much notifications created')
|
||||
self.assertEqual(set(msg_pids), set(test_pids), 'mail.message notified_partner_ids incorrect')
|
||||
test_pids = [self.partner_admin_id, p_b_id, p_c_id, p_d_id]
|
||||
self.assertEqual(set(msg_pids), set(test_pids),
|
||||
'compose wizard: mail.message notified_partner_ids incorrect')
|
||||
|
||||
# ----------------------------------------
|
||||
# CASE2: reply to last comment with attachments
|
||||
# ----------------------------------------
|
||||
# --------------------------------------------------
|
||||
# CASE2: reply + attachments
|
||||
# --------------------------------------------------
|
||||
|
||||
# 1. Update last comment subject, reply with attachments
|
||||
message.write({'subject': _subject})
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]},
|
||||
{'default_composition_mode': 'reply', 'default_model': 'mail.thread', 'default_res_id': self.group_pigs_id, 'default_parent_id': message.id})
|
||||
# Do: Reply with attachments
|
||||
compose_id = mail_compose.create(cr, user_raoul.id,
|
||||
{
|
||||
'attachment_ids': [(0, 0, _attachments[0]), (0, 0, _attachments[1])]
|
||||
}, context={
|
||||
'default_composition_mode': 'reply',
|
||||
'default_model': 'mail.thread',
|
||||
'default_res_id': self.group_pigs_id,
|
||||
'default_parent_id': message.id
|
||||
})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
# Test: model, res_id, parent_id
|
||||
self.assertEqual(compose.model, 'mail.group', 'mail.compose.message incorrect model')
|
||||
self.assertEqual(compose.res_id, self.group_pigs_id, 'mail.compose.message incorrect res_id')
|
||||
self.assertEqual(compose.parent_id.id, message.id, 'mail.compose.message incorrect parent_id')
|
||||
# Test: mail.message: subject as Re:.., body in html, parent_id
|
||||
self.assertEqual(compose.subject, _reply_subject, 'mail.message incorrect subject')
|
||||
# self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
|
||||
self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'mail.message parent_id incorrect')
|
||||
# Test: mail.message: attachments
|
||||
|
||||
# Test: mail.compose.message: model, res_id, parent_id
|
||||
self.assertEqual(compose.model, 'mail.group', 'compose wizard: mail.compose.message incorrect model')
|
||||
self.assertEqual(compose.res_id, self.group_pigs_id, 'compose wizard: mail.compose.message incorrect res_id')
|
||||
self.assertEqual(compose.parent_id.id, message.id, 'compose wizard: mail.compose.message incorrect parent_id')
|
||||
|
||||
# Test: mail.compose.message: subject as Re:.., body, parent_id
|
||||
self.assertEqual(compose.subject, _reply_subject, 'compose wizard: mail.compose.message incorrect subject')
|
||||
self.assertFalse(compose.body, 'compose wizard: mail.compose.message body should not contain parent message body')
|
||||
self.assertEqual(compose.parent_id and compose.parent_id.id, message.id, 'compose wizard: mail.compose.message parent_id incorrect')
|
||||
# Test: mail.compose.message: attachments
|
||||
for attach in compose.attachment_ids:
|
||||
self.assertIn((attach.datas_fname, attach.datas.decode('base64')), _attachments_test, 'mail.message attachment name / data incorrect')
|
||||
self.assertIn((attach.datas_fname, attach.datas.decode('base64')), _attachments_test,
|
||||
'compose wizard: mail.message attachment name / data incorrect')
|
||||
|
||||
# ----------------------------------------
|
||||
# --------------------------------------------------
|
||||
# CASE3: mass_mail on Pigs and Bird
|
||||
# ----------------------------------------
|
||||
# --------------------------------------------------
|
||||
|
||||
# 1. mass_mail on pigs and bird
|
||||
compose_id = mail_compose.create(cr, uid,
|
||||
{'subject': _subject, 'body': '${object.description}'},
|
||||
{'default_composition_mode': 'mass_mail', 'default_model': 'mail.group', 'default_res_id': False, 'default_notify': True,
|
||||
'active_ids': [self.group_pigs_id, group_bird_id]})
|
||||
# Do: Compose in mass_mail_mode on pigs and bird
|
||||
compose_id = mail_compose.create(cr, user_raoul.id,
|
||||
{
|
||||
'subject': _subject,
|
||||
'body': '${object.description}',
|
||||
'partner_ids': [(4, p_c_id), (4, p_d_id)],
|
||||
}, context={
|
||||
'default_composition_mode': 'mass_mail',
|
||||
'default_model': 'mail.group',
|
||||
'default_res_id': False,
|
||||
'active_ids': [self.group_pigs_id, group_bird_id],
|
||||
})
|
||||
compose = mail_compose.browse(cr, uid, compose_id)
|
||||
|
||||
# 2. Post the comment, get created message for each group
|
||||
mail_compose.send_mail(cr, uid, [compose_id],
|
||||
context={'default_res_id': -1, 'active_ids': [self.group_pigs_id, group_bird_id]})
|
||||
# D: Post the comment, get created message for each group
|
||||
mail_compose.send_mail(cr, user_raoul.id, [compose_id], context={
|
||||
'default_res_id': -1,
|
||||
'active_ids': [self.group_pigs_id, group_bird_id]
|
||||
})
|
||||
group_pigs.refresh()
|
||||
group_bird.refresh()
|
||||
message1 = group_pigs.message_ids[0]
|
||||
message2 = group_bird.message_ids[0]
|
||||
|
||||
# Test: Pigs and Bird did receive their message
|
||||
test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
|
||||
self.assertIn(message1.id, test_msg_ids, 'Pigs did not receive its mass mailing message')
|
||||
self.assertIn(message2.id, test_msg_ids, 'Bird did not receive its mass mailing message')
|
||||
# Test: mail.message: subject, body
|
||||
self.assertEqual(message1.subject, _subject, 'mail.message subject incorrect')
|
||||
self.assertEqual(message1.body, '<p>%s</p>' % group_pigs.description, 'mail.message body incorrect')
|
||||
self.assertEqual(message2.subject, _subject, 'mail.message subject incorrect')
|
||||
self.assertEqual(message2.body, '<p>%s</p>' % group_bird.description, 'mail.message body incorrect')
|
||||
self.assertIn(message1.id, test_msg_ids, 'compose wizard: Pigs did not receive its mass mailing message')
|
||||
self.assertIn(message2.id, test_msg_ids, 'compose wizard: Bird did not receive its mass mailing message')
|
||||
|
||||
# Test: mail.message: subject, body, subtype, notified partners (nobody + specific recipients)
|
||||
self.assertEqual(message1.subject, _subject,
|
||||
'compose wizard: message_post: mail.message in mass mail subject incorrect')
|
||||
self.assertEqual(message1.body, '<p>%s</p>' % group_pigs.description,
|
||||
'compose wizard: message_post: mail.message in mass mail body incorrect')
|
||||
self.assertEqual(set([p.id for p in message1.notified_partner_ids]), set([p_c_id, p_d_id]),
|
||||
'compose wizard: message_post: mail.message in mass mail incorrect notified partners')
|
||||
self.assertEqual(message2.subject, _subject,
|
||||
'compose wizard: message_post: mail.message in mass mail subject incorrect')
|
||||
self.assertEqual(message2.body, '<p>%s</p>' % group_bird.description,
|
||||
'compose wizard: message_post: mail.message in mass mail body incorrect')
|
||||
self.assertEqual(set([p.id for p in message2.notified_partner_ids]), set([p_c_id, p_d_id]),
|
||||
'compose wizard: message_post: mail.message in mass mail incorrect notified partners')
|
||||
|
||||
# Test: mail.group followers: author not added as follower in mass mail mode
|
||||
pigs_pids = [p.id for p in group_pigs.message_follower_ids]
|
||||
test_pids = [self.partner_admin_id, p_b_id, p_c_id, p_d_id]
|
||||
self.assertEqual(set(pigs_pids), set(test_pids),
|
||||
'compose wizard: mail_post_autofollow and mail_create_nosubscribe context keys not correctly taken into account')
|
||||
bird_pids = [p.id for p in group_bird.message_follower_ids]
|
||||
test_pids = [self.partner_admin_id]
|
||||
self.assertEqual(set(bird_pids), set(test_pids),
|
||||
'compose wizard: mail_post_autofollow and mail_create_nosubscribe context keys not correctly taken into account')
|
||||
|
||||
def test_30_needaction(self):
|
||||
""" Tests for mail.message needaction. """
|
||||
|
|
|
@ -0,0 +1,330 @@
|
|||
# -*- 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.test_mail_base import TestMailBase
|
||||
|
||||
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
||||
To: {to}
|
||||
Received: by mail1.openerp.com (Postfix, from userid 10002)
|
||||
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
|
||||
From: {email_from}
|
||||
Subject: {subject}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: multipart/alternative;
|
||||
boundary="----=_Part_4200734_24778174.1344608186754"
|
||||
Date: Fri, 10 Aug 2012 14:16:26 +0000
|
||||
Message-ID: {msg_id}
|
||||
{extra}
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: text/plain; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
Please call me as soon as possible this afternoon!
|
||||
|
||||
--
|
||||
Sylvie
|
||||
------=_Part_4200734_24778174.1344608186754
|
||||
Content-Type: text/html; charset=utf-8
|
||||
Content-Transfer-Encoding: quoted-printable
|
||||
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
|
||||
<html>
|
||||
<head>=20
|
||||
<meta http-equiv=3D"Content-Type" content=3D"text/html; charset=3Dutf-8" />
|
||||
</head>=20
|
||||
<body style=3D"margin: 0; padding: 0; background: #ffffff;-webkit-text-size-adjust: 100%;">=20
|
||||
|
||||
<p>Please call me as soon as possible this afternoon!</p>
|
||||
|
||||
<p>--<br/>
|
||||
Sylvie
|
||||
<p>
|
||||
</body>
|
||||
</html>
|
||||
------=_Part_4200734_24778174.1344608186754--
|
||||
"""
|
||||
|
||||
MAIL_TEMPLATE_PLAINTEXT = """Return-Path: <whatever-2a840@postmaster.twitter.com>
|
||||
To: {to}
|
||||
Received: by mail1.openerp.com (Postfix, from userid 10002)
|
||||
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
|
||||
From: Sylvie Lelitre <sylvie.lelitre@agrolait.com>
|
||||
Subject: {subject}
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain
|
||||
Date: Fri, 10 Aug 2012 14:16:26 +0000
|
||||
Message-ID: {msg_id}
|
||||
{extra}
|
||||
|
||||
Please call me as soon as possible this afternoon!
|
||||
|
||||
--
|
||||
Sylvie
|
||||
"""
|
||||
|
||||
|
||||
class TestMailgateway(TestMailBase):
|
||||
|
||||
def test_00_message_process(self):
|
||||
""" Testing incoming emails processing. """
|
||||
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
|
||||
|
||||
def format_and_process(template, to='groups@example.com, other@gmail.com', subject='Frogs',
|
||||
extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>',
|
||||
model=None):
|
||||
self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', subject)]), [])
|
||||
mail = template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
|
||||
self.mail_thread.message_process(cr, uid, model, mail)
|
||||
return self.mail_group.search(cr, uid, [('name', '=', subject)])
|
||||
|
||||
# --------------------------------------------------
|
||||
# Data creation
|
||||
# --------------------------------------------------
|
||||
|
||||
# groups@.. will cause the creation of new mail groups
|
||||
self.mail_group_model_id = self.ir_model.search(cr, uid, [('model', '=', 'mail.group')])[0]
|
||||
alias_id = self.mail_alias.create(cr, uid, {
|
||||
'alias_name': 'groups',
|
||||
'alias_user_id': False,
|
||||
'alias_model_id': self.mail_group_model_id})
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test1: new record creation
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: incoming mail from an unknown partner on an alias creates a new mail_group "frogs"
|
||||
self._init_mock_build_email()
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
# Test: one group created by mailgateway administrator
|
||||
self.assertTrue(len(frog_groups) == 1)
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one message that is the incoming email
|
||||
self.assertEqual(len(frog_group.message_ids), 1,
|
||||
'message_process: newly created group should have the incoming email in message_ids')
|
||||
msg = frog_group.message_ids[0]
|
||||
self.assertEqual('Frogs', msg.subject,
|
||||
'message_process: newly created group should have the incoming email as first message')
|
||||
self.assertIn('Please call me as soon as possible this afternoon!', msg.body,
|
||||
'message_process: newly created group should have the incoming email as first message')
|
||||
self.assertEqual('email', msg.type,
|
||||
'message_process: newly created group should have an email as first message')
|
||||
self.assertEqual('Discussions', msg.subtype_id.name,
|
||||
'message_process: newly created group should not have a log first message but an email')
|
||||
# Test: message: unknown email address -> message has email_from, not author_id
|
||||
self.assertFalse(msg.author_id,
|
||||
'message_process: message on created group should not have an author_id')
|
||||
self.assertIn('test.sylvie.lelitre@agrolait.com', msg.email_from,
|
||||
'message_process: message on created group should have an email_from')
|
||||
# Test: followers: nobody
|
||||
self.assertEqual(len(frog_group.message_follower_ids), 0, 'message_process: newly create group should not have any follower')
|
||||
# Test: sent emails: no-one
|
||||
self.assertEqual(len(sent_emails), 0,
|
||||
'message_process: should create emails without any follower added')
|
||||
# Data: unlink group
|
||||
frog_group.unlink()
|
||||
|
||||
# Do: incoming email from a known partner on an alias with known recipients, alias is owned by user that can create a group
|
||||
self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': self.user_raoul_id})
|
||||
p1id = self.res_partner.create(cr, uid, {'name': 'Sylvie Lelitre', 'email': 'test.sylvie.lelitre@agrolait.com'})
|
||||
p2id = self.res_partner.create(cr, uid, {'name': 'Other Poilvache', 'email': 'other@gmail.com'})
|
||||
self._init_mock_build_email()
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
|
||||
sent_emails = self._build_email_kwargs_list
|
||||
# Test: one group created by raoul
|
||||
self.assertTrue(len(frog_groups) == 1)
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one message that is the incoming email
|
||||
self.assertEqual(len(frog_group.message_ids), 1,
|
||||
'message_process: newly created group should have the incoming email in message_ids')
|
||||
msg = frog_group.message_ids[0]
|
||||
# Test: message: unknown email address -> message has email_from, not author_id
|
||||
self.assertEqual(p1id, msg.author_id.id,
|
||||
'message_process: message on created group should have Sylvie as author_id')
|
||||
self.assertIn('Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>', msg.email_from,
|
||||
'message_process: message on created group should have have an email_from')
|
||||
# Test: author (not recipient and not raoul (as alias owner)) added as follower
|
||||
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
|
||||
self.assertEqual(frog_follower_ids, set([p1id]),
|
||||
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
|
||||
# Test: sent emails: no-one, no bounce effet
|
||||
self.assertEqual(len(sent_emails), 0,
|
||||
'message_process: should not bounce incoming emails')
|
||||
# Data: unlink group
|
||||
frog_group.unlink()
|
||||
|
||||
# Do: incoming email from a known partner that is also an user that can create a mail.group
|
||||
self.res_users.create(cr, uid, {'partner_id': p1id, 'login': 'sylvie', 'groups_id': [(6, 0, [self.group_employee_id])]})
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='groups@example.com, other@gmail.com')
|
||||
# Test: one group created by Sylvie
|
||||
self.assertTrue(len(frog_groups) == 1)
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one message that is the incoming email
|
||||
self.assertEqual(len(frog_group.message_ids), 1,
|
||||
'message_process: newly created group should have the incoming email in message_ids')
|
||||
# Test: author (and not recipient) added as follower
|
||||
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
|
||||
self.assertEqual(frog_follower_ids, set([p1id]),
|
||||
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
|
||||
# Test: sent emails: no-one, no bounce effet
|
||||
self.assertEqual(len(sent_emails), 0,
|
||||
'message_process: should not bounce incoming emails')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test2: discussion update
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: even with a wrong destination, a reply should end up in the correct thread
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other@gmail.com',
|
||||
to='erroneous@example.com>', subject='Re: news',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
# Test: no group 'Re: news' created, still only 1 Frogs group
|
||||
self.assertEqual(len(frog_groups), 0,
|
||||
'message_process: reply on Frogs should not have created a new group with new subject')
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
self.assertEqual(len(frog_groups), 1,
|
||||
'message_process: reply on Frogs should not have created a duplicate group with old subject')
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: one new message
|
||||
self.assertTrue(len(frog_group.message_ids) == 2, 'message_process: group should contain 2 messages after reply')
|
||||
# Test: author (and not recipient) added as follower
|
||||
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
|
||||
self.assertEqual(frog_follower_ids, set([p1id, p2id]),
|
||||
'message_process: after reply, group should have 2 followers')
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test3: email_from and partner finding
|
||||
# --------------------------------------------------
|
||||
|
||||
# Data: extra partner with Raoul's email -> test the 'better author finding'
|
||||
extra_partner_id = self.res_partner.create(cr, uid, {'name': 'A-Raoul', 'email': 'test_raoul@email.com'})
|
||||
# extra_user_id = self.res_users.create(cr, uid, {'name': 'B-Raoul', 'email': self.user_raoul.email})
|
||||
# extra_user_pid = self.res_users.browse(cr, uid, extra_user_id).partner_id.id
|
||||
|
||||
# Do: post a new message, with a known partner -> duplicate emails -> partner
|
||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
to='erroneous@example.com>', subject='Re: news (2)',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: author is A-Raoul (only existing)
|
||||
self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id,
|
||||
'message_process: email_from -> author_id wrong')
|
||||
|
||||
# Do: post a new message, with a known partner -> duplicate emails -> user
|
||||
frog_group.message_unsubscribe([extra_partner_id])
|
||||
raoul_email = self.user_raoul.email
|
||||
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
|
||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
to='erroneous@example.com>', subject='Re: news (3)',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: author is Raoul (user), not A-Raoul
|
||||
self.assertEqual(frog_group.message_ids[0].author_id.id, self.partner_raoul_id,
|
||||
'message_process: email_from -> author_id wrong')
|
||||
|
||||
# Do: post a new message, with a known partner -> duplicate emails -> partner because is follower
|
||||
frog_group.message_unsubscribe([self.partner_raoul_id])
|
||||
frog_group.message_subscribe([extra_partner_id])
|
||||
raoul_email = self.user_raoul.email
|
||||
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
|
||||
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
|
||||
to='erroneous@example.com>', subject='Re: news (3)',
|
||||
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
|
||||
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
# Test: author is Raoul (user), not A-Raoul
|
||||
self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id,
|
||||
'message_process: email_from -> author_id wrong')
|
||||
|
||||
self.res_users.write(cr, uid, self.user_raoul_id, {'email': raoul_email})
|
||||
|
||||
# --------------------------------------------------
|
||||
# Test4: misc gateway features
|
||||
# --------------------------------------------------
|
||||
|
||||
# Do: incoming email with model that does not accepts incoming emails must raise
|
||||
self.assertRaises(AssertionError,
|
||||
format_and_process,
|
||||
MAIL_TEMPLATE, to='noone@example.com', subject='spam', extra='', model='res.country')
|
||||
|
||||
# Do: incoming email without model and without alias must raise
|
||||
self.assertRaises(AssertionError,
|
||||
format_and_process,
|
||||
MAIL_TEMPLATE, to='noone@example.com', subject='spam', extra='')
|
||||
|
||||
# Do: incoming email with model that accepting incoming emails as fallback
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE, to='noone@example.com', subject='Spammy', extra='', model='mail.group')
|
||||
self.assertEqual(len(frog_groups), 1,
|
||||
'message_process: erroneous email but with a fallback model should have created a new mail.group')
|
||||
|
||||
# Do: incoming email in plaintext should be stored as html
|
||||
frog_groups = format_and_process(MAIL_TEMPLATE_PLAINTEXT, to='groups@example.com', subject='Frogs Return', extra='', msg_id='<deadcafe.1337@smtp.agrolait.com>')
|
||||
# Test: one group created with one message
|
||||
self.assertTrue(len(frog_groups) == 1)
|
||||
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
|
||||
msg = frog_group.message_ids[0]
|
||||
# Test: plain text content should be wrapped and stored as html
|
||||
self.assertEqual(msg.body, '<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>',
|
||||
'message_process: plaintext incoming email incorrectly parsed')
|
||||
|
||||
def test_10_thread_parent_resolution(self):
|
||||
""" Testing parent/child relationships are correctly established when processing incoming mails """
|
||||
cr, uid = self.cr, self.uid
|
||||
|
||||
def format(template, to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
|
||||
extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
|
||||
msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
|
||||
return template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
|
||||
|
||||
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
|
||||
msg1 = group_pigs.message_post(body='My Body', subject='1')
|
||||
msg2 = group_pigs.message_post(body='My Body', subject='2')
|
||||
msg1, msg2 = self.mail_message.browse(cr, uid, [msg1, msg2])
|
||||
self.assertTrue(msg1.message_id, "message_process: new message should have a proper message_id")
|
||||
|
||||
# Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
|
||||
# 0. Direct alias match
|
||||
reply_msg1 = format(MAIL_TEMPLATE, to='Pretty Pigs <group+pigs@example.com>', extra='In-Reply-To: %s' % msg1.message_id)
|
||||
self.mail_group.message_process(cr, uid, None, reply_msg1)
|
||||
|
||||
# 1. In-Reply-To header
|
||||
reply_msg2 = format(MAIL_TEMPLATE, to='erroneous@example.com', extra='In-Reply-To: %s' % msg1.message_id)
|
||||
self.mail_group.message_process(cr, uid, None, reply_msg2)
|
||||
|
||||
# 2. References header
|
||||
reply_msg3 = format(MAIL_TEMPLATE, to='erroneous@example.com', extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id)
|
||||
self.mail_group.message_process(cr, uid, None, reply_msg3)
|
||||
|
||||
# 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, but not to mail (not in msg1.child_ids)
|
||||
reply_msg4 = format(MAIL_TEMPLATE, to='erroneous@example.com', extra='', subject='Re: [%s] 1' % self.group_pigs_id)
|
||||
self.mail_group.message_process(cr, uid, 'mail.group', reply_msg4)
|
||||
|
||||
group_pigs.refresh()
|
||||
msg1.refresh()
|
||||
self.assertEqual(6, len(group_pigs.message_ids), 'message_process: group should contain 6 messages')
|
||||
self.assertEqual(3, len(msg1.child_ids), 'message_process: msg1 should have 3 children now')
|
||||
|
||||
def test_20_private_discussion(self):
|
||||
""" Testing private discussion between partners. """
|
||||
pass
|
|
@ -23,6 +23,7 @@ import base64
|
|||
import re
|
||||
from openerp import tools
|
||||
|
||||
from openerp import SUPERUSER_ID
|
||||
from openerp.osv import osv
|
||||
from openerp.osv import fields
|
||||
from openerp.tools.safe_eval import safe_eval as eval
|
||||
|
@ -136,6 +137,29 @@ class mail_compose_message(osv.TransientModel):
|
|||
'same_thread': lambda self, cr, uid, ctx={}: True,
|
||||
}
|
||||
|
||||
def check_access_rule(self, cr, uid, ids, operation, context=None):
|
||||
""" Access rules of mail.compose.message:
|
||||
- create: if
|
||||
- model, no res_id, I create a message in mass mail mode
|
||||
- then: fall back on mail.message acces rules
|
||||
"""
|
||||
if isinstance(ids, (int, long)):
|
||||
ids = [ids]
|
||||
|
||||
# Author condition (CREATE (mass_mail))
|
||||
if operation == 'create' and uid != SUPERUSER_ID:
|
||||
# read mail_compose_message.ids to have their values
|
||||
message_values = {}
|
||||
cr.execute('SELECT DISTINCT id, model, res_id FROM "%s" WHERE id = ANY (%%s) AND res_id = 0' % self._table, (ids,))
|
||||
for id, rmod, rid in cr.fetchall():
|
||||
message_values[id] = {'model': rmod, 'res_id': rid}
|
||||
# remove from the set to check the ids that mail_compose_message accepts
|
||||
author_ids = [mid for mid, message in message_values.iteritems()
|
||||
if message.get('model') and not message.get('res_id')]
|
||||
ids = list(set(ids) - set(author_ids))
|
||||
|
||||
return super(mail_compose_message, self).check_access_rule(cr, uid, ids, operation, context=context)
|
||||
|
||||
def _notify(self, cr, uid, newid, context=None):
|
||||
""" Override specific notify method of mail.message, because we do
|
||||
not want that feature in the wizard. """
|
||||
|
@ -244,8 +268,12 @@ class mail_compose_message(osv.TransientModel):
|
|||
self.pool.get('mail.mail').create(cr, uid, post_values, context=context)
|
||||
else:
|
||||
subtype = 'mail.mt_comment'
|
||||
if is_log or (mass_mail_mode and not wizard.notify):
|
||||
if is_log: # log a note: subtype is False
|
||||
subtype = False
|
||||
elif mass_mail_mode: # mass mail: is a log pushed to recipients unless specified, author not added
|
||||
if not wizard.notify:
|
||||
subtype = False
|
||||
context = dict(context, mail_create_nosubscribe=True) # add context key to avoid subscribing the author
|
||||
msg_id = active_model_pool.message_post(cr, uid, [res_id], type='comment', subtype=subtype, context=context, **post_values)
|
||||
# mass_mailing, post without notify: notify specific partners
|
||||
if mass_mail_mode and not wizard.notify and post_values['partner_ids']:
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
I'm Opening that Invoice which is created for "Seagate".
|
||||
-
|
||||
!python {model: res.partner}: |
|
||||
from openerp.tools.translate import _
|
||||
invoice_pool = self.pool.get('account.invoice')
|
||||
partner_pool = self.pool.get('res.partner')
|
||||
membership_line_pool = self.pool.get('membership.membership_line')
|
||||
|
@ -39,7 +38,7 @@
|
|||
|
||||
membership_line_ids = membership_line_pool.search(cr, uid, [('membership_id','=',ref('product_product_membershipproduct0')),('partner','=',ref('base.res_partner_19'))])
|
||||
membership_lines = membership_line_pool.browse(cr, uid, membership_line_ids)
|
||||
assert membership_lines, _('Membership is not registrated.')
|
||||
assert membership_lines, 'Membership is not registrated.'
|
||||
membership_line = membership_lines[0]
|
||||
invoice_pool.signal_invoice_open(cr, uid, [membership_line.account_invoice_id.id])
|
||||
|
||||
|
@ -105,7 +104,6 @@
|
|||
I'm doing to make credit note of invoice which is paid by "Seagate" to cancel membership.
|
||||
-
|
||||
!python {model: account.invoice}: |
|
||||
from openerp.tools.translate import _
|
||||
invoice_pool = self.pool.get('account.invoice')
|
||||
partner_pool = self.pool.get('res.partner')
|
||||
membership_line_pool = self.pool.get('membership.membership_line')
|
||||
|
@ -114,7 +112,7 @@
|
|||
|
||||
membership_line_ids = membership_line_pool.search(cr, uid, [('membership_id','=',ref('product_product_membershipproduct0')),('partner','=',ref('base.res_partner_19'))])
|
||||
membership_lines = membership_line_pool.browse(cr, uid, membership_line_ids)
|
||||
assert membership_lines, _('Membership is not registrated.')
|
||||
assert membership_lines, 'Membership is not registrated.'
|
||||
membership_line = membership_lines[0]
|
||||
refund_id = invoice_refund_pool.create(cr, uid, {'description': 'Refund of Membership', 'filter_refund': 'refund'}, {'active_id': membership_line.account_invoice_id.id})
|
||||
invoice_refund_pool.invoice_refund(cr, uid, [refund_id], {'active_id': membership_line.account_invoice_id.id, 'active_ids': [membership_line.account_invoice_id.id]})
|
||||
|
|
|
@ -701,11 +701,6 @@
|
|||
<field name="prodlot_id" context="{'product_id': product_id}" groups="stock.group_production_lot"/>
|
||||
<field name="state" invisible="1"/>
|
||||
<field name="scrapped" invisible="1"/>
|
||||
<button
|
||||
name="%(stock.move_scrap)d"
|
||||
string="Scrap Products" type="action"
|
||||
icon="terp-gtk-jump-to-ltr"
|
||||
states="done,cancel"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
|
@ -724,10 +719,6 @@
|
|||
string="Partial"
|
||||
type="action" states="confirmed,assigned"
|
||||
icon="gtk-justify-fill"/>
|
||||
<button name="%(stock.move_scrap)d"
|
||||
string="Scrap Products" type="action"
|
||||
icon="terp-gtk-jump-to-ltr" context="{'scrap': True}"
|
||||
states="draft,waiting,confirmed,assigned"/>
|
||||
</tree>
|
||||
</field>
|
||||
</group>
|
||||
|
|
|
@ -33,4 +33,4 @@
|
|||
-
|
||||
!python {model: mrp.repair}: |
|
||||
repair_id = self.browse(cr, uid, [ref('mrp_repair_rmrp0')], context=context)[0]
|
||||
assert repair_id.invoice_id.id, _("No invoice exists for this repair order")
|
||||
assert repair_id.invoice_id.id, "No invoice exists for this repair order"
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
-
|
||||
!python {model: mrp.repair}: |
|
||||
repair_id = self.browse(cr, uid, [ref('mrp_repair_rmrp2')], context=context)[0]
|
||||
assert repair_id.invoice_id.id, _("No invoice exists for this repair order.")
|
||||
assert repair_id.invoice_id.id, "No invoice exists for this repair order."
|
||||
-
|
||||
I start the Repairing process by clicking on "Start Repair" button.
|
||||
-
|
||||
|
|
|
@ -23,5 +23,5 @@
|
|||
-
|
||||
!python {model: mrp.repair}: |
|
||||
repair_id = self.browse(cr, uid, [ref('mrp_repair_rmrp1')], context=context)[0]
|
||||
assert not repair_id.invoice_id.id, _("Invoice should not be exists for this repair order")
|
||||
assert not repair_id.invoice_id.id, "Invoice should not be exists for this repair order"
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ class pos_config(osv.osv):
|
|||
'sequence_id' : False,
|
||||
}
|
||||
d.update(default)
|
||||
return super(pos_order, self).copy(cr, uid, id, d, context=context)
|
||||
return super(pos_config, self).copy(cr, uid, id, d, context=context)
|
||||
|
||||
|
||||
def name_get(self, cr, uid, ids, context=None):
|
||||
|
|
|
@ -31,6 +31,10 @@ class PosBoxIn(PosBox):
|
|||
_inherit = 'cash.box.in'
|
||||
|
||||
def _compute_values_for_statement_line(self, cr, uid, box, record, context=None):
|
||||
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
values = super(PosBoxIn, self)._compute_values_for_statement_line(cr, uid, box, record, context=context)
|
||||
|
||||
active_model = context.get('active_model', False) or False
|
||||
|
|
|
@ -43,8 +43,12 @@ openerp.portal_anonymous = function(instance) {
|
|||
instance.web.Login.include({
|
||||
start: function() {
|
||||
var self = this;
|
||||
var anonymous_mode = (!self.session.session_is_valid() && !(self.params.token || self.params.login));
|
||||
if (anonymous_mode) {
|
||||
self.$el.hide();
|
||||
}
|
||||
return $.when(this._super()).then(function() {
|
||||
if (!self.session.session_is_valid() && !(self.params.token || self.params.login)) {
|
||||
if (anonymous_mode) {
|
||||
self.remember_credentials = false;
|
||||
// XXX get login/pass from server (via a rpc call) ?
|
||||
return self.do_login(self.selected_db, 'anonymous', 'anonymous');
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010-Today OpenERP S.A. (<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/>.
|
||||
#
|
||||
##############################################################################
|
|
@ -0,0 +1,42 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
##############################################################################
|
||||
#
|
||||
# OpenERP, Open Source Management Solution
|
||||
# Copyright (C) 2010-Today OpenERP S.A. (<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': 'Portal Project Long Term',
|
||||
'version': '1.0',
|
||||
'category': 'Tools',
|
||||
'complexity': 'easy',
|
||||
'description': """
|
||||
This module adds necessary security rules and access rights for project long term and portal.
|
||||
=============================================================================================
|
||||
""",
|
||||
'author': 'OpenERP SA',
|
||||
'depends': ['project_long_term', 'portal'],
|
||||
'data': [
|
||||
'security/portal_security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
],
|
||||
'installable': True,
|
||||
'auto_install': True,
|
||||
'category': 'Hidden',
|
||||
}
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
|
@ -0,0 +1,2 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_issues,project_phase,project_long_term.model_project_phase,portal.group_portal,1,0,0,0
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<openerp>
|
||||
<data>
|
||||
|
||||
<record id="portal_project_long_term_rule" model="ir.rule">
|
||||
<field name="name">Portal Personal Long term project</field>
|
||||
<field ref="project_long_term.model_project_phase" name="model_id"/>
|
||||
<field name="domain_force">['|',('project_id.message_follower_ids','in', [user.partner_id.id]),('task_ids.message_follower_ids','in', [user.partner_id.id])]</field>
|
||||
<field name="groups" eval="[(4, ref('portal.group_portal'))]"/>
|
||||
<field eval="1" name="perm_unlink"/>
|
||||
<field eval="1" name="perm_write"/>
|
||||
<field eval="1" name="perm_read"/>
|
||||
<field eval="0" name="perm_create"/>
|
||||
</record>
|
||||
|
||||
</data>
|
||||
</openerp>
|
|
@ -0,0 +1,548 @@
|
|||
# Spanish (Mexico) 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:06+0000\n"
|
||||
"PO-Revision-Date: 2013-03-22 02:49+0000\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: Spanish (Mexico) <es_MX@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-03-22 04:57+0000\n"
|
||||
"X-Generator: Launchpad (build 16532)\n"
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.model,name:portal_sale.model_account_config_settings
|
||||
msgid "account.config.settings"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.actions.act_window,help:portal_sale.portal_action_invoices
|
||||
msgid "We haven't sent you any invoice."
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:email.template,report_name:portal_sale.email_template_edi_sale
|
||||
msgid ""
|
||||
"${(object.name or '').replace('/','_')}_${object.state == 'draft' and "
|
||||
"'draft' or ''}"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:res.groups,name:portal_sale.group_payment_options
|
||||
msgid "View Online Payment Options"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: field:account.config.settings,group_payment_options:0
|
||||
msgid "Show payment buttons to employees too"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:email.template,subject:portal_sale.email_template_edi_sale
|
||||
msgid ""
|
||||
"${object.company_id.name} ${object.state in ('draft', 'sent') and "
|
||||
"'Quotation' or 'Order'} (Ref ${object.name or 'n/a' })"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.actions.act_window,help:portal_sale.action_quotations_portal
|
||||
msgid "We haven't sent you any quotation."
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.ui.menu,name:portal_sale.portal_sales_orders
|
||||
msgid "Sales Orders"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:res.groups,comment:portal_sale.group_payment_options
|
||||
msgid ""
|
||||
"Members of this group see the online payment options\n"
|
||||
"on Sale Orders and Customer Invoices. These options are meant for customers "
|
||||
"who are accessing\n"
|
||||
"their documents through the portal."
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:email.template,body_html:portal_sale.email_template_edi_sale
|
||||
msgid ""
|
||||
"\n"
|
||||
"<div style=\"font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-"
|
||||
"serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, "
|
||||
"255, 255); \">\n"
|
||||
"\n"
|
||||
" <p>Hello ${object.partner_id.name},</p>\n"
|
||||
" \n"
|
||||
" <p>Here is your ${object.state in ('draft', 'sent') and 'quotation' or "
|
||||
"'order confirmation'} from ${object.company_id.name}: </p>\n"
|
||||
"\n"
|
||||
" <p style=\"border-left: 1px solid #8e0000; margin-left: 30px;\">\n"
|
||||
" <strong>REFERENCES</strong><br />\n"
|
||||
" Order number: <strong>${object.name}</strong><br />\n"
|
||||
" Order total: <strong>${object.amount_total} "
|
||||
"${object.pricelist_id.currency_id.name}</strong><br />\n"
|
||||
" Order date: ${object.date_order}<br />\n"
|
||||
" % if object.origin:\n"
|
||||
" Order reference: ${object.origin}<br />\n"
|
||||
" % endif\n"
|
||||
" % if object.client_order_ref:\n"
|
||||
" Your reference: ${object.client_order_ref}<br />\n"
|
||||
" % endif\n"
|
||||
" % if object.user_id:\n"
|
||||
" Your contact: <a href=\"mailto:${object.user_id.email or "
|
||||
"''}?subject=Order%20${object.name}\">${object.user_id.name}</a>\n"
|
||||
" % endif\n"
|
||||
" </p>\n"
|
||||
"\n"
|
||||
" <% set signup_url = object.get_signup_url() %>\n"
|
||||
" % if signup_url:\n"
|
||||
" <p>\n"
|
||||
" You can access this document and pay online via our Customer Portal:\n"
|
||||
" </p>\n"
|
||||
" <a style=\"display:block; width: 150px; height:20px; margin-left: "
|
||||
"120px; color: #DDD; font-family: 'Lucida Grande', Helvetica, Arial, sans-"
|
||||
"serif; font-size: 13px; font-weight: bold; text-align: center; text-"
|
||||
"decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; "
|
||||
"background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat "
|
||||
"no-repeat;\"\n"
|
||||
" href=\"${signup_url}\">View ${object.state in ('draft', 'sent') "
|
||||
"and 'Quotation' or 'Order'}</a>\n"
|
||||
" % endif\n"
|
||||
"\n"
|
||||
" % if object.paypal_url:\n"
|
||||
" <br/>\n"
|
||||
" <p>It is also possible to directly pay with Paypal:</p>\n"
|
||||
" <a style=\"margin-left: 120px;\" href=\"${object.paypal_url}\">\n"
|
||||
" <img class=\"oe_edi_paypal_button\" "
|
||||
"src=\"https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif\"/>\n"
|
||||
" </a>\n"
|
||||
" % endif\n"
|
||||
"\n"
|
||||
" <br/>\n"
|
||||
" <p>If you have any question, do not hesitate to contact us.</p>\n"
|
||||
" <p>Thank you for choosing ${object.company_id.name or 'us'}!</p>\n"
|
||||
" <br/>\n"
|
||||
" <br/>\n"
|
||||
" <div style=\"width: 375px; margin: 0px; padding: 0px; background-color: "
|
||||
"#8E0000; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; "
|
||||
"background-repeat: repeat no-repeat;\">\n"
|
||||
" <h3 style=\"margin: 0px; padding: 2px 14px; font-size: 12px; color: "
|
||||
"#DDD;\">\n"
|
||||
" <strong style=\"text-"
|
||||
"transform:uppercase;\">${object.company_id.name}</strong></h3>\n"
|
||||
" </div>\n"
|
||||
" <div style=\"width: 347px; margin: 0px; padding: 5px 14px; line-height: "
|
||||
"16px; background-color: #F2F2F2;\">\n"
|
||||
" <span style=\"color: #222; margin-bottom: 5px; display: block; \">\n"
|
||||
" % if object.company_id.street:\n"
|
||||
" ${object.company_id.street}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.street2:\n"
|
||||
" ${object.company_id.street2}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.city or object.company_id.zip:\n"
|
||||
" ${object.company_id.zip} ${object.company_id.city}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.country_id:\n"
|
||||
" ${object.company_id.state_id and ('%s, ' % "
|
||||
"object.company_id.state_id.name) or ''} ${object.company_id.country_id.name "
|
||||
"or ''}<br/>\n"
|
||||
" % endif\n"
|
||||
" </span>\n"
|
||||
" % if object.company_id.phone:\n"
|
||||
" <div style=\"margin-top: 0px; margin-right: 0px; margin-bottom: "
|
||||
"0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: "
|
||||
"0px; padding-left: 0px; \">\n"
|
||||
" Phone: ${object.company_id.phone}\n"
|
||||
" </div>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.website:\n"
|
||||
" <div>\n"
|
||||
" Web : <a "
|
||||
"href=\"${object.company_id.website}\">${object.company_id.website}</a>\n"
|
||||
" </div>\n"
|
||||
" % endif\n"
|
||||
" <p></p>\n"
|
||||
" </div>\n"
|
||||
"</div>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"<div style=\"font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-"
|
||||
"serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, "
|
||||
"255, 255); \">\n"
|
||||
"\n"
|
||||
" <p>Hola, buen día ${object.partner_id.name}:</p>\n"
|
||||
" \n"
|
||||
" <p>Esta es su ${object.state in ('draft', 'sent') and 'cotización' or "
|
||||
"'pedido'} de ${object.company_id.name}: </p>\n"
|
||||
"\n"
|
||||
" <p style=\"border-left: 1px solid #8e0000; margin-left: 30px;\">\n"
|
||||
" <strong>REFERENCIAS</strong><br />\n"
|
||||
" Número: <strong>${object.name}</strong><br />\n"
|
||||
" Total: <strong>${object.amount_total} "
|
||||
"${object.pricelist_id.currency_id.name}</strong><br />\n"
|
||||
" Fecha: ${object.date_order}<br />\n"
|
||||
" % if object.origin:\n"
|
||||
" Origen: ${object.origin}<br />\n"
|
||||
" % endif\n"
|
||||
" % if object.client_order_ref:\n"
|
||||
" Referencia de la orden de compra: "
|
||||
"${object.client_order_ref}<br />\n"
|
||||
" % endif\n"
|
||||
" % if object.user_id:\n"
|
||||
" Su contacto: <a href=\"mailto:${object.user_id.email or "
|
||||
"''}?subject=Order%20${object.name}\">${object.user_id.name}</a>\n"
|
||||
" % endif\n"
|
||||
" </p>\n"
|
||||
"\n"
|
||||
" <% set signup_url = object.get_signup_url() %>\n"
|
||||
" % if signup_url:\n"
|
||||
" <p>\n"
|
||||
" Usted puede acceder a este documento y pagarlo en linea vía nuestro "
|
||||
"portal del cliente:\n"
|
||||
" </p>\n"
|
||||
" <a style=\"display:block; width: 150px; height:20px; margin-left: "
|
||||
"120px; color: #DDD; font-family: 'Lucida Grande', Helvetica, Arial, sans-"
|
||||
"serif; font-size: 13px; font-weight: bold; text-align: center; text-"
|
||||
"decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; "
|
||||
"background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat "
|
||||
"no-repeat;\"\n"
|
||||
" href=\"${signup_url}\">Ver ${object.state in ('draft', 'sent') "
|
||||
"and 'Cotización' or 'Pedido'}</a>\n"
|
||||
" % endif\n"
|
||||
"\n"
|
||||
" % if object.paypal_url:\n"
|
||||
" <br/>\n"
|
||||
" <p>Tambien es posible pagarlo directamente con Paypal:</p>\n"
|
||||
" <a style=\"margin-left: 120px;\" href=\"${object.paypal_url}\">\n"
|
||||
" <img class=\"oe_edi_paypal_button\" "
|
||||
"src=\"https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif\"/>\n"
|
||||
" </a>\n"
|
||||
" % endif\n"
|
||||
"\n"
|
||||
" <br/>\n"
|
||||
" <p>Si tiene alguna pregunta no dude en contactarnos.</p>\n"
|
||||
" <p>¡Gracias por seleccionar a ${object.company_id.name or 'us'}!</p>\n"
|
||||
" <br/>\n"
|
||||
" <br/>\n"
|
||||
" <div style=\"width: 375px; margin: 0px; padding: 0px; background-color: "
|
||||
"#8E0000; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; "
|
||||
"background-repeat: repeat no-repeat;\">\n"
|
||||
" <h3 style=\"margin: 0px; padding: 2px 14px; font-size: 12px; color: "
|
||||
"#DDD;\">\n"
|
||||
" <strong style=\"text-"
|
||||
"transform:uppercase;\">${object.company_id.name}</strong></h3>\n"
|
||||
" </div>\n"
|
||||
" <div style=\"width: 347px; margin: 0px; padding: 5px 14px; line-height: "
|
||||
"16px; background-color: #F2F2F2;\">\n"
|
||||
" <span style=\"color: #222; margin-bottom: 5px; display: block; \">\n"
|
||||
" % if object.company_id.street:\n"
|
||||
" ${object.company_id.street}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.street2:\n"
|
||||
" ${object.company_id.street2}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.city or object.company_id.zip:\n"
|
||||
" ${object.company_id.zip} ${object.company_id.city}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.country_id:\n"
|
||||
" ${object.company_id.state_id and ('%s, ' % "
|
||||
"object.company_id.state_id.name) or ''} ${object.company_id.country_id.name "
|
||||
"or ''}<br/>\n"
|
||||
" % endif\n"
|
||||
" </span>\n"
|
||||
" % if object.company_id.phone:\n"
|
||||
" <div style=\"margin-top: 0px; margin-right: 0px; margin-bottom: "
|
||||
"0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: "
|
||||
"0px; padding-left: 0px; \">\n"
|
||||
" Teléfono: ${object.company_id.phone}\n"
|
||||
" </div>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.website:\n"
|
||||
" <div>\n"
|
||||
" Página web : <a "
|
||||
"href=\"${object.company_id.website}\">${object.company_id.website}</a>\n"
|
||||
" </div>\n"
|
||||
" % endif\n"
|
||||
" <p></p>\n"
|
||||
" </div>\n"
|
||||
"</div>\n"
|
||||
" "
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:email.template,report_name:portal_sale.email_template_edi_invoice
|
||||
msgid ""
|
||||
"Invoice_${(object.number or '').replace('/','_')}_${object.state == 'draft' "
|
||||
"and 'draft' or ''}"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:email.template,subject:portal_sale.email_template_edi_invoice
|
||||
msgid "${object.company_id.name} Invoice (Ref ${object.number or 'n/a' })"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.model,name:portal_sale.model_mail_mail
|
||||
msgid "Outgoing Mails"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.actions.act_window,name:portal_sale.action_quotations_portal
|
||||
#: model:ir.ui.menu,name:portal_sale.portal_quotations
|
||||
msgid "Quotations"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.model,name:portal_sale.model_sale_order
|
||||
msgid "Sales Order"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: field:account.invoice,portal_payment_options:0
|
||||
#: field:sale.order,portal_payment_options:0
|
||||
msgid "Portal Payment Options"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: help:account.config.settings,group_payment_options:0
|
||||
msgid ""
|
||||
"Show online payment options on Sale Orders and Customer Invoices to "
|
||||
"employees. If not checked, these options are only visible to portal users."
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.actions.act_window,name:portal_sale.portal_action_invoices
|
||||
#: model:ir.ui.menu,name:portal_sale.portal_invoices
|
||||
msgid "Invoices"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: view:account.config.settings:0
|
||||
msgid "Configure payment acquiring methods"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:email.template,body_html:portal_sale.email_template_edi_invoice
|
||||
msgid ""
|
||||
"\n"
|
||||
"<div style=\"font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-"
|
||||
"serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, "
|
||||
"255, 255); \">\n"
|
||||
"\n"
|
||||
" <p>Hello ${object.partner_id.name},</p>\n"
|
||||
"\n"
|
||||
" <p>A new invoice is available for you: </p>\n"
|
||||
" \n"
|
||||
" <p style=\"border-left: 1px solid #8e0000; margin-left: 30px;\">\n"
|
||||
" <strong>REFERENCES</strong><br />\n"
|
||||
" Invoice number: <strong>${object.number}</strong><br />\n"
|
||||
" Invoice total: <strong>${object.amount_total} "
|
||||
"${object.currency_id.name}</strong><br />\n"
|
||||
" Invoice date: ${object.date_invoice}<br />\n"
|
||||
" % if object.origin:\n"
|
||||
" Order reference: ${object.origin}<br />\n"
|
||||
" % endif\n"
|
||||
" % if object.user_id:\n"
|
||||
" Your contact: <a href=\"mailto:${object.user_id.email or "
|
||||
"''}?subject=Invoice%20${object.number}\">${object.user_id.name}</a>\n"
|
||||
" % endif\n"
|
||||
" </p> \n"
|
||||
"\n"
|
||||
" <% set signup_url = object.get_signup_url() %>\n"
|
||||
" % if signup_url:\n"
|
||||
" <p>\n"
|
||||
" You can access the invoice document and pay online via our Customer "
|
||||
"Portal:\n"
|
||||
" </p>\n"
|
||||
" <a style=\"display:block; width: 150px; height:20px; margin-left: "
|
||||
"120px; color: #DDD; font-family: 'Lucida Grande', Helvetica, Arial, sans-"
|
||||
"serif; font-size: 13px; font-weight: bold; text-align: center; text-"
|
||||
"decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; "
|
||||
"background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat "
|
||||
"no-repeat;\"\n"
|
||||
" href=\"${signup_url}\">View Invoice</a>\n"
|
||||
" % endif\n"
|
||||
" \n"
|
||||
" % if object.paypal_url:\n"
|
||||
" <br/>\n"
|
||||
" <p>It is also possible to directly pay with Paypal:</p>\n"
|
||||
" <a style=\"margin-left: 120px;\" href=\"${object.paypal_url}\">\n"
|
||||
" <img class=\"oe_edi_paypal_button\" "
|
||||
"src=\"https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif\"/>\n"
|
||||
" </a>\n"
|
||||
" % endif\n"
|
||||
" \n"
|
||||
" <br/>\n"
|
||||
" <p>If you have any question, do not hesitate to contact us.</p>\n"
|
||||
" <p>Thank you for choosing ${object.company_id.name or 'us'}!</p>\n"
|
||||
" <br/>\n"
|
||||
" <br/>\n"
|
||||
" <div style=\"width: 375px; margin: 0px; padding: 0px; background-color: "
|
||||
"#8E0000; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; "
|
||||
"background-repeat: repeat no-repeat;\">\n"
|
||||
" <h3 style=\"margin: 0px; padding: 2px 14px; font-size: 12px; color: "
|
||||
"#DDD;\">\n"
|
||||
" <strong style=\"text-"
|
||||
"transform:uppercase;\">${object.company_id.name}</strong></h3>\n"
|
||||
" </div>\n"
|
||||
" <div style=\"width: 347px; margin: 0px; padding: 5px 14px; line-height: "
|
||||
"16px; background-color: #F2F2F2;\">\n"
|
||||
" <span style=\"color: #222; margin-bottom: 5px; display: block; \">\n"
|
||||
" % if object.company_id.street:\n"
|
||||
" ${object.company_id.street}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.street2:\n"
|
||||
" ${object.company_id.street2}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.city or object.company_id.zip:\n"
|
||||
" ${object.company_id.zip} ${object.company_id.city}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.country_id:\n"
|
||||
" ${object.company_id.state_id and ('%s, ' % "
|
||||
"object.company_id.state_id.name) or ''} ${object.company_id.country_id.name "
|
||||
"or ''}<br/>\n"
|
||||
" % endif\n"
|
||||
" </span>\n"
|
||||
" % if object.company_id.phone:\n"
|
||||
" <div style=\"margin-top: 0px; margin-right: 0px; margin-bottom: "
|
||||
"0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: "
|
||||
"0px; padding-left: 0px; \">\n"
|
||||
" Phone: ${object.company_id.phone}\n"
|
||||
" </div>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.website:\n"
|
||||
" <div>\n"
|
||||
" Web : <a "
|
||||
"href=\"${object.company_id.website}\">${object.company_id.website}</a>\n"
|
||||
" </div>\n"
|
||||
" % endif\n"
|
||||
" <p></p>\n"
|
||||
" </div>\n"
|
||||
"</div>\n"
|
||||
" "
|
||||
msgstr ""
|
||||
"\n"
|
||||
"<div style=\"font-family: 'Lucica Grande', Ubuntu, Arial, Verdana, sans-"
|
||||
"serif; font-size: 12px; color: rgb(34, 34, 34); background-color: rgb(255, "
|
||||
"255, 255); \">\n"
|
||||
"\n"
|
||||
" <p>Hola, buen día ${object.partner_id.name}:</p>\n"
|
||||
" \n"
|
||||
" <p>Esta es su ${object.state in ('draft', 'sent') and 'cotización' or "
|
||||
"'pedido'} de ${object.company_id.name}: </p>\n"
|
||||
"\n"
|
||||
" <p style=\"border-left: 1px solid #8e0000; margin-left: 30px;\">\n"
|
||||
" <strong>REFERENCIAS</strong><br />\n"
|
||||
" Número: <strong>${object.name}</strong><br />\n"
|
||||
" Total: <strong>${object.amount_total} "
|
||||
"${object.pricelist_id.currency_id.name}</strong><br />\n"
|
||||
" Fecha: ${object.date_order}<br />\n"
|
||||
" % if object.origin:\n"
|
||||
" Origen: ${object.origin}<br />\n"
|
||||
" % endif\n"
|
||||
" % if object.client_order_ref:\n"
|
||||
" Referencia de la orden de compra: "
|
||||
"${object.client_order_ref}<br />\n"
|
||||
" % endif\n"
|
||||
" % if object.user_id:\n"
|
||||
" Su contacto: <a href=\"mailto:${object.user_id.email or "
|
||||
"''}?subject=Order%20${object.name}\">${object.user_id.name}</a>\n"
|
||||
" % endif\n"
|
||||
" </p>\n"
|
||||
"\n"
|
||||
" <% set signup_url = object.get_signup_url() %>\n"
|
||||
" % if signup_url:\n"
|
||||
" <p>\n"
|
||||
" Usted puede acceder a este documento y pagarlo en linea vía nuestro "
|
||||
"portal del cliente:\n"
|
||||
" </p>\n"
|
||||
" <a style=\"display:block; width: 150px; height:20px; margin-left: "
|
||||
"120px; color: #DDD; font-family: 'Lucida Grande', Helvetica, Arial, sans-"
|
||||
"serif; font-size: 13px; font-weight: bold; text-align: center; text-"
|
||||
"decoration: none !important; line-height: 1; padding: 5px 0px 0px 0px; "
|
||||
"background-color: #8E0000; border-radius: 5px 5px; background-repeat: repeat "
|
||||
"no-repeat;\"\n"
|
||||
" href=\"${signup_url}\">Ver ${object.state in ('draft', 'sent') "
|
||||
"and 'Cotización' or 'Pedido'}</a>\n"
|
||||
" % endif\n"
|
||||
"\n"
|
||||
" % if object.paypal_url:\n"
|
||||
" <br/>\n"
|
||||
" <p>Tambien es posible pagarlo directamente con Paypal:</p>\n"
|
||||
" <a style=\"margin-left: 120px;\" href=\"${object.paypal_url}\">\n"
|
||||
" <img class=\"oe_edi_paypal_button\" "
|
||||
"src=\"https://www.paypal.com/en_US/i/btn/btn_paynowCC_LG.gif\"/>\n"
|
||||
" </a>\n"
|
||||
" % endif\n"
|
||||
"\n"
|
||||
" <br/>\n"
|
||||
" <p>Si tiene alguna pregunta no dude en contactarnos.</p>\n"
|
||||
" <p>¡Gracias por seleccionar a ${object.company_id.name or 'us'}!</p>\n"
|
||||
" <br/>\n"
|
||||
" <br/>\n"
|
||||
" <div style=\"width: 375px; margin: 0px; padding: 0px; background-color: "
|
||||
"#8E0000; border-top-left-radius: 5px 5px; border-top-right-radius: 5px 5px; "
|
||||
"background-repeat: repeat no-repeat;\">\n"
|
||||
" <h3 style=\"margin: 0px; padding: 2px 14px; font-size: 12px; color: "
|
||||
"#DDD;\">\n"
|
||||
" <strong style=\"text-"
|
||||
"transform:uppercase;\">${object.company_id.name}</strong></h3>\n"
|
||||
" </div>\n"
|
||||
" <div style=\"width: 347px; margin: 0px; padding: 5px 14px; line-height: "
|
||||
"16px; background-color: #F2F2F2;\">\n"
|
||||
" <span style=\"color: #222; margin-bottom: 5px; display: block; \">\n"
|
||||
" % if object.company_id.street:\n"
|
||||
" ${object.company_id.street}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.street2:\n"
|
||||
" ${object.company_id.street2}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.city or object.company_id.zip:\n"
|
||||
" ${object.company_id.zip} ${object.company_id.city}<br/>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.country_id:\n"
|
||||
" ${object.company_id.state_id and ('%s, ' % "
|
||||
"object.company_id.state_id.name) or ''} ${object.company_id.country_id.name "
|
||||
"or ''}<br/>\n"
|
||||
" % endif\n"
|
||||
" </span>\n"
|
||||
" % if object.company_id.phone:\n"
|
||||
" <div style=\"margin-top: 0px; margin-right: 0px; margin-bottom: "
|
||||
"0px; margin-left: 0px; padding-top: 0px; padding-right: 0px; padding-bottom: "
|
||||
"0px; padding-left: 0px; \">\n"
|
||||
" Teléfono: ${object.company_id.phone}\n"
|
||||
" </div>\n"
|
||||
" % endif\n"
|
||||
" % if object.company_id.website:\n"
|
||||
" <div>\n"
|
||||
" Página web : <a "
|
||||
"href=\"${object.company_id.website}\">${object.company_id.website}</a>\n"
|
||||
" </div>\n"
|
||||
" % endif\n"
|
||||
" <p></p>\n"
|
||||
" </div>\n"
|
||||
"</div>\n"
|
||||
" "
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.actions.act_window,help:portal_sale.action_orders_portal
|
||||
msgid "We haven't sent you any sales order."
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.model,name:portal_sale.model_account_invoice
|
||||
msgid "Invoice"
|
||||
msgstr ""
|
||||
|
||||
#. module: portal_sale
|
||||
#: model:ir.actions.act_window,name:portal_sale.action_orders_portal
|
||||
msgid "Sale Orders"
|
||||
msgstr ""
|
|
@ -392,10 +392,18 @@ class project_issue(base_stage, osv.osv):
|
|||
context=context)
|
||||
|
||||
def write(self, cr, uid, ids, vals, context=None):
|
||||
#Update last action date every time the user change the stage, the state or send a new email
|
||||
logged_fields = ['stage_id', 'state', 'message_ids']
|
||||
if any([field in vals for field in logged_fields]):
|
||||
vals['date_action_last'] = time.strftime('%Y-%m-%d %H:%M:%S')
|
||||
|
||||
#Update last action date every time the user changes the stage
|
||||
if 'stage_id' in vals:
|
||||
vals['date_action_last'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
state = self.pool.get('project.task.type').browse(cr, uid, vals['stage_id'], context=context).state
|
||||
for issue in self.browse(cr, uid, ids, context=context):
|
||||
# Change from draft to not draft EXCEPT cancelled: The issue has been opened -> set the opening date
|
||||
if issue.state == 'draft' and state not in ('draft', 'cancelled'):
|
||||
vals['date_open'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
# Change from not done to done: The issue has been closed -> set the closing date
|
||||
if issue.state != 'done' and state == 'done':
|
||||
vals['date_closed'] = time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)
|
||||
|
||||
return super(project_issue, self).write(cr, uid, ids, vals, context)
|
||||
|
||||
|
@ -551,6 +559,19 @@ class project_issue(base_stage, osv.osv):
|
|||
|
||||
return super(project_issue, self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
|
||||
|
||||
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):
|
||||
""" Overrides mail_thread message_post so that we can set the date of last action field when
|
||||
a new message is posted on the issue.
|
||||
"""
|
||||
if context is None:
|
||||
context = {}
|
||||
|
||||
res = super(project_issue, self).message_post(cr, uid, thread_id, body=body, subject=subject, type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, content_subtype=content_subtype, **kwargs)
|
||||
|
||||
if thread_id:
|
||||
self.write(cr, uid, thread_id, {'date_action_last': time.strftime(tools.DEFAULT_SERVER_DATETIME_FORMAT)}, context=context)
|
||||
|
||||
return res
|
||||
|
||||
class project(osv.osv):
|
||||
_inherit = "project.project"
|
||||
|
|
|
@ -27,9 +27,18 @@ class order(report_sxw.rml_parse):
|
|||
def __init__(self, cr, uid, name, context=None):
|
||||
super(order, self).__init__(cr, uid, name, context=context)
|
||||
self.localcontext.update({
|
||||
'time': time,
|
||||
'time': time,
|
||||
'show_discount':self._show_discount,
|
||||
})
|
||||
|
||||
def _show_discount(self, uid, context=None):
|
||||
cr = self.cr
|
||||
try:
|
||||
group_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'sale', 'group_discount_per_so_line')[1]
|
||||
except:
|
||||
return False
|
||||
return group_id in [x.id for x in self.pool.get('res.users').browse(cr, uid, uid, context=context).groups_id]
|
||||
|
||||
report_sxw.report_sxw('report.sale.order', 'sale.order', 'addons/sale/report/sale_order.rml', parser=order, header="external")
|
||||
|
||||
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|
||||
|
|
|
@ -251,7 +251,7 @@
|
|||
<para style="terp_tblheader_Details_Right">Unit Price</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_Details_Centre">Disc.(%)</para>
|
||||
<para style="terp_tblheader_Details_Centre">[[not show_discount(user.id) and removeParentNode('para') ]]Disc.(%)</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_tblheader_Details_Right">Price</para>
|
||||
|
@ -275,7 +275,7 @@
|
|||
<para style="terp_default_Right_9">[[ formatLang(line.price_unit , digits=get_digits(dp='Product Price'))]]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Centre_9">[[ formatLang(line.discount, digits=get_digits(dp='Discount'))]]</para>
|
||||
<para style="terp_default_Centre_9">[[show_discount(user.id) and formatLang(line.discount, digits=get_digits(dp='Discount')) or '']]</para>
|
||||
</td>
|
||||
<td>
|
||||
<para style="terp_default_Right_9">[[ formatLang(line.price_subtotal, digits=get_digits(dp='Account'), currency_obj=o.pricelist_id.currency_id) ]] </para>
|
||||
|
|
|
@ -88,12 +88,11 @@ class sale_report(osv.osv):
|
|||
s.project_id as analytic_account_id
|
||||
from
|
||||
sale_order s
|
||||
left join sale_order_line l on (s.id=l.order_id)
|
||||
join sale_order_line l on (s.id=l.order_id)
|
||||
left join product_product p on (l.product_id=p.id)
|
||||
left join product_template t on (p.product_tmpl_id=t.id)
|
||||
left join product_uom u on (u.id=l.product_uom)
|
||||
left join product_uom u2 on (u2.id=t.uom_id)
|
||||
where l.product_id is not null
|
||||
group by
|
||||
l.product_id,
|
||||
l.product_uom_qty,
|
||||
|
|
|
@ -92,11 +92,10 @@
|
|||
I verify that a procurement has been generated for sale order
|
||||
-
|
||||
!python {model: procurement.order}: |
|
||||
from openerp.tools.translate import _
|
||||
sale_order_obj = self.pool.get('sale.order')
|
||||
so = sale_order_obj.browse(cr, uid, ref("sale_order_so0"))
|
||||
proc_ids = self.search(cr, uid, [('origin','=',so.name)])
|
||||
assert proc_ids, _('No Procurements!')
|
||||
assert proc_ids, 'No Procurements!'
|
||||
-
|
||||
Then I click on the "Run Procurement" button
|
||||
-
|
||||
|
@ -112,7 +111,7 @@
|
|||
sale_order_obj = self.pool.get('sale.order')
|
||||
so = sale_order_obj.browse(cr, uid, ref("sale_order_so0"))
|
||||
proc_ids = self.search(cr, uid, [('origin','=',so.name) and ('state','=','running')])
|
||||
assert proc_ids, _('Procurement is not in the running state!')
|
||||
assert proc_ids, 'Procurement is not in the running state!'
|
||||
-
|
||||
I verify that a manufacturing order has been generated, and that its name and reference are correct
|
||||
-
|
||||
|
@ -120,7 +119,7 @@
|
|||
mnf_obj = self.pool.get('mrp.production')
|
||||
so = self.browse(cr, uid, ref("sale_order_so0"))
|
||||
mnf_id = mnf_obj.search(cr, uid, [('origin','=',so.name)])
|
||||
assert mnf_id, _('Manufacturing order has not been generated')
|
||||
assert mnf_id, 'Manufacturing order has not been generated'
|
||||
mo = mnf_obj.browse(cr, uid, mnf_id)[0]
|
||||
assert mo.sale_name == so.name, 'Wrong Name for the Manufacturing Order. Expected %s, Got %s' % (so.name, mo.name)
|
||||
assert mo.sale_ref == so.client_order_ref, 'Wrong Sale Reference for the Manufacturing Order'
|
||||
|
|
|
@ -68,8 +68,8 @@ class sale_report(osv.osv):
|
|||
s.project_id as analytic_account_id
|
||||
from
|
||||
sale_order s
|
||||
left join sale_order_line l on (s.id=l.order_id)
|
||||
left join product_product p on (l.product_id=p.id)
|
||||
join sale_order_line l on (s.id=l.order_id)
|
||||
left join product_product p on (l.product_id=p.id)
|
||||
left join product_template t on (p.product_tmpl_id=t.id)
|
||||
left join product_uom u on (u.id=l.product_uom)
|
||||
left join product_uom u2 on (u2.id=t.uom_id)
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
#
|
||||
##############################################################################
|
||||
|
||||
from lxml import etree
|
||||
from datetime import datetime
|
||||
from dateutil.relativedelta import relativedelta
|
||||
import time
|
||||
|
@ -704,29 +703,29 @@ class stock_picking(osv.osv):
|
|||
default = {}
|
||||
default = default.copy()
|
||||
picking_obj = self.browse(cr, uid, id, context=context)
|
||||
move_obj=self.pool.get('stock.move')
|
||||
if ('name' not in default) or (picking_obj.name=='/'):
|
||||
seq_obj_name = 'stock.picking.' + picking_obj.type
|
||||
move_obj = self.pool.get('stock.move')
|
||||
if ('name' not in default) or (picking_obj.name == '/'):
|
||||
seq_obj_name = 'stock.picking.' + picking_obj.type
|
||||
default['name'] = self.pool.get('ir.sequence').get(cr, uid, seq_obj_name)
|
||||
default['origin'] = ''
|
||||
default['backorder_id'] = False
|
||||
if 'invoice_state' not in default and picking_obj.invoice_state == 'invoiced':
|
||||
default['invoice_state'] = '2binvoiced'
|
||||
res=super(stock_picking, self).copy(cr, uid, id, default, context)
|
||||
res = super(stock_picking, self).copy(cr, uid, id, default, context)
|
||||
if res:
|
||||
picking_obj = self.browse(cr, uid, res, context=context)
|
||||
for move in picking_obj.move_lines:
|
||||
move_obj.write(cr, uid, [move.id], {'tracking_id': False,'prodlot_id':False, 'move_history_ids2': [(6, 0, [])], 'move_history_ids': [(6, 0, [])]})
|
||||
move_obj.write(cr, uid, [move.id], {'tracking_id': False, 'prodlot_id': False, 'move_history_ids2': [(6, 0, [])], 'move_history_ids': [(6, 0, [])]})
|
||||
return res
|
||||
|
||||
|
||||
def fields_view_get(self, cr, uid, view_id=None, view_type=False, context=None, toolbar=False, submenu=False):
|
||||
if view_type == 'form' and not view_id:
|
||||
mod_obj = self.pool.get('ir.model.data')
|
||||
if self._name == "stock.picking.in":
|
||||
model,view_id = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_in_form')
|
||||
model, view_id = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_in_form')
|
||||
if self._name == "stock.picking.out":
|
||||
model,view_id = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_out_form')
|
||||
return super(stock_picking,self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
|
||||
model, view_id = mod_obj.get_object_reference(cr, uid, 'stock', 'view_picking_out_form')
|
||||
return super(stock_picking, self).fields_view_get(cr, uid, view_id=view_id, view_type=view_type, context=context, toolbar=toolbar, submenu=submenu)
|
||||
|
||||
def onchange_partner_in(self, cr, uid, ids, partner_id=None, context=None):
|
||||
return {}
|
||||
|
@ -2440,9 +2439,8 @@ class stock_move(osv.osv):
|
|||
context = {}
|
||||
ctx = context.copy()
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
if move.state != 'draft' and not ctx.get('call_unlink',False):
|
||||
raise osv.except_osv(_('User Error!'),
|
||||
_('You can only delete draft moves.'))
|
||||
if move.state != 'draft' and not ctx.get('call_unlink', False):
|
||||
raise osv.except_osv(_('User Error!'), _('You can only delete draft moves.'))
|
||||
return super(stock_move, self).unlink(
|
||||
cr, uid, ids, context=ctx)
|
||||
|
||||
|
@ -2470,13 +2468,20 @@ class stock_move(osv.osv):
|
|||
raise osv.except_osv(_('Warning!'), _('Please provide a positive quantity to scrap.'))
|
||||
res = []
|
||||
for move in self.browse(cr, uid, ids, context=context):
|
||||
source_location = move.location_id
|
||||
if move.state == 'done':
|
||||
source_location = move.location_dest_id
|
||||
if source_location.usage != 'internal':
|
||||
#restrict to scrap from a virtual location because it's meaningless and it may introduce errors in stock ('creating' new products from nowhere)
|
||||
raise osv.except_osv(_('Error!'), _('Forbidden operation: it is not allowed to scrap products from a virtual location.'))
|
||||
move_qty = move.product_qty
|
||||
uos_qty = quantity / move_qty * move.product_uos_qty
|
||||
default_val = {
|
||||
'location_id': source_location.id,
|
||||
'product_qty': quantity,
|
||||
'product_uos_qty': uos_qty,
|
||||
'state': move.state,
|
||||
'scrapped' : True,
|
||||
'scrapped': True,
|
||||
'location_dest_id': location_id,
|
||||
'tracking_id': move.tracking_id.id,
|
||||
'prodlot_id': move.prodlot_id.id,
|
||||
|
|
|
@ -1129,7 +1129,7 @@
|
|||
<field name="model">stock.move</field>
|
||||
<field eval="8" name="priority"/>
|
||||
<field name="arch" type="xml">
|
||||
<tree colors="grey:state == 'cancel';red:(state not in ('cancel','done')) and date > current_date" string="Moves" editable="top">
|
||||
<tree colors="grey:state == 'cancel';red:(state not in ('cancel','done')) and date > current_date" string="Moves">
|
||||
<field name="name"/>
|
||||
<field name="picking_id" string="Reference"/>
|
||||
<field name="origin"/>
|
||||
|
|
|
@ -94,12 +94,11 @@
|
|||
I check the move is in waiting state.
|
||||
-
|
||||
!python {model: stock.picking }: |
|
||||
from openerp.tools.translate import _
|
||||
picking_id = self.search(cr, uid, [('origin','=','Pushed Flow Test'),('type','=','out')])
|
||||
if picking_id:
|
||||
pick=self.browse(cr,uid,picking_id[0])
|
||||
for move in pick.move_lines:
|
||||
assert(move.state == 'waiting'), _('Stock is not in waiting state')
|
||||
assert(move.state == 'waiting'), 'Stock is not in waiting state'
|
||||
-
|
||||
I receive the order of the supplier Micro Link Technologies from the Incoming Shipments menu.
|
||||
-
|
||||
|
@ -123,8 +122,7 @@
|
|||
I check the Outgoing Orders is automatically done.
|
||||
-
|
||||
!python {model: stock.picking }: |
|
||||
from openerp.tools.translate import _
|
||||
picking_id = self.search(cr, uid, [('origin','=','Pushed Flow Test'),('type','=','out')])
|
||||
if picking_id:
|
||||
pick=self.browse(cr,uid,picking_id[0])
|
||||
assert(pick.state == 'done'), _('Picking is not in done state')
|
||||
assert(pick.state == 'done'), 'Picking is not in done state'
|
||||
|
|
|
@ -85,6 +85,17 @@ class subscription_subscription(osv.osv):
|
|||
'state': lambda *a: 'draft'
|
||||
}
|
||||
|
||||
def _auto_end(self, cr, context=None):
|
||||
super(subscription_subscription, self)._auto_end(cr, context=context)
|
||||
# drop the FK from subscription to ir.cron, as it would cause deadlocks
|
||||
# during cron job execution. When model_copy() tries to write() on the subscription,
|
||||
# it has to wait for an ExclusiveLock on the cron job record, but the latter
|
||||
# is locked by the cron system for the duration of the job!
|
||||
# FIXME: the subscription module should be reviewed to simplify the scheduling process
|
||||
# and to use a unique cron job for all subscriptions, so that it never needs to
|
||||
# be updated during its execution.
|
||||
cr.execute("ALTER TABLE %s DROP CONSTRAINT %s" % (self._table, '%s_cron_id_fkey' % self._table))
|
||||
|
||||
def set_process(self, cr, uid, ids, context=None):
|
||||
for row in self.read(cr, uid, ids, context=context):
|
||||
mapping = {'name':'name','interval_number':'interval_number','interval_type':'interval_type','exec_init':'numbercall','date_init':'nextcall'}
|
||||
|
|
Loading…
Reference in New Issue