[MERGE] Sync with trunk

bzr revid: tde@openerp.com-20130808083451-plvcks1kwjhh2vzi
This commit is contained in:
Thibault Delavallée 2013-08-08 10:34:51 +02:00
commit 079ea21883
130 changed files with 7403 additions and 2312 deletions

View File

@ -612,6 +612,13 @@
</field>
</page>
</notebook>
<group class="oe_subtotal_footer oe_right" colspan="2" name="sale_total">
<div class="oe_subtotal_footer_separator oe_inline">
<label for="balance_end" />
</div>
<field name="balance_end" nolabel="1" class="oe_subtotal_footer_separator" widget='monetary' options="{'currency_field': 'currency_id'}"/>
</group>
<div class="oe_clear"/>
</sheet>
</form>
</field>

View File

@ -8,19 +8,19 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2012-04-13 22:35+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"PO-Revision-Date: 2013-07-30 22:25+0000\n"
"Last-Translator: Masaki Yamaya <Unknown>\n"
"Language-Team: Japanese <ja@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-03-16 05:47+0000\n"
"X-Generator: Launchpad (build 16532)\n"
"X-Launchpad-Export-Date: 2013-07-31 05:16+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: account_accountant
#: model:ir.actions.client,name:account_accountant.action_client_account_menu
msgid "Open Accounting Menu"
msgstr ""
msgstr "会計メニューを開く"
#~ msgid ""
#~ "\n"

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-12-21 17:04+0000\n"
"PO-Revision-Date: 2013-05-15 10:09+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"PO-Revision-Date: 2013-07-24 08:53+0000\n"
"Last-Translator: Sumonchai ( เหลา ) <sumonchai@gmail.com>\n"
"Language-Team: Thai <th@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-05-16 05:12+0000\n"
"X-Generator: Launchpad (build 16626)\n"
"X-Launchpad-Export-Date: 2013-07-25 05:13+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: account_asset
#: view:account.asset.asset:0
@ -42,7 +42,7 @@ msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Group By..."
msgstr ""
msgstr "จัดกลุ่มตาม..."
#. module: account_asset
#: field:asset.asset.report,gross_value:0
@ -58,7 +58,7 @@ msgstr ""
#: field:asset.asset.report,asset_id:0
#: model:ir.model,name:account_asset.model_account_asset_asset
msgid "Asset"
msgstr ""
msgstr "สินทรัพย์"
#. module: account_asset
#: help:account.asset.asset,prorata:0
@ -72,7 +72,7 @@ msgstr ""
#: selection:account.asset.asset,method:0
#: selection:account.asset.category,method:0
msgid "Linear"
msgstr ""
msgstr "เชิงเส้น"
#. module: account_asset
#: field:account.asset.asset,company_id:0
@ -80,24 +80,24 @@ msgstr ""
#: view:asset.asset.report:0
#: field:asset.asset.report,company_id:0
msgid "Company"
msgstr ""
msgstr "บริษัท"
#. module: account_asset
#: view:asset.modify:0
msgid "Modify"
msgstr ""
msgstr "ปรับเปลี่ยน"
#. module: account_asset
#: selection:account.asset.asset,state:0
#: view:asset.asset.report:0
#: selection:asset.asset.report,state:0
msgid "Running"
msgstr ""
msgstr "กำลังทำงานอยู่"
#. module: account_asset
#: view:account.asset.asset:0
msgid "Set to Draft"
msgstr ""
msgstr "กำหนดให้เป็นแบบร่าง"
#. module: account_asset
#: view:asset.asset.report:0
@ -110,7 +110,7 @@ msgstr ""
#. module: account_asset
#: field:asset.modify,name:0
msgid "Reason"
msgstr ""
msgstr "เหตุผล"
#. module: account_asset
#: field:account.asset.asset,method_progress_factor:0
@ -122,7 +122,7 @@ msgstr ""
#: model:ir.actions.act_window,name:account_asset.action_account_asset_asset_list_normal
#: model:ir.ui.menu,name:account_asset.menu_action_account_asset_asset_list_normal
msgid "Asset Categories"
msgstr ""
msgstr "หมวดหมู่สินทรัพย์"
#. module: account_asset
#: view:account.asset.asset:0
@ -173,7 +173,7 @@ msgstr ""
#: model:ir.ui.menu,name:account_asset.menu_finance_assets
#: model:ir.ui.menu,name:account_asset.menu_finance_config_assets
msgid "Assets"
msgstr ""
msgstr "สินทรัพย์"
#. module: account_asset
#: field:account.asset.category,account_depreciation_id:0
@ -187,7 +187,7 @@ msgstr ""
#: view:asset.modify:0
#: field:asset.modify,note:0
msgid "Notes"
msgstr ""
msgstr "บันทึกย่อ"
#. module: account_asset
#: field:account.asset.depreciation.line,move_id:0
@ -221,7 +221,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.asset,code:0
msgid "Reference"
msgstr ""
msgstr "อ้างถึง"
#. module: account_asset
#: view:account.asset.asset:0
@ -246,7 +246,7 @@ msgstr ""
#: view:asset.asset.report:0
#: selection:asset.asset.report,state:0
msgid "Draft"
msgstr ""
msgstr "ฉบับร่าง"
#. module: account_asset
#: view:asset.asset.report:0
@ -273,13 +273,13 @@ msgstr ""
#. module: account_asset
#: field:account.asset.category,account_analytic_id:0
msgid "Analytic account"
msgstr ""
msgstr "วิเคราะห์บัญชี"
#. module: account_asset
#: field:account.asset.asset,method:0
#: field:account.asset.category,method:0
msgid "Computation Method"
msgstr ""
msgstr "วิธีการคำนวณ"
#. module: account_asset
#: constraint:account.asset.asset:0
@ -308,7 +308,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.asset,salvage_value:0
msgid "Salvage Value"
msgstr ""
msgstr "มูลค่าซาก"
#. module: account_asset
#: field:account.asset.asset,category_id:0
@ -364,20 +364,20 @@ msgstr ""
#: field:account.asset.category,method_time:0
#: field:account.asset.history,method_time:0
msgid "Time Method"
msgstr ""
msgstr "วิธีการจัดการเวลา"
#. module: account_asset
#: view:asset.depreciation.confirmation.wizard:0
#: view:asset.modify:0
msgid "or"
msgstr ""
msgstr "หรือ"
#. module: account_asset
#: field:account.asset.asset,note:0
#: field:account.asset.category,note:0
#: field:account.asset.history,note:0
msgid "Note"
msgstr ""
msgstr "บันทึกย่อ"
#. module: account_asset
#: help:account.asset.history,method_time:0
@ -409,7 +409,7 @@ msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Closed"
msgstr ""
msgstr "ปิด"
#. module: account_asset
#: help:account.asset.asset,state:0
@ -425,13 +425,13 @@ msgstr ""
#: field:account.asset.asset,state:0
#: field:asset.asset.report,state:0
msgid "Status"
msgstr ""
msgstr "สถานะ"
#. module: account_asset
#: field:account.asset.asset,partner_id:0
#: field:asset.asset.report,partner_id:0
msgid "Partner"
msgstr ""
msgstr "พาร์ทเนอร์"
#. module: account_asset
#: view:asset.asset.report:0
@ -451,7 +451,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.history,user_id:0
msgid "User"
msgstr ""
msgstr "ผู้ใช้"
#. module: account_asset
#: field:account.asset.category,account_asset_id:0
@ -482,12 +482,12 @@ msgstr ""
#. module: account_asset
#: field:account.asset.asset,active:0
msgid "Active"
msgstr ""
msgstr "เปิดใช้งาน"
#. module: account_asset
#: field:account.asset.depreciation.line,parent_state:0
msgid "State of Asset"
msgstr ""
msgstr "สถานะของสินทรัพย์"
#. module: account_asset
#: field:account.asset.depreciation.line,name:0
@ -498,12 +498,12 @@ msgstr ""
#: view:account.asset.asset:0
#: field:account.asset.asset,history_ids:0
msgid "History"
msgstr ""
msgstr "ประวัติ"
#. module: account_asset
#: view:asset.depreciation.confirmation.wizard:0
msgid "Compute Asset"
msgstr ""
msgstr "คำนวณสินทรัพย์"
#. module: account_asset
#: field:asset.depreciation.confirmation.wizard,period_id:0
@ -513,7 +513,7 @@ msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "General"
msgstr ""
msgstr "ทั่วไป"
#. module: account_asset
#: field:account.asset.asset,prorata:0
@ -524,7 +524,7 @@ msgstr ""
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_invoice
msgid "Invoice"
msgstr ""
msgstr "ใบแจ้งหนี้"
#. module: account_asset
#: view:account.asset.asset:0
@ -535,13 +535,13 @@ msgstr ""
#: view:asset.depreciation.confirmation.wizard:0
#: view:asset.modify:0
msgid "Cancel"
msgstr ""
msgstr "ยกเลิก"
#. module: account_asset
#: selection:account.asset.asset,state:0
#: selection:asset.asset.report,state:0
msgid "Close"
msgstr ""
msgstr "ปิด"
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_move_line
@ -558,7 +558,7 @@ msgstr ""
#: view:asset.asset.report:0
#: field:asset.asset.report,purchase_date:0
msgid "Purchase Date"
msgstr ""
msgstr "วันที่ซื้อ"
#. module: account_asset
#: selection:account.asset.asset,method:0
@ -606,7 +606,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.asset,currency_id:0
msgid "Currency"
msgstr ""
msgstr "สกุลเงิน"
#. module: account_asset
#: field:account.asset.category,journal_id:0
@ -637,7 +637,7 @@ msgstr ""
#: view:asset.asset.report:0
#: field:asset.asset.report,move_check:0
msgid "Posted"
msgstr ""
msgstr "ลงบัญชีแล้ว"
#. module: account_asset
#: model:ir.actions.act_window,help:account_asset.action_asset_asset_report
@ -660,7 +660,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.category,name:0
msgid "Name"
msgstr ""
msgstr "ชื่อ"
#. module: account_asset
#: help:account.asset.category,open_asset:0
@ -672,7 +672,7 @@ msgstr ""
#. module: account_asset
#: field:asset.asset.report,name:0
msgid "Year"
msgstr ""
msgstr "ปี"
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_asset_depreciation_line
@ -693,7 +693,7 @@ msgid "Amount of Depreciation Lines"
msgstr ""
#. module: account_asset
#: code:addons/account_asset/wizard/wizard_asset_compute.py:49
#: code:addons/account_asset/wizard/wizard_asset_compute.py:50
#, python-format
msgid "Created Asset Moves"
msgstr ""
@ -701,7 +701,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,sequence:0
msgid "Sequence"
msgstr ""
msgstr "ลำดับ"
#. module: account_asset
#: help:account.asset.category,method_period:0
@ -711,7 +711,7 @@ msgstr ""
#. module: account_asset
#: field:account.asset.history,date:0
msgid "Date"
msgstr ""
msgstr "วันที่"
#. module: account_asset
#: field:account.asset.asset,method_number:0

View File

@ -0,0 +1,361 @@
# Russian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-08-05 10:40+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\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: 2013-08-06 04:47+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: account_bank_statement_extensions
#: help:account.bank.statement.line.global,name:0
msgid "Originator to Beneficiary Information"
msgstr "Информация от плательщика получателю"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
#: selection:account.bank.statement.line,state:0
msgid "Confirmed"
msgstr "Подтверждено"
#. module: account_bank_statement_extensions
#: view:account.bank.statement:0
#: view:account.bank.statement.line:0
msgid "Glob. Id"
msgstr ""
#. module: account_bank_statement_extensions
#: selection:account.bank.statement.line.global,type:0
msgid "CODA"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,parent_id:0
msgid "Parent Code"
msgstr "Основной код"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Debit"
msgstr "Дебет"
#. module: account_bank_statement_extensions
#: view:cancel.statement.line:0
#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_cancel_statement_line
#: model:ir.model,name:account_bank_statement_extensions.model_cancel_statement_line
msgid "Cancel selected statement lines"
msgstr "Отмена выбранных позиций выписки"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,val_date:0
msgid "Value Date"
msgstr "Дата зачисления"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Group By..."
msgstr "Группировать по ..."
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
#: selection:account.bank.statement.line,state:0
msgid "Draft"
msgstr "Черновик"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Statement"
msgstr "Выписка"
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_confirm_statement_line
#: model:ir.model,name:account_bank_statement_extensions.model_confirm_statement_line
msgid "Confirm selected statement lines"
msgstr "Подтверждение выбранных позиций выписки"
#. module: account_bank_statement_extensions
#: report:bank.statement.balance.report:0
#: model:ir.actions.report.xml,name:account_bank_statement_extensions.bank_statement_balance_report
msgid "Bank Statement Balances Report"
msgstr "Отчет об остатках банковской выписки"
#. module: account_bank_statement_extensions
#: view:cancel.statement.line:0
msgid "Cancel Lines"
msgstr "Отменить позиции"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line.global:0
#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement_line_global
msgid "Batch Payment Info"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,state:0
msgid "Status"
msgstr "Статус"
#. module: account_bank_statement_extensions
#: code:addons/account_bank_statement_extensions/account_bank_statement.py:129
#, python-format
msgid ""
"Delete operation not allowed. Please go to the associated bank "
"statement in order to delete and/or modify bank statement line."
msgstr ""
"Операция удаления запрещена. Пожалуйста, обратитесь к соответствующей "
"банковской выписке для удаления/изменения позиции."
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "or"
msgstr "или"
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "Confirm Lines"
msgstr "Подтвердить позиции"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line.global:0
msgid "Transactions"
msgstr "Операции"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,type:0
msgid "Type"
msgstr "Тип"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
#: report:bank.statement.balance.report:0
msgid "Journal"
msgstr "Журнал"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Confirmed Statement Lines."
msgstr "Подтвержденные позиции выписки."
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Credit Transactions."
msgstr "Операции по кредиту"
#. module: account_bank_statement_extensions
#: model:ir.actions.act_window,help:account_bank_statement_extensions.action_cancel_statement_line
msgid "cancel selected statement lines."
msgstr "отменить выбранные позиции выписки"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,counterparty_number:0
msgid "Counterparty Number"
msgstr "Число контрагентов"
#. module: account_bank_statement_extensions
#: report:bank.statement.balance.report:0
msgid "Closing Balance"
msgstr "Итоговый баланс"
#. module: account_bank_statement_extensions
#: report:bank.statement.balance.report:0
msgid "Date"
msgstr "Дата"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
#: field:account.bank.statement.line,globalisation_amount:0
msgid "Glob. Amount"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Debit Transactions."
msgstr "Операции по дебету"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Extended Filters..."
msgstr "Расширенные фильтры..."
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "Confirmed lines cannot be changed anymore."
msgstr "Нельзя изменить подтвержденные позиции."
#. module: account_bank_statement_extensions
#: view:cancel.statement.line:0
msgid "Are you sure you want to cancel the selected Bank Statement lines ?"
msgstr ""
"Вы уверены, что хотите отменить выбранные позиции банковской выписки ?"
#. module: account_bank_statement_extensions
#: report:bank.statement.balance.report:0
msgid "Name"
msgstr "Название"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,name:0
msgid "OBI"
msgstr "Назначение"
#. module: account_bank_statement_extensions
#: selection:account.bank.statement.line.global,type:0
msgid "ISO 20022"
msgstr "ISO 20022"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Notes"
msgstr "Примечания"
#. module: account_bank_statement_extensions
#: selection:account.bank.statement.line.global,type:0
msgid "Manual"
msgstr "Ручной"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Bank Transaction"
msgstr "Банковские операции"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Credit"
msgstr "Кредит"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,amount:0
msgid "Amount"
msgstr "Сумма"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Fin.Account"
msgstr "Фин. Счет"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,counterparty_currency:0
msgid "Counterparty Currency"
msgstr "Валюта контрагента"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,counterparty_bic:0
msgid "Counterparty BIC"
msgstr "БИК контрагента"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,child_ids:0
msgid "Child Codes"
msgstr "Субкоды"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Search Bank Transactions"
msgstr "Поиск банковских операций"
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "Are you sure you want to confirm the selected Bank Statement lines ?"
msgstr ""
"Вы уверены, что хотите, подтвердить выбранные позиции банковской выписки ?"
#. module: account_bank_statement_extensions
#: help:account.bank.statement.line,globalisation_id:0
msgid ""
"Code to identify transactions belonging to the same globalisation level "
"within a batch payment"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Draft Statement Lines."
msgstr "Черновики позиций выписки."
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Glob. Am."
msgstr ""
#. module: account_bank_statement_extensions
#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement_line
msgid "Bank Statement Line"
msgstr "Позиция банковской выписки"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,code:0
msgid "Code"
msgstr "Код"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,counterparty_name:0
msgid "Counterparty Name"
msgstr "Название контрагента"
#. module: account_bank_statement_extensions
#: model:ir.model,name:account_bank_statement_extensions.model_res_partner_bank
msgid "Bank Accounts"
msgstr "Банковские счета"
#. module: account_bank_statement_extensions
#: model:ir.model,name:account_bank_statement_extensions.model_account_bank_statement
msgid "Bank Statement"
msgstr "Банковская выписка"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Statement Line"
msgstr "Позиция выписки"
#. module: account_bank_statement_extensions
#: sql_constraint:account.bank.statement.line.global:0
msgid "The code must be unique !"
msgstr "Код должен быть уникальным !"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line.global,bank_statement_line_ids:0
#: model:ir.actions.act_window,name:account_bank_statement_extensions.action_bank_statement_line
#: model:ir.ui.menu,name:account_bank_statement_extensions.bank_statement_line
msgid "Bank Statement Lines"
msgstr "Позиции банковской выписки"
#. module: account_bank_statement_extensions
#: code:addons/account_bank_statement_extensions/account_bank_statement.py:129
#, python-format
msgid "Warning!"
msgstr "Внимание!"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line.global:0
msgid "Child Batch Payments"
msgstr ""
#. module: account_bank_statement_extensions
#: view:confirm.statement.line:0
msgid "Cancel"
msgstr "Отмена"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Statement Lines"
msgstr "Позиции выписки"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Total Amount"
msgstr "Итоговая сумма"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,globalisation_id:0
msgid "Globalisation ID"
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-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-06-13 17:16+0000\n"
"Last-Translator: Akira Hiyama <Unknown>\n"
"PO-Revision-Date: 2013-07-30 22:29+0000\n"
"Last-Translator: Masaki Yamaya <Unknown>\n"
"Language-Team: Japanese <ja@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-03-16 05:34+0000\n"
"X-Generator: Launchpad (build 16532)\n"
"X-Launchpad-Export-Date: 2013-07-31 05:16+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: account_budget
#: view:account.budget.analytic:0
@ -418,7 +418,7 @@ msgstr "からの分析"
#. module: account_budget
#: view:crossovered.budget:0
msgid "Draft Budgets"
msgstr "ドラフト予算"
msgstr "予算"
#, python-format
#~ msgid "The General Budget '%s' has no Accounts!"

View File

@ -0,0 +1,23 @@
# Ukrainian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-08-01 11:11+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Ukrainian <uk@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-08-02 05:38+0000\n"
"X-Generator: Launchpad (build 16718)\n"
#. module: account_cancel
#: view:account.invoice:0
msgid "Cancel"
msgstr ""

View File

@ -14,13 +14,7 @@
<field name="condition">True</field>
<field name="type">ir.actions.server</field>
<field name="state">email</field>
<field name="email">object.user_id.email</field>
<field name="subject">Reminder on Lead: [[object.id ]] [[object.partner_id and 'of ' +object.partner_id.name or '']]</field>
<field name="message">Warning unprocessed incoming lead is more than 5 day old.
Name: [[object.name ]]
ID: [[object.id ]]
Description: [[object.description]]
</field>
<field name="template_id" ref="email_template_opportunity_reminder_mail"/>
</record>
<record id="rule_set_reminder_lead" model="base.action.rule">
<field name="name">Set Auto Reminder on leads which are not open since 5 days.</field>

View File

@ -621,7 +621,7 @@ class crm_lead(format_address, osv.osv):
attachment.write(values)
return True
def merge_opportunity(self, cr, uid, ids, context=None):
def merge_opportunity(self, cr, uid, ids, user_id=False, section_id=False, context=None):
"""
Different cases of merge:
- merge leads together = 1 new lead
@ -655,6 +655,11 @@ class crm_lead(format_address, osv.osv):
fields = list(CRM_LEAD_FIELDS_TO_MERGE)
merged_data = self._merge_data(cr, uid, ids, highest, fields, context=context)
if user_id:
merged_data['user_id'] = user_id
if section_id:
merged_data['section_id'] = section_id
# Merge messages and attachements into the first opportunity
self._merge_opportunity_history(cr, uid, highest.id, tail_opportunities, context=context)
self._merge_opportunity_attachments(cr, uid, highest.id, tail_opportunities, context=context)
@ -940,42 +945,6 @@ class crm_lead(format_address, osv.osv):
vals.update(onchange_stage_values)
return super(crm_lead, self).write(cr, uid, ids, vals, context=context)
def new_mail_send(self, cr, uid, ids, context=None):
'''
This function opens a window to compose an email, with the edi sale template message loaded by default
'''
assert len(ids) == 1, 'This option should only be used for a single id at a time.'
ir_model_data = self.pool.get('ir.model.data')
try:
template_id = ir_model_data.get_object_reference(cr, uid, 'crm', 'email_template_opportunity_mail')[1]
except ValueError:
template_id = False
try:
compose_form_id = ir_model_data.get_object_reference(cr, uid, 'mail', 'email_compose_message_wizard_form')[1]
except ValueError:
compose_form_id = False
if context is None:
context = {}
ctx = context.copy()
ctx.update({
'default_model': 'crm.lead',
'default_res_id': ids[0],
'default_use_template': bool(template_id),
'default_template_id': template_id,
'default_composition_mode': 'comment',
})
return {
'name': _('Compose Email'),
'type': 'ir.actions.act_window',
'view_type': 'form',
'view_mode': 'form',
'res_model': 'mail.compose.message',
'views': [(compose_form_id, 'form')],
'view_id': compose_form_id,
'target': 'new',
'context': ctx,
}
# ----------------------------------------
# Mail Gateway
# ----------------------------------------

View File

@ -220,6 +220,20 @@
<field name="email_to">${not object.partner_id and object.email_from}</field>
<field name="body_html"></field>
</record>
<record id="email_template_opportunity_reminder_mail" model="email.template">
<field name="name">Reminder to User</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="auto_delete" eval="True"/>
<field name="email_from">admin@example.com</field>
<field name="email_to">${object.user_id != False and object.user_id.email}</field>
<field name="subject">Reminder on Lead: ${object.id} from ${object.partner_id != False and object.partner_id.name or object.contact_name}</field>
<field name="body_html"><![CDATA[<p>This opportunity did not have any activity since at least 5 days. Here are some details:</p>
<ul>
<li>Name: ${object.name}</li>
<li>ID: ${object.id}</li>
<li>Description: ${object.description}</field></li>
</ul>]]></field>
</record>
</data>
</openerp>

View File

@ -408,7 +408,7 @@
<group>
<group>
<field name="partner_id"
on_change="onchange_partner_id(partner_id, email_from)"
on_change="on_change_partner_id(partner_id)"
string="Customer"
context="{'default_name': partner_name, 'default_email': email_from, 'default_phone': phone}"/>
<field name="email_from" string="Email"/>

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-12-21 17:04+0000\n"
"PO-Revision-Date: 2013-07-09 07:36+0000\n"
"PO-Revision-Date: 2013-07-25 13:52+0000\n"
"Last-Translator: krnkris <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: 2013-07-10 05:25+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-07-26 05:14+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: crm
#: model:crm.case.stage,name:crm.stage_lead3
@ -241,7 +241,7 @@ msgstr "Kilép"
#: view:crm.lead:0
#: field:crm.lead,state_id:0
msgid "State"
msgstr "Állapot"
msgstr "Állam/Megye"
#. module: crm
#: field:res.partner,meeting_count:0

File diff suppressed because it is too large Load Diff

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-12-21 17:04+0000\n"
"PO-Revision-Date: 2012-12-19 07:22+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@gmail.com>\n"
"PO-Revision-Date: 2013-08-06 09:41+0000\n"
"Last-Translator: fanvil <fanvil@hotmail.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: 2013-03-16 05:10+0000\n"
"X-Generator: Launchpad (build 16532)\n"
"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
"X-Generator: Launchpad (build 16721)\n"
#. module: crm
#: view:crm.lead.report:0
@ -29,7 +29,7 @@ msgid ""
msgstr "允许你设置接收邮件服务器,并从收到的邮件中创建线索"
#. module: crm
#: code:addons/crm/crm_lead.py:881
#: code:addons/crm/crm_lead.py:898
#: selection:crm.case.stage,type:0
#: view:crm.lead:0
#: selection:crm.lead,type:0
@ -54,6 +54,11 @@ msgid ""
"Description: [[object.description]]\n"
" "
msgstr ""
"警告收到的新线索已超过5天未处理\n"
"名称:[[object.name]]\n"
"编号:[[object.id]]\n"
"描述:[[object.description]]\n"
" "
#. module: crm
#: field:crm.opportunity2phonecall,action:0
@ -64,7 +69,7 @@ msgstr "动作"
#. module: crm
#: model:ir.actions.server,name:crm.action_set_team_sales_department
msgid "Set team to Sales Department"
msgstr ""
msgstr "设置团队 到销售部"
#. module: crm
#: view:crm.lead2opportunity.partner.mass:0
@ -151,7 +156,7 @@ msgstr "规则名称"
#: code:addons/crm/crm_phonecall.py:280
#, python-format
msgid "It's only possible to convert one phonecall at a time."
msgstr ""
msgstr "每次只能转换一个电话呼叫"
#. module: crm
#: view:crm.case.resource.type:0
@ -180,11 +185,11 @@ msgstr "预计结束月份"
msgid ""
"Holds the Chatter summary (number of messages, ...). This summary is "
"directly in html format in order to be inserted in kanban views."
msgstr ""
msgstr "保留复杂的摘要(消息数量,……等)。这一摘要直接是是HTML格式以便插入到看板视图。"
#. module: crm
#: code:addons/crm/crm_lead.py:624
#: code:addons/crm/crm_lead.py:744
#: code:addons/crm/crm_lead.py:640
#: code:addons/crm/crm_lead.py:761
#: code:addons/crm/crm_phonecall.py:280
#, python-format
msgid "Warning!"
@ -232,7 +237,7 @@ msgstr "状态"
#. module: crm
#: field:res.partner,meeting_count:0
msgid "# Meetings"
msgstr ""
msgstr "#会议"
#. module: crm
#: model:ir.actions.server,name:crm.action_email_reminder_lead
@ -252,7 +257,7 @@ msgstr "排除的答案:"
#. module: crm
#: model:ir.model,name:crm.model_crm_merge_opportunity
msgid "Merge opportunities"
msgstr ""
msgstr "合并商机"
#. module: crm
#: view:crm.lead.report:0
@ -265,7 +270,7 @@ msgstr "线索分析"
#: code:addons/crm/crm_lead.py:1010
#, python-format
msgid "<b>%s a call</b> for the <em>%s</em>."
msgstr ""
msgstr "<b>%s 一个电话</b> 为了 <em>%s</em>."
#. module: crm
#: model:ir.actions.act_window,name:crm.crm_case_resource_type_act
@ -293,7 +298,7 @@ msgid "Prospect Partner"
msgstr "潜在业务伙伴"
#. module: crm
#: code:addons/crm/crm_lead.py:982
#: code:addons/crm/crm_lead.py:1002
#, python-format
msgid "No Subject"
msgstr "无主题"
@ -323,6 +328,18 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" "
"点击创建新客户分组\n"
" </p><p>\n"
" "
"创建指定的分类用于赋值给你的联系人\n"
" "
"以便更好管理你和他们的联系。分组工具\n"
" "
"可以根据你预设的条件给联系人分配组。\n"
" </p>\n"
" "
#. module: crm
#: field:crm.opportunity2phonecall,contact_name:0
@ -445,16 +462,16 @@ msgid "#Opportunities"
msgstr "#商机"
#. module: crm
#: code:addons/crm/crm_lead.py:624
#: code:addons/crm/crm_lead.py:640
#, python-format
msgid ""
"Please select more than one element (lead or opportunity) from the list view."
msgstr ""
msgstr "请从列表视图中选择一个以上元素(线索或机会)。"
#. module: crm
#: view:crm.lead:0
msgid "Leads that are assigned to one of the sale teams I manage, or to me"
msgstr ""
msgstr "分配给我锁管理的某个销售队伍,或给我的线索。"
#. module: crm
#: field:crm.lead,partner_address_email:0
@ -473,6 +490,16 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" "
"点击创建新的销售团队\n"
" <p></p>\n"
" "
"使用销售团队,把不同销售员或部门组织成独立的团队。\n"
" "
"每个团队会使用自己的商机列表工作。\n"
" </p>\n"
" "
#. module: crm
#: model:process.transition,note:crm.process_transition_opportunitymeeting0
@ -511,7 +538,7 @@ msgstr "邮件"
#. module: crm
#: model:mail.message.subtype,description:crm.mt_lead_stage
msgid "Stage changed"
msgstr ""
msgstr "任务阶段已改变"
#. module: crm
#: selection:crm.lead.report,creation_month:0
@ -556,6 +583,7 @@ msgid ""
" If the call needs to be done then the status is set "
"to 'Not Held'."
msgstr ""
"当一个案子新建时,状态被设为‘待处理’,当案子进行中,状态被设为‘打开’,当电话结束,状态被设为‘挂起’。如果需要电话,状态设为‘未挂起’"
#. module: crm
#: field:crm.case.section,message_summary:0
@ -572,7 +600,7 @@ msgstr "合并"
#. module: crm
#: model:email.template,subject:crm.email_template_opportunity_mail
msgid "Opportunity ${object.name | h})"
msgstr ""
msgstr "商机${object.name | h})"
#. module: crm
#: view:crm.case.categ:0
@ -590,6 +618,8 @@ msgid ""
"Reminder on Lead: [[object.id ]] [[object.partner_id and 'of ' "
"+object.partner_id.name or '']]"
msgstr ""
"线索:[[object.id ]] [[object.partner_id and 'of ' +object.partner_id.name or "
"'']]的提醒"
#. module: crm
#: view:crm.segmentation:0
@ -630,6 +660,13 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 点击创建新类型\n"
" </p><p>\n"
" "
"创建指定的电话类型,更好地定义系统中记录的电话类型\n"
" </p>\n"
" "
#. module: crm
#: help:crm.case.section,reply_to:0
@ -712,7 +749,7 @@ msgid "Statistics Dashboard"
msgstr "统计控制台"
#. module: crm
#: code:addons/crm/crm_lead.py:861
#: code:addons/crm/crm_lead.py:878
#: model:crm.case.stage,name:crm.stage_lead2
#: selection:crm.case.stage,type:0
#: view:crm.lead:0
@ -738,7 +775,7 @@ msgstr "转为商机"
#. module: crm
#: model:ir.model,name:crm.model_sale_config_settings
msgid "sale.config.settings"
msgstr ""
msgstr "sale.config.settings"
#. module: crm
#: view:crm.segmentation:0
@ -759,7 +796,7 @@ msgstr "查询电话访问"
#: view:crm.lead.report:0
msgid ""
"Leads/Opportunities that are assigned to one of the sale teams I manage"
msgstr ""
msgstr "分配给我所管理的某个销售团队的线索/商机。"
#. module: crm
#: field:calendar.attendee,categ_id:0
@ -772,7 +809,7 @@ msgid "Exclusive"
msgstr "唯一的"
#. module: crm
#: code:addons/crm/crm_lead.py:584
#: code:addons/crm/crm_lead.py:600
#, python-format
msgid "From %s : %s"
msgstr "发自: %s:%s"
@ -807,7 +844,7 @@ msgstr "参考2"
msgid ""
"Link between stages and sales teams. When set, this limitate the current "
"stage to the selected sales teams."
msgstr ""
msgstr "在阶段和销售团队之间建立链接。设置后将限制此阶段只能用于被选中的销售团队。"
#. module: crm
#: view:crm.case.stage:0
@ -866,7 +903,7 @@ msgstr "标记为已赢得"
#. module: crm
#: model:ir.filters,name:crm.filter_usa_lead
msgid "Leads from USA"
msgstr ""
msgstr "来自USA的线索"
#. module: crm
#: view:crm.lead:0
@ -917,7 +954,7 @@ msgstr "参考"
msgid ""
"Opportunities that are assigned to either me or one of the sale teams I "
"manage"
msgstr ""
msgstr "分配给我或者我管理的某个销售团队的商机。"
#. module: crm
#: help:crm.case.section,resource_calendar_id:0
@ -940,10 +977,10 @@ msgid "Next Action"
msgstr "下一动作"
#. module: crm
#: code:addons/crm/crm_lead.py:763
#: code:addons/crm/crm_lead.py:780
#, python-format
msgid "<b>Partner</b> set to <em>%s</em>."
msgstr ""
msgstr "<b>业务伙伴</b> 设给 <em>%s</em>."
#. module: crm
#: selection:crm.lead.report,state:0
@ -983,6 +1020,8 @@ msgid ""
"Allows you to track your customers/suppliers claims and grievances.\n"
" This installs the module crm_claim."
msgstr ""
"允许你记录客户/供应商的索赔和抱怨。\n"
" 将安装crm_claim模块"
#. module: crm
#: model:crm.case.stage,name:crm.stage_lead6
@ -1030,10 +1069,10 @@ msgid "Creation Date"
msgstr "创建日期"
#. module: crm
#: code:addons/crm/crm_lead.py:698
#: code:addons/crm/crm_lead.py:715
#, python-format
msgid "Lead <b>converted into an Opportunity</b>"
msgstr ""
msgstr "线索 <b>已转换成商机</b>"
#. module: crm
#: selection:crm.segmentation.line,expr_name:0
@ -1094,7 +1133,7 @@ msgstr "删除"
#. module: crm
#: model:mail.message.subtype,description:crm.mt_lead_create
msgid "Opportunity created"
msgstr ""
msgstr "商机已创建"
#. module: crm
#: view:crm.lead:0
@ -1123,6 +1162,17 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 点击创建新的商机\n"
" </p><p>\n"
" OpenERP帮你记录你的销售管道\n"
" 跟踪潜在销售并更好地预测未来收益\n"
" </p><p>\n"
" 你可以对商机安排会议或电话,\n"
" 将他们转化成报价单,附上相关\n"
" 文档,跟踪所有的讨论等等。\n"
" </p>\n"
" "
#. module: crm
#: field:crm.segmentation,partner_id:0
@ -1186,13 +1236,13 @@ msgstr "如果你勾选了这里,这个阶段会作为每个销售团队的默
msgid ""
"This field is used to distinguish stages related to Leads from stages "
"related to Opportunities, or to specify stages available for both types."
msgstr ""
msgstr "此域用于区分用于线索的阶段和用于商机的阶段,或者指示同时可用于两个类型。"
#. module: crm
#: model:mail.message.subtype,name:crm.mt_lead_create
#: model:mail.message.subtype,name:crm.mt_salesteam_lead
msgid "Lead Created"
msgstr ""
msgstr "线索已创建"
#. module: crm
#: help:crm.segmentation,sales_purchase_active:0
@ -1217,7 +1267,9 @@ msgid "Days to Close"
msgstr "结束日期"
#. module: crm
#: code:addons/crm/crm_lead.py:1057
#: field:crm.case.section,complete_name:0
#, python-format
msgid "unknown"
msgstr "未知"
@ -1264,7 +1316,7 @@ msgid ""
"progress the Status is set to 'Open'. When the case is over, the Status is "
"set to 'Done'. If the case needs to be reviewed then the Status is set to "
"'Pending'."
msgstr ""
msgstr "当一个创建时,状态设为‘草稿’。如果此案在进行中,被设为‘打开‘。当案子结束,状态被设为’结束‘。如果需要被审阅,状态被设为’等待‘"
#. module: crm
#: model:crm.case.section,name:crm.crm_case_section_1
@ -1287,7 +1339,7 @@ msgstr "发送邮件的时候,默认的邮件地址来自销售团队。"
msgid ""
"Phone Calls Assigned to the current user or with a team having the current "
"user as team leader"
msgstr ""
msgstr "被分配给当前用户或者当前用户领导的销售团队的电话沟通任务。"
#. module: crm
#: view:crm.segmentation:0
@ -1300,7 +1352,7 @@ msgid "Lead Description"
msgstr "销售线索描述"
#. module: crm
#: code:addons/crm/crm_lead.py:565
#: code:addons/crm/crm_lead.py:581
#, python-format
msgid "Merged opportunities"
msgstr "合并商机"
@ -1373,7 +1425,7 @@ msgstr "待办事项"
#. module: crm
#: model:mail.message.subtype,description:crm.mt_lead_lost
msgid "Opportunity lost"
msgstr ""
msgstr "丢掉的商机"
#. module: crm
#: field:crm.lead2opportunity.partner,action:0
@ -1422,7 +1474,7 @@ msgstr "用户"
#. module: crm
#: model:mail.message.subtype,name:crm.mt_lead_stage
msgid "Stage Changed"
msgstr ""
msgstr "阶段已改变"
#. module: crm
#: field:crm.case.stage,section_ids:0
@ -1495,7 +1547,7 @@ msgstr "信息和通信历史记录"
#. module: crm
#: view:crm.lead:0
msgid "Show Countries"
msgstr ""
msgstr "显示国家"
#. module: crm
#: view:crm.lead:0
@ -1567,7 +1619,7 @@ msgid ""
"The status of your document will automatically change regarding the selected "
"stage. For example, if a stage is related to the status 'Close', when your "
"document reaches this stage, it is automatically closed."
msgstr ""
msgstr "文档的状态会根据选中的阶段自动改变。如果一个阶段关联了‘关闭’状态,当文档到达这个阶段就自动被关闭。"
#. module: crm
#: view:crm.lead2opportunity.partner.mass:0
@ -1588,7 +1640,7 @@ msgstr "通话月份"
#: code:addons/crm/crm_phonecall.py:290
#, python-format
msgid "Partner has been <b>created</b>."
msgstr ""
msgstr "合作伙伴已<b>创建</b>"
#. module: crm
#: field:sale.config.settings,module_crm_claim:0
@ -1602,6 +1654,7 @@ msgid ""
"the treatment delays or number of leads per state. You can sort out your "
"leads analysis by different groups to get accurate grained analysis."
msgstr ""
"线索分析允许你检查不同的关联CRM的信息例如延迟或每个状态的线索的数量。你可以使用不同的分组对线索分析进行整理已获得精确细致的分析结果。"
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor3
@ -1822,7 +1875,7 @@ msgstr "销售团队"
#. module: crm
#: field:crm.case.stage,case_default:0
msgid "Default to New Sales Team"
msgstr ""
msgstr "默认分配给新建销售团队"
#. module: crm
#: view:crm.lead:0
@ -1861,10 +1914,10 @@ msgid "Leads"
msgstr "线索"
#. module: crm
#: code:addons/crm/crm_lead.py:563
#: code:addons/crm/crm_lead.py:579
#, python-format
msgid "Merged leads"
msgstr ""
msgstr "合并的线索"
#. module: crm
#: model:crm.case.categ,name:crm.categ_oppor5
@ -1887,7 +1940,7 @@ msgstr "待办"
#: model:mail.message.subtype,name:crm.mt_lead_convert_to_opportunity
#: model:mail.message.subtype,name:crm.mt_salesteam_lead_opportunity
msgid "Lead to Opportunity"
msgstr ""
msgstr "线索到商机"
#. module: crm
#: field:crm.lead,user_email:0
@ -1955,7 +2008,6 @@ msgid "Global CC"
msgstr "完整抄送"
#. module: crm
#: view:crm.lead:0
#: view:crm.phonecall:0
#: model:ir.actions.act_window,name:crm.crm_case_categ_phone0
#: model:ir.ui.menu,name:crm.menu_crm_case_phone
@ -2089,7 +2141,7 @@ msgstr "转换选项"
msgid ""
"Follow this salesteam to automatically track the events associated to users "
"of this team."
msgstr ""
msgstr "跟踪此销售团队,自动跟踪此团队中用户关联的事件。"
#. module: crm
#: view:crm.lead:0
@ -2101,7 +2153,7 @@ msgstr "地址"
msgid ""
"The email address associated with this team. New emails received will "
"automatically create new leads assigned to the team."
msgstr ""
msgstr "此邮件地址关联此团队。收到的新邮件将自动创建线索并分配给此团队。"
#. module: crm
#: view:crm.lead:0
@ -2193,13 +2245,24 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 点击安排一个销售电话\n"
" </p><p>\n"
" "
"OpenERP允许你容易地定义销售团队要打的\n"
" 电话并根据跟踪处理。 "
" \n"
" </p><p> \n"
" 你可以使用导入功能来导入新列表\n"
" </p>\n"
" "
#. module: crm
#: help:crm.case.stage,fold:0
msgid ""
"This stage is not visible, for example in status bar or kanban view, when "
"there are no records in that stage to display."
msgstr ""
msgstr "此阶段无记录可显示时,在状态栏或看板视图等处不可见。"
#. module: crm
#: field:crm.lead.report,nbr:0
@ -2245,7 +2308,7 @@ msgstr "运行中"
#. module: crm
#: model:mail.message.subtype,description:crm.mt_lead_convert_to_opportunity
msgid "Lead converted into an opportunity"
msgstr ""
msgstr "装换成商机的线索"
#. module: crm
#: view:crm.lead:0
@ -2255,7 +2318,7 @@ msgstr "未分配的线索"
#. module: crm
#: model:mail.message.subtype,description:crm.mt_lead_won
msgid "Opportunity won"
msgstr ""
msgstr "获胜的商机"
#. module: crm
#: field:crm.case.categ,object_id:0
@ -2321,7 +2384,7 @@ msgstr "在这报表中,你能分析你的销售团队在电话访问上的业
#. module: crm
#: field:crm.case.stage,fold:0
msgid "Fold by Default"
msgstr ""
msgstr "默认折叠"
#. module: crm
#: field:crm.case.stage,state:0
@ -2357,7 +2420,7 @@ msgstr "已确认"
#. module: crm
#: model:ir.model,name:crm.model_crm_partner_binding
msgid "Handle partner binding or generation in CRM wizards."
msgstr ""
msgstr "在CRM向导中处理合作伙伴的绑定或生成。"
#. module: crm
#: model:ir.actions.act_window,name:crm.act_oppor_stage_user
@ -2408,11 +2471,23 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 点击创建一个未验证的线索。\n"
" </p><p>\n"
" 如果你在创建一个商机或者客户前\n"
" 需要验证,使用线索。可以是一张收到的\n"
" 名片,网站上收到的一个联系方式,或者\n"
" 你导入系统的一个文件数据。\n"
" </p><p>\n"
" 一旦被确定,线索可以被装换成商机\n"
" 以及/或者你的地址簿中的新客户。\n"
" </p>\n"
" "
#. module: crm
#: field:sale.config.settings,fetchmail_lead:0
msgid "Create leads from incoming mails"
msgstr ""
msgstr "从收到的邮件创建线索"
#. module: crm
#: view:crm.lead:0
@ -2480,6 +2555,19 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 点击创建电话总结\n"
" </p><p>\n"
" OpenERP "
"允许你快速记录打入的电话,用来\n"
" "
"跟踪与用户沟通的历史,或通知另一个销售团队。\n"
" </p><p>\n"
" "
"为了跟踪处理一个电话,你可以触发请求另一次电话,\n"
" 一次会议或者一个商机。\n"
" </p>\n"
" "
#. module: crm
#: model:process.node,note:crm.process_node_leads0
@ -2526,6 +2614,20 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" "
"点击新建一个销售标签\n"
" </p><p>\n"
" "
"创建指定的适合公司活动的标签\n"
" "
"用于更好地分析你的线索和商机。\n"
" "
"标签分类可以反映你的商品结构或者\n"
" "
"不同的销售类型。\n"
" </p>\n"
" "
#. module: crm
#: selection:crm.lead.report,creation_month:0
@ -2538,7 +2640,7 @@ msgstr "8月"
#: model:mail.message.subtype,name:crm.mt_lead_lost
#: model:mail.message.subtype,name:crm.mt_salesteam_lead_lost
msgid "Opportunity Lost"
msgstr ""
msgstr "丢失的商机"
#. module: crm
#: field:crm.lead.report,deadline_month:0
@ -2634,7 +2736,7 @@ msgstr "通话日志"
#: model:mail.message.subtype,name:crm.mt_lead_won
#: model:mail.message.subtype,name:crm.mt_salesteam_lead_won
msgid "Opportunity Won"
msgstr ""
msgstr "获胜的商机"
#. module: crm
#: model:ir.actions.act_window,name:crm.crm_case_section_act_tree
@ -2773,7 +2875,7 @@ msgstr "电话访问"
#. module: crm
#: view:crm.phonecall.report:0
msgid "Phone calls that are assigned to one of the sale teams I manage"
msgstr ""
msgstr "分配给某个我所管理的销售团队的电话。"
#. module: crm
#: view:crm.lead:0
@ -2783,7 +2885,7 @@ msgstr "发现日期"
#. module: crm
#: view:crm.lead:0
msgid "at"
msgstr ""
msgstr ""
#. module: crm
#: model:crm.case.stage,name:crm.stage_lead1
@ -2848,6 +2950,19 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" "
"点击定义一个新渠道\n"
" </p><p>\n"
" "
"使用渠道来记录你的线索和商机的来源。\n"
" "
"主要用于市场工作相关的销售分析报告。\n"
" </p><p>\n"
" "
"渠道例子:公司网站,电话,市场活动,经销商等\n"
" <p>\n"
" "
#. module: crm
#: view:crm.lead:0
@ -2885,7 +3000,7 @@ msgid "Working Hours"
msgstr "工作时间"
#. module: crm
#: code:addons/crm/crm_lead.py:968
#: code:addons/crm/crm_lead.py:986
#: view:crm.lead:0
#: field:crm.lead2opportunity.partner,partner_id:0
#: field:crm.lead2opportunity.partner.mass,partner_id:0
@ -3007,7 +3122,7 @@ msgstr "时事通信"
#. module: crm
#: model:mail.message.subtype,name:crm.mt_salesteam_lead_stage
msgid "Opportunity Stage Changed"
msgstr ""
msgstr "商机状态已改变"
#. module: crm
#: model:ir.actions.act_window,help:crm.crm_lead_stage_act
@ -3021,6 +3136,16 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" "
"点击设置你的线索/商机管道的新阶段。\n"
" </p><p>\n"
" "
"阶段允许销售人员记录特定的线索或商机\n"
" "
"在销售循环中的位置。\n"
" </p>\n"
" "
#~ msgid "My Draft "
#~ msgstr "我的草稿 "

View File

@ -35,6 +35,8 @@ class crm_lead2opportunity_partner(osv.osv_memory):
('merge', 'Merge with existing opportunities')
], 'Conversion Action', required=True),
'opportunity_ids': fields.many2many('crm.lead', string='Opportunities'),
'user_id': fields.many2one('res.users', 'Salesperson', select=True),
'section_id': fields.many2one('crm.case.section', 'Sales Team', select=True),
}
def default_get(self, cr, uid, fields, context=None):
@ -74,9 +76,27 @@ class crm_lead2opportunity_partner(osv.osv_memory):
res.update({'name' : len(tomerge) >= 2 and 'merge' or 'convert'})
if 'opportunity_ids' in fields and len(tomerge) >= 2:
res.update({'opportunity_ids': list(tomerge)})
if lead.user_id:
res.update({'user_id': lead.user_id.id})
if lead.section_id:
res.update({'section_id': lead.section_id.id})
return res
def on_change_user(self, cr, uid, ids, user_id, section_id, context=None):
""" When changing the user, also set a section_id or restrict section id
to the ones user_id is member of. """
if user_id:
if section_id:
user_in_section = self.pool.get('crm.case.section').search(cr, uid, [('id', '=', section_id), '|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context, count=True)
else:
user_in_section = False
if not user_in_section:
section_id = False
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
if section_ids:
section_id = section_ids[0]
return {'value': {'section_id': section_id}}
def view_init(self, cr, uid, fields, context=None):
"""
Check some preconditions before the wizard executes.
@ -118,15 +138,15 @@ class crm_lead2opportunity_partner(osv.osv_memory):
w = self.browse(cr, uid, ids, context=context)[0]
opp_ids = [o.id for o in w.opportunity_ids]
if w.name == 'merge':
lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, context=context)
lead_id = self.pool.get('crm.lead').merge_opportunity(cr, uid, opp_ids, w.user_id.id, w.section_id.id, context=context)
lead_ids = [lead_id]
lead = self.pool.get('crm.lead').read(cr, uid, lead_id, ['type'], context=context)
if lead['type'] == "lead":
context.update({'active_ids': lead_ids})
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
else:
lead_ids = context.get('active_ids', [])
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids}, context=context)
self._convert_opportunity(cr, uid, ids, {'lead_ids': lead_ids, 'user_ids': [w.user_id.id], 'section_id': w.section_id.id}, context=context)
return self.pool.get('crm.lead').redirect_opportunity_view(cr, uid, lead_ids[0], context=context)

View File

@ -10,6 +10,10 @@
<group name="name">
<field name="name" class="oe_inline"/>
</group>
<group string="Assign opportunities to">
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, section_id, context)"/>
<field name="section_id" class="oe_inline"/>
</group>
<group string="Opportunities">
<field name="opportunity_ids" attrs="{'invisible': [('name', '!=', 'merge')]}" nolabel="1">
<tree>
@ -56,6 +60,10 @@
attrs="{'required': [('action', '=', 'exist')], 'invisible':[('action','!=','exist')]}"
class="oe_inline"/>
</group>
<group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
<field name="section_id" groups="base.group_multi_salesteams"/>
<field name="user_ids" widget="many2many_tags"/>
</group>
<group string="Select Opportunities" attrs="{'invisible': [('name', '!=', 'merge')]}">
<field name="opportunity_ids" colspan="4" nolabel="1" attrs="{'invisible': [('name', '=', 'convert')]}">
<tree>
@ -72,12 +80,6 @@
</tree>
</field>
</group>
<group string="Assign opportunities to" attrs="{'invisible': [('name', '=', '')]}">
<field name="section_id" groups="base.group_multi_salesteams"/>
<field name="user_ids" widget="many2many_tags"/>
</group>
<footer>
<button name="mass_convert" string="Convert to Opportunities" type="object" class="oe_highlight"/>
or

View File

@ -34,6 +34,8 @@ class crm_merge_opportunity(osv.osv_memory):
_description = 'Merge opportunities'
_columns = {
'opportunity_ids': fields.many2many('crm.lead', rel='merge_opportunity_rel', id1='merge_id', id2='opportunity_id', string='Leads/Opportunities'),
'user_id': fields.many2one('res.users', 'Salesperson', select=True),
'section_id': fields.many2one('crm.case.section', 'Sales Team', select=True),
}
def action_merge(self, cr, uid, ids, context=None):
@ -47,7 +49,7 @@ class crm_merge_opportunity(osv.osv_memory):
#TODO: why is this passed through the context ?
context['lead_ids'] = [opportunity2merge_ids[0].id]
merge_id = lead_obj.merge_opportunity(cr, uid, [x.id for x in opportunity2merge_ids], context=context)
merge_id = lead_obj.merge_opportunity(cr, uid, [x.id for x in opportunity2merge_ids], wizard.user_id.id, wizard.section_id.id, context=context)
# The newly created lead might be a lead or an opp: redirect toward the right view
merge_result = lead_obj.browse(cr, uid, merge_id, context=context)
@ -79,4 +81,19 @@ class crm_merge_opportunity(osv.osv_memory):
return res
def on_change_user(self, cr, uid, ids, user_id, section_id, context=None):
""" When changing the user, also set a section_id or restrict section id
to the ones user_id is member of. """
if user_id:
if section_id:
user_in_section = self.pool.get('crm.case.section').search(cr, uid, [('id', '=', section_id), '|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context, count=True)
else:
user_in_section = False
if not user_in_section:
section_id = False
section_ids = self.pool.get('crm.case.section').search(cr, uid, ['|', ('user_id', '=', user_id), ('member_ids', '=', user_id)], context=context)
if section_ids:
section_id = section_ids[0]
return {'value': {'section_id': section_id}}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -8,20 +8,25 @@
<field name="model">crm.merge.opportunity</field>
<field name="arch" type="xml">
<form string="Merge Leads/Opportunities" version="7.0">
<separator string="Select Leads/Opportunities"/>
<field name="opportunity_ids">
<tree>
<field name="create_date"/>
<field name="name"/>
<field name="type"/>
<field name="contact_name"/>
<field name="email_from"/>
<field name="phone"/>
<field name="stage_id"/>
<field name="user_id"/>
<field name="section_id" groups="base.group_multi_salesteams"/>
</tree>
</field>
<group string="Assign opportunities to">
<field name="user_id" class="oe_inline" on_change="on_change_user(user_id, context)"/>
<field name="section_id" class="oe_inline"/>
</group>
<group string="Select Leads/Opportunities">
<field name="opportunity_ids" nolabel="1">
<tree>
<field name="create_date"/>
<field name="name"/>
<field name="type"/>
<field name="contact_name"/>
<field name="email_from"/>
<field name="phone"/>
<field name="stage_id"/>
<field name="user_id"/>
<field name="section_id" groups="base.group_multi_salesteams"/>
</tree>
</field>
</group>
<footer>
<button name="action_merge" type="object" string="Merge" class="oe_highlight"/>
or

View File

@ -38,7 +38,7 @@ You can also use the geolocalization without using the GPS coordinates.
""",
'author': 'OpenERP SA',
'depends': ['crm', 'account', 'portal'],
'demo': ['res_partner_demo.xml'],
'demo': ['res_partner_demo.xml', 'crm_lead_demo.xml'],
'data': [
'security/ir.model.access.csv',
'res_partner_view.xml',

View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<openerp>
<data noupdate="1">
<!-- Demo Leads -->
<record id="crm_case_partner_assign_1" model="crm.lead">
<field name="type">lead</field>
<field name="name">Specifications and price of your phones</field>
<field name="contact_name">Steve Martinez</field>
<field name="partner_name"></field>
<field name="partner_id" ref=""/>
<field name="function">Reseller</field>
<field name="country_id" ref="base.uk"/>
<field name="city">Edinburgh</field>
<field name="type_id" ref="crm.type_lead8"/>
<field name="categ_ids" eval="[(6, 0, [ref('crm.categ_oppor1')])]"/>
<field name="channel_id" ref="crm.crm_case_channel_email"/>
<field name="priority">2</field>
<field name="section_id" ref="crm.crm_case_section_2"/>
<field name="user_id" ref=""/>
<field name="stage_id" ref="crm.stage_lead1"/>
<field name="description">Hi,
Please, can you give me more details about your phones, including their specifications and their prices.
Regards,
Steve</field>
<field eval="1" name="active"/>
<field name="partner_assigned_id" ref="portal.partner_demo_portal"/>
</record>
</data>
</openerp>

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-12-21 17:05+0000\n"
"PO-Revision-Date: 2012-12-14 13:50+0000\n"
"Last-Translator: 盈通 ccdos <ccdos@163.com>\n"
"PO-Revision-Date: 2013-07-23 06:22+0000\n"
"Last-Translator: fanvil <fanvil@hotmail.com>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-03-16 05:47+0000\n"
"X-Generator: Launchpad (build 16532)\n"
"X-Launchpad-Export-Date: 2013-07-24 05:31+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: crm_partner_assign
#: field:crm.lead.report.assign,delay_close:0
@ -37,7 +37,7 @@ msgstr "计划收入"
msgid ""
"Message type: email for email message, notification for system message, "
"comment for other messages such as user replies"
msgstr ""
msgstr "消息类型Email 用于 邮件消息, 通知用户系统消息,评论用于其他消息,例如用户回复。"
#. module: crm_partner_assign
#: field:crm.lead.report.assign,nbr:0
@ -53,7 +53,7 @@ msgstr "分组..."
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,body:0
msgid "Automatically sanitized HTML contents"
msgstr ""
msgstr "自动整理HTML内容"
#. module: crm_partner_assign
#: view:crm.lead:0
@ -68,7 +68,7 @@ msgstr "geolocalization定位"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,starred:0
msgid "Starred"
msgstr ""
msgstr "加星号的邮件"
#. module: crm_partner_assign
#: view:crm.lead.forward.to.partner:0
@ -80,7 +80,7 @@ msgstr "正文"
msgid ""
"Email address of the sender. This field is set when no matching partner is "
"found for incoming emails."
msgstr ""
msgstr "发送者的Email地址。当收取的email没有对应的合作伙伴时此字段被设置"
#. module: crm_partner_assign
#: view:crm.partner.report.assign:0
@ -111,7 +111,7 @@ msgstr "公司"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,notification_ids:0
msgid "Notifications"
msgstr ""
msgstr "通知"
#. module: crm_partner_assign
#: field:crm.lead.report.assign,date_assign:0
@ -123,7 +123,7 @@ msgstr "业务伙伴日期"
#: view:crm.partner.report.assign:0
#: view:res.partner:0
msgid "Salesperson"
msgstr ""
msgstr "销售员"
#. module: crm_partner_assign
#: selection:crm.lead.report.assign,priority:0
@ -170,7 +170,7 @@ msgstr "指派的geolocalization"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_crm_lead_forward_to_partner
msgid "Email composition wizard"
msgstr ""
msgstr "Email撰写向导"
#. module: crm_partner_assign
#: field:crm.partner.report.assign,turnover:0
@ -192,7 +192,7 @@ msgstr "为线索指定一个业务伙伴的概率0表示没指派"
#. module: crm_partner_assign
#: view:res.partner:0
msgid "Partner Activation"
msgstr ""
msgstr "激活合作伙伴"
#. module: crm_partner_assign
#: selection:crm.lead.forward.to.partner,type:0
@ -203,7 +203,7 @@ msgstr "系统通知"
#: code:addons/crm_partner_assign/wizard/crm_forward_to_partner.py:74
#, python-format
msgid "Lead forward"
msgstr ""
msgstr "转发的线索"
#. module: crm_partner_assign
#: field:crm.lead.report.assign,probability:0
@ -284,7 +284,7 @@ msgstr "最低"
#. module: crm_partner_assign
#: view:crm.partner.report.assign:0
msgid "Date Invoice"
msgstr ""
msgstr "发票排程"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,template_id:0
@ -309,7 +309,7 @@ msgstr "创建日期"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_res_partner_activation
msgid "res.partner.activation"
msgstr ""
msgstr "res.partner.activation"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,parent_id:0
@ -344,7 +344,7 @@ msgstr "7月"
#. module: crm_partner_assign
#: view:crm.partner.report.assign:0
msgid "Date Review"
msgstr ""
msgstr "回顾排程"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@ -356,12 +356,12 @@ msgstr "阶段"
#: view:crm.lead.report.assign:0
#: field:crm.lead.report.assign,state:0
msgid "Status"
msgstr ""
msgstr "状态"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,to_read:0
msgid "To read"
msgstr ""
msgstr "查看"
#. module: crm_partner_assign
#: code:addons/crm_partner_assign/wizard/crm_forward_to_partner.py:74
@ -416,12 +416,12 @@ msgstr "到期天数"
#: help:crm.lead.forward.to.partner,notified_partner_ids:0
msgid ""
"Partners that have a notification pushing this message in their mailboxes"
msgstr ""
msgstr "推送此通知消息进他们邮箱的合作伙伴"
#. module: crm_partner_assign
#: selection:crm.lead.forward.to.partner,type:0
msgid "Comment"
msgstr ""
msgstr "评论"
#. module: crm_partner_assign
#: field:res.partner,partner_weight:0
@ -449,7 +449,7 @@ msgstr "12月"
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,vote_user_ids:0
msgid "Users that voted for this message"
msgstr ""
msgstr "投票给这条消息的用户"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@ -465,13 +465,13 @@ msgstr "开启日期"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,child_ids:0
msgid "Child Messages"
msgstr ""
msgstr "子信息"
#. module: crm_partner_assign
#: field:crm.partner.report.assign,date_review:0
#: field:res.partner,date_review:0
msgid "Latest Partner Review"
msgstr ""
msgstr "最新合作伙伴评论"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,subject:0
@ -481,17 +481,17 @@ msgstr "主题"
#. module: crm_partner_assign
#: view:crm.lead.forward.to.partner:0
msgid "or"
msgstr ""
msgstr ""
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,body:0
msgid "Contents"
msgstr ""
msgstr "内容"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,vote_user_ids:0
msgid "Votes"
msgstr ""
msgstr "投票"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@ -501,13 +501,13 @@ msgstr "#商机"
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,starred:0
msgid "Current user has a starred notification linked to this message"
msgstr ""
msgstr "当前用户用 星号 提醒关联到这条消息"
#. module: crm_partner_assign
#: field:crm.partner.report.assign,date_partnership:0
#: field:res.partner,date_partnership:0
msgid "Partnership Date"
msgstr ""
msgstr "合作关系日期"
#. module: crm_partner_assign
#: view:crm.lead:0
@ -661,7 +661,7 @@ msgstr ""
#. module: crm_partner_assign
#: field:crm.partner.report.assign,period_id:0
msgid "Invoice Period"
msgstr ""
msgstr "发票期间"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_res_partner_grade
@ -682,7 +682,7 @@ msgstr "附件"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,record_name:0
msgid "Message Record Name"
msgstr ""
msgstr "消息记录名称"
#. module: crm_partner_assign
#: field:res.partner.activation,sequence:0
@ -696,7 +696,7 @@ msgstr "序列"
msgid ""
"Cannot contact geolocation servers. Please make sure that your internet "
"connection is up and running (%s)."
msgstr ""
msgstr "无法连接地理信息服务器。请确保你的互联网链接畅通(%s)。"
#. module: crm_partner_assign
#: selection:crm.lead.report.assign,month:0
@ -722,7 +722,7 @@ msgstr "开启"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,subtype_id:0
msgid "Subtype"
msgstr ""
msgstr "子类型"
#. module: crm_partner_assign
#: field:res.partner,date_localization:0
@ -737,12 +737,12 @@ msgstr "当前的"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_crm_lead
msgid "Lead/Opportunity"
msgstr ""
msgstr "线索/商机"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,notified_partner_ids:0
msgid "Notified partners"
msgstr ""
msgstr "已通知的合作伙伴"
#. module: crm_partner_assign
#: view:crm.lead.forward.to.partner:0
@ -773,7 +773,7 @@ msgstr "可能收入"
#: field:res.partner,activation:0
#: view:res.partner.activation:0
msgid "Activation"
msgstr ""
msgstr "激活"
#. module: crm_partner_assign
#: view:crm.lead:0
@ -784,12 +784,12 @@ msgstr "指定的业务伙伴"
#. module: crm_partner_assign
#: field:res.partner,grade_id:0
msgid "Partner Level"
msgstr ""
msgstr "合作伙伴级别"
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,to_read:0
msgid "Current user has an unread notification linked to this message"
msgstr ""
msgstr "当前用户有关联到这条消息的未读的提醒"
#. module: crm_partner_assign
#: selection:crm.lead.report.assign,type:0
@ -815,7 +815,7 @@ msgstr "名称"
#: model:ir.actions.act_window,name:crm_partner_assign.res_partner_activation_act
#: model:ir.ui.menu,name:crm_partner_assign.res_partner_activation_config_mi
msgid "Partner Activations"
msgstr ""
msgstr "合作伙伴激活"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@ -872,7 +872,7 @@ msgstr "客户关系管理 线索报表"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,composition_mode:0
msgid "Composition mode"
msgstr ""
msgstr "写作模式"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,model:0

View File

@ -16,12 +16,8 @@
<field name="user_id"/>
<field string="Timebox" name="timebox_id"/>
<button name="prev_timebox" type="object" icon="gtk-go-back" string="Previous" states="draft,pending,open"/>
<button name="next_timebox" type="object" icon="gtk-go-forward" string="Next" states="draft,pending,open"/>
<field name="state"/>
<button name="do_cancel" states="draft,open,pending" string="Cancel" type="object" icon="gtk-cancel" help="For cancelling the task"/>
<button name="action_close" states="draft,pending,open" string="Done" type="object" icon="terp-dialog-close" help="For changing to done state"/>
<button name="prev_timebox" type="object" string="Previous"/>
<button name="next_timebox" type="object" string="Next"/>
</tree>
</field>
</page>

View File

@ -22,5 +22,6 @@
import email_template
import wizard
import res_partner
import ir_actions
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -59,6 +59,7 @@ campaigns on any OpenERP document.
'wizard/email_template_preview_view.xml',
'email_template_view.xml',
'res_partner_view.xml',
'ir_actions_view.xml',
'wizard/mail_compose_message_view.xml',
'security/ir.model.access.csv'
],

View File

@ -0,0 +1,12 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- ``mail.compose.message``: added support of ``mail_server_id`` from template
- Server action update
- added `email` server action type, now entirely based on email templates.

View File

@ -0,0 +1,13 @@
Email Template module documentation
===================================
Email Template documentation topics
'''''''''''''''''''''''''''''''''''
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013 OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.osv import fields, osv
class actions_server(osv.Model):
""" Add email option in server actions. """
_name = 'ir.actions.server'
_inherit = ['ir.actions.server']
def _get_states(self, cr, uid, context=None):
res = super(actions_server, self)._get_states(cr, uid, context=context)
res.insert(0, ('email', 'Send Email'))
return res
_columns = {
'email_from': fields.char('From',
help="Sender address; define the template to see its value. If not set, the default "
"value will be the author's email alias if configured, or email address."),
'email_to': fields.char('To (Emails)',
help="Comma-separated recipient addresses; define the template to see its value"),
'partner_to': fields.char('To (Partners)',
help="Comma-separated ids of recipient partners; define the template to see its value"),
'subject': fields.char('Subject',
help="Email subject; define the template to see its value"),
'body_html': fields.text('Body',
help="Rich-text/HTML version of the message; define the template to see its value"),
'template_id': fields.many2one('email.template', 'Email Template', ondelete='set null',
help="Define the email template to use for the email to send.")
}
def on_change_template_id(self, cr, uid, ids, template_id, context=None):
""" Render the raw template in the server action fields. """
if template_id:
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
if not values.get('email_from'):
return {'warning': {'title': 'Incomplete template', 'message': 'Your template should define email_from'}, 'value': values}
else:
values = self.default_get(cr, uid, ['subject', 'body_html', 'email_from', 'email_to', 'partner_to'], context=context)
return {'value': values}
def create(self, cr, uid, values, context=None):
if values.get('template_id'):
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
template_values = self.pool.get('email.template').read(cr, uid, values.get('template_id'), fields, context)
values.update(dict((field, template_values[field]) for field in fields if template_values.get(field)))
return super(actions_server, self).create(cr, uid, values, context=context)
def write(self, cr, uid, ids, values, context=None):
if values.get('template_id'):
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
template_values = self.pool.get('email.template').read(cr, uid, values.get('template_id'), fields, context)
values.update(dict((field, template_values[field]) for field in fields if template_values.get(field)))
return super(actions_server, self).write(cr, uid, ids, values, context=context)
def run_action_email(self, cr, uid, action, eval_context=None, context=None):
if not action.template_id or not context.get('active_id'):
return False
self.pool['email.template'].send_mail(cr, uid, action.template_id.id, context.get('active_id'),
force_send=False, raise_exception=False, context=context)
return False
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_server_action_form_template">
<field name="name">ir.actions.server.form</field>
<field name="model">ir.actions.server</field>
<field name="inherit_id" ref="base.view_server_action_form"/>
<field name="arch" type="xml">
<xpath expr="//page[@name='code']" position="after">
<page string="Email" autofocus="autofocus"
attrs="{'invisible': [('state', '!=', 'email')]}">
<p attrs="{'invisible': [('model_id', '!=', False)]}">
Please set the Base Model before setting the action details.
</p>
<group attrs="{'invisible': [('model_id', '=', False)]}">
<field name="template_id"
on_change='on_change_template_id(template_id)'
domain="[('model_id', '=', model_id)]"
attrs="{'required': [('state', '=', 'email')]}"/>
<p colspan="2" attrs="{'invisible': [('template_id', '!=', False)]}">
Choose a template to display its values.
</p>
<p colspan="2" attrs="{'invisible': [('template_id', '=', False)]}">
The values displayed hereunder are informative. When sending the email, the values
will be taken from the email template.
</p>
</group>
<group attrs="{'invisible': ['|', ('model_id', '=', False), ('template_id', '=', False)]}">
<label for="email_from"/>
<div>
<field name="email_from" nolabel="1'" readonly="1"
attrs="{'required': [('state', '=', 'email')]}"/>
<p attrs="{'invisible': [('email_from', '!=', False)]}">
Your template does not defined any email_from. Please update your template.
</p>
</div>
<field name="email_to" readonly="1"/>
<field name="partner_to" readonly="1"/>
<field name="subject" readonly="1" attrs="{'required': [('state', '=', 'email')]}"/>
<field name="body_html" readonly="1" attrs="{'required': [('state', '=', 'email')]}"/>
</group>
</page>
</xpath>
</field>
</record>
</data>
</openerp>

View File

@ -18,10 +18,11 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_mail
from . import test_mail, test_ir_actions
checks = [
test_mail,
test_ir_actions,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.base.tests.test_ir_actions import TestServerActionsBase
class TestServerActionsEmail(TestServerActionsBase):
def test_00_state_email(self):
""" Test ir.actions.server email type """
cr, uid = self.cr, self.uid
# create email_template
template_id = self.registry('email.template').create(cr, uid, {
'name': 'TestTemplate',
'email_from': 'myself@example.com',
'email_to': 'brigitte@example.com',
'partner_to': '[%s]' % self.test_partner_id,
'model_id': self.res_partner_model_id,
'subject': 'About ${object.name}',
'body_html': '<p>Dear ${object.name}, your parent is ${object.parent_id and object.parent_id.name or "False"}</p>',
})
self.ir_actions_server.write(cr, uid, self.act_id, {
'state': 'email',
'template_id': template_id,
})
run_res = self.ir_actions_server.run(cr, uid, [self.act_id], context=self.context)
self.assertFalse(run_res, 'ir_actions_server: email server action correctly finished should return False')
# check an email is waiting for sending
mail_ids = self.registry('mail.mail').search(cr, uid, [('subject', '=', 'About TestingPartner')])
self.assertEqual(len(mail_ids), 1, 'ir_actions_server: TODO')
# check email content
mail = self.registry('mail.mail').browse(cr, uid, mail_ids[0])
self.assertEqual(mail.body, '<p>Dear TestingPartner, your parent is False</p>',
'ir_actions_server: TODO')

View File

@ -22,6 +22,7 @@
from openerp import tools
from openerp.osv import osv, fields
def _reopen(self, res_id, model):
return {'type': 'ir.actions.act_window',
'view_mode': 'form',
@ -34,7 +35,8 @@ def _reopen(self, res_id, model):
'context': {
'default_model': model,
},
}
}
class mail_compose_message(osv.TransientModel):
_inherit = 'mail.compose.message'
@ -58,7 +60,7 @@ class mail_compose_message(osv.TransientModel):
context = {}
wizard_context = dict(context)
for wizard in self.browse(cr, uid, ids, context=context):
if wizard.template_id and not wizard.template_id.user_signature:
if wizard.template_id:
wizard_context['mail_notify_user_signature'] = False # template user_signature is added when generating body_html
if not wizard.attachment_ids or wizard.composition_mode == 'mass_mail' or not wizard.template_id:
continue
@ -75,7 +77,7 @@ class mail_compose_message(osv.TransientModel):
""" - mass_mailing: we cannot render, so return the template values
- normal mode: return rendered values """
if template_id and composition_mode == 'mass_mail':
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids']
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'mail_server_id']
template_values = self.pool.get('email.template').read(cr, uid, template_id, fields, context)
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
elif template_id:
@ -95,7 +97,7 @@ class mail_compose_message(osv.TransientModel):
}
values['attachment_ids'].append(ir_attach_obj.create(cr, uid, data_attach, context=context))
else:
values = self.default_get(cr, uid, ['subject', 'body', 'email_from', 'email_to', 'email_cc', 'partner_to', 'reply_to', 'attachment_ids'], context=context)
values = self.default_get(cr, uid, ['subject', 'body', 'email_from', 'email_to', 'email_cc', 'partner_to', 'reply_to', 'attachment_ids', 'mail_server_id'], context=context)
if values.get('body_html'):
values['body'] = values.pop('body_html')
@ -150,7 +152,7 @@ class mail_compose_message(osv.TransientModel):
mail.compose.message, transform email_cc and email_to into partner_ids """
template_values = self.pool.get('email.template').generate_email(cr, uid, template_id, res_id, context=context)
# filter template values
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments']
fields = ['subject', 'body_html', 'email_from', 'email_to', 'partner_to', 'email_cc', 'reply_to', 'attachment_ids', 'attachments', 'mail_server_id']
values = dict((field, template_values[field]) for field in fields if template_values.get(field))
values['body'] = values.pop('body_html', '')

View File

@ -28,7 +28,7 @@ import urllib2
import simplejson
class google_service(osv.osv):
class google_service(osv.osv_memory):
_name = 'google.service'
def generate_refresh_token(self, cr, uid, service, authorization_code, context=None):
@ -51,10 +51,10 @@ class google_service(osv.osv):
content = simplejson.loads(content)
return content.get('refresh_token')
def _get_google_token_uri(self, cr, uid, service, context=None):
def _get_google_token_uri(self, cr, uid, service, scope, context=None):
ir_config = self.pool['ir.config_parameter']
params = {
'scope': 'https://www.googleapis.com/auth/drive',
'scope': scope,
'redirect_uri': ir_config.get_param(cr, SUPERUSER_ID, 'google_redirect_uri'),
'client_id': ir_config.get_param(cr, SUPERUSER_ID, 'google_%s_client_id' % service),
'response_type': 'code',

View File

@ -54,58 +54,69 @@ class config(osv.osv):
attachment = attach_pool.browse(cr, uid, attach_ids[0], context)
url = attachment.url
else:
url = self.copy_doc(cr, uid, res_id, template_id, name_gdocs, model.model, context)
url = self.copy_doc(cr, uid, res_id, template_id, name_gdocs, model.model, context).get('url')
return url
def copy_doc(self, cr, uid, res_id, template_id, name_gdocs, res_model, context=None):
def get_access_token(self, cr, uid, scope=None, context=None):
ir_config = self.pool['ir.config_parameter']
google_drive_refresh_token = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_refresh_token')
group_config = self.pool['ir.model.data'].get_object_reference(cr, uid, 'base', 'group_erp_manager')[1]
user = self.pool['res.users'].read(cr, uid, uid, "groups_id")
if not google_drive_refresh_token:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("You haven't configured 'Authorization Code' generated from google, Please generate and configure it in %(menu:base_setup.menu_general_configuration)s."), context=context)
if group_config in user['groups_id']:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("You haven't configured 'Authorization Code' generated from google, Please generate and configure it in %(menu:base_setup.menu_general_configuration)s."), context=context)
else:
raise osv.except_osv(_('Error!'), _("Google Drive is not yet configured. Please contact your administrator."))
google_drive_client_id = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_id')
google_drive_client_secret = ir_config.get_param(cr, SUPERUSER_ID, 'google_drive_client_secret')
google_web_base_url = ir_config.get_param(cr, SUPERUSER_ID, 'web.base.url')
#For Getting New Access Token With help of old Refresh Token
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"}
data = dict(client_id=google_drive_client_id,
refresh_token=google_drive_refresh_token,
client_secret=google_drive_client_secret,
grant_type="refresh_token")
data = urllib.urlencode(data)
data = urllib.urlencode(dict(client_id=google_drive_client_id,
refresh_token=google_drive_refresh_token,
client_secret=google_drive_client_secret,
grant_type="refresh_token",
scope=scope or 'https://www.googleapis.com/auth/drive'))
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"}
try:
req = urllib2.Request('https://accounts.google.com/o/oauth2/token', data, headers)
content = urllib2.urlopen(req).read()
except urllib2.HTTPError:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during the token generation. Please request again an authorization code in %(menu:base_setup.menu_general_configuration)s."), context=context)
if group_config in user['groups_id']:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("Something went wrong during the token generation. Please request again an authorization code in %(menu:base_setup.menu_general_configuration)s."), context=context)
else:
raise osv.except_osv(_('Error!'), _("Google Drive is not yet configured. Please contact your administrator."))
content = json.loads(content)
return content.get('access_token')
def copy_doc(self, cr, uid, res_id, template_id, name_gdocs, res_model, context=None):
ir_config = self.pool['ir.config_parameter']
google_web_base_url = ir_config.get_param(cr, SUPERUSER_ID, 'web.base.url')
access_token = self.get_access_token(cr, uid, context=context)
# Copy template in to drive with help of new access token
if 'access_token' in content:
request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % (template_id, content['access_token'])
try:
req = urllib2.Request(request_url, None, headers)
parents = urllib2.urlopen(req).read()
except urllib2.HTTPError:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("The Google Template cannot be found. Maybe it has been deleted."), context=context)
parents_dict = json.loads(parents)
request_url = "https://www.googleapis.com/drive/v2/files/%s?fields=parents/id&access_token=%s" % (template_id, access_token)
headers = {"Content-type": "application/x-www-form-urlencoded", "Accept-Encoding": "gzip, deflate"}
try:
req = urllib2.Request(request_url, None, headers)
parents = urllib2.urlopen(req).read()
except urllib2.HTTPError:
raise self.pool.get('res.config.settings').get_config_warning(cr, _("The Google Template cannot be found. Maybe it has been deleted."), context=context)
parents_dict = json.loads(parents)
record_url = "Click on link to open Record in OpenERP\n %s/?db=%s#id=%s&model=%s" % (google_web_base_url, cr.dbname, res_id, res_model)
data = {"title": name_gdocs, "description": record_url, "parents": parents_dict['parents']}
request_url = "https://www.googleapis.com/drive/v2/files/%s/copy?access_token=%s" % (template_id, content['access_token'])
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
data_json = json.dumps(data)
# resp, content = Http().request(request_url, "POST", data_json, headers)
req = urllib2.Request(request_url, data_json, headers)
content = urllib2.urlopen(req).read()
content = json.loads(content)
res = False
if 'alternateLink' in content.keys():
attach_pool = self.pool.get("ir.attachment")
attach_vals = {'res_model': res_model, 'name': name_gdocs, 'res_id': res_id, 'type': 'url', 'url': content['alternateLink']}
attach_pool.create(cr, uid, attach_vals)
res = content['alternateLink']
record_url = "Click on link to open Record in OpenERP\n %s/?db=%s#id=%s&model=%s" % (google_web_base_url, cr.dbname, res_id, res_model)
data = {"title": name_gdocs, "description": record_url, "parents": parents_dict['parents']}
request_url = "https://www.googleapis.com/drive/v2/files/%s/copy?access_token=%s" % (template_id, access_token)
headers = {'Content-type': 'application/json', 'Accept': 'text/plain'}
data_json = json.dumps(data)
# resp, content = Http().request(request_url, "POST", data_json, headers)
req = urllib2.Request(request_url, data_json, headers)
content = urllib2.urlopen(req).read()
content = json.loads(content)
res = {}
if content.get('alternateLink'):
attach_pool = self.pool.get("ir.attachment")
attach_vals = {'res_model': res_model, 'name': name_gdocs, 'res_id': res_id, 'type': 'url', 'url': content['alternateLink']}
res['id'] = attach_pool.create(cr, uid, attach_vals)
res['url'] = content['alternateLink']
return res
def get_google_drive_config(self, cr, uid, res_model, res_id, context=None):
@ -166,6 +177,7 @@ class config(osv.osv):
'google_drive_resource_id': fields.function(_resource_get, type="char", string='Resource Id'),
'google_drive_client_id': fields.function(_client_id_get, type="char", string='Google Client '),
'name_template': fields.char('Google Drive Name Pattern', size=64, help='Choose how the new google drive will be named, on google side. Eg. gdoc_%(field_name)s', required=True),
'active': fields.boolean('Active'),
}
def onchange_model_id(self, cr, uid, ids, model_id, context=None):
@ -179,6 +191,7 @@ class config(osv.osv):
_defaults = {
'name_template': 'Document %(name)s',
'active': True,
}
def _check_model_id(self, cr, uid, ids, context=None):
@ -191,6 +204,9 @@ class config(osv.osv):
(_check_model_id, 'Model of selected filter is not matching with model of current template.', ['model_id', 'filter_id']),
]
def get_google_scope(self):
return 'https://www.googleapis.com/auth/drive'
config()
@ -202,7 +218,7 @@ class base_config_settings(osv.osv):
'google_drive_uri': fields.char('URI', readonly=True, help="The URL to generate the authorization code from Google"),
}
_defaults = {
'google_drive_uri': lambda s, cr, uid, c: s.pool['google.service']._get_google_token_uri(cr, uid, 'drive', context=c),
'google_drive_uri': lambda s, cr, uid, c: s.pool['google.service']._get_google_token_uri(cr, uid, 'drive', scope=s.pool['google.drive.config'].get_google_scope(), context=c),
}
def set_google_authorization_code(self, cr, uid, ids, context=None):

View File

@ -4,12 +4,12 @@
<record id="config_google_drive_client_id" model="ir.config_parameter">
<field name="key">google_drive_client_id</field>
<field name="value">39623646228-eg3ggo3mk6o40m7rguobi3rkl9frh4tb.apps.googleusercontent.com</field>
<field name="value">598905559630.apps.googleusercontent.com</field>
</record>
<record id="config_google_drive_client_secret" model="ir.config_parameter">
<field name="key">google_drive_client_secret</field>
<field name="value">Ul-PtmnSWs3euWs20fdono0e</field>
<field name="value">vTmou73c-njP-1qCxm7qx7QE</field>
</record>
</data>

View File

@ -8,19 +8,19 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2013-06-27 16:03+0000\n"
"PO-Revision-Date: 2012-12-12 12:51+0000\n"
"Last-Translator: Pedro Manuel Baeza <pedro.baeza@gmail.com>\n"
"PO-Revision-Date: 2013-08-07 02:00+0000\n"
"Last-Translator: Alejandro Santana <alejandrosantana@anubia.es>\n"
"Language-Team: Spanish <es@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-06-28 05:42+0000\n"
"X-Generator: Launchpad (build 16681)\n"
"X-Launchpad-Export-Date: 2013-08-08 04:39+0000\n"
"X-Generator: Launchpad (build 16723)\n"
#. module: google_drive
#: model:ir.ui.menu,name:google_drive.menu_google_drive_config
msgid "Google Drive configuration"
msgstr ""
msgstr "Configuración de Google Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:48
@ -39,6 +39,11 @@ msgid ""
"in your Google Drive and in OpenERP attachment will be named\n"
" 'Agrolait_SO0001_Sales'."
msgstr ""
"El nombre del documento adjunto puede contener información fija o variable. "
"Para distinguir documentos en Google Drive, use palabras y campos fijos. "
"Así, en el ejemplo anterior, si se escribió 'Agrolait_%(name)s_Sales' en el "
"campo de nombre de GoogleDrive, el documento en su Google Drive y en los "
"adjuntos de OpenERP se llamarán 'Agrolait_SO0001_Sales'."
#. module: google_drive
#: view:google.drive.config:0
@ -46,16 +51,18 @@ msgid ""
"- If filter is not specified, link of google document will appear in "
"\"More\" option for all users for all opportunities."
msgstr ""
"- Si no especifica un filtro, el enlace del documento de Google aparecerá en "
"la opción \"Más\" para todos los usuarios para todas las oportunidades."
#. module: google_drive
#: view:google.drive.config:0
msgid "To create a new filter:"
msgstr ""
msgstr "Para crear un nuevo filtro:"
#. module: google_drive
#: model:ir.model,name:google_drive.model_base_config_settings
msgid "base.config.settings"
msgstr ""
msgstr "base.config.settings"
#. module: google_drive
#: model:ir.actions.act_window,help:google_drive.action_google_drive_users_config
@ -73,17 +80,29 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" Pulse para añadir una plantilla.\n"
" </p>\n"
" <p>\n"
" Enlace sus propias plantillas de Google Drive a "
"cualquier registro de OpenERP. Si tiene documentos realmente específicos que "
"quiere que su colaborador rellene (como usar una hoja de cálculo para "
"controlar la calidad de su producto o revisar los detalles de entrega de "
"cada pedido en un país extranjero,...) es muy sencillo gestionarlo. "
"Enlácelos en OpenERP y úselos para colaborar con sus empleados.\n"
" </p>\n"
" "
#. module: google_drive
#: code:addons/google_drive/google_drive.py:150
#, python-format
msgid "Incorrect URL!"
msgstr ""
msgstr "¡URL incorrecta!"
#. module: google_drive
#: view:base.config.settings:0
msgid "Configure your templates"
msgstr ""
msgstr "Configure sus plantillas"
#. module: google_drive
#: help:google.drive.config,name_template:0
@ -91,6 +110,8 @@ msgid ""
"Choose how the new google drive will be named, on google side. Eg. "
"gdoc_%(field_name)s"
msgstr ""
"Elija el nombre de la nueva unidad de Google Drive, en el lado de Google. "
"P.ej.: gdoc_%(field_name)s"
#. module: google_drive
#: view:google.drive.config:0
@ -98,6 +119,8 @@ msgid ""
"- Go to the OpenERP document you want to filter. For instance, go to "
"Opportunities and search on Sales Department."
msgstr ""
"- Vaya al documento de OpenERP que quiera filtrar. Por ejemplo, vaya a "
"Oportunidades y busque en el Departamento de ventas."
#. module: google_drive
#: view:google.drive.config:0
@ -105,6 +128,8 @@ msgid ""
"- In this \"Search\" view, select the option \"Save Current Filter\", enter "
"the name (Ex: Sales Department)"
msgstr ""
"- En esta vista de \"Búsqueda\", seleccione la opción \"Guardar filtro "
"actual\" e introduzca el nombre (p.ej.: Departamento de ventas)"
#. module: google_drive
#: view:google.drive.config:0
@ -113,6 +138,9 @@ msgid ""
"\"More\" options will appear for all users in opportunities of Sales "
"Department."
msgstr ""
"- Si selecciona \"Compartir con todos los usuarios\", el enlace al documento "
"de Google en la opción \"Más\" aparecerá para todos los usuarios en las "
"oportunidades del Departamento de ventas."
#. module: google_drive
#: view:google.drive.config:0
@ -121,23 +149,28 @@ msgid ""
"\"More\" options will not appear for other users in opportunities of Sales "
"Department."
msgstr ""
"- Si no selecciona \"Compartir con todos los usuarios\", el enlace al "
"documento de Google en la opción \"Más\" no aparecerá para otros usuarios en "
"las oportunidades del Departamento de ventas."
#. module: google_drive
#: code:addons/google_drive/google_drive.py:48
#, python-format
msgid "At least one key cannot be found in your Google Drive name pattern"
msgstr ""
"No se ha podido encontrar al menos una clave de su patrón de nombres de "
"Google Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:150
#, python-format
msgid "Please enter a valid Google Document URL."
msgstr ""
msgstr "Por favor, introduzca una URL de documento de Google válida."
#. module: google_drive
#: field:google.drive.config,google_drive_client_id:0
msgid "Google Client "
msgstr ""
msgstr "Cliente de Google "
#. module: google_drive
#: view:google.drive.config:0
@ -145,43 +178,45 @@ msgid ""
"https://docs.google.com/document/d/1vOtpJK9scIQz6taD9tJRIETWbEw3fSiaQHArsJYcu"
"a4/edit"
msgstr ""
"https://docs.google.com/document/d/1vOtpJK9scIQz6taD9tJRIETWbEw3fSiaQHArsJYcu"
"a4/edit"
#. module: google_drive
#: field:google.drive.config,filter_id:0
msgid "Filter"
msgstr ""
msgstr "Filtro"
#. module: google_drive
#: field:google.drive.config,name_template:0
msgid "Google Drive Name Pattern"
msgstr ""
msgstr "Patrón de nombres de Google Drive"
#. module: google_drive
#: help:base.config.settings,google_drive_uri:0
msgid "The URL to generate the authorization code from Google"
msgstr ""
msgstr "La URL para generar el código de autorización de Google"
#. module: google_drive
#: model:ir.filters,name:google_drive.filter_partner
msgid "Customer"
msgstr ""
msgstr "Cliente"
#. module: google_drive
#: field:google.drive.config,google_drive_resource_id:0
msgid "Resource Id"
msgstr ""
msgstr "ID del recurso"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:91
#, python-format
msgid "The Google Template cannot be found. Maybe it has been deleted."
msgstr ""
msgstr "No se encuentra la plantilla de Google. Tal vez se haya eliminado."
#. module: google_drive
#: model:ir.actions.act_window,name:google_drive.action_google_drive_users_config
#: model:ir.ui.menu,name:google_drive.menu_google_drive_model_config
msgid "Google Drive Templates"
msgstr ""
msgstr "Plantillas de Google Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:81
@ -190,23 +225,27 @@ msgid ""
"Something went wrong during the token generation. Please request again an "
"authorization code in %(menu:base_setup.menu_general_configuration)s."
msgstr ""
"Algo ha ido mal durante la creación del testigo. Por favor, solicite un "
"código de autorización otra vez en "
"%(menu:base_setup.menu_general_configuration)s."
#. module: google_drive
#: code:addons/google_drive/google_drive.py:124
#, python-format
msgid "Google Drive Error!"
msgstr ""
msgstr "¡Error de Google Drive!"
#. module: google_drive
#: field:base.config.settings,google_drive_uri:0
msgid "URI"
msgstr ""
msgstr "URI"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:124
#, python-format
msgid "Creating google drive may only be done by one at a time."
msgstr ""
"Es posible que la creación de Google Drive sólo se pueda hacer de una en una."
#. module: google_drive
#: field:google.drive.config,model:0
@ -217,38 +256,40 @@ msgstr "Modelo"
#. module: google_drive
#: view:google.drive.config:0
msgid "Google Drive Configuration"
msgstr ""
msgstr "Configuración de Google Drive"
#. module: google_drive
#: field:google.drive.config,name:0
msgid "Template Name"
msgstr ""
msgstr "Nombre de la plantilla"
#. module: google_drive
#: constraint:google.drive.config:0
msgid ""
"Model of selected filter is not matching with model of current template."
msgstr ""
"El modelo del filtro seleccionado no coincide con el modelo de la plantilla "
"actual."
#. module: google_drive
#: field:google.drive.config,google_drive_template_url:0
msgid "Template URL"
msgstr ""
msgstr "URL de la plantilla"
#. module: google_drive
#: view:base.config.settings:0
msgid "and paste it here"
msgstr ""
msgstr "y péguela aquí"
#. module: google_drive
#: field:base.config.settings,google_drive_authorization_code:0
msgid "Authorization Code"
msgstr ""
msgstr "Cídigo de autorización"
#. module: google_drive
#: model:ir.model,name:google_drive.model_google_drive_config
msgid "Google Drive templates config"
msgstr ""
msgstr "Configuración de plantillas de Google Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:64
@ -257,3 +298,6 @@ msgid ""
"You haven't configured 'Authorization Code' generated from google, Please "
"generate and configure it in %(menu:base_setup.menu_general_configuration)s."
msgstr ""
"No ha configurado el 'código de autorización' generado por Google. Por "
"favor, genérelo y configúrelo en "
"%(menu:base_setup.menu_general_configuration)s."

View File

@ -8,20 +8,19 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2013-06-27 16:03+0000\n"
"PO-Revision-Date: 2012-12-07 22:47+0000\n"
"Last-Translator: Fábio Martinelli - http://zupy.com.br "
"<webmaster@guaru.net>\n"
"PO-Revision-Date: 2013-07-20 22:32+0000\n"
"Last-Translator: Claudio de Araujo Santos <claudioaraujosantos@gmail.com>\n"
"Language-Team: Brazilian Portuguese <pt_BR@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-06-28 05:42+0000\n"
"X-Generator: Launchpad (build 16681)\n"
"X-Launchpad-Export-Date: 2013-07-22 05:34+0000\n"
"X-Generator: Launchpad (build 16696)\n"
#. module: google_drive
#: model:ir.ui.menu,name:google_drive.menu_google_drive_config
msgid "Google Drive configuration"
msgstr ""
msgstr "Configuração do Google Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:48
@ -40,6 +39,13 @@ msgid ""
"in your Google Drive and in OpenERP attachment will be named\n"
" 'Agrolait_SO0001_Sales'."
msgstr ""
"O nome do documento em anexo pode usar dados fixos ou variáveis. Para "
"distinguir entre documentos\n"
" Google Drive, usar palavras fixos e campos. "
"Por exemplo, no exemplo acima, se escreveu Agrolait_% (nome) s_Sales\n"
" no campo Nome da unidade do Google, o "
"documento em seu Google Drive e em OpenERP anexo será nomeado\n"
" 'Agrolait_SO0001_Sales \"."
#. module: google_drive
#: view:google.drive.config:0
@ -47,16 +53,18 @@ msgid ""
"- If filter is not specified, link of google document will appear in "
"\"More\" option for all users for all opportunities."
msgstr ""
"- Se o filtro não for especificado, ligação de documento google aparecerá na "
"opção \"Mais\" para todos os usuários de todas as oportunidades."
#. module: google_drive
#: view:google.drive.config:0
msgid "To create a new filter:"
msgstr ""
msgstr "Para criar um novo filtro:"
#. module: google_drive
#: model:ir.model,name:google_drive.model_base_config_settings
msgid "base.config.settings"
msgstr ""
msgstr "base.config.settings"
#. module: google_drive
#: model:ir.actions.act_window,help:google_drive.action_google_drive_users_config
@ -74,17 +82,30 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" Clique para adicionar um novo modelo.\n"
" </ P>\n"
" <p>\n"
" Vincular seus próprios modelos Google Drive para "
"qualquer registro de OpenERP. Se você tem documentos específicos que você "
"realmente deseja que seu colaborador preencher, por exemplo, usar uma "
"planilha para controlar a qualidade do seu produto ou rever a lista de "
"entrega para cada ordem em um país estrangeiro, ... É muito fácil de "
"gerenciá-los, associá-los a OpenERP e usá-los para colaborar com seus "
"empregados.\n"
" </ P>\n"
" "
#. module: google_drive
#: code:addons/google_drive/google_drive.py:150
#, python-format
msgid "Incorrect URL!"
msgstr ""
msgstr "URL incorreto!"
#. module: google_drive
#: view:base.config.settings:0
msgid "Configure your templates"
msgstr ""
msgstr "Configure seus modelos"
#. module: google_drive
#: help:google.drive.config,name_template:0
@ -92,6 +113,8 @@ msgid ""
"Choose how the new google drive will be named, on google side. Eg. "
"gdoc_%(field_name)s"
msgstr ""
"Escolha como a nova unidade google será nomeado, no lado google. Por "
"exemplo. gdoc_% (field_name) s"
#. module: google_drive
#: view:google.drive.config:0
@ -99,6 +122,8 @@ msgid ""
"- Go to the OpenERP document you want to filter. For instance, go to "
"Opportunities and search on Sales Department."
msgstr ""
"- Vá para o documento OpenERP você deseja filtrar. Por exemplo, ir para "
"Oportunidades e de pesquisa no Departamento de Vendas."
#. module: google_drive
#: view:google.drive.config:0
@ -106,6 +131,8 @@ msgid ""
"- In this \"Search\" view, select the option \"Save Current Filter\", enter "
"the name (Ex: Sales Department)"
msgstr ""
"- Neste ponto de vista \"Pesquisar\", selecione a opção \"Salvar Filtro "
"Atual\", digite o nome (Ex: Departamento de Vendas)"
#. module: google_drive
#: view:google.drive.config:0
@ -114,6 +141,9 @@ msgid ""
"\"More\" options will appear for all users in opportunities of Sales "
"Department."
msgstr ""
"- Se você selecionar \"Compartilhar com todos os usuários\", ligação de "
"documento google na opção \"Mais\" será exibido para todos os usuários em "
"oportunidades do Departamento de Vendas."
#. module: google_drive
#: view:google.drive.config:0
@ -122,23 +152,28 @@ msgid ""
"\"More\" options will not appear for other users in opportunities of Sales "
"Department."
msgstr ""
"- Se você não selecionar \"Compartilhar com todos os usuários\", link do "
"documento google em \"More\" opções não aparecerá para outros usuários em "
"oportunidades do Departamento de Vendas."
#. module: google_drive
#: code:addons/google_drive/google_drive.py:48
#, python-format
msgid "At least one key cannot be found in your Google Drive name pattern"
msgstr ""
"Pelo menos uma chave não pode ser encontrado em seu padrão de nome de Google "
"Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:150
#, python-format
msgid "Please enter a valid Google Document URL."
msgstr ""
msgstr "Por favor insira um URL Documento Google válida."
#. module: google_drive
#: field:google.drive.config,google_drive_client_id:0
msgid "Google Client "
msgstr ""
msgstr "Google Cliente "
#. module: google_drive
#: view:google.drive.config:0
@ -146,43 +181,46 @@ msgid ""
"https://docs.google.com/document/d/1vOtpJK9scIQz6taD9tJRIETWbEw3fSiaQHArsJYcu"
"a4/edit"
msgstr ""
"https://docs.google.com/document/d/1vOtpJK9scIQz6taD9tJRIETWbEw3fSiaQHArsJYcu"
"a4/edit"
#. module: google_drive
#: field:google.drive.config,filter_id:0
msgid "Filter"
msgstr ""
msgstr "Filtro"
#. module: google_drive
#: field:google.drive.config,name_template:0
msgid "Google Drive Name Pattern"
msgstr ""
msgstr "Nome Padrão Google Drive"
#. module: google_drive
#: help:base.config.settings,google_drive_uri:0
msgid "The URL to generate the authorization code from Google"
msgstr ""
msgstr "O URL para gerar o código de autorização do Google"
#. module: google_drive
#: model:ir.filters,name:google_drive.filter_partner
msgid "Customer"
msgstr ""
msgstr "Cliente"
#. module: google_drive
#: field:google.drive.config,google_drive_resource_id:0
msgid "Resource Id"
msgstr ""
msgstr "ID Recurso"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:91
#, python-format
msgid "The Google Template cannot be found. Maybe it has been deleted."
msgstr ""
"O modelo do Google não pode ser encontrado. Talvez tenha sido excluído."
#. module: google_drive
#: model:ir.actions.act_window,name:google_drive.action_google_drive_users_config
#: model:ir.ui.menu,name:google_drive.menu_google_drive_model_config
msgid "Google Drive Templates"
msgstr ""
msgstr "Templates do Google Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:81
@ -191,23 +229,25 @@ msgid ""
"Something went wrong during the token generation. Please request again an "
"authorization code in %(menu:base_setup.menu_general_configuration)s."
msgstr ""
"Algo deu errado durante a geração de token. Por favor, solicite novamente um "
"código de autorização em%(menu:base_setup.menu_general_configuration)s."
#. module: google_drive
#: code:addons/google_drive/google_drive.py:124
#, python-format
msgid "Google Drive Error!"
msgstr ""
msgstr "Google Drive Error!"
#. module: google_drive
#: field:base.config.settings,google_drive_uri:0
msgid "URI"
msgstr ""
msgstr "URL"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:124
#, python-format
msgid "Creating google drive may only be done by one at a time."
msgstr ""
msgstr "Criação de unidade google só pode ser feito por um de cada vez."
#. module: google_drive
#: field:google.drive.config,model:0
@ -218,38 +258,40 @@ msgstr "Modelo"
#. module: google_drive
#: view:google.drive.config:0
msgid "Google Drive Configuration"
msgstr ""
msgstr "Configuração do acionamento Google"
#. module: google_drive
#: field:google.drive.config,name:0
msgid "Template Name"
msgstr ""
msgstr "Nome do modelo"
#. module: google_drive
#: constraint:google.drive.config:0
msgid ""
"Model of selected filter is not matching with model of current template."
msgstr ""
"Modelo de filtro selecionado não está combinando com o modelo do modelo "
"atual."
#. module: google_drive
#: field:google.drive.config,google_drive_template_url:0
msgid "Template URL"
msgstr ""
msgstr "Modelo de URL"
#. module: google_drive
#: view:base.config.settings:0
msgid "and paste it here"
msgstr ""
msgstr "e cole-o aqui"
#. module: google_drive
#: field:base.config.settings,google_drive_authorization_code:0
msgid "Authorization Code"
msgstr ""
msgstr "Código de Autorização"
#. module: google_drive
#: model:ir.model,name:google_drive.model_google_drive_config
msgid "Google Drive templates config"
msgstr ""
msgstr "Google Drive templates de configuração"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:64
@ -258,3 +300,6 @@ msgid ""
"You haven't configured 'Authorization Code' generated from google, Please "
"generate and configure it in %(menu:base_setup.menu_general_configuration)s."
msgstr ""
"Você não configurou \"Código de Autorização\" gerado a partir do Google, por "
"favor, gerar e configurá-lo em "
"%(menu:base_setup.menu_general_configuration)s."

View File

@ -23,6 +23,7 @@
<field name="model" invisible="1" />
<group>
<field name="name" />
<field name="active" />
<field name="model_id" on_change="onchange_model_id(model_id)" />
<label for='filter_id' />
<div>
@ -54,7 +55,7 @@
</record>
<record model='ir.actions.act_window' id='action_google_drive_users_config'>
<field name='name'>Google Drive Templates</field>
<field name='name'>Templates</field>
<field name='res_model'>google.drive.config</field>
<field name='type'>ir.actions.act_window</field>
<field name='view_type'>form</field>
@ -87,7 +88,7 @@
</field>
</record>
<menuitem name='Google Drive configuration' id='menu_google_drive_config' parent='base.menu_administration' />
<menuitem name='Google Drive' id='menu_google_drive_config' parent='base.menu_administration' />
<menuitem id='menu_google_drive_model_config' parent='menu_google_drive_config' action='action_google_drive_users_config' />
</data>
</openerp>

View File

@ -29,19 +29,24 @@ openerp.google_drive = function (instance, m) {
ds.call('get_google_drive_config', [view.dataset.model, res_id, context]).done(function (r) {
if (!_.isEmpty(r)) {
_.each(r, function (res) {
var g_item = _.indexOf(_.pluck(self.items.other, 'label'), res.name);
if (g_item !== -1) {
self.items.other.splice(g_item, 1);
var already_there = false;
for (var i = 0;i < self.items.other.length;i++){
if (self.items.other[i].classname === "oe_share_gdoc" && self.items.other[i].label.indexOf(res.name) > -1){
already_there = true;
break;
}
}
if (!already_there){
self.add_items('other', [{
label: res.name+ '<img style="position:absolute;right:5px;height:20px;width:20px;" title="Google Drive" src="google_drive/static/src/img/drive_icon.png"/>',
config_id: res.id,
res_id: res_id,
res_model: view.dataset.model,
callback: self.on_google_doc,
classname: 'oe_share_gdoc'
},
]);
}
self.add_items('other', [{
label: res.name+ '<img style="position:absolute;right:5px;height:20px;width:20px;" title="Google Drive" src="google_drive/static/src/img/drive_icon.png"/>',
config_id: res.id,
res_id: res_id,
res_model: view.dataset.model,
callback: self.on_google_doc,
classname: 'oe_share_gdoc'
},
]);
})
}
});

View File

@ -0,0 +1 @@
import google_spreadsheet

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
{
'name': 'Google Spreadsheet',
'version': '1.0',
'category': 'Tools',
'description': """
The module adds the possibility to display data from OpenERP in Google Spreadsheets in real time.
========================================
""",
'author': 'OpenERP SA',
'website': 'http://www.openerp.com',
'depends': ['board', 'google_drive'],
'js': [
'static/src/js/search.js',
],
'qweb': ['static/src/xml/*.xml'],
'data': ['google_spreadsheet_view.xml', 'google_spreadsheet_data.xml'],
'demo': [],
'installable': True,
'auto_install': False,
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,119 @@
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2012 OpenERP SA (<http://www.openerp.com>).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
import simplejson
import logging
from lxml import etree
import re
import urllib
import urllib2
from openerp.osv import osv
from openerp import SUPERUSER_ID
_logger = logging.getLogger(__name__)
class config(osv.osv):
_inherit = 'google.drive.config'
def get_google_scope(self):
scope = super(config, self).get_google_scope()
return '%s https://spreadsheets.google.com/feeds' % scope
def write_config_formula(self, cr, uid, attachment_id, spreadsheet_key, model, domain, groupbys, view_id, context=None):
access_token = self.get_access_token(cr, uid, scope='https://spreadsheets.google.com/feeds', context=context)
fields = self.pool.get(model).fields_view_get(cr, uid, view_id=view_id, view_type='tree')
doc = etree.XML(fields.get('arch'))
display_fields = []
for node in doc.xpath("//field"):
if node.get('modifiers'):
modifiers = simplejson.loads(node.get('modifiers'))
if not modifiers.get('invisible') and not modifiers.get('tree_invisible'):
display_fields.append(node.get('name'))
fields = " ".join(display_fields)
domain = domain.replace("'", r"\'").replace('"', "'")
if groupbys:
fields = "%s %s" % (groupbys, fields)
formula = '=oe_read_group("%s";"%s";"%s";"%s")' % (model, fields, groupbys, domain)
else:
formula = '=oe_browse("%s";"%s";"%s")' % (model, fields, domain)
url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url')
dbname = cr.dbname
user = self.pool['res.users'].read(cr, uid, uid, ['login', 'password'], context=context)
username = user['login']
password = user['password']
if self.pool['ir.module.module'].search_count(cr, SUPERUSER_ID, ['&', ('name', '=', 'auth_crypt'), ('state', '=', 'installed')]) == 1:
config_formula = '=oe_settings("%s";"%s")' % (url, dbname)
else:
config_formula = '=oe_settings("%s";"%s";"%s";"%s")' % (url, dbname, username, password)
request = '''<feed xmlns="http://www.w3.org/2005/Atom"
xmlns:batch="http://schemas.google.com/gdata/batch"
xmlns:gs="http://schemas.google.com/spreadsheets/2006">
<id>https://spreadsheets.google.com/feeds/cells/%s/od6/private/full</id>
<entry>
<batch:id>A1</batch:id>
<batch:operation type="update"/>
<id>https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R1C1</id>
<link rel="edit" type="application/atom+xml"
href="https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R1C1"/>
<gs:cell row="1" col="1" inputValue="%s"/>
</entry>
<entry>
<batch:id>A2</batch:id>
<batch:operation type="update"/>
<id>https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R60C15</id>
<link rel="edit" type="application/atom+xml"
href="https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/R60C15"/>
<gs:cell row="60" col="15" inputValue="%s"/>
</entry>
</feed>''' % (spreadsheet_key, spreadsheet_key, spreadsheet_key, formula.replace('"', '&quot;'), spreadsheet_key, spreadsheet_key, config_formula.replace('"', '&quot;'))
try:
req = urllib2.Request(
'https://spreadsheets.google.com/feeds/cells/%s/od6/private/full/batch?%s' % (spreadsheet_key, urllib.urlencode({'v': 3, 'access_token': access_token})),
data=request,
headers={'content-type': 'application/atom+xml', 'If-Match': '*'})
urllib2.urlopen(req)
except (urllib2.HTTPError, urllib2.URLError):
_logger.warning("An error occured while writting the formula on the Google Spreadsheet.")
description = '''
formula: %s
''' % formula
if attachment_id:
self.pool['ir.attachment'].write(cr, uid, attachment_id, {'description': description}, context=context)
return True
def set_spreadsheet(self, cr, uid, model, domain, groupbys, view_id, context=None):
try:
config_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'google_spreadsheet', 'google_spreadsheet_template')[1]
except ValueError:
raise
config = self.browse(cr, uid, config_id, context=context)
title = 'Spreadsheet %s' % model
res = self.copy_doc(cr, uid, False, config.google_drive_resource_id, title, model, context=context)
mo = re.search("(key=|/d/)([A-Za-z0-9-_]+)", res['url'])
if mo:
key = mo.group(2)
self.write_config_formula(cr, uid, res.get('id'), key, model, domain, groupbys, view_id, context=context)
return res

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<record id="google_spreadsheet_template" model="google.drive.config">
<field name="name">Base Spreadsheet Template</field>
<field name="model_id" ref="base.model_res_partner"/>
<field name="google_drive_template_url">https://docs.google.com/spreadsheet/ccc?key=0ApGVjjwUC-ygdDZ0TG5EQnRlLVFQNlFGdFN5b1ZrY1E</field>
<field name="name_template">Reporting %(name)s</field>
<field name="active" eval="0" />
</record>
</data>
</openerp>

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="utf-8"?>
<openerp>
<data>
<!-- add google drive config field in user form -->
<record model="ir.ui.view" id="view_ir_attachment_google_spreadsheet_tree">
<field name="name">ir.attachment.google.spreadsheet.tree</field>
<field name="model">ir.attachment</field>
<field name="arch" type="xml">
<tree string="Google Spreadsheets" version="7.0">
<field name="name" string="Name"/>
<field name="url" />
</tree>
</field>
</record>
<record model="ir.ui.view" id="view_ir_attachment_google_spreadsheet_form">
<field name="name">ir.attachment.google.spreadsheet.form</field>
<field name="model">ir.attachment</field>
<field name="arch" type="xml">
<form string="Google Spreadsheets" version="7.0">
<sheet>
<group>
<group>
<field name="name" string="Name"/>
<field name="url" widget="url"/>
</group>
<group colspan="2">
<label for="description" colspan="2"/>
<field name="description" nolabel="1" colspan="2"/>
</group>
</group>
</sheet>
</form>
</field>
</record>
<record id="action_ir_attachment_google_spreadsheet_tree" model="ir.actions.act_window">
<field name="name">Google Spreadsheets</field>
<field name="res_model">ir.attachment</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{}</field>
<field name="domain">[('url', 'like', '/spreadsheet/')]</field>
<field name="help">Google Spreadsheets</field>
</record>
<record id="action_ir_attachment_google_spreadsheet_tree_view" model="ir.actions.act_window.view">
<field eval="1" name="sequence"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_ir_attachment_google_spreadsheet_tree"/>
<field name="act_window_id" ref="action_ir_attachment_google_spreadsheet_tree"/>
</record>
<record id="action_ir_attachment_google_spreadsheet_form_view" model="ir.actions.act_window.view">
<field eval="2" name="sequence"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_ir_attachment_google_spreadsheet_form"/>
<field name="act_window_id" ref="action_ir_attachment_google_spreadsheet_tree"/>
</record>
<menuitem id="menu_reporting_dashboard_google_spreadsheets" parent="base.menu_reporting_dashboard" action="action_ir_attachment_google_spreadsheet_tree"/>
</data>
</openerp>

View File

@ -0,0 +1,42 @@
# Translation of OpenERP Server.
# This file contains the translation of the following modules:
# * google_spreadsheet
#
msgid ""
msgstr ""
"Project-Id-Version: OpenERP Server 8.0alpha1\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2013-07-25 12:39+0000\n"
"PO-Revision-Date: 2013-07-25 12:39+0000\n"
"Last-Translator: <>\n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: \n"
"Plural-Forms: \n"
#. module: google_spreadsheet
#: model:ir.actions.act_window,help:google_spreadsheet.action_ir_attachment_google_spreadsheet_tree
#: model:ir.actions.act_window,name:google_spreadsheet.action_ir_attachment_google_spreadsheet_tree
#: view:ir.attachment:0
#: model:ir.ui.menu,name:google_spreadsheet.menu_reporting_dashboard_google_spreadsheets
msgid "Google Spreadsheets"
msgstr ""
#. module: google_spreadsheet
#. openerp-web
#: code:addons/google_spreadsheet/static/src/xml/addtospreadsheet.xml:3
#, python-format
msgid "Add to Google Spreadsheet"
msgstr ""
#. module: google_spreadsheet
#: view:ir.attachment:0
msgid "Name"
msgstr ""
#. module: google_spreadsheet
#: model:ir.model,name:google_spreadsheet.model_google_drive_config
msgid "Google Drive templates config"
msgstr ""

View File

@ -0,0 +1,54 @@
openerp.google_spreadsheet = function(instance) {
var _t = instance.web._t;
instance.web.FormView.include({
on_processed_onchange: function(result, processed) {
var self = this;
var fields = self.fields;
_(result.selection).each(function (selection, fieldname) {
var field = fields[fieldname];
if (!field) { return; }
field.field.selection = selection;
field.values = selection;
field.renderElement();
});
return this._super(result, processed);
},
});
instance.board.AddToGoogleSpreadsheet = instance.web.search.Input.extend({
template: 'SearchView.addtogooglespreadsheet',
_in_drawer: true,
start: function () {
var self = this;
this.$el.on('click', 'h4', function(){
var view = self.view;
var data = view.build_search_data();
var model = view.model;
var list_view = self.view.getParent().views['list'];
var view_id = list_view ? list_view.view_id : false;
var context = new instance.web.CompoundContext(view.dataset.get_context() || []);
var domain = new instance.web.CompoundDomain(view.dataset.get_domain() || []);
_.each(data.contexts, context.add, context);
_.each(data.domains, domain.add, domain);
domain = JSON.stringify(domain.eval());
var groupbys = instance.web.pyeval.eval('groupbys', data.groupbys).join(" ");
var view_id = view_id;
var ds = new instance.web.DataSet(self, 'google.drive.config');
ds.call('set_spreadsheet', [model, domain, groupbys, view_id]).done(function (res) {
if (res['url']){
window.open(res['url'], '_blank');
}
});
});
},
});
instance.web.SearchView.include({
add_common_inputs: function() {
this._super();
var vm = this.getParent().getParent();
if (vm.inner_action && vm.inner_action.views) {
(new instance.board.AddToGoogleSpreadsheet(this));
}
}
});
};

View File

@ -0,0 +1,5 @@
<template>
<div t-name="SearchView.addtogooglespreadsheet" class="oe_searchview_dashboard">
<h4>Add to Google Spreadsheet</h4>
</div>
</template>

View File

@ -51,9 +51,9 @@ class hr_employee_category(osv.osv):
_name = "hr.employee.category"
_description = "Employee Category"
_columns = {
'name': fields.char("Category", size=64, required=True),
'name': fields.char("Employee Tag", size=64, required=True),
'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
'parent_id': fields.many2one('hr.employee.category', 'Parent Category', select=True),
'parent_id': fields.many2one('hr.employee.category', 'Parent Employee Tag', select=True),
'child_ids': fields.one2many('hr.employee.category', 'parent_id', 'Child Categories'),
'employee_ids': fields.many2many('hr.employee', 'employee_category_rel', 'category_id', 'emp_id', 'Employees'),
}
@ -129,7 +129,7 @@ class hr_job(osv.osv):
}
_sql_constraints = [
('name_company_uniq', 'unique(name, company_id)', 'The name of the job position must be unique per company!'),
('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
]
@ -156,6 +156,8 @@ class hr_employee(osv.osv):
_inherits = {'resource.resource': "resource_id"}
_inherit = ['mail.thread']
_mail_post_access = 'read'
def _get_image(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
@ -324,22 +326,6 @@ class hr_employee(osv.osv):
(_check_recursion, 'Error! You cannot create recursive hierarchy of Employee(s).', ['parent_id']),
]
# ---------------------------------------------------
# Mail gateway
# ---------------------------------------------------
def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
""" mail.message document permission rule: can post a new message if can read
because of portal document. """
if not model_obj:
model_obj = self
employee_ids = model_obj.search(cr, uid, [('user_id', '=', uid)], context=context)
if employee_ids and operation == 'create':
model_obj.check_access_rights(cr, uid, 'read')
model_obj.check_access_rule(cr, uid, mids, 'read', context=context)
else:
return super(hr_employee, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
class hr_department(osv.osv):
_description = "Department"

View File

@ -79,7 +79,7 @@ class hr_holidays_status(osv.osv):
'categ_id': fields.many2one('crm.meeting.type', 'Meeting Type',
help='Once a leave is validated, OpenERP will create a corresponding meeting of this type in the calendar.'),
'color_name': fields.selection([('red', 'Red'),('blue','Blue'), ('lightgreen', 'Light Green'), ('lightblue','Light Blue'), ('lightyellow', 'Light Yellow'), ('magenta', 'Magenta'),('lightcyan', 'Light Cyan'),('black', 'Black'),('lightpink', 'Light Pink'),('brown', 'Brown'),('violet', 'Violet'),('lightcoral', 'Light Coral'),('lightsalmon', 'Light Salmon'),('lavender', 'Lavender'),('wheat', 'Wheat'),('ivory', 'Ivory')],'Color in Report', required=True, help='This color will be used in the leaves summary located in Reporting\Leaves by Department.'),
'limit': fields.boolean('Allow to Override Limit', help='If you select this check box, the system allows the employees to take more leaves than the available ones for this type and take them into account for the "Remaining Legal Leaves" defined on the employee form.'),
'limit': fields.boolean('Allow to Override Limit', help='If you select this check box, the system allows the employees to take more leaves than the available ones for this type and will not take them into account for the "Remaining Legal Leaves" defined on the employee form.'),
'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the leave type without removing it."),
'max_leaves': fields.function(_user_left_days, string='Maximum Allowed', help='This value is given by the sum of all holidays requests with a positive value.', multi='user_left_days'),
'leaves_taken': fields.function(_user_left_days, string='Leaves Already Taken', help='This value is given by the sum of all holidays requests with a negative value.', multi='user_left_days'),

View File

@ -35,7 +35,7 @@
new_id = self.create(cr, uid, {'account_id': ref('account.analytic_nebula'),'analytic_amount': 7.0,
'date': (datetime.now()+timedelta(1)).strftime('%Y-%m-%d %H:%M:%S') ,
'date_start': time.strftime('%Y-%m-%d %H:%M:%S'), 'info': 'Create Yaml for hr module',
'name': 'Quentin Paolino', 'server_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'state': 'action'})
'name': 'Quentin Paolino', 'server_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'state': 'present'})
self.sign_out_result(cr, uid, [new_id], context)
-
My work for this project "Sednacom" is over and I stop working by clicking on "Stop Work" button of this wizard.

View File

@ -32,8 +32,8 @@ class hr_so_project(osv.osv_memory):
'date_start': fields.datetime('Starting Date', readonly=True),
'date': fields.datetime('Closing Date'),
'analytic_amount': fields.float('Minimum Analytic Amount'),
'name': fields.char('Employees name', size=32, required=True, readonly=True),
'state': fields.related('emp_id', 'state', string='Current Status', type='char', required=True, readonly=True),
'name': fields.char('Employee\'s Name', size=32, required=True, readonly=True),
'state': fields.related('emp_id', 'state', string='Current Status', type='selection', selection=[('present', 'Present'), ('absent', 'Absent')], required=True, readonly=True),
'server_date': fields.datetime('Current Date', required=True, readonly=True),
'emp_id': fields.many2one('hr.employee', 'Employee ID')
}
@ -109,8 +109,8 @@ class hr_si_project(osv.osv_memory):
_name = 'hr.sign.in.project'
_description = 'Sign In By Project'
_columns = {
'name': fields.char('Employees name', size=32, readonly=True),
'state': fields.related('emp_id', 'state', string='Current Status', type='char', required=True, readonly=True),
'name': fields.char('Employee\'s Name', size=32, readonly=True),
'state': fields.related('emp_id', 'state', string='Current Status', type='selection', selection=[('present', 'Present'), ('absent', 'Absent')], required=True, readonly=True),
'date': fields.datetime('Starting Date'),
'server_date': fields.datetime('Current Date', readonly=True),
'emp_id': fields.many2one('hr.employee', 'Employee ID')

View File

@ -43,7 +43,7 @@
</record>
<record id="action_hr_timesheet_sign_in" model="ir.actions.act_window">
<field name="name">Sign in / Sign out by project</field>
<field name="name">Sign in / Sign out by Project</field>
<field name="res_model">hr.sign.in.project</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
@ -67,7 +67,7 @@
<field name="account_id" colspan="2"/>
<field name="info" colspan="2"/>
<field name="date"/>
<label string="(Keep empty for current_time)" colspan="2"/>
<label string="(Keep empty for current time)" colspan="2"/>
<field name="analytic_amount"/>
</group>
@ -82,7 +82,7 @@
</record>
<record id="action_hr_timesheet_sign_out" model="ir.actions.act_window">
<field name="name">Sign in / Sign out by project</field>
<field name="name">Sign in / Sign out by Project</field>
<field name="res_model">hr.sign.out.project</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>

View File

@ -0,0 +1,169 @@
# Chinese (Simplified) translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 01:06+0000\n"
"PO-Revision-Date: 2013-07-24 12:29+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (Simplified) <zh_CN@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-07-25 05:13+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_fiscal_position_template
msgid "Template for Fiscal Position"
msgstr "财务结构模板"
#. module: l10n_multilang
#: sql_constraint:account.account:0
msgid "The code of the account must be unique per company !"
msgstr "该科目的代码,每家公司必须是唯一的!"
#. module: l10n_multilang
#: constraint:account.account.template:0
msgid ""
"Configuration Error!\n"
"You can not define children to an account with internal type different of "
"\"View\"! "
msgstr ""
"配置错误! \n"
"上级科目必须是“视图”类型的科目! "
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_analytic_journal
msgid "Analytic Journal"
msgstr "辅助核算类型"
#. module: l10n_multilang
#: constraint:account.account.template:0
msgid "Error ! You can not create recursive account templates."
msgstr "错误!您不能创建递归的科目模板。"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_journal
msgid "Journal"
msgstr "分录序时薄"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_chart_template
msgid "Templates for Account Chart"
msgstr "科目一览表模板"
#. module: l10n_multilang
#: sql_constraint:account.tax:0
msgid "The description must be unique per company!"
msgstr "此说明必须是每个公司唯一的"
#. module: l10n_multilang
#: constraint:account.tax.code.template:0
msgid "Error ! You can not create recursive Tax Codes."
msgstr "错误!您不能创建递归的税编码。"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_tax_template
msgid "account.tax.template"
msgstr "account.tax.template"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_tax
msgid "account.tax"
msgstr "account.tax"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_account
msgid "Account"
msgstr "科目"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_wizard_multi_charts_accounts
msgid "wizard.multi.charts.accounts"
msgstr "wizard.multi.charts.accounts"
#. module: l10n_multilang
#: constraint:account.journal:0
msgid ""
"Configuration error! The currency chosen should be shared by the default "
"accounts too."
msgstr "设置错误!所选币种应与默认科目共享。"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_account_template
msgid "Templates for Accounts"
msgstr "科目模板"
#. module: l10n_multilang
#: help:account.chart.template,spoken_languages:0
msgid ""
"State here the languages for which the translations of templates could be "
"loaded at the time of installation of this localization module and copied in "
"the final object when generating them from templates. You must provide the "
"language codes separated by ';'"
msgstr ""
#. module: l10n_multilang
#: constraint:account.account:0
msgid "Error ! You can not create recursive accounts."
msgstr "错误!您不能创建递归的科目"
#. module: l10n_multilang
#: constraint:account.account:0
msgid ""
"Configuration Error! \n"
"You can not select an account type with a deferral method different of "
"\"Unreconciled\" for accounts with internal type \"Payable/Receivable\"! "
msgstr ""
"配置错误!\n"
"对于内部类型是‘应收/应付’的会计科目,你需要选择结转方式是‘未核销’的科目类型。 "
#. module: l10n_multilang
#: sql_constraint:account.journal:0
msgid "The name of the journal must be unique per company !"
msgstr "每个公司的日记账名称必须唯一!"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_analytic_account
msgid "Analytic Account"
msgstr "辅助核算项目"
#. module: l10n_multilang
#: sql_constraint:account.journal:0
msgid "The code of the journal must be unique per company !"
msgstr "每个公司的日记账代码必须唯一!"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_fiscal_position
msgid "Fiscal Position"
msgstr "财务结构"
#. module: l10n_multilang
#: constraint:account.account:0
msgid ""
"Configuration Error! \n"
"You can not define children to an account with internal type different of "
"\"View\"! "
msgstr ""
"配置错误! \n"
"您不能给 非“视图”类型的科目定义一个子科目。 "
#. module: l10n_multilang
#: constraint:account.analytic.account:0
msgid "Error! You can not create recursive analytic accounts."
msgstr "错误! 你不能创建递归的辅助核算项目"
#. module: l10n_multilang
#: model:ir.model,name:l10n_multilang.model_account_tax_code_template
msgid "Tax Code Template"
msgstr "税编码模板"
#. module: l10n_multilang
#: field:account.chart.template,spoken_languages:0
msgid "Spoken Languages"
msgstr "交流语言"

View File

@ -0,0 +1,17 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- added support of ``active_domain`` form context, coming from the list view.
When checking the header hook, the mass mailing will be done on all records
matching the ``active_domain``.
- added ``mail_server_id`` to mail_message, removing it from mail_mail. This allows
to set the preferred mail server to use for notifications emails, when using
templates.
- added ``_mail_post_access`` attribute that specifies the access right that
should have the user in order to post a new message on a given model. Values
are ``read`` (portal documents), ``write`` (default value), ``unlink`` or ``create``.

View File

@ -12,3 +12,12 @@ Mail Module documentation topics
mail_partner
mail_state
mail_subtype
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

1687
addons/mail/i18n/bs.po Normal file

File diff suppressed because it is too large Load Diff

View File

@ -151,7 +151,7 @@ class mail_notification(osv.Model):
return footer
def _notify(self, cr, uid, msg_id, partners_to_notify=None, context=None,
force_send=False, user_signature=True):
force_send=False, user_signature=True):
""" Send by email the notification depending on the user preferences
:param list partners_to_notify: optional list of partner ids restricting

View File

@ -44,7 +44,6 @@ class mail_mail(osv.Model):
_columns = {
'mail_message_id': fields.many2one('mail.message', 'Message', required=True, ondelete='cascade'),
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
'state': fields.selection([
('outgoing', 'Outgoing'),
('sent', 'Sent'),
@ -140,10 +139,16 @@ class mail_mail(osv.Model):
# notification field: if not set, set if mail comes from an existing mail.message
if 'notification' not in values and values.get('mail_message_id'):
values['notification'] = True
mail_id = super(mail_mail, self).create(cr, uid, values, context=context)
# reply_to: if not set, set with default values that require creation values
# but delegate after creation because of mail_message.message_id automatic
# creation using existence of reply_to
if not values.get('reply_to'):
values['reply_to'] = self._get_reply_to(cr, uid, values, context=context)
return super(mail_mail, self).create(cr, uid, values, context=context)
reply_to = self._get_reply_to(cr, uid, values, context=context)
if reply_to:
self.write(cr, uid, [mail_id], {'reply_to': reply_to}, context=context)
return mail_id
def unlink(self, cr, uid, ids, context=None):
# cascade-delete the parent message for all mails that are not created for a notification

View File

@ -191,6 +191,7 @@ class mail_message(osv.Model):
'vote_user_ids': fields.many2many('res.users', 'mail_vote',
'message_id', 'user_id', string='Votes',
help='Users that voted for this message'),
'mail_server_id': fields.many2one('ir.mail_server', 'Outgoing mail server', readonly=1),
}
def _needaction_domain_get(self, cr, uid, context=None):
@ -786,8 +787,8 @@ class mail_message(osv.Model):
values['message_id'] = tools.generate_tracking_message_id('private')
newid = super(mail_message, self).create(cr, uid, values, context)
self._notify(cr, uid, newid, context=context,
force_send=context.get('mail_notify_force_send', True),
user_signature=context.get('mail_notify_user_signature', True))
force_send=context.get('mail_notify_force_send', True),
user_signature=context.get('mail_notify_user_signature', True))
# TDE FIXME: handle default_starred. Why not setting an inv on starred ?
# Because starred will call set_message_starred, that looks for notifications.
# When creating a new mail_message, it will create a notification to a message

View File

@ -70,6 +70,7 @@ class mail_thread(osv.AbstractModel):
_name = 'mail.thread'
_description = 'Email Thread'
_mail_flat_thread = True
_mail_post_access = 'write'
# Automatic logging system if mail installed
# _track = {
@ -156,12 +157,26 @@ class mail_thread(osv.AbstractModel):
res[id]['message_summary'] = "<span class='oe_kanban_mail_new' title='%s'><span class='oe_e'>9</span> %d %s</span>" % (title, res[id].pop('message_unread_count'), _("New"))
return res
def _get_subscription_data(self, cr, uid, ids, name, args, context=None):
def read_followers_data(self, cr, uid, follower_ids, context=None):
result = []
technical_group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_no_one')
for follower in self.pool.get('res.partner').browse(cr, uid, follower_ids, context=context):
is_editable = uid in map(lambda x: x.id, technical_group.users)
is_uid = uid in map(lambda x: x.id, follower.user_ids)
data = (follower.id,
follower.name,
{'is_editable': is_editable, 'is_uid': is_uid},
)
result.append(data)
return result
def _get_subscription_data(self, cr, uid, ids, name, args, user_pid=None, context=None):
""" Computes:
- message_subtype_data: data about document subtypes: which are
available, which are followed if any """
res = dict((id, dict(message_subtype_data='')) for id in ids)
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
if user_pid is None:
user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
# find current model subtypes, add them to a dictionary
subtype_obj = self.pool.get('mail.message.subtype')
@ -495,12 +510,22 @@ class mail_thread(osv.AbstractModel):
access rule on the document, for portal document such as issues. """
if not model_obj:
model_obj = self
if operation in ['create', 'write', 'unlink']:
model_obj.check_access_rights(cr, uid, 'write')
model_obj.check_access_rule(cr, uid, mids, 'write', context=context)
if hasattr(self, '_mail_post_access'):
create_allow = self._mail_post_access
else:
model_obj.check_access_rights(cr, uid, operation)
model_obj.check_access_rule(cr, uid, mids, operation, context=context)
create_allow = 'write'
if operation in ['write', 'unlink']:
check_operation = 'write'
elif operation == 'create' and create_allow in ['create', 'read', 'write', 'unlink']:
check_operation = create_allow
elif operation == 'create':
check_operation = 'write'
else:
check_operation = operation
model_obj.check_access_rights(cr, uid, check_operation)
model_obj.check_access_rule(cr, uid, mids, check_operation, context=context)
def _get_formview_action(self, cr, uid, id, model=None, context=None):
""" Return an action to open the document. This method is meant to be
@ -657,6 +682,12 @@ class mail_thread(osv.AbstractModel):
assert thread_id == 0, 'Routing: posting a message without model should be with a null res_id (private message).'
_warn('posting a message without model should be with a null res_id (private message), resetting thread_id')
thread_id = 0
# Private message: should have a parent_id (only answers)
if not model and not message_dict.get('parent_id'):
if assert_model:
assert message_dict.get('parent_id'), 'Routing: posting a message without model should be with a parent_id (private mesage).'
_warn('posting a message without model should be with a parent_id (private mesage), skipping')
return ()
# Existing Document: check if exists; if not, fallback on create if allowed
if thread_id and not model_pool.exists(cr, uid, thread_id):
@ -670,7 +701,7 @@ class mail_thread(osv.AbstractModel):
return ()
# Existing Document: check model accepts the mailgateway
if thread_id and not hasattr(model_pool, 'message_update'):
if thread_id and model and not hasattr(model_pool, 'message_update'):
if create_fallback:
_warn('model %s does not accept document update, fall back on document creation' % model)
thread_id = None
@ -681,7 +712,7 @@ class mail_thread(osv.AbstractModel):
return ()
# New Document: check model accepts the mailgateway
if not thread_id and not hasattr(model_pool, 'message_new'):
if not thread_id and model and not hasattr(model_pool, 'message_new'):
if assert_model:
assert hasattr(model_pool, 'message_new'), 'Model %s does not accept document creation, crashing' % model
_warn('model %s does not accept document creation, skipping' % model)
@ -773,16 +804,16 @@ class mail_thread(osv.AbstractModel):
# 2. Reply to a private message
if in_reply_to:
message_ids = self.pool.get('mail.message').search(cr, uid, [
mail_message_ids = self.pool.get('mail.message').search(cr, uid, [
('message_id', '=', in_reply_to),
'!', ('message_id', 'ilike', 'reply_to')
], limit=1, context=context)
if message_ids:
message = self.pool.get('mail.message').browse(cr, uid, message_ids[0], context=context)
if mail_message_ids:
mail_message = self.pool.get('mail.message').browse(cr, uid, mail_message_ids[0], context=context)
_logger.info('Routing mail from %s to %s with Message-Id %s: direct reply to a private message: %s, custom_values: %s, uid: %s',
email_from, email_to, message_id, message.id, custom_values, uid)
email_from, email_to, message_id, mail_message.id, custom_values, uid)
route = self.message_route_verify(cr, uid, message, message_dict,
(message.model, message.res_id, custom_values, uid, None),
(mail_message.model, mail_message.res_id, custom_values, uid, None),
update_author=True, assert_model=True, create_fallback=True, context=context)
return route and [route] or []
@ -1401,9 +1432,9 @@ class mail_thread(osv.AbstractModel):
# Followers API
#------------------------------------------------------
def message_get_subscription_data(self, cr, uid, ids, context=None):
def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None):
""" Wrapper to get subtypes data. """
return self._get_subscription_data(cr, uid, ids, None, None, context=context)
return self._get_subscription_data(cr, uid, ids, None, None, user_pid=user_pid, context=context)
def message_subscribe_users(self, cr, uid, ids, user_ids=None, subtype_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not

View File

@ -14,11 +14,22 @@
<record id="mail_followers_read_write_own" model="ir.rule">
<field name="name">mail.followers: read and write its own entries</field>
<field name="model_id" ref="model_mail_followers"/>
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
<field name="domain_force">[('partner_id', '=', user.partner_id.id)]</field>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<!-- If technical rights then read and write others subscriptions -->
<record id="mail_followers_read_write_others" model="ir.rule">
<field name="name">mail.followers: read and write others entries</field>
<field name="model_id" ref="model_mail_followers"/>
<field name="groups" eval="[(4, ref('base.group_no_one'))]"/>
<field name="domain_force">[]</field>
<field name="perm_create" eval="False"/>
<field name="perm_unlink" eval="False"/>
</record>
<record id="mail_notification_read_write_own" model="ir.rule">
<field name="name">mail.notification: read and write its own entries</field>
<field name="model_id" ref="model_mail_notification"/>

View File

@ -635,11 +635,10 @@
}
.openerp .oe_followers .oe_partner {
height: 32px;
margin-right: 8px;
margin-right: 24px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
margin-right: 10px;
}
.openerp .oe_followers .oe_partner img{
width: 32px;
@ -652,7 +651,18 @@
right: 0px;
line-height: 20px;
}
.openerp .oe_followers .oe_edit_subtype{
cursor: pointer;
position: absolute;
right: 12px;
line-height: 20px;
}
.openerp .oe_followers .oe_partner .oe_hidden{
display: none;
}
.openerp.ui-dialog .ui-dialog-titlebar .ui-dialog-title{
padding-right: 20px;
}
.openerp .oe_followers .oe_show_more{
cursor: pointer;
}

View File

@ -2,7 +2,7 @@ openerp.mail = function (session) {
var _t = session.web._t,
_lt = session.web._lt;
var mail = session.mail = {};
var mail = session.mail;
openerp_mail_followers(session, mail); // import mail_followers.js
openerp_FieldMany2ManyTagsEmail(session); // import manyy2many_tags_email.js

View File

@ -29,7 +29,6 @@ openerp_mail_followers = function(session, mail) {
this.displayed_limit = this.node.attrs.displayed_nb || 10;
this.displayed_nb = this.displayed_limit;
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');
this.value = [];
@ -74,10 +73,33 @@ openerp_mail_followers = function(session, mail) {
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', self.on_invite_follower);
// event: click on 'edit_subtype(pencil)' button to edit subscription
this.$el.on('click', '.oe_edit_subtype', self.on_edit_subtype);
this.$el.on('click', '.oe_remove_follower', self.on_remove_follower);
this.$el.on('click', '.oe_show_more', self.on_show_more_followers)
},
on_edit_subtype: function(event) {
var self = this;
var $currentTarget = $(event.currentTarget);
var user_pid = $currentTarget.data('id');
$('div.oe_edit_actions').remove();
self.$dialog = new session.web.dialog($('<div class="oe_edit_actions">'), {
modal: true,
width: 'auto',
height: 'auto',
title: _t('Edit Subscription of ') + $currentTarget.siblings('a').text(),
buttons: [
{ text: _t("Apply"), click: function() {
self.do_update_subscription(event, user_pid);
$(this).dialog("close");
}},
{ text: _t("Cancel"), click: function() { $(this).dialog("close"); }}
],
});
return self.fetch_subtypes(user_pid);
},
on_invite_follower: function (event) {
var self = this;
var action = {
@ -130,7 +152,7 @@ openerp_mail_followers = function(session, mail) {
fetch_followers: function (value_) {
this.value = value_ || {};
return this.ds_follow.call('read', [this.value, ['name', 'user_ids']])
return this.ds_model.call('read_followers_data', [this.value])
.then(this.proxy('display_followers'), this.proxy('fetch_generic'))
.then(this.proxy('display_buttons'))
.then(this.proxy('fetch_subtypes'));
@ -147,6 +169,7 @@ openerp_mail_followers = function(session, mail) {
self.message_is_follower = (_.indexOf(self.value, pid) != -1);
}).then(self.proxy('display_generic'));
},
_format_followers: function(count){
var str = '';
if(count <= 0){
@ -158,6 +181,7 @@ openerp_mail_followers = function(session, mail) {
}
return str;
},
/* Display generic info about follower, for people not having access to res_partner */
display_generic: function () {
var self = this;
@ -168,16 +192,32 @@ openerp_mail_followers = function(session, mail) {
/** Display the followers */
display_followers: function (records) {
var self = this;
this.message_is_follower = false;
this.followers = records || this.followers;
this.message_is_follower = this.set_is_follower(this.followers);
// clean and display title
var node_user_list = this.$('.oe_follower_list').empty();
this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
// truncate number of displayed followers
var truncated = this.followers.slice(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, 'widget': self})).appendTo(node_user_list);
partner = {
'id': record[0],
'name': record[1],
'is_uid': record[2]['is_uid'],
'is_editable': record[2]['is_editable'],
'avatar_url': mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record[0]),
}
if (partner.is_uid) {
self.message_is_follower = partner.is_uid;
}
$(session.web.qweb.render('mail.followers.partner', {'record': partner, 'widget': self})).appendTo(node_user_list);
// On mouse-enter it will show the edit_subtype pencil.
if (partner.is_editable) {
self.$('.oe_follower_list').on('mouseenter mouseleave', function(e) {
self.$('.oe_edit_subtype').toggleClass('oe_hidden', e.type == 'mouseleave');
self.$('.oe_follower_list').find('.oe_partner').toggleClass('oe_partner_name', e.type == 'mouseenter');
});
}
});
// FVA note: be sure it is correctly translated
if (truncated.length < this.followers.length) {
@ -185,12 +225,6 @@ openerp_mail_followers = function(session, mail) {
}
},
/** Computes whether the current user is in the followers */
set_is_follower: function (records) {
var user_ids = _.pluck(_.pluck(records, 'user_ids'), 0);
return _.indexOf(user_ids, this.session.uid) != -1;
},
display_buttons: function () {
if (this.message_is_follower) {
this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
@ -206,25 +240,37 @@ openerp_mail_followers = function(session, mail) {
},
/** Fetch subtypes, only if current user is follower */
fetch_subtypes: function () {
fetch_subtypes: function (user_pid) {
var self = this;
var subtype_list_ul = this.$('.oe_subtype_list').empty();
if (! this.message_is_follower) return;
var dialog = false;
if (user_pid) {
dialog = true;
} else {
var subtype_list_ul = this.$('.oe_subtype_list').empty();
if (! this.message_is_follower) return;
}
var id = this.view.datarecord.id;
this.ds_model.call('message_get_subscription_data', [[id], new session.web.CompoundContext(this.build_context(), {})])
.then(function (data) {self.display_subtypes(data, id);});
this.ds_model.call('message_get_subscription_data', [[id], user_pid, new session.web.CompoundContext(this.build_context(), {})])
.then(function (data) {self.display_subtypes(data, id, dialog);});
},
/** Display subtypes: {'name': default, followed} */
display_subtypes:function (data, id) {
display_subtypes:function (data, id, dialog) {
var self = this;
var $list = this.$('.oe_subtype_list');
if (dialog) {
var $list = self.$dialog;
}
else {
var $list = this.$('.oe_subtype_list');
}
$list.empty().hide();
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
this.records_length = $.map(records, function(value, index) { return index; }).length;
if (this.records_length > 1) { self.display_followers(); }
_(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.$('.oe_subtype_list') );
$(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo($list);
});
if (_.size(records) > 1) {
$list.show();
@ -241,35 +287,49 @@ openerp_mail_followers = function(session, mail) {
});
},
do_unfollow: function () {
do_unfollow: function (user_pid) {
if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
_(this.$('.oe_msg_subtype_check')).each(function (record) {
$(record).attr('checked',false);
});
var action_unsubscribe = 'message_unsubscribe_users';
var follower_ids = [this.session.uid];
if (user_pid) {
action_unsubscribe = 'message_unsubscribe';
follower_ids = [user_pid];
}
var context = new session.web.CompoundContext(this.build_context(), {});
return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], [this.session.uid], context])
.then(this.proxy('read_value'));
return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id], follower_ids, context])
.then(this.proxy('read_value'));
}
return false;
},
do_update_subscription: function (event) {
do_update_subscription: function (event, user_pid) {
var self = this;
var action_subscribe = 'message_subscribe_users';
var follower_ids = [this.session.uid];
var oe_action = this.$('.oe_actions input[type="checkbox"]');
if (user_pid) {
action_subscribe = 'message_subscribe';
follower_ids = [user_pid];
oe_action = $('.oe_edit_actions input[type="checkbox"]');
}
var checklist = new Array();
_(this.$('.oe_actions input[type="checkbox"]')).each(function (record) {
_(oe_action).each(function (record) {
if ($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')));
}
});
if (!checklist.length) {
if (!this.do_unfollow()) {
if (!this.do_unfollow(user_pid)) {
$(event.target).attr("checked", "checked");
}
} else {
var context = new session.web.CompoundContext(this.build_context(), {});
return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], checklist, context])
return this.ds_model.call(action_subscribe, [[this.view.datarecord.id], follower_ids, checklist, context])
.then(this.proxy('read_value'));
}
},

View File

@ -31,6 +31,7 @@
<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-att-title="record.name"><t t-esc="record.name"/></a>
<span t-if="record.is_editable and (widget.records_length &gt; 1)" class="oe_edit_subtype oe_e oe_hidden" title="Edit subscription" t-att-data-id="record.id">&amp;</span>
<span t-if="widget.view_is_editable" class="oe_remove_follower oe_e" title="Remove this follower" t-att-data-id="record.id">X</span>
</div>

View File

@ -640,7 +640,7 @@ class test_mail(TestMailBase):
})
compose = mail_compose.browse(cr, uid, compose_id)
# D: Post the comment, get created message for each group
# Do: Post the comment, get created message for each group
mail_compose.send_mail(cr, user_raoul.id, [compose_id], context={
'default_res_id': -1,
'active_ids': [self.group_pigs_id, group_bird_id]
@ -679,6 +679,37 @@ class test_mail(TestMailBase):
self.assertEqual(set(bird_pids), set(test_pids),
'compose wizard: mail_post_autofollow and mail_create_nosubscribe context keys not correctly taken into account')
# Do: Compose in mass_mail, coming from list_view, we have an active_domain that should be supported
compose_id = mail_compose.create(cr, user_raoul.id,
{
'subject': _subject,
'body': '${object.description}',
'partner_ids': [(4, p_c_id), (4, p_d_id)],
}, context={
'default_composition_mode': 'mass_mail',
'default_model': 'mail.group',
'default_res_id': False,
'active_ids': [self.group_pigs_id],
'active_domain': [('name', 'in', ['Pigs', 'Bird'])],
})
compose = mail_compose.browse(cr, uid, compose_id)
# Do: Post the comment, get created message for each group
mail_compose.send_mail(
cr, user_raoul.id, [compose_id], context={
'default_res_id': -1,
'active_ids': [self.group_pigs_id, group_bird_id]
})
group_pigs.refresh()
group_bird.refresh()
message1 = group_pigs.message_ids[0]
message2 = group_bird.message_ids[0]
# Test: Pigs and Bird did receive their message
test_msg_ids = self.mail_message.search(cr, uid, [], limit=2)
self.assertIn(message1.id, test_msg_ids, 'compose wizard: Pigs did not receive its mass mailing message')
self.assertIn(message2.id, test_msg_ids, 'compose wizard: Bird did not receive its mass mailing message')
def test_30_needaction(self):
""" Tests for mail.message needaction. """
cr, uid, user_admin, user_raoul, group_pigs = self.cr, self.uid, self.user_admin, self.user_raoul, self.group_pigs

View File

@ -20,7 +20,7 @@
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
from openerp.tools import mute_logger, email_split
from openerp.tools import mute_logger
MAIL_TEMPLATE = """Return-Path: <whatever-2a840@postmaster.twitter.com>
To: {to}
@ -101,9 +101,9 @@ class TestMailgateway(TestMailBase):
# Do: find partner with email -> first partner should be found
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
self.assertEqual(partner_info['full_name'], 'Maybe Raoul <test@test.fr>',
'mail_thread: message_partner_info_from_emails did not handle email')
'mail_thread: message_partner_info_from_emails did not handle email')
self.assertEqual(partner_info['partner_id'], p_a_id,
'mail_thread: message_partner_info_from_emails wrong partner found')
'mail_thread: message_partner_info_from_emails wrong partner found')
# Data: add some data about partners
# 2 - User BRaoul
@ -112,7 +112,7 @@ class TestMailgateway(TestMailBase):
# Do: find partner with email -> first user should be found
partner_info = self.mail_thread.message_partner_info_from_emails(cr, uid, None, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
self.assertEqual(partner_info['partner_id'], p_b_id,
'mail_thread: message_partner_info_from_emails wrong partner found')
'mail_thread: message_partner_info_from_emails wrong partner found')
# --------------------------------------------------
# CASE1: with object
@ -122,7 +122,7 @@ class TestMailgateway(TestMailBase):
self.mail_group.message_subscribe(cr, uid, [group_pigs.id], [p_b_id])
partner_info = self.mail_group.message_partner_info_from_emails(cr, uid, group_pigs.id, ['Maybe Raoul <test@test.fr>'], link_mail=False)[0]
self.assertEqual(partner_info['partner_id'], p_b_id,
'mail_thread: message_partner_info_from_emails wrong partner found')
'mail_thread: message_partner_info_from_emails wrong partner found')
def test_05_mail_message_mail_mail(self):
""" Tests designed for testing email values based on mail.message, aliases, ... """
@ -146,46 +146,46 @@ class TestMailgateway(TestMailBase):
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
# Test: message content
self.assertIn('reply_to', msg.message_id,
'mail_message: message_id should be specific to a mail_message with a given reply_to')
'mail_message: message_id should be specific to a mail_message with a given reply_to')
self.assertEqual(msg.reply_to, reply_to1,
'mail_message: incorrect reply_to: should come from values')
'mail_message: incorrect reply_to: should come from values')
self.assertEqual(msg.email_from, email_from1,
'mail_message: incorrect email_from: should come from values')
'mail_message: incorrect email_from: should come from values')
# Do: create a mail_mail with the previous mail_message
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
# Test: mail_mail content
self.assertEqual(mail.reply_to, reply_to1,
'mail_mail: incorrect reply_to: should come from mail.message')
'mail_mail: incorrect reply_to: should come from mail.message')
self.assertEqual(mail.email_from, email_from1,
'mail_mail: incorrect email_from: should come from mail.message')
'mail_mail: incorrect email_from: should come from mail.message')
# Do: create a mail_mail with the previous mail_message + specified reply_to
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel', 'reply_to': reply_to2})
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
# Test: mail_mail content
self.assertEqual(mail.reply_to, reply_to2,
'mail_mail: incorrect reply_to: should come from values')
'mail_mail: incorrect reply_to: should come from values')
self.assertEqual(mail.email_from, email_from1,
'mail_mail: incorrect email_from: should come from mail.message')
'mail_mail: incorrect email_from: should come from mail.message')
# Do: mail_message attached to a document
msg_id = self.mail_message.create(cr, user_raoul_id, {'model': 'mail.group', 'res_id': self.group_pigs_id})
msg = self.mail_message.browse(cr, user_raoul_id, msg_id)
# Test: message content
self.assertIn('mail.group', msg.message_id,
'mail_message: message_id should contain model')
'mail_message: message_id should contain model')
self.assertIn('%s' % self.group_pigs_id, msg.message_id,
'mail_message: message_id should contain res_id')
'mail_message: message_id should contain res_id')
self.assertFalse(msg.reply_to,
'mail_message: incorrect reply_to: should not be generated if not specified')
'mail_message: incorrect reply_to: should not be generated if not specified')
self.assertEqual(msg.email_from, raoul_from,
'mail_message: incorrect email_from: should be Raoul')
'mail_message: incorrect email_from: should be Raoul')
# Do: create a mail_mail based on the previous mail_message
mail_id = self.mail_mail.create(cr, user_raoul_id, {'mail_message_id': msg_id, 'state': 'cancel'})
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
# Test: mail_mail content
self.assertEqual(mail.reply_to, raoul_reply,
'mail_mail: incorrect reply_to: should be Raoul')
'mail_mail: incorrect reply_to: should be Raoul')
# Data: set catchall domain
self.registry('ir.config_parameter').set_param(cr, uid, 'mail.catchall.domain', alias_domain)
@ -199,7 +199,7 @@ class TestMailgateway(TestMailBase):
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
# Test: mail_mail content
self.assertEqual(mail.reply_to, raoul_reply_alias,
'mail_mail: incorrect reply_to: should be Pigs alias')
'mail_mail: incorrect reply_to: should be Pigs alias')
# Update message: test alias on email_from
msg_id = self.mail_message.create(cr, user_raoul_id, {})
@ -209,7 +209,7 @@ class TestMailgateway(TestMailBase):
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
# Test: mail_mail content
self.assertEqual(mail.reply_to, raoul_from_alias,
'mail_mail: incorrect reply_to: should be message email_from using Raoul alias')
'mail_mail: incorrect reply_to: should be message email_from using Raoul alias')
# Update message
self.mail_message.write(cr, user_raoul_id, [msg_id], {'res_id': False, 'email_from': 'someone@schlouby.fr', 'reply_to': False})
@ -219,7 +219,7 @@ class TestMailgateway(TestMailBase):
mail = self.mail_mail.browse(cr, user_raoul_id, mail_id)
# Test: mail_mail content
self.assertEqual(mail.reply_to, msg.email_from,
'mail_mail: incorrect reply_to: should be message email_from')
'mail_mail: incorrect reply_to: should be message email_from')
# Data: set catchall alias
self.registry('ir.config_parameter').set_param(self.cr, self.uid, 'mail.catchall.alias', 'gateway')
@ -232,21 +232,21 @@ class TestMailgateway(TestMailBase):
mail = self.mail_mail.browse(cr, uid, mail_id)
# Test: mail_mail Content-Type
self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
'mail_mail: reply_to should equal the catchall email alias')
'mail_mail: reply_to should equal the catchall email alias')
# Do: create a mail_mail
mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel'})
mail = self.mail_mail.browse(cr, uid, mail_id)
# Test: mail_mail content
self.assertEqual(mail.reply_to, 'gateway@schlouby.fr',
'mail_mail: reply_to should equal the catchall email alias')
'mail_mail: reply_to should equal the catchall email alias')
# Do: create a mail_mail
mail_id = self.mail_mail.create(cr, uid, {'state': 'cancel', 'reply_to': 'someone@example.com'})
mail = self.mail_mail.browse(cr, uid, mail_id)
# Test: mail_mail content
self.assertEqual(mail.reply_to, 'someone@example.com',
'mail_mail: reply_to should equal the rpely_to given to create')
'mail_mail: reply_to should equal the rpely_to given to create')
@mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm')
def test_10_message_process(self):
@ -254,9 +254,9 @@ class TestMailgateway(TestMailBase):
cr, uid, user_raoul = self.cr, self.uid, self.user_raoul
def format_and_process(template, to='groups@example.com, other@gmail.com', subject='Frogs',
extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>',
model=None):
extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>',
model=None):
self.assertEqual(self.mail_group.search(cr, uid, [('name', '=', subject)]), [])
mail = template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
self.mail_thread.message_process(cr, uid, model, mail)
@ -289,29 +289,29 @@ class TestMailgateway(TestMailBase):
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
res = self.mail_group.perm_read(cr, uid, [frog_group.id], details=False)
self.assertEqual(res[0].get('create_uid'), uid,
'message_process: group should have been created by uid as alias_user__id is False on the alias')
'message_process: group should have been created by uid as alias_user__id is False on the alias')
# Test: one message that is the incoming email
self.assertEqual(len(frog_group.message_ids), 1,
'message_process: newly created group should have the incoming email in message_ids')
'message_process: newly created group should have the incoming email in message_ids')
msg = frog_group.message_ids[0]
self.assertEqual('Frogs', msg.subject,
'message_process: newly created group should have the incoming email as first message')
'message_process: newly created group should have the incoming email as first message')
self.assertIn('Please call me as soon as possible this afternoon!', msg.body,
'message_process: newly created group should have the incoming email as first message')
'message_process: newly created group should have the incoming email as first message')
self.assertEqual('email', msg.type,
'message_process: newly created group should have an email as first message')
'message_process: newly created group should have an email as first message')
self.assertEqual('Discussions', msg.subtype_id.name,
'message_process: newly created group should not have a log first message but an email')
'message_process: newly created group should not have a log first message but an email')
# Test: message: unknown email address -> message has email_from, not author_id
self.assertFalse(msg.author_id,
'message_process: message on created group should not have an author_id')
'message_process: message on created group should not have an author_id')
self.assertIn('test.sylvie.lelitre@agrolait.com', msg.email_from,
'message_process: message on created group should have an email_from')
'message_process: message on created group should have an email_from')
# Test: followers: nobody
self.assertEqual(len(frog_group.message_follower_ids), 0, 'message_process: newly create group should not have any follower')
# Test: sent emails: no-one
self.assertEqual(len(sent_emails), 0,
'message_process: should create emails without any follower added')
'message_process: should create emails without any follower added')
# Data: unlink group
frog_group.unlink()
@ -324,11 +324,11 @@ class TestMailgateway(TestMailBase):
# Test: email bounced
sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 1,
'message_process: incoming email on Partners alias should send a bounce email')
'message_process: incoming email on Partners alias should send a bounce email')
self.assertIn('Frogs', sent_emails[0].get('subject'),
'message_process: bounce email on Partners alias should contain the original subject')
'message_process: bounce email on Partners alias should contain the original subject')
self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
'message_process: bounce email on Partners alias should have original email sender as recipient')
'message_process: bounce email on Partners alias should have original email sender as recipient')
# Do: incoming email from an unknown partner on a Followers only alias -> bounce
self._init_mock_build_email()
@ -339,11 +339,11 @@ class TestMailgateway(TestMailBase):
# Test: email bounced
sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 1,
'message_process: incoming email on Followers alias should send a bounce email')
'message_process: incoming email on Followers alias should send a bounce email')
self.assertIn('Frogs', sent_emails[0].get('subject'),
'message_process: bounce email on Followers alias should contain the original subject')
'message_process: bounce email on Followers alias should contain the original subject')
self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to'),
'message_process: bounce email on Followers alias should have original email sender as recipient')
'message_process: bounce email on Followers alias should have original email sender as recipient')
# Do: incoming email from a known partner on a Partners alias -> ok (+ test on alias.user_id)
self.mail_alias.write(cr, uid, [alias_id], {'alias_user_id': self.user_raoul_id, 'alias_contact': 'partners'})
@ -357,24 +357,24 @@ class TestMailgateway(TestMailBase):
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
res = self.mail_group.perm_read(cr, uid, [frog_group.id], details=False)
self.assertEqual(res[0].get('create_uid'), self.user_raoul_id,
'message_process: group should have been created by alias_user_id')
'message_process: group should have been created by alias_user_id')
# Test: one message that is the incoming email
self.assertEqual(len(frog_group.message_ids), 1,
'message_process: newly created group should have the incoming email in message_ids')
'message_process: newly created group should have the incoming email in message_ids')
msg = frog_group.message_ids[0]
# Test: message: author found
self.assertEqual(p1id, msg.author_id.id,
'message_process: message on created group should have Sylvie as author_id')
'message_process: message on created group should have Sylvie as author_id')
self.assertIn('Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>', msg.email_from,
'message_process: message on created group should have have an email_from')
'message_process: message on created group should have have an email_from')
# Test: author (not recipient and not Raoul (as alias owner)) added as follower
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
self.assertEqual(frog_follower_ids, set([p1id]),
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
# Test: sent emails: no-one, no bounce effet
sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 0,
'message_process: should not bounce incoming emails')
'message_process: should not bounce incoming emails')
# Data: unlink group
frog_group.unlink()
@ -387,7 +387,7 @@ class TestMailgateway(TestMailBase):
# Test: email bounced
sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 1,
'message_process: incoming email on Partners alias should send a bounce email')
'message_process: incoming email on Partners alias should send a bounce email')
# Do: incoming email from a parent document follower on a Followers only alias -> ok
self._init_mock_build_email()
@ -398,15 +398,15 @@ class TestMailgateway(TestMailBase):
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: one message that is the incoming email
self.assertEqual(len(frog_group.message_ids), 1,
'message_process: newly created group should have the incoming email in message_ids')
'message_process: newly created group should have the incoming email in message_ids')
# Test: author (and not recipient) added as follower
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
self.assertEqual(frog_follower_ids, set([p1id]),
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
'message_process: newly created group should have 1 follower (author, not creator, not recipients)')
# Test: sent emails: no-one, no bounce effet
sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 0,
'message_process: should not bounce incoming emails')
'message_process: should not bounce incoming emails')
# --------------------------------------------------
# Test2: update-like alias
@ -416,43 +416,43 @@ class TestMailgateway(TestMailBase):
self._init_mock_build_email()
self.mail_group.write(cr, uid, [frog_group.id], {'alias_name': 'frogs', 'alias_contact': 'followers', 'alias_force_thread_id': frog_group.id})
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
to='frogs@example.com>', subject='Re: news')
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
to='frogs@example.com>', subject='Re: news')
# Test: no group 'Re: news' created, still only 1 Frogs group
self.assertEqual(len(frog_groups), 0,
'message_process: reply on Frogs should not have created a new group with new subject')
'message_process: reply on Frogs should not have created a new group with new subject')
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
self.assertEqual(len(frog_groups), 1,
'message_process: reply on Frogs should not have created a duplicate group with old subject')
'message_process: reply on Frogs should not have created a duplicate group with old subject')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: email bounced
sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 1,
'message_process: incoming email on Followers alias should send a bounce email')
'message_process: incoming email on Followers alias should send a bounce email')
self.assertIn('Re: news', sent_emails[0].get('subject'),
'message_process: bounce email on Followers alias should contain the original subject')
'message_process: bounce email on Followers alias should contain the original subject')
# Do: Pigs alias is restricted, should accept Followers
self._init_mock_build_email()
self.mail_group.message_subscribe(cr, uid, [frog_group.id], [p2id])
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
msg_id='<1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>',
to='frogs@example.com>', subject='Re: cats')
msg_id='<1198923581.41972151344608186799.JavaMail.diff1@agrolait.com>',
to='frogs@example.com>', subject='Re: cats')
# Test: no group 'Re: news' created, still only 1 Frogs group
self.assertEqual(len(frog_groups), 0,
'message_process: reply on Frogs should not have created a new group with new subject')
'message_process: reply on Frogs should not have created a new group with new subject')
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
self.assertEqual(len(frog_groups), 1,
'message_process: reply on Frogs should not have created a duplicate group with old subject')
'message_process: reply on Frogs should not have created a duplicate group with old subject')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: one new message
self.assertEqual(len(frog_group.message_ids), 2, 'message_process: group should contain 2 messages after reply')
# Test: sent emails: 1 (Sylvie copy of the incoming email, but no bounce)
sent_emails = self._build_email_kwargs_list
self.assertEqual(len(sent_emails), 1,
'message_process: one email should have been generated')
'message_process: one email should have been generated')
self.assertIn('test.sylvie.lelitre@agrolait.com', sent_emails[0].get('email_to')[0],
'message_process: email should be sent to Sylvie')
'message_process: email should be sent to Sylvie')
self.mail_group.message_unsubscribe(cr, uid, [frog_group.id], [p2id])
# --------------------------------------------------
@ -461,40 +461,40 @@ class TestMailgateway(TestMailBase):
# Do: even with a wrong destination, a reply should end up in the correct thread
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
to='erroneous@example.com>', subject='Re: news',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
to='erroneous@example.com>', subject='Re: news',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
# Test: no group 'Re: news' created, still only 1 Frogs group
self.assertEqual(len(frog_groups), 0,
'message_process: reply on Frogs should not have created a new group with new subject')
'message_process: reply on Frogs should not have created a new group with new subject')
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
self.assertEqual(len(frog_groups), 1,
'message_process: reply on Frogs should not have created a duplicate group with old subject')
'message_process: reply on Frogs should not have created a duplicate group with old subject')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: one new message
self.assertEqual(len(frog_group.message_ids), 3, 'message_process: group should contain 2 messages after reply')
# Test: author (and not recipient) added as follower
frog_follower_ids = set([p.id for p in frog_group.message_follower_ids])
self.assertEqual(frog_follower_ids, set([p1id, p2id]),
'message_process: after reply, group should have 2 followers')
'message_process: after reply, group should have 2 followers')
# Do: due to some issue, same email goes back into the mailgateway
frog_groups = format_and_process(MAIL_TEMPLATE, email_from='other4@gmail.com',
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
subject='Re: news', extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
msg_id='<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>',
subject='Re: news', extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
# Test: no group 'Re: news' created, still only 1 Frogs group
self.assertEqual(len(frog_groups), 0,
'message_process: reply on Frogs should not have created a new group with new subject')
'message_process: reply on Frogs should not have created a new group with new subject')
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
self.assertEqual(len(frog_groups), 1,
'message_process: reply on Frogs should not have created a duplicate group with old subject')
'message_process: reply on Frogs should not have created a duplicate group with old subject')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: no new message
self.assertEqual(len(frog_group.message_ids), 3, 'message_process: message with already existing message_id should not have been duplicated')
# Test: message_id is still unique
msg_ids = self.mail_message.search(cr, uid, [('message_id', 'ilike', '<1198923581.41972151344608186760.JavaMail.diff1@agrolait.com>')])
self.assertEqual(len(msg_ids), 1,
'message_process: message with already existing message_id should not have been duplicated')
'message_process: message with already existing message_id should not have been duplicated')
# --------------------------------------------------
# Test4: email_from and partner finding
@ -505,28 +505,28 @@ class TestMailgateway(TestMailBase):
# Do: post a new message, with a known partner -> duplicate emails -> partner
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
to='erroneous@example.com>', subject='Re: news (2)',
msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
to='erroneous@example.com>', subject='Re: news (2)',
msg_id='<1198923581.41972151344608186760.JavaMail.new1@agrolait.com>',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: author is A-Raoul (only existing)
self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id,
'message_process: email_from -> author_id wrong')
'message_process: email_from -> author_id wrong')
# Do: post a new message, with a known partner -> duplicate emails -> user
frog_group.message_unsubscribe([extra_partner_id])
raoul_email = self.user_raoul.email
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
to='erroneous@example.com>', subject='Re: news (3)',
msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
to='erroneous@example.com>', subject='Re: news (3)',
msg_id='<1198923581.41972151344608186760.JavaMail.new2@agrolait.com>',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: author is Raoul (user), not A-Raoul
self.assertEqual(frog_group.message_ids[0].author_id.id, self.partner_raoul_id,
'message_process: email_from -> author_id wrong')
'message_process: email_from -> author_id wrong')
# Do: post a new message, with a known partner -> duplicate emails -> partner because is follower
frog_group.message_unsubscribe([self.partner_raoul_id])
@ -534,14 +534,14 @@ class TestMailgateway(TestMailBase):
raoul_email = self.user_raoul.email
self.res_users.write(cr, uid, self.user_raoul_id, {'email': 'test_raoul@email.com'})
format_and_process(MAIL_TEMPLATE, email_from='Lombrik Lubrik <test_raoul@email.com>',
to='erroneous@example.com>', subject='Re: news (3)',
msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
to='erroneous@example.com>', subject='Re: news (3)',
msg_id='<1198923581.41972151344608186760.JavaMail.new3@agrolait.com>',
extra='In-Reply-To: <12321321-openerp-%d-mail.group@example.com>\n' % frog_group.id)
frog_groups = self.mail_group.search(cr, uid, [('name', '=', 'Frogs')])
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
# Test: author is Raoul (user), not A-Raoul
self.assertEqual(frog_group.message_ids[0].author_id.id, extra_partner_id,
'message_process: email_from -> author_id wrong')
'message_process: email_from -> author_id wrong')
self.res_users.write(cr, uid, self.user_raoul_id, {'email': raoul_email})
@ -551,37 +551,37 @@ class TestMailgateway(TestMailBase):
# Do: incoming email with model that does not accepts incoming emails must raise
self.assertRaises(AssertionError,
format_and_process,
MAIL_TEMPLATE,
to='noone@example.com', subject='spam', extra='', model='res.country',
msg_id='<1198923581.41972151344608186760.JavaMail.new4@agrolait.com>')
format_and_process,
MAIL_TEMPLATE,
to='noone@example.com', subject='spam', extra='', model='res.country',
msg_id='<1198923581.41972151344608186760.JavaMail.new4@agrolait.com>')
# Do: incoming email without model and without alias must raise
self.assertRaises(AssertionError,
format_and_process,
MAIL_TEMPLATE,
to='noone@example.com', subject='spam', extra='',
msg_id='<1198923581.41972151344608186760.JavaMail.new5@agrolait.com>')
format_and_process,
MAIL_TEMPLATE,
to='noone@example.com', subject='spam', extra='',
msg_id='<1198923581.41972151344608186760.JavaMail.new5@agrolait.com>')
# Do: incoming email with model that accepting incoming emails as fallback
frog_groups = format_and_process(MAIL_TEMPLATE,
to='noone@example.com',
subject='Spammy', extra='', model='mail.group',
msg_id='<1198923581.41972151344608186760.JavaMail.new6@agrolait.com>')
to='noone@example.com',
subject='Spammy', extra='', model='mail.group',
msg_id='<1198923581.41972151344608186760.JavaMail.new6@agrolait.com>')
self.assertEqual(len(frog_groups), 1,
'message_process: erroneous email but with a fallback model should have created a new mail.group')
'message_process: erroneous email but with a fallback model should have created a new mail.group')
# Do: incoming email in plaintext should be stored as html
frog_groups = format_and_process(MAIL_TEMPLATE_PLAINTEXT,
to='groups@example.com', subject='Frogs Return', extra='',
msg_id='<deadcafe.1337@smtp.agrolait.com>')
to='groups@example.com', subject='Frogs Return', extra='',
msg_id='<deadcafe.1337@smtp.agrolait.com>')
# Test: one group created with one message
self.assertEqual(len(frog_groups), 1, 'message_process: a new mail.group should have been created')
frog_group = self.mail_group.browse(cr, uid, frog_groups[0])
msg = frog_group.message_ids[0]
# Test: plain text content should be wrapped and stored as html
self.assertIn('<pre>\nPlease call me as soon as possible this afternoon!\n\n--\nSylvie\n</pre>', msg.body,
'message_process: plaintext incoming email incorrectly parsed')
'message_process: plaintext incoming email incorrectly parsed')
@mute_logger('openerp.addons.mail.mail_thread', 'openerp.osv.orm')
def test_20_thread_parent_resolution(self):
@ -602,26 +602,26 @@ class TestMailgateway(TestMailBase):
# Reply to msg1, make sure the reply is properly attached using the various reply identification mechanisms
# 0. Direct alias match
reply_msg1 = format(MAIL_TEMPLATE, to='Pretty Pigs <group+pigs@example.com>',
extra='In-Reply-To: %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.2@agrolait.com>')
extra='In-Reply-To: %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.2@agrolait.com>')
self.mail_group.message_process(cr, uid, None, reply_msg1)
# 1. In-Reply-To header
reply_msg2 = format(MAIL_TEMPLATE, to='erroneous@example.com',
extra='In-Reply-To: %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.3@agrolait.com>')
extra='In-Reply-To: %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.3@agrolait.com>')
self.mail_group.message_process(cr, uid, None, reply_msg2)
# 2. References header
reply_msg3 = format(MAIL_TEMPLATE, to='erroneous@example.com',
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.4@agrolait.com>')
extra='References: <2233@a.com>\r\n\t<3edss_dsa@b.com> %s' % msg1.message_id,
msg_id='<1198923581.41972151344608186760.JavaMail.4@agrolait.com>')
self.mail_group.message_process(cr, uid, None, reply_msg3)
# 3. Subject contains [<ID>] + model passed to message+process -> only attached to group, but not to mail (not in msg1.child_ids)
reply_msg4 = format(MAIL_TEMPLATE, to='erroneous@example.com',
extra='', subject='Re: [%s] 1' % self.group_pigs_id,
msg_id='<1198923581.41972151344608186760.JavaMail.5@agrolait.com>')
extra='', subject='Re: [%s] 1' % self.group_pigs_id,
msg_id='<1198923581.41972151344608186760.JavaMail.5@agrolait.com>')
self.mail_group.message_process(cr, uid, 'mail.group', reply_msg4)
group_pigs.refresh()
@ -633,12 +633,19 @@ class TestMailgateway(TestMailBase):
""" Testing private discussion between partners. """
cr, uid = self.cr, self.uid
def format(template, to='Pretty Pigs <group+pigs@example.com>, other@gmail.com', subject='Re: 1',
extra='', email_from='Sylvie Lelitre <test.sylvie.lelitre@agrolait.com>',
msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
return template.format(to=to, subject=subject, extra=extra, email_from=email_from, msg_id=msg_id)
# Do: Raoul writes to Bert and Administrator, with a thread_model in context that should not be taken into account
msg1_pids = [self.partner_admin_id, self.partner_bert_id]
msg1_id = self.mail_thread.message_post(cr, self.user_raoul_id, False,
partner_ids=msg1_pids,
subtype='mail.mt_comment',
context={'thread_model': 'mail.group'})
msg1_id = self.mail_thread.message_post(
cr, self.user_raoul_id, False,
partner_ids=msg1_pids,
subtype='mail.mt_comment',
context={'thread_model': 'mail.group'}
)
# Test: message recipients
msg = self.mail_message.browse(cr, uid, msg1_id)
@ -647,16 +654,26 @@ class TestMailgateway(TestMailBase):
test_pids = msg1_pids
test_nids = msg1_pids
self.assertEqual(set(msg_pids), set(test_pids),
'message_post: private discussion: incorrect recipients')
'message_post: private discussion: incorrect recipients')
self.assertEqual(set(msg_nids), set(test_nids),
'message_post: private discussion: incorrect notified recipients')
'message_post: private discussion: incorrect notified recipients')
self.assertEqual(msg.model, False,
'message_post: private discussion: context key "thread_model" not correctly ignored when having no res_id')
'message_post: private discussion: context key "thread_model" not correctly ignored when having no res_id')
# Test: message reply_to and message-id
self.assertFalse(msg.reply_to,
'message_post: private discussion: initial message should not have any reply_to specified')
self.assertIn('openerp-private', msg.message_id,
'message_post: private discussion: message-id should contain the private keyword')
# Do: Bert replies through mailgateway (is a customer)
msg2_id = self.mail_thread.message_post(cr, uid, False,
author_id=self.partner_bert_id,
parent_id=msg1_id, subtype='mail.mt_comment')
reply_message = format(MAIL_TEMPLATE, to='not_important@mydomain.com',
email_from='bert@bert.fr',
extra='In-Reply-To: %s' % msg.message_id,
msg_id='<test30.JavaMail.0@agrolait.com>')
self.mail_thread.message_process(cr, uid, None, reply_message)
# Test: last mail_message created
msg2_id = self.mail_message.search(cr, uid, [], limit=1)[0]
# Test: message recipients
msg = self.mail_message.browse(cr, uid, msg2_id)
@ -664,14 +681,32 @@ class TestMailgateway(TestMailBase):
msg_nids = [p.id for p in msg.notified_partner_ids]
test_pids = [self.partner_admin_id, self.partner_raoul_id]
test_nids = test_pids
self.assertEqual(msg.author_id.id, self.partner_bert_id,
'message_post: private discussion: wrong author through mailgatewya based on email')
self.assertEqual(set(msg_pids), set(test_pids),
'message_post: private discussion: incorrect recipients when replying')
'message_post: private discussion: incorrect recipients when replying')
self.assertEqual(set(msg_nids), set(test_nids),
'message_post: private discussion: incorrect notified recipients when replying')
'message_post: private discussion: incorrect notified recipients when replying')
# Do: Bert replies through chatter (is a customer)
msg3_id = self.mail_thread.message_post(
cr, uid, False,
author_id=self.partner_bert_id,
parent_id=msg1_id, subtype='mail.mt_comment')
# Test: message recipients
msg = self.mail_message.browse(cr, uid, msg3_id)
msg_pids = [p.id for p in msg.partner_ids]
msg_nids = [p.id for p in msg.notified_partner_ids]
test_pids = [self.partner_admin_id, self.partner_raoul_id]
test_nids = test_pids
self.assertEqual(set(msg_pids), set(test_pids),
'message_post: private discussion: incorrect recipients when replying')
self.assertEqual(set(msg_nids), set(test_nids),
'message_post: private discussion: incorrect notified recipients when replying')
# Do: Administrator replies
msg3_id = self.mail_thread.message_post(cr, uid, False,
parent_id=msg2_id, subtype='mail.mt_comment')
msg3_id = self.mail_thread.message_post(cr, uid, False, parent_id=msg3_id, subtype='mail.mt_comment')
# Test: message recipients
msg = self.mail_message.browse(cr, uid, msg3_id)
@ -680,6 +715,6 @@ class TestMailgateway(TestMailBase):
test_pids = [self.partner_bert_id, self.partner_raoul_id]
test_nids = test_pids
self.assertEqual(set(msg_pids), set(test_pids),
'message_post: private discussion: incorrect recipients when replying')
'message_post: private discussion: incorrect recipients when replying')
self.assertEqual(set(msg_nids), set(test_nids),
'message_post: private discussion: incorrect notified recipients when replying')
'message_post: private discussion: incorrect notified recipients when replying')

View File

@ -73,6 +73,11 @@ class mail_compose_message(osv.TransientModel):
res_id = context.get('default_res_id', context.get('active_id'))
message_id = context.get('default_parent_id', context.get('message_id', context.get('active_id')))
active_ids = context.get('active_ids')
if 'active_domain' in context: # not context.get() because we want to keep global [] domains
result['use_active_domain'] = True
result['active_domain'] = '%s' % context.get('active_domain')
else:
result['active_domain'] = ''
# get default values according to the composition mode
if composition_mode == 'reply':
@ -112,6 +117,8 @@ class mail_compose_message(osv.TransientModel):
'partner_ids': fields.many2many('res.partner',
'mail_compose_message_res_partner_rel',
'wizard_id', 'partner_id', 'Additional contacts'),
'use_active_domain': fields.boolean('Use active domain'),
'active_domain': fields.char('Active domain', readonly=True),
'post': fields.boolean('Post a copy in the document',
help='Post a copy of the message on the document communication history.'),
'notify': fields.boolean('Notify followers',
@ -129,7 +136,6 @@ class mail_compose_message(osv.TransientModel):
'body': lambda self, cr, uid, ctx={}: '',
'subject': lambda self, cr, uid, ctx={}: False,
'partner_ids': lambda self, cr, uid, ctx={}: [],
'notify': lambda self, cr, uid, ctx={}: False,
'post': lambda self, cr, uid, ctx={}: True,
'same_thread': lambda self, cr, uid, ctx={}: True,
}
@ -238,8 +244,14 @@ class mail_compose_message(osv.TransientModel):
context['thread_model'] = wizard.model
active_model_pool = self.pool['mail.thread']
# wizard works in batch mode: [res_id] or active_ids
res_ids = active_ids if mass_mail_mode and wizard.model and active_ids else [wizard.res_id]
# wizard works in batch mode: [res_id] or active_ids or active_domain
if mass_mail_mode and wizard.use_active_domain and wizard.model:
res_ids = self.pool[wizard.model].search(cr, uid, eval(wizard.active_domain), context=context)
elif mass_mail_mode and wizard.model and active_ids:
res_ids = active_ids
else:
res_ids = [wizard.res_id]
for res_id in res_ids:
# mail.message values, according to the wizard options
post_values = {

View File

@ -12,6 +12,9 @@
<field name="model" invisible="1"/>
<field name="res_id" invisible="1"/>
<field name="parent_id" invisible="1"/>
<field name="mail_server_id" invisible="1"/>
<field name="use_active_domain" invisible="1"/>
<field name="active_domain" invisible="1"/>
<!-- visible wizard -->
<field name="email_from"
attrs="{'invisible':[('composition_mode', '!=', 'mass_mail')]}"/>

View File

@ -1,10 +1,10 @@
<openerp>
<data>
<record id="action_dummy" model="ir.actions.server">
<field name="name">Dummy Action</field>
<field name="name">Dummy Python Code</field>
<field name="model_id" ref="crm.model_crm_lead"/>
<field name="state">dummy</field>
<field name="code"/>
<field name="state">code</field>
<field name="code">True</field>
<field eval="5" name="sequence"/>
<field eval="True" name="condition"/>
</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-12-21 17:04+0000\n"
"PO-Revision-Date: 2012-11-28 14:03+0000\n"
"Last-Translator: hifly <Unknown>\n"
"PO-Revision-Date: 2013-08-07 16:09+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@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: 2013-03-16 05:02+0000\n"
"X-Generator: Launchpad (build 16532)\n"
"X-Launchpad-Export-Date: 2013-08-08 04:39+0000\n"
"X-Generator: Launchpad (build 16723)\n"
#. module: mrp
#: help:mrp.config.settings,module_mrp_repair:0
@ -69,7 +69,7 @@ msgstr "周期数"
msgid ""
"The 'Minimum stock rule' allows the system to create procurement orders "
"automatically as soon as the minimum stock is reached."
msgstr "最小库存规则允许系统在达到最小库存数量的时候立即创建补货单"
msgstr "最小库存规则允许系统在达到最小库存数量的时会自动创建补货单"
#. module: mrp
#: code:addons/mrp/report/price.py:130
@ -199,7 +199,7 @@ msgstr "用于外购的物料"
#. module: mrp
#: model:ir.ui.menu,name:mrp.menu_mrp_production_order_action
msgid "Order Planning"
msgstr ""
msgstr "制造单计划"
#. module: mrp
#: field:mrp.config.settings,module_mrp_operations:0
@ -286,7 +286,7 @@ msgstr "从一个生产订单生产几个产品"
msgid ""
"The selection of the right Bill of Material to use will depend on the "
"properties specified on the sales order and the Bill of Material."
msgstr ""
msgstr "被正确使用的BOM依赖于销售单和BOM中指定的属性"
#. module: mrp
#: view:mrp.bom:0
@ -294,7 +294,7 @@ msgid ""
"When processing a sales order for this product, the delivery order\n"
" will contain the raw materials, instead of "
"the finished product."
msgstr ""
msgstr "当处理该产品的销售单时,发运单将以原料来替代成品"
#. module: mrp
#: report:mrp.production.order:0
@ -490,6 +490,16 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 创建新属性\n"
" </p><p>\n"
" 当同一个产品有多种组成的方式,\n"
" OpenERP会根据属性来选择合适的BoM。\n"
" 你可以给每个物料清单分配若干个属性,\n"
" 当销售人员创建销售订单,根据需要指定几个属性,\n"
" 然后OpenERP 会根据其来自动选择相应的BoM。\n"
" </p>\n"
" "
#. module: mrp
#: view:mrp.production:0
@ -499,7 +509,7 @@ msgid "Scheduled Date"
msgstr "下单日期"
#. module: mrp
#: code:addons/mrp/procurement.py:124
#: code:addons/mrp/procurement.py:129
#, python-format
msgid "Manufacturing Order <em>%s</em> created."
msgstr "生产订单 <em>%s</em> 创建。"
@ -684,7 +694,7 @@ msgid "Work Center Load"
msgstr "工作中心负载"
#. module: mrp
#: code:addons/mrp/procurement.py:50
#: code:addons/mrp/procurement.py:55
#, python-format
msgid "No BoM defined for this product !"
msgstr "该产品尚未定义物料清单!"
@ -991,7 +1001,7 @@ msgid "BoM Type"
msgstr "物料清单类型"
#. module: mrp
#: code:addons/mrp/procurement.py:52
#: code:addons/mrp/procurement.py:57
#, python-format
msgid ""
"Procurement '%s' has an exception: 'No BoM defined for this product !'"
@ -1120,7 +1130,7 @@ msgstr "生产订单"
#. module: mrp
#: selection:mrp.production,state:0
msgid "Awaiting Raw Materials"
msgstr ""
msgstr "等待原材料"
#. module: mrp
#: field:mrp.bom,position:0
@ -1629,6 +1639,17 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 点击创建属性组\n"
" </p><p>\n"
" 可以定义特定的属性组用来分配到BoM和销售订单。\n"
" 根据销售人员在销售订单上选取的属性OpenERP可以\n"
" 自动选择相应的BoM。\n"
" </p><p>\n"
" 例如,在属性组“保修” 你有两个属性1年保修3年保修。\n"
" 根据销售订单上的选择OpenERP会自动选择对应的BoM排单生产。\n"
" </p>\n"
" "
#. module: mrp
#: field:mrp.workcenter,capacity_per_cycle:0
@ -1774,8 +1795,8 @@ msgid ""
"the quantity selected and it will finish the production order when total "
"ordered quantities are produced."
msgstr ""
"'废料' 模式会仅消耗对应成品数量的原材料而无产成品产出。\n"
"'报工' 模式会消耗对应成品数量的原材料而且产出对应数量的产成品。"
"'投料' 模式会仅消耗对应成品数量的原材料暂无产成品产出。\n"
"'投料并产出' 模式会消耗对应成品数量的原材料并立即产出对应数量的产成品。"
#. module: mrp
#: view:mrp.production:0
@ -1981,7 +2002,7 @@ msgstr "新建"
#. module: mrp
#: selection:mrp.product.produce,mode:0
msgid "Consume Only"
msgstr "料"
msgstr "料"
#. module: mrp
#: view:mrp.production:0
@ -2161,7 +2182,7 @@ msgstr "用户"
#. module: mrp
#: selection:mrp.product.produce,mode:0
msgid "Consume & Produce"
msgstr "报工"
msgstr "投料并产出"
#. module: mrp
#: field:mrp.bom,bom_id:0
@ -2191,7 +2212,7 @@ msgstr "产品类型是可库存或消耗品"
#. module: mrp
#: selection:mrp.production,state:0
msgid "Production Started"
msgstr "生产开始了"
msgstr "已开始生产"
#. module: mrp
#: model:process.node,name:mrp.process_node_procureproducts0

View File

@ -246,6 +246,7 @@ class mrp_production(osv.osv):
""" Cancels work order if production order is canceled.
@return: Super method
"""
workcenter_pool = self.pool.get('mrp.production.workcenter.line')
obj = self.browse(cr, uid, ids,context=context)[0]
for workcenter_line in obj.workcenter_lines:
workcenter_pool.signal_button_cancel(cr, uid, [workcenter_line.id])

284
addons/note/i18n/bs.po Normal file
View File

@ -0,0 +1,284 @@
# Bosnian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:04+0000\n"
"PO-Revision-Date: 2013-08-06 16:51+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Bosnian <bs@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
"X-Generator: Launchpad (build 16721)\n"
#. module: note
#: field:note.note,memo:0
msgid "Note Content"
msgstr ""
#. module: note
#: view:note.stage:0
msgid "Stages of Notes"
msgstr ""
#. module: note
#: model:note.stage,name:note.demo_note_stage_04
#: model:note.stage,name:note.note_stage_02
msgid "This Week"
msgstr ""
#. module: note
#: model:ir.model,name:note.model_base_config_settings
msgid "base.config.settings"
msgstr ""
#. module: note
#: model:ir.model,name:note.model_note_tag
msgid "Note Tag"
msgstr ""
#. module: note
#: model:res.groups,name:note.group_note_fancy
msgid "Notes / Fancy mode"
msgstr ""
#. module: note
#: model:ir.model,name:note.model_note_note
#: view:note.note:0
msgid "Note"
msgstr ""
#. module: note
#: view:note.note:0
msgid "Group By..."
msgstr ""
#. module: note
#: field:note.note,message_follower_ids:0
msgid "Followers"
msgstr ""
#. module: note
#: model:ir.actions.act_window,help:note.action_note_note
msgid ""
"<p class=\"oe_view_nocontent_create\">\n"
" Click to add a personal note.\n"
" </p><p>\n"
" Use notes to organize personal tasks or notes. All\n"
" notes are private; no one else will be able to see them. "
"However\n"
" you can share some notes with other people by inviting "
"followers\n"
" on the note. (Useful for meeting minutes, especially if\n"
" you activate the pad feature for collaborative writings).\n"
" </p><p>\n"
" You can customize how you process your notes/tasks by adding,\n"
" removing or modifying columns.\n"
" </p>\n"
" "
msgstr ""
#. module: note
#: model:note.stage,name:note.demo_note_stage_01
#: model:note.stage,name:note.note_stage_01
msgid "Today"
msgstr ""
#. module: note
#: model:ir.model,name:note.model_res_users
msgid "Users"
msgstr ""
#. module: note
#: view:note.note:0
msgid "í"
msgstr ""
#. module: note
#: view:note.stage:0
msgid "Stage of Notes"
msgstr ""
#. module: note
#: field:note.note,message_unread:0
msgid "Unread Messages"
msgstr ""
#. module: note
#: field:note.note,current_partner_id:0
msgid "unknown"
msgstr ""
#. module: note
#: view:note.note:0
msgid "By sticky note Category"
msgstr ""
#. module: note
#: help:note.note,message_unread:0
msgid "If checked new messages require your attention."
msgstr ""
#. module: note
#: field:note.stage,name:0
msgid "Stage Name"
msgstr ""
#. module: note
#: field:note.note,message_is_follower:0
msgid "Is a Follower"
msgstr ""
#. module: note
#: model:note.stage,name:note.demo_note_stage_02
msgid "Tomorrow"
msgstr ""
#. module: note
#: view:note.note:0
#: field:note.note,open:0
msgid "Active"
msgstr ""
#. module: note
#: help:note.stage,user_id:0
msgid "Owner of the note stage."
msgstr ""
#. module: note
#: model:ir.ui.menu,name:note.menu_notes_stage
msgid "Categories"
msgstr ""
#. module: note
#: view:note.note:0
#: field:note.note,stage_id:0
msgid "Stage"
msgstr ""
#. module: note
#: field:note.tag,name:0
msgid "Tag Name"
msgstr ""
#. module: note
#: field:note.note,message_ids:0
msgid "Messages"
msgstr ""
#. module: note
#: view:base.config.settings:0
#: model:ir.actions.act_window,name:note.action_note_note
#: model:ir.ui.menu,name:note.menu_note_notes
#: view:note.note:0
#: model:note.stage,name:note.note_stage_04
msgid "Notes"
msgstr ""
#. module: note
#: model:note.stage,name:note.demo_note_stage_03
#: model:note.stage,name:note.note_stage_03
msgid "Later"
msgstr ""
#. module: note
#: model:ir.model,name:note.model_note_stage
msgid "Note Stage"
msgstr ""
#. module: note
#: field:note.note,message_summary:0
msgid "Summary"
msgstr ""
#. module: note
#: field:note.note,stage_ids:0
msgid "Stages of Users"
msgstr ""
#. module: note
#: field:note.note,name:0
msgid "Note Summary"
msgstr ""
#. module: note
#: model:ir.actions.act_window,name:note.action_note_stage
#: view:note.note:0
msgid "Stages"
msgstr ""
#. module: note
#: help:note.note,message_ids:0
msgid "Messages and communication history"
msgstr ""
#. module: note
#: view:note.note:0
msgid "Delete"
msgstr ""
#. module: note
#: field:note.note,color:0
msgid "Color Index"
msgstr ""
#. module: note
#: field:note.note,sequence:0
#: field:note.stage,sequence:0
msgid "Sequence"
msgstr ""
#. module: note
#: view:note.note:0
#: field:note.note,tag_ids:0
msgid "Tags"
msgstr ""
#. module: note
#: view:note.note:0
msgid "Archive"
msgstr ""
#. module: note
#: field:base.config.settings,module_note_pad:0
msgid "Use collaborative pads (etherpad)"
msgstr ""
#. module: note
#: help:note.note,message_summary:0
msgid ""
"Holds the Chatter summary (number of messages, ...). This summary is "
"directly in html format in order to be inserted in kanban views."
msgstr ""
#. module: note
#: field:base.config.settings,group_note_fancy:0
msgid "Use fancy layouts for notes"
msgstr ""
#. module: note
#: field:note.note,current_partner_id:0
#: field:note.stage,user_id:0
msgid "Owner"
msgstr ""
#. module: note
#: help:note.stage,sequence:0
msgid "Used to order the note stages"
msgstr ""
#. module: note
#: field:note.note,date_done:0
msgid "Date done"
msgstr ""
#. module: note
#: field:note.stage,fold:0
msgid "Folded by Default"
msgstr ""

View File

@ -6,7 +6,7 @@
<field name="inherit_id" ref="project.view_task_form2"/>
<field name="arch" type="xml">
<field name="description" position="replace">
<field name="description_pad" attrs="{'readonly':[('state','=','done')]}" widget="pad"/>
<field name="description_pad" widget="pad"/>
</field>
</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-12-21 17:04+0000\n"
"PO-Revision-Date: 2013-04-24 06:41+0000\n"
"Last-Translator: viney <Unknown>\n"
"PO-Revision-Date: 2013-08-06 09:45+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@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: 2013-04-25 05:20+0000\n"
"X-Generator: Launchpad (build 16580)\n"
"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
"X-Generator: Launchpad (build 16721)\n"
#. module: point_of_sale
#: field:report.transaction.pos,product_nb:0
@ -42,6 +42,15 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 单击定义一个新的产品类别。\n"
" </p><p>\n"
" 产品类别用于通过触摸屏浏览产品。\n"
" </p><p>\n"
" "
"为产品类别上传图片后触摸屏布局将会自动调整。因此针对分辨率小于1024x768的显示设备我们建议不要为产品类别上传图片。\n"
" </p>\n"
" "
#. module: point_of_sale
#: view:pos.receipt:0
@ -51,7 +60,7 @@ msgstr "打印销售订单的收据"
#. module: point_of_sale
#: field:pos.session,cash_register_balance_end:0
msgid "Computed Balance"
msgstr ""
msgstr "计算余额"
#. module: point_of_sale
#: view:pos.session:0
@ -62,7 +71,7 @@ msgstr "今天"
#. module: point_of_sale
#: field:pos.config,iface_electronic_scale:0
msgid "Electronic Scale Interface"
msgstr ""
msgstr "电子称接口"
#. module: point_of_sale
#: model:pos.category,name:point_of_sale.plain_water
@ -85,7 +94,7 @@ msgstr ""
#: field:pos.config,journal_id:0
#: field:pos.order,sale_journal:0
msgid "Sale Journal"
msgstr ""
msgstr "销售日记账"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.spa_2l_product_template
@ -102,7 +111,7 @@ msgstr "销售详情"
#. module: point_of_sale
#: constraint:pos.config:0
msgid "You cannot have two cash controls in one Point Of Sale !"
msgstr ""
msgstr "同一个销售点不能使用两个钱箱"
#. module: point_of_sale
#: field:pos.payment.report.user,user_id:0
@ -111,7 +120,7 @@ msgstr ""
#: view:report.pos.order:0
#: field:report.pos.order,user_id:0
msgid "Salesperson"
msgstr ""
msgstr "销售员"
#. module: point_of_sale
#: view:report.pos.order:0
@ -128,13 +137,13 @@ msgstr "产品名称"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.pamplemousse_rouge_pamplemousse_product_template
msgid "Red grapefruit"
msgstr ""
msgstr "红葡萄柚"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1343
#: code:addons/point_of_sale/point_of_sale.py:1373
#, python-format
msgid "Assign a Custom EAN"
msgstr ""
msgstr "使用自定义的条码"
#. module: point_of_sale
#: view:pos.session.opening:0
@ -164,12 +173,12 @@ msgstr "取钱"
#: code:addons/point_of_sale/point_of_sale.py:105
#, python-format
msgid "not used"
msgstr ""
msgstr "未被使用"
#. module: point_of_sale
#: field:pos.config,iface_vkeyboard:0
msgid "Virtual KeyBoard Interface"
msgstr ""
msgstr "虚拟键盘接口"
#. module: point_of_sale
#. openerp-web
@ -181,11 +190,11 @@ msgstr "+/-"
#. module: point_of_sale
#: field:pos.ean_wizard,ean13_pattern:0
msgid "Reference"
msgstr ""
msgstr "单号"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1041
#: code:addons/point_of_sale/point_of_sale.py:1057
#: code:addons/point_of_sale/point_of_sale.py:1066
#: code:addons/point_of_sale/point_of_sale.py:1083
#: report:pos.invoice:0
#: report:pos.lines:0
#, python-format
@ -200,32 +209,32 @@ msgstr "开始日期"
#. module: point_of_sale
#: constraint:pos.session:0
msgid "You cannot create two active sessions with the same responsible!"
msgstr ""
msgstr "同一个负责人不能创建两个活动销售会话"
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:473
#, python-format
msgid "Weighting"
msgstr ""
msgstr "称重"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.fenouil_fenouil_product_template
msgid "Fennel"
msgstr ""
msgstr "小茴香"
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:472
#, python-format
msgid "Help needed"
msgstr ""
msgstr "需要帮助"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:739
#: code:addons/point_of_sale/point_of_sale.py:760
#, python-format
msgid "Configuration Error!"
msgstr ""
msgstr "设置错误!"
#. module: point_of_sale
#: report:account.statement:0
@ -237,7 +246,7 @@ msgstr "业务伙伴"
#. module: point_of_sale
#: view:pos.session:0
msgid "Closing Cash Control"
msgstr ""
msgstr "关闭现金箱"
#. module: point_of_sale
#: report:pos.details:0
@ -259,7 +268,7 @@ msgstr "会计信息"
#. module: point_of_sale
#: field:pos.session.opening,show_config:0
msgid "Show Config"
msgstr ""
msgstr "显示配置"
#. module: point_of_sale
#: report:pos.lines:0
@ -277,7 +286,7 @@ msgstr "折扣合计"
#: code:addons/point_of_sale/static/src/xml/pos.xml:441
#, python-format
msgid "Debug Window"
msgstr ""
msgstr "调试窗口"
#. module: point_of_sale
#. openerp-web
@ -285,7 +294,7 @@ msgstr ""
#: code:addons/point_of_sale/static/src/xml/pos.xml:613
#, python-format
msgid "Change:"
msgstr "零钞"
msgstr "找零:"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.coca_regular_2l_product_template
@ -315,10 +324,10 @@ msgid "Disc.(%)"
msgstr "折扣(%)"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1006
#: code:addons/point_of_sale/point_of_sale.py:1031
#, python-format
msgid "Please define income account for this product: \"%s\" (id:%d)."
msgstr ""
msgstr "请为这个产品定义收益科目:\"%s\" (id:%d)."
#. module: point_of_sale
#: view:report.pos.order:0
@ -336,7 +345,7 @@ msgstr "Leffe Brune 33cl"
msgid ""
"Check this if this point of sale should open by default in a self checkout "
"mode. If unchecked, OpenERP uses the normal cashier mode by default."
msgstr ""
msgstr "如果需要把自助收银作为默认模式,请选中这里。 如果未选, OpenERP 将使用普通收银员作为默认模式。"
#. module: point_of_sale
#: model:ir.actions.report.xml,name:point_of_sale.pos_sales_user
@ -353,7 +362,7 @@ msgstr "饮料类"
#: model:ir.actions.act_window,name:point_of_sale.action_pos_session_opening
#: model:ir.ui.menu,name:point_of_sale.menu_pos_session_opening
msgid "Your Session"
msgstr ""
msgstr "您的销售会话"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.stella_50cl_product_template
@ -375,12 +384,12 @@ msgstr "上级类别"
#: code:addons/point_of_sale/static/src/xml/pos.xml:482
#, python-format
msgid "Open Cashbox"
msgstr ""
msgstr "打开钱箱"
#. module: point_of_sale
#: view:pos.session.opening:0
msgid "Select your Point of Sale"
msgstr ""
msgstr "选择您的销售点"
#. module: point_of_sale
#: field:report.sales.by.margin.pos,total:0
@ -404,25 +413,25 @@ msgstr "Dr. Oetker Ristorante Speciale"
#: code:addons/point_of_sale/static/src/xml/pos.xml:480
#, python-format
msgid "Payment Request"
msgstr ""
msgstr "付款请求"
#. module: point_of_sale
#: field:product.product,to_weight:0
msgid "To Weight"
msgstr ""
msgstr "需要称重"
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:476
#, python-format
msgid "Hardware Events"
msgstr ""
msgstr "硬件事件"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:301
#, python-format
msgid "You should assign a Point of Sale to your session."
msgstr ""
msgstr "您需要为您的销售会话指定一个销售点"
#. module: point_of_sale
#: view:pos.order.line:0
@ -435,7 +444,7 @@ msgid "Fanta Orange 33cl"
msgstr "Fanta Orange 33cl"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:420
#: code:addons/point_of_sale/point_of_sale.py:423
#, python-format
msgid ""
"Please set your profit and loss accounts on your payment method '%s'. This "
@ -446,10 +455,10 @@ msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:315
#: code:addons/point_of_sale/point_of_sale.py:512
#: code:addons/point_of_sale/point_of_sale.py:514
#, python-format
msgid "error!"
msgstr ""
msgstr "错误!"
#. module: point_of_sale
#: model:ir.actions.act_window,name:point_of_sale.action_report_sales_by_user_pos_month
@ -471,12 +480,12 @@ msgstr ""
#. module: point_of_sale
#: model:product.template,name:point_of_sale.Onions_product_template
msgid "Onions"
msgstr ""
msgstr "洋葱"
#. module: point_of_sale
#: view:pos.session:0
msgid "Validate & Open Session"
msgstr ""
msgstr "验证并打开销售会话"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:99
@ -484,18 +493,18 @@ msgstr ""
#: selection:pos.session.opening,pos_state:0
#, python-format
msgid "In Progress"
msgstr ""
msgstr "进行中"
#. module: point_of_sale
#: view:pos.session:0
#: field:pos.session,opening_details_ids:0
msgid "Opening Cash Control"
msgstr ""
msgstr "正在打开钱箱"
#. module: point_of_sale
#: help:res.users,ean13:0
msgid "BarCode"
msgstr ""
msgstr "条码"
#. module: point_of_sale
#: help:pos.category,image_medium:0
@ -508,19 +517,19 @@ msgstr ""
#. module: point_of_sale
#: view:pos.session.opening:0
msgid "Open Session"
msgstr ""
msgstr "打开销售会话"
#. module: point_of_sale
#: model:ir.ui.menu,name:point_of_sale.menu_point_of_sale
msgid "Daily Operations"
msgstr "每天经营"
msgstr "日常经营"
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:42
#, python-format
msgid "Google Chrome"
msgstr ""
msgstr "Google Chrome"
#. module: point_of_sale
#: model:pos.category,name:point_of_sale.sparkling_water
@ -543,7 +552,7 @@ msgstr "查询现金对账单"
#: field:pos.session.opening,pos_state_str:0
#: field:report.pos.order,state:0
msgid "Status"
msgstr ""
msgstr "状态"
#. module: point_of_sale
#: selection:report.pos.order,month:0
@ -568,14 +577,14 @@ msgstr "POS单明细"
#. module: point_of_sale
#: view:pos.config:0
msgid "Point of Sale Configuration"
msgstr ""
msgstr "销售点配置"
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:359
#, python-format
msgid "Your order has to be validated by a cashier."
msgstr ""
msgstr "您的订单已被收银员确认"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.fanta_orange_50cl_product_template
@ -609,7 +618,7 @@ msgid ""
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:867
#: code:addons/point_of_sale/point_of_sale.py:888
#, python-format
msgid "Customer Invoice"
msgstr "客户发票"
@ -619,7 +628,7 @@ msgstr "客户发票"
msgid ""
"You can continue sales from the touchscreen interface by clicking on \"Start "
"Selling\" or close the cash register session."
msgstr ""
msgstr "通过触摸屏幕的“开始销售”或者关闭钱箱之后您就可以继续进行销售。"
#. module: point_of_sale
#: report:account.statement:0
@ -631,7 +640,7 @@ msgstr "结束日期"
#. module: point_of_sale
#: view:pos.session:0
msgid "Opening Cashbox Lines"
msgstr ""
msgstr "正在打开钱箱"
#. module: point_of_sale
#: selection:report.pos.order,month:0
@ -678,7 +687,7 @@ msgstr "行号"
#: code:addons/point_of_sale/static/src/xml/pos.xml:453
#, python-format
msgid "Set Weight"
msgstr ""
msgstr "设置重量"
#. module: point_of_sale
#: view:account.bank.statement:0
@ -693,7 +702,7 @@ msgstr "净合计:"
#. module: point_of_sale
#: model:ir.actions.client,name:point_of_sale.action_client_pos_menu
msgid "Open POS Menu"
msgstr ""
msgstr "打开销售点菜单"
#. module: point_of_sale
#: report:pos.details_summary:0
@ -710,12 +719,12 @@ msgstr "发表POS机日记帐分录"
#: code:addons/point_of_sale/static/src/xml/pos.xml:457
#, python-format
msgid "Barcode Scanner"
msgstr ""
msgstr "条码扫描器"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.pomme_granny_smith_product_template
msgid "Granny Smith apples"
msgstr ""
msgstr "Granny Smith 苹果"
#. module: point_of_sale
#: help:product.product,expense_pdt:0
@ -739,11 +748,13 @@ msgid ""
"use\n"
" a modern browser like"
msgstr ""
"销售点功能不支持 Microsoft Internet Explorer 浏览器请使用更现代化的浏览器如Mozilla Firefox 和 "
"Google Chrome 等"
#. module: point_of_sale
#: view:pos.session.opening:0
msgid "Click to start a session."
msgstr ""
msgstr "单击开始销售会话"
#. module: point_of_sale
#: view:pos.details:0
@ -778,7 +789,7 @@ msgstr "新增产品"
#. module: point_of_sale
#: field:pos.config,name:0
msgid "Point of Sale Name"
msgstr ""
msgstr "销售点名称"
#. module: point_of_sale
#: field:report.transaction.pos,invoice_am:0
@ -825,7 +836,7 @@ msgstr "期末结余"
#: code:addons/point_of_sale/wizard/pos_box_out.py:89
#, python-format
msgid "please check that account is set to %s."
msgstr ""
msgstr "请检查该科目已经设为为:%s"
#. module: point_of_sale
#: help:pos.category,image:0
@ -863,7 +874,7 @@ msgstr ""
#. module: point_of_sale
#: view:pos.ean_wizard:0
msgid "Ean13 Generator"
msgstr ""
msgstr "条码生成器"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.spa_1l_product_template
@ -879,7 +890,7 @@ msgstr "错误无效的EAN编码"
#. module: point_of_sale
#: model:pos.category,name:point_of_sale.legumes_racine
msgid "Root vegetables"
msgstr ""
msgstr "根类蔬菜"
#. module: point_of_sale
#: model:ir.actions.act_window,name:point_of_sale.act_pos_open_statement
@ -934,12 +945,12 @@ msgstr "付款合计"
#. module: point_of_sale
#: model:ir.model,name:point_of_sale.model_pos_session_opening
msgid "pos.session.opening"
msgstr ""
msgstr "pos.session.opening"
#. module: point_of_sale
#: view:res.users:0
msgid "Edit EAN"
msgstr ""
msgstr "编辑条码"
#. module: point_of_sale
#: code:addons/point_of_sale/wizard/pos_open_statement.py:80
@ -965,7 +976,7 @@ msgstr ""
#. module: point_of_sale
#: field:pos.session.opening,pos_session_id:0
msgid "PoS Session"
msgstr ""
msgstr "销售点会话"
#. module: point_of_sale
#: selection:report.pos.order,month:0
@ -979,7 +990,7 @@ msgid "User's Product"
msgstr "用户的产品"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1143
#: code:addons/point_of_sale/point_of_sale.py:1173
#, python-format
msgid ""
"You have to select a pricelist in the sale form !\n"
@ -1011,7 +1022,7 @@ msgstr "增加一个全局折扣"
#. module: point_of_sale
#: view:pos.config:0
msgid "Journals"
msgstr ""
msgstr "日记账"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.oetker_prosciutto_product_template
@ -1031,7 +1042,7 @@ msgstr "Coca-Cola Light Lemon 50cl"
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/point_of_sale.py:518
#: code:addons/point_of_sale/point_of_sale.py:520
#: code:addons/point_of_sale/static/src/xml/pos.xml:689
#: code:addons/point_of_sale/static/src/xml/pos.xml:744
#, python-format
@ -1041,7 +1052,7 @@ msgstr ""
#. module: point_of_sale
#: view:product.product:0
msgid "Set a Custom EAN"
msgstr ""
msgstr "设置自定义条码"
#. module: point_of_sale
#. openerp-web
@ -1053,7 +1064,7 @@ msgstr ""
#. module: point_of_sale
#: model:pos.category,name:point_of_sale.legumes
msgid "Fresh vegetables"
msgstr ""
msgstr "新鲜蔬菜"
#. module: point_of_sale
#: view:pos.session:0
@ -1065,7 +1076,7 @@ msgstr ""
#: code:addons/point_of_sale/static/src/xml/pos.xml:478
#, python-format
msgid "Scan Item Success"
msgstr ""
msgstr "扫描条目成功"
#. module: point_of_sale
#: report:account.statement:0
@ -1077,22 +1088,22 @@ msgstr "期初余额"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.lays_naturel_oven_150g_product_template
msgid "Oven Baked Lays Natural 150g"
msgstr ""
msgstr "Oven Baked Lays Natural 150g"
#. module: point_of_sale
#: sql_constraint:pos.session:0
msgid "The name of this POS Session must be unique !"
msgstr ""
msgstr "销售会话名称必须唯一"
#. module: point_of_sale
#: view:pos.session:0
msgid "Opening Subtotal"
msgstr ""
msgstr "小计"
#. module: point_of_sale
#: view:pos.session:0
msgid "payment method."
msgstr ""
msgstr "付款方式"
#. module: point_of_sale
#: view:pos.order:0
@ -1137,12 +1148,12 @@ msgstr "折扣"
#. module: point_of_sale
#: view:pos.order:0
msgid "(update)"
msgstr ""
msgstr "(更新)"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.ijsboerke_vanille_2,5l_product_template
msgid "IJsboerke Vanilla 2.5L"
msgstr ""
msgstr "IJsboerke Vanilla 2.5L"
#. module: point_of_sale
#: model:ir.actions.act_window,name:point_of_sale.action_report_pos_details
@ -1153,15 +1164,15 @@ msgstr "销售详情"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.evian_2l_product_template
msgid "2L Evian"
msgstr ""
msgstr "2L 依云"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:373
#: code:addons/point_of_sale/point_of_sale.py:472
#: code:addons/point_of_sale/wizard/pos_session_opening.py:34
#: code:addons/point_of_sale/point_of_sale.py:474
#: code:addons/point_of_sale/wizard/pos_session_opening.py:33
#, python-format
msgid "Start Point Of Sale"
msgstr ""
msgstr "启动销售点"
#. module: point_of_sale
#: model:pos.category,name:point_of_sale.pils
@ -1215,14 +1226,14 @@ msgstr "Chaudfontaine Petillante 50cl"
#: code:addons/point_of_sale/static/src/xml/pos.xml:485
#, python-format
msgid "Read Weighting Scale"
msgstr ""
msgstr "读取电子秤"
#. module: point_of_sale
#. openerp-web
#: code:addons/point_of_sale/static/src/xml/pos.xml:426
#, python-format
msgid "0.00 €"
msgstr ""
msgstr "0.00 €"
#. module: point_of_sale
#: field:pos.order.line,create_date:0
@ -1240,7 +1251,7 @@ msgstr "今天的销售情况"
#: code:addons/point_of_sale/static/src/xml/pos.xml:324
#, python-format
msgid "Welcome"
msgstr ""
msgstr "欢迎"
#. module: point_of_sale
#: code:addons/point_of_sale/wizard/pos_box_entries.py:46
@ -1295,7 +1306,7 @@ msgstr "已开票合计"
#: model:ir.model,name:point_of_sale.model_pos_category
#: field:product.product,pos_categ_id:0
msgid "Point of Sale Category"
msgstr ""
msgstr "销售点商品分类"
#. module: point_of_sale
#: view:report.pos.order:0
@ -1315,7 +1326,7 @@ msgstr ""
#: code:addons/point_of_sale/static/src/xml/pos.xml:324
#, python-format
msgid "Choose your type of receipt:"
msgstr ""
msgstr "选择小票类型"
#. module: point_of_sale
#: model:ir.model,name:point_of_sale.model_report_sales_by_margin_pos_month
@ -1352,7 +1363,7 @@ msgstr ""
#: code:addons/point_of_sale/static/src/xml/pos.xml:479
#, python-format
msgid "Scan Item Unrecognized"
msgstr ""
msgstr "未能识别扫描的条目"
#. module: point_of_sale
#: report:all.closed.cashbox.of.the.day:0
@ -1360,7 +1371,7 @@ msgid "Today's Closed Cashbox"
msgstr "今日关闭的钱箱"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:897
#: code:addons/point_of_sale/point_of_sale.py:920
#, python-format
msgid "Selected orders do not have the same session!"
msgstr ""
@ -1396,7 +1407,7 @@ msgstr "开启日期"
#: model:ir.actions.act_window,name:point_of_sale.action_pos_session
#: model:ir.ui.menu,name:point_of_sale.menu_pos_session_all
msgid "All Sessions"
msgstr ""
msgstr "全部销售会话"
#. module: point_of_sale
#. openerp-web
@ -1415,7 +1426,7 @@ msgstr "税:"
#: code:addons/point_of_sale/static/src/xml/pos.xml:271
#, python-format
msgid "Thank you for shopping with us."
msgstr ""
msgstr "感谢您的光临。"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.coca_light_2l_product_template
@ -1431,7 +1442,7 @@ msgstr "Dr. Oetker Ristorante Funghi"
#: model:ir.actions.act_window,name:point_of_sale.pos_category_action
#: model:ir.ui.menu,name:point_of_sale.menu_pos_category
msgid "Product Categories"
msgstr ""
msgstr "商品类别"
#. module: point_of_sale
#: help:pos.config,journal_id:0
@ -1448,7 +1459,7 @@ msgstr "折扣"
#: code:addons/point_of_sale/static/src/xml/pos.xml:467
#, python-format
msgid "Invalid Ean"
msgstr ""
msgstr "无效的条码"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.lindemans_kriek_37,5cl_product_template
@ -1458,7 +1469,7 @@ msgstr "Lindemans Kriek 37.5cl"
#. module: point_of_sale
#: view:pos.config:0
msgid "Point of Sale Config"
msgstr ""
msgstr "销售点配置"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.coca_zero_33cl_product_template
@ -1511,7 +1522,7 @@ msgstr "税:"
#. module: point_of_sale
#: view:pos.session:0
msgid "+ Transactions"
msgstr ""
msgstr "+ 交易"
#. module: point_of_sale
#: model:ir.actions.act_window,name:point_of_sale.action_pos_discount
@ -1541,12 +1552,12 @@ msgstr "用户"
#: code:addons/point_of_sale/static/src/xml/pos.xml:194
#, python-format
msgid "Kg"
msgstr ""
msgstr "千克"
#. module: point_of_sale
#: field:product.product,available_in_pos:0
msgid "Available in the Point of Sale"
msgstr ""
msgstr "可在销售点销售"
#. module: point_of_sale
#: selection:pos.config,state:0
@ -1563,7 +1574,7 @@ msgstr ""
#: code:addons/point_of_sale/static/src/xml/pos.xml:338
#, python-format
msgid "The scanned product was not recognized"
msgstr ""
msgstr "无法识别扫描的商品"
#. module: point_of_sale
#: model:ir.model,name:point_of_sale.model_report_transaction_pos
@ -1613,7 +1624,7 @@ msgstr "11月"
#: code:addons/point_of_sale/static/src/xml/pos.xml:267
#, python-format
msgid "Please scan an item or your member card"
msgstr ""
msgstr "请扫描商品或您的会员卡"
#. module: point_of_sale
#: model:product.template,name:point_of_sale.poivron_verts_product_template
@ -1644,7 +1655,7 @@ msgid "Number of Transaction"
msgstr "交易数"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:738
#: code:addons/point_of_sale/point_of_sale.py:759
#, python-format
msgid ""
"There is no receivable account defined to make payment for the partner: "
@ -1655,7 +1666,7 @@ msgstr ""
#: view:pos.config:0
#: selection:pos.config,state:0
msgid "Inactive"
msgstr ""
msgstr "不活跃的"
#. module: point_of_sale
#. openerp-web
@ -1679,7 +1690,7 @@ msgstr "取消"
#: code:addons/point_of_sale/static/src/xml/pos.xml:296
#, python-format
msgid "Please put your product on the scale"
msgstr ""
msgstr "请将商品放到称上"
#. module: point_of_sale
#: model:ir.actions.report.xml,name:point_of_sale.pos_details_summary
@ -1699,7 +1710,7 @@ msgstr "Timmermans Kriek 37.5cl"
#. module: point_of_sale
#: field:pos.config,sequence_id:0
msgid "Order IDs Sequence"
msgstr ""
msgstr "订单号序列"
#. module: point_of_sale
#: report:pos.invoice:0
@ -1815,7 +1826,7 @@ msgid "Difference"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:529
#: code:addons/point_of_sale/point_of_sale.py:549
#, python-format
msgid "Unable to Delete !"
msgstr "不能删除!"
@ -1978,7 +1989,7 @@ msgid "Salespeople"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:756
#: code:addons/point_of_sale/point_of_sale.py:777
#: code:addons/point_of_sale/wizard/pos_box_entries.py:118
#: code:addons/point_of_sale/wizard/pos_box_out.py:91
#, python-format
@ -1986,7 +1997,7 @@ msgid "You have to open at least one cashbox."
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1142
#: code:addons/point_of_sale/point_of_sale.py:1172
#, python-format
msgid "No Pricelist !"
msgstr "没有价格表!"
@ -2038,7 +2049,7 @@ msgid "No Cash Register Defined !"
msgstr "没有定义收银机"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:513
#: code:addons/point_of_sale/point_of_sale.py:515
#, python-format
msgid ""
"No cash statement found for this session. Unable to record returned cash."
@ -2761,7 +2772,7 @@ msgid "Pos Lines"
msgstr "销售单明细"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:413
#: code:addons/point_of_sale/point_of_sale.py:416
#, python-format
msgid "Point of Sale Profit"
msgstr ""
@ -2774,7 +2785,7 @@ msgid "Please wait, a cashier is on the way"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/wizard/pos_session_opening.py:68
#: code:addons/point_of_sale/wizard/pos_session_opening.py:67
#: field:pos.box.entries,session_id:0
#: view:pos.order:0
#: field:pos.order,session_id:0
@ -3085,15 +3096,17 @@ msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:300
#: code:addons/point_of_sale/point_of_sale.py:409
#: code:addons/point_of_sale/point_of_sale.py:419
#: code:addons/point_of_sale/point_of_sale.py:457
#: code:addons/point_of_sale/point_of_sale.py:712
#: code:addons/point_of_sale/point_of_sale.py:756
#: code:addons/point_of_sale/point_of_sale.py:818
#: code:addons/point_of_sale/point_of_sale.py:897
#: code:addons/point_of_sale/point_of_sale.py:1006
#: code:addons/point_of_sale/point_of_sale.py:412
#: code:addons/point_of_sale/point_of_sale.py:422
#: code:addons/point_of_sale/point_of_sale.py:459
#: code:addons/point_of_sale/point_of_sale.py:536
#: code:addons/point_of_sale/point_of_sale.py:733
#: code:addons/point_of_sale/point_of_sale.py:777
#: code:addons/point_of_sale/point_of_sale.py:839
#: code:addons/point_of_sale/point_of_sale.py:920
#: code:addons/point_of_sale/point_of_sale.py:1031
#: code:addons/point_of_sale/report/pos_invoice.py:46
#: code:addons/point_of_sale/wizard/pos_box.py:23
#: code:addons/point_of_sale/wizard/pos_box.py:22
#: code:addons/point_of_sale/wizard/pos_box_entries.py:46
#: code:addons/point_of_sale/wizard/pos_box_entries.py:118
#: code:addons/point_of_sale/wizard/pos_box_entries.py:123
@ -3187,13 +3200,13 @@ msgid "Chaudfontaine 1.5l"
msgstr "Chaudfontaine 1.5l"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:1069
#: code:addons/point_of_sale/point_of_sale.py:1096
#, python-format
msgid "Trade Receivables"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:529
#: code:addons/point_of_sale/point_of_sale.py:549
#, python-format
msgid "In order to delete a sale, it must be new or cancelled."
msgstr "只有新的或被取消的销售流水可以删除"
@ -3228,7 +3241,7 @@ msgid "Journal Entry"
msgstr "日记账簿"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:736
#: code:addons/point_of_sale/point_of_sale.py:757
#, python-format
msgid "There is no receivable account defined to make payment."
msgstr ""
@ -3259,7 +3272,7 @@ msgid "Supplier Invoice"
msgstr "供应商发票"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:458
#: code:addons/point_of_sale/point_of_sale.py:460
#, python-format
msgid ""
"You cannot confirm all orders of this session, because they have not the "
@ -3338,7 +3351,7 @@ msgid "Coca-Cola Regular 1L"
msgstr "Coca-Cola Regular 1L"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:712
#: code:addons/point_of_sale/point_of_sale.py:733
#, python-format
msgid "Unable to cancel the picking."
msgstr "无法取消发货"
@ -3379,7 +3392,7 @@ msgid "Spa Barisart 1.5l"
msgstr "Spa Barisart 1.5l"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:789
#: code:addons/point_of_sale/point_of_sale.py:810
#: view:pos.order:0
#, python-format
msgid "Return Products"
@ -3418,7 +3431,7 @@ msgstr "销售明细"
#: view:res.partner:0
#: view:res.users:0
msgid "Point of Sale"
msgstr "POS"
msgstr "销售点"
#. module: point_of_sale
#: view:pos.order:0
@ -3495,7 +3508,7 @@ msgid "Default Point of Sale"
msgstr ""
#. module: point_of_sale
#: code:addons/point_of_sale/wizard/pos_box.py:24
#: code:addons/point_of_sale/wizard/pos_box.py:23
#, python-format
msgid "There is no cash register for this PoS Session"
msgstr ""
@ -3539,7 +3552,7 @@ msgid "PRO-FORMA"
msgstr "形式发票"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:818
#: code:addons/point_of_sale/point_of_sale.py:839
#, python-format
msgid "Please provide a partner for the sale."
msgstr "请提供业务伙伴。"
@ -3591,7 +3604,7 @@ msgid "Print Receipt"
msgstr "打印收银条"
#. module: point_of_sale
#: code:addons/point_of_sale/point_of_sale.py:417
#: code:addons/point_of_sale/point_of_sale.py:420
#, python-format
msgid "Point of Sale Loss"
msgstr ""
@ -4020,6 +4033,7 @@ msgstr ""
#~ msgid "Discount percentage"
#~ msgstr "折扣百分比"
#, python-format
#~ msgid "Close"
#~ msgstr "关闭"
@ -4843,9 +4857,11 @@ msgstr ""
#~ msgid "Subtotal:"
#~ msgstr "小计:"
#, python-format
#~ msgid "Validate"
#~ msgstr "确认"
#, python-format
#~ msgid "Print"
#~ msgstr "打印"
@ -4931,6 +4947,7 @@ msgstr ""
#~ msgid "PoS Backend"
#~ msgstr "POS后端"
#, python-format
#~ msgid "Next Order"
#~ msgstr "下一订单"

View File

@ -0,0 +1,546 @@
# Bosnian translation for openobject-addons
# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2013.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
"PO-Revision-Date: 2013-08-06 08:35+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Bosnian <bs@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
"X-Generator: Launchpad (build 16721)\n"
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,type:0
msgid "Lead"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,title:0
msgid "Title"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,probability:0
msgid "Success Rate (%)"
msgstr ""
#. module: portal_crm
#: view:portal_crm.crm_contact_us:0
msgid "Contact us"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,date_action:0
msgid "Next Action Date"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,fax:0
msgid "Fax"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,zip:0
msgid "Zip"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,message_unread:0
msgid "Unread Messages"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,company_id:0
msgid "Company"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,day_open:0
msgid "Days to Open"
msgstr ""
#. module: portal_crm
#: view:portal_crm.crm_contact_us:0
msgid "Thank you for your interest, we'll respond to your request shortly."
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,priority:0
msgid "Highest"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,mobile:0
msgid "Mobile"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,description:0
msgid "Notes"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,message_ids:0
msgid "Messages"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,color:0
msgid "Color Index"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,partner_latitude:0
msgid "Geo Latitude"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,partner_name:0
msgid "Customer Name"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,state:0
msgid "Cancelled"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,message_unread:0
msgid "If checked new messages require your attention."
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,channel_id:0
msgid "Communication channel (mail, direct, phone, ...)"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,type_id:0
msgid "Campaign"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,ref:0
msgid "Reference"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,date_action_next:0
#: field:portal_crm.crm_contact_us,title_action:0
msgid "Next Action"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,message_summary:0
msgid ""
"Holds the Chatter summary (number of messages, ...). This summary is "
"directly in html format in order to be inserted in kanban views."
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,partner_id:0
msgid "Partner"
msgstr ""
#. module: portal_crm
#: model:ir.actions.act_window,name:portal_crm.action_contact_us
msgid "Contact Us"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,name:0
msgid "Subject"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,opt_out:0
msgid "Opt-Out"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,priority:0
msgid "Priority"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,state_id:0
msgid "State"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,message_follower_ids:0
msgid "Followers"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,partner_id:0
msgid "Linked partner (optional). Usually created when converting the lead."
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,payment_mode:0
msgid "Payment Mode"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,state:0
msgid "New"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,type:0
msgid "Type"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,email_from:0
msgid "Email"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,channel_id:0
msgid "Channel"
msgstr ""
#. module: portal_crm
#: view:portal_crm.crm_contact_us:0
msgid "Name"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,priority:0
msgid "Lowest"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,create_date:0
msgid "Creation Date"
msgstr ""
#. module: portal_crm
#: view:portal_crm.crm_contact_us:0
msgid "Close"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,state:0
msgid "Pending"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,type:0
msgid "Type is used to separate Leads and Opportunities"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,categ_ids:0
msgid "Categories"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,stage_id:0
msgid "Stage"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,user_login:0
msgid "User Login"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,opt_out:0
msgid ""
"If opt-out is checked, this contact has refused to receive emails or "
"unsubscribed to a campaign."
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,contact_name:0
msgid "Contact Name"
msgstr ""
#. module: portal_crm
#: model:ir.ui.menu,name:portal_crm.portal_company_contact
msgid "Contact"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,partner_address_email:0
msgid "Partner Contact Email"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,planned_revenue:0
msgid "Expected Revenue"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,task_ids:0
msgid "Tasks"
msgstr ""
#. module: portal_crm
#: view:portal_crm.crm_contact_us:0
msgid "Contact form"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,company_currency:0
msgid "Currency"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,write_date:0
msgid "Update Date"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,date_deadline:0
msgid "Expected Closing"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,ref2:0
msgid "Reference 2"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,user_email:0
msgid "User Email"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,date_open:0
msgid "Opened"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,state:0
msgid "In Progress"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,partner_name:0
msgid ""
"The name of the future partner company that will be created while converting "
"the lead into opportunity"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,planned_cost:0
msgid "Planned Costs"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,date_deadline:0
msgid "Estimate of the date on which the opportunity will be won."
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,email_cc:0
msgid ""
"These email addresses will be added to the CC field of all inbound and "
"outbound emails for this record before being sent. Separate multiple email "
"addresses with a comma"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,priority:0
msgid "Low"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,date_closed:0
#: selection:portal_crm.crm_contact_us,state:0
msgid "Closed"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,date_assign:0
msgid "Assignation Date"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,state:0
msgid "Status"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,priority:0
msgid "Normal"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,email_cc:0
msgid "Global CC"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,street2:0
msgid "Street2"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,id:0
msgid "ID"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,phone:0
msgid "Phone"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,message_is_follower:0
msgid "Is a Follower"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,active:0
msgid "Active"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,user_id:0
msgid "Salesperson"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,day_close:0
msgid "Days to Close"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,company_ids:0
msgid "Companies"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,message_summary:0
msgid "Summary"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,section_id:0
msgid ""
"When sending mails, the default email address is taken from the sales team."
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,partner_address_name:0
msgid "Partner Contact Name"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,partner_longitude:0
msgid "Geo Longitude"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,date_assign:0
msgid "Last date this case was forwarded/assigned to a partner"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,email_from:0
msgid "Email address of the contact"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,city:0
msgid "City"
msgstr ""
#. module: portal_crm
#: view:portal_crm.crm_contact_us:0
msgid "Submit"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,function:0
msgid "Function"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,referred:0
msgid "Referred By"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,partner_assigned_id:0
msgid "Assigned Partner"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,type:0
msgid "Opportunity"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,partner_assigned_id:0
msgid "Partner this case has been forwarded/assigned to."
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,country_id:0
msgid "Country"
msgstr ""
#. module: portal_crm
#: view:portal_crm.crm_contact_us:0
msgid "Thank you"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,state:0
msgid ""
"The Status is set to 'Draft', when a case is created. If the case is in "
"progress the Status is set to 'Open'. When the case is over, the Status is "
"set to 'Done'. If the case needs to be reviewed then the Status is set to "
"'Pending'."
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,message_ids:0
msgid "Messages and communication history"
msgstr ""
#. module: portal_crm
#: help:portal_crm.crm_contact_us,type_id:0
msgid ""
"From which campaign (seminar, marketing campaign, mass mailing, ...) did "
"this contact come from?"
msgstr ""
#. module: portal_crm
#: selection:portal_crm.crm_contact_us,priority:0
msgid "High"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,section_id:0
msgid "Sales Team"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,street:0
msgid "Street"
msgstr ""
#. module: portal_crm
#: field:portal_crm.crm_contact_us,date_action_last:0
msgid "Last Action"
msgstr ""
#. module: portal_crm
#: model:ir.model,name:portal_crm.model_portal_crm_crm_contact_us
msgid "Contact form for the portal"
msgstr ""

View File

@ -19,35 +19,17 @@
#
##############################################################################
from openerp.addons.project.tests.test_project_base import TestProjectBase
from openerp.osv.orm import except_orm
from openerp.tests import common
from openerp.tools import mute_logger
class TestPortalProject(common.TransactionCase):
class TestPortalProjectBase(TestProjectBase):
def setUp(self):
super(TestPortalProject, self).setUp()
super(TestPortalProjectBase, self).setUp()
cr, uid = self.cr, self.uid
# Useful models
self.project_project = self.registry('project.project')
self.project_task = self.registry('project.task')
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')
self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
# Find Project User group
group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
# Find Project Manager group
group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
# Find Portal group
group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
self.group_portal_id = group_portal_ref and group_portal_ref[1] or False
@ -56,62 +38,39 @@ class TestPortalProject(common.TransactionCase):
group_anonymous_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_anonymous')
self.group_anonymous_id = group_anonymous_ref and group_anonymous_ref[1] or False
# Test users to use through the various tests
self.user_alfred_id = self.res_users.create(cr, uid, {
'name': 'Alfred EmployeeUser',
'login': 'alfred',
'alias_name': 'alfred',
'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
})
self.user_bert_id = self.res_users.create(cr, uid, {
'name': 'Bert Nobody',
'login': 'bert',
'alias_name': 'bert',
'groups_id': [(6, 0, [])]
})
self.user_chell_id = self.res_users.create(cr, uid, {
'name': 'Chell Portal',
'login': 'chell',
'alias_name': 'chell',
'groups_id': [(6, 0, [self.group_portal_id])]
})
self.user_donovan_id = self.res_users.create(cr, uid, {
'name': 'Donovan Anonymous',
'login': 'donovan',
'alias_name': 'donovan',
'groups_id': [(6, 0, [self.group_anonymous_id])]
})
self.user_ernest_id = self.res_users.create(cr, uid, {
'name': 'Ernest Manager',
'login': 'ernest',
'alias_name': 'ernest',
'groups_id': [(6, 0, [self.group_project_manager_id])]
})
# # Test users to use through the various tests
self.user_portal_id = self.res_users.create(cr, uid, {
'name': 'Chell Portal',
'login': 'chell',
'alias_name': 'chell',
'groups_id': [(6, 0, [self.group_portal_id])]
})
self.user_anonymous_id = self.res_users.create(cr, uid, {
'name': 'Donovan Anonymous',
'login': 'donovan',
'alias_name': 'donovan',
'groups_id': [(6, 0, [self.group_anonymous_id])]
})
# Test 'Pigs' project
self.project_pigs_id = self.project_project.create(cr, uid,
{'name': 'Pigs', 'privacy_visibility': 'public'},
{'mail_create_nolog': True})
self.project_pigs_id = self.project_project.create(cr, uid, {
'name': 'Pigs', 'privacy_visibility': 'public'}, {'mail_create_nolog': True})
# Various test tasks
self.task_1_id = self.project_task.create(cr, uid,
{'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.task_2_id = self.project_task.create(cr, uid,
{'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.task_3_id = self.project_task.create(cr, uid,
{'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.task_4_id = self.project_task.create(cr, uid,
{'name': 'Test4', 'user_id': self.user_alfred_id, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.task_5_id = self.project_task.create(cr, uid,
{'name': 'Test5', 'user_id': self.user_chell_id, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.task_6_id = self.project_task.create(cr, uid,
{'name': 'Test6', 'user_id': self.user_donovan_id, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.task_1_id = self.project_task.create(cr, uid, {
'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.task_2_id = self.project_task.create(cr, uid, {
'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.task_3_id = self.project_task.create(cr, uid, {
'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.task_4_id = self.project_task.create(cr, uid, {
'name': 'Test4', 'user_id': self.user_projectuser_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.task_5_id = self.project_task.create(cr, uid, {
'name': 'Test5', 'user_id': self.user_portal_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.task_6_id = self.project_task.create(cr, uid, {
'name': 'Test6', 'user_id': self.user_anonymous_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
class TestPortalProject(TestPortalProjectBase):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_project_access_rights(self):
""" Test basic project access rights, for project and portal_project """
@ -122,53 +81,47 @@ class TestPortalProject(common.TransactionCase):
# ----------------------------------------
# Do: Alfred reads project -> ok (employee ok public)
self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_2_id, self.task_3_id, self.task_4_id, self.task_5_id, self.task_6_id])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: project user cannot see all tasks of a public project')
'access rights: project user cannot see all tasks of a public project')
# Test: all project tasks readable
self.project_task.read(cr, self.user_alfred_id, task_ids, ['name'])
self.project_task.read(cr, self.user_projectuser_id, task_ids, ['name'])
# Test: all project tasks writable
self.project_task.write(cr, self.user_alfred_id, task_ids, {'description': 'TestDescription'})
self.project_task.write(cr, self.user_projectuser_id, task_ids, {'description': 'TestDescription'})
# Do: Bert reads project -> crash, no group
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_bert_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Test: no project task visible
self.assertRaises(except_orm, self.project_task.search,
cr, self.user_bert_id, [('project_id', '=', pigs_id)])
self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Test: no project task readable
self.assertRaises(except_orm, self.project_task.read,
cr, self.user_bert_id, task_ids, ['name'])
self.assertRaises(except_orm, self.project_task.read, cr, self.user_none_id, task_ids, ['name'])
# Test: no project task writable
self.assertRaises(except_orm, self.project_task.write,
cr, self.user_bert_id, task_ids, {'description': 'TestDescription'})
self.assertRaises(except_orm, self.project_task.write, cr, self.user_none_id, task_ids, {'description': 'TestDescription'})
# Do: Chell reads project -> ok (portal ok public)
self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: all project tasks visible
task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: project user cannot see all tasks of a public project')
'access rights: project user cannot see all tasks of a public project')
# Test: all project tasks readable
self.project_task.read(cr, self.user_chell_id, task_ids, ['name'])
self.project_task.read(cr, self.user_portal_id, task_ids, ['name'])
# Test: no project task writable
self.assertRaises(except_orm, self.project_task.write,
cr, self.user_chell_id, task_ids, {'description': 'TestDescription'})
self.assertRaises(except_orm, self.project_task.write, cr, self.user_portal_id, task_ids, {'description': 'TestDescription'})
# Do: Donovan reads project -> ok (anonymous ok public)
self.project_project.read(cr, self.user_donovan_id, pigs_id, ['name'])
self.project_project.read(cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: all project tasks visible
task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: anonymous user cannot see all tasks of a public project')
'access rights: anonymous user cannot see all tasks of a public project')
# Test: all project tasks readable
self.project_task.read(cr, self.user_donovan_id, task_ids, ['name'])
self.project_task.read(cr, self.user_anonymous_id, task_ids, ['name'])
# Test: no project task writable
self.assertRaises(except_orm, self.project_task.write,
cr, self.user_donovan_id, task_ids, {'description': 'TestDescription'})
self.assertRaises(except_orm, self.project_task.write, cr, self.user_anonymous_id, task_ids, {'description': 'TestDescription'})
# ----------------------------------------
# CASE2: portal project
@ -176,39 +129,36 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'portal'})
# Do: Alfred reads project -> ok (employee ok public)
self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: project user cannot see all tasks of a portal project')
'access rights: project user cannot see all tasks of a portal project')
# Do: Bert reads project -> crash, no group
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_bert_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Test: no project task searchable
self.assertRaises(except_orm, self.project_task.search,
cr, self.user_bert_id, [('project_id', '=', pigs_id)])
self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Data: task follower
self.project_task.message_subscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id])
self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id])
# Do: Chell reads project -> ok (portal ok public)
self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: only followed project tasks visible + assigned
task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: portal user should see the followed tasks of a portal project')
'access rights: portal user should see the followed tasks of a portal project')
# Do: Donovan reads project -> ko (anonymous ko portal)
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_donovan_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of a portal project')
# Data: task follower cleaning
self.project_task.message_unsubscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id])
self.project_task.message_unsubscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id])
# ----------------------------------------
# CASE3: employee project
@ -216,29 +166,26 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'employees'})
# Do: Alfred reads project -> ok (employee ok employee)
self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_2_id, self.task_3_id, self.task_4_id, self.task_5_id, self.task_6_id])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: project user cannot see all tasks of an employees project')
'access rights: project user cannot see all tasks of an employees project')
# Do: Bert reads project -> crash, no group
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_bert_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Do: Chell reads project -> ko (portal ko employee)
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_chell_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name'])
# Test: no project task visible + assigned
task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: portal user should not see tasks of an employees project, even if assigned')
# Do: Donovan reads project -> ko (anonymous ko employee)
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_donovan_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of an employees project')
# ----------------------------------------
@ -247,54 +194,49 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'followers'})
# Do: Alfred reads project -> ko (employee ko followers)
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_alfred_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: no project task visible
task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_4_id])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: employee user should not see tasks of a not-followed followers project, only assigned')
'access rights: employee user should not see tasks of a not-followed followers project, only assigned')
# Do: Bert reads project -> crash, no group
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_bert_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Do: Chell reads project -> ko (portal ko employee)
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_chell_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name'])
# Test: no project task visible
task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: portal user should not see tasks of a not-followed followers project, only assigned')
'access rights: portal user should not see tasks of a not-followed followers project, only assigned')
# Do: Donovan reads project -> ko (anonymous ko employee)
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_donovan_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of a followers project')
# Data: subscribe Alfred, Chell and Donovan as follower
self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_alfred_id, self.user_chell_id, self.user_donovan_id])
self.project_task.message_subscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id, self.user_alfred_id])
self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id, self.user_projectuser_id])
# Do: Alfred reads project -> ok (follower ok followers)
self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: followed + assigned tasks visible
task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_4_id])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: employee user should not see followed + assigned tasks of a follower project')
'access rights: employee user should not see followed + assigned tasks of a follower project')
# Do: Chell reads project -> ok (follower ok follower)
self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: followed + assigned tasks visible
task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
'access rights: employee user should not see followed + assigned tasks of a follower project')
'access rights: employee user should not see followed + assigned tasks of a follower project')
# Do: Donovan reads project -> ko (anonymous ko follower even if follower)
self.assertRaises(except_orm, self.project_project.read,
cr, self.user_donovan_id, pigs_id, ['name'])
self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])

View File

@ -26,7 +26,7 @@
</div>
<div><field name="categ_ids" widget="many2many_tags" class="oe_left"/></div>
<div class="oe_text_right">
<h1><field name="state" readonly="1"/></h1>
<h1><field name="stage_id" readonly="1"/></h1>
</div>
</div>
<div>

View File

@ -19,40 +19,36 @@
#
##############################################################################
from openerp.addons.portal_project.tests.test_access_rights import TestPortalProject
from openerp.addons.portal_project.tests.test_access_rights import TestPortalProjectBase
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
class TestPortalIssueProject(TestPortalProject):
class TestPortalProjectBase(TestPortalProjectBase):
def setUp(self):
super(TestPortalIssueProject, self).setUp()
super(TestPortalProjectBase, self).setUp()
cr, uid = self.cr, self.uid
# Useful models
self.project_issue = self.registry('project.issue')
# Various test issues
self.issue_1_id = self.project_issue.create(cr, uid,
{'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.issue_2_id = self.project_issue.create(cr, uid,
{'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.issue_3_id = self.project_issue.create(cr, uid,
{'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.issue_4_id = self.project_issue.create(cr, uid,
{'name': 'Test4', 'user_id': self.user_alfred_id, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.issue_5_id = self.project_issue.create(cr, uid,
{'name': 'Test5', 'user_id': self.user_chell_id, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.issue_6_id = self.project_issue.create(cr, uid,
{'name': 'Test6', 'user_id': self.user_donovan_id, 'project_id': self.project_pigs_id},
{'mail_create_nolog': True})
self.issue_1_id = self.project_issue.create(cr, uid, {
'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.issue_2_id = self.project_issue.create(cr, uid, {
'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.issue_3_id = self.project_issue.create(cr, uid, {
'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.issue_4_id = self.project_issue.create(cr, uid, {
'name': 'Test4', 'user_id': self.user_projectuser_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.issue_5_id = self.project_issue.create(cr, uid, {
'name': 'Test5', 'user_id': self.user_portal_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
self.issue_6_id = self.project_issue.create(cr, uid, {
'name': 'Test6', 'user_id': self.user_anonymous_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
class TestPortalIssue(TestPortalProjectBase):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_project_access_rights(self):
""" Test basic project access rights, for project and portal_project """
@ -64,42 +60,38 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok public)
# Test: all project issues visible
issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_2_id, self.issue_3_id, self.issue_4_id, self.issue_5_id, self.issue_6_id])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: project user cannot see all issues of a public project')
'access rights: project user cannot see all issues of a public project')
# Test: all project issues readable
self.project_issue.read(cr, self.user_alfred_id, issue_ids, ['name'])
self.project_issue.read(cr, self.user_projectuser_id, issue_ids, ['name'])
# Test: all project issues writable
self.project_issue.write(cr, self.user_alfred_id, issue_ids, {'description': 'TestDescription'})
self.project_issue.write(cr, self.user_projectuser_id, issue_ids, {'description': 'TestDescription'})
# Do: Bert reads project -> crash, no group
# Test: no project issue visible
self.assertRaises(except_orm, self.project_issue.search,
cr, self.user_bert_id, [('project_id', '=', pigs_id)])
self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Test: no project issue readable
self.assertRaises(except_orm, self.project_issue.read,
cr, self.user_bert_id, issue_ids, ['name'])
self.assertRaises(except_orm, self.project_issue.read, cr, self.user_none_id, issue_ids, ['name'])
# Test: no project issue writable
self.assertRaises(except_orm, self.project_issue.write,
cr, self.user_bert_id, issue_ids, {'description': 'TestDescription'})
self.assertRaises(except_orm, self.project_issue.write, cr, self.user_none_id, issue_ids, {'description': 'TestDescription'})
# Do: Chell reads project -> ok (portal ok public)
# Test: all project issues visible
issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: project user cannot see all issues of a public project')
'access rights: project user cannot see all issues of a public project')
# Test: all project issues readable
self.project_issue.read(cr, self.user_chell_id, issue_ids, ['name'])
self.project_issue.read(cr, self.user_portal_id, issue_ids, ['name'])
# Test: no project issue writable
self.assertRaises(except_orm, self.project_issue.write,
cr, self.user_chell_id, issue_ids, {'description': 'TestDescription'})
self.assertRaises(except_orm, self.project_issue.write, cr, self.user_portal_id, issue_ids, {'description': 'TestDescription'})
# Do: Donovan reads project -> ok (anonymous ok public)
# Test: all project issues visible
issue_ids = self.project_issue.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: project user cannot see all issues of a public project')
'access rights: project user cannot see all issues of a public project')
# ----------------------------------------
# CASE2: portal project
@ -108,27 +100,26 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok public)
# Test: all project issues visible
issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: project user cannot see all issues of a portal project')
'access rights: project user cannot see all issues of a portal project')
# Do: Bert reads project -> crash, no group
# Test: no project issue searchable
self.assertRaises(except_orm, self.project_issue.search,
cr, self.user_bert_id, [('project_id', '=', pigs_id)])
self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Data: issue follower
self.project_issue.message_subscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id])
self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id])
# Do: Chell reads project -> ok (portal ok public)
# Test: only followed project issues visible + assigned
issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: portal user should see the followed issues of a portal project')
'access rights: portal user should see the followed issues of a portal project')
# Data: issue follower cleaning
self.project_issue.message_unsubscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id])
self.project_issue.message_unsubscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id])
# ----------------------------------------
# CASE3: employee project
@ -137,14 +128,14 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok employee)
# Test: all project issues visible
issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_2_id, self.issue_3_id, self.issue_4_id, self.issue_5_id, self.issue_6_id])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: project user cannot see all issues of an employees project')
'access rights: project user cannot see all issues of an employees project')
# Do: Chell reads project -> ko (portal ko employee)
# Test: no project issue visible + assigned
issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertFalse(issue_ids, 'access rights: portal user should not see issues of an employees project, even if assigned')
# ----------------------------------------
@ -154,32 +145,32 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ko (employee ko followers)
# Test: no project issue visible
issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_4_id])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: employee user should not see issues of a not-followed followers project, only assigned')
'access rights: employee user should not see issues of a not-followed followers project, only assigned')
# Do: Chell reads project -> ko (portal ko employee)
# Test: no project issue visible
issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: portal user should not see issues of a not-followed followers project, only assigned')
'access rights: portal user should not see issues of a not-followed followers project, only assigned')
# Data: subscribe Alfred, Chell and Donovan as follower
self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_alfred_id, self.user_chell_id, self.user_donovan_id])
self.project_issue.message_subscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id, self.user_alfred_id])
self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id, self.user_projectuser_id])
# Do: Alfred reads project -> ok (follower ok followers)
# Test: followed + assigned issues visible
issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_4_id])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: employee user should not see followed + assigned issues of a follower project')
'access rights: employee user should not see followed + assigned issues of a follower project')
# Do: Chell reads project -> ok (follower ok follower)
# Test: followed + assigned issues visible
issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
'access rights: employee user should not see followed + assigned issues of a follower project')
'access rights: employee user should not see followed + assigned issues of a follower project')

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-12-21 17:06+0000\n"
"PO-Revision-Date: 2012-11-27 09:28+0000\n"
"Last-Translator: 盈通 ccdos <ccdos@163.com>\n"
"PO-Revision-Date: 2013-07-28 11:18+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@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: 2013-03-16 05:25+0000\n"
"X-Generator: Launchpad (build 16532)\n"
"X-Launchpad-Export-Date: 2013-07-29 05:25+0000\n"
"X-Generator: Launchpad (build 16700)\n"
#. module: product
#: field:product.packaging,rows:0
@ -513,7 +513,7 @@ msgstr "标准耳机"
#. module: product
#: model:product.uom,name:product.product_uom_day
msgid "Day(s)"
msgstr ""
msgstr ""
#. module: product
#: help:product.product,incoming_qty:0
@ -800,7 +800,7 @@ msgstr "结束日期"
#. module: product
#: model:product.uom,name:product.product_uom_litre
msgid "Liter(s)"
msgstr ""
msgstr ""
#. module: product
#: view:product.price_list:0
@ -843,7 +843,7 @@ msgstr "价格表"
#. module: product
#: model:product.uom,name:product.product_uom_hour
msgid "Hour(s)"
msgstr ""
msgstr "小时"
#. module: product
#: selection:product.template,state:0
@ -1114,7 +1114,7 @@ msgstr "右父项"
#. module: product
#: field:product.product,price:0
msgid "Price"
msgstr ""
msgstr "价格"
#. module: product
#: field:product.pricelist.item,price_surcharge:0
@ -1192,7 +1192,7 @@ msgstr "显示产品列表的顺序号"
#. module: product
#: model:product.uom,name:product.product_uom_dozen
msgid "Dozen(s)"
msgstr ""
msgstr ""
#. module: product
#: field:product.uom,factor:0
@ -1338,7 +1338,7 @@ msgstr "指定一个产品分类 ,规则只被应用在与这个分类以及
#. module: product
#: view:product.product:0
msgid "Inventory"
msgstr "盘点"
msgstr "库存"
#. module: product
#: field:product.product,seller_info_id:0
@ -1346,7 +1346,7 @@ msgid "Supplier Info"
msgstr "供应商信息"
#. module: product
#: code:addons/product/product.py:729
#: code:addons/product/product.py:732
#, python-format
msgid "%s (copy)"
msgstr "%s (副本)"
@ -1891,7 +1891,7 @@ msgstr "消息"
#. module: product
#: model:product.uom,name:product.product_uom_unit
msgid "Unit(s)"
msgstr ""
msgstr ""
#. module: product
#: code:addons/product/product.py:176
@ -2341,7 +2341,7 @@ msgstr "序列"
msgid ""
"Average delay in days to produce this product. In the case of multi-level "
"BOM, the manufacturing lead times of the components will be added."
msgstr ""
msgstr "生成该产品的平均延时天数。在多级BoM的情况下部件的制造提前期将被累加"
#. module: product
#: model:product.template,name:product.product_assembly_product_template
@ -2364,6 +2364,8 @@ msgid ""
" </p>\n"
" "
msgstr ""
"单击以创建一个新的计量单位\n"
" "
#. module: product
#: model:product.template,name:product.product_product_11_product_template
@ -2444,7 +2446,7 @@ msgid "15” LCD Monitor"
msgstr "15” LCD 显示器"
#. module: product
#: code:addons/product/pricelist.py:376
#: code:addons/product/pricelist.py:379
#: field:product.pricelist.item,base_pricelist_id:0
#, python-format
msgid "Other Pricelist"

View File

@ -376,11 +376,26 @@ class product_pricelist_item(osv.osv):
result.append((-2, _('Supplier Prices on the product form')))
return result
# Added default function to fetch the Price type Based on Pricelist type.
def _get_default_base(self, cr, uid, fields, context=None):
product_price_type_obj = self.pool.get('product.price.type')
if fields.get('type') == 'purchase':
product_price_type_ids = product_price_type_obj.search(cr, uid, [('field', '=', 'standard_price')], context=context)
elif fields.get('type') == 'sale':
product_price_type_ids = product_price_type_obj.search(cr, uid, [('field','=','list_price')], context=context)
else:
return -1
if not product_price_type_ids:
return False
else:
pricetype = product_price_type_obj.browse(cr, uid, product_price_type_ids, context=context)[0]
return pricetype.id
_name = "product.pricelist.item"
_description = "Pricelist item"
_order = "sequence, min_quantity desc"
_defaults = {
'base': lambda *a: -1,
'base': _get_default_base,
'min_quantity': lambda *a: 0,
'sequence': lambda *a: 5,
'price_discount': lambda *a: 0,

View File

@ -152,7 +152,7 @@
<field name="currency_id" groups="base.group_multi_currency"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
<field name="version_id">
<field name="version_id" context="{'type':type}">
<form string="Pricelist Version" version="7.0">
<group col="4">
<field name="name"/>
@ -160,7 +160,7 @@
<field name="date_start"/>
<field name="date_end"/>
</group>
<field name="items_id"/>
<field name="items_id" context="{'type':type}"/>
</form>
<tree string="Pricelist Version">
<field name="name"/>

View File

@ -40,7 +40,6 @@
],
'depends': [
'base_setup',
'base_status',
'product',
'analytic',
'board',
@ -66,7 +65,6 @@ Dashboard / Reports for Project Management will include:
'data': [
'security/project_security.xml',
'wizard/project_task_delegate_view.xml',
'wizard/project_task_reevaluate_view.xml',
'security/ir.model.access.csv',
'project_data.xml',
'project_view.xml',
@ -79,9 +77,6 @@ Dashboard / Reports for Project Management will include:
],
'demo': ['project_demo.xml'],
'test': [
'test/project_demo.yml',
'test/project_process.yml',
'test/task_process.yml',
],
'installable': True,
'auto_install': False,

View File

@ -16,7 +16,6 @@
<field name="effective_hours" widget="float_time"/>
<field name="progress" widget="progressbar"/>
<field name="stage_id" invisible="context.get('set_visible',False)"/>
<field name="state" invisible="1"/>
</tree>
</field>
</record>
@ -26,7 +25,7 @@
<field name="res_model">project.task</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">[('user_id','=',uid),('state','not in',('cancel','done'))]</field>
<field name="domain">[('user_id', '=', uid), ('stage_id.fold', '!=', True)]</field>
<field name="view_id" ref="view_task_tree"/>
</record>

View File

@ -0,0 +1,16 @@
.. _changelog:
Changelog
=========
`trunk (saas-2)`
----------------
- Stage/state update
- ``project.task``: removed inheritance from ``base_stage`` class and removed
``state`` field. Added ``date_last_stage_update`` field holding last stage_id
modification. Updated reports.
- ``project.task.type``: removed ``state`` field.
- Removed ``project.task.reevaluate`` wizard.

View File

@ -0,0 +1,22 @@
=====================
Project DevDoc
=====================
Project module documentation
===================================
Documentation topics
''''''''''''''''''''
.. toctree::
:maxdepth: 1
stage_status.rst
Changelog
'''''''''
.. toctree::
:maxdepth: 1
changelog.rst

View File

@ -0,0 +1,55 @@
.. _stage_status:
Stage and Status
================
.. versionchanged:: 8.0 saas-2 state/stage cleaning
Stage
+++++
This revision removed the concept of state on project.task objects. The ``state``
field has been totally removed and replaced by stages, using ``stage_id``. The
following models are impacted:
- ``project.task`` now use only stages. However a convention still exists about
'New' stage. A task is consdered as ``new`` when it has the following
properties:
- ``stage_id and stage_id.sequence = 1``
- ``project.task.type`` do not have any ``state`` field anymore.
- ``project.task.report`` do not have any ``state`` field anymore.
By default a newly created task is in a new stage. It means that it will
fetch the stage having ``sequence = 1``. Stage mangement is done using the
kanban view or the clikable statusbar. It is not done using buttons anymore.
Stage analysis
++++++++++++++
Stage analysis can be performed using the newly introduced ``date_last_stage_update``
datetime field. This field is updated everytime ``stage_id`` is updated.
``project.task.report`` model also uses the ``date_last_stage_update`` field.
This allows to group and analyse the time spend in the various stages.
Open / Assignation date
+++++++++++++++++++++++
The ``date_open`` field meaning has been updated. It is now set when the ``user_id``
(responsible) is set. It is therefore the assignation date.
Subtypes
++++++++
The following subtypes are triggered on ``project.task``:
- ``mt_task_new``: new tasks. Condition: ``obj.stage_id and obj.stage_id.sequence == 1``
- ``mt_task_stage``: stage changed. Condition: ``obj.stage_id and obj.stage_id.sequence != 1``
- ``mt_task_assigned``: user assigned. condition: ``obj.user_id and obj.user_id.id``
- ``mt_task_blocked``: kanban state blocked. Condition: ``obj.kanban_state == 'blocked'``
Those subtypes are also available on the ``project.project`` model and are used
for the auto subscription.

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-12-21 17:04+0000\n"
"PO-Revision-Date: 2013-07-14 04:15+0000\n"
"Last-Translator: Alan <shuchuan.wu@gmail.com>\n"
"PO-Revision-Date: 2013-08-06 09:36+0000\n"
"Last-Translator: Wei \"oldrev\" Li <oldrev@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: 2013-07-15 04:51+0000\n"
"X-Generator: Launchpad (build 16696)\n"
"X-Launchpad-Export-Date: 2013-08-07 04:46+0000\n"
"X-Generator: Launchpad (build 16721)\n"
#. module: project
#: view:project.project:0
@ -749,7 +749,7 @@ msgstr "待处理任务草稿"
#. module: project
#: field:project.project,alias_model:0
msgid "Alias Model"
msgstr ""
msgstr "别名模型"
#. module: project
#: help:report.project.task.user,closing_days:0
@ -854,7 +854,7 @@ msgstr "增加筛选条件"
#. module: project
#: model:ir.ui.menu,name:project.menu_tasks_config
msgid "GTD"
msgstr ""
msgstr "GTD"
#. module: project
#: help:project.task,state:0
@ -1214,7 +1214,7 @@ msgstr "%s (副本)"
#. module: project
#: model:mail.message.subtype,name:project.mt_project_task_stage
msgid "Task Stage Changed"
msgstr ""
msgstr "任务阶段已改变"
#. module: project
#: view:project.task:0
@ -1706,6 +1706,12 @@ msgid ""
" </p>\n"
" "
msgstr ""
"<p class=\"oe_view_nocontent_create\">\n"
" 单击创建新任务。\n"
" </p><p>\n"
"OpenERP 系统的任务管理允许你为了更高效地完成工作而管理任务管道。你可以跟踪任务进度、与他人讨论任务、附加文档附件等。\n"
" </p>\n"
" "
#. module: project
#: field:project.task,date_end:0
@ -2013,7 +2019,7 @@ msgstr "准备好下一个阶段"
#. module: project
#: field:project.task.type,case_default:0
msgid "Default for New Projects"
msgstr ""
msgstr "默认用于新项目"
#. module: project
#: view:project.task:0
@ -2051,6 +2057,8 @@ msgid ""
"resource allocation.\n"
" This installs the module project_long_term."
msgstr ""
"用于长期项目管理的模块,能够跟踪计划、排程和资源控制。\n"
" 将会安装模块“project_long_term”。"
#. module: project
#: model:mail.message.subtype,description:project.mt_task_closed
@ -2083,6 +2091,8 @@ msgid ""
"Provides timesheet support for the issues/bugs management in project.\n"
" This installs the module project_issue_sheet."
msgstr ""
"为项目中的问题/错误管理提供时间表支持。\n"
" 将安装模块“project_issue_sheet”。"
#. module: project
#: selection:project.project,privacy_visibility:0

View File

@ -25,13 +25,10 @@ import time
from openerp import SUPERUSER_ID
from openerp import tools
from openerp.addons.resource.faces import task as Task
from openerp.osv import fields, osv
from openerp.tools.translate import _
from openerp.addons.base_status.base_stage import base_stage
from openerp.addons.resource.faces import task as Task
_TASK_STATE = [('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')]
class project_task_type(osv.osv):
_name = 'project.task.type'
@ -44,33 +41,18 @@ class project_task_type(osv.osv):
'case_default': fields.boolean('Default for New Projects',
help="If you check this field, this stage will be proposed by default on each new project. It will not assign this stage to existing projects."),
'project_ids': fields.many2many('project.project', 'project_task_type_rel', 'type_id', 'project_id', 'Projects'),
'state': fields.selection(_TASK_STATE, 'Related Status', required=True,
help="The status of your document is automatically changed regarding the selected stage. " \
"For example, if a stage is related to the status 'Close', when your document reaches this stage, it is automatically closed."),
'fold': fields.boolean('Folded by Default',
help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
}
def _get_default_project_id(self, cr, uid, ctx={}):
proj = ctx.get('default_project_id', False)
if type(proj) is int:
return [proj]
return proj
_defaults = {
'sequence': 1,
'state': 'open',
'fold': False,
'case_default': False,
'project_ids': _get_default_project_id
'project_ids': lambda self, cr, uid, ctx=None: self.pool['project.task']._get_default_project_id(cr, uid, context=ctx),
}
_order = 'sequence'
def short_name(name):
"""Keep first word(s) of name to make it small enough
but distinctive"""
if not name: return name
# keep 7 chars + end of the last word
keep_words = name[:7].strip().split()
return ' '.join(name.split()[:len(keep_words)])
class project(osv.osv):
_name = "project.project"
@ -99,9 +81,9 @@ class project(osv.osv):
def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
partner_obj = self.pool.get('res.partner')
if not part:
return {'value':{}}
val = {}
if not part:
return {'value': val}
if 'pricelist_id' in self.fields_get(cr, uid, context=context):
pricelist = partner_obj.read(cr, uid, part, ['property_product_pricelist'], context=context)
pricelist_id = pricelist.get('property_product_pricelist', False) and pricelist.get('property_product_pricelist')[0] or False
@ -152,11 +134,13 @@ class project(osv.osv):
cr.execute("""
SELECT project_id, COALESCE(SUM(planned_hours), 0.0),
COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0)
FROM project_task WHERE project_id IN %s AND state <> 'cancelled'
FROM project_task
LEFT JOIN project_task_type ON project_task.stage_id = project_task_type.id
WHERE project_task.project_id IN %s AND project_task_type.fold = False
GROUP BY project_id
""", (tuple(child_parent.keys()),))
# aggregate results into res
res = dict([(id, {'planned_hours':0.0,'total_hours':0.0,'effective_hours':0.0}) for id in ids])
res = dict([(id, {'planned_hours':0.0, 'total_hours':0.0, 'effective_hours':0.0}) for id in ids])
for id, planned, total, effective in cr.fetchall():
# add the values specific to id to all parent projects of id in the result
while id:
@ -253,22 +237,22 @@ class project(osv.osv):
'planned_hours': fields.function(_progress_rate, multi="progress", string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', help="Sum of total hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'progress_rate': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Tasks Stages', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
@ -323,26 +307,19 @@ class project(osv.osv):
]
def set_template(self, cr, uid, ids, context=None):
res = self.setActive(cr, uid, ids, value=False, context=context)
return res
return self.setActive(cr, uid, ids, value=False, context=context)
def set_done(self, cr, uid, ids, context=None):
task_obj = self.pool.get('project.task')
task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', 'not in', ('cancelled', 'done'))])
task_obj.case_close(cr, uid, task_ids, context=context)
return self.write(cr, uid, ids, {'state':'close'}, context=context)
return self.write(cr, uid, ids, {'state': 'close'}, context=context)
def set_cancel(self, cr, uid, ids, context=None):
task_obj = self.pool.get('project.task')
task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', '!=', 'done')])
task_obj.case_cancel(cr, uid, task_ids, context=context)
return self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
def set_pending(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state':'pending'}, context=context)
return self.write(cr, uid, ids, {'state': 'pending'}, context=context)
def set_open(self, cr, uid, ids, context=None):
return self.write(cr, uid, ids, {'state':'open'}, context=context)
return self.write(cr, uid, ids, {'state': 'open'}, context=context)
def reset_project(self, cr, uid, ids, context=None):
return self.setActive(cr, uid, ids, value=True, context=context)
@ -459,8 +436,6 @@ class project(osv.osv):
if project.user_id and (project.user_id.id not in u_ids):
u_ids.append(project.user_id.id)
for task in project.tasks:
if task.state in ('done','cancelled'):
continue
if task.user_id and (task.user_id.id not in u_ids):
u_ids.append(task.user_id.id)
calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
@ -525,7 +500,7 @@ def Project():
for project in projects:
project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
for task in project.tasks:
if task.state in ('done', 'cancelled'):
if task.stage_id and task.stage_id.fold:
continue
p = getattr(project_gantt, 'Task_%d' % (task.id,))
@ -540,16 +515,13 @@ def Project():
}, context=context)
return True
# ------------------------------------------------
# OpenChatter methods and notifications
# ------------------------------------------------
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
# Prevent double project creation when 'use_tasks' is checked + alias management
create_context = dict(context, project_creation_in_progress=True,
alias_model_name=vals.get('alias_model', 'project.task'), alias_parent_model_name=self._name)
alias_model_name=vals.get('alias_model', 'project.task'),
alias_parent_model_name=self._name)
if vals.get('type', False) not in ('template', 'contract'):
vals['type'] = 'contract'
@ -566,22 +538,23 @@ def Project():
vals.update(alias_model_id=model_ids[0])
return super(project, self).write(cr, uid, ids, vals, context=context)
class task(base_stage, osv.osv):
class task(osv.osv):
_name = "project.task"
_description = "Task"
_date_name = "date_start"
_inherit = ['mail.thread', 'ir.needaction_mixin']
_mail_post_access = 'read'
_track = {
'state': {
'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
'project.mt_task_started': lambda self, cr, uid, obj, ctx=None: obj.state == 'open',
'project.mt_task_closed': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
},
'stage_id': {
'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'done', 'open'],
'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id.sequence != 1,
},
'kanban_state': { # kanban state: tracked, but only block subtype
'user_id': {
'project.mt_task_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
},
'kanban_state': {
'project.mt_task_blocked': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'blocked',
},
}
@ -593,7 +566,7 @@ class task(base_stage, osv.osv):
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
if project and project.partner_id:
return project.partner_id.id
return super(task, self)._get_default_partner(cr, uid, context=context)
return False
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default section by checking if present in the context """
@ -602,7 +575,7 @@ class task(base_stage, osv.osv):
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
project_id = self._get_default_project_id(cr, uid, context=context)
return self.stage_find(cr, uid, [], project_id, [('state', '=', 'draft')], context=context)
return self.stage_find(cr, uid, [], project_id, [('sequence', '=', '1')], context=context)
def _resolve_project_id_from_context(self, cr, uid, context=None):
""" Returns ID of project based on the value of 'default_project_id'
@ -679,17 +652,18 @@ class task(base_stage, osv.osv):
res[task.id]['progress'] = 0.0
if (task.remaining_hours + hours.get(task.id, 0.0)):
res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 99.99),2)
if task.state in ('done','cancelled'):
# TDE CHECK: if task.state in ('done','cancelled'):
if task.stage_id and task.stage_id.fold:
res[task.id]['progress'] = 100.0
return res
def onchange_remaining(self, cr, uid, ids, remaining=0.0, planned=0.0):
if remaining and not planned:
return {'value':{'planned_hours': remaining}}
return {'value': {'planned_hours': remaining}}
return {}
def onchange_planned(self, cr, uid, ids, planned=0.0, effective=0.0):
return {'value':{'remaining_hours': planned - effective}}
return {'value': {'remaining_hours': planned - effective}}
def onchange_project(self, cr, uid, id, project_id, context=None):
if project_id:
@ -698,6 +672,12 @@ class task(base_stage, osv.osv):
return {'value': {'partner_id': project.partner_id.id}}
return {}
def onchange_user_id(self, cr, uid, ids, user_id, context=None):
vals = {}
if user_id:
vals['date_start'] = fields.datetime.now()
return {'value': vals}
def duplicate_task(self, cr, uid, map_ids, context=None):
for new in map_ids.values():
task = self.browse(cr, uid, new, context)
@ -755,13 +735,6 @@ class task(base_stage, osv.osv):
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."),
'stage_id': fields.many2one('project.task.type', 'Stage', track_visibility='onchange',
domain="[('project_ids', '=', project_id)]"),
'state': fields.related('stage_id', 'state', type="selection", store=True,
selection=_TASK_STATE, string="Status", readonly=True,
help='The status is set to \'Draft\', when a case is created.\
If the case is in progress the status is set to \'Open\'.\
When the case is over, the status is set to \'Done\'.\
If the case needs to be reviewed then the status is \
set to \'Pending\'.'),
'categ_ids': fields.many2many('project.category', string='Tags'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
track_visibility='onchange',
@ -774,6 +747,7 @@ class task(base_stage, osv.osv):
'date_start': fields.datetime('Starting Date',select=True),
'date_end': fields.datetime('Ending Date',select=True),
'date_deadline': fields.date('Deadline',select=True),
'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
'project_id': fields.many2one('project.project', 'Project', ondelete='set null', select="1", track_visibility='onchange'),
'parent_ids': fields.many2many('project.task', 'project_task_parent_rel', 'task_id', 'parent_id', 'Parent Tasks'),
'child_ids': fields.many2many('project.task', 'project_task_parent_rel', 'parent_id', 'task_id', 'Delegated Tasks'),
@ -790,7 +764,7 @@ class task(base_stage, osv.osv):
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
'progress': fields.function(_hours_get, string='Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
'progress': fields.function(_hours_get, string='Working Time Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours','state'], 10),
'project.task.work': (_get_task, ['hours'], 10),
@ -813,6 +787,7 @@ class task(base_stage, osv.osv):
_defaults = {
'stage_id': _get_default_stage_id,
'project_id': _get_default_project_id,
'date_last_stage_update': lambda *a: fields.datetime.now(),
'kanban_state': 'normal',
'priority': '2',
'progress': 0,
@ -940,7 +915,7 @@ class task(base_stage, osv.osv):
section_ids.append(task.project_id.id)
search_domain = []
if section_ids:
search_domain = [('|')] * (len(section_ids)-1)
search_domain = [('|')] * (len(section_ids) - 1)
for section_id in section_ids:
search_domain.append(('project_ids', '=', section_id))
search_domain += list(domain)
@ -957,82 +932,10 @@ class task(base_stage, osv.osv):
for task in tasks:
if task.child_ids:
for child in task.child_ids:
if child.state in ['draft', 'open', 'pending']:
if child.stage_id and not child.stage_id.fold:
raise osv.except_osv(_("Warning!"), _("Child task still open.\nPlease cancel or complete child task first."))
return True
def action_close(self, cr, uid, ids, context=None):
""" This action closes the task
"""
task_id = len(ids) and ids[0] or False
self._check_child_task(cr, uid, ids, context=context)
if not task_id: return False
return self.do_close(cr, uid, [task_id], context=context)
def do_close(self, cr, uid, ids, context=None):
""" Compatibility when changing to case_close. """
return self.case_close(cr, uid, ids, context=context)
def case_close(self, cr, uid, ids, context=None):
""" Closes Task """
if not isinstance(ids, list): ids = [ids]
for task in self.browse(cr, uid, ids, context=context):
vals = {}
project = task.project_id
for parent_id in task.parent_ids:
if parent_id.state in ('pending','draft'):
reopen = True
for child in parent_id.child_ids:
if child.id != task.id and child.state not in ('done','cancelled'):
reopen = False
if reopen:
self.do_reopen(cr, uid, [parent_id.id], context=context)
# close task
vals['remaining_hours'] = 0.0
if not task.date_end:
vals['date_end'] = fields.datetime.now()
self.case_set(cr, uid, [task.id], 'done', vals, context=context)
return True
def do_reopen(self, cr, uid, ids, context=None):
for task in self.browse(cr, uid, ids, context=context):
project = task.project_id
self.case_set(cr, uid, [task.id], 'open', {}, context=context)
return True
def do_cancel(self, cr, uid, ids, context=None):
""" Compatibility when changing to case_cancel. """
return self.case_cancel(cr, uid, ids, context=context)
def case_cancel(self, cr, uid, ids, context=None):
tasks = self.browse(cr, uid, ids, context=context)
self._check_child_task(cr, uid, ids, context=context)
for task in tasks:
self.case_set(cr, uid, [task.id], 'cancelled', {'remaining_hours': 0.0}, context=context)
return True
def do_open(self, cr, uid, ids, context=None):
""" Compatibility when changing to case_open. """
return self.case_open(cr, uid, ids, context=context)
def case_open(self, cr, uid, ids, context=None):
if not isinstance(ids,list): ids = [ids]
return self.case_set(cr, uid, ids, 'open', {'date_start': fields.datetime.now()}, context=context)
def do_draft(self, cr, uid, ids, context=None):
""" Compatibility when changing to case_draft. """
return self.case_draft(cr, uid, ids, context=context)
def case_draft(self, cr, uid, ids, context=None):
return self.case_set(cr, uid, ids, 'draft', {}, context=context)
def do_pending(self, cr, uid, ids, context=None):
""" Compatibility when changing to case_pending. """
return self.case_pending(cr, uid, ids, context=context)
def case_pending(self, cr, uid, ids, context=None):
return self.case_set(cr, uid, ids, 'pending', {}, context=context)
def _delegate_task_attachments(self, cr, uid, task_id, delegated_task_id, context=None):
attachment = self.pool.get('ir.attachment')
attachment_ids = attachment.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', task_id)], context=context)
@ -1053,6 +956,7 @@ class task(base_stage, osv.osv):
delegated_task_id = self.copy(cr, uid, task.id, {
'name': delegate_data['name'],
'project_id': delegate_data['project_id'] and delegate_data['project_id'][0] or False,
'stage_id': delegate_data.get('stage_id') and delegate_data.get('stage_id')[0] or False,
'user_id': delegate_data['user_id'] and delegate_data['user_id'][0] or False,
'planned_hours': delegate_data['planned_hours'] or 0.0,
'parent_ids': [(6, 0, [task.id])],
@ -1067,16 +971,12 @@ class task(base_stage, osv.osv):
'planned_hours': delegate_data['planned_hours_me'] + (task.effective_hours or 0.0),
'name': newname,
}, context=context)
if delegate_data['state'] == 'pending':
self.do_pending(cr, uid, [task.id], context=context)
elif delegate_data['state'] == 'done':
self.do_close(cr, uid, [task.id], context=context)
delegated_tasks[task.id] = delegated_task_id
return delegated_tasks
def set_remaining_time(self, cr, uid, ids, remaining_time=1.0, context=None):
for task in self.browse(cr, uid, ids, context=context):
if (task.state=='draft') or (task.planned_hours==0.0):
if (task.stage_id and task.stage_id.sequence == 1) or (task.planned_hours == 0.0):
self.write(cr, uid, [task.id], {'planned_hours': remaining_time}, context=context)
self.write(cr, uid, ids, {'remaining_hours': remaining_time}, context=context)
return True
@ -1111,17 +1011,25 @@ class task(base_stage, osv.osv):
'planned_hours': task.planned_hours,
'kanban_state': task.kanban_state,
'type_id': task.stage_id.id,
'state': task.state,
'user_id': task.user_id.id
}, context=context)
return True
# ------------------------------------------------
# CRUD overrides
# ------------------------------------------------
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
# for default stage
if vals.get('project_id') and not context.get('default_project_id'):
context['default_project_id'] = vals.get('project_id')
# user_id change: update date_start
if vals.get('user_id'):
vals['date_start'] = fields.datetime.now()
# context: no_log, because subtype already handle this
create_context = dict(context, mail_create_nolog=True)
@ -1129,25 +1037,30 @@ class task(base_stage, osv.osv):
self._store_history(cr, uid, [task_id], context=context)
return task_id
# Overridden to reset the kanban_state to normal whenever
# the stage (stage_id) of the task changes.
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
# stage change: update date_last_stage_update
if 'stage_id' in vals:
vals['date_last_stage_update'] = fields.datetime.now()
# user_id change: update date_start
if vals.get('user_id'):
vals['date_start'] = fields.datetime.now()
# Overridden to reset the kanban_state to normal whenever
# the stage (stage_id) of the task changes.
if vals and not 'kanban_state' in vals and 'stage_id' in vals:
new_stage = vals.get('stage_id')
vals_reset_kstate = dict(vals, kanban_state='normal')
for t in self.browse(cr, uid, ids, context=context):
#TO FIX:Kanban view doesn't raise warning
#stages = [stage.id for stage in t.project_id.type_ids]
#if new_stage not in stages:
#raise osv.except_osv(_('Warning!'), _('Stage is not defined in the project.'))
write_vals = vals_reset_kstate if t.stage_id != new_stage else vals
super(task, self).write(cr, uid, [t.id], write_vals, context=context)
result = True
else:
result = super(task, self).write(cr, uid, ids, vals, context=context)
if ('stage_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals):
if any(item in vals for item in ['stage_id', 'remaining_hours', 'user_id', 'kanban_state']):
self._store_history(cr, uid, ids, context=context)
return result
@ -1163,7 +1076,7 @@ class task(base_stage, osv.osv):
result = ""
ident = ' '*ident
for task in tasks:
if task.state in ('done','cancelled'):
if task.stage_id and task.stage_id.fold:
continue
result += '''
%sdef Task_%s():
@ -1194,17 +1107,6 @@ class task(base_stage, osv.osv):
return [task.project_id.message_get_reply_to()[0] if task.project_id else False
for task in self.browse(cr, uid, ids, context=context)]
def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
""" mail.message document permission rule: can post a new message if can read
because of portal document. """
if not model_obj:
model_obj = self
if operation == 'create':
model_obj.check_access_rights(cr, uid, 'read')
model_obj.check_access_rule(cr, uid, mids, 'read', context=context)
else:
return super(task, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
def message_new(self, cr, uid, msg, custom_values=None, context=None):
""" Override to updates the document according to the email. """
if custom_values is None: custom_values = {}
@ -1233,24 +1135,10 @@ class task(base_stage, osv.osv):
update_vals[field] = float(res.group(2).lower())
except (ValueError, TypeError):
pass
elif match.lower() == 'state' \
and res.group(2).lower() in ['cancel','close','draft','open','pending']:
act = 'do_%s' % res.group(2).lower()
if act:
getattr(self,act)(cr, uid, ids, context=context)
return super(task,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
def project_task_reevaluate(self, cr, uid, ids, context=None):
if self.pool.get('res.users').has_group(cr, uid, 'project.group_time_work_estimation_tasks'):
return {
'view_type': 'form',
"view_mode": 'form',
'res_model': 'project.task.reevaluate',
'type': 'ir.actions.act_window',
'target': 'new',
}
return self.do_reopen(cr, uid, ids, context=context)
class project_work(osv.osv):
_name = "project.task.work"
_description = "Project Task Work"
@ -1371,7 +1259,7 @@ class project_task_history(osv.osv):
def _get_date(self, cr, uid, ids, name, arg, context=None):
result = {}
for history in self.browse(cr, uid, ids, context=context):
if history.state in ('done','cancelled'):
if history.type_id and history.type_id.fold:
result[history.id] = history.date
continue
cr.execute('''select
@ -1405,14 +1293,13 @@ class project_task_history(osv.osv):
_columns = {
'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True, select=True),
'type_id': fields.many2one('project.task.type', 'Stage'),
'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done')], 'Status'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State', required=False),
'kanban_state': fields.selection([('normal', 'Normal'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')], 'Kanban State', required=False),
'date': fields.date('Date', select=True),
'end_date': fields.function(_get_date, string='End Date', type="date", store={
'project.task.history': (_get_related_date, None, 20)
}),
'remaining_hours': fields.float('Remaining Time', digits=(16,2)),
'planned_hours': fields.float('Planned Time', digits=(16,2)),
'remaining_hours': fields.float('Remaining Time', digits=(16, 2)),
'planned_hours': fields.float('Planned Time', digits=(16, 2)),
'user_id': fields.many2one('res.users', 'Responsible'),
}
_defaults = {
@ -1442,7 +1329,7 @@ class project_task_history_cumulative(osv.osv):
SELECT
h.id AS history_id,
h.date+generate_series(0, CAST((coalesce(h.end_date, DATE 'tomorrow')::date - h.date) AS integer)-1) AS date,
h.task_id, h.type_id, h.user_id, h.kanban_state, h.state,
h.task_id, h.type_id, h.user_id, h.kanban_state,
greatest(h.remaining_hours, 1) AS remaining_hours, greatest(h.planned_hours, 1) AS planned_hours,
t.project_id
FROM

View File

@ -30,51 +30,43 @@
<record id="project_tt_analysis" model="project.task.type">
<field name="sequence">1</field>
<field name="name">Analysis</field>
<field name="state">draft</field>
<field name="case_default" eval="False"/>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_specification" model="project.task.type">
<field name="sequence">2</field>
<field name="sequence">10</field>
<field name="name">Specification</field>
<field name="state">pending</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_design" model="project.task.type">
<field name="sequence">2</field>
<field name="sequence">11</field>
<field name="name">Design</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_development" model="project.task.type">
<field name="sequence">3</field>
<field name="sequence">12</field>
<field name="name">Development</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_testing" model="project.task.type">
<field name="sequence">4</field>
<field name="sequence">13</field>
<field name="name">Testing</field>
<field name="state">open</field>
<field name="case_default" eval="True"/>
</record>
<record id="project_tt_merge" model="project.task.type">
<field name="sequence">5</field>
<field name="sequence">14</field>
<field name="name">Merge</field>
<field name="state">open</field>
<field name="case_default" eval="False"/>
<field name="fold" eval="True"/>
</record>
<record id="project_tt_deployment" model="project.task.type">
<field name="sequence">100</field>
<field name="sequence">20</field>
<field name="name">Done</field>
<field name="state">done</field>
<field name="case_default" eval="True"/>
<field name="fold" eval="True"/>
</record>
<record id="project_tt_cancel" model="project.task.type">
<field name="sequence">200</field>
<field name="sequence">30</field>
<field name="name">Cancelled</field>
<field name="state">cancelled</field>
<field name="case_default" eval="True"/>
<field name="fold" eval="True"/>
</record>
@ -86,11 +78,11 @@
<field name="default" eval="False"/>
<field name="description">Task created</field>
</record>
<record id="mt_task_started" model="mail.message.subtype">
<field name="name">Task Started</field>
<record id="mt_task_assigned" model="mail.message.subtype">
<field name="name">Task Assigned</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task started</field>
<field name="description">Task Assigned</field>
</record>
<record id="mt_task_blocked" model="mail.message.subtype">
<field name="name">Task Blocked</field>
@ -98,12 +90,6 @@
<field name="default" eval="False"/>
<field name="description">Task blocked</field>
</record>
<record id="mt_task_closed" model="mail.message.subtype">
<field name="name">Task Done</field>
<field name="res_model">project.task</field>
<field name="default" eval="False"/>
<field name="description">Task closed</field>
</record>
<record id="mt_task_stage" model="mail.message.subtype">
<field name="name">Stage Changed</field>
<field name="res_model">project.task</field>
@ -118,11 +104,11 @@
<field name="parent_id" eval="ref('mt_task_new')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_started" model="mail.message.subtype">
<field name="name">Task Started</field>
<record id="mt_project_task_assigned" model="mail.message.subtype">
<field name="name">Task Assigned</field>
<field name="res_model">project.project</field>
<field name="default" eval="False"/>
<field name="parent_id" eval="ref('mt_task_started')"/>
<field name="parent_id" eval="ref('mt_task_assigned')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_blocked" model="mail.message.subtype">
@ -131,12 +117,6 @@
<field name="parent_id" eval="ref('mt_task_blocked')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_closed" model="mail.message.subtype">
<field name="name">Task Done</field>
<field name="res_model">project.project</field>
<field name="parent_id" eval="ref('mt_task_closed')"/>
<field name="relation_field">project_id</field>
</record>
<record id="mt_project_task_stage" model="mail.message.subtype">
<field name="name">Task Stage Changed</field>
<field name="res_model">project.project</field>

View File

@ -225,7 +225,6 @@
ref('project.project_category_04')])]"/>
<field name="stage_id" ref="project_tt_merge"/>
</record>
<function model="project.task" name="do_close" eval="[ref('project_task_11')], {'install_mode': True}"/>
<record id="project_task_12" model="project.task">
<field name="planned_hours" eval="40.0"/>
@ -237,7 +236,6 @@
<field name="stage_id" ref="project_tt_merge"/>
<field name="color">6</field>
</record>
<function model="project.task" name="do_close" eval="[ref('project_task_12')], {'install_mode': True}"/>
<record id="project_task_13" model="project.task">
<field name="planned_hours" eval="12.0"/>
@ -248,7 +246,6 @@
<field name="name">Design Use Cases</field>
<field name="stage_id" ref="project_tt_analysis"/>
</record>
<function model="project.task" name="do_pending" eval="[ref('project_task_13')], {'install_mode': True}"/>
<record id="project_task_14" model="project.task">
<field name="planned_hours" eval="12.0"/>
@ -282,7 +279,6 @@
<field name="name">Set target for all deparments</field>
<field name="stage_id" ref="project_tt_development"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_16')], {'install_mode': True}"/>
<record id="project_task_17" model="project.task">
<field name="planned_hours" eval="34.0"/>
@ -293,7 +289,6 @@
<field name="name">Integration of core components</field>
<field name="stage_id" ref="project_tt_testing"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_17')], {'install_mode': True}"/>
<record id="project_task_18" model="project.task">
<field name="planned_hours" eval="16.0"/>
@ -315,7 +310,6 @@
<field name="categ_ids" eval="[(6, 0, [
ref('project_category_03')])]"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_19')], {'install_mode': True}"/>
<record id="project_task_20" model="project.task">
<field name="planned_hours">42.0</field>
@ -325,7 +319,6 @@
<field name="project_id" ref="project.project_project_4"/>
<field name="name">Create new components</field>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_20')], {'install_mode': True}"/>
<record id="project_task_21" model="project.task">
<field name="planned_hours">14.0</field>
@ -337,7 +330,6 @@
<field name="categ_ids" eval="[(6, 0, [
ref('project_category_04')])]"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_21')], {'install_mode': True}"/>
<record id="project_task_22" model="project.task">
<field name="planned_hours">12.0</field>
@ -371,7 +363,6 @@
<field name="categ_ids" eval="[(6, 0, [
ref('project_category_01')])]"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_24')], {'install_mode': True}"/>
<record id="project_task_25" model="project.task">
<field name="sequence">20</field>
@ -382,7 +373,6 @@
<field name="name">Data importation + Doc</field>
<field name="stage_id" ref="project_tt_development"/>
</record>
<function model="project.task" name="do_open" eval="[ref('project_task_25')], {'install_mode': True}"/>
<record id="project_task_26" model="project.task">
<field name="sequence">20</field>

View File

@ -18,29 +18,26 @@
<search string="Tasks">
<field name="name" string="Tasks"/>
<field name="categ_ids"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id', '=', False)]"/>
<filter string="New" name="draft" domain="[('stage_id.sequence', '=', 1)]"/>
<separator/>
<filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<filter string="My Tasks" domain="[('user_id','=',uid)]"/>
<separator/>
<filter name="draft" string="New" domain="[('state','=','draft')]" help="New Tasks" icon="terp-check"/>
<filter name="open" string="In Progress" domain="[('state','=','open')]" help="In Progress Tasks" icon="terp-camera_test"/>
<filter string="Pending" domain="[('state','=','pending')]" context="{'show_delegated':False}" help="Pending Tasks" icon="terp-gtk-media-pause"/>
<separator/>
<filter name="My project" string="Project" domain="[('project_id.user_id','=',uid)]" help="My Projects" icon="terp-check"/>
<separator/>
<filter string="My Tasks" domain="[('user_id','=',uid)]" help="My Tasks" icon="terp-personal"/>
<filter string="Unassigned Tasks" domain="[('user_id','=',False)]" help="Unassigned Tasks" icon="terp-personal-"/>
<filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/>
<separator/>
<filter string="Deadlines" context="{'deadline_visible': False}" domain="[('date_deadline','&lt;&gt;',False)]"
help="Show only tasks having a deadline" icon="terp-gnome-cpu-frequency-applet+"/>
help="Show only tasks having a deadline"/>
<field name="partner_id"/>
<field name="project_id"/>
<field name="user_id"/>
<field name="stage_id" domain="[]"/>
<group expand="0" string="Group By...">
<filter string="Users" name="group_user_id" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}"/>
<filter string="Project" name="group_project_id" icon="terp-folder-violet" domain="[]" context="{'group_by':'project_id'}"/>
<filter string="Stage" name="group_stage_id" icon="terp-stage" domain="[]" context="{'group_by':'stage_id'}"/>
<filter string="Last Stage Update" icon="terp-go-month" domain="[]" context="{'group_by':'date_last_stage_update'}"/>
<filter string="Deadline" icon="terp-gnome-cpu-frequency-applet+" domain="[]" context="{'group_by':'date_deadline'}"/>
<filter string="Start Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_start'}" groups="base.group_no_one"/>
<filter string="Start Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_start'}"/>
<filter string="End Date" icon="terp-go-month" domain="[]" context="{'group_by':'date_end'}" groups="base.group_no_one"/>
</group>
</search>
@ -138,6 +135,7 @@
<div style="position: relative">
<a t-if="! read_only_mode" type="delete" style="position: absolute; right: 0; padding: 4px; diplay: inline-block">X</a>
<div class="oe_module_vignette">
<img t-att-src="kanban_image('res.users', 'image_small', record.id.value)" class="oe_avatar oe_kanban_avatar_smallbox"/>
<div class="oe_module_desc">
<field name="name"/>
</div>
@ -371,18 +369,6 @@
<field name="arch" type="xml">
<form string="Project" version="7.0">
<header>
<!--
<button name="do_open" string="Start Task" type="object"
states="draft,pending" class="oe_highlight"/>
<button name="do_draft" string="Draft" type="object"
states="cancel,done"/>
-->
<button name="project_task_reevaluate" string="Reactivate" type="object"
states="cancelled,done" context="{'button_reactivate':True}" groups="base.group_user"/>
<button name="action_close" string="Done" type="object"
states="draft,open,pending" groups="base.group_user"/>
<button name="do_cancel" string="Cancel Task" type="object"
states="draft,open,pending" groups="base.group_user"/>
<field name="stage_id" widget="statusbar" clickable="True"/>
</header>
<sheet string="Task">
@ -396,8 +382,7 @@
<group>
<group>
<field name="project_id" on_change="onchange_project(project_id)" context="{'default_use_tasks':1}"/>
<field name="user_id"
attrs="{'readonly':[('state','in',['done', 'cancelled'])]}"
<field name="user_id"
options='{"no_open": True}'
context="{'default_groups_ref': ['base.group_user', 'project.group_project_user']}"/>
<field name="planned_hours" widget="float_time"
@ -405,15 +390,15 @@
on_change="onchange_planned(planned_hours, effective_hours)"/>
</group>
<group>
<field name="date_deadline" attrs="{'readonly':[('state','in',['done', 'cancelled'])]}"/>
<field name="date_deadline"/>
<field name="categ_ids" widget="many2many_tags"/>
<field name="progress" widget="progressbar"
groups="project.group_time_work_estimation_tasks" attrs="{'invisible':[('state','=','cancelled')]}"/>
groups="project.group_time_work_estimation_tasks"/>
</group>
</group>
<notebook>
<page string="Description">
<field name="description" attrs="{'readonly':[('state','=','done')]}" placeholder="Add a Description..."/>
<field name="description" placeholder="Add a Description..."/>
<field name="work_ids" groups="project.group_tasks_work_on_tasks">
<tree string="Task Work" editable="top">
<field name="name"/>
@ -427,7 +412,7 @@
<field name="effective_hours" widget="float_time"/>
<label for="remaining_hours" string="Remaining" groups="project.group_time_work_estimation_tasks"/>
<div>
<field name="remaining_hours" widget="float_time" attrs="{'readonly':[('state','in',('done','cancelled'))]}" groups="project.group_time_work_estimation_tasks"/>
<field name="remaining_hours" widget="float_time" groups="project.group_time_work_estimation_tasks"/>
</div>
<field name="total_hours" widget="float_time" class="oe_subtotal_footer_separator"/>
</group>
@ -436,7 +421,7 @@
</page>
<page string="Delegation" groups="project.group_delegate_task">
<button name="%(action_project_task_delegate)d" string="Delegate" type="action"
states="pending,open,draft" groups="project.group_delegate_task"/>
groups="project.group_delegate_task"/>
<separator string="Parent Tasks"/>
<field name="parent_ids"/>
<separator string="Delegated tasks"/>
@ -445,7 +430,6 @@
<field name="name"/>
<field name="user_id"/>
<field name="stage_id"/>
<field name="state" invisible="1"/>
<field name="effective_hours" widget="float_time"/>
<field name="progress" widget="progressbar"/>
<field name="remaining_hours" widget="float_time"/>
@ -453,12 +437,11 @@
</tree>
</field>
</page>
<page string="Extra Info" attrs="{'readonly':[('state','=','done')]}">
<page string="Extra Info">
<group col="4">
<field name="priority" groups="base.group_user"/>
<field name="sequence"/>
<field name="partner_id"/>
<field name="state" invisible="1"/>
<field name="company_id" groups="base.group_multi_company" widget="selection"/>
</group>
<group>
@ -467,6 +450,7 @@
<field name="date_end"/>
</group>
<group>
<field name="date_last_stage_update" groups="base.group_no_one"/>
</group>
</group>
</page>
@ -493,7 +477,6 @@
<field name="user_email"/>
<field name="description"/>
<field name="sequence"/>
<field name="state" groups="base.group_no_one"/>
<field name="kanban_state"/>
<field name="remaining_hours" sum="Remaining Time" groups="project.group_time_work_estimation_tasks"/>
<field name="date_deadline"/>
@ -513,7 +496,6 @@
<li><a name="set_remaining_time_2" type="object" class="oe_kanban_button">2</a></li>
<li><a name="set_remaining_time_5" type="object" class="oe_kanban_button">5</a></li>
<li><a name="set_remaining_time_10" type="object" class="oe_kanban_button">10</a></li>
<li><a name="do_open" states="draft" string="Validate planned time" type="object" class="oe_kanban_button oe_kanban_button_active">!</a></li>
</ul>
</li>
<br/>
@ -562,7 +544,7 @@
<field name="model">project.task</field>
<field eval="2" name="priority"/>
<field name="arch" type="xml">
<tree fonts="bold:message_unread==True" colors="grey:state in ('cancelled','done');blue:state == 'pending';red:date_deadline and (date_deadline&lt;current_date) and (state in ('draft','pending','open'))" string="Tasks">
<tree fonts="bold:message_unread==True" colors="red:date_deadline and (date_deadline&lt;current_date)" string="Tasks">
<field name="message_unread" invisible="1"/>
<field name="sequence" invisible="not context.get('seq_visible', False)"/>
<field name="name"/>
@ -575,7 +557,6 @@
<field name="remaining_hours" widget="float_time" sum="Remaining Hours" on_change="onchange_remaining(remaining_hours,planned_hours)" invisible="context.get('set_visible',False)" groups="project.group_time_work_estimation_tasks"/>
<field name="date_deadline" invisible="context.get('deadline_visible',True)"/>
<field name="stage_id" invisible="context.get('set_visible',False)"/>
<field name="state" invisible="1"/>
<field name="date_start" groups="base.group_no_one"/>
<field name="date_end" groups="base.group_no_one"/>
<field name="progress" widget="progressbar" invisible="context.get('set_visible',False)"/>
@ -661,7 +642,7 @@
<field name="res_model">project.task</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form,calendar,graph,kanban</field>
<field name="domain">[('date_deadline','&lt;',time.strftime('%Y-%m-%d')),('state','in',('draft','pending','open'))]</field>
<field name="domain">[('date_deadline','&lt;',time.strftime('%Y-%m-%d'))]</field>
<field name="filter" eval="True"/>
<field name="search_view_id" ref="view_task_search_form"/>
</record>
@ -706,7 +687,6 @@
<field name="case_default"/>
</group>
<group>
<field name="state"/>
<field name="sequence"/>
<field name="fold"/>
</group>
@ -723,7 +703,7 @@
<tree string="Task Stage">
<field name="sequence" widget="handle"/>
<field name="name"/>
<field name="state"/>
<field name="fold"/>
</tree>
</field>
</record>
@ -767,7 +747,9 @@
</record>
<!-- User Form -->
<act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}" domain="[('state', '&lt;&gt;', 'cancelled'),('state', '&lt;&gt;', 'done')]" id="act_res_users_2_project_task_opened" name="Assigned Tasks" res_model="project.task" src_model="res.users" view_mode="tree,form,gantt,calendar,graph" view_type="form"/>
<act_window context="{'search_default_user_id': [active_id], 'default_user_id': active_id}"
id="act_res_users_2_project_task_opened" name="Assigned Tasks"
res_model="project.task" src_model="res.users" view_mode="tree,form,gantt,calendar,graph" view_type="form"/>
<!-- Tags -->
<record model="ir.ui.view" id="project_category_search_view">

View File

@ -14,7 +14,6 @@
<field name="user_id"/>
<field name="remaining_hours"/>
<field name="kanban_state"/>
<field name="state" groups="base.group_no_one"/>
</tree>
</field>
</record>
@ -40,8 +39,7 @@
<field name="date"/>
<field name="project_id"/>
<field name="user_id"/>
<filter name="open" string="In Progress Tasks" domain="[('state','in',('open','draft'))]"/>
<filter string="Pending Tasks" domain="[('state','=','pending')]" context="{'show_delegated':False}"/>
<filter name="new" string="New" domain="[('type_id.sequence', '=', 1)]"/>
<separator/>
<filter name="kanban_blocked" string="Blocked" domain="[('kanban_state','=','blocked')]"/>
<filter name="kanban_ready" string="Ready" domain="[('kanban_state','=','done')]"/>

View File

@ -19,9 +19,10 @@
#
##############################################################################
from openerp.osv import fields,osv
from openerp.osv import fields, osv
from openerp import tools
class report_project_task_user(osv.osv):
_name = "report.project.task.user"
_description = "Tasks by user and project"
@ -31,10 +32,11 @@ class report_project_task_user(osv.osv):
'day': fields.char('Day', size=128, readonly=True),
'year': fields.char('Year', size=64, required=False, readonly=True),
'user_id': fields.many2one('res.users', 'Assigned To', readonly=True),
'date_start': fields.date('Starting Date',readonly=True),
'date_start': fields.date('Assignation Date', readonly=True),
'no_of_days': fields.integer('# of Days', size=128, readonly=True),
'date_end': fields.date('Ending Date', readonly=True),
'date_deadline': fields.date('Deadline', readonly=True),
'date_last_stage_update': fields.date('Last Stage Update', readonly=True),
'project_id': fields.many2one('project.project', 'Project', readonly=True),
'hours_planned': fields.float('Planned Hours', readonly=True),
'hours_effective': fields.float('Effective Hours', readonly=True),
@ -44,16 +46,17 @@ class report_project_task_user(osv.osv):
'total_hours': fields.float('Total Hours', readonly=True),
'closing_days': fields.float('Days to Close', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to close the task"),
'opening_days': fields.float('Days to Open', digits=(16,2), readonly=True, group_operator="avg",
'opening_days': fields.float('Days to Assign', digits=(16,2), readonly=True, group_operator="avg",
help="Number of Days to Open the task"),
'delay_endings_days': fields.float('Overpassed Deadline', digits=(16,2), readonly=True),
'nbr': fields.integer('# of tasks', readonly=True),
'priority' : fields.selection([('4','Very Low'), ('3','Low'), ('2','Medium'), ('1','Urgent'),
('0','Very urgent')], 'Priority', readonly=True),
'month':fields.selection([('01','January'), ('02','February'), ('03','March'), ('04','April'), ('05','May'), ('06','June'), ('07','July'), ('08','August'), ('09','September'), ('10','October'), ('11','November'), ('12','December')], 'Month', readonly=True),
'priority': fields.selection([('4', 'Very Low'), ('3', 'Low'), ('2', 'Medium'), ('1', 'Urgent'), ('0', 'Very urgent')],
string='Priority', readonly=True),
'month':fields.selection(fields.date.MONTHS, 'Month', readonly=True),
'state': fields.selection([('draft', 'Draft'), ('open', 'In Progress'), ('pending', 'Pending'), ('cancelled', 'Cancelled'), ('done', 'Done')],'Status', readonly=True),
'company_id': fields.many2one('res.company', 'Company', readonly=True),
'partner_id': fields.many2one('res.partner', 'Contact', readonly=True),
'stage_id': fields.many2one('project.task.type', 'Stage'),
}
_order = 'name desc, project_id'
@ -69,19 +72,19 @@ class report_project_task_user(osv.osv):
to_char(date_start, 'YYYY-MM-DD') as day,
date_trunc('day',t.date_start) as date_start,
date_trunc('day',t.date_end) as date_end,
date_trunc('day',t.date_last_stage_update) as date_last_stage_update,
to_date(to_char(t.date_deadline, 'dd-MM-YYYY'),'dd-MM-YYYY') as date_deadline,
-- sum(cast(to_char(date_trunc('day',t.date_end) - date_trunc('day',t.date_start),'DD') as int)) as no_of_days,
abs((extract('epoch' from (t.date_end-t.date_start)))/(3600*24)) as no_of_days,
t.user_id,
progress as progress,
t.project_id,
t.state,
t.effective_hours as hours_effective,
t.priority,
t.name as name,
t.company_id,
t.partner_id,
t.stage_id,
t.stage_id as stage_id,
remaining_hours as remaining_hours,
total_hours as total_hours,
t.delay_hours as hours_delay,
@ -106,15 +109,12 @@ class report_project_task_user(osv.osv):
date_start,
date_end,
date_deadline,
date_last_stage_update,
t.user_id,
t.project_id,
t.state,
t.priority,
name,
t.company_id,
t.partner_id,
t.stage_id
stage_id
""")

View File

@ -13,11 +13,12 @@
<tree string="Tasks Analysis" create="false">
<field name="name" invisible="1"/>
<field name="project_id" invisible="1"/>
<field name="stage_id" invisible="1"/>
<field name="user_id" invisible="1"/>
<field name="date_deadline" invisible="1"/>
<field name="state" invisible="1"/>
<field name="date_start" invisible="1"/>
<field name="date_end" invisible="1"/>
<field name="date_last_stage_update" invisible="1"/>
<field name="company_id" invisible="1" groups="base.group_multi_company"/>
<field name="partner_id" invisible="1"/>
<field name="day" invisible="1"/>
@ -44,7 +45,7 @@
<field name="arch" type="xml">
<graph string="Tasks Analysis" type="bar">
<field name="name"/>
<field name="state" group="True"/>
<field name="stage_id" group="True"/>
<field name="no_of_days" operator="+"/>
</graph>
</field>
@ -58,31 +59,29 @@
<field name="date_start"/>
<field name="date_end"/>
<field name="date_deadline"/>
<filter string="New" icon="terp-document-new" domain="[('state','=','draft')]" help = "New tasks"/>
<filter string="In progress" icon="terp-check" domain="[('state', '=' ,'open')]" help = "In progress tasks"/>
<filter string="Pending" icon="terp-gtk-media-pause" domain="[('state','=','pending')]" help = "Pending tasks"/>
<filter string="Done" icon="terp-dialog-close" name="done" domain="[('state','=','done')]"/>
<separator/>
<filter icon="terp-folder-violet" string="My Projects" help="My Projects" domain="[('project_id.user_id','=',uid)]"/>
<separator/>
<filter icon="terp-personal" string="My Task" help = "My tasks" domain="[('user_id','=',uid)]" />
<filter icon="terp-personal-" string="Non Assigned Tasks to users" help="Non Assigned Tasks to users" domain="[('user_id','=',False)]"/>
<field name="date_last_stage_update"/>
<field name="project_id"/>
<field name="user_id"/>
<field name="partner_id" filter_domain="[('partner_id', 'child_of', self)]"/>
<field name="stage_id"/>
<filter string="Unassigned" name="unassigned" domain="[('user_id','=',False)]"/>
<filter string="New" name="new" domain="[('stage_id.sequence', '=', 1)]"/>
<separator/>
<filter string="My Task" domain="[('user_id','=',uid)]" />
<group expand="0" string="Extended Filters...">
<field name="priority"/>
<field name="company_id" groups="base.group_multi_company"/>
</group>
<group expand="1" string="Group By...">
<filter string="Project" name="project" icon="terp-folder-violet" context="{'group_by':'project_id'}"/>
<filter string="Task" icon="terp-stock_align_left_24" context="{'group_by':'name'}" />
<filter string="Contact" icon="terp-partner" context="{'group_by':'partner_id'}" />
<filter string="Assigned to" name="User" icon="terp-personal" context="{'group_by':'user_id'}" />
<filter string="Company" icon="terp-go-home" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Day" icon="terp-go-today" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" icon="terp-go-month" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" icon="terp-go-year" context="{'group_by':'year'}" help="Creation Date"/>
<filter string="Project" name="project" context="{'group_by':'project_id'}"/>
<filter string="Task" context="{'group_by':'name'}" />
<filter string="Contact" context="{'group_by':'partner_id'}" />
<filter string="Assigned to" name="User" context="{'group_by':'user_id'}" />
<filter string="Company" context="{'group_by':'company_id'}" groups="base.group_multi_company"/>
<filter string="Day" context="{'group_by':'day'}" help="Creation Date"/>
<filter string="Month" context="{'group_by':'month'}" help="Creation Date"/>
<filter string="Year" context="{'group_by':'year'}" help="Creation Date"/>
<filter string="Last Stage Update" context="{'group_by':'date_last_stage_update'}" help="Last Stage Update"/>
</group>
</search>
</field>

View File

@ -19,6 +19,7 @@ access_project_task_history,project.task.history project,project.model_project_t
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_manager,1,0,0,0
access_project_task_history_cumulative,project.task.history project,project.model_project_task_history_cumulative,project.group_project_user,1,0,0,0
access_resource_calendar,project.resource_calendar manager,resource.model_resource_calendar,project.group_project_manager,1,0,0,0
access_resource_calendar_leaves_user,resource.calendar.leaves user,resource.model_resource_calendar_leaves,project.group_project_user,1,1,1,1
access_project_category,project.project_category,model_project_category,,1,0,0,0
access_project_category_manager,project.project_category,model_project_category,project.group_project_manager,1,1,1,1
access_mail_alias,mail.alias,mail.model_mail_alias,project.group_project_manager,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
19 access_project_task_history_cumulative project.task.history project project.model_project_task_history_cumulative project.group_project_manager 1 0 0 0
20 access_project_task_history_cumulative project.task.history project project.model_project_task_history_cumulative project.group_project_user 1 0 0 0
21 access_resource_calendar project.resource_calendar manager resource.model_resource_calendar project.group_project_manager 1 0 0 0
22 access_resource_calendar_leaves_user resource.calendar.leaves user resource.model_resource_calendar_leaves project.group_project_user 1 1 1 1
23 access_project_category project.project_category model_project_category 1 0 0 0
24 access_project_category_manager project.project_category model_project_category project.group_project_manager 1 1 1 1
25 access_mail_alias mail.alias mail.model_mail_alias project.group_project_manager 1 1 1 1

View File

@ -1,12 +0,0 @@
-
!record {model: project.project, id: project_project_1, view: False}:
partner_id: base.res_partner_2
-
!record {model: project.task, id: project_task_1, view: False}:
remaining_hours: 10.00
-
!record {model: project.task, id: project_task_1, view: False}:
planned_hours: 10.00
-
!record {model: project.task, id: project_task_1, view: False}:
project_id: project_project_1

View File

@ -1,70 +0,0 @@
-
In order to Test Process of Project Management,
-
I create duplicate template.
-
!python {model: project.project}: |
new_template = self.duplicate_template(cr, uid, [ref("project_project_1")])
assert new_template, "duplicate template is not created"
template = self.browse(cr, uid, new_template['res_id'], context=context)
assert template.state == 'open', "Duplicate template must be in open state."
-
I convert template into real Project.
-
!python {model: project.project}: |
self.reset_project(cr, uid, [ref("project_project_1")])
-
I check project details after convert from template.
-
!assert {model: project.project, id: project_project_1, severity: error, string: Project should be active}:
- state == "open"
-
I put project in pending.
-
!python {model: project.project}: |
self.set_pending(cr, uid, [ref("project_project_1")])
-
I check state after put in pending.
-
!assert {model: project.project, id: project_project_1, severity: error, string: Project should be in pending state}:
- state == "pending"
-
I re-open the project.
-
!python {model: project.project}: |
self.set_open(cr, uid, [ref("project_project_1")])
-
I check state after reopen.
-
!assert {model: project.project, id: project_project_1, severity: error, string: Project should be open.}:
- state == "open"
-
I close the project.
-
!python {model: project.project}: |
self.set_done(cr, uid, [ref("project_project_1")])
-
I check state after closed.
-
!assert {model: project.project, id: project_project_1, severity: error, string: Project should be close.}:
- state == "close"
-
I set project into template.
-
!python {model: project.project}: |
self.set_template(cr, uid, [ref("project_project_1")])
-
I schedule tasks of project.
-
!python {model: project.project}: |
self.schedule_tasks(cr, uid, [ref("project_project_1")], context=context)
-
I copy the tasks of project.
-
!python {model: project.project}: |
self.copy(cr, uid, ref("project_project_1"))
-
I cancel Project.
-
!python {model: project.project}: |
self.set_cancel(cr, uid, [ref("project_project_2")])

View File

@ -1,76 +0,0 @@
-
I put task in pending due to specification is not clear.
-
!python {model: project.task}: |
self.do_pending(cr, uid, [ref("project_task_1")])
context.update({"active_id": ref("project_task_1")})
-
I check state of task after put in pending.
-
!assert {model: project.task, id: project_task_1, severity: error, string: task should be in pending state}:
- state == "pending"
-
!record {model: project.task.delegate, id: delegate_id}:
user_id: base.user_demo
planned_hours: 12.0
planned_hours_me: 2.0
-
Now I delegate task to team member.
-
!python {model: project.task.delegate}: |
self.delegate(cr, uid, [ref("delegate_id")], {"active_id": ref("project_task_1")})
-
I check delegated task details.
-
!python {model: project.task}: |
task = self.browse(cr, uid, ref("project_task_1"), context=context)
assert task.planned_hours == 2.0, "Planning hours is not correct after delegated."
assert task.state == "pending", "Task should be in Pending after delegated."
-
I re-open the task.
-
!python {model: project.task}: |
self.do_reopen(cr, uid, [ref("project_task_1")])
-
I check reopened task details.
-
!assert {model: project.task, id: project_task_1, severity: error, string: task should be open.}:
- state == "open"
-
I change the stage of task to next stage.
-
!python {model: project.task}: |
self.stage_next(cr, uid, [ref("project_task_1")])
-
!record {model: project.task.reevaluate, id: reevaluate_id}:
remaining_hours : 120
-
I reevaluate task with remaining hours.
-
!python {model: project.task.reevaluate}: |
self.compute_hours(cr, uid, [ref("reevaluate_id")], {"active_id": ref("project_task_1")})
-
I check remaining hours after reevaluated task.
-
!assert {model: project.task, id: project_task_1, severity: error, string: task should be reevaluated}:
- remaining_hours == 120.0
-
I close the task.
-
!python {model: project.task}: |
self.action_close(cr, uid, [ref("project_task_1")])
-
I check state after closed.
-
!assert {model: project.task, id: project_task_1, severity: error, string: task is in open state}:
- state == "done"
-
I change the stage of task to previous stage.
-
!python {model: project.task}: |
self.stage_previous(cr, uid, [ref("project_task_1")])
-
I cancel Task.
-
!python {model: project.task}: |
self.do_cancel(cr, uid, [ref("project_task_2")])

View File

@ -0,0 +1,28 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from . import test_project_flow
checks = [
test_project_flow,
]
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,99 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.mail.tests.test_mail_base import TestMailBase
class TestProjectBase(TestMailBase):
def setUp(self):
super(TestProjectBase, self).setUp()
cr, uid = self.cr, self.uid
# Usefull models
self.project_project = self.registry('project.project')
self.project_task = self.registry('project.task')
self.project_task_delegate = self.registry('project.task.delegate')
# Find Project User group
group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
# Find Project Manager group
group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
# Test partners to use through the various tests
self.project_partner_id = self.res_partner.create(cr, uid, {
'name': 'Gertrude AgrolaitPartner',
'email': 'gertrude.partner@agrolait.com',
})
self.email_partner_id = self.res_partner.create(cr, uid, {
'name': 'Patrick Ratatouille',
'email': 'patrick.ratatouille@agrolait.com',
})
# Test users to use through the various tests
self.user_projectuser_id = self.res_users.create(cr, uid, {
'name': 'Armande ProjectUser',
'login': 'Armande',
'alias_name': 'armande',
'email': 'armande.projectuser@example.com',
'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
})
self.user_projectmanager_id = self.res_users.create(cr, uid, {
'name': 'Bastien ProjectManager',
'login': 'bastien',
'alias_name': 'bastien',
'email': 'bastien.projectmanager@example.com',
'groups_id': [(6, 0, [self.group_employee_id, self.group_project_manager_id])]
})
self.user_none_id = self.res_users.create(cr, uid, {
'name': 'Charlie Avotbonkeur',
'login': 'charlie',
'alias_name': 'charlie',
'email': 'charlie.noone@example.com',
'groups_id': [(6, 0, [])]
})
self.user_projectuser = self.res_users.browse(cr, uid, self.user_projectuser_id)
self.user_projectmanager = self.res_users.browse(cr, uid, self.user_projectmanager_id)
self.partner_projectuser_id = self.user_projectuser.partner_id.id
self.partner_projectmanager_id = self.user_projectmanager.partner_id.id
# Test 'Pigs' project
self.project_pigs_id = self.project_project.create(cr, uid, {
'name': 'Pigs',
'privacy_visibility': 'public',
'alias_name': 'project+pigs',
'partner_id': self.partner_raoul_id,
}, {'mail_create_nolog': True})
# Already-existing tasks in Pigs
self.task_1_id = self.project_task.create(cr, uid, {
'name': 'Pigs UserTask',
'user_id': self.user_projectuser_id,
'project_id': self.project_pigs_id,
}, {'mail_create_nolog': True})
self.task_2_id = self.project_task.create(cr, uid, {
'name': 'Pigs ManagerTask',
'user_id': self.user_projectmanager_id,
'project_id': self.project_pigs_id,
}, {'mail_create_nolog': True})

View File

@ -0,0 +1,163 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Business Applications
# Copyright (c) 2013-TODAY OpenERP S.A. <http://www.openerp.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
from openerp.addons.project.tests.test_project_base import TestProjectBase
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
EMAIL_TPL = """Return-Path: <whatever-2a840@postmaster.twitter.com>
X-Original-To: {email_to}
Delivered-To: {email_to}
To: {email_to}
Received: by mail1.openerp.com (Postfix, from userid 10002)
id 5DF9ABFB2A; Fri, 10 Aug 2012 16:16:39 +0200 (CEST)
Message-ID: {msg_id}
Date: Tue, 29 Nov 2011 12:43:21 +0530
From: {email_from}
MIME-Version: 1.0
Subject: {subject}
Content-Type: text/plain; charset=ISO-8859-1; format=flowed
Hello,
This email should create a new entry in your module. Please check that it
effectively works.
Thanks,
--
Raoul Boitempoils
Integrator at Agrolait"""
class TestProjectFlow(TestProjectBase):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_project_process(self):
""" Testing project management """
cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
# ProjectUser: set project as template -> raise
self.assertRaises(except_orm, self.project_project.set_template, cr, user_projectuser_id, [project_pigs_id])
# Other tests are done using a ProjectManager
project = self.project_project.browse(cr, user_projectmanager_id, project_pigs_id)
self.assertNotEqual(project.state, 'template', 'project: incorrect state, should not be a template')
# Set test project as template
self.project_project.set_template(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'template', 'project: set_template: project state should be template')
self.assertEqual(len(project.tasks), 0, 'project: set_template: project tasks should have been set inactive')
# Duplicate template
new_template_act = self.project_project.duplicate_template(cr, user_projectmanager_id, [project_pigs_id])
new_project = self.project_project.browse(cr, user_projectmanager_id, new_template_act['res_id'])
self.assertEqual(new_project.state, 'open', 'project: incorrect duplicate_template')
self.assertEqual(len(new_project.tasks), 2, 'project: duplicating a project template should duplicate its tasks')
# Convert into real project
self.project_project.reset_project(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'open', 'project: resetted project should be in open state')
self.assertEqual(len(project.tasks), 2, 'project: reset_project: project tasks should have been set active')
# Put as pending
self.project_project.set_pending(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'pending', 'project: should be in pending state')
# Re-open
self.project_project.set_open(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'open', 'project: reopened project should be in open state')
# Close project
self.project_project.set_done(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
self.assertEqual(project.state, 'close', 'project: closed project should be in close state')
# Re-open
self.project_project.set_open(cr, user_projectmanager_id, [project_pigs_id])
project.refresh()
# Re-convert into a template and schedule tasks
self.project_project.set_template(cr, user_projectmanager_id, [project_pigs_id])
self.project_project.schedule_tasks(cr, user_projectmanager_id, [project_pigs_id])
# Copy the project
new_project_id = self.project_project.copy(cr, user_projectmanager_id, project_pigs_id)
new_project = self.project_project.browse(cr, user_projectmanager_id, new_project_id)
self.assertEqual(len(new_project.tasks), 2, 'project: copied project should have copied task')
# Cancel the project
self.project_project.set_cancel(cr, user_projectmanager_id, [project_pigs_id])
self.assertEqual(project.state, 'cancelled', 'project: cancelled project should be in cancel state')
def test_10_task_process(self):
""" Testing task creation and management """
cr, uid, user_projectuser_id, user_projectmanager_id, project_pigs_id = self.cr, self.uid, self.user_projectuser_id, self.user_projectmanager_id, self.project_pigs_id
def format_and_process(template, email_to='project+pigs@mydomain.com, other@gmail.com', subject='Frogs',
email_from='Patrick Ratatouille <patrick.ratatouille@agrolait.com>',
msg_id='<1198923581.41972151344608186760.JavaMail@agrolait.com>'):
self.assertEqual(self.project_task.search(cr, uid, [('name', '=', subject)]), [])
mail = template.format(email_to=email_to, subject=subject, email_from=email_from, msg_id=msg_id)
self.mail_thread.message_process(cr, uid, None, mail)
return self.project_task.search(cr, uid, [('name', '=', subject)])
# Do: incoming mail from an unknown partner on an alias creates a new task 'Frogs'
frogs = format_and_process(EMAIL_TPL)
# Test: one task created by mailgateway administrator
self.assertEqual(len(frogs), 1, 'project: message_process: a new project.task should have been created')
task = self.project_task.browse(cr, user_projectuser_id, frogs[0])
res = self.project_task.perm_read(cr, uid, [task.id], details=False)
self.assertEqual(res[0].get('create_uid'), uid,
'project: message_process: task should have been created by uid as alias_user_id is False on the alias')
# Test: messages
self.assertEqual(len(task.message_ids), 3,
'project: message_process: newly created task should have 2 messages: creation and email')
self.assertEqual(task.message_ids[2].subtype_id.name, 'Task Created',
'project: message_process: first message of new task should have Task Created subtype')
self.assertEqual(task.message_ids[1].subtype_id.name, 'Task Assigned',
'project: message_process: first message of new task should have Task Created subtype')
self.assertEqual(task.message_ids[0].author_id.id, self.email_partner_id,
'project: message_process: second message should be the one from Agrolait (partner failed)')
self.assertEqual(task.message_ids[0].subject, 'Frogs',
'project: message_process: second message should be the one from Agrolait (subject failed)')
# Test: task content
self.assertEqual(task.name, 'Frogs', 'project_task: name should be the email subject')
self.assertEqual(task.project_id.id, self.project_pigs_id, 'project_task: incorrect project')
self.assertEqual(task.stage_id.sequence, 1, 'project_task: should have a stage with sequence=1')
# Open the delegation wizard
delegate_id = self.project_task_delegate.create(cr, user_projectuser_id, {
'user_id': user_projectuser_id,
'planned_hours': 12.0,
'planned_hours_me': 2.0,
}, {'active_id': task.id})
self.project_task_delegate.delegate(cr, user_projectuser_id, [delegate_id], {'active_id': task.id})
# Check delegation details
task.refresh()
self.assertEqual(task.planned_hours, 2, 'project_task_delegate: planned hours is not correct after delegation')

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