[MRG] From last trunk

bzr revid: joel.grandguillaume@camptocamp.com-20121029152959-ugtxjoe8rc7mlhcp
This commit is contained in:
Joël Grand-Guillaume 2012-10-29 16:29:59 +01:00
commit 85b2285e49
133 changed files with 4103 additions and 2991 deletions

View File

@ -39,7 +39,6 @@ class account_analytic_line(osv.osv):
}
_defaults = {
'date': fields.date.context_today,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
}
_order = 'date desc'

View File

@ -865,8 +865,11 @@ class account_invoice(osv.osv):
self.check_tax_lines(cr, uid, inv, compute_taxes, ait_obj)
# I disabled the check_total feature
#if inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0):
# raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe real total does not match the computed total.'))
group_check_total_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'account', 'group_supplier_inv_check_total')[1]
group_check_total = self.pool.get('res.groups').browse(cr, uid, group_check_total_id, context=context)
if group_check_total and uid in [x.id for x in group_check_total.users]:
if (inv.type in ('in_invoice', 'in_refund') and abs(inv.check_total - inv.amount_total) >= (inv.currency_id.rounding/2.0)):
raise osv.except_osv(_('Bad total !'), _('Please verify the price of the invoice !\nThe encoded total does not match the computed total.'))
if inv.payment_term:
total_fixed = total_percent = 0

View File

@ -186,6 +186,7 @@
<field name="journal_id" groups="account.group_account_user"
on_change="onchange_journal_id(journal_id, context)" widget="selection"/>
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="check_total" groups="account.group_supplier_inv_check_total"/>
</group>
</group>
<notebook>
@ -279,8 +280,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@ -436,8 +437,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
</div>
</form>
</field>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,7 @@
<group col="4">
<field name="date1"/>
<field name="date2"/>
<field name="analytic_account_journal_id" widget="many2many_tags" class="oe_inline" required="1"/>
<field name="analytic_account_journal_id" widget="many2many_tags" class="oe_inline" required="1" colspan="4"/>
</group>
<footer>
<button name="check_report" string="Print" type="object" class="oe_highlight"/>

View File

@ -120,6 +120,8 @@ class account_config_settings(osv.osv_memory):
'group_analytic_accounting': fields.boolean('Analytic accounting',
implied_group='analytic.group_analytic_accounting',
help="Allows you to use the analytic accounting."),
'group_check_supplier_invoice_total': fields.boolean('Check the total of supplier invoices',
implied_group="account.group_supplier_inv_check_total"),
}
def _default_company(self, cr, uid, context=None):

View File

@ -220,6 +220,10 @@
<field name="module_account_check_writing" class="oe_inline"/>
<label for="module_account_check_writing"/>
</div>
<div>
<field name="group_check_supplier_invoice_total" class="oe_inline"/>
<label for="group_check_supplier_invoice_total"/>
</div>
</div>
</group>
<separator string="Bank &amp; Cash"/>

View File

@ -26,6 +26,11 @@
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="group_supplier_inv_check_total" model="res.groups">
<field name="name">Check Total on supplier invoices</field>
<field name="category_id" ref="base.module_category_hidden"/>
</record>
<record id="account_move_comp_rule" model="ir.rule">
<field name="name">Account Entry</field>
<field name="model_id" ref="model_account_move"/>

View File

@ -74,36 +74,18 @@ class account_analytic_account(osv.osv):
for id in ids:
res[id][f] = 0.0
res2 = {}
if parent_ids:
# Amount uninvoiced hours to invoice at sale price
# Warning
# This computation doesn't take care of pricelist !
# Just consider list_price
cr.execute("""SELECT account_analytic_account.id, \
COALESCE(SUM (product_template.list_price * \
account_analytic_line.unit_amount * \
((100-hr_timesheet_invoice_factor.factor)/100)), 0.0) \
AS ca_to_invoice \
FROM product_template \
JOIN product_product \
ON product_template.id = product_product.product_tmpl_id \
JOIN account_analytic_line \
ON account_analytic_line.product_id = product_product.id \
JOIN account_analytic_journal \
ON account_analytic_line.journal_id = account_analytic_journal.id \
JOIN account_analytic_account \
ON account_analytic_account.id = account_analytic_line.account_id \
JOIN hr_timesheet_invoice_factor \
ON hr_timesheet_invoice_factor.id = account_analytic_account.to_invoice \
WHERE account_analytic_account.id IN %s \
AND account_analytic_line.invoice_id IS NULL \
AND account_analytic_line.to_invoice IS NOT NULL \
AND account_analytic_journal.type = 'general' \
GROUP BY account_analytic_account.id;""", (parent_ids,))
for account_id, sum in cr.fetchall():
if account_id not in res:
res[account_id] = {}
res[account_id][f] = round(sum, dp)
for account in accounts:
cr.execute("SELECT product_id, user_id, to_invoice, sum(unit_amount), product_uom_id, name " \
"FROM account_analytic_line as line " \
"WHERE account_id = %s " \
"AND invoice_id is NULL AND to_invoice IS NOT NULL " \
"GROUP BY product_id, user_id, to_invoice, product_uom_id, name", (account.id,))
res[account.id][f] = 0.0
for product_id, user_id, factor_id, qty, uom, line_name in cr.fetchall():
price = self.pool.get('account.analytic.line')._get_invoice_price(cr, uid, account, product_id, user_id, qty, context)
factor = self.pool.get('hr_timesheet_invoice.factor').browse(cr, uid, factor_id, context=context)
res[account.id][f] += price * qty * (100-factor.factor or 0.0) / 100.0
# sum both result on account_id
for id in ids:

View File

@ -30,18 +30,18 @@
<separator name="toinvoice" string="Invoicing"/>
<table class="oe_form_analytic_account">
<tr>
<th class="oe_grey" width="160px"></th>
<th class="oe_grey" width="25px"></th>
<th class="oe_grey" width="100px"><label string="Expected"/></th>
<th class="oe_grey" width="100px"><label string="Invoiced"/></th>
<th class="oe_grey" width="100px"><label string="Remaining"/></th>
<th class="oe_grey" width="100px"><label string="To Invoice"/></th>
<th class="oe_timesheet_grey" width="160px"></th>
<th class="oe_timesheet_grey" width="25px"></th>
<th class="oe_timesheet_grey" width="100px"><label string="Expected"/></th>
<th class="oe_timesheet_grey" width="100px"><label string="Invoiced"/></th>
<th class="oe_timesheet_grey" width="100px"><label string="Remaining"/></th>
<th class="oe_timesheet_grey" width="100px"><label string="To Invoice"/></th>
<th width="30px"></th>
<th></th>
</tr><tr>
<td class="oe_grey">
<td class="oe_timesheet_grey">
<label for="fix_price_invoices"/>
</td><td class="oe_grey">
</td><td class="oe_timesheet_grey">
<field name="fix_price_invoices" class="oe_inline"/>
</td><td>
<field class="oe_inline" name="amount_max" attrs="{'invisible': [('fix_price_invoices','=',False)]}"/>
@ -51,23 +51,28 @@
<field class="oe_inline" name="remaining_ca" attrs="{'invisible': [('fix_price_invoices','=',False)]}"/>
</td><td>
<field class="oe_inline" name="fix_price_to_invoice" attrs="{'invisible': [('fix_price_invoices','=',False)]}"/>
</td><td>
<button name="open_sale_order_lines"
class="oe_link oe_e"
string="/" type="object"
context="{'default_partner_id': [partner_id],'default_project_id': active_id,'search_default_uninvoiced': 1,'search_default_project_id': active_id,'search_default_partner_id': [partner_id]}"
attrs="{'invisible': [('fix_price_to_invoice','=',0)]}"/>
</td><td>
</td><td attrs="{'invisible': [('fix_price_invoices','=',False)]}" class="oe_timesheet_action">
<span attrs="{'invisible': [('fix_price_to_invoice','=',0.0)]}" class="oe_grey">
<button name="open_sale_order_lines"
class="oe_link"
string="⇒ Invoice" type="object"
context="{'default_partner_id': [partner_id],'default_project_id': active_id,'search_default_uninvoiced': 1,'search_default_project_id': active_id,'search_default_partner_id': [partner_id]}"/>
or view
</span>
<span attrs="{'invisible': [('fix_price_to_invoice','&lt;&gt;',0.0)]}" class="oe_grey">
No order to invoice, create
</span>
<button name="%(action_sales_order)d" string="Sale Orders"
type="action"
class="oe_link"
context="{'default_partner_id': [partner_id], 'search_default_partner_id': [partner_id],'search_default_project_id': [active_id],'default_project_id': [active_id]}"
attrs="{'invisible': [('fix_price_invoices','=',False)]}"/>
/>
</td>
</tr><tr>
<td class="oe_grey">
<td class="oe_timesheet_grey">
<label for="invoice_on_timesheets"/>
</td><td class="oe_grey">
</td><td class="oe_timesheet_grey">
<field name="invoice_on_timesheets"/>
</td><td>
<field class="oe_inline" name="hours_qtt_est" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"/>
@ -77,44 +82,55 @@
<field class="oe_inline" name="remaining_hours_to_invoice" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"/>
</td><td>
<field class="oe_inline" name="ca_to_invoice" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"/>
</td><td>
<button name="%(hr_timesheet_invoice.act_acc_analytic_acc_2_report_acc_analytic_line_to_invoice)d"
type="action"
class="oe_link oe_e"
string="/"
attrs="{'invisible': [('ca_to_invoice','=',0.0)]}"/>
</td><td class="oe_timesheet_action" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
<span attrs="{'invisible': [('ca_to_invoice','=',0.0)]}" class="oe_grey">
<button name="%(hr_timesheet_invoice.action_hr_timesheet_invoice_create_final)d"
type="action"
class="oe_link"
string="⇒ Invoice"/>
or view
</span>
<span attrs="{'invisible': [('ca_to_invoice','&lt;&gt;',0.0)]}" class="oe_grey">
Nothing to invoice, create
</span>
</td><td>
<button name="%(hr_timesheet.act_hr_timesheet_line_evry1_all_form)d"
string="Timesheets" type="action"
class="oe_link"
attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"
context="{'default_account_id': active_id,'search_default_account_id': active_id}"/>
</td>
</tr><tr name='total'>
<th class="oe_grey">
<th class="oe_timesheet_grey">
<label string="Total"/>
</th><td class="oe_grey">
</td><td class="oe_grey">
</th><td class="oe_timesheet_grey">
</td><td class="oe_timesheet_grey">
<field name="est_total" class="oe_inline" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}"/>
</td><td class="oe_grey">
</td><td class="oe_timesheet_grey">
<field name="invoiced_total" class="oe_inline"/>
</td><td class="oe_grey">
</td><td class="oe_timesheet_grey">
<field name="remaining_total" class="oe_inline"/>
</td><td class="oe_grey">
</td><td class="oe_timesheet_grey">
<field name="toinvoice_total" class="oe_inline"/>
</td><td>
</td>
</tr>
</table>
<group name='invoice_on_timesheets' attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
<field name="pricelist_id"
class="oe_inline"
attrs="{'required': [('invoice_on_timesheets', '=', True)]}"/>
<field name="to_invoice"
class="oe_inline"
widget="selection"
attrs="{'required': [('invoice_on_timesheets', '=', True)]}"/>
<p class="oe_grey oe_edit_only" colspan="2">
When invoicing on timesheet, OpenERP uses the
pricelist of the contract which uses the price
defined on the product related to each employee to
define the customer invoice price rate.
</p>
<group>
<field name="pricelist_id"
class="oe_inline"
attrs="{'required': [('invoice_on_timesheets', '=', True)]}"/>
<field name="to_invoice"
class="oe_inline"
widget="selection"
attrs="{'required': [('invoice_on_timesheets', '=', True)]}"/>
</group>
</group>
</xpath>
</field>

View File

@ -9,7 +9,11 @@
float: right;
width: auto !important;
}
.openerp .oe_form table.oe_form_analytic_account .oe_grey {
.openerp .oe_form table.oe_form_analytic_account tr td.oe_timesheet_action {
width: 250px;
padding-left: 7px;
}
.openerp .oe_form table.oe_form_analytic_account .oe_timesheet_grey {
background-color: #eeeeee;
color: #404040;
}

View File

@ -9,7 +9,10 @@
.oe_form_field_float
float: right
width: auto !important
.oe_grey
td.oe_timesheet_action
width: 250px
padding-left: 7px
.oe_timesheet_grey
background-color: #eeeeee
color: #404040

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2011-04-15 19:38+0000\n"
"PO-Revision-Date: 2012-10-25 12:32+0000\n"
"Last-Translator: Chertykov Denis <chertykov@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:15+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: account_analytic_plans
#: view:analytic.plan.create.model:0
@ -136,7 +136,7 @@ msgstr "Определить аналитический план счетов"
#. module: account_analytic_plans
#: constraint:account.invoice:0
msgid "Invalid BBA Structured Communication !"
msgstr ""
msgstr "Неверна структурная связь BBA!"
#. module: account_analytic_plans
#: constraint:account.bank.statement:0
@ -149,6 +149,8 @@ msgid ""
"The date of your Journal Entry is not in the defined period! You should "
"change the date or remove this constraint from the journal."
msgstr ""
"Дата проводки в журнале не в определённом периоде! Вы должны сменить дату "
"или удалить это ограничение из журнала."
#. module: account_analytic_plans
#: sql_constraint:account.journal:0
@ -251,6 +253,8 @@ msgid ""
"currency. You should remove the secondary currency on the account or select "
"a multi-currency view on the journal."
msgstr ""
"Выбранный счёт проводки в журнале нуждается во вторичной валюте. Вы должны "
"удалить вторичную валюту по счёту или выбрать мульти-валютный вид по журналу."
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0
@ -429,7 +433,7 @@ msgstr "Счет 4 уровня"
#. module: account_analytic_plans
#: constraint:account.move.line:0
msgid "Company must be the same for its related account and period."
msgstr ""
msgstr "Для счета и периода должна быть одна компания."
#. module: account_analytic_plans
#: view:account.analytic.plan.instance.line:0
@ -515,11 +519,14 @@ msgid ""
"analytic accounts for each plan set. Then, you must attach a plan set to "
"your account journals."
msgstr ""
"Для настройки нескольких планов счетов аналитики, вы должны определить "
"корневые счета аналитики для каждого набора плана. Затем вы можете "
"прикрепить набор плана к вашим учётным журналам."
#. module: account_analytic_plans
#: constraint:account.move.line:0
msgid "You can not create journal items on closed account."
msgstr ""
msgstr "Нельзя создать элемент журнала по закрытому счету ."
#. module: account_analytic_plans
#: report:account.analytic.account.crossovered.analytic:0

View File

@ -0,0 +1,199 @@
# Polish translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2012-10-25 16:56+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Polish <pl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: account_check_writing
#: selection:res.company,check_layout:0
msgid "Check on Top"
msgstr ""
#. module: account_check_writing
#: model:ir.actions.act_window,help:account_check_writing.action_write_check
msgid ""
"The check payment form allows you to track the payment you do to your "
"suppliers specially by check. When you select a supplier, the payment method "
"and an amount for the payment, OpenERP will propose to reconcile your "
"payment with the open supplier invoices or bills.You can print the check"
msgstr ""
#. module: account_check_writing
#: view:account.voucher:0
#: model:ir.actions.report.xml,name:account_check_writing.account_print_check_bottom
#: model:ir.actions.report.xml,name:account_check_writing.account_print_check_middle
#: model:ir.actions.report.xml,name:account_check_writing.account_print_check_top
msgid "Print Check"
msgstr ""
#. module: account_check_writing
#: selection:res.company,check_layout:0
msgid "Check in middle"
msgstr ""
#. module: account_check_writing
#: help:res.company,check_layout:0
msgid ""
"Check on top is compatible with Quicken, QuickBooks and Microsoft Money. "
"Check in middle is compatible with Peachtree, ACCPAC and DacEasy. Check on "
"bottom is compatible with Peachtree, ACCPAC and DacEasy only"
msgstr ""
#. module: account_check_writing
#: selection:res.company,check_layout:0
msgid "Check on bottom"
msgstr ""
#. module: account_check_writing
#: constraint:res.company:0
msgid "Error! You can not create recursive companies."
msgstr ""
#. module: account_check_writing
#: help:account.journal,allow_check_writing:0
msgid "Check this if the journal is to be used for writing checks."
msgstr ""
#. module: account_check_writing
#: field:account.journal,allow_check_writing:0
msgid "Allow Check writing"
msgstr ""
#. module: account_check_writing
#: report:account.print.check.bottom:0
#: report:account.print.check.middle:0
#: report:account.print.check.top:0
msgid "Description"
msgstr "Opis"
#. module: account_check_writing
#: model:ir.model,name:account_check_writing.model_account_journal
msgid "Journal"
msgstr "Dziennik"
#. module: account_check_writing
#: model:ir.actions.act_window,name:account_check_writing.action_write_check
#: model:ir.ui.menu,name:account_check_writing.menu_action_write_check
msgid "Write Checks"
msgstr ""
#. module: account_check_writing
#: report:account.print.check.bottom:0
#: report:account.print.check.middle:0
#: report:account.print.check.top:0
msgid "Discount"
msgstr ""
#. module: account_check_writing
#: report:account.print.check.bottom:0
#: report:account.print.check.middle:0
#: report:account.print.check.top:0
msgid "Original Amount"
msgstr ""
#. module: account_check_writing
#: view:res.company:0
msgid "Configuration"
msgstr "Konfiguracja"
#. module: account_check_writing
#: field:account.voucher,allow_check:0
msgid "Allow Check Writing"
msgstr ""
#. module: account_check_writing
#: report:account.print.check.bottom:0
#: report:account.print.check.middle:0
#: report:account.print.check.top:0
msgid "Payment"
msgstr ""
#. module: account_check_writing
#: field:account.journal,use_preprint_check:0
msgid "Use Preprinted Check"
msgstr ""
#. module: account_check_writing
#: sql_constraint:res.company:0
msgid "The company name must be unique !"
msgstr ""
#. module: account_check_writing
#: report:account.print.check.bottom:0
#: report:account.print.check.middle:0
#: report:account.print.check.top:0
msgid "Due Date"
msgstr ""
#. module: account_check_writing
#: model:ir.model,name:account_check_writing.model_res_company
msgid "Companies"
msgstr ""
#. module: account_check_writing
#: view:res.company:0
msgid "Default Check Layout"
msgstr ""
#. module: account_check_writing
#: constraint:account.journal:0
msgid ""
"Configuration error! The currency chosen should be shared by the default "
"accounts too."
msgstr ""
#. module: account_check_writing
#: report:account.print.check.bottom:0
#: report:account.print.check.middle:0
msgid "Balance Due"
msgstr ""
#. module: account_check_writing
#: report:account.print.check.bottom:0
#: report:account.print.check.middle:0
#: report:account.print.check.top:0
msgid "Check Amount"
msgstr ""
#. module: account_check_writing
#: model:ir.model,name:account_check_writing.model_account_voucher
msgid "Accounting Voucher"
msgstr ""
#. module: account_check_writing
#: sql_constraint:account.journal:0
msgid "The name of the journal must be unique per company !"
msgstr ""
#. module: account_check_writing
#: sql_constraint:account.journal:0
msgid "The code of the journal must be unique per company !"
msgstr ""
#. module: account_check_writing
#: field:account.voucher,amount_in_word:0
msgid "Amount in Word"
msgstr ""
#. module: account_check_writing
#: report:account.print.check.top:0
msgid "Open Balance"
msgstr ""
#. module: account_check_writing
#: field:res.company,check_layout:0
msgid "Choose Check layout"
msgstr ""

View File

@ -19,11 +19,14 @@
#
##############################################################################
import logging
import time
from osv import osv, fields
import netsvc
_logger = logging.getLogger(__name__)
class payment_mode(osv.osv):
_name= 'payment.mode'
_description= 'Payment Mode'
@ -70,9 +73,7 @@ class payment_order(osv.osv):
#dead code
def get_wizard(self, type):
logger = netsvc.Logger()
logger.notifyChannel("Warning!", netsvc.LOG_WARNING,
"No wizard is found for the payment type '%s'." % type)
_logger.warning("No wizard found for the payment type '%s'.", type)
return None
def _total(self, cursor, user, ids, name, args, context=None):

View File

@ -988,11 +988,11 @@ class account_voucher(osv.osv):
if amount_residual > 0:
account_id = line.voucher_id.company_id.expense_currency_exchange_account_id
if not account_id:
raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Expense Currency Rate' on the company, then create accounting entry for currency rate difference."))
raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Loss Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
else:
account_id = line.voucher_id.company_id.income_currency_exchange_account_id
if not account_id:
raise osv.except_osv(_('Warning!'),_("First you have to configure the 'Income Currency Rate' on the company, then create accounting entry for currency rate difference."))
raise osv.except_osv(_('Insufficient Configuration!'),_("You should configure the 'Gain Exchange Rate Account' in the accounting settings, to manage automatically the booking of accounting entries related to differences between exchange rates."))
# Even if the amount_currency is never filled, we need to pass the foreign currency because otherwise
# the receivable/payable account may have a secondary currency, which render this field mandatory
account_currency_id = company_currency <> current_currency and current_currency or False

View File

@ -109,8 +109,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -240,8 +240,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@ -512,8 +512,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -64,7 +64,7 @@
<button name="proforma_voucher" string="Validate" states="draft" class="oe_highlight"/>
<button name="%(act_pay_voucher)d" context="{'narration':narration, 'title':'Customer Payment', 'type':'receipt', 'partner_id':partner_id, 'reference':reference, 'amount':amount}" type="action" string="Pay" attrs="{'invisible':['|',('pay_now','=','pay_now'),'|',('state','=','draft'), ('paid','=',True)]}" class="oe_highlight"/>
<button name="cancel_voucher" string="Cancel" states="draft,proforma" />
<button name="cancel_voucher" string="Cancel" type="object" states="posted" confirm="Are you sure to confirm this record ?"/>
<button name="cancel_voucher" string="Cancel" type="object" states="posted" confirm="Are you sure you want to cancel this receipt?"/>
<button name="action_cancel_draft" type="object" states="cancel" string="Set to Draft"/>
<field name="state" widget="statusbar" statusbar_visible="draft,posted" statusbar_colors='{"proforma":"blue"}'/>
</header>
@ -89,7 +89,7 @@
<page string="Sales Information">
<field name="line_cr_ids" on_change="onchange_price(line_cr_ids, tax_id, partner_id)" context="{'journal_id':journal_id, 'type':type, 'partner_id':partner_id}">
<tree string="Sales Lines" editable="bottom">
<field name="account_id" domain="[('user_type.report_type','=','income'),('type','!=','view')]" widget="selection" groups="account.group_account_user"/>
<field name="account_id" domain="[('user_type.report_type','=','income'),('type','!=','view')]" widget="selection"/>
<field name="name"/>
<field name="amount" sum="Total"/>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
@ -147,8 +147,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@ -209,7 +209,7 @@
<button name="proforma_voucher" string="Validate" states="draft" class="oe_highlight"/>
<button name="%(act_pay_bills)d" context="{'narration':narration, 'title':'Bill Payment', 'type':'payment', 'partner_id': partner_id, 'reference':reference}" type="action" string="Pay Bill" attrs="{'invisible':['|',('pay_now','=','pay_now'),'|',('state','=','draft'), ('paid','=',True)]}" class="oe_highlight"/>
<button name="cancel_voucher" string="Cancel" states="draft,proforma" />
<button name="cancel_voucher" string="Cancel" type="object" states="posted" confirm="Are you sure to confirm this record ?"/>
<button name="cancel_voucher" string="Cancel" type="object" states="posted" confirm="Are you sure you want to cancel this receipt?"/>
<button name="action_cancel_draft" type="object" states="cancel" string="Set to Draft"/>
<field name="state" widget="statusbar" statusbar_visible="draft,posted" statusbar_colors='{"proforma":"blue"}'/>
</header>
@ -243,7 +243,7 @@
<page string="Bill Information">
<field name="line_dr_ids" on_change="onchange_price(line_dr_ids, tax_id, partner_id)" context="{'journal_id':journal_id,'partner_id':partner_id}">
<tree string="Expense Lines" editable="bottom">
<field name="account_id" widget="selection" domain="[('user_type.report_type','=','expense'), ('type','!=','view')]" groups="account.group_account_user"/>
<field name="account_id" widget="selection" domain="[('user_type.report_type','=','expense'), ('type','!=','view')]"/>
<field name="name"/>
<field name="amount"/>
<field name="account_analytic_id" groups="analytic.group_analytic_accounting"/>
@ -303,8 +303,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -96,9 +96,29 @@ class account_analytic_account(osv.osv):
res[row['id']][field] = row[field]
return self._compute_level_tree(cr, uid, ids, child_ids, res, fields, context)
def _complete_name_calc(self, cr, uid, ids, prop, unknow_none, unknow_dict):
res = self.name_get(cr, uid, ids)
return dict(res)
def name_get(self, cr, uid, ids, context=None):
res = []
for id in ids:
elmt = self.browse(cr, uid, id, context=context)
res.append((id, self._get_one_full_name(elmt)))
return res
def _get_full_name(self, cr, uid, ids, name=None, args=None, context=None):
if context == None:
context = {}
res = {}
for elmt in self.browse(cr, uid, ids, context=context):
res[elmt.id] = self._get_one_full_name(elmt)
return res
def _get_one_full_name(self, elmt, level=6):
if level<=0:
return '...'
if elmt.parent_id:
parent_path = self._get_one_full_name(elmt.parent_id, level-1) + "/"
else:
parent_path = ''
return parent_path + elmt.name
def _child_compute(self, cr, uid, ids, name, arg, context=None):
result = {}
@ -139,13 +159,13 @@ class account_analytic_account(osv.osv):
_columns = {
'name': fields.char('Account/Contract Name', size=128, required=True),
'complete_name': fields.function(_complete_name_calc, type='char', string='Full Account Name'),
'complete_name': fields.function(_get_full_name, type='char', string='Full Account Name'),
'code': fields.char('Reference', size=24, select=True),
'type': fields.selection([('view','Analytic View'), ('normal','Analytic Account'),('contract','Contract or Project'),('template','Template of Project')], 'Type of Account', required=True,
'type': fields.selection([('view','Analytic View'), ('normal','Analytic Account'),('contract','Contract or Project'),('template','Template of Contract')], 'Type of Account', required=True,
help="If you select the View Type, it means you won\'t allow to create journal entries using that account.\n"\
"The type 'Analytic account' stands for usual accounts that you only want to use in accounting.\n"\
"If you select Contract or Project, it offers you the possibility to manage the validity and the invoicing options for this account.\n"\
"The special type 'Template of Project' allows you to define a template with default data that you can reuse easily."),
"The special type 'Template of Contract' allows you to define a template with default data that you can reuse easily."),
'template_id': fields.many2one('account.analytic.account', 'Template of Contract'),
'description': fields.text('Description'),
'parent_id': fields.many2one('account.analytic.account', 'Parent Analytic Account', select=2),
@ -156,7 +176,7 @@ class account_analytic_account(osv.osv):
'debit': fields.function(_debit_credit_bal_qtty, type='float', string='Debit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
'credit': fields.function(_debit_credit_bal_qtty, type='float', string='Credit', multi='debit_credit_bal_qtty', digits_compute=dp.get_precision('Account')),
'quantity': fields.function(_debit_credit_bal_qtty, type='float', string='Quantity', multi='debit_credit_bal_qtty'),
'quantity_max': fields.float('Prepaid Units', help='Sets the higher limit of time to work on the contract.'),
'quantity_max': fields.float('Prepaid Service Units', help='Sets the higher limit of time to work on the contract, based on the timesheet. (for instance, number of hours in a limited support contract.)'),
'partner_id': fields.many2one('res.partner', 'Customer'),
'user_id': fields.many2one('res.users', 'Project Manager'),
'manager_id': fields.many2one('res.users', 'Account Manager'),
@ -305,8 +325,15 @@ class account_analytic_line(osv.osv):
'company_id': fields.related('account_id', 'company_id', type='many2one', relation='res.company', string='Company', store=True, readonly=True),
}
def _get_default_date(self, cr, uid, context=None):
return fields.date.context_today(self, cr, uid, context=context)
def __get_default_date(self, cr, uid, context=None):
return self._get_default_date(cr, uid, context=context)
_defaults = {
'date': lambda *a: time.strftime('%Y-%m-%d'),
'date': __get_default_date,
'company_id': lambda self,cr,uid,c: self.pool.get('res.company')._company_default_get(cr, uid, 'account.analytic.line', context=c),
'amount': 0.00
}

View File

@ -21,7 +21,7 @@
<group name="main">
<group>
<field name="partner_id" on_change="on_change_partner_id(partner_id, name)" attrs="{'required':[('type','=','contract')]}"/>
<field name="partner_id" on_change="on_change_partner_id(partner_id, name)"/>
<field name="manager_id"/>
<field name="code"/>
<field name="currency_id" attrs="{'invisible': ['|',('type', '&lt;&gt;', 'view'), ('company_id', '&lt;&gt;', False)]}"/>
@ -39,8 +39,10 @@
<p colspan="2" class="oe_grey oe_edit_only">
Once the end date of the contract is
passed or the maximum number of service
units is reached, the account manager
is warned by email to renew the contract.
units (e.g. support contract) is
reached, the account manager is warned
by email to renew the contract with the
customer.
</p>
<field name="date_start"/>
<label for="date" string="End Date"/>
@ -54,8 +56,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -9,9 +9,9 @@
<field name="arch" type="xml">
<xpath expr='//tr[@name="total"]' position='before'>
<tr>
<td class="oe_grey">
<td class="oe_timesheet_grey">
<label for="charge_expenses"/>
</td><td class="oe_grey">
</td><td class="oe_timesheet_grey">
<field name="charge_expenses"/>
</td><td>
<field class="oe_form_inline" name="est_expenses" attrs="{'invisible': [('charge_expenses','=',False)]}"/>
@ -21,17 +21,22 @@
<field class="oe_form_inline" name="remaining_expense" attrs="{'invisible': [('charge_expenses','=',False)]}"/>
</td><td>
<field class="oe_form_inline" name="expense_to_invoice" attrs="{'invisible': [('charge_expenses','=',False)]}"/>
</td><td>
<button
name="hr_to_invoice_expense"
class="oe_link oe_e"
string="/" type="object"
attrs="{'invisible': [('expense_to_invoice','=',0)]}"/>
</td><td>
</td><td attrs="{'invisible': [('charge_expenses','=',False)]}" class="oe_timesheet_action">
<span attrs="{'invisible': [('expense_to_invoice','=',0)]}" class="oe_grey">
<button
name="hr_to_invoice_expense"
class="oe_link"
string="⇒ Invoice" type="object"
/>
or view
</span>
<span attrs="{'invisible': [('expense_to_invoice','&lt;&gt;',0)]}" class="oe_grey">
Nothing to invoice, create
</span>
<button
name="open_hr_expense"
class="oe_link"
string="Expenses" type="object" attrs="{'invisible': [('charge_expenses','=',False)]}"/>
string="expenses" type="object"/>
</td>
</tr>
</xpath>

View File

@ -21,39 +21,51 @@
from osv import fields,osv
from tools.translate import _
import decimal_precision as dp
class analytic_user_funct_grid(osv.osv):
_name="analytic.user.funct.grid"
_description= "Relation table between users and products on a analytic account"
_description= "Price per User"
_columns={
'user_id': fields.many2one("res.users", "User", required=True,),
'product_id': fields.many2one("product.product", "Product", required=True,),
'product_id': fields.many2one("product.product", "Service", required=True,),
'account_id': fields.many2one("account.analytic.account", "Analytic Account", required=True,),
}
'uom_id': fields.related("product_id", "uom_id", relation="product.uom", string="Unit of Measure", type="many2one", readonly=True),
'price': fields.float('Price', digits_compute=dp.get_precision('Product Price'), help="Price per hour for this user.", required=True),
}
def onchange_user_product_id(self, cr, uid, ids, user_id, product_id, context=None):
if not user_id:
return {}
emp_obj = self.pool.get('hr.employee')
emp_id = emp_obj.search(cr, uid, [('user_id', '=', user_id)], context=context)
if not emp_id:
return {}
analytic_user_funct_grid()
value = {}
emp = emp_obj.browse(cr, uid, emp_id[0], context=context)
if emp.product_id and not product_id:
value['product_id'] = emp.product_id.id
prod = emp.product_id
if product_id:
prod = self.pool.get('product.product').browse(cr, uid, product_id, context=context)
if prod:
value['price'] = prod.list_price
value['uom_id'] = prod.uom_id.id
return {'value': value}
class account_analytic_account(osv.osv):
_inherit = "account.analytic.account"
_columns = {
'user_product_ids': fields.one2many('analytic.user.funct.grid', 'account_id', 'Users/Products Rel.'),
}
account_analytic_account()
class hr_analytic_timesheet(osv.osv):
_inherit = "hr.analytic.timesheet"
# Look in account, if no value for the user => look in parent until there is no more parent to look
# Take the first found... if nothing found => return False
def _get_related_user_account_recursiv(self, cr, uid, user_id, account_id):
temp=self.pool.get('analytic.user.funct.grid').search(cr, uid, [('user_id', '=', user_id),('account_id', '=', account_id) ])
account=self.pool.get('account.analytic.account').browse(cr, uid, account_id)
if temp:
@ -64,7 +76,6 @@ class hr_analytic_timesheet(osv.osv):
else:
return False
def on_change_account_id(self, cr, uid, ids, account_id, user_id=False, unit_amount=0):
res = {}
if not (account_id):
@ -106,12 +117,6 @@ class hr_analytic_timesheet(osv.osv):
return res
def on_change_user_id(self, cr, uid, ids, user_id, account_id, unit_amount=0):
res = {}
if not (user_id):
#avoid a useless call to super
return res
#get the old values from super
res = super(hr_analytic_timesheet, self).on_change_user_id(cr, uid, ids, user_id)
if account_id:
@ -141,7 +146,11 @@ class hr_analytic_timesheet(osv.osv):
res ['value']['general_account_id']= a
return res
hr_analytic_timesheet()
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"
def _get_invoice_price(self, cr, uid, account, product_id, user_id, qty, context = {}):
for grid in account.user_product_ids:
if grid.user_id.id==user_id:
return grid.price
return super(account_analytic_line, self)._get_invoice_price(cr, uid, account, product_id, user_id, qty, context)
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -7,9 +7,11 @@
<field name="name">analytic_user_funct_grid.tree</field>
<field name="model">analytic.user.funct.grid</field>
<field name="arch" type="xml">
<tree string="User's Product for this Analytic Account" editable="bottom">
<field name="user_id" required="1"/>
<field name="product_id" required="1" />
<tree string="Invoicing Data" editable="bottom">
<field name="user_id" on_change="onchange_user_product_id(user_id, product_id)"/>
<field name="product_id" on_change="onchange_user_product_id(user_id, product_id)" domain="[('type','=','service')]"/>
<field name="price"/>
<field name="uom_id"/>
</tree>
</field>
</record>
@ -18,9 +20,13 @@
<field name="name">analytic_user_funct_grid.form</field>
<field name="model">analytic.user.funct.grid</field>
<field name="arch" type="xml">
<form string="User's Product for this Analytic Account">
<field name="user_id" required="1"/>
<field name="product_id" required="1"/>
<form string="Invoicing Data">
<group>
<field name="user_id" on_change="onchange_user_product_id(user_id, product_id)"/>
<field name="product_id" domain="[('type','=','service')]" on_change="onchange_user_product_id(user_id, product_id)"/>
<field name="price"/>
<field name="uom_id"/>
</group>
</form>
</field>
</record>
@ -29,13 +35,26 @@
<record model="ir.ui.view" id="view_account_analytic_account_form_inherit">
<field name="name">account.analytic.account.form</field>
<field name="model">account.analytic.account</field>
<field eval="60" name="priority"/>
<field name="inherit_id" ref="analytic.view_account_analytic_account_form"/>
<field name="arch" type="xml">
<xpath expr="//notebook/page" position="after">
<page string="Users/Products Rel.">
<field name="user_product_ids" colspan="4" nolabel="1"/>
</page>
</xpath>
<separator name="description" position="before">
<div name="user_function_price" attrs="{'invisible': [('invoice_on_timesheets','=',False)]}">
<separator string="Invoice Price Rate per User"/>
<p class="oe_grey oe_edit_only">
Define a specific service (e.g. Senior Consultant)
and price for some users to use these data instead
of the default values when invoicing the customer.
</p>
<p class="oe_grey oe_edit_only">
OpenERP will recursively search on parent accounts
to check if specific conditions are defined for a
specific user. This allows to set invoicing
conditions for a group of contracts.
</p>
<field name="user_product_ids"/>
</div>
</separator>
</field>
</record>

View File

@ -0,0 +1,112 @@
# Polish translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 01:37+0100\n"
"PO-Revision-Date: 2012-10-25 17:11+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Polish <pl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. #-#-#-#-# auth_openid.pot (OpenERP Server 6.1rc1) #-#-#-#-#
#. module: auth_openid
#. #-#-#-#-# auth_openid.pot.web (PROJECT VERSION) #-#-#-#-#
#. openerp-web
#: view:res.users:0
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:12
msgid "OpenID"
msgstr ""
#. #-#-#-#-# auth_openid.pot (OpenERP Server 6.1rc1) #-#-#-#-#
#. module: auth_openid
#. #-#-#-#-# auth_openid.pot.web (PROJECT VERSION) #-#-#-#-#
#. openerp-web
#: field:res.users,openid_url:0
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:47
msgid "OpenID URL"
msgstr ""
#. module: auth_openid
#: help:res.users,openid_email:0
msgid "Used for disambiguation in case of a shared OpenID URL"
msgstr ""
#. module: auth_openid
#: sql_constraint:res.users:0
msgid "You can not have two users with the same login !"
msgstr ""
#. module: auth_openid
#: field:res.users,openid_email:0
msgid "OpenID Email"
msgstr ""
#. module: auth_openid
#: constraint:res.users:0
msgid "The chosen company is not in the allowed companies for this user"
msgstr ""
#. module: auth_openid
#: field:res.users,openid_key:0
msgid "OpenID Key"
msgstr ""
#. module: auth_openid
#: model:ir.model,name:auth_openid.model_res_users
msgid "res.users"
msgstr ""
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:8
msgid "Password"
msgstr "Hasło"
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:9
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:10
msgid "Google"
msgstr "Google"
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:10
msgid "Google Apps"
msgstr ""
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:11
msgid "Launchpad"
msgstr "Launchpad"
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:20
msgid "Google Apps Domain:"
msgstr ""
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:24
msgid "Username:"
msgstr ""
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:28
msgid "OpenID URL:"
msgstr ""
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:35
msgid "Google Apps Domain"
msgstr ""
#. openerp-web
#: /home/odo/repositories/addons/trunk/auth_openid/static/src/xml/auth_openid.xml:41
msgid "Username"
msgstr ""

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:36+0000\n"
"PO-Revision-Date: 2012-05-10 17:50+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
"PO-Revision-Date: 2012-10-25 16:32+0000\n"
"Last-Translator: ccdos <Unknown>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:35+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: base_action_rule
#: help:base.action.rule,act_mail_to_user:0
@ -315,7 +315,7 @@ msgstr "激活"
#: code:addons/base_action_rule/base_action_rule.py:329
#, python-format
msgid "No Email ID Found for your Company address!"
msgstr ""
msgstr "公司地址中没有设置Email !"
#. module: base_action_rule
#: field:base.action.rule,act_remind_user:0

View File

@ -1151,7 +1151,7 @@ rule or repeating pattern of time to exclude from the recurring rule."),
context = {}
result = []
for data in super(calendar_event, self).read(cr, uid, select, context=context):
for data in super(calendar_event, self).read(cr, uid, select, ['rrule', 'exdate', 'exrule', 'date'], context=context):
if not data['rrule']:
result.append(data['id'])
continue

View File

@ -170,12 +170,7 @@
</group>
</group>
</page>
<!--
Temporarily removing invitation feature as the implementation
was not clean. Invitation should be trigerred automatically
based on partner_ids.
-->
<page string="Invitations">
<page string="Invitations" groups="base.group_no_one">
<field name="attendee_ids" widget="one2many" mode="tree">
<tree string="Invitation details" editable="top">
<field name="partner_id"/>
@ -224,8 +219,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@ -333,9 +328,6 @@
<field name="view_id" ref="view_crm_meeting_gantt"/>
</record>
<menuitem id="menu_crm_meeting" parent="base.menu_sales" sequence="8"
name="Calendar" action="action_crm_meeting"/>
<menuitem name="Calendar"
id="mail_menu_calendar" parent="mail.mail_my_stuff"
sequence="10" action="action_crm_meeting"/>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:36+0000\n"
"PO-Revision-Date: 2010-10-30 10:01+0000\n"
"Last-Translator: OpenERP Administrators <Unknown>\n"
"PO-Revision-Date: 2012-10-26 09:05+0000\n"
"Last-Translator: Chertykov Denis <chertykov@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:20+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-27 04:59+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: board
#: view:res.log.report:0
@ -103,7 +103,7 @@ msgstr "Месячная активность на документ"
#. module: board
#: view:board.board:0
msgid "Configuration Overview"
msgstr ""
msgstr "Обзор конфигурации"
#. module: board
#: model:ir.actions.act_window,name:board.action_view_board_list_form
@ -211,7 +211,7 @@ msgstr "Январь"
#. module: board
#: view:board.board:0
msgid "Users"
msgstr ""
msgstr "Пользователи"
#. module: board
#: selection:res.log.report,month:0
@ -262,7 +262,7 @@ msgstr "Модель"
#. module: board
#: model:ir.actions.act_window,name:board.board_homepage_action
msgid "Home Page"
msgstr ""
msgstr "Домашняя страница"
#. module: board
#: model:ir.actions.act_window,name:board.action_latest_activities_tree

View File

@ -86,7 +86,6 @@ Dashboard for CRM will include:
'crm_lead_menu.xml',
'crm_meeting_menu.xml',
'crm_meeting_shortcut_data.xml',
'crm_phonecall_view.xml',
'crm_phonecall_menu.xml',

View File

@ -6,13 +6,6 @@
<field name="groups_id" eval="[(4,ref('base.group_sale_salesman'))]"/>
</record>
<record id="ir_ui_view_sc_calendar_demo" model="ir.ui.view_sc">
<field name="name">Meetings</field>
<field name="resource">ir.ui.menu</field>
<field name="user_id" ref="base.user_demo"/>
<field name="res_id" ref="base_calendar.menu_crm_meeting"/>
</record>
<record model="crm.case.section" id="crm_case_section_1">
<field name="name">Sales Marketing Department</field>
<field name="code">SMD</field>

View File

@ -223,8 +223,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>
@ -525,8 +525,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -1,13 +0,0 @@
<?xml version="1.0"?>
<openerp>
<data noupdate="1">
<record id="ir_ui_view_sc_calendar0" model="ir.ui.view_sc">
<field name="name">Meetings</field>
<field name="resource">ir.ui.menu</field>
<field name="user_id" ref="base.user_root"/>
<field name="res_id" ref="base_calendar.menu_crm_meeting"/>
</record>
</data>
</openerp>

View File

@ -125,7 +125,7 @@
</div>
<div class="oe_title">
<div class="oe_edit_only">
<label for="name" string="Title"/>
<label for="name"/>
</div>
<h1><field name="name" required="1"/></h1>
<div class="oe_edit_only">
@ -150,8 +150,8 @@
<field name="description" placeholder="Description..."/>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -132,8 +132,8 @@
</page>
</notebook>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers" help="Followers of this salesteam follow automatically all opportunities related to this salesteam."/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 01:37+0100\n"
"PO-Revision-Date: 2012-03-22 16:17+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
"PO-Revision-Date: 2012-10-25 16:22+0000\n"
"Last-Translator: fenshuajiang <openerp@126.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-08-28 06:06+0000\n"
"X-Generator: Launchpad (build 15864)\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: crm
#: view:crm.lead.report:0
@ -172,7 +172,7 @@ msgstr "预计结束月份"
#. module: crm
#: view:crm.lead2opportunity.partner.mass:0
msgid "Assigned Opportunities to"
msgstr ""
msgstr "分配商机给"
#. module: crm
#: view:crm.lead:0 field:crm.lead,partner_id:0 view:crm.lead.report:0
@ -583,7 +583,7 @@ msgstr "结束日期"
#. module: crm
#: view:crm.opportunity2phonecall:0 view:crm.phonecall2phonecall:0
msgid "Schedule/Log a Call"
msgstr ""
msgstr "计划/记录一个电话"
#. module: crm
#: constraint:base.action.rule:0
@ -763,7 +763,7 @@ msgstr "继续"
#. module: crm
#: field:crm.segmentation,som_interval:0
msgid "Days per Period"
msgstr ""
msgstr "每阶段的天数"
#. module: crm
#: field:crm.meeting,byday:0
@ -926,7 +926,7 @@ msgstr "开启天数"
#. module: crm
#: view:crm.meeting:0
msgid "Show Time as"
msgstr ""
msgstr "显示时间为"
#. module: crm
#: view:crm.phonecall2partner:0
@ -1299,7 +1299,7 @@ msgstr "写日期"
#. module: crm
#: view:crm.meeting:0
msgid "End of Recurrency"
msgstr ""
msgstr "结束循环"
#. module: crm
#: view:crm.meeting:0
@ -1835,7 +1835,7 @@ msgstr "回复到"
#. module: crm
#: view:crm.case.section:0
msgid "Select Stages for this Sales Team"
msgstr ""
msgstr "为这个销售团队选择阶段"
#. module: crm
#: view:board.board:0
@ -2683,7 +2683,7 @@ msgstr "这联系的邮件地址"
#. module: crm
#: field:crm.lead,referred:0
msgid "Referred by"
msgstr ""
msgstr "推荐人"
#. module: crm
#: view:crm.lead:0 model:ir.model,name:crm.model_crm_add_note

View File

@ -179,8 +179,8 @@
</group>
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
</div>
</form>
</field>

View File

@ -0,0 +1,49 @@
# Polish translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:36+0000\n"
"PO-Revision-Date: 2012-10-25 16:50+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Polish <pl@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: decimal_precision
#: field:decimal.precision,digits:0
msgid "Digits"
msgstr "Cyfry"
#. module: decimal_precision
#: model:ir.actions.act_window,name:decimal_precision.action_decimal_precision_form
#: model:ir.ui.menu,name:decimal_precision.menu_decimal_precision_form
msgid "Decimal Accuracy"
msgstr ""
#. module: decimal_precision
#: field:decimal.precision,name:0
msgid "Usage"
msgstr "Użycie"
#. module: decimal_precision
#: sql_constraint:decimal.precision:0
msgid "Only one value can be defined for each given usage!"
msgstr ""
#. module: decimal_precision
#: view:decimal.precision:0
msgid "Decimal Precision"
msgstr ""
#. module: decimal_precision
#: model:ir.model,name:decimal_precision.model_decimal_precision
msgid "decimal.precision"
msgstr ""

View File

@ -103,7 +103,7 @@ class DocIndex(indexer):
return _to_unicode(data)
except OSError:
_logger.warn("Failed attempt to execute antiword (MS Word reader). Antiword is necessary to index the file %s of MIME type %s. Detailed error available at DEBUG level.", fname, self._getMimeTypes()[0])
_logger.warning("Failed attempt to execute antiword (MS Word reader). Antiword is necessary to index the file %s of MIME type %s. Detailed error available at DEBUG level.", fname, self._getMimeTypes()[0])
_logger.debug("Trace of the failed file indexing attempt.", exc_info=True)
return False

View File

@ -29,7 +29,7 @@ _logger = logging.getLogger(__name__)
try:
import controllers
except ImportError:
_logger.warn(
_logger.warning(
"""Could not load openerp-web section of EDI, EDI will not behave correctly
To fix, launch openerp-web in embedded mode""")

View File

@ -165,11 +165,8 @@ class test_message_compose(test_mail.TestMailMockups):
self.assertEqual(message_pigs.body, _body_html1, 'mail.message body on Pigs incorrect')
self.assertEqual(message_bird.body, _body_html2, 'mail.message body on Bird incorrect')
# Test: partner_ids: p_a_id (default) + 3 newly created partners
message_pigs_pids = [partner.id for partner in message_pigs.partner_ids]
message_bird_pids = [partner.id for partner in message_bird.partner_ids]
message_pigs_pids = [partner.id for partner in message_pigs.notified_partner_ids]
message_bird_pids = [partner.id for partner in message_bird.notified_partner_ids]
partner_ids = self.res_partner.search(cr, uid, [('email', 'in', ['b@b.b', 'c@c.c', 'd@d.d'])])
self.assertEqual(len(message_pigs_pids), len(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of partner_ids')
self.assertEqual(len(message_bird_pids), len(partner_ids), 'mail.message on bird partner_ids incorrect')
self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird partner_ids incorrect')
self.assertEqual(set(message_pigs_pids), set(partner_ids), 'mail.message on pigs incorrect number of notified_partner_ids')
self.assertEqual(set(message_bird_pids), set(partner_ids), 'mail.message on bird notified_partner_ids incorrect')

View File

@ -8,7 +8,7 @@
<field name="inherit_id" ref="mail.email_compose_message_wizard_form"/>
<field name="arch" type="xml">
<data>
<xpath expr="//form/notebook" position="after">
<xpath expr="//form/group" position="after">
<group attrs="{'invisible':[('use_template','=',False)]}">
<field name="use_template" invisible="1"
on_change="onchange_use_template(use_template, template_id, composition_mode, model, res_id, context)"/>

View File

@ -204,8 +204,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
</div>
</form>
</field>
@ -486,8 +486,8 @@
</group>
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
</div>
</form>
</field>

View File

@ -112,7 +112,7 @@ class hr_job(osv.osv):
'requirements': fields.text('Requirements'),
'department_id': fields.many2one('hr.department', 'Department'),
'company_id': fields.many2one('res.company', 'Company'),
'state': fields.selection([('open', 'In Position'), ('recruit', 'In Recruitement')], 'Status', readonly=True, required=True,
'state': fields.selection([('open', 'No Recruitment'), ('recruit', 'Recruitement in Progress')], 'Status', readonly=True, required=True,
help="By default 'In position', set it to 'In Recruitment' if recruitment process is going on for this job position."),
}
_defaults = {

View File

@ -330,8 +330,8 @@
<field name="arch" type="xml">
<form string="Job" version="7.0">
<header>
<button name="job_recruitement" string="In Recruitement" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="job_open" string="Recruitment Done" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="job_recruitement" string="Launch Recruitement" states="open" type="object" class="oe_highlight" groups="base.group_user"/>
<button name="job_open" string="Stop Recruitment" states="recruit" type="object" class="oe_highlight" groups="base.group_user"/>
<field name="state" widget="statusbar" statusbar_visible="recruit,open"/>
</header>
<sheet>
@ -359,8 +359,8 @@
</div>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 01:37+0100\n"
"PO-Revision-Date: 2012-08-18 17:32+0000\n"
"Last-Translator: Heling Yao <Unknown>\n"
"PO-Revision-Date: 2012-10-25 16:29+0000\n"
"Last-Translator: fenshuajiang <openerp@126.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:21+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: hr
#: model:process.node,name:hr.process_node_openerpuser0
@ -181,7 +181,7 @@ msgstr "女性"
#: help:hr.job,expected_employees:0
msgid ""
"Expected number of employees for this job position after new recruitment."
msgstr ""
msgstr "新的招聘后这个工作岗位上期望的员工人数"
#. module: hr
#: model:ir.ui.menu,name:hr.menu_open_view_attendance_reason_new_config
@ -685,7 +685,7 @@ msgstr "下属"
#. module: hr
#: field:hr.job,no_of_employee:0
msgid "Number of employees currently occupying this job position."
msgstr ""
msgstr "这个工作岗位上现有的员工人数"
#. module: hr
#: field:hr.job,no_of_recruitment:0

View File

@ -28,7 +28,7 @@ class hr_config_settings(osv.osv_memory):
_columns = {
'module_hr_timesheet_sheet': fields.boolean('Allow timesheets validation by managers',
help ="""This installs the module hr_timesheet_sheet."""),
'module_hr_attendance': fields.boolean('Track attendances',
'module_hr_attendance': fields.boolean('Install attendances feature',
help ="""This installs the module hr_attendance."""),
'module_hr_timesheet': fields.boolean('Manage timesheets',
help ="""This installs the module hr_timesheet."""),
@ -42,6 +42,8 @@ class hr_config_settings(osv.osv_memory):
help ="""This installs the module hr_contract."""),
'module_hr_evaluation': fields.boolean('Organize employees periodic evaluation',
help ="""This installs the module hr_evaluation."""),
'module_account_analytic_analysis': fields.boolean('Allow invoicing based on timesheets (the sale application will be installed)',
help ="""This installs the module account_analytic_analysis, which will install sales management too."""),
'module_hr_payroll': fields.boolean('Manage payroll',
help ="""This installs the module hr_payroll."""),
}

View File

@ -69,6 +69,10 @@
<field name="module_hr_timesheet_sheet" class="oe_inline"/>
<label for="module_hr_timesheet_sheet"/>
</div>
<div name="account_analytic_analysis">
<field name="module_account_analytic_analysis" class="oe_inline"/>
<label for="module_account_analytic_analysis"/>
</div>
<div name="hr_attendance">
<field name="module_hr_attendance" on_change="onchange_hr_attendance(module_hr_attendance)" class="oe_inline"/>
<label for="module_hr_attendance"/>

View File

@ -25,7 +25,7 @@ class hr_attendance_config_settings(osv.osv_memory):
_inherit = 'hr.config.settings'
_columns = {
'group_hr_attendance': fields.boolean('Track attendances',
'group_hr_attendance': fields.boolean('Track attendances for all employees',
implied_group='base.group_hr_attendance',
help="Allocates attendance group to all users."),
}

View File

@ -210,8 +210,8 @@
</group>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 01:37+0100\n"
"PO-Revision-Date: 2012-05-10 17:47+0000\n"
"Last-Translator: Jeff Wang <wjfonhand@hotmail.com>\n"
"PO-Revision-Date: 2012-10-25 17:16+0000\n"
"Last-Translator: ccdos <Unknown>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:27+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: hr_evaluation
#: help:hr_evaluation.plan.phase,send_anonymous_manager:0
@ -304,7 +304,7 @@ msgstr "上个月完成的评估"
#. module: hr_evaluation
#: model:ir.model,name:hr_evaluation.model_mail_compose_message
msgid "Email composition wizard"
msgstr ""
msgstr "电子邮件撰写向导"
#. module: hr_evaluation
#: view:hr.evaluation.report:0

View File

@ -64,15 +64,15 @@ class hr_expense_expense(osv.osv):
_description = "Expense"
_order = "id desc"
_columns = {
'name': fields.char('Description', size=128, required=True),
'name': fields.char('Description', size=128),
'id': fields.integer('Sheet ID', readonly=True),
'date': fields.date('Date', select=True),
'journal_id': fields.many2one('account.journal', 'Force Journal', help = "The journal used when the expense is done."),
'employee_id': fields.many2one('hr.employee', "Employee", required=True),
'user_id': fields.many2one('res.users', 'User', required=True),
'date_confirm': fields.date('Confirmation Date', select=True, help = "Date of the confirmation of the sheet expense. It's filled when the button Confirm is pressed."),
'date_valid': fields.date('Validation Date', select=True, help = "Date of the acceptation of the sheet expense. It's filled when the button Accept is pressed."),
'user_valid': fields.many2one('res.users', 'Validation User'),
'date_confirm': fields.date('Confirmation Date', select=True, help="Date of the confirmation of the sheet expense. It's filled when the button Confirm is pressed."),
'date_valid': fields.date('Validation Date', select=True, help="Date of the acceptation of the sheet expense. It's filled when the button Accept is pressed."),
'user_valid': fields.many2one('res.users', 'Validation By'),
'account_move_id': fields.many2one('account.move', 'Ledger Posting'),
'line_ids': fields.one2many('hr.expense.line', 'expense_id', 'Expense Lines', readonly=True, states={'draft':[('readonly',False)]} ),
'note': fields.text('Note'),
@ -189,7 +189,7 @@ class hr_expense_expense(osv.osv):
raise osv.except_osv(_('Error!'), _('The employee must have a home address.'))
acc = exp.employee_id.address_home_id.property_account_payable.id
voucher = {
'name': exp.name,
'name': exp.name or '/',
'reference': sequence_obj.get(cr, uid, 'hr.expense.invoice'),
'account_id': acc,
'type': 'purchase',

View File

@ -79,7 +79,7 @@
</group>
<group>
<field name="name"/>
<field name="user_valid"/>
<field name="user_valid" attrs="{'invisible': [('state','=','draft')]}"/>
<field name="currency_id" groups="base.group_multi_currency" on_change="onchange_currency_id(currency_id, company_id)"/>
</group>
</group>
@ -128,9 +128,9 @@
</group>
</group>
</page>
<page string="Other Info">
<page string="Accounting" groups="account.group_account_user">
<group>
<group string="Accounting Data" groups="account.group_account_user">
<group string="Accounting Data">
<field name="journal_id" widget="selection" domain="[('type', '=', 'purchase')]"/>
<field name="voucher_id" context="{'form_view_ref': 'account_voucher.view_purchase_receipt_form'}"/>
</group>
@ -139,8 +139,8 @@
</notebook>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -121,8 +121,8 @@
</group>
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
</div>
</form>
</field>
@ -158,8 +158,8 @@
<field name="notes" nolabel="1" colspan="4" placeholder="Add a reason..."/>
</sheet>
<div class="oe_chatter">
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" colspan="4" widget="mail_thread" nolabel="1"/>
</div>
</form>
</field>

View File

@ -107,7 +107,7 @@
<form string="Jobs - Recruitment Form" version="7.0">
<header>
<button name="case_close_with_emp" string="Hire" type="object"
states="draft,open,pending" class="oe_highlight"/>
states="draft,open,pending,done" class="oe_highlight"/>
<button name="case_cancel" string="Refuse" type="object"
states="draft,open,pending" class="oe_highlight"/>
<field name="stage_id" widget="statusbar" clickable="True"/>
@ -183,8 +183,8 @@
<field name="description" placeholder="Feedback of interviews..."/>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -154,7 +154,7 @@ class hr_analytic_timesheet(osv.osv):
'date': lambda self, cr, uid, ctx: ctx.get('date', fields.date.context_today(self,cr,uid,context=ctx)),
'user_id': lambda obj, cr, uid, ctx: ctx.get('user_id', uid),
}
def on_change_account_id(self, cr, uid, ids, account_id):
def on_change_account_id(self, cr, uid, ids, account_id, context=None):
return {'value':{}}
def on_change_date(self, cr, uid, ids, date):
@ -190,17 +190,14 @@ class hr_analytic_timesheet(osv.osv):
'journal_id': self._getAnalyticJournal(cr, uid, context),
}}
hr_analytic_timesheet()
class account_analytic_account(osv.osv):
_inherit = 'account.analytic.account'
_description = 'Analytic Account'
_columns = {
'use_timesheets': fields.boolean('Timesheets', help="Check this field if this project manages timesheets"),
'use_timesheets': fields.boolean('Use Timesheets', help="Check this field if this project manages timesheets"),
}
def on_change_template(self, cr, uid, ids, template_id, context=None):
res = super(account_analytic_account, self).on_change_template(cr, uid, ids, template_id, context=context)
if template_id and 'value' in res:
@ -208,6 +205,4 @@ class account_analytic_account(osv.osv):
res['value']['use_timesheets'] = template.use_timesheets
return res
account_analytic_account()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -6,12 +6,12 @@
<field name="name">hr.analytic.timesheet.tree</field>
<field name="model">hr.analytic.timesheet</field>
<field name="arch" type="xml">
<tree editable="bottom" string="Timesheet Lines">
<tree editable="bottom" string="Timesheet Activities">
<field name="date" on_change="on_change_date(date)"/>
<field name="user_id" on_change="on_change_user_id(user_id)" required="1"/>
<field name="user_id" on_change="on_change_user_id(user_id)" required="1" options='{"no_open": True}'/>
<field name="journal_id" invisible="1"/>
<field name="name"/>
<field domain="[('type','=','normal'),('use_timesheets','=',1)]" name="account_id" context="{'default_use_timesheets': 1}"/>
<field domain="[('type','=','normal'),('use_timesheets','=',1)]" name="account_id" context="{'default_use_timesheets': 1, 'default_type': 'contract'}"/>
<field name="product_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" required="1" domain="[('type','=','service')]" invisible="1"/>
<field name="unit_amount" string="Duration" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" sum="Total time" widget="float_time"/>
<field name="product_uom_id" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" invisible="1"/>
@ -24,7 +24,7 @@
<field name="name">hr.analytic.timesheet.form</field>
<field name="model">hr.analytic.timesheet</field>
<field name="arch" type="xml">
<form string="Timesheet Lines" version="7.0">
<form string="Timesheet Activities" version="7.0">
<sheet>
<group>
<group>
@ -85,6 +85,23 @@
<field name="src_model">account.analytic.account</field>
<field name="view_mode">tree,form</field>
<field name="view_type">form</field>
<field name="help" type="html">
<p>
No activity yet on this contract.
</p><p>
In OpenERP, contracts and projects are implemented using
analytic account. So, you can track costs and revenues to analyse
your margins easily.
</p><p>
Costs will be created automatically when you register supplier
invoices, expenses or timesheets.
</p><p>
Revenues will be created automatically when you create customer
invoices. Customer invoices can be created based on sale orders
(fixed price invoices), on timesheets (based on the work done) or
on expenses (e.g. reinvoicing of travel costs).
</p>
</field>
</record>
@ -107,7 +124,7 @@
</record>
<record id="act_hr_timesheet_line_evry1_all_form" model="ir.actions.act_window">
<field name="name">Timesheet Lines</field>
<field name="name">Timesheet Activities</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr.analytic.timesheet</field>
<field name="view_type">form</field>
@ -116,12 +133,12 @@
<field name="search_view_id" ref="hr_timesheet_line_search"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to record your timesheets.
Click to record activities.
</p><p>
You can register and track your workings hours by project every
day. Every time spent on a project will become a cost in the
analytic accounting and can be re-invoiced to customers if
required.
analytic accounting/contract and can be re-invoiced to
customers if required.
</p>
</field>
</record>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:36+0000\n"
"PO-Revision-Date: 2012-05-10 18:19+0000\n"
"Last-Translator: Jeff Wang <wjfonhand@hotmail.com>\n"
"PO-Revision-Date: 2012-10-25 17:20+0000\n"
"Last-Translator: ccdos <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:15+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:43
@ -468,7 +468,7 @@ msgstr "一般信息"
#. module: hr_timesheet
#: model:ir.actions.act_window,name:hr_timesheet.action_hr_timesheet_my
msgid "My Current Timesheet"
msgstr ""
msgstr "我当前的时间表"
#. module: hr_timesheet
#: code:addons/hr_timesheet/report/user_timesheet.py:40

View File

@ -70,8 +70,7 @@ class account_analytic_account(osv.osv):
'amount_invoiced': fields.function(_invoiced_calc, string='Invoiced Amount',
help="Total invoiced"),
'to_invoice': fields.many2one('hr_timesheet_invoice.factor', 'Timesheet Invoicing Ratio',
help="This field allows you to define the rate in case you plan to reinvoice " \
"the costs in this analytic account: timesheets, expenses, ..."),
help="You usually invoice 100% of the timesheets. But if you mix fixed price and timesheet invoicing, you may use another ratio. For instance, if you do a 20% advance invoice (fixed price, based on a sale order), you should invoice the rest on timesheet with a 80% ratio."),
}
_defaults = {
'pricelist_id': lambda self, cr, uid, ctx: ctx.get('pricelist_id', False),

View File

@ -16,5 +16,10 @@
<field name="customer_name">50%</field>
<field name="factor">50.0</field>
</record>
<record id="timesheet_invoice_factor4" model="hr_timesheet_invoice.factor">
<field name="name">80%</field>
<field name="customer_name">80%</field>
<field name="factor">20.0</field>
</record>
</data>
</openerp>

View File

@ -9,8 +9,8 @@
<field name="arch" type="xml">
<xpath expr='//separator[@name="description"]' position='before'>
<group string="Invoice on Timesheets Options" name="invoice_on_timesheets" col="4">
<field name="pricelist_id" />
<field name="to_invoice" widget="selection"/>
<field name="pricelist_id" groups="product.group_sale_pricelist"/>
<field name="to_invoice"/>
</group>
</xpath>
<xpath expr="//field[@name='use_timesheets']" position="replace">
@ -91,6 +91,20 @@
</field>
</record>
<record id="view_hr_timesheet_line_search_to_invoice" model="ir.ui.view">
<field name="name">hr.analytic.timesheet.search.to_invoice</field>
<field name="model">hr.analytic.timesheet</field>
<field name="inherit_id" ref="hr_timesheet.hr_timesheet_line_search"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='account_id']" position="after">
<separator/>
<filter name="to_invoice" string="To Invoice" context="{'to_invoice': 1}" domain="[('invoice_id','=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
<filter name="invoiced" string="Invoiced" domain="[('invoice_id','!=',False), ('to_invoice','&lt;&gt;',False)]" icon="terp-dolar"/>
</xpath>
</field>
</record>
<record id="view_account_analytic_line_tree_inherit" model="ir.ui.view">
<field name="name">account.analytic.line.tree.to_invoice</field>
<field name="model">account.analytic.line</field>

View File

@ -36,14 +36,17 @@ class final_invoice_create(osv.osv_memory):
_description = 'Create invoice from timesheet final'
_columns = {
'date': fields.boolean('Date', help='Display date in the history of works'),
'time': fields.boolean('Time spent', help='Display time in the history of works'),
'name': fields.boolean('Name of entry', help='Display detail of work in the invoice line.'),
'time': fields.boolean('Time Spent', help='Display time in the history of works'),
'name': fields.boolean('Log of Activity', help='Display detail of work in the invoice line.'),
'price': fields.boolean('Cost', help='Display cost of the item you reinvoice'),
'product': fields.many2one('product.product', 'Product', help='The product that will be used to invoice the remaining amount'),
}
def do_create(self, cr, uid, ids, context=None):
data = self.read(cr, uid, ids, [], context=context)[0]
# hack for fixing small issue (context should not propagate implicitly between actions)
if 'default_type' in context:
del context['default_type']
ids = self.pool.get('account.analytic.line').search(cr, uid, [('invoice_id','=',False),('to_invoice','<>', False), ('account_id', 'in', context['active_ids'])], context=context)
invs = self.pool.get('account.analytic.line').invoice_cost_create(cr, uid, ids, data, context=context)
mod_obj = self.pool.get('ir.model.data')

View File

@ -29,6 +29,15 @@ from tools.translate import _
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"
def _get_invoice_price(self, cr, uid, account, product_id, user_id, qty, context = {}):
pro_price_obj = self.pool.get('product.pricelist')
if account.pricelist_id:
pl = account.pricelist_id.id
price = pro_price_obj.price_get(cr,uid,[pl], product_id, qty or 1.0, account.partner_id.id, context=context)[pl]
else:
price = 0.0
return price
#
# data = {
@ -45,7 +54,6 @@ class account_analytic_line(osv.osv):
invoice_obj = self.pool.get('account.invoice')
product_obj = self.pool.get('product.product')
invoice_factor_obj = self.pool.get('hr_timesheet_invoice.factor')
pro_price_obj = self.pool.get('product.pricelist')
fiscal_pos_obj = self.pool.get('account.fiscal.position')
product_uom_obj = self.pool.get('product.uom')
invoice_line_obj = self.pool.get('account.invoice.line')
@ -99,13 +107,13 @@ class account_analytic_line(osv.osv):
last_invoice = invoice_obj.create(cr, uid, curr_invoice, context=context2)
invoices.append(last_invoice)
cr.execute("SELECT product_id, to_invoice, sum(unit_amount), product_uom_id, name " \
cr.execute("SELECT product_id, user_id, to_invoice, sum(unit_amount), product_uom_id, name " \
"FROM account_analytic_line as line " \
"WHERE account_id = %s " \
"AND id IN %s AND to_invoice IS NOT NULL " \
"GROUP BY product_id, to_invoice, product_uom_id, name", (account.id, tuple(ids),))
"GROUP BY product_id, user_id, to_invoice, product_uom_id, name", (account.id, tuple(ids),))
for product_id, factor_id, qty, uom, line_name in cr.fetchall():
for product_id, user_id, factor_id, qty, uom, line_name in cr.fetchall():
if data.get('product'):
product_id = data['product'][0]
product = product_obj.browse(cr, uid, product_id, context=context2)
@ -118,11 +126,8 @@ class account_analytic_line(osv.osv):
ctx = context.copy()
ctx.update({'uom':uom})
if account.pricelist_id:
pl = account.pricelist_id.id
price = pro_price_obj.price_get(cr,uid,[pl], product_id, qty or 1.0, account.partner_id.id, context=ctx)[pl]
else:
price = 0.0
price = self._get_invoice_price(cr, uid, account, product_id, user_id, qty, ctx)
general_account = product.product_tmpl_id.property_account_income or product.categ_id.property_account_income_categ
if not general_account:
@ -164,7 +169,8 @@ class account_analytic_line(osv.osv):
details.append(line['name'])
note.append(u' - '.join(map(lambda x: unicode(x) or '',details)))
curr_line['name'] += "\n".join(map(lambda x: unicode(x) or '',note))
if note:
curr_line['name'] += "\n" + ("\n".join(map(lambda x: unicode(x) or '',note)))
invoice_line_obj.create(cr, uid, curr_line, context=context)
cr.execute("update account_analytic_line set invoice_id=%s WHERE account_id = %s and id IN %s", (last_invoice, account.id, tuple(ids)))

View File

@ -26,13 +26,19 @@
<field name="model">hr.timesheet.invoice.create.final</field>
<field name="arch" type="xml">
<form string="Invoice contract" version="7.0">
<separator string="Do you want to display work details on the invoice?" colspan="4"/>
<field name="date"/>
<field name="time"/>
<field name="name"/>
<field name="price"/>
<separator string="Force to use a special product" colspan="4"/>
<field name="product"/>
<p>Do you want to show details of each activity to your customer?</p>
<group>
<group>
<field name="date"/>
<field name="time"/>
</group><group>
<field name="name"/>
<field name="price"/>
</group>
</group>
<group string="Force to use a special product" groups="base.group_no_one">
<field name="product"/>
</group>
<footer>
<button name="do_create" string="Create Invoice" type="object" class="oe_highlight"/>
or

View File

@ -34,103 +34,22 @@ class hr_timesheet_sheet(osv.osv):
_order = "id desc"
_description="Timesheet"
def _total_attendances(self, cr, uid, ids, name, args, context=None):
""" Get the total attendance for the timesheets
Returns a dict like :
{id: {'total_per_day': {day: timedelta, ...},
},
...
}
"""
context = context or {}
attendance_obj = self.pool.get('hr.attendance')
res = {}
for sheet_id in ids:
sheet = self.browse(cr, uid, sheet_id, context=context)
# field attendances_ids of hr_timesheet_sheet.sheet only
# returns attendances of timesheet's current date
attendance_ids = attendance_obj.search(cr, uid, [('sheet_id', '=', sheet_id)], context=context)
attendances = attendance_obj.browse(cr, uid, attendance_ids, context=context)
total_attendance = {}
for attendance in [att for att in attendances
if att.action in ('sign_in', 'sign_out')]:
day = attendance.name[:10]
if not total_attendance.get(day, False):
total_attendance[day] = timedelta(seconds=0)
attendance_in_time = datetime.strptime(attendance.name, '%Y-%m-%d %H:%M:%S')
attendance_interval = timedelta(hours=attendance_in_time.hour,
minutes=attendance_in_time.minute,
seconds=attendance_in_time.second)
if attendance.action == 'sign_in':
total_attendance[day] -= attendance_interval
else:
total_attendance[day] += attendance_interval
res[sheet_id] = {'total_per_day': total_attendance}
return res
def _total_timesheet(self, cr, uid, ids, name, args, context=None):
""" Get the total of analytic lines for the timesheets
Returns a dict like :
{id: {day: timedelta, ...}}
"""
context = context or {}
sheet_line_obj = self.pool.get('hr.analytic.timesheet')
res = {}
for sheet_id in ids:
# field timesheet_ids of hr_timesheet_sheet.sheet only
# returns lines of timesheet's current date
sheet_lines_ids = sheet_line_obj.search(cr, uid, [('sheet_id', '=', sheet_id)], context=context)
sheet_lines = sheet_line_obj.browse(cr, uid, sheet_lines_ids, context=context)
total_timesheet = {}
for line in sheet_lines:
day = line.date
if not total_timesheet.get(day, False):
total_timesheet[day] = timedelta(seconds=0)
total_timesheet[day] += timedelta(hours=line.unit_amount)
res[sheet_id] = total_timesheet
return res
def _total(self, cr, uid, ids, name, args, context=None):
""" Compute the attendances, analytic lines timesheets and differences between them
for all the days of a timesheet and the current day
"""
def sum_all_days(sheet_amounts):
if not sheet_amounts:
return timedelta(seconds=0)
total = reduce(lambda memo, value: memo + value, sheet_amounts.values())
return total
def timedelta_to_hours(delta):
hours = 0.0
seconds = float(delta.seconds)
if delta.microseconds:
seconds += float(delta.microseconds) / 100000
hours += delta.days * 24
if seconds:
hours += seconds / 3600
return hours
res = {}
all_timesheet_attendances = self._total_attendances(cr, uid, ids, name, args, context=context)
all_timesheet_lines = self._total_timesheet(cr, uid, ids, name, args, context=context)
for id in ids:
res[id] = {}
all_attendances_sheet = all_timesheet_attendances[id]
total_attendances_sheet = all_attendances_sheet['total_per_day']
total_attendances_all_days = sum_all_days(total_attendances_sheet)
total_timesheets_sheet = all_timesheet_lines[id]
total_timesheets_all_days = sum_all_days(total_timesheets_sheet)
total_difference_all_days = total_attendances_all_days - total_timesheets_all_days
res[id]['total_attendance'] = timedelta_to_hours(total_attendances_all_days)
res[id]['total_timesheet'] = timedelta_to_hours(total_timesheets_all_days)
res[id]['total_difference'] = timedelta_to_hours(total_difference_all_days)
for sheet in self.browse(cr, uid, ids, context=context or {}):
res.setdefault(sheet.id, {
'total_attendance': 0.0,
'total_timesheet': 0.0,
'total_difference': 0.0,
})
for period in sheet.period_ids:
res[sheet.id]['total_attendance'] += period.total_attendance
res[sheet.id]['total_timesheet'] += period.total_timesheet
res[sheet.id]['total_difference'] += period.total_attendance - period.total_timesheet
return res
def check_employee_attendance_state(self, cr, uid, sheet_id, context=None):
@ -160,7 +79,7 @@ class hr_timesheet_sheet(osv.osv):
if not new_user_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must assign it to a user.'))
if not self._sheet_date(cr, uid, ids, forced_user_id=new_user_id):
raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlaps!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
raise osv.except_osv(_('Error!'), _('You cannot have 2 timesheets that overlap!\nYou should use the menu \'My Timesheet\' to avoid this problem.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).product_id:
raise osv.except_osv(_('Error!'), _('In order to create a timesheet for this employee, you must link the employee to a product.'))
if not self.pool.get('hr.employee').browse(cr, uid, vals['employee_id']).journal_id:
@ -268,7 +187,7 @@ class hr_timesheet_sheet(osv.osv):
_constraints = [
(_sheet_date, 'You cannot have 2 timesheets that overlaps !\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
(_sheet_date, 'You cannot have 2 timesheets that overlap!\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']),
]
def action_set_to_draft(self, cr, uid, ids, *args):
@ -283,8 +202,8 @@ class hr_timesheet_sheet(osv.osv):
return []
if isinstance(ids, (long, int)):
ids = [ids]
return [(r['id'], r['date_from'] + ' - ' + r['date_to']) \
for r in self.read(cr, uid, ids, ['date_from', 'date_to'],
return [(r['id'], _('Week ')+datetime.strptime(r['date_from'], '%Y-%m-%d').strftime('%U')) \
for r in self.read(cr, uid, ids, ['date_from'],
context=context, load='_classic_write')]
def unlink(self, cr, uid, ids, context=None):
@ -304,16 +223,26 @@ class hr_timesheet_sheet(osv.osv):
hr_timesheet_sheet()
class hr_timesheet_line(osv.osv):
_inherit = "hr.analytic.timesheet"
class account_analytic_line(osv.osv):
_inherit = "account.analytic.line"
def _get_default_date(self, cr, uid, context=None):
if context is None:
context = {}
if 'date' in context:
return context['date']
return time.strftime('%Y-%m-%d')
#get the default date (should be: today)
res = super(account_analytic_line, self)._get_default_date(cr, uid, context=context)
#if we got the dates from and to from the timesheet and if the default date is in between, we use the default
#but if the default isn't included in those dates, we use the date start of the timesheet as default
if context.get('timesheet_date_from') and context.get('timesheet_date_to'):
if context['timesheet_date_from'] <= res <= context['timesheet_date_to']:
return res
return context.get('timesheet_date_from')
#if we don't get the dates from the timesheet, we return the default value from super()
return res
class hr_timesheet_line(osv.osv):
_inherit = "hr.analytic.timesheet"
def _sheet(self, cursor, user, ids, name, args, context=None):
sheet_obj = self.pool.get('hr_timesheet_sheet.sheet')
@ -359,9 +288,6 @@ class hr_timesheet_line(osv.osv):
},
),
}
_defaults = {
'date': _get_default_date,
}
def _check_sheet_state(self, cr, uid, ids, context=None):
if context is None:

View File

@ -40,6 +40,23 @@
</field>
</record>
<record model="ir.actions.act_window" id="act_hr_timesheet_sheet_sheet_2_hr_analytic_timesheet">
<field name="context">{'search_default_sheet_id': [active_id]}</field>
<field name="name">Timesheet Activities</field>
<field name="res_model">hr.analytic.timesheet</field>
<field name="src_model">hr_timesheet_sheet.sheet</field>
</record>
<record model="ir.actions.act_window" id="act_hr_timesheet_sheet_sheet_2_hr_attendance">
<field name="context">{'search_default_sheet_id': [active_id]}</field>
<field name="name">Attendances</field>
<field name="res_model">hr.attendance</field>
<field name="src_model">hr_timesheet_sheet.sheet</field>
</record>
<record id="hr_timesheet_sheet_form" model="ir.ui.view">
<field name="name">hr.timesheet.sheet.form</field>
<field name="model">hr_timesheet_sheet.sheet</field>
@ -53,8 +70,18 @@
<field name="state" widget="statusbar" statusbar_visible="new,confirm,done"/>
</header>
<sheet>
<label for="employee_id" class="oe_edit_only"/>
<h1><field name="employee_id" on_change="onchange_employee_id(employee_id)" class="oe_inline"/></h1>
<div class="oe_right oe_button_box" name="buttons">
<button type="action"
name="%(act_hr_timesheet_sheet_sheet_2_hr_analytic_timesheet)d"
string="Timesheet Activities" />
<button type="action" groups="base.group_hr_attendance"
name="%(act_hr_timesheet_sheet_sheet_2_hr_attendance)d"
string="Attendances" />
</div>
<div class="oe_title">
<label for="employee_id" class="oe_edit_only"/>
<h1><field name="employee_id" on_change="onchange_employee_id(employee_id)" class="oe_inline"/></h1>
</div>
<group>
<group>
<label for="date_from" string="Timesheet Period"/>
@ -63,41 +90,25 @@
<field name="department_id" invisible="1"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group>
<field name="total_attendance" widget="float_time" groups="base.group_hr_attendance"/>
<field name="total_timesheet" widget="float_time" groups="base.group_hr_attendance"/>
<field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/>
<group groups="base.group_hr_attendance">
<field name="total_attendance" widget="float_time"/>
<field name="total_timesheet" widget="float_time"/>
<field name="total_difference" widget="float_time"/>
<field name="user_id" invisible="1"/>
</group>
</group>
<notebook>
<page string="Weekly">
<page string="Summary">
<widget type="weekly_timesheet">
</widget>
</page>
<page string="Daily">
<group groups="base.group_hr_attendance">
<field context="{'user_id':user_id}" name="attendances_ids" nolabel="1">
<tree string="Attendances" editable="bottom">
<field name="name"/>
<field name="action"/>
<field invisible="1" name="employee_id"/>
</tree>
</field>
<group>
<field name="state_attendance" nolabel="1"/>
<div align="right" groups="base.group_hr_manager">
<button name="attendance_action_change" attrs="{'invisible': [('state_attendance', '=', 'present')]}" type="object" string="Sign In"/>
<button name="attendance_action_change" attrs="{'invisible': ['|', ('state_attendance','=',False), ('state_attendance', '=', 'absent')]}" type="object" string="Sign Out"/>
</div>
</group>
</group>
<field context="{'user_id':user_id}" name="timesheet_ids" nolabel="1">
<tree editable="top" string="Timesheet Lines">
<page string="Details">
<field context="{'user_id':user_id, 'timesheet_date_from': date_from, 'timesheet_date_to': date_to}" name="timesheet_ids" nolabel="1">
<tree editable="top" string="Timesheet Activities">
<field name="date"/>
<field domain="[('type','in',['normal', 'contract']), ('state', '&lt;&gt;', 'close'),('use_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id, user_id)" context="{'default_use_timesheets': 1}"/>
<field name="name"/>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time" string="Hours"/>
<field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time" string="Hours" sum="Hours"/>
<field name="to_invoice" widget="selection"/>
<field invisible="1" name="journal_id"/>
<field invisible="1" name="product_id" domain="[('type','=','service')]" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)"/>
@ -106,7 +117,7 @@
<field invisible="1" name="general_account_id"/>
<field invisible="1" name="user_id" required="1"/>
</tree>
<form string="Timesheet Lines" version="7.0">
<form string="Timesheet Activities" version="7.0">
<field name="date"/>
<field domain="[('type','=','normal'), ('state', '&lt;&gt;', 'close')]" name="account_id" on_change="on_change_account_id(account_id, user_id)"/>
<field name="name"/>
@ -121,13 +132,30 @@
</form>
</field>
</page>
<page string="Summary" groups="base.group_hr_attendance">
<page string="Attendances" groups="base.group_hr_attendance">
<group>
<field context="{'user_id':user_id}" name="attendances_ids" nolabel="1">
<tree string="Attendances" editable="bottom">
<field name="name"/>
<field name="action"/>
<field invisible="1" name="employee_id"/>
</tree>
</field>
<group>
<label for="state_attendance"/>
<div>
<field name="state_attendance"/>
<button name="attendance_action_change" attrs="{'invisible': [('state_attendance', '=', 'present')]}" type="object" string="Sign In" class="oe_link"/>
<button name="attendance_action_change" attrs="{'invisible': ['|', ('state_attendance','=',False), ('state_attendance', '=', 'absent')]}" type="object" string="Sign Out" class="oe_link"/>
</div>
</group>
</group>
<field name="period_ids">
<tree colors="red:total_difference&lt;0.1;blue:total_difference&gt;=0.1" string="Period">
<field name="name"/>
<field name="total_attendance" widget="float_time"/>
<field name="total_timesheet" widget="float_time"/>
<field name="total_difference" widget="float_time"/>
<field name="total_attendance" widget="float_time" sum="Attendances"/>
<field name="total_timesheet" widget="float_time" sum="Timesheets"/>
<field name="total_difference" widget="float_time" sum="Differences"/>
</tree>
</field>
</page>
@ -147,8 +175,8 @@
<field name="arch" type="xml">
<search string="Search Timesheet">
<field name="date_from"/>
<filter icon="terp-document-new" string="In Draft" domain="[('state','in',('draft', 'new'))]" help="Unvalidated Timesheets"/>
<filter icon="terp-camera_test" string="To Approve" domain="[('state','=','confirm')]" help="Confirmed Timesheets"/>
<filter name="new" string="In Draft" domain="[('state','in',('draft', 'new'))]" help="Unvalidated Timesheets"/>
<filter name="to_approve" string="To Approve" domain="[('state','=','confirm')]" help="Confirmed Timesheets"/>
<field name="employee_id"/>
<field name="department_id"/>
<group expand="0" string="Group By...">
@ -160,23 +188,23 @@
</record>
<record id="act_hr_timesheet_sheet_form" model="ir.actions.act_window">
<field name="name">Timesheets</field>
<field name="name">Timesheets to Validate</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">hr_timesheet_sheet.sheet</field>
<field name="view_type">form</field>
<field name="view_id" eval="False"/>
<field name="context">{'search_default_my_timesheet':1}</field>
<field name="context">{'search_default_to_approve':1}</field>
<field name="search_view_id" ref="view_hr_timesheet_sheet_filter"/>
<field name="help" type="html">
<p class="oe_view_nocontent_create">
Click to go to your timesheet.
New timesheet to approve.
</p><p>
You must record timesheets every day and confirm at the end
of the week. Once the timesheet is confirmed, it his sent to
the manager for validation.
of the week. Once the timesheet is confirmed, it should be
validated by a manager.
</p><p>
Timesheets can also be invoiced to customers, depending on the
configuration of the project.
configuration of each project's related contract.
</p>
</field>
</record>
@ -275,33 +303,20 @@
</record>
<act_window
context="{'search_default_sheet_id': [active_id]}"
id="act_hr_timesheet_sheet_sheet_by_account"
name="Timesheet by Account"
res_model="hr_timesheet_sheet.sheet.account"
src_model="hr_timesheet_sheet.sheet"/>
context="{'search_default_sheet_id': [active_id]}"
id="act_hr_timesheet_sheet_sheet_by_account"
name="Timesheet by Account"
groups="base.group_hr_attendance"
res_model="hr_timesheet_sheet.sheet.account"
src_model="hr_timesheet_sheet.sheet"/>
<act_window
context="{'search_default_sheet_id': [active_id]}"
id="act_hr_timesheet_sheet_sheet_by_day"
name="Timesheet by Day"
res_model="hr_timesheet_sheet.sheet.day"
src_model="hr_timesheet_sheet.sheet"/>
<act_window
context="{'search_default_sheet_id': [active_id]}"
id="act_hr_timesheet_sheet_sheet_2_hr_analytic_timesheet"
name="Timesheet Lines"
res_model="hr.analytic.timesheet"
src_model="hr_timesheet_sheet.sheet"/>
<act_window
context="{'search_default_sheet_id': [active_id]}"
id="act_hr_timesheet_sheet_sheet_2_hr_attendance"
name="Attendances"
res_model="hr.attendance"
src_model="hr_timesheet_sheet.sheet"/>
context="{'search_default_sheet_id': [active_id]}"
id="act_hr_timesheet_sheet_sheet_by_day"
name="Timesheet by Day"
groups="base.group_hr_attendance"
res_model="hr_timesheet_sheet.sheet.day"
src_model="hr_timesheet_sheet.sheet"/>
<record id="hr_timesheet_sheet_tree_simplified" model="ir.ui.view">
<field name="name">hr.timesheet.sheet.tree</field>
@ -312,7 +327,7 @@
<field name="employee_id"/>
<field name="date_from"/>
<field name="date_to"/>
<field name="department_id"/>
<field name="department_id" invisible="1"/>
<field name="total_attendance" widget="float_time" groups="base.group_hr_attendance"/>
<field name="total_timesheet" widget="float_time"/>
<field name="total_difference" widget="float_time" groups="base.group_hr_attendance"/>

View File

@ -0,0 +1,3 @@
timesheet.css: timesheet.sass
sass -t expanded timesheet.sass timesheet.css

View File

@ -1,79 +1,65 @@
@charset "utf-8";
.openerp .oe_form_readonly .oe_timesheet_weekly .oe_timesheet_button_add {
display: none;
}
.openerp .oe_form_readonly .oe_timesheet_weekly div.oe_view_nocontent {
display: none;
}
.openerp .oe_timesheet_weekly {
overflow-x: auto;
overflow-x: auto;
}
.openerp .oe_timesheet_weekly table {
width: 100%;
width: 100%;
}
.openerp .oe_timesheet_weekly td {
padding-top: 15px;
padding: 3px;
text-align: right;
}
.openerp .oe_timesheet_weekly th {
text-align: right;
color: #069;
font-family: 'Helvetica Neue', Arial, Verdana, 'Nimbus Sans L', sans-serif;
font-size: 10px;
text-align: center;
color: #006699;
font-family: "Helvetica Neue", Arial, Verdana, "Nimbus Sans L", sans-serif;
font-size: 10px;
background: #eeeeee;
min-width: 47px;
}
.openerp .oe_timesheet_weekly th.oe_timesheet_weekly_date_head {
width: 60px;
.openerp .oe_timesheet_weekly .oe_timesheet_total {
background: #eeeeee;
}
.openerp .oe_timesheet_weekly td {
text-align: right;
vertical-align: middle;
}
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_account {
text-align: left;
padding-right: 30px;
text-align: left;
}
.openerp .oe_timesheet_weekly .oe_timesheet_first_col {
min-width: 130px;
}
.openerp .oe_timesheet_weekly td input.oe_timesheet_weekly_input {
border: 1px solid #CCC;
padding: 5px 2px !important;
color: #666 !important;
font-size: 14px;
font-weight: bold;
width: 38px;
text-align: right;
min-width: 0 !important;
padding: 5px 2px !important;
width: 40px;
text-align: right;
min-width: 0 !important;
}
.openerp .oe_timesheet_weekly td .oe_timesheet_weekly_box {
padding: 5px 2px !important;
color: #666 !important;
font-size: 14px;
font-weight: bold;
width: 38px;
display: inline-block;
.openerp .oe_timesheet_weekly td.oe_timesheet_total {
font-weight: bold;
padding: 5px 3px !important;
}
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_adding_tot {
display: table;
width: 100%;
display: table;
width: 100%;
}
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_adding {
display: table-cell;
text-align: left;
display: table-cell;
text-align: left;
}
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_tottot {
display: table-cell;
display: table-cell;
}
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_add_row td {
text-align: left;
text-align: left;
}
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_add_row .oe_form_field_many2one {
display: inline-block;
width: 200px;
display: inline-block;
width: 200px;
}
.openerp .oe_timesheet_weekly_today {
.openerp .oe_timesheet_weekly .oe_timesheet_weekly_today {
background: #ffddee;
}

View File

@ -0,0 +1,52 @@
@charset "utf-8"
.openerp
.oe_form_readonly
.oe_timesheet_weekly
.oe_timesheet_button_add
display: none
div.oe_view_nocontent
display: none
.oe_timesheet_weekly
overflow-x: auto
table
width: 100%
td
padding: 3px
text-align: right
th
text-align: center
color: #069
font-family: 'Helvetica Neue', Arial, Verdana, 'Nimbus Sans L', sans-serif
font-size: 10px
background: #eee
min-width: 47px
.oe_timesheet_total
background: #eee
.oe_timesheet_weekly_account
text-align: left
.oe_timesheet_first_col
min-width: 130px
td input.oe_timesheet_weekly_input
padding: 5px 2px !important
width: 40px
text-align: right
min-width: 0 !important
td.oe_timesheet_total
font-weight: bold
padding: 5px 3px !important
.oe_timesheet_weekly_adding_tot
display: table
width: 100%
.oe_timesheet_weekly_adding
display: table-cell
text-align: left
.oe_timesheet_weekly_tottot
display: table-cell
.oe_timesheet_weekly_add_row td
text-align: left
.oe_timesheet_weekly_add_row .oe_form_field_many2one
display: inline-block
width: 200px
.oe_timesheet_weekly_today
background: #fde

View File

@ -4,6 +4,9 @@ openerp.hr_timesheet_sheet = function(instance) {
var _t = instance.web._t;
instance.hr_timesheet_sheet.WeeklyTimesheet = instance.web.form.FormWidget.extend(instance.web.form.ReinitializeWidgetMixin, {
events: {
"click .oe_timesheet_weekly_account a": "go_to",
},
init: function() {
this._super.apply(this, arguments);
this.set({
@ -26,6 +29,16 @@ openerp.hr_timesheet_sheet = function(instance) {
this.render_drop = new instance.web.DropMisordered();
this.description_line = _t("/");
},
go_to: function(event) {
var id = JSON.parse($(event.target).data("id"));
this.do_action({
type: 'ir.actions.act_window',
res_model: "account.analytic.account",
res_id: id,
views: [[false, 'form']],
target: 'current'
});
},
query_sheets: function() {
var self = this;
if (self.updating)
@ -194,6 +207,10 @@ openerp.hr_timesheet_sheet = function(instance) {
['use_timesheets','=',1],
['id', 'not in', _.pluck(self.accounts, "account")],
],
context: {
default_use_timesheets: 1,
default_type: "contract",
},
modifiers: '{"required": true}',
},
});

View File

@ -5,7 +5,7 @@
<div class="oe_timesheet_weekly">
<table>
<tr>
<th> </th>
<th class="oe_timesheet_first_col"> </th>
<t t-foreach="widget.dates" t-as="date">
<th t-att-class="'oe_timesheet_weekly_date_head' + (Date.compare(date, Date.today()) === 0 ? ' oe_timesheet_weekly_today' : '')">
<t t-esc="date.toString('ddd')"/><br />
@ -15,7 +15,7 @@
<th class="oe_timesheet_weekly_date_head">Total</th>
</tr>
<tr t-foreach="widget.accounts" t-as="account">
<td class="oe_timesheet_weekly_account"><t t-esc="widget.account_names[account.account]"/></td>
<td class="oe_timesheet_weekly_account"><a href="javascript:void(0)" t-att-data-id="JSON.stringify(account.account)"><t t-esc="widget.account_names[account.account]"/></a></td>
<t t-set="day_count" t-value="0"/>
<t t-foreach="account.days" t-as="day">
<td t-att-class="(Date.compare(day.day, Date.today()) === 0 ? 'oe_timesheet_weekly_today' : '')">
@ -26,31 +26,47 @@
<t t-set="day_count" t-value="day_count + 1"/>
</td>
</t>
<td t-att-data-account-total="account.account"> </td>
<td t-att-data-account-total="account.account" class="oe_timesheet_total"> </td>
</tr>
<tr class="oe_timesheet_weekly_add_row" style="display: none">
<td t-att-colspan="widget.dates.length + 2">
<button>Add</button>
<button class="oe_highlight">Add</button>
</td>
</tr>
<tr>
<tr class="oe_timesheet_total">
<td>
<div class="oe_timesheet_weekly_adding_tot">
<div class="oe_timesheet_weekly_adding"><button>Add a Project</button></div>
<div class="oe_timesheet_weekly_adding"><button class="oe_timesheet_button_add">Add a Line</button></div>
<div class="oe_timesheet_weekly_tottot"><span>Total</span></div>
</div>
</td>
<t t-set="day_count" t-value="0"/>
<t t-foreach="widget.dates" t-as="date">
<td t-att-class="(Date.compare(date, Date.today()) === 0 ? 'oe_timesheet_weekly_today' : '')">
<td class="oe_timesheet_total">
<span class="oe_timesheet_weekly_box" t-att-data-day-total="day_count">
</span>
<t t-set="day_count" t-value="day_count + 1"/>
</td>
</t>
<td class="oe_timesheet_weekly_supertotal"> </td>
<td class="oe_timesheet_weekly_supertotal oe_timesheet_total"> </td>
</tr>
</table>
<div t-if="widget.accounts.length == 0">
<div class="oe_view_nocontent">
<p class="oe_view_nocontent_create">
Click to add projects/analytic accounts you worked on.
</p><p>
You will be able to register your working hours and
activities.
</p><p>
By default, you record timesheets on analytic accounts.
But if an analytic account represents a customer
contract, you can change the type of the analytic
account to 'Contract or Project' to setup the invoicing
options.
</p>
</div>
</div>
</div>
</t>
</templates>

View File

@ -36,7 +36,7 @@ class hr_timesheet_current_open(osv.osv_memory):
user_ids = self.pool.get('hr.employee').search(cr, uid, [('user_id','=',uid)], context=context)
if not len(user_ids):
raise osv.except_osv(_('Error!'), _('Please create an employee and associate it with this user.'))
ids = ts.search(cr, uid, [('user_id','=',uid),('state','=','draft'),('date_from','<=',time.strftime('%Y-%m-%d')), ('date_to','>=',time.strftime('%Y-%m-%d'))], context=context)
ids = ts.search(cr, uid, [('user_id','=',uid),('state','in',('draft','new')),('date_from','<=',time.strftime('%Y-%m-%d')), ('date_to','>=',time.strftime('%Y-%m-%d'))], context=context)
if len(ids) > 1:
view_type = 'tree,form'

View File

@ -5,7 +5,7 @@
<field name="name">hr_timesheet_current_open.form</field>
<field name="model">hr.timesheet.current.open</field>
<field name="arch" type="xml">
<form string="My Current Timesheet" version="7.0">
<form string="My Timesheet" version="7.0">
<group>
<separator string="It will open your current timesheet"/>
</group>
@ -19,7 +19,7 @@
</record>
<record id="action_hr_timesheet_current_open" model="ir.actions.act_window">
<field name="name">My Current Timesheet</field>
<field name="name">My Timesheet</field>
<field name="res_model">hr.timesheet.current.open</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
@ -28,7 +28,7 @@
<field name="help">My Timesheet opens your timesheet so that you can book your activities into the system. From the same form, you can register your attendances (Sign In/Out) and describe the working hours made on the different projects. At the end of the period defined in the company, the timesheet is confirmed by the user and can be validated by his manager. If required, as defined on the project, you can generate the invoices based on the timesheet.</field>
</record>
<menuitem action="action_hr_timesheet_current_open" icon="STOCK_NEW" id="menu_act_hr_timesheet_sheet_form_my_current" name="My Current Timesheet" parent="hr_attendance.menu_hr_time_tracking" sequence="1"/>
<menuitem action="action_hr_timesheet_current_open" icon="STOCK_NEW" id="menu_act_hr_timesheet_sheet_form_my_current" name="My Timesheet" parent="hr_attendance.menu_hr_time_tracking" sequence="1"/>
</data>
</openerp>

View File

@ -79,8 +79,8 @@
<field name="description"/>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread"/>
</div>
</form>
</field>

View File

@ -84,7 +84,6 @@ Main Features
'css': [
'static/src/css/mail.css',
'static/src/css/mail_group.css',
'static/src/css/mail_compose_message.css',
],
'js': [
'static/lib/jquery.expander/jquery.expander.js',

View File

@ -8,6 +8,7 @@
<field name="body">Your monthly meal vouchers arrived. You can get them at Christine's office.
This month you also get 250 EUR of eco-vouchers if you have been in the company for more than a year.</field>
<field name="type">comment</field>
<field name="subtype_id" ref="mt_comment"/>
</record>
<record id="message_blogpost0_comment0" model="mail.message">
@ -16,6 +17,7 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
<field name="body"><![CDATA[Great.]]></field>
<field name="parent_id" ref="message_blogpost0"/>
<field name="type">comment</field>
<field name="subtype_id" ref="mt_comment"/>
</record>
<record id="message_blogpost0_comment1" model="mail.message">
@ -24,22 +26,25 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company
<field name="body">Thanks, but where is Christine's office, if I may ask? (I'm new here)</field>
<field name="parent_id" ref="message_blogpost0"/>
<field name="type">comment</field>
<field name="subtype_id" ref="mt_comment"/>
</record>
<record id="message_blogpost0_comment2" model="mail.message">
<field name="model">mail.group</field>
<field name="res_id" ref="group_all_employees"/>
<field name="body">Building B3, second floor on the right :-)</field>
<field name="parent_id" ref="message_blogpost0"/>
<field name="type">comment</field>
<field name="subtype_id" ref="mt_comment"/>
</record>
<record id="message_blogpost0_comment3" model="mail.message">
<field name="model">mail.group</field>
<field name="res_id" ref="group_all_employees"/>
<field name="body">Great news, I need to buy a new fridge, I think I can pay it with the eco-vouchers!</field>
<field name="parent_id" ref="message_blogpost0"/>
<field name="type">comment</field>
</record>
<record id="message_blogpost0_comment1_2" model="mail.message">
<field name="model">mail.group</field>
<field name="res_id" ref="group_all_employees"/>
<field name="body">Building B3, second floor on the right :-)</field>
<field name="parent_id" ref="message_blogpost0_comment1"/>
<field name="type">comment</field>
<field name="subtype_id" ref="mt_comment"/>
</record>
</data>

View File

@ -8,45 +8,45 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-09 00:36+0000\n"
"PO-Revision-Date: 2011-12-22 17:13+0000\n"
"Last-Translator: Tomislav Bosnjakovic <Unknown>\n"
"PO-Revision-Date: 2012-10-28 19:42+0000\n"
"Last-Translator: Marijan Rajic <Unknown>\n"
"Language-Team: Croatian <hr@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-29 05:00+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
#: field:mail.message.common,subtype:0
msgid "Message Type"
msgstr ""
msgstr "Tip poruke"
#. module: mail
#: help:mail.compose.message,auto_delete:0
msgid "Permanently delete emails after sending"
msgstr ""
msgstr "Trajno obrisati e-poštu nakon slanja"
#. module: mail
#: view:mail.message:0
msgid "Open Related Document"
msgstr ""
msgstr "Otvori povezani dokument"
#. module: mail
#: view:mail.message:0
msgid "Open Attachments"
msgstr "Otvori privitke"
msgstr "Otvori priloge"
#. module: mail
#: view:mail.message:0
msgid "Message Details"
msgstr "Detalji poruke"
msgstr "Detalji porukue"
#. module: mail
#: view:mail.thread:0
msgid "Communication History"
msgstr ""
msgstr "Povijest komunikacije"
#. module: mail
#: view:mail.message:0
@ -57,73 +57,73 @@ msgstr "Grupiraj po..."
#: model:ir.actions.act_window,name:mail.action_email_compose_message_wizard
#: view:mail.compose.message:0
msgid "Compose Email"
msgstr ""
msgstr "Sastavi e-poštu"
#. module: mail
#: help:mail.compose.message,body_text:0 help:mail.message,body_text:0
#: help:mail.message.common,body_text:0
msgid "Plain-text version of the message"
msgstr ""
msgstr "Verzija poruke u običnom tekstu"
#. module: mail
#: view:mail.compose.message:0
msgid "Body"
msgstr ""
msgstr "Sadržaj"
#. module: mail
#: help:mail.compose.message,email_to:0 help:mail.message,email_to:0
#: help:mail.message.common,email_to:0
msgid "Message recipients"
msgstr ""
msgstr "Primatelji poruke"
#. module: mail
#: field:mail.compose.message,body_text:0 field:mail.message,body_text:0
#: field:mail.message.common,body_text:0
msgid "Text Contents"
msgstr ""
msgstr "Sadržaj"
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Received"
msgstr ""
msgstr "Primljeno"
#. module: mail
#: view:mail.message:0
msgid "Thread"
msgstr ""
msgstr "Nit"
#. module: mail
#: field:mail.message,mail_server_id:0
msgid "Outgoing mail server"
msgstr ""
msgstr "Izlazni poslužitelj e-pošte"
#. module: mail
#: selection:mail.message,state:0
msgid "Cancelled"
msgstr ""
msgstr "Otkazano"
#. module: mail
#: field:mail.compose.message,reply_to:0 field:mail.message,reply_to:0
#: field:mail.message.common,reply_to:0
msgid "Reply-To"
msgstr ""
msgstr "Odgovori na"
#. module: mail
#: help:mail.compose.message,body_html:0 help:mail.message,body_html:0
#: help:mail.message.common,body_html:0
msgid "Rich-text/HTML version of the message"
msgstr ""
msgstr "Verzija poruke u Rich-text/HTML formatu"
#. module: mail
#: field:mail.compose.message,auto_delete:0 field:mail.message,auto_delete:0
msgid "Auto Delete"
msgstr ""
msgstr "Auto brisanje"
#. module: mail
#: help:mail.compose.message,email_bcc:0 help:mail.message,email_bcc:0
#: help:mail.message.common,email_bcc:0
msgid "Blind carbon copy message recipients"
msgstr ""
msgstr "Primatelji skrivene kopije poruke"
#. module: mail
#: model:ir.model,name:mail.model_res_partner view:mail.message:0
@ -134,13 +134,13 @@ msgstr "Partner"
#: field:mail.compose.message,subject:0 field:mail.message,subject:0
#: field:mail.message.common,subject:0
msgid "Subject"
msgstr ""
msgstr "Naslov"
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:152
#, python-format
msgid "On %(date)s, "
msgstr ""
msgstr "Na %(date)s, "
#. module: mail
#: field:mail.compose.message,email_from:0 field:mail.message,email_from:0
@ -151,32 +151,32 @@ msgstr "Od"
#. module: mail
#: view:mail.message:0
msgid "Email message"
msgstr ""
msgstr "Poruka e-pošte"
#. module: mail
#: view:mail.compose.message:0
msgid "Send"
msgstr ""
msgstr "Pošalji"
#. module: mail
#: view:mail.message:0
msgid "Failed"
msgstr ""
msgstr "Neuspjelo"
#. module: mail
#: view:mail.message:0 field:mail.message,state:0
msgid "State"
msgstr ""
msgstr "Stanje"
#. module: mail
#: view:mail.message:0
msgid "Reply"
msgstr ""
msgstr "Odgovor"
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Sent"
msgstr ""
msgstr "Poslano"
#. module: mail
#: help:mail.compose.message,subtype:0 help:mail.message,subtype:0
@ -185,39 +185,41 @@ msgid ""
"Type of message, usually 'html' or 'plain', used to select plaintext or rich "
"text contents accordingly"
msgstr ""
"Tip poruke, obično 'html' ili 'običan tekst', koristi se za odabir običnog "
"ili bogatije formatiranog teksta"
#. module: mail
#: view:mail.message:0
msgid "Recipients"
msgstr ""
msgstr "Primatelji"
#. module: mail
#: model:ir.model,name:mail.model_mail_compose_message
msgid "Email composition wizard"
msgstr ""
msgstr "Čarobnjak za sastavljanje e-pošte"
#. module: mail
#: field:mail.compose.message,res_id:0 field:mail.message,res_id:0
#: field:mail.message.common,res_id:0
msgid "Related Document ID"
msgstr ""
msgstr "Povezani ID dokumenta"
#. module: mail
#: view:mail.message:0
msgid "Advanced"
msgstr ""
msgstr "Napredno"
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:157
#, python-format
msgid "Re:"
msgstr ""
msgstr "Re:"
#. module: mail
#: field:mail.compose.message,model:0 field:mail.message,model:0
#: field:mail.message.common,model:0
msgid "Related Document Model"
msgstr ""
msgstr "Povezani model dokumenta"
#. module: mail
#: view:mail.message:0
@ -227,42 +229,42 @@ msgstr "Mjesec"
#. module: mail
#: view:mail.message:0
msgid "Email Search"
msgstr "Pretraživanje e-mailova"
msgstr "Pretraga e-pošte"
#. module: mail
#: help:mail.message,original:0
msgid "Original version of the message, as it was sent on the network"
msgstr ""
msgstr "Originalna varijanta poruke, kakva je poslana na mrežu"
#. module: mail
#: view:mail.message:0
msgid "Partner Name"
msgstr "Naziv partnera"
msgstr "Ime partnera"
#. module: mail
#: view:mail.message:0
msgid "Retry"
msgstr ""
msgstr "Pokušaj ponovo"
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Outgoing"
msgstr ""
msgstr "Odlazno"
#. module: mail
#: view:mail.message:0
msgid "Send Now"
msgstr ""
msgstr "Pošalji odmah"
#. module: mail
#: field:mail.message,partner_id:0
msgid "Related partner"
msgstr ""
msgstr "Povezani partner"
#. module: mail
#: view:mail.message:0
msgid "User"
msgstr ""
msgstr "Korisnik"
#. module: mail
#: field:mail.compose.message,date:0 field:mail.message,date:0
@ -273,19 +275,19 @@ msgstr "Datum"
#. module: mail
#: view:mail.message:0
msgid "Extended Filters..."
msgstr ""
msgstr "Prošireni filteri..."
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:153
#, python-format
msgid "%(sender_name)s wrote:"
msgstr ""
msgstr "%(sender_name)s je napisao:"
#. module: mail
#: field:mail.compose.message,body_html:0 field:mail.message,body_html:0
#: field:mail.message.common,body_html:0
msgid "Rich-text Contents"
msgstr ""
msgstr "Formatirani tekst"
#. module: mail
#: field:mail.message,original:0
@ -346,18 +348,18 @@ msgstr ""
#. module: mail
#: view:mail.message:0
msgid "Open"
msgstr ""
msgstr "Otvori"
#. module: mail
#: code:addons/mail/mail_thread.py:434
#, python-format
msgid "[OpenERP-Forward-Failed] %s"
msgstr ""
msgstr "[OpenERP-Forward-Failed] %s"
#. module: mail
#: field:mail.message,user_id:0
msgid "Related User"
msgstr ""
msgstr "Povezani korisnik"
#. module: mail
#: help:mail.compose.message,headers:0 help:mail.message,headers:0
@ -366,11 +368,13 @@ msgid ""
"Full message headers, e.g. SMTP session headers (usually available on "
"inbound messages only)"
msgstr ""
"Cjelovito zaglavlje poruke, npr. zaglavlje SMTP sesije (obično dostupno samo "
"kod dolaznih poruka)"
#. module: mail
#: view:mail.message:0
msgid "Creation Month"
msgstr ""
msgstr "Mjesec kreiranja"
#. module: mail
#: field:mail.compose.message,email_to:0 field:mail.message,email_to:0
@ -387,7 +391,7 @@ msgstr "Detalji"
#: model:ir.actions.act_window,name:mail.action_view_mailgate_thread
#: view:mail.thread:0
msgid "Email Threads"
msgstr ""
msgstr "Niti e-pošte"
#. module: mail
#: help:mail.compose.message,email_from:0 help:mail.message,email_from:0
@ -396,28 +400,30 @@ msgid ""
"Message sender, taken from user preferences. If empty, this is not a mail "
"but a message."
msgstr ""
"Pošiljatelj poruke, preuzet iz korisničkih preferenci. Ukoliko je prazno, "
"biti će poruka a ne e-pošta."
#. module: mail
#: view:mail.message:0
msgid "Body (Plain)"
msgstr ""
msgstr "Tijelo (obično)"
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:153
#, python-format
msgid "You"
msgstr ""
msgstr "Vi"
#. module: mail
#: help:mail.compose.message,message_id:0 help:mail.message,message_id:0
#: help:mail.message.common,message_id:0
msgid "Message unique identifier"
msgstr ""
msgstr "Jedinstveni identifikator poruke"
#. module: mail
#: view:mail.message:0
msgid "Body (Rich)"
msgstr ""
msgstr "Tijelo (formatirano)"
#. module: mail
#: code:addons/mail/mail_message.py:155
@ -427,6 +433,9 @@ msgid ""
" Subject: %s \n"
"\t"
msgstr ""
"%s napisano %s: \n"
" Predmet: %s \n"
"\t"
#. module: mail
#: model:ir.actions.act_window,name:mail.act_res_partner_emails
@ -445,64 +454,64 @@ msgstr "Poruke"
#: field:mail.compose.message,headers:0 field:mail.message,headers:0
#: field:mail.message.common,headers:0
msgid "Message Headers"
msgstr ""
msgstr "Zaglavlja poruke"
#. module: mail
#: field:mail.compose.message,email_bcc:0 field:mail.message,email_bcc:0
#: field:mail.message.common,email_bcc:0
msgid "Bcc"
msgstr "Bcc"
msgstr "Skrivena kopija"
#. module: mail
#: model:ir.model,name:mail.model_mail_message_common
msgid "mail.message.common"
msgstr ""
msgstr "mail.message.common"
#. module: mail
#: help:mail.compose.message,references:0 help:mail.message,references:0
#: help:mail.message.common,references:0
msgid "Message references, such as identifiers of previous messages"
msgstr ""
msgstr "Reference poruke, poput identifikatora prethodnih poruka"
#. module: mail
#: constraint:res.partner:0
msgid "Error ! You cannot create recursive associated members."
msgstr ""
msgstr "Greška! Nije moguće kreirati pridružene članove rekurzivno."
#. module: mail
#: help:mail.compose.message,email_cc:0 help:mail.message,email_cc:0
#: help:mail.message.common,email_cc:0
msgid "Carbon copy message recipients"
msgstr ""
msgstr "Primatelji skrivene kopije"
#. module: mail
#: selection:mail.message,state:0
msgid "Delivery Failed"
msgstr ""
msgstr "Isporuka nije uspjela"
#. module: mail
#: model:ir.model,name:mail.model_mail_message
msgid "Email Message"
msgstr ""
msgstr "Poruka e-pošte"
#. module: mail
#: model:ir.model,name:mail.model_mail_thread view:mail.thread:0
msgid "Email Thread"
msgstr ""
msgstr "Nit e-pošte"
#. module: mail
#: field:mail.compose.message,filter_id:0
msgid "Filters"
msgstr ""
msgstr "Filteri"
#. module: mail
#: code:addons/mail/mail_thread.py:220
#, python-format
msgid "Mail attachment"
msgstr ""
msgstr "Prilog e-pošte"
#. module: mail
#: help:mail.compose.message,reply_to:0 help:mail.message,reply_to:0
#: help:mail.message.common,reply_to:0
msgid "Preferred response address for the message"
msgstr ""
msgstr "Željena povratna adresa za poruku"

View File

@ -7,21 +7,20 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-09 00:36+0000\n"
"PO-Revision-Date: 2011-01-21 12:01+0000\n"
"Last-Translator: NOVOTRADE RENDSZERHÁZ ( novotrade.hu ) "
"<openerp@novotrade.hu>\n"
"PO-Revision-Date: 2012-10-26 12:29+0000\n"
"Last-Translator: Herczeg Péter <herczegp@gmail.com>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-27 04:59+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
#: field:mail.message.common,subtype:0
msgid "Message Type"
msgstr ""
msgstr "Üzenettípus"
#. module: mail
#: help:mail.compose.message,auto_delete:0
@ -31,7 +30,7 @@ msgstr ""
#. module: mail
#: view:mail.message:0
msgid "Open Related Document"
msgstr ""
msgstr "Kapcsolódó dokumentum megnyitása"
#. module: mail
#: view:mail.message:0
@ -57,7 +56,7 @@ msgstr "Csoportosítás..."
#: model:ir.actions.act_window,name:mail.action_email_compose_message_wizard
#: view:mail.compose.message:0
msgid "Compose Email"
msgstr ""
msgstr "Email írás"
#. module: mail
#: help:mail.compose.message,body_text:0 help:mail.message,body_text:0
@ -68,7 +67,7 @@ msgstr ""
#. module: mail
#: view:mail.compose.message:0
msgid "Body"
msgstr ""
msgstr "Levéltörzs"
#. module: mail
#: help:mail.compose.message,email_to:0 help:mail.message,email_to:0
@ -85,7 +84,7 @@ msgstr ""
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Received"
msgstr ""
msgstr "Fogadott"
#. module: mail
#: view:mail.message:0
@ -95,18 +94,18 @@ msgstr ""
#. module: mail
#: field:mail.message,mail_server_id:0
msgid "Outgoing mail server"
msgstr ""
msgstr "Kimenő levelező szerver"
#. module: mail
#: selection:mail.message,state:0
msgid "Cancelled"
msgstr ""
msgstr "Megszakítva"
#. module: mail
#: field:mail.compose.message,reply_to:0 field:mail.message,reply_to:0
#: field:mail.message.common,reply_to:0
msgid "Reply-To"
msgstr ""
msgstr "Válaszcím"
#. module: mail
#: help:mail.compose.message,body_html:0 help:mail.message,body_html:0
@ -117,7 +116,7 @@ msgstr ""
#. module: mail
#: field:mail.compose.message,auto_delete:0 field:mail.message,auto_delete:0
msgid "Auto Delete"
msgstr ""
msgstr "Automatikus törlés"
#. module: mail
#: help:mail.compose.message,email_bcc:0 help:mail.message,email_bcc:0
@ -146,37 +145,37 @@ msgstr ""
#: field:mail.compose.message,email_from:0 field:mail.message,email_from:0
#: field:mail.message.common,email_from:0
msgid "From"
msgstr ""
msgstr "Feladó"
#. module: mail
#: view:mail.message:0
msgid "Email message"
msgstr ""
msgstr "Email üzenet"
#. module: mail
#: view:mail.compose.message:0
msgid "Send"
msgstr ""
msgstr "Küldés"
#. module: mail
#: view:mail.message:0
msgid "Failed"
msgstr ""
msgstr "Sikertelen"
#. module: mail
#: view:mail.message:0 field:mail.message,state:0
msgid "State"
msgstr ""
msgstr "Állapot"
#. module: mail
#: view:mail.message:0
msgid "Reply"
msgstr ""
msgstr "Válasz"
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Sent"
msgstr ""
msgstr "Elküldött"
#. module: mail
#: help:mail.compose.message,subtype:0 help:mail.message,subtype:0
@ -189,7 +188,7 @@ msgstr ""
#. module: mail
#: view:mail.message:0
msgid "Recipients"
msgstr ""
msgstr "Címzettek"
#. module: mail
#: model:ir.model,name:mail.model_mail_compose_message
@ -205,7 +204,7 @@ msgstr ""
#. module: mail
#: view:mail.message:0
msgid "Advanced"
msgstr ""
msgstr "Speciális"
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:157
@ -242,17 +241,17 @@ msgstr "Partner neve"
#. module: mail
#: view:mail.message:0
msgid "Retry"
msgstr ""
msgstr "Újra"
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Outgoing"
msgstr ""
msgstr "Kimenő"
#. module: mail
#: view:mail.message:0
msgid "Send Now"
msgstr ""
msgstr "Küldés most"
#. module: mail
#: field:mail.message,partner_id:0
@ -262,7 +261,7 @@ msgstr ""
#. module: mail
#: view:mail.message:0
msgid "User"
msgstr ""
msgstr "Felhasználó"
#. module: mail
#: field:mail.compose.message,date:0 field:mail.message,date:0
@ -273,7 +272,7 @@ msgstr "Dátum"
#. module: mail
#: view:mail.message:0
msgid "Extended Filters..."
msgstr ""
msgstr "Kiterjesztett szűrők…"
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:153
@ -290,7 +289,7 @@ msgstr ""
#. module: mail
#: field:mail.message,original:0
msgid "Original"
msgstr ""
msgstr "Eredeti"
#. module: mail
#: code:addons/mail/mail_thread.py:247 view:res.partner:0
@ -341,12 +340,12 @@ msgstr ""
#. module: mail
#: view:mail.compose.message:0 view:mail.message:0
msgid "Cancel"
msgstr ""
msgstr "Mégsem"
#. module: mail
#: view:mail.message:0
msgid "Open"
msgstr ""
msgstr "Megnyitás"
#. module: mail
#: code:addons/mail/mail_thread.py:434
@ -357,7 +356,7 @@ msgstr ""
#. module: mail
#: field:mail.message,user_id:0
msgid "Related User"
msgstr ""
msgstr "Kapcsolódó felhasználó"
#. module: mail
#: help:mail.compose.message,headers:0 help:mail.message,headers:0

View File

@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-09 00:36+0000\n"
"PO-Revision-Date: 2011-01-25 08:25+0000\n"
"Last-Translator: Alexey Y. Fedotov <Unknown>\n"
"PO-Revision-Date: 2012-10-26 17:02+0000\n"
"Last-Translator: Chertykov Denis <chertykov@gmail.com>\n"
"Language-Team: Russian <ru@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:36+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-27 04:59+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: mail
#: field:mail.compose.message,subtype:0 field:mail.message,subtype:0
@ -26,12 +26,12 @@ msgstr ""
#. module: mail
#: help:mail.compose.message,auto_delete:0
msgid "Permanently delete emails after sending"
msgstr ""
msgstr "Не сохранять сообщения после отправки"
#. module: mail
#: view:mail.message:0
msgid "Open Related Document"
msgstr ""
msgstr "Открыть связанный документ"
#. module: mail
#: view:mail.message:0
@ -46,7 +46,7 @@ msgstr "Подробности сообщения"
#. module: mail
#: view:mail.thread:0
msgid "Communication History"
msgstr ""
msgstr "История общения"
#. module: mail
#: view:mail.message:0
@ -57,24 +57,24 @@ msgstr "Объеденить по..."
#: model:ir.actions.act_window,name:mail.action_email_compose_message_wizard
#: view:mail.compose.message:0
msgid "Compose Email"
msgstr ""
msgstr "Написать письмо"
#. module: mail
#: help:mail.compose.message,body_text:0 help:mail.message,body_text:0
#: help:mail.message.common,body_text:0
msgid "Plain-text version of the message"
msgstr ""
msgstr "Простая текстовая версия сообщения"
#. module: mail
#: view:mail.compose.message:0
msgid "Body"
msgstr ""
msgstr "Содержимое"
#. module: mail
#: help:mail.compose.message,email_to:0 help:mail.message,email_to:0
#: help:mail.message.common,email_to:0
msgid "Message recipients"
msgstr ""
msgstr "Получатели сообщения"
#. module: mail
#: field:mail.compose.message,body_text:0 field:mail.message,body_text:0
@ -85,7 +85,7 @@ msgstr ""
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Received"
msgstr ""
msgstr "Получено"
#. module: mail
#: view:mail.message:0
@ -95,40 +95,40 @@ msgstr "Цепочка"
#. module: mail
#: field:mail.message,mail_server_id:0
msgid "Outgoing mail server"
msgstr ""
msgstr "Сервер исходящей почты"
#. module: mail
#: selection:mail.message,state:0
msgid "Cancelled"
msgstr ""
msgstr "Отменено"
#. module: mail
#: field:mail.compose.message,reply_to:0 field:mail.message,reply_to:0
#: field:mail.message.common,reply_to:0
msgid "Reply-To"
msgstr ""
msgstr "Адрес ответа"
#. module: mail
#: help:mail.compose.message,body_html:0 help:mail.message,body_html:0
#: help:mail.message.common,body_html:0
msgid "Rich-text/HTML version of the message"
msgstr ""
msgstr "Форматный текст/HTML версия сообщения"
#. module: mail
#: field:mail.compose.message,auto_delete:0 field:mail.message,auto_delete:0
msgid "Auto Delete"
msgstr ""
msgstr "Авто удаление"
#. module: mail
#: help:mail.compose.message,email_bcc:0 help:mail.message,email_bcc:0
#: help:mail.message.common,email_bcc:0
msgid "Blind carbon copy message recipients"
msgstr ""
msgstr "Получатели скрытой копии сообщения"
#. module: mail
#: model:ir.model,name:mail.model_res_partner view:mail.message:0
msgid "Partner"
msgstr "Контрагент"
msgstr "Партнёр"
#. module: mail
#: field:mail.compose.message,subject:0 field:mail.message,subject:0
@ -140,7 +140,7 @@ msgstr "Тема"
#: code:addons/mail/wizard/mail_compose_message.py:152
#, python-format
msgid "On %(date)s, "
msgstr ""
msgstr "За %(date)s, "
#. module: mail
#: field:mail.compose.message,email_from:0 field:mail.message,email_from:0
@ -151,32 +151,32 @@ msgstr "От"
#. module: mail
#: view:mail.message:0
msgid "Email message"
msgstr ""
msgstr "Сообщение эл. почты"
#. module: mail
#: view:mail.compose.message:0
msgid "Send"
msgstr ""
msgstr "Отправить"
#. module: mail
#: view:mail.message:0
msgid "Failed"
msgstr ""
msgstr "Ошибка"
#. module: mail
#: view:mail.message:0 field:mail.message,state:0
msgid "State"
msgstr ""
msgstr "Состояние"
#. module: mail
#: view:mail.message:0
msgid "Reply"
msgstr ""
msgstr "Ответить"
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Sent"
msgstr ""
msgstr "Отправлено"
#. module: mail
#: help:mail.compose.message,subtype:0 help:mail.message,subtype:0
@ -185,11 +185,13 @@ msgid ""
"Type of message, usually 'html' or 'plain', used to select plaintext or rich "
"text contents accordingly"
msgstr ""
"Тип сообщения, обычно 'html' или 'простой', используемый для выбора "
"содержимого соответственно простого текста или форматного текста"
#. module: mail
#: view:mail.message:0
msgid "Recipients"
msgstr ""
msgstr "Получатели"
#. module: mail
#: model:ir.model,name:mail.model_mail_compose_message
@ -200,18 +202,18 @@ msgstr ""
#: field:mail.compose.message,res_id:0 field:mail.message,res_id:0
#: field:mail.message.common,res_id:0
msgid "Related Document ID"
msgstr ""
msgstr "ID связанного документа"
#. module: mail
#: view:mail.message:0
msgid "Advanced"
msgstr ""
msgstr "Дополнительно"
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:157
#, python-format
msgid "Re:"
msgstr ""
msgstr "Ответ:"
#. module: mail
#: field:mail.compose.message,model:0 field:mail.message,model:0
@ -232,37 +234,37 @@ msgstr "Искать эл. почту"
#. module: mail
#: help:mail.message,original:0
msgid "Original version of the message, as it was sent on the network"
msgstr ""
msgstr "Оригинальная версия сообщения, как было отправлено в сеть"
#. module: mail
#: view:mail.message:0
msgid "Partner Name"
msgstr "Имя партнера"
msgstr "Название партнера"
#. module: mail
#: view:mail.message:0
msgid "Retry"
msgstr ""
msgstr "Повторить"
#. module: mail
#: view:mail.message:0 selection:mail.message,state:0
msgid "Outgoing"
msgstr ""
msgstr "Исходящее"
#. module: mail
#: view:mail.message:0
msgid "Send Now"
msgstr ""
msgstr "Отправить сейчас"
#. module: mail
#: field:mail.message,partner_id:0
msgid "Related partner"
msgstr ""
msgstr "Связанный партнер"
#. module: mail
#: view:mail.message:0
msgid "User"
msgstr ""
msgstr "Пользователь"
#. module: mail
#: field:mail.compose.message,date:0 field:mail.message,date:0
@ -273,13 +275,13 @@ msgstr "Дата"
#. module: mail
#: view:mail.message:0
msgid "Extended Filters..."
msgstr ""
msgstr "Расширенные фильтры..."
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:153
#, python-format
msgid "%(sender_name)s wrote:"
msgstr ""
msgstr "%(sender_name)s написал:"
#. module: mail
#: field:mail.compose.message,body_html:0 field:mail.message,body_html:0
@ -290,7 +292,7 @@ msgstr ""
#. module: mail
#: field:mail.message,original:0
msgid "Original"
msgstr ""
msgstr "Оригинал"
#. module: mail
#: code:addons/mail/mail_thread.py:247 view:res.partner:0
@ -302,13 +304,13 @@ msgstr "История"
#: field:mail.compose.message,message_id:0 field:mail.message,message_id:0
#: field:mail.message.common,message_id:0
msgid "Message-Id"
msgstr ""
msgstr "ID сообщения"
#. module: mail
#: view:mail.compose.message:0 field:mail.compose.message,attachment_ids:0
#: view:mail.message:0 field:mail.message,attachment_ids:0
msgid "Attachments"
msgstr "Прикрепленные файлы"
msgstr "Вложения"
#. module: mail
#: field:mail.compose.message,email_cc:0 field:mail.message,email_cc:0
@ -325,7 +327,7 @@ msgstr " на "
#. module: mail
#: help:mail.message,auto_delete:0
msgid "Permanently delete this email after sending it, to save space"
msgstr ""
msgstr "Навсегда удалить это письмо после отправки, для экономии места"
#. module: mail
#: field:mail.compose.message,references:0 field:mail.message,references:0
@ -341,18 +343,18 @@ msgstr "Вывести текст"
#. module: mail
#: view:mail.compose.message:0 view:mail.message:0
msgid "Cancel"
msgstr ""
msgstr "Отмена"
#. module: mail
#: view:mail.message:0
msgid "Open"
msgstr ""
msgstr "Открыть"
#. module: mail
#: code:addons/mail/mail_thread.py:434
#, python-format
msgid "[OpenERP-Forward-Failed] %s"
msgstr ""
msgstr "[OpenERP-Forward-Failed] %s"
#. module: mail
#: field:mail.message,user_id:0
@ -366,11 +368,13 @@ msgid ""
"Full message headers, e.g. SMTP session headers (usually available on "
"inbound messages only)"
msgstr ""
"Полные заголовки сообщения, т.е. заголовки сессии SMTP (обычно доступные "
"только во входящих сообщениях)"
#. module: mail
#: view:mail.message:0
msgid "Creation Month"
msgstr ""
msgstr "Месяц создания"
#. module: mail
#: field:mail.compose.message,email_to:0 field:mail.message,email_to:0
@ -387,7 +391,7 @@ msgstr "Подробности"
#: model:ir.actions.act_window,name:mail.action_view_mailgate_thread
#: view:mail.thread:0
msgid "Email Threads"
msgstr ""
msgstr "Цепочки эл.писем"
#. module: mail
#: help:mail.compose.message,email_from:0 help:mail.message,email_from:0
@ -396,28 +400,30 @@ msgid ""
"Message sender, taken from user preferences. If empty, this is not a mail "
"but a message."
msgstr ""
"Отправитель сообщения, взятый из параметров пользователя. Если пусто, то это "
"не почта, а сообщение."
#. module: mail
#: view:mail.message:0
msgid "Body (Plain)"
msgstr ""
msgstr "Текст (простой)"
#. module: mail
#: code:addons/mail/wizard/mail_compose_message.py:153
#, python-format
msgid "You"
msgstr ""
msgstr "Вы"
#. module: mail
#: help:mail.compose.message,message_id:0 help:mail.message,message_id:0
#: help:mail.message.common,message_id:0
msgid "Message unique identifier"
msgstr ""
msgstr "Уникальный идентификатор сообщения"
#. module: mail
#: view:mail.message:0
msgid "Body (Rich)"
msgstr ""
msgstr "Текст (форматный)"
#. module: mail
#: code:addons/mail/mail_message.py:155
@ -427,6 +433,9 @@ msgid ""
" Subject: %s \n"
"\t"
msgstr ""
"%s написал на %s: \n"
" Тема: %s \n"
"\t"
#. module: mail
#: model:ir.actions.act_window,name:mail.act_res_partner_emails
@ -456,53 +465,74 @@ msgstr "Скрытая копия"
#. module: mail
#: model:ir.model,name:mail.model_mail_message_common
msgid "mail.message.common"
msgstr ""
msgstr "mail.message.common"
#. module: mail
#: help:mail.compose.message,references:0 help:mail.message,references:0
#: help:mail.message.common,references:0
msgid "Message references, such as identifiers of previous messages"
msgstr ""
msgstr "Ссылки сообщения, такие как идентификаторы предыдущих сообщений"
#. module: mail
#: constraint:res.partner:0
msgid "Error ! You cannot create recursive associated members."
msgstr ""
msgstr "Ошибка! Вы не можете создавать рекурсивные ссылки на участников."
#. module: mail
#: help:mail.compose.message,email_cc:0 help:mail.message,email_cc:0
#: help:mail.message.common,email_cc:0
msgid "Carbon copy message recipients"
msgstr ""
msgstr "Получатели скрытой копии сообщения"
#. module: mail
#: selection:mail.message,state:0
msgid "Delivery Failed"
msgstr ""
msgstr "Доставка не удалась"
#. module: mail
#: model:ir.model,name:mail.model_mail_message
msgid "Email Message"
msgstr ""
msgstr "Сообщение эл.почты"
#. module: mail
#: model:ir.model,name:mail.model_mail_thread view:mail.thread:0
msgid "Email Thread"
msgstr ""
msgstr "Цепочка эл.почты"
#. module: mail
#: field:mail.compose.message,filter_id:0
msgid "Filters"
msgstr ""
msgstr "Фильтры"
#. module: mail
#: code:addons/mail/mail_thread.py:220
#, python-format
msgid "Mail attachment"
msgstr ""
msgstr "Почтовое вложение"
#. module: mail
#: help:mail.compose.message,reply_to:0 help:mail.message,reply_to:0
#: help:mail.message.common,reply_to:0
msgid "Preferred response address for the message"
msgstr ""
msgstr "Предпочтительный адрес ответа для сообщения"
#~ msgid "Message type"
#~ msgstr "Тип сообщения"
#~ msgid "Text contents"
#~ msgstr "Текстовое содержимое"
#~ msgid "Related Document model"
#~ msgstr "Модель связанного документа"
#~ msgid "E-mail composition wizard"
#~ msgstr "Мастер создания электронной почты"
#~ msgid "Rich-text contents"
#~ msgstr "Форматное текстовое сообщение"
#~ msgid "Related user"
#~ msgstr "Связанный пользователь"
#~ msgid "Message headers"
#~ msgstr "Заголовки сообщения"

View File

@ -84,9 +84,16 @@ class mail_notification(osv.Model):
return False
def set_message_read(self, cr, uid, msg_ids, read=None, context=None):
""" TDE note: add a comment, verify method calls, because js seems obfuscated. """
""" Set a message and its child messages as (un)read for uid.
:param bool read: read / unread
"""
# TDE note: use child_of or front-end send correct values ?
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
notif_ids = self.search(cr, uid, [('partner_id', '=', user_pid), ('message_id', 'in', msg_ids)], context=context)
notif_ids = self.search(cr, uid, [
('partner_id', '=', user_pid),
('message_id', 'in', msg_ids)
], context=context)
# all message have notifications: already set them as (un)read
if len(notif_ids) == len(msg_ids):
@ -130,7 +137,8 @@ class mail_notification(osv.Model):
def _notify(self, cr, uid, msg_id, context=None):
""" Send by email the notification depending on the user preferences """
context = context or {}
if context is None:
context = {}
# mail_noemail (do not send email) or no partner_ids: do not send, return
if context.get('mail_noemail'):
return True
@ -140,9 +148,15 @@ class mail_notification(osv.Model):
if not notify_partner_ids:
return True
# add the context in the email
# TDE FIXME: commented, to be improved in a future branch
# quote_context = self.pool.get('mail.message').message_quote_context(cr, uid, msg_id, context=context)
mail_mail = self.pool.get('mail.mail')
# add signature
body_html = msg.body
# if quote_context:
# body_html = tools.append_content_to_html(body_html, quote_context, plaintext=False)
signature = msg.author_id and msg.author_id.user_ids[0].signature or ''
if signature:
body_html = tools.append_content_to_html(body_html, signature)

View File

@ -82,9 +82,8 @@
</group>
</sheet>
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"
options='{"thread_level": 1}'/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
</div>
</form>
</field>

View File

@ -24,11 +24,17 @@ import tools
from email.header import decode_header
from openerp import SUPERUSER_ID
from osv import osv, orm, fields
from tools.translate import _
from openerp.osv import osv, orm, fields
from openerp.tools.translate import _
_logger = logging.getLogger(__name__)
try:
from mako.template import Template as MakoTemplate
except ImportError:
_logger.warning("payment_acquirer: mako templates not available, payment acquirer will not work!")
""" Some tools for parsing / creating email fields """
def decode(text):
"""Returns unicode() string conversion of the the given encoded smtp header text"""
@ -46,7 +52,7 @@ class mail_message(osv.Model):
_order = 'id desc'
_message_read_limit = 10
_message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read',
_message_read_fields = ['id', 'parent_id', 'model', 'res_id', 'body', 'subject', 'date', 'to_read', 'email_from',
'type', 'vote_user_ids', 'attachment_ids', 'author_id', 'partner_ids', 'record_name', 'favorite_user_ids']
_message_record_name_length = 18
_message_read_more_limit = 1024
@ -57,17 +63,14 @@ class mail_message(osv.Model):
return name[:self._message_record_name_length] + '...'
def _get_record_name(self, cr, uid, ids, name, arg, context=None):
""" Return the related document name, using name_get. It is included in
a try/except statement, because if uid cannot read the related
document, he should see a void string instead of crashing. """
""" Return the related document name, using name_get. It is done using
SUPERUSER_ID, to be sure to have the record name correctly stored. """
# TDE note: regroup by model/ids, to have less queries to perform
result = dict.fromkeys(ids, False)
for message in self.read(cr, uid, ids, ['model', 'res_id'], context=context):
if not message['model'] or not message['res_id']:
if not message.get('model') or not message.get('res_id'):
continue
try:
result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, uid, [message['res_id']], context=context)[0][1])
except (orm.except_orm, osv.except_osv):
pass
result[message['id']] = self._shorten_name(self.pool.get(message['model']).name_get(cr, SUPERUSER_ID, [message['res_id']], context=context)[0][1])
return result
def _get_to_read(self, cr, uid, ids, name, arg, context=None):
@ -78,10 +81,10 @@ class mail_message(osv.Model):
notif_ids = notif_obj.search(cr, uid, [
('partner_id', 'in', [partner_id]),
('message_id', 'in', ids),
('read', '=', False)
('read', '=', False),
], context=context)
for notif in notif_obj.browse(cr, uid, notif_ids, context=context):
res[notif.message_id.id] = not notif.read
res[notif.message_id.id] = True
return res
def _search_to_read(self, cr, uid, obj, name, domain, context=None):
@ -115,16 +118,21 @@ class mail_message(osv.Model):
], 'Type',
help="Message type: email for email message, notification for system "\
"message, comment for other messages such as user replies"),
'author_id': fields.many2one('res.partner', 'Author', required=True),
'partner_ids': fields.many2many('res.partner', 'mail_notification', 'message_id', 'partner_id', 'Recipients'),
'email_from': fields.char('From',
help="Email address of the sender. This field is set when no matching partner is found for incoming emails."),
'author_id': fields.many2one('res.partner', 'Author',
help="Author of the message. If not set, email_from may hold an email address that did not match any partner."),
'partner_ids': fields.many2many('res.partner', string='Recipients'),
'notified_partner_ids': fields.many2many('res.partner', 'mail_notification',
'message_id', 'partner_id', 'Recipients'),
'attachment_ids': fields.many2many('ir.attachment', 'message_attachment_rel',
'message_id', 'attachment_id', 'Attachments'),
'parent_id': fields.many2one('mail.message', 'Parent Message', select=True, ondelete='set null', help="Initial thread message."),
'child_ids': fields.one2many('mail.message', 'parent_id', 'Child Messages'),
'model': fields.char('Related Document Model', size=128, select=1),
'res_id': fields.integer('Related Document ID', select=1),
'record_name': fields.function(_get_record_name, type='string',
string='Message Record Name',
'record_name': fields.function(_get_record_name, type='char',
store=True, string='Message Record Name',
help="Name get of the related document."),
'notification_ids': fields.one2many('mail.notification', 'message_id', 'Notifications'),
'subject': fields.char('Subject'),
@ -198,19 +206,33 @@ class mail_message(osv.Model):
:param dict message: read result of a mail.message
"""
# TDE note: this method should be optimized, to lessen the number of queries, will be done ASAP
is_author = False
if message['author_id']:
is_author = message['author_id'][0] == self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
author_id = message['author_id']
elif message['email_from']:
author_id = (0, message['email_from'])
has_voted = False
if uid in message['vote_user_ids']:
if uid in message.get('vote_user_ids'):
has_voted = True
is_favorite = False
if uid in message['favorite_user_ids']:
if uid in message.get('favorite_user_ids'):
is_favorite = True
is_private = True
if message.get('model') and message.get('res_id'):
is_private = False
try:
attachment_ids = [{'id': attach[0], 'name': attach[1]} for attach in self.pool.get('ir.attachment').name_get(cr, uid, message['attachment_ids'], context=context)]
except (orm.except_orm, osv.except_osv):
attachment_ids = []
# TDE note: should we send partner_ids ?
# TDE note: shouldn't we separated followers and other partners ? costly to compute maybe ,
try:
partner_ids = self.pool.get('res.partner').name_get(cr, uid, message['partner_ids'], context=context)
except (orm.except_orm, osv.except_osv):
@ -226,95 +248,106 @@ class mail_message(osv.Model):
'record_name': message['record_name'],
'subject': message['subject'],
'date': message['date'],
'author_id': message['author_id'],
'is_author': message['author_id'] and message['author_id'][0] == uid,
# TDE note: is this useful ? to check
'author_id': author_id,
'is_author': is_author,
'partner_ids': partner_ids,
'parent_id': message['parent_id'] and message['parent_id'][0] or False,
# TDE note: see with CHM about votes, how they are displayed (only number, or name_get ?)
# vote: should only use number of votes
'parent_id': False,
'vote_nb': len(message['vote_user_ids']),
'has_voted': has_voted,
'is_private': message['model'] and message['res_id'],
'is_private': is_private,
'is_favorite': is_favorite,
'to_read': message['to_read'],
}
def _message_read_expandable(self, cr, uid, message_list, read_messages,
message_loaded_ids=[], domain=[], context=None, parent_id=False, limit=None):
""" Create the expandable message for all parent message read
this function is used by message_read
def _message_read_add_expandables(self, cr, uid, message_list, read_messages,
thread_level=0, message_loaded_ids=[], domain=[], parent_id=False, context=None, limit=None):
""" Create expandables for message_read, to load new messages.
1. get the expandable for new threads
if display is flat (thread_level == 0):
fetch message_ids < min(already displayed ids), because we
want a flat display, ordered by id
else:
fetch message_ids that are not childs of already displayed
messages
2. get the expandables for new messages inside threads if display
is not flat
for each thread header, search for its childs
for each hole in the child list based on message displayed,
create an expandable
:param list message_list: list of messages given by message_read to
which we have to add expandables
:param list message_list:list of message structure for the Chatter
widget to which expandables are added
:param dict read_messages: dict [id]: read result of the messages to
easily have access to their values, given their ID
:return bool: True
"""
# sort for group items / TDE: move to message_read
# result = sorted(result, key=lambda k: k['id'])
tree_not = []
# expandable for not show message
def _get_expandable(domain, message_nb, parent_id, id, model):
return {
'domain': domain,
'nb_messages': message_nb,
'type': 'expandable',
'parent_id': parent_id,
'id': id,
# TDE note: why do we need model sometimes, and sometimes not ???
'model': model,
}
# all_not_loaded_ids = []
id_list = sorted(read_messages.keys())
if not id_list:
return message_list
# 1. get the expandable for new threads
if thread_level == 0:
exp_domain = domain + [('id', '<', min(message_loaded_ids + id_list))]
else:
exp_domain = domain + ['!', ('id', 'child_of', message_loaded_ids + id_list)]
ids = self.search(cr, uid, exp_domain, context=context, limit=1)
if ids:
message_list.append(_get_expandable(exp_domain, -1, parent_id, -1, None))
# 2. get the expandables for new messages inside threads if display is not flat
if thread_level == 0:
return True
for message_id in id_list:
message = read_messages[message_id]
# message is not a thread header (has a parent_id)
# TDE note: parent_id is false is there is a parent we can not see -> ok
if message.get('parent_id'):
continue
# TDE note: check search is correctly implemented in mail.message
not_loaded_ids = self.search(cr, uid, [
('parent_id', '=', message['id']),
('id', 'child_of', message['id']),
('id', 'not in', message_loaded_ids),
], context=context, limit=self._message_read_more_limit)
# group childs not read
id_min = None
id_max = None
nb = 0
if not not_loaded_ids:
continue
# all_not_loaded_ids += not_loaded_ids
# group childs not read
id_min, id_max, nb = max(not_loaded_ids), 0, 0
for not_loaded_id in not_loaded_ids:
if not read_messages.get(not_loaded_id):
nb += 1
if id_min == None or id_min > not_loaded_id:
if id_min > not_loaded_id:
id_min = not_loaded_id
if id_max == None or id_max < not_loaded_id:
if id_max < not_loaded_id:
id_max = not_loaded_id
tree_not.append(not_loaded_id)
elif nb > 0:
exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
id_min, id_max, nb = max(not_loaded_ids), 0, 0
else:
if nb > 0:
message_list.append({
'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
'nb_messages': nb,
'type': 'expandable',
'parent_id': message_id,
'id': id_min,
'model': message['model']
})
id_min = None
id_max = None
nb = 0
id_min, id_max, nb = max(not_loaded_ids), 0, 0
if nb > 0:
message_list.append({
'domain': [('id', '>=', id_min), ('id', '<=', id_max), ('parent_id', '=', message_id)],
'nb_messages': nb,
'type': 'expandable',
'parent_id': message_id,
'id': id_min,
'model': message['model'],
})
exp_domain = [('id', '>=', id_min), ('id', '<=', id_max), ('id', 'child_of', message_id)]
message_list.append(_get_expandable(exp_domain, nb, message_id, id_min, message.get('model')))
for msg_id in read_messages.keys() + tree_not:
message_loaded_ids.append(msg_id)
# message_loaded_ids = list(set(message_loaded_ids + read_messages.keys() + all_not_loaded_ids))
# expandable for limit max
ids = self.search(cr, uid, domain + [('id', 'not in', message_loaded_ids)], context=context, limit=1)
if len(ids) > 0:
message_list.append({
'domain': domain,
'nb_messages': 0,
'type': 'expandable',
'parent_id': parent_id,
'id': -1,
'max_limit': True,
})
return message_list
return True
def _get_parent(self, cr, uid, message, context=None):
""" Tools method that tries to get the parent of a mail.message. If
@ -331,57 +364,70 @@ class mail_message(osv.Model):
except (orm.except_orm, osv.except_osv):
return False
def message_read(self, cr, uid, ids=False, domain=[], message_loaded_ids=[], context=None, parent_id=False, limit=None):
""" Read messages from mail.message, and get back a structured tree
of messages to be displayed as discussion threads. If IDs is set,
def message_read(self, cr, uid, ids=None, domain=None, message_unload_ids=None, thread_level=0, context=None, parent_id=False, limit=None):
""" Read messages from mail.message, and get back a list of structured
messages to be displayed as discussion threads. If IDs is set,
fetch these records. Otherwise use the domain to fetch messages.
After having fetch messages, their parents & child will be added to obtain
well formed threads.
After having fetch messages, their ancestors will be added to obtain
well formed threads, if uid has access to them.
TDE note: update this comment after final method implementation
After reading the messages, expandable messages are added in the
message list (see ``_message_read_add_expandables``). It consists
in messages holding the 'read more' data: number of messages to
read, domain to apply.
:param domain: optional domain for searching ids
:param limit: number of messages to fetch
:param parent_id: if parent_id reached, stop searching for
further parents
:return list: list of trees of messages
:param list ids: optional IDs to fetch
:param list domain: optional domain for searching ids if ids not set
:param list message_unload_ids: optional ids we do not want to fetch,
because i.e. they are already displayed somewhere
:param int parent_id: context of parent_id
- if parent_id reached when adding ancestors, stop going further
in the ancestor search
- if set in flat mode, ancestor_id is set to parent_id
:param int limit: number of messages to fetch, before adding the
ancestors and expandables
:return list: list of message structure for the Chatter widget
"""
if message_loaded_ids:
domain += [('id', 'not in', message_loaded_ids)]
# print 'message_read', ids, domain, message_unload_ids, thread_level, context, parent_id, limit
assert thread_level in [0, 1], 'message_read() thread_level should be 0 (flat) or 1 (1 level of thread); given %s.' % thread_level
domain = domain if domain is not None else []
message_unload_ids = message_unload_ids if message_unload_ids is not None else []
if message_unload_ids:
domain += [('id', 'not in', message_unload_ids)]
limit = limit or self._message_read_limit
read_messages = {}
message_list = []
# specific IDs given: fetch those ids and return directly the message list
if ids:
for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
message_list.append(self._message_get_dict(cr, uid, message, context=context))
message_list = sorted(message_list, key=lambda k: k['id'])
return message_list
# TDE FIXME: check access rights on search are implemented for mail.message
# fetch messages according to the domain, add their parents if uid has access to
ids = self.search(cr, uid, domain, context=context, limit=limit)
# no specific IDS given: fetch messages according to the domain, add their parents if uid has access to
if ids is None:
ids = self.search(cr, uid, domain, context=context, limit=limit)
for message in self.read(cr, uid, ids, self._message_read_fields, context=context):
# if not in tree and not in message_loded list
if not read_messages.get(message.get('id')) and message.get('id') not in message_loaded_ids:
read_messages[message.get('id')] = message
message_id = message['id']
# if not in tree and not in message_loaded list
if not message_id in read_messages and not message_id in message_unload_ids:
read_messages[message_id] = message
message_list.append(self._message_get_dict(cr, uid, message, context=context))
# get all parented message if the user have the access
# get the older ancestor the user can read, update its ancestor field
if not thread_level:
message_list[-1]['parent_id'] = parent_id
continue
parent = self._get_parent(cr, uid, message, context=context)
while parent and parent.get('id') != parent_id:
if not read_messages.get(parent.get('id')) and parent.get('id') not in message_loaded_ids:
read_messages[parent.get('id')] = parent
message_list.append(self._message_get_dict(cr, uid, parent, context=context))
parent = self._get_parent(cr, uid, parent, context=context)
message_list[-1]['parent_id'] = parent.get('id')
message = parent
parent = self._get_parent(cr, uid, message, context=context)
# if in thread: add its ancestor to the list of messages
if not message['id'] in read_messages and not message['id'] in message_unload_ids:
read_messages[message['id']] = message
message_list.append(self._message_get_dict(cr, uid, message, context=context))
# get the child expandable messages for the tree
message_list = sorted(message_list, key=lambda k: k['id'])
message_list = self._message_read_expandable(cr, uid, message_list, read_messages,
message_loaded_ids=message_loaded_ids, domain=domain, context=context, parent_id=parent_id, limit=limit)
self._message_read_add_expandables(cr, uid, message_list, read_messages, thread_level=thread_level,
message_loaded_ids=message_unload_ids, domain=domain, parent_id=parent_id, context=context, limit=limit)
# message_list = sorted(message_list, key=lambda k: k['id'])
return message_list
# TDE Note: do we need this ?
@ -394,7 +440,7 @@ class mail_message(osv.Model):
# return attachment_list
#------------------------------------------------------
# Email api
# mail_message internals
#------------------------------------------------------
def init(self, cr):
@ -402,23 +448,75 @@ class mail_message(osv.Model):
if not cr.fetchone():
cr.execute("""CREATE INDEX mail_message_model_res_id_idx ON mail_message (model, res_id)""")
def _search(self, cr, uid, args, offset=0, limit=None, order=None,
context=None, count=False, access_rights_uid=None):
""" Override that adds specific access rights of mail.message, to remove
ids uid could not see according to our custom rules. Please refer
to check_access_rule for more details about those rules.
After having received ids of a classic search, keep only:
- if author_id == pid, uid is the author, OR
- a notification (id, pid) exists, uid has been notified, OR
- uid have read access to the related document is model, res_id
- otherwise: remove the id
"""
# Rules do not apply to administrator
# print '_search', uid, args
if uid == SUPERUSER_ID:
return super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
context=context, count=count, access_rights_uid=access_rights_uid)
# Perform a super with count as False, to have the ids, not a counter
ids = super(mail_message, self)._search(cr, uid, args, offset=offset, limit=limit, order=order,
context=context, count=False, access_rights_uid=access_rights_uid)
if not ids and count:
return 0
elif not ids:
return ids
pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'])['partner_id'][0]
author_ids, partner_ids, allowed_ids = set([]), set([]), set([])
model_ids = {}
messages = super(mail_message, self).read(cr, uid, ids, ['author_id', 'model', 'res_id', 'notified_partner_ids'], context=context)
for message in messages:
if message.get('author_id') and message.get('author_id')[0] == pid:
author_ids.add(message.get('id'))
elif pid in message.get('notified_partner_ids'):
partner_ids.add(message.get('id'))
elif message.get('model') and message.get('res_id'):
model_ids.setdefault(message.get('model'), {}).setdefault(message.get('res_id'), set()).add(message.get('id'))
model_access_obj = self.pool.get('ir.model.access')
for doc_model, doc_dict in model_ids.iteritems():
if not model_access_obj.check(cr, uid, doc_model, 'read', False):
continue
doc_ids = doc_dict.keys()
allowed_doc_ids = self.pool.get(doc_model).search(cr, uid, [('id', 'in', doc_ids)], context=context)
allowed_ids |= set([message_id for allowed_doc_id in allowed_doc_ids for message_id in doc_dict[allowed_doc_id]])
final_ids = author_ids | partner_ids | allowed_ids
if count:
return len(final_ids)
else:
return list(final_ids)
def check_access_rule(self, cr, uid, ids, operation, context=None):
""" Access rules of mail.message:
- read: if
- notification exist (I receive pushed message) OR
- author_id = pid (I am the author) OR
- I can read the related document if res_model, res_id
- Otherwise: raise
- author_id == pid, uid is the author, OR
- mail_notification (id, pid) exists, uid has been notified, OR
- uid have read access to the related document if model, res_id
- otherwise: raise
- create: if
- I am in the document message_follower_ids OR
- I can write on the related document if res_model, res_id OR
- I create a private message (no model, no res_id)
- Otherwise: raise
- no model, no res_id, I create a private message
- pid in message_follower_ids if model, res_id OR
- uid have write access on the related document if model, res_id, OR
- otherwise: raise
- write: if
- I can write on the related document if res_model, res_id
- uid has write access on the related document if model, res_id
- Otherwise: raise
- unlink: if
- I can write on the related document if res_model, res_id
- uid has write access on the related document if model, res_id
- Otherwise: raise
"""
if uid == SUPERUSER_ID:
@ -428,15 +526,25 @@ class mail_message(osv.Model):
partner_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=None)['partner_id'][0]
# Read mail_message.ids to have their values
model_record_ids = {}
message_values = dict.fromkeys(ids)
model_record_ids = {}
cr.execute('SELECT DISTINCT id, model, res_id, author_id FROM "%s" WHERE id = ANY (%%s)' % self._table, (ids,))
for id, rmod, rid, author_id in cr.fetchall():
message_values[id] = {'res_model': rmod, 'res_id': rid, 'author_id': author_id}
if rmod:
model_record_ids.setdefault(rmod, set()).add(rid)
model_record_ids.setdefault(rmod, dict()).setdefault(rid, set()).add(id)
# Read: Check for received notifications -> could become an ir.rule, but not till we do not have a many2one variable field
# Author condition, for read and create (private message) -> could become an ir.rule, but not till we do not have a many2one variable field
if operation == 'read':
author_ids = [mid for mid, message in message_values.iteritems()
if message.get('author_id') and message.get('author_id') == partner_id]
elif operation == 'create':
author_ids = [mid for mid, message in message_values.iteritems()
if not message.get('model') and not message.get('res_id')]
else:
author_ids = []
# Notification condition, for read (check for received notifications and create (in message_follower_ids)) -> could become an ir.rule, but not till we do not have a many2one variable field
if operation == 'read':
not_obj = self.pool.get('mail.notification')
not_ids = not_obj.search(cr, SUPERUSER_ID, [
@ -444,38 +552,24 @@ class mail_message(osv.Model):
('message_id', 'in', ids),
], context=context)
notified_ids = [notification.message_id.id for notification in not_obj.browse(cr, SUPERUSER_ID, not_ids, context=context)]
else:
notified_ids = []
# Read: Check messages you are author -> could become an ir.rule, but not till we do not have a many2one variable field
if operation == 'read':
author_ids = [mid for mid, message in message_values.iteritems()
if message.get('author_id') and message.get('author_id') == partner_id]
# Create: Check messages you create that are private messages -> ir.rule ?
elif operation == 'create':
author_ids = [mid for mid, message in message_values.iteritems()
if not message.get('model') and not message.get('res_id')]
else:
author_ids = []
# Create: Check message_follower_ids
if operation == 'create':
doc_follower_ids = []
for model, mids in model_record_ids.items():
notified_ids = []
for doc_model, doc_dict in model_record_ids.items():
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [
('res_model', '=', model),
('res_id', 'in', list(mids)),
('res_model', '=', doc_model),
('res_id', 'in', list(doc_dict.keys())),
('partner_id', '=', partner_id),
], context=context)
fol_mids = [follower.res_id for follower in fol_obj.browse(cr, SUPERUSER_ID, fol_ids, context=context)]
doc_follower_ids += [mid for mid, message in message_values.iteritems()
if message.get('res_model') == model and message.get('res_id') in fol_mids]
notified_ids += [mid for mid, message in message_values.iteritems()
if message.get('res_model') == doc_model and message.get('res_id') in fol_mids]
else:
doc_follower_ids = []
notified_ids = []
# Calculate remaining ids, and related model/res_ids
model_record_ids = {}
other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids))
other_ids = set(ids).difference(set(author_ids), set(notified_ids))
for id in other_ids:
if message_values[id]['res_model']:
model_record_ids.setdefault(message_values[id]['res_model'], set()).add(message_values[id]['res_id'])
@ -495,7 +589,7 @@ class mail_message(osv.Model):
if message.get('res_model') == model and message.get('res_id') in mids]
# Calculate remaining ids: if not void, raise an error
other_ids = set(ids).difference(set(notified_ids), set(author_ids), set(doc_follower_ids), set(document_related_ids))
other_ids = other_ids - set(document_related_ids)
if not other_ids:
return
raise orm.except_orm(_('Access Denied'),
@ -529,50 +623,123 @@ class mail_message(osv.Model):
self.pool.get('ir.attachment').unlink(cr, uid, attachments_to_delete, context=context)
return super(mail_message, self).unlink(cr, uid, ids, context=context)
def _notify_followers(self, cr, uid, newid, message, context=None):
""" Add the related record followers to the destination partner_ids.
def copy(self, cr, uid, id, default=None, context=None):
""" Overridden to avoid duplicating fields that are unique to each email """
if default is None:
default = {}
default.update(message_id=False, headers=False)
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
#------------------------------------------------------
# Messaging API
#------------------------------------------------------
# TDE note: this code is not used currently, will be improved in a future merge, when quoted context
# will be added to email send for notifications. Currently only WIP.
MAIL_TEMPLATE = """<div>
% if message:
${display_message(message)}
% endif
% for ctx_msg in context_messages:
${display_message(ctx_msg)}
% endfor
% if add_expandable:
${display_expandable()}
% endif
${display_message(header_message)}
</div>
<%def name="display_message(message)">
<div>
Subject: ${message.subject}<br />
Body: ${message.body}
</div>
</%def>
<%def name="display_expandable()">
<div>This is an expandable.</div>
</%def>
"""
def message_quote_context(self, cr, uid, id, context=None, limit=3, add_original=False):
"""
partners_to_notify = set([])
# message has no subtype_id: pure log message -> no partners, no one notified
if not message.subtype_id:
message.write({'partner_ids': [5]})
return True
# all partner_ids of the mail.message have to be notified
if message.partner_ids:
partners_to_notify |= set(partner.id for partner in message.partner_ids)
# all followers of the mail.message document have to be added as partners and notified
if message.model and message.res_id:
fol_obj = self.pool.get("mail.followers")
fol_ids = fol_obj.search(cr, uid, [('res_model', '=', message.model), ('res_id', '=', message.res_id), ('subtype_ids', 'in', message.subtype_id.id)], context=context)
fol_objs = fol_obj.browse(cr, uid, fol_ids, context=context)
extra_notified = set(fol.partner_id.id for fol in fol_objs)
missing_notified = extra_notified - partners_to_notify
if missing_notified:
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, p_id) for p_id in missing_notified]}, context=context)
1. message.parent_id = False: new thread, no quote_context
2. get the lasts messages in the thread before message
3. get the message header
4. add an expandable between them
:param dict quote_context: options for quoting
:return string: html quote
"""
add_expandable = False
message = self.browse(cr, uid, id, context=context)
if not message.parent_id:
return ''
context_ids = self.search(cr, uid, [
('parent_id', '=', message.parent_id.id),
('id', '<', message.id),
], limit=limit, context=context)
if len(context_ids) >= limit:
add_expandable = True
context_ids = context_ids[0:-1]
context_ids.append(message.parent_id.id)
context_messages = self.browse(cr, uid, context_ids, context=context)
header_message = context_messages.pop()
try:
if not add_original:
message = False
result = MakoTemplate(self.MAIL_TEMPLATE).render_unicode(message=message,
context_messages=context_messages,
header_message=header_message,
add_expandable=add_expandable,
# context kw would clash with mako internals
ctx=context,
format_exceptions=True)
result = result.strip()
return result
except Exception:
_logger.exception("failed to render mako template for quoting message")
return ''
return result
def _notify(self, cr, uid, newid, context=None):
""" Add the related record followers to the destination partner_ids if is not a private message.
Call mail_notification.notify to manage the email sending
"""
message = self.browse(cr, uid, newid, context=context)
if message.model and message.res_id:
self._notify_followers(cr, uid, newid, message, context=context)
message = self.read(cr, uid, newid, ['model', 'res_id', 'author_id', 'subtype_id', 'partner_ids'], context=context)
# add myself if I wrote on my wall, otherwise remove myself author
if ((message.model == "res.partner" and message.res_id == message.author_id.id)):
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(4, message.author_id.id)]}, context=context)
else:
self.write(cr, SUPERUSER_ID, [newid], {'partner_ids': [(3, message.author_id.id)]}, context=context)
partners_to_notify = set([])
# message has no subtype_id: pure log message -> no partners, no one notified
if not message.get('subtype_id'):
return True
# all partner_ids of the mail.message have to be notified
if message.get('partner_ids'):
partners_to_notify |= set(message.get('partner_ids'))
# all followers of the mail.message document have to be added as partners and notified
if message.get('model') and message.get('res_id'):
fol_obj = self.pool.get("mail.followers")
fol_ids = fol_obj.search(cr, uid, [
('res_model', '=', message.get('model')),
('res_id', '=', message.get('res_id')),
('subtype_ids', 'in', message.get('subtype_id')[0])
], context=context)
fol_objs = fol_obj.read(cr, uid, fol_ids, ['partner_id'], context=context)
partners_to_notify |= set(fol['partner_id'][0] for fol in fol_objs)
# when writing to a wall
if message.get('author_id') and message.get('model') == "res.partner" and message.get('res_id') == message.get('author_id')[0]:
partners_to_notify |= set([message.get('author_id')[0]])
elif message.get('author_id'):
partners_to_notify = partners_to_notify - set([message.get('author_id')[0]])
if partners_to_notify:
self.write(cr, SUPERUSER_ID, [newid], {'notified_partner_ids': [(4, p_id) for p_id in partners_to_notify]}, context=context)
self.pool.get('mail.notification')._notify(cr, uid, newid, context=context)
def copy(self, cr, uid, id, default=None, context=None):
"""Overridden to avoid duplicating fields that are unique to each email"""
if default is None:
default = {}
default.update(message_id=False, headers=False)
return super(mail_message, self).copy(cr, uid, id, default=default, context=context)
#------------------------------------------------------
# Tools
#------------------------------------------------------

View File

@ -29,6 +29,7 @@
<group>
<field name="subject"/>
<field name="author_id"/>
<field name="email_from"/>
<field name="date"/>
<field name="type"/>
<field name="subtype_id"/>
@ -38,6 +39,7 @@
<field name="res_id"/>
<field name="parent_id"/>
<field name="partner_ids" widget="many2many_tags"/>
<field name="notified_partner_ids" widget="many2many_tags"/>
</group>
</group>
<field name="body"/>
@ -63,6 +65,9 @@
<filter string="Comments"
name="comments" help="Comments"
domain="[('type', '=', 'comment')]"/>
<filter string="Has attachments"
name="attachments"
domain="[('attachment_ids', '!=', False)]"/>
<filter string="Notifications"
name="notifications" help="Notifications"
domain="[('type', '=', 'notification')]"/>

View File

@ -554,7 +554,11 @@ class mail_thread(osv.AbstractModel):
('file2', 'bytes')}
}
"""
msg_dict = {}
msg_dict = {
'type': 'email',
'subtype': 'mail.mt_comment',
'author_id': False,
}
if not isinstance(message, Message):
if isinstance(message, unicode):
# Warning: message_from_string doesn't always work correctly on unicode,
@ -572,7 +576,7 @@ class mail_thread(osv.AbstractModel):
if 'Subject' in message:
msg_dict['subject'] = decode(message.get('Subject'))
# Envelope fields not stored in mail.message but made available for message_new()
# Envelope fields not stored in mail.message but made available for message_new()
msg_dict['from'] = decode(message.get('from'))
msg_dict['to'] = decode(message.get('to'))
msg_dict['cc'] = decode(message.get('cc'))
@ -581,6 +585,8 @@ class mail_thread(osv.AbstractModel):
author_ids = self._message_find_partners(cr, uid, message, ['From'], context=context)
if author_ids:
msg_dict['author_id'] = author_ids[0]
else:
msg_dict['email_from'] = message.get('from')
partner_ids = self._message_find_partners(cr, uid, message, ['From', 'To', 'Cc'], context=context)
msg_dict['partner_ids'] = partner_ids
@ -670,6 +676,18 @@ class mail_thread(osv.AbstractModel):
if self._mail_flat_thread and not parent_id and thread_id:
message_ids = mail_message.search(cr, uid, ['&', ('res_id', '=', thread_id), ('model', '=', model)], context=context, order="id ASC", limit=1)
parent_id = message_ids and message_ids[0] or False
# we want to set a parent: force to set the parent_id to the oldest ancestor, to avoid having more than 1 level of thread
elif parent_id:
message_ids = mail_message.search(cr, SUPERUSER_ID, [('id', '=', parent_id), ('parent_id', '!=', False)], context=context)
# avoid loops when finding ancestors
processed_list = []
if message_ids:
_counter, _counter_max = 0, 200
message = mail_message.browse(cr, SUPERUSER_ID, message_ids[0], context=context)
while (message.parent_id and message.parent_id.id not in processed_list):
processed_list.append(message.parent_id.id)
message = message.parent_id
parent_id = message.id
values = kwargs
values.update({
@ -689,27 +707,28 @@ class mail_thread(osv.AbstractModel):
return mail_message.create(cr, uid, values, context=context)
def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
# TDE FIXME: body is plaintext: convert it into html
# when writing on res.partner, without specific thread_id -> redirect to the user's partner
if self._name == 'res.partner' and not thread_id:
thread_id = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
""" Wrapper on message_post, used only in Chatter (JS). The purpose is
to handle attachments.
# TDE FIXME: body is plaintext: convert it into html
"""
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type='comment',
subtype='mail.mt_comment', parent_id=parent_id, context=context)
new_message_id = self.message_post(cr, uid, thread_id=thread_id, body=body, subject=subject, type=type,
subtype=subtype, parent_id=parent_id, context=context)
# Chatter: attachments linked to the document (not done JS-side), load the message
if attachments:
# HACK FIXME: Chatter: attachments linked to the document (not done JS-side), load the message
if attachment_ids:
ir_attachment = self.pool.get('ir.attachment')
mail_message = self.pool.get('mail.message')
attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [('res_model', '=', 'mail.message'), ('res_id', '=', 0), ('create_uid', '=', uid), ('id', 'in', attachments)], context=context)
if attachment_ids:
filtered_attachment_ids = ir_attachment.search(cr, SUPERUSER_ID, [
('res_model', '=', 'mail.compose.message'),
('res_id', '=', 0),
('create_uid', '=', uid),
('id', 'in', attachment_ids)], context=context)
if filtered_attachment_ids:
ir_attachment.write(cr, SUPERUSER_ID, attachment_ids, {'res_model': self._name, 'res_id': thread_id}, context=context)
mail_message.write(cr, SUPERUSER_ID, [new_message_id], {'attachment_ids': [(6, 0, [pid for pid in attachment_ids])]}, context=context)
new_message = self.pool.get('mail.message').message_read(cr, uid, [new_message_id], context=context)
return new_message
return new_message_id
#------------------------------------------------------
# Followers API

View File

@ -39,7 +39,7 @@
<!-- MENU -->
<!-- Top menu item -->
<menuitem name="Mails"
<menuitem name="Emails"
id="mail.mail_feeds_main"
groups="base.group_user"
sequence="10"/>

View File

@ -9,9 +9,8 @@
<field name="arch" type="xml">
<xpath expr="//sheet" position="after">
<div class="oe_chatter">
<field name="message_ids" widget="mail_thread"
options='{"thread_level": 1}'/>
<field name="message_follower_ids" widget="mail_followers"/>
<field name="message_ids" widget="mail_thread" options='{"thread_level": 1}'/>
</div>
</xpath>
</field>

View File

@ -111,8 +111,7 @@ class res_users(osv.Model):
alias_pool.unlink(cr, uid, alias_ids, context=context)
return res
def message_post_api(self, cr, uid, thread_id, body='', subject=False, type='notification',
subtype=None, parent_id=False, attachments=None, context=None, **kwargs):
def message_post_api(self, cr, uid, thread_id, body='', subject=False, parent_id=False, attachment_ids=None, context=None):
""" Redirect the posting of message on res.users to the related partner.
This is done because when giving the context of Chatter on the
various mailboxes, we do not have access to the current partner_id.
@ -124,7 +123,7 @@ class res_users(osv.Model):
thread_id = thread_id[0]
partner_id = self.pool.get('res.users').read(cr, uid, thread_id, ['partner_id'], context=context)['partner_id'][0]
return self.pool.get('res.partner').message_post_api(cr, uid, partner_id, body=body, subject=subject,
type=type, subtype=subtype, parent_id=parent_id, attachments=attachments, context=context, **kwargs)
parent_id=parent_id, attachment_ids=attachment_ids, context=context)
def message_post(self, cr, uid, thread_id, context=None, **kwargs):
""" Redirect the posting of message on res.users to the related partner.

View File

@ -2,6 +2,7 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message_all,mail.message.all,model_mail_message,,1,0,1,0
access_mail_message_group_user,mail.message.group.user,model_mail_message,base.group_user,1,1,1,1
access_mail_mail_all,mail.mail.all,model_mail_mail,,0,0,1,0
access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
access_mail_mail_system,mail.mail.system,model_mail_mail,base.group_system,1,1,1,1
access_mail_followers_all,mail.followers.all,model_mail_followers,,1,0,0,0
access_mail_followers_system,mail.followers.system,model_mail_followers,base.group_system,1,1,1,1
@ -12,6 +13,6 @@ access_mail_group_user,mail.group.user,model_mail_group,base.group_user,1,1,1,1
access_mail_alias_all,mail.alias.all,model_mail_alias,,1,0,0,0
access_mail_alias_user,mail.alias,model_mail_alias,base.group_user,1,1,1,0
access_mail_alias_system,mail.alias,model_mail_alias,base.group_system,1,1,1,1
access_mail_message_subtype,mail.message.subtype,model_mail_message_subtype,,1,1,1,1
access_mail_mail_user,mail.mail,model_mail_mail,base.group_user,1,1,1,0
access_mail_message_subtype_all,mail.message.subtype.all,model_mail_message_subtype,,1,0,0,0
access_mail_vote_all,mail.vote.all,model_mail_vote,,1,1,1,1
access_mail_favorite_all,mail.favorite.all,model_mail_favorite,,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mail_message_all mail.message.all model_mail_message 1 0 1 0
3 access_mail_message_group_user mail.message.group.user model_mail_message base.group_user 1 1 1 1
4 access_mail_mail_all mail.mail.all model_mail_mail 0 0 1 0
5 access_mail_mail_user mail.mail model_mail_mail base.group_user 1 1 1 0
6 access_mail_mail_system mail.mail.system model_mail_mail base.group_system 1 1 1 1
7 access_mail_followers_all mail.followers.all model_mail_followers 1 0 0 0
8 access_mail_followers_system mail.followers.system model_mail_followers base.group_system 1 1 1 1
13 access_mail_alias_all mail.alias.all model_mail_alias 1 0 0 0
14 access_mail_alias_user mail.alias model_mail_alias base.group_user 1 1 1 0
15 access_mail_alias_system mail.alias model_mail_alias base.group_system 1 1 1 1
16 access_mail_message_subtype access_mail_message_subtype_all mail.message.subtype mail.message.subtype.all model_mail_message_subtype 1 1 0 1 0 1 0
access_mail_mail_user mail.mail model_mail_mail base.group_user 1 1 1 0
17 access_mail_vote_all mail.vote.all model_mail_vote 1 1 1 1
18 access_mail_favorite_all mail.favorite.all model_mail_favorite 1 1 1 1

View File

@ -1,439 +1,382 @@
/* ------------------------------------------------------------ */
/* Reset because of ugly display of end of August
/* ------------------------------------------------------------ */
.openerp .oe_mail_wall ul, .openerp .oe_mail_wall li {
list-style-type: none;
padding: 0;
margin: 0;
/* ------------ MAIL WIDGET --------------- */
.openerp .oe_mail, .openerp .oe_mail *{
box-sizing: border-box;
}
.openerp .oe_chatter ul, .openerp .oe_chatter li {
list-style-type: none;
padding: 0;
margin: 0;
}
/* ------------------------------------------------------------ */
/* Wall
/* ------------------------------------------------------------ */
.openerp div.oe_mail_wall {
overflow: auto;
padding: 0;
background: white;
}
.openerp div.oe_mail_wall div.oe_mail_wall_aside {
margin-left: 565px;
margin: 8px;
}
.openerp div.oe_mail_wall ul.oe_mail_wall_threads {
float: left;
width: 560px;
margin: 8px;
list-style-type: none;
}
/* ------------------------------------------------------------ */
/* Followers
/* ------------------------------------------------------------ */
.openerp div.oe_mail_recthread_aside h4 {
display: inline-block;
}
.openerp div.oe_mail_recthread_aside button {
.openerp .oe_mail {
display: block;
position: relative;
margin: 0px;
}
.openerp div.oe_mail_recthread_aside label,
.openerp div.oe_mail_recthread_aside input {
cursor:pointer;
.openerp .oe_mail .oe_thread{
margin-left: 32px;
}
.openerp .oe_mail > .oe_thread{
margin-left: 0px;
}
/* Specific display of threads in the wall */
/* ------------------------------------------------------------ */
/* ---------------- MESSAGES ------------------ */
.openerp ul.oe_mail_wall_threads .oe_msg_content textarea.oe_mail_compose_textarea {
width: 434px;
height: 30px;
padding: 4px;
}
.openerp li.oe_mail_wall_thread:first .oe_msg_notification {
border-top: 0;
}
.openerp div.oe_thread_placeholder img {
width: 28px;
height: 28px;
}
.openerp div.oe_thread_placeholder div.oe_msg_content {
width: 440px;
}
/* ------------------------------------------------------------ */
/* RecordThread
/* ------------------------------------------------------------ */
.openerp .oe_form div.oe_chatter {
overflow: auto;
}
.openerp .oe_mail_record_wall {
margin: auto;
width: 560px;
}
.openerp .oe_mail_record_wall > .oe_mail_wall_threads {
float: left;
}
.openerp div.oe_mail_recthread_aside {
float: right;
width: 250px;
}
.openerp div.oe_mail_recthread_actions {
margin-bottom: 8px;
}
.openerp div.oe_mail_recthread_actions button {
width: 120px;
}
.openerp .oe_mail_recthread_aside .oe_follower.oe_follow {
color: white;
background-color: #8a89ba;
background-image: -webkit-gradient(linear, left top, left bottom, from(#8a89ba), to(#807fb4));
background-image: -webkit-linear-gradient(top, #8a89ba, #807fb4);
background-image: -moz-linear-gradient(top, #8a89ba, #807fb4);
background-image: -ms-linear-gradient(top, #8a89ba, #807fb4);
background-image: -o-linear-gradient(top, #8a89ba, #807fb4);
background-image: linear-gradient(to bottom, #8a89ba, #807fb4);
}
.openerp .oe_mail_recthread_aside .oe_follower.oe_following {
color: white;
background-color: #dc5f59;
background-image: -webkit-gradient(linear, left top, left bottom, from(#dc5f59), to(#b33630));
background-image: -webkit-linear-gradient(top, #dc5f59, #b33630);
background-image: -moz-linear-gradient(top, #dc5f59, #b33630);
background-image: -ms-linear-gradient(top, #dc5f59, #b33630);
background-image: -o-linear-gradient(top, #dc5f59, #b33630);
background-image: linear-gradient(to bottom, #dc5f59, #b33630);
}
.openerp .oe_mail_recthread_aside .oe_follower span {
display:none;
}
.openerp .oe_mail_recthread_aside .oe_following span.oe_following,
.openerp .oe_mail_recthread_aside .oe_notfollow span.oe_follow {
display:block;
}
.openerp div.oe_mail_recthread_followers {
margin-bottom: 8px;
}
/* ------------------------------------------------------------ */
/* subtypes
/* ------------------------------------------------------------ */
.openerp .oe_mail_subtypes {
display:inline-block;
.openerp .oe_mail .oe_msg{
position: relative;
z-index: 5;
background: #F4F5FA;
border-radius: 2px;
margin-bottom: 2px;
min-height: 42px;
border: solid 1px rgba(0,0,0,0.03);
}
.openerp .oe_mail_subtypes .oe_recthread_subtypes {
background: #fff;
padding: 2px;
border: 1px solid #aaaaaa;
border-top: 0px;
.openerp .oe_mail .oe_msg .oe_msg_left{
position: absolute;
z-index: 2;
left:0; top: 0; bottom: 0; width: 40px;
overflow: hidden;
}
.openerp .oe_mail_subtypes.oe_mouseout .oe_recthread_subtypes {
display: none;
.openerp .oe_mail .oe_msg .oe_msg_icon{
width: 32px;
margin: 4px;
border-radius: 2px;
}
.openerp .oe_mail_subtypes.oe_mouseover .oe_recthread_subtypes {
display: block;
}
/* ------------------------------------------------------------ */
/* Thread
/* ------------------------------------------------------------ */
.openerp div.oe_mail_thread_action {
white-space: normal;
padding: 8px;
z-index:5;
background: #fff;
}
.openerp div.oe_mail_thread_action:after {
content: "";
display: block;
clear: both;
}
/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
.openerp .oe_msg_content textarea.oe_mail_compose_textarea:focus,
.openerp .oe_msg_content div.oe_mail_compose_message_body textarea:focus {
outline: 0;
border-color: rgba(82, 168, 236, 0.8);
-moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
}
.openerp .oe_mail_vote_count,
.openerp .oe_msg_vote{
vertical-align: bottom;
}
.openerp button.oe_mail_starbox{
background: #ff0000;
}
.openerp button.oe_mail_starbox.oe_stared{
background: #00FF00;
}
.openerp div.oe_mail_thread_display {
white-space: normal;
}
.openerp div.oe_thread_placeholder {
margin-left: 66px;
}
.openerp li.oe_mail_thread_msg {
width: 560px;
}
.openerp div.oe_thread_placeholder li.oe_mail_thread_msg:last-child {
margin-bottom: 8px;
}
.openerp div.oe_mail_thread_more {
display: none;
border-bottom: 1px solid #D2D9E7;
}
.openerp li.oe_mail_thread_msg:after {
content: "";
display: block;
clear: both;
}
.openerp li.oe_mail_thread_msg.oe_mail_read,
.openerp li.oe_mail_thread_msg.oe_mail_read div {
border-left: #F0F0F0;
}
.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread,
.openerp li.oe_mail_thread_msg.oe_mail_read li.oe_mail_thread_msg.oe_mail_unread div {
background-color: #F6F6F6;
}
.openerp li.oe_mail_thread_msg.oe_mail_unread>div>ul>li.oe_unread,
.openerp li.oe_mail_thread_msg.oe_mail_read>div>ul>li.oe_read {
display: none;
}
.openerp li.oe_mail_thread_msg > div:after {
content: "";
display: block;
clear: both;
}
.openerp div.oe_mail_msg {
padding: 0;
margin: 0 0 4px 0;
}
.openerp .oe_msg_notification,
.openerp .oe_msg_expandable,
.openerp .oe_msg_comment,
.openerp .oe_msg_email {
padding: 8px;
background: white;
.openerp .oe_mail .oe_msg .oe_msg_center{
position: relative;
}
.openerp .oe_msg_notification:after,
.openerp .oe_msg_comment:after,
.openerp .oe_msg_email:after {
content: "";
display: block;
clear: both;
margin-left: 40px;
}
.openerp .oe_mail .oe_msg .oe_msg_footer{
padding-left: 4px;
overflow: hidden;
opacity:0.8;
-webkit-transition: opacity 0.2s linear;
}
.openerp .oe_mail .oe_msg .oe_msg_content{
display: block;
overflow: hidden;
padding: 4px;
padding-bottom:1px;
}
.openerp .oe_mail .oe_msg .oe_msg_content .oe_msg_title{
font-size: 13px;
margin-bottom: 0px;
margin-top: 2px;
}
.openerp div.oe_msg_content {
float: left;
/* a) Indented Messages */
.openerp .oe_mail .oe_msg_indented{
background: #FFF;
border: none;
margin-bottom:0px;
min-height:38px;
}
.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_icon{
width:32px;
margin:2px;
border-radius:2px;
}
.openerp .oe_mail .oe_msg .oe_subtle{
color: #B7B7D5;
}
.openerp .oe_mail .oe_msg_indented .oe_msg_center{
margin-left:34px;
}
.openerp .oe_mail .oe_msg.oe_msg_indented .oe_msg_content{
padding-top:2px;
}
/* b) Votes (likes) */
.openerp .oe_mail .oe_mail_vote_count{
display: inline;
position: relative;
width: 486px;
}
.openerp div.oe_msg_content > li {
float: left;
background: #7C7BAD;
color: white;
text-shadow: none;
border-radius: 3px;
margin: 0px;
padding-left: 3px;
padding-right: 18px;
margin-right: 3px;
}
.openerp .oe_msg_content:after {
content: "";
display: block;
clear: both;
.openerp .oe_mail .oe_mail_vote_count .oe_e{
position: absolute;
bottom: 1px;
right: 2px;
font-size: 26px;
}
.openerp .oe_chatter a {
cursor: pointer;
}
/* c) Message action icons */
.openerp img.oe_mail_icon {
width: 50px;
height: 50px;
.openerp .oe_mail .oe_msg.oe_msg_unread .oe_unread{
display:none;
}
.openerp img.oe_mail_thumbnail {
width: 28px;
height: 28px;
margin: 4px;
.openerp .oe_mail .oe_msg.oe_msg_read .oe_read{
display:none;
}
.openerp img.oe_mail_frame {
.openerp .oe_mail .oe_msg .oe_msg_icons{
float: right;
margin-top: 4px;
margin-right: 8px;
margin-left: 8px;
height: 24px;
-webkit-user-select: none;
}
.openerp .oe_mail .oe_msg .oe_msg_icons span{
float:right;
width:24px;
height:24px;
line-height:24px;
text-align: center;
overflow: hidden;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
-o-border-radius: 3px;
-ms-border-radius: 3px;
border-radius: 3px;
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
clip: rect(5px, 40px, 45px, 0px);
}
.openerp .oe_mail .oe_msg .oe_msg_icons a {
text-decoration: none;
color: #FFF;
text-shadow: 0px 1px #AAA,0px -1px #AAA, -1px 0px #AAA, 1px 0px #AAA, 0px 3px 3px rgba(0,0,0,0.1);
-webkit-transition: all 0.2s linear;
}
.openerp .oe_mail .oe_msg:hover .oe_msg_icons a{
opacity: 1;
-webkit-transition: all 0.1s linear;
}
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_star:hover a{
color: #FFF6C0;
text-shadow: 0px 1px #FFA162,0px -1px #FFA162, -1px 0px #FFA162, 1px 0px #FFA162, 0px 3px 3px rgba(0,0,0,0.1);
}
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_star.oe_starred a{
color: #FFE41F;
text-shadow: 0px 1px #DF6200,0px -1px #DF6200, -1px 0px #DF6200, 1px 0px #DF6200, 0px 3px 3px rgba(0,0,0,0.1);
}
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_reply:hover a{
color: #1fc0ff;
text-shadow: 0px 1px #184fc5,0px -1px #184fc5, -1px 0px #184fc5, 1px 0px #184fc5, 0px 3px 3px rgba(0,0,0,0.1);
}
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_read:hover a{
color: #bbbaff;
text-shadow: 0px 1px #7c7bad,0px -1px #7c7bad, -1px 0px #7c7bad, 1px 0px #7c7bad, 0px 3px 3px rgba(0,0,0,0.1);
}
.openerp .oe_mail .oe_msg .oe_msg_icons .oe_unread:hover a{
color: #c2ff00;
text-shadow: 0px 1px #009441,0px -1px #009441, -1px 0px #009441, 1px 0px #009441, 0px 3px 3px rgba(0,0,0,0.1);
}
.openerp .oe_mail .oe_msg .oe_msg_content textarea{
width: 100%;
height: 32px;
margin: 0px;
padding: 0px;
resize: vertical;
padding: 4px;
}
.openerp .oe_mail .oe_msg.oe_msg_composer_compact, .openerp .oe_mail .oe_msg.oe_msg_expandable{
padding:4px;
min-height:0px;
}
.openerp .oe_mail .oe_msg.oe_msg_composer_compact textarea{
height: 24px;
width: 100%;
}
.openerp .oe_mail_invisible {
/* ---------------- MESSAGE QUICK COMPOSER --------------- */
.openerp .oe_mail .oe_msg_composer .oe_msg_footer{
padding-right:4px;
padding-top: 2px;
padding-bottom:6px;
}
.openerp .oe_mail .oe_msg_attachments.oe_hidden,
.openerp .oe_mail .oe_msg_images.oe_hidden{
margin:0px;
border: none;
display: none;
}
/* ------------------------------------------------------------ */
/* Messages layout
/* ------------------------------------------------------------ */
.openerp .oe_mail_msg .oe_msg_title {
margin: 0;
font-size: 1.3em;
font-weight: bold;
.openerp .oe_mail .oe_msg_attachments{
margin-bottom: 4px;
margin-right: 0px;
font-size: 12px;
border-radius: 2px;
border: solid 1px rgba(124,123,173,0.14);
}
.openerp .oe_mail_msg .oe_msg_title a:link,
.openerp .oe_mail_msg .oe_msg_title a:visited {
color: #4C4C4C;
.openerp .oe_mail .oe_msg_attachments .oe_attachment{
padding: 2px;
padding-left: 4px;
padding-right: 4px;
}
.openerp .oe_mail .oe_msg_attachments .oe_attachment .oe_e{
font-size: 23px;
margin-top: -5px;
}
.openerp .oe_mail .oe_msg_attachments .oe_attachment .oe_e:hover{
text-decoration: none;
}
.openerp .oe_mail .oe_msg_attachments .oe_attachment:nth-child(odd){
background:white;
}
.openerp .oe_mail .oe_msg_attachments .oe_attachment:nth-child(even){
background: #F4F5FA;
}
.openerp .oe_mail .oe_msg_images {
display: block;
}
.openerp .oe_mail .oe_msg_footer button{
display: inline;
height: 24px;
font-size: 12px;
line-height: 12px;
vertical-align: middle;
}
.openerp .oe_mail .oe_msg_footer button.oe_attach{
width: 24px;
overflow: hidden;
}
.openerp .oe_mail .oe_msg_footer button.oe_attach .oe_e{
position: relative;
top: -1px;
left: -9px;
}
.openerp .oe_mail .oe_hidden_input_file, .openerp .oe_mail .oe_hidden_input_file form{
display:inline;
}
.openerp .oe_mail .oe_msg_footer button.oe_full{
width:24px;
overflow:hidden;
float: right;
}
.openerp .oe_mail .oe_msg_footer button.oe_full .oe_e{
position: relative;
top: -1px;
left: -9px;
}
.openerp .oe_mail button.oe_attach, .openerp .oe_mail button.oe_full{
background: transparent;
color: #7C7BAD;
box-shadow: none;
border: none;
text-shadow: none;
}
.openerp .oe_mail .oe_attach_label{
color: #7C7BAD;
margin-left: -3px;
}
.openerp .oe_mail .oe_msg_footer .oe_attachment_file .oe_form_binary_file{
display: inline-block;
margin-left: -47px;
height: 28px;
width: 52px;
margin-top: -6px;
}
.openerp .oe_mail .oe_mail_list_recipients{
font-size: 12px;
margin-top: 4px;
margin-bottom: 4px;
}
/* ---------------- HIDDEN MESSAGES ------------------ */
.openerp .oe_mail .oe_msg_content.oe_msg_more_message{
text-align: right;
}
.openerp .oe_mail .oe_msg_content.oe_msg_more_message .oe_separator{
height: 0;
border-bottom: dashed 1px #e6e6e6;
margin-left: -4px;
margin-right: 8px;
margin-top: 6px;
margin-bottom: -9px;
}
.openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more {
background: white;
margin-right: 280px;
padding-left: 8px;
padding-right: 8px;
text-decoration: none;
color: #b4b4b4;
}
.openerp .oe_mail .oe_msg_more_message .oe_msg_fetch_more:hover{
text-decoration: none;
}
.openerp .oe_mail_msg .oe_msg_body {
margin-bottom: .5em;
text-align: justify;
/* ---------------- FOLLOWERS ------------------ */
.openerp .oe_followers{
position: relative;
display: inline-block;
padding-top: 5px;
width: 160px;
float: right;
}
.openerp .oe_mail_msg .oe_msg_body pre {
font-family: "Lucida Grande", Helvetica, Verdana, Arial, sans-serif;
margin: 0px;
white-space: pre-wrap;
/* a) THE FOLLOW BUTTON */
.openerp .oe_followers button.oe_follower{
display: block;
text-align: center;
width:100%;
}
.openerp .oe_followers button.oe_follower.oe_following{
background-color: #3465A4;
background-image: -webkit-linear-gradient(top, #729FCF, #3465A4);
color: white;
}
.openerp .oe_followers button.oe_follower .oe_follow,
.openerp .oe_followers button.oe_follower .oe_unfollow,
.openerp .oe_followers button.oe_follower .oe_following{
display: none;
}
/* a.1) when following, show 'following' */
.openerp .oe_followers button.oe_follower.oe_following .oe_following{
display: inline;
}
/* a.2) when following and hovering, show 'unfollow' */
.openerp .oe_followers button.oe_follower.oe_following:hover .oe_following{
display: none;
}
.openerp .oe_followers button.oe_follower.oe_following:hover .oe_unfollow{
display: inline;
}
/* a.3) when not following show 'follow' */
.openerp .oe_followers button.oe_follower.oe_notfollow .oe_follow{
display: inline;
}
/* Read more/less link */
.openerp .oe_mail_msg span.oe_mail_reduce {
position: absolute;
right: 0;
.openerp .oe_followers .oe_subtype_list{
margin-top: 4px;
}
/* Dropdown menu */
.openerp .oe_mail ul.oe_mail_thread_display ul.oe_mail_thread_display {
position: relative;
border-left: 1px #DDD dashed;
/* b) THE FOLLOWERS */
.openerp .oe_followers .oe_follower_title{
display: inline;
}
.openerp .oe_mail ul.oe_header {
position: absolute;
right: 3px;
top: -6px;
z-index: 10;
.openerp .oe_followers .oe_follower_title_box{
margin-top: 12px;
margin-bottom: 4px;
}
.openerp .oe_mail ul.oe_header a {
text-decoration: none;
}
.openerp .oe_mail ul.oe_header>li {
display: inline-block;
height: 20px;
text-align: right;
}
/* Message footer */
.openerp .oe_mail_msg .oe_msg_footer {
color: #888;
}
.openerp .oe_mail_msg .oe_msg_footer li {
float: left;
margin-right: 3px;
}
.openerp .oe_mail_msg .oe_msg_footer li:after {
content: " · ";
}
.openerp .oe_mail_msg .oe_msg_footer li:last-child:after {
content: "";
}
/* Attachments list */
.openerp .oe_msg_content ul.oe_msg_attachments {
width: 100%;
margin: .5em 0 0 0;
padding: .5em 0;
list-style-position: inside;
}
.openerp .oe_msg_content ul.oe_msg_attachments.oe_hidden {
display: none;
}
.openerp .oe_msg_content ul.oe_msg_attachments li {
float: none;
height: 20px;
line-height: 20px;
margin: 0;
padding: 0;
list-style-type: square;
}
.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process {
.openerp .oe_followers .oe_invite{
float: right;
width: 200px;
height: 16px;
}
.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process div {
float: left;
width: 38px;
height: 16px;
margin-right: 2px;
background: #66FF66;
.openerp .oe_followers .oe_partner {
height: 32px;
overflow: hidden;
}
.openerp .oe_msg_content ul.oe_msg_attachments .oe_upload_in_process span {
color: #aaaaaa;
position: absolute;
.openerp .oe_followers .oe_partner img{
width: 32px;
margin-right:4px;
border-radius: 2px;
}
/* ------------------------------------------------------------ */
/* Topbar button
/* ------------------------------------------------------------ */
/* ---------------- MESSAGES BODY ------------------ */
.openerp .oe_mail .oe_msg_content .oe_blockquote,
.openerp .oe_mail .oe_msg_content blockquote {
padding: 4px;
border-radius: 2px;
border: solid 1px rgba(124,123,173,0.14);
}
.openerp .oe_topbar .oe_topbar_compose_full_email {
float: right;
margin: 3px 25px 0 0;
}
/* ----------- FORM INTEGRATION ------------ */
.openerp .oe_record_thread{
display: block;
margin-right: 180px;
}
/* ----------- INBOX INTEGRATION ----------- */
.openerp .oe_mail_wall .oe_mail{
margin: 16px;
width: 720px;
}

View File

@ -1,166 +0,0 @@
/* ------------------------------ */
/* Compose Message */
/* ------------------------------ */
.openerp .oe_msg_content .oe_mail_compose_message_footer {
height: 24px;
}
.openerp .oe_msg_content .oe_mail_compose_message_footer button.oe_mail_compose_message_button_send {
float: left;
}
.openerp .oe_mail .oe_mail_compose_textarea
{
display: none;
}
.openerp .oe_mail .oe_mail_compose_textarea .oe_mail_post_header,
.openerp .oe_mail .oe_mail_compose_textarea .oe_mail_post_footer,
{
position: relative;
}
.openerp .oe_mail .oe_mail_compose_textarea a.oe_cancel {
position: absolute;
right: -8px;
top: -8px;
}
.openerp .oe_mail .oe_mail_compose_textarea a.oe_cancel:first-of-type {
display:none;
}
.openerp .oe_mail .oe_mail_compose_textarea button.oe_full {
float: right;
position: relative;
right: -10px;
}
/* ------------------------------------------------------------ */
/* mail.compose.message : list_recipients
/* ------------------------------------------------------------ */
.openerp .oe_mail .oe_mail_list_recipients {
display: inline;
}
.openerp .oe_mail .oe_mail_list_recipients .oe_all_follower {
color: blue;
}
.openerp .oe_mail .oe_mail_list_recipients .oe_partner_follower a {
color: red;
}
.openerp .oe_mail .oe_mail_list_recipients .oe_hidden,
.openerp .oe_mail .oe_mail_list_recipients .oe_more_hidden {
display: none;
}
/* ------------------------------------------------------------ */
/* mail.compose.message : attachment
/* ------------------------------------------------------------ */
.openerp .oe_mail .oe_attachment_file {
display: inline-block;
}
.openerp .oe_mail .oe_attachment_file .oe_add {
float: left;
width: 24px;
height: 24px;
position: relative;
z-index: 10;
left: +2px;
top: +7px;
overflow: hidden;
}
/* attachment button: override of openerp values */
.openerp .oe_mail .oe_attachment_file .oe_add button,
.openerp .oe_mail .oe_attachment_file .oe_add input.oe_insert_file {
position: absolute;
bottom: +0px;
left: +0px;
height: 24px;
width: 24px;
margin: 0px;
padding: 0px;
}
.openerp .oe_mail .oe_attachment_file .oe_add input.oe_insert_file {
z-index:2;
width: 300px;
left: -100px;
background: transparent;
border: 0;
color: transparent;
}
.openerp .oe_mail .oe_attachment_file .oe_add button span {
position: relative;
bottom: +4px;
font-size: 30px;
}
.openerp .oe_mail .oe_msg_attachments input {
visibility: hidden;
}
.openerp .oe_mail .oe_mail_compose_attachment_list {
clear: both;
}
/* ------------------------------------------------------------ */
/* mail.compose.message
/* ------------------------------------------------------------ */
/* default textarea (oe_mail_compose_textarea), and body textarea for compose form view */
.openerp .oe_mail.oe_semantic_html_override .oe_mail_compose_textarea textarea.field_text,
.openerp .oe_mail div.oe_mail_compose_message_body textarea.field_text {
width: 100%;
min-height: 120px;
height: auto;
padding: 4px;
font-size: 12px;
border: 1px solid #cccccc;
}
/* not top textarea */
.openerp .oe_mail.oe_semantic_html_override .oe_semantic_html_override .oe_mail_compose_textarea textarea.field_text {
height: 60px;
}
/* form_view: delete white background */
.openerp .oe_msg_content div.oe_formview {
background-color: transparent;
}
.openerp .oe_msg_content div.oe_form_nosheet {
margin: 0px;
}
.openerp .oe_msg_content table.oe_form_group {
margin: 0px;
}
.openerp .oe_msg_content table.oe_form_field,
.openerp .oe_msg_content div.oe_form_field {
padding: 0px;
}
.openerp .oe_msg_content td.oe_form_group_cell {
vertical-align: bottom;
}
/* subject: change width */
.openerp .oe_msg_content .oe_form .oe_form_field input[type='text'] {
width: 472px;
}
/* body_html: cleditor */
.openerp .oe_msg_content div.cleditorMain {
border: 1px solid #cccccc;
}
/* destination_partner_ids */
.openerp .oe_msg_content div.text-core {
height: 22px !important;
width: 472px;
}

File diff suppressed because it is too large Load Diff

View File

@ -24,10 +24,9 @@ openerp_mail_followers = function(session, mail) {
init: function() {
this._super.apply(this, arguments);
this.options.image = this.node.attrs.image || 'image_small';
this.options.title = this.node.attrs.title || 'Followers';
this.options.comment = this.node.attrs.help || false;
this.options.displayed_nb = this.node.attrs.displayed_nb || 10;
this.image = this.node.attrs.image || 'image_small';
this.comment = this.node.attrs.help || false;
this.displayed_nb = this.node.attrs.displayed_nb || 10;
this.ds_model = new session.web.DataSetSearch(this, this.view.model);
this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
this.ds_users = new session.web.DataSetSearch(this, 'res.users');
@ -61,7 +60,7 @@ openerp_mail_followers = function(session, mail) {
self.do_unfollow();
});
// event: click on a subtype, that (un)subscribe for this subtype
this.$el.on('click', 'ul.oe_subtypes input', self.do_update_subscription);
this.$el.on('click', '.oe_subtype_list input', self.do_update_subscription);
// event: click on 'invite' button, that opens the invite wizard
this.$('.oe_invite').on('click', function (event) {
action = {
@ -115,20 +114,24 @@ openerp_mail_followers = function(session, mail) {
self.message_is_follower = (_.indexOf(self.get('value'), pid) != -1);
}).pipe(self.proxy('display_generic'));
},
_format_followers: function(count){
// TDE note: why redefining _t ?
function _t(str) { return str; }
var str = '';
if(count <= 0){
str = _t('No followers');
}else if(count === 1){
str = _t('One follower');
}else{
str = ''+count+' '+_t('followers');
}
return str;
},
/* Display generic info about follower, for people not having access to res_partner */
display_generic: function () {
var self = this;
var node_user_list = this.$('ul.oe_mail_followers_display').empty();
// format content: Followers (You and 0 other) // Followers (3)
var content = this.options.title;
if (this.message_is_follower) {
content += ' (You and ' + (this.get('value').length-1) + ' other)';
}
else {
content += ' (' + this.get('value').length + ')'
}
this.$('div.oe_mail_recthread_followers h4').html(content);
var node_user_list = this.$('.oe_follower_list').empty();
this.$('.oe_follower_title').html(this._format_followers(this.get('value').length));
},
/** Display the followers */
@ -137,16 +140,17 @@ openerp_mail_followers = function(session, mail) {
records = records || [];
this.message_is_follower = this.set_is_follower(records);
// clean and display title
var node_user_list = this.$('ul.oe_mail_followers_display').empty();
this.$('div.oe_mail_recthread_followers h4').html(this.options.title + ' (' + records.length + ')');
var node_user_list = this.$('.oe_follower_list').empty();
this.$('.oe_follower_title').html(this._format_followers(records.length));
// truncate number of displayed followers
truncated = records.splice(0, this.options.displayed_nb);
truncated = records.splice(0, this.displayed_nb);
_(truncated).each(function (record) {
record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
$(session.web.qweb.render('mail.followers.partner', {'record': record})).appendTo(node_user_list);
});
// FVA note: be sure it is correctly translated
if (truncated.length < records.length) {
$('<li>And ' + (records.length - truncated.length) + ' more.</li>').appendTo(node_user_list);
$('<div class="oe_partner">And ' + (records.length - truncated.length) + ' more.</div>').appendTo(node_user_list);
}
},
@ -172,7 +176,7 @@ openerp_mail_followers = function(session, mail) {
/** Fetch subtypes, only if current user is follower */
fetch_subtypes: function () {
var subtype_list_ul = this.$('.oe_subtypes').empty();
var subtype_list_ul = this.$('.oe_subtype_list').empty();
if (! this.message_is_follower) return;
var context = new session.web.CompoundContext(this.build_context(), {});
this.ds_model.call('message_get_subscription_data', [[this.view.datarecord.id], context]).pipe(this.proxy('display_subtypes'));
@ -181,13 +185,12 @@ openerp_mail_followers = function(session, mail) {
/** Display subtypes: {'name': default, followed} */
display_subtypes:function (data) {
var self = this;
var subtype_list_ul = this.$('.oe_subtypes');
var records = data[this.view.datarecord.id].message_subtype_data;
var subtype_list_ul = this.$('.oe_subtype_list');
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
_(records).each(function (record, record_name) {
record.name = record_name;
record.followed = record.followed || undefined;
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('ul.oe_subtypes') );
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
});
},
@ -210,7 +213,7 @@ openerp_mail_followers = function(session, mail) {
var self = this;
var checklist = new Array();
_(this.$('.oe_mail_recthread_actions input[type="checkbox"]')).each(function (record) {
_(this.$('.oe_actions input[type="checkbox"]')).each(function (record) {
if ($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')));
}

View File

@ -1,33 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<!-- this template contains the mail widget and is used to namespace the css -->
<t t-name="mail.Widget">
<div class="oe_mail">
</div>
</t>
<!--
mail.compose_message template
This template holds the composition form to write a note or send
an e-mail. It contains by default a textarea, that will be replaced
by another composition form in the main wall composition form, or
for main thread composition form in document form view.
mail.compose_message.compact template
This template holds the composition form to write a message, this box is converted into
mail.compose_message when focus on textarea
-->
<t t-name="mail.compose_message">
<div class="oe_mail_compose_textarea">
<img class="oe_mail_icon oe_mail_frame oe_left" alt="User img"/>
<div class="oe_msg_content">
<!-- contains the composition form -->
<!-- default content: old basic textarea -->
<div class="oe_mail_post_header">
<div t-if="widget.show_composer" t-attf-class="oe_msg oe_msg_composer #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
<div class="oe_msg_left">
<img class="oe_msg_icon" alt="User img" t-attf-src="#{widget.avatar}"/>
</div>
<div class="oe_msg_center">
<div class="oe_msg_content">
<t t-call="mail.thread.list_recipients"/>
<a class="oe_cancel oe_e">X</a>
<textarea class="field_text"></textarea>
</div>
<textarea class="field_text" placeholder="Add your comment here..."/>
<div class="oe_mail_post_footer">
<div class="oe_mail_compose_attachment_list"/>
<button class="oe_full">Full mail message</button>
<button class="oe_post">Post message</button>
<div class="oe_msg_footer">
<div class="oe_msg_attachment_list"></div>
<button class="oe_post">Post</button>
<t t-call="mail.compose_message.add_attachment"/>
<!--<a class="oe_cancel oe_e">X</a>-->
<button class="oe_full"><span class='oe_e'>&amp;ograve</span></button>
</div>
</div>
<div class="oe_clear"/>
</div>
<div t-if="widget.show_compact_message and !widget.show_composer" t-attf-class="oe_msg oe_msg_composer_compact #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''}">
<textarea class="field_text oe_compact" placeholder="Write a reply..."/>
</div>
<span t-if="!(widget.show_compact_message and !widget.show_composer) and !widget.show_composer" class="oe_placeholder_compose"></span>
</t>
<!--
@ -35,19 +48,20 @@
Small template to be inserted in the composition for add attachments
-->
<t t-name="mail.compose_message.add_attachment">
<div class="oe_attachment_file">
<div class="oe_add">
<span class="oe_attachment_file">
<span class="oe_add">
<!-- uploader of file -->
<button><span class="oe_e">p</span></button>
<button class="oe_attach"><span class="oe_e">'</span></button>
<span class='oe_attach_label'>File</span>
<t t-call="HiddenInputFile">
<t t-set="fileupload_id" t-value="widget.fileupload_id"/>
<t t-set="fileupload_action">/web/binary/upload_attachment</t>
<input type="hidden" name="model" value="mail.message"/>
<input type="hidden" name="model" value="mail.compose.message"/>
<input type="hidden" name="id" value="0"/>
<input type="hidden" name="session_id" t-att-value="widget.session.session_id"/>
</t>
</div>
</div>
</span>
</span>
</t>
<!--
@ -55,25 +69,33 @@
Template used to display attachments in a mail.message
-->
<t t-name="mail.thread.message.attachments">
<ul t-attf-class="oe_msg_attachments #{widget.datasets.attachment_ids[0] and widget.options.thread.show_attachment_link?'':'oe_hidden'}">
<t t-foreach="widget.datasets.attachment_ids" t-as="attachment">
<li>
<span t-if="(attachment.upload or attachment.percent_loaded&lt;100)" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-attf-name="{attachment.name || attachment.filename}">
<span class="oe_msg_attachments">
<t t-foreach="widget.attachment_ids" t-as="attachment" t-if="!attachment.is_image">
<div class="oe_attachment">
<span t-if="(attachment.upload and attachment.percent_loaded&lt;100)" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-attf-name="{attachment.name || attachment.filename}">
<div class="oe_upload_in_process">
<span>...Upload in progress...</span>
</div>
<t t-raw="attachment.name || attachment.filename"/>
</span>
<a t-if="(!attachment.upload or attachment.percent_loaded&gt;=100)" t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
<t t-raw="attachment.name || attachment.filename"/>
</a>
<t t-if="widget.options.thread.show_attachment_delete and (!attachment.upload or attachment.percent_loaded&gt;=100)">
<a class="oe_right oe_mail_attachment_delete" title="Delete this attachment" t-attf-data-id="{attachment.id}">x</a>
<t t-if="(!attachment.upload or attachment.percent_loaded&gt;=100)">
<a t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
<t t-raw="attachment.name || attachment.filename"/>
</a>
</t>
</li>
<t t-if="(widget.show_delete_attachment and (!attachment.upload or attachment.percent_loaded&gt;=100))">
<a class="oe_right oe_mail_attachment_delete oe_e" title="Delete this attachment" t-attf-data-id="{attachment.id}">[</a>
</t>
</div>
</t>
</ul>
</span>
<span class="oe_msg_images">
<t t-foreach="widget.attachment_ids" t-as="attachment" t-if="attachment.is_image">
<a t-att-href="attachment.url" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}">
<img t-if="attachment.is_image" t-attf-title="{(attachment.name || attachment.filename) + (attachment.date?' \n('+attachment.date+')':'' )}" t-att-src="attachment.url"/>
</a>
</t>
</span>
</t>
<t t-name="mail.thread.message.private">
@ -89,14 +111,14 @@
-->
<t t-name="mail.thread.list_recipients">
<div class="oe_mail_list_recipients">
Post to:
<span t-if="!widget.datasets.is_private" class="oe_all_follower">All Followers</span>
<t t-if="!widget.datasets.is_private and widget.datasets.partner_ids.length"> and </t>
To:
<span t-if="!widget.is_private" class="oe_all_follower">Everyone</span>
<t t-if="!widget.is_private and widget.partner_ids.length"> and </t>
<t t-set="inc" t-value="0"/>
<t t-if="widget.datasets.partner_ids.length" t-foreach="widget.datasets.partner_ids" t-as="partner"><span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/><a t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-raw="partner[1]"/></a></span><t t-set="inc" t-value="inc+1"/>
<t t-if="widget.partner_ids.length" t-foreach="widget.partner_ids" t-as="partner"><span t-attf-class="oe_partner_follower #{inc>=3?'oe_hidden':''}"><t t-if="inc" t-raw="', '"/><a t-attf-href="#model=res.partner&amp;id=#{partner[0]}"><t t-raw="partner[1]"/></a></span><t t-set="inc" t-value="inc+1"/>
</t>
<t t-if="widget.datasets.partner_ids.length>=3">
<span class="oe_more">, <a><t t-raw="widget.datasets.partner_ids.length-3"/> others...</a></span>
<t t-if="widget.partner_ids.length>=3">
<span class="oe_more">, <a><t t-raw="widget.partner_ids.length-3"/> others...</a></span>
<a class="oe_more_hidden">&lt;&lt;&lt;</a>
</t>
</div>
@ -131,122 +153,95 @@
<button type="button" class="oe_write_full oe_highlight">
Compose a new message
</button>
<button type="button" class="oe_write_onwall" help="Your followers can read this message">
Write to your followers
</button>
<span class='oe_alternative'>
or
<a href='#' class='oe_write_onwall oe_bold' help='Your followers can read this message'>Write to your followers</a>
</span>
</td>
</tr>
</tbody>
</table>
<!-- placeholder for the wall threads -->
<div class="oe_mail_wall_threads"/>
<div class="oe_mail_wall_aside">
<!-- contains currently nothing -->
</div>
<div class="oe_mail-placeholder"></div>
</div>
<!--
display message on the wall when there are no message
-->
<li t-name="mail.wall_no_message" class="oe_wall_no_message">
You have no messages
</li>
<t t-name="mail.wall_no_message">
<div class="oe_wall_no_message">You have no messages</div>
</t>
<!--
record_thread main template
Template used to display the communication history in documents
form view.
-->
<div t-name="mail.record_thread" class="oe_mail_record_wall">
<!-- <h4>History and Comments</h4> -->
<ul class="oe_mail_wall_threads">
<!-- contains the document thread -->
</ul>
<div t-name="mail.record_thread" class="oe_record_thread">
<div class="oe_mail-placeholder">
</div>
</div>
<!--
thread template
This template holds a thread of comments. It begins with an actions
container, holding the composition form. Then come the various
messages. Then comes the 'more' button.
-->
<div t-name="mail.thread" class="oe_mail oe_mail_thread oe_semantic_html_override">
<div class="oe_mail_thread_action">
<!-- contains the composition box (form + image) -->
</div>
<ul class="oe_mail_thread_display">
<!-- contains the threads -->
</ul>
</div>
<t t-name="mail.thread">
<div t-attf-class="oe_thread #{widget.root?'oe_root_thread':''}"/>
</t>
<!-- default layout -->
<li t-name="mail.thread.message" t-attf-class="oe_mail oe_mail_thread_msg #{widget.datasets.to_read ?'oe_mail_unread':'oe_mail_read'}">
<div t-attf-class="oe_msg_#{widget.datasets.type} oe_semantic_html_override">
<!-- message actions (read/unread, reply, delete...) -->
<ul class="oe_header">
<li class="placeholder-mail-vote"><t t-call="mail.thread.message.vote"/></li>
<li class="placeholder-mail-star"><t t-call="mail.thread.message.star"/></li>
<li t-if="widget.datasets.show_read_unread" title="Read" class="oe_read"><a class="oe_read oe_e">W</a></li>
<li t-if="widget.datasets.show_read_unread" title="Set back to unread" class="oe_unread"><a class="oe_unread oe_e">h</a></li>
<li title="Quick reply" t-if="widget.datasets.show_reply"><a class="oe_reply oe_e">)</a></li>
<t t-if="(widget.datasets.is_author and widget.options.message.show_dd_delete) or widget.datasets.type == 'email'">
<li>
<span class="oe_dropdown_toggle">
<a class="oe_e" title="More options">í</a>
<ul class="oe_dropdown_menu">
<li t-if="widget.datasets.is_author and widget.options.message.show_dd_delete"><a class="oe_msg_delete">Delete</a></li>
<li t-if="widget.datasets.type == 'email'"><a class="oe_msg_details" t-attf-href="#model=mail.message&amp;id=#{widget.datasets.id}" >Details</a></li>
</ul>
</span>
</li>
</t>
</ul>
<t t-name="mail.thread.message">
<div t-attf-class="oe_msg #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} oe_msg_#{widget.type} oe_msg_#{widget.to_read?'unread':'read'}">
<a t-attf-href="#model=res.partner&amp;id=#{widget.datasets.author_id[0]}" t-att-title="widget.datasets.author_id[1]">
<img class="oe_mail_icon oe_mail_frame oe_left" t-att-src="widget.datasets.avatar"/>
</a>
<div class='oe_msg_left'>
<a t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}" t-att-title="widget.author_id[1]">
<img class="oe_msg_icon" t-att-src="widget.avatar"/>
</a>
</div>
<div class="oe_msg_content">
<div class="oe_msg_center">
<div class='oe_msg_icons'>
<span class='oe_read' t-if="widget.show_read_unread_button"><a title="Read" class="oe_e">X</a></span>
<span class='oe_unread' t-if="widget.show_read_unread_button"><a title="Set back to unread" class="oe_e">v</a></span>
<span class='oe_reply' t-if="widget.show_reply_button"><a title="Reply" class="oe_e">(</a></span>
<span t-attf-class="oe_star #{widget.is_favorite?'oe_starred':''}"><a title="Add To Favorites" class="oe_e">7</a></span>
</div>
<!-- message itself -->
<div class="oe_mail_msg">
<h1 t-if="widget.datasets.subject" class="oe_msg_title">
<t t-raw="widget.datasets.subject"/>
<div class="oe_msg_content">
<h1 t-if="widget.subject and !widget.thread_level" class="oe_msg_title">
<t t-raw="widget.subject"/>
</h1>
<ul class="oe_msg_footer">
<li t-if="widget.datasets.author_id"><a t-attf-href="#model=res.partner&amp;id=#{widget.datasets.author_id[0]}"><t t-raw="widget.datasets.author_id[1]"/></a></li>
<li><span t-att-title="widget.datasets.date"><t t-raw="widget.datasets.timerelative"/></span></li>
<li t-if="widget.datasets.attachment_ids.length > 0">
<a class="oe_msg_view_attachments">
<t t-if="widget.datasets.attachment_ids.length == 1">1 Attachment</t>
<t t-if="widget.datasets.attachment_ids.length > 1"><t t-raw="widget.datasets.attachment_ids.length"/> Attachments</t>
</a>
</li>
</ul>
<div class="oe_clear"/>
<div class="oe_msg_body">
<t t-if="widget.options.message.show_record_name and widget.datasets.record_name and (!widget.datasets.subject) and !widget.options.thread.thread_level and !widget.options.thread.display_on_thread[0] and widget.datasets.model!='res.partner'">
<a class="oe_mail_action_model" t-attf-href="#model=#{widget.datasets.model}&amp;id=#{widget.res_id}"><t t-raw="widget.datasets.record_name"/></a>
<t t-if="widget.options.show_record_name and widget.record_name and (!widget.subject) and !widget.options.thread_level and !widget.options.display_on_thread[0] and widget.model!='res.partner'">
<a class="oe_mail_action_model" t-attf-href="#model=#{widget.model}&amp;id=#{widget.res_id}"><t t-raw="widget.record_name"/></a>
</t>
<t t-raw="widget.datasets.body"/>
<t t-raw="widget.body"/>
</div>
<t t-if="widget.datasets.attachment_ids.length > 0">
<div class="oe_clear"></div>
<t t-if="widget.attachment_ids.length > 0">
<t t-call="mail.thread.message.attachments"/>
</t>
</div>
<div class="oe_msg_footer">
<a t-if="widget.author_id" t-attf-href="#model=res.partner&amp;id=#{widget.author_id[0]}"><t t-raw="widget.author_id[1]"/></a>
<span class='oe_subtle'></span>
<span t-att-title="widget.date"><t t-raw="widget.timerelative"/></span>
<a t-if="widget.attachment_ids.length > 0" class="oe_mail_msg_view_attachments">
<t t-if="widget.attachment_ids.length == 1">1 Attachment</t>
<t t-if="widget.attachment_ids.length > 1"><t t-raw="widget.attachment_ids.length"/> Attachments</t>
</a>
<span class='oe_subtle'></span>
<t t-call="mail.thread.message.vote"/>
</div>
</div>
</div>
<div class="oe_thread_placeholder"></div>
</li>
</t>
<!-- expandable message layout -->
<li t-name="mail.thread.expandable" class="oe_mail oe_mail_thread_msg oe_mail_unread">
<div t-attf-class="oe_msg_#{widget.datasets.type} oe_semantic_html_override">
<t t-name="mail.thread.expandable">
<div t-attf-class="oe_msg oe_msg_#{widget.type} #{widget.thread_level and widget.options.display_indented_thread > -1 ? 'oe_msg_indented' : ''} oe_msg_unread">
<div class="oe_msg_content oe_msg_more_message">
<a class="oe_mail_fetch_more">Load more messages <span t-if="widget.datasets.nb_messages>0">(<t t-raw="widget.datasets.nb_messages"/> messages not display)</span>...</a>
<div class='oe_separator'></div>
<a t-if="widget.nb_messages === 1" class="oe_msg_fetch_more">show one more message</a>
<a t-if="widget.nb_messages !== 1" class="oe_msg_fetch_more">show <t t-raw="widget.nb_messages" /> more messages</a>
</div>
</div>
</li>
</t>
<!--
mail.compose_message.button_top_bar
@ -254,7 +249,7 @@
-->
<t t-name="mail.compose_message.button_top_bar">
<div class="oe_topbar_compose_full_email">
<button class="oe_button oe_highlight">Write an email</button>
<!-- <button class="oe_button oe_highlight">Write an email</button> -->
</div>
</t>
@ -262,33 +257,18 @@
Template used to display Like/Unlike in a mail.message
-->
<span t-name="mail.thread.message.vote">
<span class="oe_left oe_mail_vote_count">
<t t-if='widget.datasets.has_voted'>
You
</t>
<t t-if='(widget.datasets.vote_user_ids.length-(widget.datasets.has_voted?1:0)) > 0'>
<t t-if='widget.datasets.has_voted'> and </t>
<t t-esc="widget.datasets.vote_user_ids.length"/> people
</t>
<t t-if='widget.datasets.vote_user_ids.length > 0'>
agree
</t>
<span class="oe_mail_vote_count" t-if='widget.vote_nb > 0'>
<t t-raw='widget.vote_nb' />
<span class='oe_e'>8</span>
</span>
<button t-attf-class="oe_msg_vote oe_tag">
<span>
<t t-if="!widget.has_voted">Agree</t>
<t t-if="widget.has_voted">Undo</t>
</span>
</button>
<a href='#' t-attf-class="oe_msg_vote">
<t t-if="!widget.has_voted">like</t>
<t t-if="widget.has_voted">unlike</t>
</a>
</span>
<!-- mail.thread.message.star
Template used to display stared/unstared message in a mail.message
-->
<span t-name="mail.thread.message.star">
<span class="oe_left">
<button t-attf-class="oe_mail_starbox oe_tag #{widget.datasets.is_favorite?'oe_stared':''}">*</button>
</span>
</span>
</template>

View File

@ -5,51 +5,45 @@
followers main template
Template used to display the followers, the actions and the subtypes in a record.
-->
<div t-name="mail.followers" class="oe_mail_recthread_aside oe_semantic_html_override">
<div class="oe_mail_recthread_actions">
<div class="oe_mail_subtypes">
<button type="button" class="oe_follower oe_notfollow">
<span class="oe_follow">Follow</span>
<span class="oe_unfollow">Unfollow</span>
<span class="oe_following">Following</span>
</button>
</div>
<div class="oe_recthread_subtypes">
<ul class="oe_subtypes"></ul>
</div>
<div t-name="mail.followers" class="oe_followers">
<div class="oe_actions">
<button type="button" class="oe_follower oe_notfollow">
<span class="oe_follow">Follow</span>
<span class="oe_unfollow">Unfollow</span>
<span class="oe_following">Following</span>
</button>
<div class="oe_subtype_list"></div>
</div>
<t t-if="widget.options.comment">
<h5 class="oe_grey"><t t-raw="widget.options.comment"/></h5>
<t t-if="widget.comment">
<h5 class="oe_comment"><t t-raw="widget.comment"/></h5>
</t>
<div class="oe_mail_recthread_followers">
<button type="button" class="oe_invite"><span>Invite</span></button>
<t t-if="widget.options.title">
<h4><t t-raw="widget.options.title"/></h4>
</t>
<ul class="oe_mail_followers_display"></ul>
<div class='oe_follower_title_box'>
<h4 class='oe_follower_title'>Followers</h4>
<a href='#' class="oe_invite">Invite others</a>
</div>
<div class="oe_follower_list"></div>
</div>
<!--
followers.partner template
Template used to display a partner following the record
-->
<li t-name="mail.followers.partner">
<div t-name="mail.followers.partner" class='oe_partner'>
<img class="oe_mail_thumbnail oe_mail_frame" t-attf-src="{record.avatar_url}"/>
<a t-attf-href="#model=res.partner&amp;id=#{record.id}"><t t-raw="record.name"/></a>
</li>\
</div>
<!--
followers.subtype template
Template used to display message subtypes of a follower subscription
-->
<li t-name="mail.followers.subtype">
<table width="50%">
<t t-name="mail.followers.subtype">
<table class='oe_subtype'>
<tr>
<td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-raw="record.name"/></label></td>
<td width="10%"><input type="checkbox" t-att-checked="record.followed" t-att-id="'input_mail_followers_subtype_'+record.id" t-att-data-id="record.id" t-att-name="record.name" class="oe_msg_subtype_check"/></td>
<td><label t-att-for="'input_mail_followers_subtype_'+record.id"><t t-raw="record.name"/></label></td>
</tr>
</table>
</li>
</t>
</template>

View File

@ -94,6 +94,8 @@ class TestMailMockups(common.TransactionCase):
self._build_email_kwargs_list = []
def _mock_build_email(self, *args, **kwargs):
""" Mock build_email to be able to test its values. Store them into
some internal variable for latter processing. """
self._build_email_args_list.append(args)
self._build_email_kwargs_list.append(kwargs)
return self._build_email(*args, **kwargs)
@ -123,6 +125,7 @@ class test_mail(TestMailMockups):
def setUp(self):
super(test_mail, self).setUp()
cr, uid = self.cr, self.uid
self.ir_model = self.registry('ir.model')
self.mail_alias = self.registry('mail.alias')
self.mail_thread = self.registry('mail.thread')
@ -135,22 +138,27 @@ class test_mail(TestMailMockups):
self.res_users = self.registry('res.users')
self.res_partner = self.registry('res.partner')
# Find Employee group
group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
group_employee_id = group_employee_ref and group_employee_ref[1] or False
# Test users
self.user_demo_id = self.registry('ir.model.data').get_object_reference(self.cr, self.uid, 'base', 'user_demo')[1]
self.user_admin = self.res_users.browse(self.cr, self.uid, self.uid)
self.user_raoul_id = self.res_users.create(cr, uid,
{'name': 'Raoul Grosbedon', 'email': 'raoul@raoul.fr', 'login': 'raoul', 'groups_id': [(6, 0, [group_employee_id])]})
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
self.user_admin = self.res_users.browse(cr, uid, uid)
# Mock send_get_mail_body to test its functionality without other addons override
self._send_get_mail_body = self.registry('mail.mail').send_get_mail_body
self.registry('mail.mail').send_get_mail_body = self._mock_send_get_mail_body
# groups@.. will cause the creation of new mail groups
self.mail_group_model_id = self.ir_model.search(self.cr, self.uid, [('model', '=', 'mail.group')])[0]
self.mail_alias.create(self.cr, self.uid, {'alias_name': '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})
# create a 'pigs' group that will be used through the various tests
self.group_pigs_id = self.mail_group.create(self.cr, self.uid,
self.group_pigs_id = self.mail_group.create(cr, uid,
{'name': 'Pigs', 'description': 'Fans of Pigs, unite !'})
self.group_pigs = self.mail_group.browse(self.cr, self.uid, self.group_pigs_id)
self.group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
def tearDown(self):
# Remove mocks
@ -159,7 +167,7 @@ class test_mail(TestMailMockups):
def test_00_message_process(self):
""" Testing incoming emails processing. """
cr, uid = self.cr, self.uid
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
# 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='')
@ -197,6 +205,26 @@ class test_mail(TestMailMockups):
self.assertEqual(new_mail.body, '\n<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>\n',
'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.assertFalse(new_mail.email_from, 'message process should not set the email_from when an author is found')
# 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_10_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,
@ -260,7 +288,7 @@ class test_mail(TestMailMockups):
""" Tests designed for the subscriber API as well as message subtypes """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
# Data: user Raoul
user_raoul_id = self.res_users.create(cr, uid, {'name': 'Raoul Grosbedon', 'login': 'raoul'})
user_raoul_id = self.user_raoul_id
user_raoul = self.res_users.browse(cr, uid, user_raoul_id)
# Data: message subtypes
self.mail_message_subtype.create(cr, uid, {'name': 'mt_mg_def', 'default': True, 'res_model': 'mail.group'})
@ -311,7 +339,25 @@ class test_mail(TestMailMockups):
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_post(self):
def test_20_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
msg1_id = self.mail_message.create(cr, uid, {'body': 'Thread header about Zap Brannigan', 'subject': 'My subject'})
msg2_id = self.mail_message.create(cr, uid, {'body': 'First answer, should not be displayed', 'subject': 'Re: My subject', 'parent_id': msg1_id})
msg3_id = self.mail_message.create(cr, uid, {'body': 'Second answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
msg4_id = self.mail_message.create(cr, uid, {'body': 'Third answer', 'subject': 'Re: My subject', 'parent_id': msg1_id})
msg_new_id = self.mail_message.create(cr, uid, {'body': 'My answer I am propagating', 'subject': 'Re: My subject', 'parent_id': msg1_id})
result = self.mail_message.message_quote_context(cr, uid, msg_new_id, limit=3)
self.assertIn('Thread header about Zap Brannigan', result, 'Thread header content should be in quote.')
self.assertIn('Second answer', result, 'Answer should be in quote.')
self.assertIn('Third answer', result, 'Answer should be in quote.')
self.assertIn('expandable', result, 'Expandable should be present.')
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):
""" Tests designed for message_post. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
self.res_users.write(cr, uid, [uid], {'signature': 'Admin', 'email': 'a@a'})
@ -357,11 +403,11 @@ class test_mail(TestMailMockups):
# the html2plaintext uses etree or beautiful soup, so the result may be slighly different
# depending if you have installed beautiful soup.
self.assertIn(sent_email['body_alternative'], _mail_bodyalt1 + '\nBert Tartopoils\n', 'sent_email body_alternative is incorrect')
# Test: mail_message: partner_ids = group followers
message_pids = set([partner.id for partner in message.partner_ids])
# Test: mail_message: notified_partner_ids = group followers
message_pids = set([partner.id for partner in message.notified_partner_ids])
test_pids = set([p_b_id, p_c_id])
self.assertEqual(test_pids, message_pids, 'mail.message partners incorrect')
# Test: notification linked to this message = group followers = partner_ids
# Test: notification linked to this message = group followers = notified_partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.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')
@ -390,11 +436,11 @@ class test_mail(TestMailMockups):
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_bodyalt2, sent_email['body_alternative'], 'sent_email body_alternative incorrect')
# Test: mail_message: partner_ids = group followers
message_pids = set([partner.id for partner in message.partner_ids])
# Test: mail_message: notified_partner_ids = group followers
message_pids = set([partner.id for partner in message.notified_partner_ids])
test_pids = set([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 = partner_ids
# Test: notifications linked to this message = group followers = notified_partner_ids
notif_ids = self.mail_notification.search(cr, uid, [('message_id', '=', message.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')
@ -408,6 +454,11 @@ class test_mail(TestMailMockups):
self.assertIn((attach.name, attach.datas.decode('base64')), _attachments,
'mail.message attachment name / data incorrect')
# 3. Reply to the last message, check that its parent will be the first message
msg_id3 = self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Test', parent_id=msg_id2)
message = self.mail_message.browse(cr, uid, msg_id3)
self.assertEqual(message.parent_id.id, msg_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
@ -458,12 +509,12 @@ class test_mail(TestMailMockups):
# Test: mail.message: subject, body inside pre
self.assertEqual(message.subject, False, 'mail.message incorrect subject')
self.assertEqual(message.body, _msg_body, 'mail.message incorrect body')
# Test: mail.message: partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
msg_pids = [partner.id for partner in message.partner_ids]
# Test: mail.message: notified_partner_ids = entries in mail.notification: group_pigs fans (a, b) + mail.compose.message partner_ids (c, d)
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 partner_ids incorrect')
self.assertEqual(set(msg_pids), set(test_pids), 'mail.message notified_partner_ids incorrect')
# ----------------------------------------
# CASE2: reply to last comment with attachments
@ -482,7 +533,7 @@ class test_mail(TestMailMockups):
self.assertEqual(compose.content_subtype, 'html', 'mail.compose.message incorrect content_subtype')
# Test: mail.message: subject as Re:.., body in html, parent_id
self.assertEqual(compose.subject, _msg_reply, 'mail.message incorrect subject')
self.assertIn('Administrator wrote:<blockquote><pre>Pigs rules</pre></blockquote>', compose.body, 'mail.message body is incorrect')
# 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
for attach in compose.attachment_ids:
@ -522,15 +573,158 @@ class test_mail(TestMailMockups):
def test_30_message_read(self):
""" Tests for message_read and expandables. """
self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
pigs_domain = [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)]
# Data: create a discussion in Pigs (2 messages, one with 2 and one with 3 answers)
msg_id0 = self.group_pigs.message_post(body='0', subtype='mt_comment')
msg_id1 = self.group_pigs.message_post(body='1', subtype='mt_comment')
msg_id2 = self.group_pigs.message_post(body='2', subtype='mt_comment')
msg_id3 = self.group_pigs.message_post(body='1-1', subtype='mt_comment', parent_id=msg_id1)
msg_id4 = self.group_pigs.message_post(body='2-1', subtype='mt_comment', parent_id=msg_id2)
msg_id5 = self.group_pigs.message_post(body='1-2', subtype='mt_comment', parent_id=msg_id1)
msg_id6 = self.group_pigs.message_post(body='2-2', subtype='mt_comment', parent_id=msg_id2)
msg_id7 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
msg_id8 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
msg_id9 = self.group_pigs.message_post(body='1-1-1', subtype='mt_comment', parent_id=msg_id3)
msg_id10 = self.group_pigs.message_post(body='2-1-1', subtype='mt_comment', parent_id=msg_id4)
msg_ids = [msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8, msg_id9, msg_id10]
# Test: read some specific ids
read_msg_list = self.mail_message.message_read(cr, uid, ids=msg_ids[2:4], domain=[('body', 'like', 'dummy')])
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(msg_ids[2:4], read_msg_ids, 'message_read with direct ids should read only the requested ids')
# Test: read messages of Pigs through a domain, being thread or not threaded
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(msg_ids, read_msg_ids, 'message_read flat with domain on Pigs should equal all messages of Pigs')
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=200, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list]
self.assertEqual(msg_ids, read_msg_ids, 'message_read threaded with domain on Pigs should equal all messages of Pigs')
# ----------------------------------------
# CASE1: message_read with domain, threaded
# We simulate an entire flow, using the expandables to test them
# ----------------------------------------
# Do: read last message, threaded
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 4, 'message_read on last Pigs message should return 2 messages and 2 expandables')
self.assertEqual(set([msg_id2, msg_id10]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
new_threads_exp = msg
elif msg.get('type') == 'expandable':
new_msg_exp = msg
# Do: fetch new messages in first thread, domain from expandable
self.assertIsNotNone(new_msg_exp, 'message_read on last Pigs message should have returned a new messages expandable')
domain = new_msg_exp.get('domain', [])
# Test: expandable, conditions in domain
self.assertIn(('id', 'child_of', msg_id2), domain, 'new messages expandable domain should contain a child_of condition')
self.assertIn(('id', '>=', msg_id4), domain, 'new messages expandable domain should contain an id greater than condition')
self.assertIn(('id', '<=', msg_id8), domain, 'new messages expandable domain should contain an id less than condition')
self.assertEqual(new_msg_exp.get('parent_id'), msg_id2, 'new messages expandable should have ancestor_id set to the thread header')
# Do: message_read with domain, thread_level=0, parent_id=msg_id2 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id2)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: other message in thread have been fetch
self.assertEqual(set([msg_id4, msg_id6, msg_id8]), set(read_msg_ids), 'message_read in Pigs thread should return all the previous messages')
# Do: fetch a new thread, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read on last Pigs message should have returned a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
self.assertFalse(new_threads_exp.get('parent_id'), 'new threads expandable should not have an ancestor_id')
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 4, 'message_read on Pigs should return 2 messages and 2 expandables')
self.assertEqual(set([msg_id1, msg_id9]), set(read_msg_ids), 'message_read on a Pigs message should also get its parent')
self.assertEqual(read_msg_list[1].get('parent_id'), read_msg_list[0].get('id'), 'message_read should set the ancestor to the thread header')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
new_threads_exp = msg
elif msg.get('type') == 'expandable':
new_msg_exp = msg
# Do: fetch new messages in second thread, domain from expandable
self.assertIsNotNone(new_msg_exp, 'message_read on Pigs message should have returned a new messages expandable')
domain = new_msg_exp.get('domain', [])
# Test: expandable, conditions in domain
self.assertIn(('id', 'child_of', msg_id1), domain, 'new messages expandable domain should contain a child_of condition')
self.assertIn(('id', '>=', msg_id3), domain, 'new messages expandable domain should contain an id greater than condition')
self.assertIn(('id', '<=', msg_id7), domain, 'new messages expandable domain should contain an id less than condition')
self.assertEqual(new_msg_exp.get('parent_id'), msg_id1, 'new messages expandable should have ancestor_id set to the thread header')
# Do: message_read with domain, thread_level=0, parent_id=msg_id1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=200, thread_level=0, parent_id=msg_id1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: other message in thread have been fetch
self.assertEqual(set([msg_id3, msg_id5, msg_id7]), set(read_msg_ids), 'message_read on the last Pigs message should also get its parent')
# Test: fetch a new thread, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read should have returned a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'general expandable domain should contain the message_read domain parameter')
# Do: message_read with domain, thread_level=1 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=1, thread_level=1)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 1, 'message_read on Pigs should return 1 message because everything else has been fetched')
self.assertEqual([msg_id0], read_msg_ids, 'message_read after 2 More should return only 1 last message')
# ----------------------------------------
# CASE2: message_read with domain, flat
# ----------------------------------------
# Do: read 2 lasts message, flat
read_msg_list = self.mail_message.message_read(cr, uid, domain=pigs_domain, limit=2, thread_level=0)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is not set, 1 expandable
self.assertEqual(len(read_msg_list), 3, 'message_read on last Pigs message should return 2 messages and 1 expandable')
self.assertEqual(set([msg_id9, msg_id10]), set(read_msg_ids), 'message_read flat on Pigs last messages should only return those messages')
self.assertFalse(read_msg_list[0].get('parent_id'), 'message_read flat should set the ancestor as False')
self.assertFalse(read_msg_list[1].get('parent_id'), 'message_read flat should set the ancestor as False')
# Data: get expandables
new_threads_exp, new_msg_exp = None, None
for msg in read_msg_list:
if msg.get('type') == 'expandable' and msg.get('nb_messages') == -1 and msg.get('id') == -1:
new_threads_exp = msg
# Do: fetch new messages, domain from expandable
self.assertIsNotNone(new_threads_exp, 'message_read flat on the 2 last Pigs messages should have returns a new threads expandable')
domain = new_threads_exp.get('domain', [])
# Test: expandable, conditions in domain
for condition in pigs_domain:
self.assertIn(condition, domain, 'new threads expandable domain should contain the message_read domain parameter')
# Do: message_read with domain, thread_level=0 (should be imposed by JS)
read_msg_list = self.mail_message.message_read(cr, uid, domain=domain, limit=20, thread_level=0)
read_msg_ids = [msg.get('id') for msg in read_msg_list if msg.get('type') != 'expandable']
# Test: structure content, ancestor is added to the read messages, ordered by id, ancestor is set, 2 expandables
self.assertEqual(len(read_msg_list), 9, 'message_read on Pigs should return 9 messages and 0 expandable')
self.assertEqual([msg_id0, msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8], read_msg_ids,
'message_read, More on flat, should return all remaning messages')
def test_40_needaction(self):
""" Tests for mail.message needaction. """
cr, uid, user_admin, group_pigs = self.cr, self.uid, self.user_admin, self.group_pigs
user_demo = self.res_users.browse(cr, uid, self.user_demo_id)
group_pigs_demo = self.mail_group.browse(cr, self.user_demo_id, self.group_pigs_id)
user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
group_pigs_demo = self.mail_group.browse(cr, self.user_raoul_id, self.group_pigs_id)
na_admin_base = self.mail_message._needaction_count(cr, uid, domain=[])
na_demo_base = self.mail_message._needaction_count(cr, user_demo.id, domain=[])
na_demo_base = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
# Test: number of unread notification = needaction on mail.message
notif_ids = self.mail_notification.search(cr, uid, [
@ -558,12 +752,12 @@ class test_mail(TestMailMockups):
self.assertEqual(na_admin_group, 3, 'Admin should have 3 needaction related to Pigs')
# Test: demo has 0 new notifications (not a follower, not receiving its own messages), and 0 new needaction
notif_ids = self.mail_notification.search(cr, uid, [
('partner_id', '=', user_demo.partner_id.id),
('partner_id', '=', user_raoul.partner_id.id),
('read', '=', False)
])
self.assertEqual(len(notif_ids), na_demo_base + 0, 'Demo should have 0 new unread notifications')
na_demo = self.mail_message._needaction_count(cr, user_demo.id, domain=[])
na_demo_group = self.mail_message._needaction_count(cr, user_demo.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
na_demo = self.mail_message._needaction_count(cr, user_raoul.id, domain=[])
na_demo_group = self.mail_message._needaction_count(cr, user_raoul.id, domain=[('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)])
self.assertEqual(na_demo, na_demo_base + 0, 'Demo should have 0 new needaction')
self.assertEqual(na_demo_group, 0, 'Demo should have 0 needaction related to Pigs')
@ -581,19 +775,11 @@ class test_mail(TestMailMockups):
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)
# TDE note: temp various asserts because of the random bug about msg1.child_ids
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], limit=1)
new_msg = self.mail_message.browse(cr, uid, msg_ids[0])
self.assertEqual(new_msg.parent_id, msg1, 'Newly processed mail_message (%d) should have msg1 as parent' % (new_msg.id))
# 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)
# TDE note: temp various asserts because of the random bug about msg1.child_ids
msg_ids = self.mail_message.search(cr, uid, [('model', '=', 'mail.group'), ('res_id', '=', self.group_pigs_id)], limit=1)
new_msg = self.mail_message.browse(cr, uid, msg_ids[0])
self.assertEqual(new_msg.parent_id, msg1, 'Newly processed mail_message should have msg1 as parent')
# 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',
@ -613,34 +799,46 @@ class test_mail(TestMailMockups):
def test_60_message_vote(self):
""" Test designed for the vote/unvote feature. """
cr, uid = self.cr, self.uid
user_admin = self.res_users.browse(cr, uid, uid)
group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id)
msg1 = group_pigs.message_post(body='My Body', subject='1')
msg1 = self.mail_message.browse(cr, uid, msg1)
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
# Data: post a message on Pigs
msg_id = group_pigs.message_post(body='My Body', subject='1')
msg = self.mail_message.browse(cr, uid, msg_id)
# Create user Bert Tartopoils
user_bert_id = self.res_users.create(cr, uid, {'name': 'Bert', 'login': 'bert'})
user_bert = self.res_users.browse(cr, uid, user_bert_id)
# Test: msg1 and msg2 have void vote_user_ids
self.assertFalse(msg1.vote_user_ids, 'newly created message msg1 has not void vote_user_ids')
# Do: Admin vote for msg1
self.mail_message.vote_toggle(cr, uid, [msg1.id])
msg1.refresh()
# Test: msg1 has Admin as voter
self.assertEqual(set(msg1.vote_user_ids), set([user_admin]), 'after voting, Admin is not the voter')
# Do: Bert vote for msg1
self.mail_message.vote_toggle(cr, user_bert_id, [msg1.id])
msg1.refresh()
# Test: msg1 has Admin and Bert as voters
self.assertEqual(set(msg1.vote_user_ids), set([user_admin, user_bert]), 'after voting, Admin and Bert are not the voters')
# Do: Admin unvote for msg1
self.mail_message.vote_toggle(cr, uid, [msg1.id])
msg1.refresh()
# Test: msg1 has Bert as voter
self.assertEqual(set(msg1.vote_user_ids), set([user_bert]), 'after unvoting for Admin, Bert is not the voter')
# Do: Admin vote for msg
self.mail_message.vote_toggle(cr, uid, [msg.id])
msg.refresh()
# Test: msg has Admin as voter
self.assertEqual(set(msg.vote_user_ids), set([user_admin]), 'mail_message vote: after voting, Admin should be in the voter')
# Do: Bert vote for msg
self.mail_message.vote_toggle(cr, user_raoul.id, [msg.id])
msg.refresh()
# Test: msg has Admin and Bert as voters
self.assertEqual(set(msg.vote_user_ids), set([user_admin, user_raoul]), 'mail_message vote: after voting, Admin and Bert should be in the voters')
# Do: Admin unvote for msg
self.mail_message.vote_toggle(cr, uid, [msg.id])
msg.refresh()
# Test: msg has Bert as voter
self.assertEqual(set(msg.vote_user_ids), set([user_raoul]), 'mail_message vote: after unvoting, Bert should be in the voter')
def test_70_message_favorite(self):
""" Tests for favorites. """
self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs
# Data: post a message on Pigs
msg_id = group_pigs.message_post(body='My Body', subject='1')
msg = self.mail_message.browse(cr, uid, msg_id)
# Do: Admin stars msg
self.mail_message.favorite_toggle(cr, uid, [msg.id])
msg.refresh()
# Test: msg starred by Admin
self.assertEqual(set(msg.favorite_user_ids), set([user_admin]), 'mail_message favorite: after starring, Admin should be in favorite_user_ids')
# Do: Bert stars msg
self.mail_message.favorite_toggle(cr, user_raoul.id, [msg.id])
msg.refresh()
# Test: msg starred by Admin and Raoul
self.assertEqual(set(msg.favorite_user_ids), set([user_admin, user_raoul]), 'mail_message favorite: after starring, Admin and Raoul should be in favorite_user_ids')
# Do: Admin unvote for msg
self.mail_message.favorite_toggle(cr, uid, [msg.id])
msg.refresh()
# Test: msg starred by Raoul
self.assertEqual(set(msg.favorite_user_ids), set([user_raoul]), 'mail_message favorite: after unstarring, Raoul should be in favorite_user_ids')

View File

@ -51,7 +51,40 @@ class test_mail_access_rights(test_mail.TestMailMockups):
self.user_raoul = self.res_users.browse(cr, uid, self.user_raoul_id)
self.partner_raoul_id = self.user_raoul.partner_id.id
def test_00_mail_message_read_access_rights(self):
def test_00_mail_message_search_access_rights(self):
""" Test mail_message search override about access rights. """
cr, uid, group_pigs_id = self.cr, self.uid, self.group_pigs_id
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
user_bert_id, user_raoul_id = self.user_bert_id, self.user_raoul_id
# Data: comment subtype for mail.message creation
ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'mail', 'mt_comment')
subtype_id = ref and ref[1] or False
# Data: Birds group, private
group_birds_id = self.mail_group.create(self.cr, self.uid, {'name': 'Birds', 'public': 'private'})
# Data: raoul is member of Pigs
self.mail_group.message_subscribe(cr, uid, [group_pigs_id], [partner_raoul_id])
# Data: various author_ids, partner_ids, documents
msg_id1 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A', 'subtype_id': subtype_id})
msg_id2 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B', 'partner_ids': [(6, 0, [partner_bert_id])], 'subtype_id': subtype_id})
msg_id3 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'subtype_id': subtype_id})
msg_id4 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+B Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [partner_bert_id])], 'subtype_id': subtype_id})
msg_id5 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A+R Pigs', 'model': 'mail.group', 'res_id': group_pigs_id, 'partner_ids': [(6, 0, [partner_raoul_id])], 'subtype_id': subtype_id})
msg_id6 = self.mail_message.create(cr, uid, {'subject': '_Test', 'body': 'A Birds', 'model': 'mail.group', 'res_id': group_birds_id, 'subtype_id': subtype_id})
msg_id7 = self.mail_message.create(cr, user_bert_id, {'subject': '_Test', 'body': 'B', 'subtype_id': subtype_id})
msg_id8 = self.mail_message.create(cr, user_bert_id, {'subject': '_Test', 'body': 'B+R', 'partner_ids': [(6, 0, [partner_raoul_id])], 'subtype_id': subtype_id})
# Test: Bert: 2 messages that have Bert in partner_ids + 2 messages as author
msg_ids = self.mail_message.search(cr, user_bert_id, [('subject', 'like', '_Test')])
self.assertEqual(set([msg_id2, msg_id4, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
# Test: Raoul: 3 messages on Pigs Raoul can read (employee can read group with default values), 0 on Birds (private group)
msg_ids = self.mail_message.search(cr, user_raoul_id, [('subject', 'like', '_Test'), ('body', 'like', 'A')])
self.assertEqual(set([msg_id3, msg_id4, msg_id5]), set(msg_ids), 'mail_message search failed')
# Test: Admin: all messages
msg_ids = self.mail_message.search(cr, uid, [('subject', 'like', '_Test')])
self.assertEqual(set([msg_id1, msg_id2, msg_id3, msg_id4, msg_id5, msg_id6, msg_id7, msg_id8]), set(msg_ids), 'mail_message search failed')
def test_05_mail_message_read_access_rights(self):
""" Test basic mail_message read access rights. """
cr, uid = self.cr, self.uid
partner_bert_id, partner_raoul_id = self.partner_bert_id, self.partner_raoul_id
@ -98,10 +131,6 @@ class test_mail_access_rights(test_mail.TestMailMockups):
self.assertRaises(except_orm, self.mail_message.read,
cr, user_bert_id, message_id)
def test_05_mail_message_search_access_rights(self):
""" Test mail_message search override about access rights. """
self.assertTrue(1 == 1, 'Test not implemented, do not replace by return True')
def test_10_mail_flow_access_rights(self):
""" Test a Chatter-looks alike flow. """
cr, uid = self.cr, self.uid

View File

@ -156,12 +156,6 @@ class mail_compose_message(osv.TransientModel):
reply_subject = tools.ustr(message_data.subject or '')
if not (reply_subject.startswith('Re:') or reply_subject.startswith(re_prefix)) and message_data.subject:
reply_subject = "%s %s" % (re_prefix, reply_subject)
# create the reply in the body
reply_body = _('<div>On %(date)s, %(sender_name)s wrote:<blockquote>%(body)s</blockquote></div>') % {
'date': message_data.date if message_data.date else '',
'sender_name': message_data.author_id.name,
'body': message_data.body,
}
# get partner_ids from original message
partner_ids = [partner.id for partner in message_data.partner_ids] if message_data.partner_ids else []
@ -170,7 +164,6 @@ class mail_compose_message(osv.TransientModel):
'model': message_data.model,
'res_id': message_data.res_id,
'parent_id': message_data.id,
'body': reply_body,
'subject': reply_subject,
'partner_ids': partner_ids,
'content_subtype': 'html',

View File

@ -14,22 +14,16 @@
<field name="parent_id" invisible="1"/>
<field name="content_subtype" invisible="1"/>
<!-- visible wizard -->
<field name="subject" placeholder="Subject..."
attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>/>
<field name="partner_ids" widget="many2many_tags" placeholder="Add contacts to notify..."
context="{'force_email':True}"
on_change="onchange_partner_ids(partner_ids)"/>
on_change="onchange_partner_ids(partner_ids)" required="1"/>
<field name="subject" placeholder="Subject..."
attrs="{'invisible':[('content_subtype', '=', 'plain')]}"/>
</group>
<notebook>
<page string="Body">
<field name="body" nolabel="1"/>
</page>
<page string="Attachments">
<field name="attachment_ids" colspan="4" nolabel="1"/>
</page>
</notebook>
<field name="body"/>
<field name="attachment_ids" widget="one2many_binary" blockui="0"/>
<footer>
<button string="Send" name="send_mail" type="object" class="oe_highlight" />
<button string="Send" name="send_mail" type="object" class="oe_highlight"/>
or
<button string="Cancel" class="oe_link" special="cancel" />
</footer>

View File

@ -7,14 +7,14 @@ msgstr ""
"Project-Id-Version: OpenERP Server 6.0dev\n"
"Report-Msgid-Bugs-To: support@openerp.com\n"
"POT-Creation-Date: 2012-02-08 00:49+0000\n"
"PO-Revision-Date: 2012-02-13 09:52+0000\n"
"Last-Translator: Jeff Wang <wjfonhand@hotmail.com>\n"
"PO-Revision-Date: 2012-10-25 17:08+0000\n"
"Last-Translator: ccdos <Unknown>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-10-19 05:06+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"X-Launchpad-Export-Date: 2012-10-26 04:56+0000\n"
"X-Generator: Launchpad (build 16194)\n"
#. module: mrp
#: view:mrp.routing.workcenter:0
@ -89,7 +89,7 @@ msgstr "单位成本"
#. module: mrp
#: view:mrp.production:0
msgid "Scrap Products"
msgstr "原料消耗"
msgstr "报废原料"
#. module: mrp
#: model:ir.actions.act_window,name:mrp.mrp_routing_action
@ -135,7 +135,7 @@ msgstr "生产订单编号"
#. module: mrp
#: view:mrp.production:0
msgid "Finished Products"
msgstr "已完工数量"
msgstr "产成品"
#. module: mrp
#: view:mrp.production:0
@ -209,7 +209,7 @@ msgstr "输入一个费用产品用于跟踪辅助核算会计中的成本"
#. module: mrp
#: model:process.node,note:mrp.process_node_purchaseprocure0
msgid "For purchased material"
msgstr "用于采购原料"
msgstr "用于外购的物料"
#. module: mrp
#: model:ir.actions.act_window,help:mrp.product_form_config_action
@ -1288,7 +1288,7 @@ msgstr "如果产品的供应方法是外购,系统创建采购订单。"
#. module: mrp
#: model:ir.model,name:mrp.model_procurement_order
msgid "Procurement"
msgstr "需求"
msgstr "需求"
#. module: mrp
#: model:ir.actions.act_window,name:mrp.action_view_mrp_product_price_wizard

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