merge with lp:~openerp-dev/openobject-addons/trunk-social-tde

bzr revid: rgaopenerp-20120313094508-h0oolqltgxja46r7
This commit is contained in:
RGA(OpenERP) 2012-03-13 15:15:08 +05:30
commit debad43b1c
38 changed files with 5720 additions and 225 deletions

View File

@ -0,0 +1,793 @@
# Lithuanian translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 01:37+0100\n"
"PO-Revision-Date: 2012-03-09 15:10+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Lithuanian <lt@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-03-10 04:54+0000\n"
"X-Generator: Launchpad (build 14914)\n"
#. module: account_asset
#: view:account.asset.asset:0
msgid "Assets in draft and open states"
msgstr ""
#. module: account_asset
#: field:account.asset.category,method_end:0
#: field:account.asset.history,method_end:0 field:asset.modify,method_end:0
msgid "Ending date"
msgstr "Pabaigos data"
#. module: account_asset
#: field:account.asset.asset,value_residual:0
msgid "Residual Value"
msgstr ""
#. module: account_asset
#: field:account.asset.category,account_expense_depreciation_id:0
msgid "Depr. Expense Account"
msgstr ""
#. module: account_asset
#: view:asset.depreciation.confirmation.wizard:0
msgid "Compute Asset"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Group By..."
msgstr ""
#. module: account_asset
#: field:asset.asset.report,gross_value:0
msgid "Gross Amount"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0 field:account.asset.asset,name:0
#: field:account.asset.depreciation.line,asset_id:0
#: field:account.asset.history,asset_id:0 field:account.move.line,asset_id:0
#: view:asset.asset.report:0 field:asset.asset.report,asset_id:0
#: model:ir.model,name:account_asset.model_account_asset_asset
msgid "Asset"
msgstr ""
#. module: account_asset
#: help:account.asset.asset,prorata:0 help:account.asset.category,prorata:0
msgid ""
"Indicates that the first depreciation entry for this asset have to be done "
"from the purchase date instead of the first January"
msgstr ""
#. module: account_asset
#: field:account.asset.history,name:0
msgid "History name"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,company_id:0
#: field:account.asset.category,company_id:0 view:asset.asset.report:0
#: field:asset.asset.report,company_id:0
msgid "Company"
msgstr ""
#. module: account_asset
#: view:asset.modify:0
msgid "Modify"
msgstr ""
#. module: account_asset
#: selection:account.asset.asset,state:0 view:asset.asset.report:0
#: selection:asset.asset.report,state:0
msgid "Running"
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,amount:0
msgid "Depreciation Amount"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
#: model:ir.actions.act_window,name:account_asset.action_asset_asset_report
#: model:ir.model,name:account_asset.model_asset_asset_report
#: model:ir.ui.menu,name:account_asset.menu_action_asset_asset_report
msgid "Assets Analysis"
msgstr ""
#. module: account_asset
#: field:asset.modify,name:0
msgid "Reason"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,method_progress_factor:0
#: field:account.asset.category,method_progress_factor:0
msgid "Degressive Factor"
msgstr ""
#. module: account_asset
#: 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 ""
#. module: account_asset
#: view:asset.depreciation.confirmation.wizard:0
msgid ""
"This wizard will post the depreciation lines of running assets that belong "
"to the selected period."
msgstr ""
#. module: account_asset
#: field:account.asset.asset,account_move_line_ids:0
#: field:account.move.line,entry_ids:0
#: model:ir.actions.act_window,name:account_asset.act_entries_open
msgid "Entries"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
#: field:account.asset.asset,depreciation_line_ids:0
msgid "Depreciation Lines"
msgstr ""
#. module: account_asset
#: help:account.asset.asset,salvage_value:0
msgid "It is the amount you plan to have that you cannot depreciate."
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,depreciation_date:0
#: view:asset.asset.report:0 field:asset.asset.report,depreciation_date:0
msgid "Depreciation Date"
msgstr ""
#. module: account_asset
#: field:account.asset.category,account_asset_id:0
msgid "Asset Account"
msgstr ""
#. module: account_asset
#: field:asset.asset.report,posted_value:0
msgid "Posted Amount"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0 view:asset.asset.report:0
#: model:ir.actions.act_window,name:account_asset.action_account_asset_asset_form
#: model:ir.ui.menu,name:account_asset.menu_action_account_asset_asset_form
#: model:ir.ui.menu,name:account_asset.menu_finance_assets
#: model:ir.ui.menu,name:account_asset.menu_finance_config_assets
msgid "Assets"
msgstr ""
#. module: account_asset
#: field:account.asset.category,account_depreciation_id:0
msgid "Depreciation Account"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0 view:account.asset.category:0
#: view:account.asset.history:0 view:asset.modify:0 field:asset.modify,note:0
msgid "Notes"
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,move_id:0
msgid "Depreciation Entry"
msgstr ""
#. module: account_asset
#: sql_constraint:account.move.line:0
msgid "Wrong credit or debit value in accounting entry !"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0 field:asset.asset.report,nbr:0
msgid "# of Depreciation Lines"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Assets in draft state"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,method_end:0
#: selection:account.asset.asset,method_time:0
#: selection:account.asset.category,method_time:0
#: selection:account.asset.history,method_time:0
msgid "Ending Date"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,code:0
msgid "Reference"
msgstr ""
#. module: account_asset
#: constraint:account.invoice:0
msgid "Invalid BBA Structured Communication !"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Account Asset"
msgstr ""
#. module: account_asset
#: model:ir.actions.act_window,name:account_asset.action_asset_depreciation_confirmation_wizard
#: model:ir.ui.menu,name:account_asset.menu_asset_depreciation_confirmation_wizard
msgid "Compute Assets"
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,sequence:0
msgid "Sequence of the depreciation"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,method_period:0
#: field:account.asset.category,method_period:0
#: field:account.asset.history,method_period:0
#: field:asset.modify,method_period:0
msgid "Period Length"
msgstr ""
#. module: account_asset
#: selection:account.asset.asset,state:0 view:asset.asset.report:0
#: selection:asset.asset.report,state:0
msgid "Draft"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Date of asset purchase"
msgstr ""
#. module: account_asset
#: help:account.asset.asset,method_number:0
msgid "Calculates Depreciation within specified interval"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Change Duration"
msgstr ""
#. module: account_asset
#: field:account.asset.category,account_analytic_id:0
msgid "Analytic account"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,method:0 field:account.asset.category,method:0
msgid "Computation Method"
msgstr ""
#. module: account_asset
#: help:account.asset.asset,method_period:0
msgid "State here the time during 2 depreciations, in months"
msgstr ""
#. module: account_asset
#: constraint:account.asset.asset:0
msgid ""
"Prorata temporis can be applied only for time method \"number of "
"depreciations\"."
msgstr ""
#. module: account_asset
#: help:account.asset.history,method_time:0
msgid ""
"The method to use to compute the dates and number of depreciation lines.\n"
"Number of Depreciations: Fix the number of depreciation lines and the time "
"between 2 depreciations.\n"
"Ending Date: Choose the time between 2 depreciations and the date the "
"depreciations won't go beyond."
msgstr ""
#. module: account_asset
#: field:account.asset.asset,purchase_value:0
msgid "Gross value "
msgstr ""
#. module: account_asset
#: constraint:account.asset.asset:0
msgid "Error ! You can not create recursive assets."
msgstr ""
#. module: account_asset
#: help:account.asset.history,method_period:0
msgid "Time in month between two depreciations"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0 field:asset.asset.report,name:0
msgid "Year"
msgstr ""
#. module: account_asset
#: view:asset.modify:0
#: model:ir.actions.act_window,name:account_asset.action_asset_modify
#: model:ir.model,name:account_asset.model_asset_modify
msgid "Modify Asset"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Other Information"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,salvage_value:0
msgid "Salvage Value"
msgstr ""
#. module: account_asset
#: field:account.invoice.line,asset_category_id:0 view:asset.asset.report:0
msgid "Asset Category"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Set to Close"
msgstr ""
#. module: account_asset
#: model:ir.actions.wizard,name:account_asset.wizard_asset_compute
msgid "Compute assets"
msgstr ""
#. module: account_asset
#: model:ir.actions.wizard,name:account_asset.wizard_asset_modify
msgid "Modify asset"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Assets in closed state"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,parent_id:0
msgid "Parent Asset"
msgstr ""
#. module: account_asset
#: view:account.asset.history:0
#: model:ir.model,name:account_asset.model_account_asset_history
msgid "Asset history"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Assets purchased in current year"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,state:0 field:asset.asset.report,state:0
msgid "State"
msgstr ""
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_invoice_line
msgid "Invoice Line"
msgstr ""
#. module: account_asset
#: constraint:account.move.line:0
msgid ""
"The selected account of your Journal Entry forces to provide a secondary "
"currency. You should remove the secondary currency on the account or select "
"a multi-currency view on the journal."
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Month"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Depreciation Board"
msgstr ""
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_move_line
msgid "Journal Items"
msgstr ""
#. module: account_asset
#: field:asset.asset.report,unposted_value:0
msgid "Unposted Amount"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,method_time:0
#: field:account.asset.category,method_time:0
#: field:account.asset.history,method_time:0
msgid "Time Method"
msgstr ""
#. module: account_asset
#: view:account.asset.category:0
msgid "Analytic information"
msgstr ""
#. module: account_asset
#: view:asset.modify:0
msgid "Asset durations to modify"
msgstr ""
#. module: account_asset
#: constraint:account.move.line:0
msgid ""
"The date of your Journal Entry is not in the defined period! You should "
"change the date or remove this constraint from the journal."
msgstr ""
#. module: account_asset
#: field:account.asset.asset,note:0 field:account.asset.category,note:0
#: field:account.asset.history,note:0
msgid "Note"
msgstr ""
#. module: account_asset
#: help:account.asset.asset,method:0 help:account.asset.category,method:0
msgid ""
"Choose the method to use to compute the amount of depreciation lines.\n"
" * Linear: Calculated on basis of: Gross Value / Number of Depreciations\n"
" * Degressive: Calculated on basis of: Remaining Value * Degressive Factor"
msgstr ""
#. module: account_asset
#: help:account.asset.asset,method_time:0
#: help:account.asset.category,method_time:0
msgid ""
"Choose the method to use to compute the dates and number of depreciation "
"lines.\n"
" * Number of Depreciations: Fix the number of depreciation lines and the "
"time between 2 depreciations.\n"
" * Ending Date: Choose the time between 2 depreciations and the date the "
"depreciations won't go beyond."
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Assets in running state"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Closed"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,partner_id:0
#: field:asset.asset.report,partner_id:0
msgid "Partner"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0 field:asset.asset.report,depreciation_value:0
msgid "Amount of Depreciation Lines"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Posted depreciation lines"
msgstr ""
#. module: account_asset
#: constraint:account.move.line:0
msgid "Company must be the same for its related account and period."
msgstr ""
#. module: account_asset
#: field:account.asset.asset,child_ids:0
msgid "Children Assets"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Date of depreciation"
msgstr ""
#. module: account_asset
#: field:account.asset.history,user_id:0
msgid "User"
msgstr ""
#. module: account_asset
#: field:account.asset.history,date:0
msgid "Date"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Assets purchased in current month"
msgstr ""
#. module: account_asset
#: constraint:account.move.line:0
msgid "You can not create journal items on an account of type view."
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Extended Filters..."
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0 view:asset.depreciation.confirmation.wizard:0
msgid "Compute"
msgstr ""
#. module: account_asset
#: view:account.asset.category:0
msgid "Search Asset Category"
msgstr ""
#. module: account_asset
#: model:ir.model,name:account_asset.model_asset_depreciation_confirmation_wizard
msgid "asset.depreciation.confirmation.wizard"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,active:0
msgid "Active"
msgstr ""
#. module: account_asset
#: model:ir.actions.wizard,name:account_asset.wizard_asset_close
msgid "Close asset"
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,parent_state:0
msgid "State of Asset"
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,name:0
msgid "Depreciation Name"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0 field:account.asset.asset,history_ids:0
msgid "History"
msgstr ""
#. module: account_asset
#: sql_constraint:account.invoice:0
msgid "Invoice Number must be unique per Company!"
msgstr ""
#. module: account_asset
#: field:asset.depreciation.confirmation.wizard,period_id:0
msgid "Period"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "General"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,prorata:0 field:account.asset.category,prorata:0
msgid "Prorata Temporis"
msgstr ""
#. module: account_asset
#: view:account.asset.category:0
msgid "Accounting information"
msgstr ""
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_invoice
msgid "Invoice"
msgstr ""
#. module: account_asset
#: model:ir.actions.act_window,name:account_asset.action_account_asset_asset_form_normal
msgid "Review Asset Categories"
msgstr ""
#. module: account_asset
#: view:asset.depreciation.confirmation.wizard:0 view:asset.modify:0
msgid "Cancel"
msgstr ""
#. module: account_asset
#: selection:account.asset.asset,state:0 selection:asset.asset.report,state:0
msgid "Close"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0 view:account.asset.category:0
msgid "Depreciation Method"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,purchase_date:0 view:asset.asset.report:0
#: field:asset.asset.report,purchase_date:0
msgid "Purchase Date"
msgstr ""
#. module: account_asset
#: selection:account.asset.asset,method:0
#: selection:account.asset.category,method:0
msgid "Degressive"
msgstr ""
#. module: account_asset
#: help:asset.depreciation.confirmation.wizard,period_id:0
msgid ""
"Choose the period for which you want to automatically post the depreciation "
"lines of running assets"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Current"
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,remaining_value:0
msgid "Amount to Depreciate"
msgstr ""
#. module: account_asset
#: field:account.asset.category,open_asset:0
msgid "Skip Draft State"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0 view:account.asset.category:0
#: view:account.asset.history:0
msgid "Depreciation Dates"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,currency_id:0
msgid "Currency"
msgstr ""
#. module: account_asset
#: field:account.asset.category,journal_id:0
msgid "Journal"
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,depreciated_value:0
msgid "Amount Already Depreciated"
msgstr ""
#. module: account_asset
#: field:account.asset.depreciation.line,move_check:0
#: view:asset.asset.report:0 field:asset.asset.report,move_check:0
msgid "Posted"
msgstr ""
#. module: account_asset
#: help:account.asset.asset,state:0
msgid ""
"When an asset is created, the state is 'Draft'.\n"
"If the asset is confirmed, the state goes in 'Running' and the depreciation "
"lines can be posted in the accounting.\n"
"You can manually close an asset when the depreciation is over. If the last "
"line of depreciation is posted, the asset automatically goes in that state."
msgstr ""
#. module: account_asset
#: field:account.asset.category,name:0
msgid "Name"
msgstr ""
#. module: account_asset
#: help:account.asset.category,open_asset:0
msgid ""
"Check this if you want to automatically confirm the assets of this category "
"when created by invoices."
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Set to Draft"
msgstr ""
#. module: account_asset
#: selection:account.asset.asset,method:0
#: selection:account.asset.category,method:0
msgid "Linear"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Month-1"
msgstr ""
#. module: account_asset
#: model:ir.model,name:account_asset.model_account_asset_depreciation_line
msgid "Asset depreciation line"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,category_id:0 view:account.asset.category:0
#: field:asset.asset.report,asset_category_id:0
#: model:ir.model,name:account_asset.model_account_asset_category
msgid "Asset category"
msgstr ""
#. module: account_asset
#: view:asset.asset.report:0
msgid "Assets purchased in last month"
msgstr ""
#. module: account_asset
#: code:addons/account_asset/wizard/wizard_asset_compute.py:49
#, python-format
msgid "Created Asset Moves"
msgstr ""
#. module: account_asset
#: constraint:account.move.line:0
msgid "You can not create journal items on closed account."
msgstr ""
#. module: account_asset
#: model:ir.actions.act_window,help:account_asset.action_asset_asset_report
msgid ""
"From this report, you can have an overview on all depreciation. The tool "
"search can also be used to personalise your Assets reports and so, match "
"this analysis to your needs;"
msgstr ""
#. module: account_asset
#: help:account.asset.category,method_period:0
msgid "State here the time between 2 depreciations, in months"
msgstr ""
#. module: account_asset
#: field:account.asset.asset,method_number:0
#: selection:account.asset.asset,method_time:0
#: field:account.asset.category,method_number:0
#: selection:account.asset.category,method_time:0
#: field:account.asset.history,method_number:0
#: selection:account.asset.history,method_time:0
#: field:asset.modify,method_number:0
msgid "Number of Depreciations"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Create Move"
msgstr ""
#. module: account_asset
#: view:asset.depreciation.confirmation.wizard:0
msgid "Post Depreciation Lines"
msgstr ""
#. module: account_asset
#: view:account.asset.asset:0
msgid "Confirm Asset"
msgstr ""
#. module: account_asset
#: model:ir.actions.act_window,name:account_asset.action_account_asset_asset_tree
#: model:ir.ui.menu,name:account_asset.menu_action_account_asset_asset_tree
msgid "Asset Hierarchy"
msgstr ""

View File

@ -0,0 +1,376 @@
# Arabic translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2012-03-09 13:28+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Arabic <ar@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-03-10 04:54+0000\n"
"X-Generator: Launchpad (build 14914)\n"
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Search Bank Transactions"
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
#: constraint:res.partner.bank:0
msgid "The RIB and/or IBAN is not valid"
msgstr ""
#. module: account_bank_statement_extensions
#: view:account.bank.statement.line:0
msgid "Group By..."
msgstr "تجميع حسب"
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,state:0
msgid "State"
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
#: view:confirm.statement.line:0
msgid "Confirm Lines"
msgstr "تأكيد الأسطر"
#. module: account_bank_statement_extensions
#: code:addons/account_bank_statement_extensions/account_bank_statement.py:130
#, python-format
msgid ""
"Delete operation not allowed ! Please go to the associated bank "
"statement in order to delete and/or modify this bank statement line"
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
#: field:account.bank.statement.line,journal_id: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
#: view:account.bank.statement.line.global:0
msgid "Transactions"
msgstr "المعاملات البنكية"
#. module: account_bank_statement_extensions
#: code:addons/account_bank_statement_extensions/account_bank_statement.py:130
#, python-format
msgid "Warning"
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
#: constraint:res.partner.bank:0
msgid ""
"\n"
"Please define BIC/Swift code on bank for bank type IBAN Account to make "
"valid payments"
msgstr ""
#. module: account_bank_statement_extensions
#: field:account.bank.statement.line,val_date:0
msgid "Valuta Date"
msgstr ""
#. module: account_bank_statement_extensions
#: model:ir.actions.act_window,help:account_bank_statement_extensions.action_confirm_statement_line
msgid "Confirm selected statement lines."
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
#: selection:account.bank.statement.line.global,type:0
msgid "ISO 20022"
msgstr ""
#. 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 "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:confirm.statement.line:0
msgid "Are you sure you want to confirm the selected Bank Statement lines ?"
msgstr ""
#. module: account_bank_statement_extensions
#: constraint:account.bank.statement.line:0
msgid ""
"The amount of the voucher must be the same amount as the one on the "
"statement line"
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
#: field:account.bank.statement.line.global,name:0
msgid "Communication"
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
#: constraint:account.bank.statement:0
msgid "The journal and period chosen have to belong to the same company."
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
#: view:account.bank.statement.line.global:0
msgid "Child Batch Payments"
msgstr ""
#. module: account_bank_statement_extensions
#: view:cancel.statement.line:0
#: 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

@ -0,0 +1,23 @@
# Lithuanian translation for openobject-addons
# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012
# This file is distributed under the same license as the openobject-addons package.
# FIRST AUTHOR <EMAIL@ADDRESS>, 2012.
#
msgid ""
msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
"POT-Creation-Date: 2012-02-08 00:35+0000\n"
"PO-Revision-Date: 2012-03-09 15:21+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Lithuanian <lt@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-03-10 04:54+0000\n"
"X-Generator: Launchpad (build 14914)\n"
#. module: account_cancel
#: view:account.invoice:0
msgid "Cancel"
msgstr "Atšaukti"

View File

@ -267,7 +267,7 @@ class crm_base(object):
data = {'state': 'open', 'active': True}
if not case.user_id:
data['user_id'] = uid
self.write(cr, uid, case.id, data)
self.write(cr, uid, [case.id], data)
self._action(cr, uid, cases, 'open')
return True
@ -393,12 +393,11 @@ class crm_case(crm_base):
def case_open(self, cr, uid, ids, *args):
"""Opens Case"""
cases = self.browse(cr, uid, ids)
self.message_append(cr, uid, cases, _('Open'))
for case in cases:
data = {'state': 'open', 'active': True }
if not case.user_id:
data['user_id'] = uid
self.write(cr, uid, case.id, data)
self.write(cr, uid, [case.id], data)
self._action(cr, uid, cases, 'open')
return True
@ -406,7 +405,6 @@ class crm_case(crm_base):
"""Closes Case"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.message_append(cr, uid, cases, _('Close'))
self.write(cr, uid, ids, {'state': 'done',
'date_closed': time.strftime('%Y-%m-%d %H:%M:%S'),
})
@ -430,7 +428,6 @@ class crm_case(crm_base):
raise osv.except_osv(_('Error !'), _('You can not escalate, you are already at the top level regarding your sales-team category.'))
self.write(cr, uid, [case.id], data)
cases = self.browse(cr, uid, ids)
self.message_append(cr, uid, cases, _('Escalate'))
self._action(cr, uid, cases, 'escalate')
return True
@ -438,7 +435,6 @@ class crm_case(crm_base):
"""Cancels Case"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.message_append(cr, uid, cases, _('Cancel'))
self.write(cr, uid, ids, {'state': 'cancel',
'active': True})
self._action(cr, uid, cases, 'cancel')
@ -451,7 +447,6 @@ class crm_case(crm_base):
"""Marks case as pending"""
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.message_append(cr, uid, cases, _('Pending'))
self.write(cr, uid, ids, {'state': 'pending', 'active': True})
self._action(cr, uid, cases, 'pending')
return True
@ -463,7 +458,6 @@ class crm_case(crm_base):
state = 'open'
cases = self.browse(cr, uid, ids)
cases[0].state # to fill the browse record cache
self.message_append(cr, uid, cases, _('Draft'))
self.write(cr, uid, ids, {'state': state, 'active': True})
self._action(cr, uid, cases, state)
return True

View File

@ -547,27 +547,27 @@
</group>
</page>
<page string="Communication &amp; History" groups="base.group_extended">
<group colspan="4">
<group colspan="4">
<field colspan="4" name="email_cc" widget="char" size="512"/>
</group>
<field name="message_ids" colspan="4" nolabel="1" mode="tree" readonly="1">
<tree string="History">
<field name="display_text" string="History Information"/>
<field name="email_from" invisible="1"/>
<button
string="Reply" attrs="{'invisible': [('email_from', '=', False)]}"
name="%(mail.action_email_compose_message_wizard)d"
context="{'mail.compose.message.mode':'reply', 'message_id':active_id}"
icon="terp-mail-replied" type="action" />
</tree>
</field>
<button string="Add Internal Note"
name="%(crm.action_crm_add_note)d"
context="{'model': 'crm.lead' }"
icon="terp-document-new" type="action" />
<button string="Send New Email"
name="%(mail.action_email_compose_message_wizard)d"
icon="terp-mail-message-new" type="action"/>
</group>
<field name="message_ids" colspan="4" nolabel="1" mode="tree" readonly="1">
<tree string="History">
<field name="display_text" string="History Information"/>
<field name="email_from" invisible="1"/>
<button
string="Reply" attrs="{'invisible': [('email_from', '=', False)]}"
name="%(mail.action_email_compose_message_wizard)d"
context="{'mail.compose.message.mode':'reply', 'message_id':active_id}"
icon="terp-mail-replied" type="action" />
</tree>
</field>
<button string="Add Internal Note"
name="%(crm.action_crm_add_note)d"
context="{'model': 'crm.lead' }"
icon="terp-document-new" type="action" />
<button string="Send New Email"
name="%(mail.action_email_compose_message_wizard)d"
icon="terp-mail-message-new" type="action"/>
</page>
<page string="Extra Info" groups="base.group_extended">
<group col="2" colspan="2">

View File

@ -138,7 +138,7 @@ class crm_lead_forward_to_partner(osv.osv_memory):
if email_to not in new_cc:
new_cc.append(to)
update_vals = {'email_cc' : ', '.join(new_cc) }
lead.write(cr, uid, case.id, update_vals, context=context)
lead.write(cr, uid, [case.id], update_vals, context=context)
return res
def _get_info_body_text(self, cr, uid, lead, context=None):

View File

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

View File

@ -23,6 +23,9 @@ from osv import fields, osv
import logging
import addons
import io, StringIO
from PIL import Image
class hr_employee_category(osv.osv):
def name_get(self, cr, uid, ids, context=None):
@ -145,6 +148,26 @@ class hr_employee(osv.osv):
_name = "hr.employee"
_description = "Employee"
_inherits = {'resource.resource': "resource_id"}
def _get_photo_mini(self, cr, uid, ids, name, args, context=None):
result = {}
for obj in self.browse(cr, uid, ids, context=context):
if not obj.photo:
result[obj.id] = False
continue
image_stream = io.BytesIO(obj.photo.decode('base64'))
img = Image.open(image_stream)
img.thumbnail((180, 150), Image.ANTIALIAS)
img_stream = StringIO.StringIO()
img.save(img_stream, "JPEG")
result[obj.id] = img_stream.getvalue().encode('base64')
return result
def _set_photo_mini(self, cr, uid, id, name, value, args, context=None):
self.write(cr, uid, [id], {'photo': value}, context=context)
return True
_columns = {
'country_id': fields.many2one('res.country', 'Nationality'),
'birthday': fields.date("Date of Birth"),
@ -171,6 +194,10 @@ class hr_employee(osv.osv):
'coach_id': fields.many2one('hr.employee', 'Coach'),
'job_id': fields.many2one('hr.job', 'Job'),
'photo': fields.binary('Photo'),
'photo_mini': fields.function(_get_photo_mini, fnct_inv=_set_photo_mini, string='Photo Mini', type="binary",
store = {
'hr.employee': (lambda self, cr, uid, ids, c={}: ids, ['photo'], 10),
}),
'passport_id':fields.char('Passport No', size=64),
'color': fields.integer('Color Index'),
'city': fields.related('address_id', 'city', type='char', string='City'),

File diff suppressed because one or more lines are too long

View File

@ -33,7 +33,7 @@
<field name="parent_id" />
</group>
<group colspan="2" col="1">
<field name="photo" widget='image' nolabel="1"/>
<field name="photo_mini" widget='image' nolabel="1"/>
</group>
</group>
<notebook colspan="6">
@ -130,7 +130,6 @@
<field name="model">hr.employee</field>
<field name="type">kanban</field>
<field name="arch" type="xml">
<kanban>
<templates>
<t t-name="kanban-box">

View File

@ -93,6 +93,7 @@ class hr_holidays(osv.osv):
_name = "hr.holidays"
_description = "Leave"
_order = "type desc, date_from asc"
_inherit = ['mail.thread']
def _employee_get(self, cr, uid, context=None):
ids = self.pool.get('hr.employee').search(cr, uid, [('user_id', '=', uid)], context=context)
@ -150,7 +151,12 @@ class hr_holidays(osv.osv):
('date_check2', "CHECK ( (type='add') OR (date_from <= date_to))", "The start date must be before the end date !"),
('date_check', "CHECK ( number_of_days_temp >= 0 )", "The number of days must be greater than 0 !"),
]
def create(self, cr, uid, vals, context=None):
obj_id = super(hr_holidays, self).create(cr, uid, vals, context=context)
self.create_notificate(cr, uid, [obj_id], context=context)
return obj_id
def _create_resource_leave(self, cr, uid, leaves, context=None):
'''This method will create entry in resource calendar leave object at the time of holidays validated '''
obj_res_leave = self.pool.get('resource.calendar.leaves')
@ -250,8 +256,9 @@ class hr_holidays(osv.osv):
obj_emp = self.pool.get('hr.employee')
ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
manager = ids2 and ids2[0] or False
self.holidays_validate_notificate(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state':'validate1', 'manager_id': manager})
def holidays_validate2(self, cr, uid, ids, context=None):
self.check_holidays(cr, uid, ids, context=context)
obj_emp = self.pool.get('hr.employee')
@ -301,13 +308,15 @@ class hr_holidays(osv.osv):
wf_service.trg_validate(uid, 'hr.holidays', leave_id, 'validate', cr)
wf_service.trg_validate(uid, 'hr.holidays', leave_id, 'second_validate', cr)
if holiday_ids:
self.holidays_valid2_notificate(self, cr, uid, [holiday_ids], context=context)
self.write(cr, uid, holiday_ids, {'manager_id2': manager})
return True
def holidays_confirm(self, cr, uid, ids, context=None):
self.check_holidays(cr, uid, ids, context=context)
self.holidays_confirm_notificate(cr, uid, ids, context=context)
return self.write(cr, uid, ids, {'state':'confirm'})
def holidays_refuse(self, cr, uid, ids, approval, context=None):
obj_emp = self.pool.get('hr.employee')
ids2 = obj_emp.search(cr, uid, [('user_id', '=', uid)])
@ -316,6 +325,7 @@ class hr_holidays(osv.osv):
self.write(cr, uid, ids, {'state': 'refuse', 'manager_id': manager})
else:
self.write(cr, uid, ids, {'state': 'refuse', 'manager_id2': manager})
self.holidays_refuse_notificate(cr, uid, ids, approval, context=context)
self.holidays_cancel(cr, uid, ids, context=context)
return True
@ -343,6 +353,63 @@ class hr_holidays(osv.osv):
if leaves_rest < record.number_of_days_temp:
raise osv.except_osv(_('Warning!'),_('You cannot validate leaves for employee %s: too few remaining days (%s).') % (record.employee_id.name, leaves_rest))
return True
# -----------------------------
# OpenChatter and notifications
# -----------------------------
def get_needaction_user_id(self, cr, uid, ids, name, arg, context=None):
result = {}
for obj in self.browse(cr, uid, ids, context=context):
result[obj.id] = False
if (obj.state == 'confirm' and obj.employee_id.parent_id):
result[obj.id] = obj.employee_id.parent_id.user_id.id
return result
def message_get_subscribers(self, cr, uid, ids, context=None):
sub_ids = self._message_get_subscribers_ids(cr, uid, ids, context=context);
# add the employee and its manager if specified to the subscribed users
for obj in self.browse(cr, uid, ids, context=context):
if obj.employee_id.parent_id:
sub_ids.append(obj.employee_id.parent_id.user_id.id)
return self.pool.get('res.users').read(cr, uid, sub_ids, context=context)
def create_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids, context=context):
self.message_append_note(cr, uid, ids, _('System notification'),
_("The %s request has been created and is waiting confirmation")
% ('leave' if obj.type == 'remove' else 'allocation',), type='notification', context=context)
return True
def holidays_confirm_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
self.message_append_note(cr, uid, [obj.id], _('System notification'),
_("The %s request has been confirmed and is waiting for validation by the manager.")
% ('leave' if obj.type == 'remove' else 'allocation',), type='notification')
def holidays_validate_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
if obj.holiday_status_id.double_validation:
self.message_append_note(cr, uid, [obj.id], _('System notification'),
_("The %s request has been validated. A second validation is necessary and is now pending.")
% ('leave' if obj.type == 'remove' else 'allocation',), type='notification', context=context)
else:
self.message_append_note(cr, uid, [obj.id], _('System notification'),
_("The %s request has been validated. The validation process is now over.")
% ('leave' if obj.type == 'remove' else 'allocation',), type='notification', context=context)
def holidays_valid2_notificate(self, cr, uid, ids, context=None):
for obj in self.browse(cr, uid, ids):
self.message_append_note(cr, uid, [obj.id], _('System notification'),
_("The %s request has been double validated. The validation process is now over.")
% ('leave' if obj.type == 'remove' else 'allocation',), type='notification', context=context)
def holidays_refuse_notificate(self, cr, uid, ids, approval, context=None):
for obj in self.browse(cr, uid, ids):
self.message_append_note(cr, uid, [obj.id], _('System notification'),
_("The %s request has been refused. The validation process is now over.")
% ('leave' if obj.type == 'remove' else 'allocation',), type='notification', context=context)
hr_holidays()
class resource_calendar_leaves(osv.osv):

View File

@ -95,6 +95,10 @@
<button string="Approved" name="second_validate" states="validate1" type="workflow" icon="gtk-apply" groups="base.group_hr_user"/>
<button string="Set to Draft" name="set_to_draft" states="refuse,validate" type="object" icon="gtk-convert" groups="base.group_hr_user"/>
</group>
<separator string="Temporary Need Action" colspan="4"/>
<field name="need_action_user_id"/>
<newline/>
<field name="message_ids_social" colspan="4" widget="ThreadView" nolabel="1"/>
</page>
</notebook>
</form>

1119
addons/hr_payroll/i18n/lt.po Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -68,7 +68,7 @@ class hr_recruitment_partner_create(osv.osv_memory):
'email': case.email_from
}, context=context)
case_obj.write(cr, uid, case.id, {
case_obj.write(cr, uid, [case.id], {
'partner_id': partner_id,
'partner_address_id': contact_id
}, context=context)
@ -88,4 +88,4 @@ class hr_recruitment_partner_create(osv.osv_memory):
hr_recruitment_partner_create()
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -21,6 +21,9 @@
import mail_message
import mail_thread
import mail_group
import mail_subscription
import res_users
import res_partner
import wizard

View File

@ -58,7 +58,9 @@ The main features are:
'data': [
"wizard/mail_compose_message_view.xml",
"mail_message_view.xml",
"mail_subscription_view.xml",
"mail_thread_view.xml",
"mail_group_view.xml",
"res_partner_view.xml",
'security/ir.model.access.csv',
'mail_data.xml',
@ -66,6 +68,21 @@ The main features are:
'installable': True,
'auto_install': False,
'certificate': '001056784984222247309',
'images': ['images/customer_history.jpeg','images/messages_form.jpeg','images/messages_list.jpeg'],
'images': [
'images/customer_history.jpeg',
'images/messages_form.jpeg',
'images/messages_list.jpeg',
'static/src/img/email_icong.png',
],
'css': [
'static/src/css/mail.css',
'static/src/css/mail_group.css',
],
'js': [
'static/src/js/mail.js',
],
'qweb': [
'static/src/xml/mail.xml',
],
}
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -0,0 +1,6 @@
:orphan:
Mail module documentation
=========================
.. include:: index.rst.inc

View File

@ -0,0 +1,8 @@
Mail Module
'''''''''''
.. toctree::
:maxdepth: 1
mail_thread

View File

@ -0,0 +1,6 @@
.. _mail_thread:
OpenChatter
===========
TODO

110
addons/mail/mail_group.py Normal file
View File

@ -0,0 +1,110 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010-2011 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 tools
from osv import osv
from osv import fields
from tools.translate import _
import io, StringIO
from PIL import Image
class mail_group(osv.osv):
"""
A mail_group is a collection of users sharing messages in a discussion group.
Mail groups are different from user groups because they don't have a specific field holding users.
Group users are users that follow the mail group, using the subscription/follow mechanism of OpenChatter.
"""
_name = 'mail.group'
_inherit = ['mail.thread']
def action_group_join(self, cr, uid, ids, context={}):
return self.message_subscribe(cr, uid, ids, context=context);
def action_group_leave(self, cr, uid, ids, context={}):
return self.message_unsubscribe(cr, uid, ids, context=context);
def _get_photo_mini(self, cr, uid, ids, name, args, context=None):
result = {}
for obj in self.browse(cr, uid, ids, context=context):
if not obj.photo:
result[obj.id] = False
continue
image_stream = io.BytesIO(obj.photo.decode('base64'))
img = Image.open(image_stream)
img.thumbnail((120, 100), Image.ANTIALIAS)
img_stream = StringIO.StringIO()
img.save(img_stream, "JPEG")
result[obj.id] = img_stream.getvalue().encode('base64')
return result
def _set_photo_mini(self, cr, uid, id, name, value, args, context=None):
self.write(cr, uid, [id], {'photo': value}, context=context)
return True
def is_subscriber(self, cr, uid, ids, name, args, context=None):
result = {}
for id in ids:
result[id] = self.message_is_subscriber(cr, uid, [id], context=context)
return result
def get_messages_nbr(self, cr, uid, ids, name, args, context=None):
result = {}
for id in ids:
result[id] = self.message_get_messages_nbr(cr, uid, [id], context=context)
return result
def get_discussions_nbr(self, cr, uid, ids, name, args, context=None):
result = {}
for id in ids:
result[id] = self.message_get_discussions_nbr(cr, uid, [id], context=context)
return result
def get_members_nbr(self, cr, uid, ids, name, args, context=None):
result = {}
for id in ids:
result[id] = len(self._message_get_subscribers_ids(cr, uid, [id], context=context))
return result
_columns = {
'name': fields.char('Name', size=64, required=True),
'description': fields.text('Description'),
'responsible_id': fields.many2one('res.users', string='Responsible',
ondelete='set null', required=True, select=1),
'public': fields.boolean('Public', help='This group is visible by non members'),
'photo': fields.binary('Photo'),
'photo_mini': fields.function(_get_photo_mini, fnct_inv=_set_photo_mini, string='Photo Mini', type="binary",
store = {
'mail.group': (lambda self, cr, uid, ids, c={}: ids, ['photo'], 10),
}),
'joined': fields.function(is_subscriber, type='boolean', string='Joined'),
'messages_nbr': fields.function(get_messages_nbr, type='integer', string='Messages count'),
'discussions_nbr': fields.function(get_discussions_nbr, type='integer', string='Discussions count'),
'members_nbr': fields.function(get_members_nbr, type='integer', string='Members count'),
}
_defaults = {
'public': True,
}
mail_group()

View File

@ -0,0 +1,117 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- Group Kanban View !-->
<record model="ir.ui.view" id="view_group_kanban">
<field name="name">mail.group.kanban</field>
<field name="model">mail.group</field>
<field name="type">kanban</field>
<field name="priority" eval="10"/>
<field name="arch" type="xml">
<kanban>
<templates>
<t t-name="kanban-description">
<div class="oe_group_description" t-if="record.description.raw_value">
<field name="description"/>
</div>
</t>
<t t-name="kanban-box">
<div t-attf-class="{record.joined.raw_value} oe_group_vignette">
<div class="oe_group_image">
<a type="edit"><img t-att-src="kanban_image('mail.group', 'photo', record.id.value)" class="oe_group_photo" tooltip="kanban-description"/></a>
</div>
<div class="oe_group_details">
<h4><a type="edit"><field name="name"/></a></h4>
<span style="display: none;"><field name="joined"/></span>
<ul>
<li><field name="members_nbr"/> members</li>
<li t-if="record.joined.raw_value"><b>Joined</b></li>
<li t-if="! record.joined.raw_value"><a name="action_group_join" string="Join" type="object">Join</a></li>
<li t-if="record.joined.raw_value"><a name="action_group_leave" string="Join" type="object">Leave</a></li>
<li><field name="messages_nbr"/> messages in <field name="discussions_nbr"/> discussions</li>
</ul>
</div>
</div>
<script>
$('.oe_group_photo').load(function() { if($(this).width() > $(this).height()) { $(this).addClass('oe_group_photo_wide') } });
</script>
</t>
</templates>
</kanban>
</field>
</record>
<!-- Group Form View !-->
<record model="ir.ui.view" id="view_group_form">
<field name="name">mail.group.form</field>
<field name="model">mail.group</field>
<field name="type">form</field>
<field name="priority" eval="10"/>
<field name="arch" type="xml">
<form string="Group">
<group colspan="4" col="8">
<group colspan="6" col="4">
<separator string="General information" colspan="4"/>
<field name="name" colspan="2"/>
<field name="responsible_id" colspan="2"/>
<newline/>
<field name="description" colspan="4"/>
</group>
<group colspan="1" col="2">
<separator string="Group image" colspan="2"/>
<field name="photo_mini" widget='image' nolabel="1"/>
</group>
<group colspan="1" col="2">
<separator string="Privacy settings" colspan="2"/>
<field name="public" nolabel="1"/>
<label string="This group is visible by non members" colspan="2"/>
</group>
</group>
<field name="message_ids_social" colspan="4" widget="ThreadView" nolabel="1"/>
</form>
</field>
</record>
<!-- Group List View !-->
<record model="ir.ui.view" id="view_group_tree">
<field name="name">mail.group.tree</field>
<field name="model">mail.group</field>
<field name="type">tree</field>
<field name="priority" eval="10"/>
<field name="arch" type="xml">
<tree string="Groups">
<field name="name" colspan="2"/>
<field name="responsible_id" colspan="2"/>
</tree>
</field>
</record>
<!-- Group Search View !-->
<record model="ir.ui.view" id="view_group_search">
<field name="name">mail.group.search</field>
<field name="model">mail.group</field>
<field name="type">search</field>
<field name="priority" eval="10"/>
<field name="arch" type="xml">
<search string="Search groups">
<field name="name" colspan="2"/>
<field name="responsible_id" colspan="2"/>
</search>
</field>
</record>
<!-- group record !-->
<record id="action_view_groups" model="ir.actions.act_window">
<field name="name">Groups</field>
<field name="res_model">mail.group</field>
<field name="view_type">form</field>
<field name="view_mode">kanban,tree,form</field>
<field name="search_view_id" ref="view_group_search"/>
</record>
<!-- left-side menu: Groups !-->
<menuitem id="mail_groups" name="Groups" sequence="15" parent="mail_feeds_main"/>
<menuitem id="mail_allgroups" name="All groups" parent="mail_groups" action="action_view_groups"/>
</data>
</openerp>

View File

@ -26,6 +26,7 @@ import email
import logging
import re
import time
import datetime
from email.header import decode_header
from email.message import Message
@ -71,8 +72,8 @@ class mail_message_common(osv.osv_memory):
_rec_name = 'subject'
_columns = {
'subject': fields.char('Subject', size=512, required=True),
'model': fields.char('Related Document model', size=128, select=1, readonly=1),
'res_id': fields.integer('Related Document ID', select=1, readonly=1),
'model': fields.char('Related Document model', size=128, select=1), # was readonly
'res_id': fields.integer('Related Document ID', select=1), # was readonly
'date': fields.datetime('Date'),
'email_from': fields.char('From', size=128, help='Message sender, taken from user preferences. If empty, this is not a mail but a message.'),
'email_to': fields.char('To', size=256, help='Message recipients'),
@ -91,7 +92,8 @@ class mail_message_common(osv.osv_memory):
}
_defaults = {
'subtype': 'plain'
'subtype': 'plain',
'date': (lambda *a: datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')),
}
class mail_message(osv.osv):
@ -107,7 +109,7 @@ class mail_message(osv.osv):
_name = 'mail.message'
_inherit = 'mail.message.common'
_description = 'Email Message'
_description = 'Generic Message (Email, Comment, Notification)'
_order = 'date desc'
# XXX to review - how to determine action to use?
@ -160,7 +162,7 @@ class mail_message(osv.osv):
msg_txt += (message.subject or '')
result[message.id] = msg_txt
return result
_columns = {
'partner_id': fields.many2one('res.partner', 'Related partner'),
'user_id': fields.many2one('res.users', 'Related user', readonly=1),
@ -176,12 +178,36 @@ class mail_message(osv.osv):
], 'State', readonly=True),
'auto_delete': fields.boolean('Auto Delete', help="Permanently delete this email after sending it, to save space"),
'original': fields.binary('Original', help="Original version of the message, as it was sent on the network", readonly=1),
# note feature: add type (email, comment, notification) and need_action
'type': fields.selection([
('email', 'e-mail'),
('comment', 'Comment'),
('notification', 'System notification'),
], 'Type', help="Message type: e-mail for e-mail message, notification for system message, comment for other messages such as user replies"),
'parent_id': fields.many2one('mail.message', 'Parent message', help="Parent message if message belongs to a thread"),
}
_defaults = {
'state': 'received',
'type': 'comment',
}
#------------------------------------------------------
# Generic api
#------------------------------------------------------
def create(self, cr, uid, vals, context=None):
# temporary log directly created messages (to debug OpenSocial)
if not 'mail.thread' in context:
_logger.warning('Creating message without using mail.thread API')
_logger.warning('Message details: %s', str(vals))
msg_id = super(mail_message, self).create(cr, uid, vals, context)
return msg_id
#------------------------------------------------------
# E-Mail api
#------------------------------------------------------
def init(self, cr):
cr.execute("""SELECT indexname FROM pg_indexes WHERE indexname = 'mail_message_model_res_id_idx'""")
if not cr.fetchone():

View File

@ -1,6 +1,61 @@
<?xml version="1.0"?>
<openerp>
<data>
<!-- mail.message tree: short view !-->
<record model="ir.ui.view" id="view_message_tree_short">
<field name="name">mail.message.tree.short</field>
<field name="model">mail.message</field>
<field name="type">tree</field>
<field name="sequence">15</field>
<field name="arch" type="xml">
<tree string="Messages">
<field name="date"/>
<field name="subject"/>
<field name="user_id"/>
<field name="model"/>
<field name="res_id"/>
</tree>
</field>
</record>
<!-- mail.message form: short view !-->
<record model="ir.ui.view" id="view_message_form_short">
<field name="name">mail.message.form.short</field>
<field name="model">mail.message</field>
<field name="type">form</field>
<field name="sequence">15</field>
<field name="arch" type="xml">
<form string="Message">
<group colspan="2" col="2">
<field name="subject"/>
<field name="date"/>
<field name="type"/>
<field name="body_text"/>
</group>
<group colspan="2" col="2">
<field name="user_id" string="User" readonly="0"/>
<field name="model"/>
<field name="res_id"/>
<field name="parent_id"/>
</group>
</form>
</field>
</record>
<!-- mail.message search: short view !-->
<record model="ir.ui.view" id="view_message_search_short">
<field name="name">mail.message.search.short</field>
<field name="model">mail.message</field>
<field name="type">search</field>
<field name="arch" type="xml">
<search string="Messages Search">
<field name="user_id"/>
<field name="model"/>
<field name="date"/>
</search>
</field>
</record>
<record model="ir.ui.view" id="view_email_message_form">
<field name="name">mail.message.form</field>
<field name="model">mail.message</field>
@ -124,12 +179,21 @@
</field>
</record>
<record id="action_view_all_messages_short" model="ir.actions.act_window">
<field name="name">Messages</field>
<field name="res_model">mail.message</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="context">{'tree_view_ref': 'mail.view_message_tree_short', 'form_view_ref': 'mail.view_message_form_short'}</field>
<field name="search_view_id" ref="view_message_search_short"/>
</record>
<record id="action_view_mail_message" model="ir.actions.act_window">
<field name="name">Messages</field>
<field name="res_model">mail.message</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
<field name="domain">['|',('state','in',['outgoing','exception']),('email_from', '!=', False)]</field>
<field name="domain">[('type', '=', 'email'), '|',('state','in',['outgoing','exception']),('email_from', '!=', False)]</field>
<field name="search_view_id" ref="view_email_message_search"/>
</record>
@ -145,5 +209,16 @@
parent="base.menu_email"
action="action_view_mail_message" />
<record id="action_mail_all_feeds" model="ir.actions.client">
<field name="name">(w)All Feeds</field>
<field name="tag">mail.all_feeds</field>
<field name="params" eval="{'search_view_id': ref('view_message_search_short')}"/>
</record>
<record id="action_mail_my_feeds" model="ir.actions.client">
<field name="name">My Feeds</field>
<field name="tag">mail.all_feeds</field>
<field name="params" eval="{'search_view_id': ref('view_message_search_short')}"/>
</record>
</data>
</openerp>

View File

@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2010-2011 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 tools
from osv import osv
from osv import fields
from tools.translate import _
class mail_subscription(osv.osv):
"""
mail_subscription holds the data related to the follow mechanism inside OpenERP.
A subscription can be of following:
- res_model: model of the followed objects
- res_id: ID of resource OR
- res_domain: a domain filtering followed objects - currently removed
"""
_name = 'mail.subscription'
_rec_name = 'id'
_columns = {
'res_model': fields.char('Related Document Model', size=128,
select=1, required=True),
'res_id': fields.integer('Related Document ID', select=1),
#'res_domain': fields.char('res_domain', size=256),
'user_id': fields.many2one('res.users', string='Related User ID',
ondelete='cascade', required=True, select=1),
}
_defaults = {
}
mail_subscription()
class mail_notification(osv.osv):
"""
TODO
"""
_name = 'mail.notification'
_rec_name = 'id'
_log_access = False
_columns = {
'user_id': fields.many2one('res.users', string='User',
ondelete='cascade', required=True, select=1),
'message_id': fields.many2one('mail.message', string='Message',
ondelete='cascade', required=True),
'read': fields.boolean('Read'),
# TODO: add a timestamp ? or use message date ?
}
_defaults = {
'read': False,
}
mail_notification()

View File

@ -0,0 +1,56 @@
<?xml version="1.0"?>
<openerp>
<data>
<!--
SUBSCRIPTION
!-->
<record model="ir.ui.view" id="view_subscription_tree">
<field name="name">mail.subscription.tree</field>
<field name="model">mail.subscription</field>
<field name="type">tree</field>
<field name="sequence">10</field>
<field name="arch" type="xml">
<tree string="Subscription">
<field name="user_id"/>
<field name="res_model"/>
<field name="res_id"/>
</tree>
</field>
</record>
<!--
NOTIFICATION
!-->
<record model="ir.ui.view" id="view_notification_tree">
<field name="name">mail.notification.tree</field>
<field name="model">mail.notification</field>
<field name="type">tree</field>
<field name="sequence">10</field>
<field name="arch" type="xml">
<tree string="Subscription">
<field name="user_id"/>
<field name="message_id"/>
<field name="read"/>
</tree>
</field>
</record>
<record id="action_view_subscriptions" model="ir.actions.act_window">
<field name="name">Subscriptions</field>
<field name="res_model">mail.subscription</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
<record id="action_view_notifications" model="ir.actions.act_window">
<field name="name">Pushed notif</field>
<field name="res_model">mail.notification</field>
<field name="view_type">form</field>
<field name="view_mode">tree,form</field>
</record>
</data>
</openerp>

View File

@ -25,13 +25,14 @@ import base64
import email
from email.utils import parsedate
import re
import logging
import xmlrpclib
from osv import osv, fields
from tools.translate import _
from mail_message import decode, to_email
_logger = logging.getLogger('mail')
_logger = logging.getLogger(__name__)
class mail_thread(osv.osv):
'''Mixin model, meant to be inherited by any model that needs to
@ -53,11 +54,89 @@ class mail_thread(osv.osv):
'''
_name = 'mail.thread'
_description = 'Email Thread'
_inherit = ['res.needaction']
def _get_message_ids(self, cr, uid, ids, name, arg, context=None):
res = {}
for thread in self.browse(cr, uid, ids, context=context):
records = self.message_load(cr, uid, [thread.id], context=context)
res[thread.id] = [obj['id'] for obj in records]
return res
# OpenSocial: removed message_ids and copy method, this will be replaced by message_load
_columns = {
'message_ids': fields.one2many('mail.message', 'res_id', 'Messages', readonly=True),
'message_ids_social': fields.function(_get_message_ids, method=True,
type='one2many', obj='mail.message', string='Temp messages',
),
#widget='mail.ThreadView'),
}
#------------------------------------------------------
# Automatic subscription when creating/reading
#------------------------------------------------------
def create(self, cr, uid, vals, context=None):
thread_id = super(mail_thread, self).create(cr, uid, vals, context=context);
self.message_subscribe(cr, uid, [thread_id], [uid], context=context)
return thread_id;
def write(self, cr, uid, ids, vals, context=None):
write_res = super(mail_thread, self).write(cr, uid, ids, vals, context=context);
if write_res:
self.message_subscribe(cr, uid, ids, [uid], context=context)
return write_res;
#------------------------------------------------------
# Generic message api
#------------------------------------------------------
def message_create(self, cr, uid, thread_id, vals, context=None):
"""OpenSocial: wrapper of mail.message create method
- creates the mail.message
- automatically subscribe the message writer if not already done
- push the message to subscribed users"""
if context is None:
context = {}
subscription_obj = self.pool.get('mail.subscription')
notification_obj = self.pool.get('mail.notification')
need_action_pushed = False
# create message
msg_id = self.pool.get('mail.message').create(cr, uid, vals, context=context)
obj = self.browse(cr, uid, [thread_id], context=context)[0]
# automatically subscribe the writer of the message if not subscribed
if vals['user_id']:
if not self.message_is_subscriber(cr, uid, [thread_id], context=context):
self.message_subscribe(cr, uid, [thread_id], context=context)
# push the message to suscribed users
users = self.message_get_subscribers(cr, uid, [thread_id], context=context)
for user in users:
notification_obj.create(cr, uid, {'user_id': user['id'], 'message_id': msg_id}, context=context)
# push to need_action_user_id
if obj.need_action_user_id == user['id']: need_action_pushed = True
# push to need_action_user_id if user does not follow the object
if obj.need_action_user_id and not need_action_pushed:
notification_obj.create(cr, uid, {'user_id': obj.need_action_user_id.id, 'message_id': msg_id}, context=context)
# parse message to get requested users
user_ids = self.message_parse_users(cr, uid, [msg_id], vals['body_text'], context=context)
for user_id in user_ids:
notification_obj.create(cr, uid, {'user_id': user_id, 'message_id': msg_id}, context=context)
return msg_id
def message_parse_users(self, cr, uid, ids, string, context=None):
'''Parse message content; if find @login -(^|\s)@(\w*)-: returns the related ids'''
regex = re.compile('(^|\s)@(\w*)')
login_lst = [item[1] for item in regex.findall(string)]
if not login_lst: return []
user_ids = self.pool.get('res.users').search(cr, uid, [('login', 'in', login_lst)], context=context)
return user_ids
def message_capable_models(self, cr, uid, context=None):
ret_dict = {}
for model_name in self.pool.obj_list():
@ -66,97 +145,11 @@ class mail_thread(osv.osv):
ret_dict[model_name] = model._description
return ret_dict
def message_thread_followers(self, cr, uid, ids, context=None):
"""Returns a list of email addresses of the people following
this thread, including the sender of each mail, and the
people who were in CC of the messages, if any.
"""
res = {}
if isinstance(ids, (str, int, long)):
ids = [long(ids)]
for thread in self.browse(cr, uid, ids, context=context):
l = set()
for message in thread.message_ids:
l.add((message.user_id and message.user_id.user_email) or '')
l.add(message.email_from or '')
l.add(message.email_cc or '')
res[thread.id] = filter(None, l)
return res
def copy(self, cr, uid, id, default=None, context=None):
"""Overrides default copy method to empty the thread of
messages attached to this record, as the copied object
will have its own thread and does not have to share it.
"""
if default is None:
default = {}
default.update({
'message_ids': [],
})
return super(mail_thread, self).copy(cr, uid, id, default, context=context)
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
"""Called by ``message_process`` when a new message is received
for a given thread model, if the message did not belong to
an existing thread.
The default behavior is to create a new record of the corresponding
model (based on some very basic info extracted from the message),
then attach the message to the newly created record
(by calling ``message_append_dict``).
Additional behavior may be implemented by overriding this method.
:param dict msg_dict: a map containing the email details and
attachments. See ``message_process`` and
``mail.message.parse`` for details.
:param dict custom_values: optional dictionary of additional
field values to pass to create()
when creating the new thread record.
Be careful, these values may override
any other values coming from the message.
:param dict context: if a ``thread_model`` value is present
in the context, its value will be used
to determine the model of the record
to create (instead of the current model).
:rtype: int
:return: the id of the newly created thread object
"""
if context is None:
context = {}
model = context.get('thread_model') or self._name
model_pool = self.pool.get(model)
fields = model_pool.fields_get(cr, uid, context=context)
data = model_pool.default_get(cr, uid, fields, context=context)
if 'name' in fields and not data.get('name'):
data['name'] = msg_dict.get('from','')
if custom_values and isinstance(custom_values, dict):
data.update(custom_values)
res_id = model_pool.create(cr, uid, data, context=context)
self.message_append_dict(cr, uid, [res_id], msg_dict, context=context)
return res_id
def message_update(self, cr, uid, ids, msg_dict, vals={}, default_act=None, context=None):
"""Called by ``message_process`` when a new message is received
for an existing thread. The default behavior is to create a
new mail.message in the given thread (by calling
``message_append_dict``)
Additional behavior may be implemented by overriding this
method.
:param dict msg_dict: a map containing the email details and
attachments. See ``message_process`` and
``mail.message.parse()`` for details.
:param dict context: if a ``thread_model`` value is present
in the context, its value will be used
to determine the model of the thread to
update (instead of the current model).
"""
return self.message_append_dict(cr, uid, ids, msg_dict, context=context)
def message_append(self, cr, uid, threads, subject, body_text=None, email_to=False,
email_from=False, email_cc=None, email_bcc=None, reply_to=None,
email_date=None, message_id=False, references=None,
attachments=None, body_html=None, subtype=None, headers=None,
original=None, context=None):
def message_append(self, cr, uid, threads, subject, parent_id=False, body_text=None, type='email',
email_to=False, email_from=False, email_cc=None, email_bcc=None,
reply_to=None, email_date=None, message_id=False, references=None,
attachments=None, body_html=None, subtype=None, headers=None,
original=None, context=None):
"""Creates a new mail.message attached to the current mail.thread,
containing all the details passed as parameters. All attachments
will be attached to the thread record as well as to the actual
@ -189,7 +182,7 @@ class mail_thread(osv.osv):
to determine the model of the thread to
update (instead of the current model).
"""
if context is None:
if context is None:
context = {}
if attachments is None:
attachments = {}
@ -229,14 +222,16 @@ class mail_thread(osv.osv):
data = {
'subject': subject,
'user_id': uid,
'parent_id': parent_id,
'model' : thread._name,
'partner_id': partner_id,
'res_id': thread.id,
'date': time.strftime('%Y-%m-%d %H:%M:%S'),
'message_id': message_id,
'body_text': body_text or (hasattr(thread, 'description') and thread.description or False),
'body_text': body_text or (hasattr(thread, 'description') and thread.description or ''),
'attachment_ids': [(6, 0, to_attach)],
'state' : 'received',
'state': 'received',
'type': type,
}
if email_from:
@ -266,7 +261,9 @@ class mail_thread(osv.osv):
'reply_to': reply_to,
'original': original,
}
mail_message.create(cr, uid, data, context=context)
#mail_message.create(cr, uid, data, context=context)
context['mail.thread'] = True
self.message_create(cr, uid, thread.id, data, context=context)
return True
def message_append_dict(self, cr, uid, ids, msg_dict, context=None):
@ -285,9 +282,13 @@ class mail_thread(osv.osv):
to determine the model of the thread to
update (instead of the current model).
"""
# 6.2 OpenSocial feature: add default email type for old API
if not 'type' in msg_dict: msg_dict['type'] = 'email'
return self.message_append(cr, uid, ids,
subject = msg_dict.get('subject'),
parent_id = msg_dict.get('parent_id', False),
body_text = msg_dict.get('body_text'),
type = msg_dict.get('type'),
email_to = msg_dict.get('to'),
email_from = msg_dict.get('from'),
email_cc = msg_dict.get('cc'),
@ -304,6 +305,83 @@ class mail_thread(osv.osv):
original = msg_dict.get('original'),
context = context)
# Message loading
def _message_get_parent_ids(self, cr, uid, ids, child_ids, root_ids, context=None):
if context is None: context = {}
msg_obj = self.pool.get('mail.message')
msgs_tmp = msg_obj.read(cr, uid, child_ids, context=context)
parent_ids = [msg['parent_id'][0] for msg in msgs_tmp if msg['parent_id'] not in root_ids and msg['parent_id'][0] not in child_ids]
child_ids += parent_ids
cur_iter = 0; max_iter = 10;
while (parent_ids and (cur_iter < max_iter)):
cur_iter += 1
msgs_tmp = msg_obj.read(cr, uid, parent_ids, context=context)
parent_ids = [msg['parent_id'][0] for msg in msgs_tmp if msg['parent_id'] not in root_ids and msg['parent_id'][0] not in child_ids]
child_ids += parent_ids
return child_ids
def message_load_ids(self, cr, uid, ids, limit=100, offset=0, domain=[], ascent=False, root_ids=[False], context=None):
""" OpenSocial feature: return thread messages ids (for web compatibility)
loading messages: search in mail.messages where res_id = ids, (res_)model = current model
see get_pushed_messages for parameters explanation
"""
if context is None: context = {}
msg_obj = self.pool.get('mail.message')
msg_ids = msg_obj.search(cr, uid, ['&', ('res_id', 'in', ids), ('model', '=', self._name)] + domain,
limit=limit, offset=offset, context=context)
if (ascent): msg_ids = self._message_get_parent_ids(cr, uid, ids, msg_ids, root_ids, context=context)
return msg_ids
def message_load(self, cr, uid, ids, limit=100, offset=0, domain=[], ascent=False, root_ids=[False], context=None):
""" OpenSocial feature: return thread messages
loading messages: search in mail.messages where res_id = ids, (res_)model = current model
see get_pushed_messages for parameters explanation
"""
msg_ids = self.message_load_ids(cr, uid, ids, limit, offset, domain, ascent, root_ids, context=context)
return self.pool.get('mail.message').read(cr, uid, msg_ids, context=context)
def get_pushed_messages(self, cr, uid, ids, limit=100, offset=0, domain=[], ascent=False, root_ids=[False], context=None):
"""OpenSocial: wall: get messages to display (=pushed notifications)
:param domain: domain to add to the search; especially child_of is interesting when dealing with threaded display
:param deep: performs an ascended search; will add to fetched msgs all their parents until root_ids
WARNING: must be used in combinaison with a child_of domain
EXAMPLE: domain = ['id', 'child_of', [32, 33]], root_ids=[32,33]
:param root_ids: root_ids when performing an ascended search
:return: list of mail.messages sorted by date
"""
if context is None: context = {}
notification_obj = self.pool.get('mail.notification')
msg_obj = self.pool.get('mail.message')
# get user notifications
notification_ids = notification_obj.search(cr, uid, [('user_id', '=', uid)], context=context)
notifications = notification_obj.browse(cr, uid, notification_ids, context=context)
msg_ids = [notification.message_id.id for notification in notifications]
# search messages: ids in notifications, add domain coming from wall search view
search_domain = [('id', 'in', msg_ids)] + domain
msg_ids = msg_obj.search(cr, uid, search_domain, limit=limit, offset=offset, context=context)
if (ascent): msg_ids = self._message_get_parent_ids(cr, uid, ids, msg_ids, root_ids, context=context)
msgs = msg_obj.read(cr, uid, msg_ids, context=context)
return msgs
# Message tools
def message_get_discussions_nbr(self, cr, uid, ids, context=None):
count = 0
message_obj = self.pool.get('mail.message')
for id in ids:
count += message_obj.search(cr, uid, [('model', '=', self._name), ('res_id', '=', id)], count=True) # TODO: add parent_id when merging branch
return count
def message_get_messages_nbr(self, cr, uid, ids, context=None):
count = 0
message_obj = self.pool.get('mail.message')
for id in ids:
count += message_obj.search(cr, uid, [('model', '=', self._name), ('res_id', '=', id)], count=True)
return count
#------------------------------------------------------
# Email specific
#------------------------------------------------------
# message_process will call either message_new or message_update.
def message_process(self, cr, uid, model, message, custom_values=None,
save_original=False, strip_attachments=False,
@ -389,8 +467,79 @@ class mail_thread(osv.osv):
self.message_forward(cr, uid, model, [res_id], msg_txt, context=context)
return res_id
# for backwards-compatibility with old scripts
process_email = message_process
def message_new(self, cr, uid, msg_dict, custom_values=None, context=None):
"""Called by ``message_process`` when a new message is received
for a given thread model, if the message did not belong to
an existing thread.
The default behavior is to create a new record of the corresponding
model (based on some very basic info extracted from the message),
then attach the message to the newly created record
(by calling ``message_append_dict``).
Additional behavior may be implemented by overriding this method.
:param dict msg_dict: a map containing the email details and
attachments. See ``message_process`` and
``mail.message.parse`` for details.
:param dict custom_values: optional dictionary of additional
field values to pass to create()
when creating the new thread record.
Be careful, these values may override
any other values coming from the message.
:param dict context: if a ``thread_model`` value is present
in the context, its value will be used
to determine the model of the record
to create (instead of the current model).
:rtype: int
:return: the id of the newly created thread object
"""
if context is None:
context = {}
model = context.get('thread_model') or self._name
model_pool = self.pool.get(model)
fields = model_pool.fields_get(cr, uid, context=context)
data = model_pool.default_get(cr, uid, fields, context=context)
if 'name' in fields and not data.get('name'):
data['name'] = msg_dict.get('from','')
if custom_values and isinstance(custom_values, dict):
data.update(custom_values)
res_id = model_pool.create(cr, uid, data, context=context)
self.message_append_dict(cr, uid, [res_id], msg_dict, context=context)
return res_id
def message_update(self, cr, uid, ids, msg_dict, vals={}, default_act=None, context=None):
"""Called by ``message_process`` when a new message is received
for an existing thread. The default behavior is to create a
new mail.message in the given thread (by calling
``message_append_dict``)
Additional behavior may be implemented by overriding this
method.
:param dict msg_dict: a map containing the email details and
attachments. See ``message_process`` and
``mail.message.parse()`` for details.
:param dict context: if a ``thread_model`` value is present
in the context, its value will be used
to determine the model of the thread to
update (instead of the current model).
"""
return self.message_append_dict(cr, uid, ids, msg_dict, context=context)
def message_thread_followers(self, cr, uid, ids, context=None):
"""Returns a list of email addresses of the people following
this thread, including the sender of each mail, and the
people who were in CC of the messages, if any.
"""
res = {}
if isinstance(ids, (str, int, long)):
ids = [long(ids)]
for thread in self.browse(cr, uid, ids, context=context):
l = set()
for message in thread.message_ids:
l.add((message.user_id and message.user_id.user_email) or '')
l.add(message.email_from or '')
l.add(message.email_cc or '')
res[thread.id] = filter(None, l)
return res
def message_forward(self, cr, uid, model, thread_ids, msg, email_error=False, context=None):
"""Sends an email to all people following the given threads.
@ -469,4 +618,68 @@ class mail_thread(osv.osv):
res['partner_id'] = address.partner_id.id
return res
# for backwards-compatibility with old scripts
process_email = message_process
#------------------------------------------------------
# Note specific
#------------------------------------------------------
def message_append_note(self, cr, uid, ids, subject, body, parent_id=False, type='notification', context=None):
return self.message_append(cr, uid, ids, subject, body_text=body, parent_id=parent_id, type=type, context=context)
# old log overrided method: now calls message_append_note
def log(self, cr, uid, id, message, secondary=False, context=None):
""" OpenSocial add: new res_log implementation
A res.log is now a mail.message, as all messages in OpenERP
It has a notification type.
It can have a need_action flag attached if an user
has to perform a given action.
See mail.message, mail.subscription and mail.notification for more details.
"""
if context and context.get('disable_log'):
#return True # old behavior
print 'Log diabled, but we do not care currently about that. We want you to have our logs !'
#return self.message_append_note(cr, uid, [id], 'System notification', message, context=context)
#------------------------------------------------------
# Subscription mechanism
#------------------------------------------------------
def _message_get_subscribers_ids(self, cr, uid, ids, context=None):
subscription_obj = self.pool.get('mail.subscription')
sub_ids = subscription_obj.search(cr, uid, ['&', ('res_model', '=', self._name), ('res_id', 'in', ids)], context=context)
subs = subscription_obj.read(cr, uid, sub_ids, context=context)
return [sub['user_id'][0] for sub in subs]
def message_get_subscribers(self, cr, uid, ids, context=None):
user_ids = self._message_get_subscribers_ids(cr, uid, ids, context=context)
users = self.pool.get('res.users').read(cr, uid, user_ids, fields=['id', 'name', 'avatar_mini'], context=context)
return users
def message_is_subscriber(self, cr, uid, ids, user_id = None, context=None):
users = self.message_get_subscribers(cr, uid, ids, context=context)
sub_user_id = uid if user_id is None else user_id
if sub_user_id in [user['id'] for user in users]:
return True
return False
def message_subscribe(self, cr, uid, ids, user_ids = None, context=None):
subscription_obj = self.pool.get('mail.subscription')
to_subscribe_uids = [uid] if user_ids is None else user_ids
create_ids = []
for id in ids:
for user_id in to_subscribe_uids:
if self.message_is_subscriber(cr, uid, [id], user_id=user_id, context=context): continue
create_ids.append(subscription_obj.create(cr, uid, {'res_model': self._name, 'res_id': id, 'user_id': user_id}, context=context))
return create_ids
def message_unsubscribe(self, cr, uid, ids, user_ids = None, context=None):
subscription_obj = self.pool.get('mail.subscription')
to_unsubscribe_uids = [uid] if user_ids is None else user_ids
to_delete_sub_ids = subscription_obj.search(cr, uid,
['&', '&', ('res_model', '=', self._name), ('res_id', 'in', ids), ('user_id', 'in', to_unsubscribe_uids)], context=context)
subscription_obj.unlink(cr, uid, to_delete_sub_ids, context=context)
return True
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:

View File

@ -1,53 +1,32 @@
<?xml version="1.0"?>
<openerp>
<data>
<record model="ir.ui.view" id="view_mailgate_thread_form">
<field name="name">mail.thread.form</field>
<field name="model">mail.thread</field>
<field name="type">form</field>
<field name="arch" type="xml">
<form string="Email Thread">
<separator string="Communication History" colspan="4"/>
<field name="message_ids" nolabel="1" colspan="4" mode="tree">
<tree string="Communication History">
<field name="display_text"/>
</tree>
</field>
</form>
</field>
<!-- toplevel menu -->
<record id="mail_feeds_main" model="ir.ui.menu">
<field name="name">Feeds</field>
<field name="action" ref="action_mail_all_feeds"/>
</record>
<record model="ir.ui.view" id="view_mailgate_thread_tree">
<field name="name">mail.thread.tree</field>
<field name="model">mail.thread</field>
<field name="type">tree</field>
<field name="arch" type="xml">
<tree string="Email Threads">
<field name="message_ids" />
</tree>
</field>
<!-- left-side menu: Feeds !-->
<menuitem id="mail_feeds" name="Feeds" parent="mail_feeds_main"/>
<record id="mail_myfeeds" model="ir.ui.menu">
<field name="name">My Feeds</field>
<field name="sequence" eval="10"/>
<field name="action" ref="action_mail_my_feeds"/>
<field name="parent_id" ref="mail_feeds"/>
</record>
<!-- Emails thread action -->
<record model="ir.actions.act_window" id="action_view_mailgate_thread">
<field name="name">Email Threads</field>
<field name="res_model">mail.thread</field>
<field name="view_mode">tree,form</field>
<field name="view_type">form</field>
<field name="view_id" ref="view_mailgate_thread_tree"/>
<record id="mail_wallfeeds" model="ir.ui.menu">
<field name="name">(w)All Feeds</field>
<field name="sequence" eval="20"/>
<field name="action" ref="action_mail_all_feeds"/>
<field name="parent_id" ref="mail_feeds"/>
</record>
<record model="ir.actions.act_window.view" id="action_view_mailgate_thread_view1">
<field name="sequence" eval="1"/>
<field name="view_mode">tree</field>
<field name="view_id" ref="view_mailgate_thread_tree"/>
<field name="act_window_id" ref="action_view_mailgate_thread"/>
</record>
<record model="ir.actions.act_window.view" id="action_view_mailgate_thread_view2">
<field name="sequence" eval="2"/>
<field name="view_mode">form</field>
<field name="view_id" ref="view_mailgate_thread_form"/>
<field name="act_window_id" ref="action_view_mailgate_thread"/>
</record>
<!-- left-side menu: Tmp !-->
<menuitem id="mail_debug" name="Debug/Tmp" sequence="20" parent="mail_feeds_main"/>
<menuitem id="mail_debug_msgs" name="Messages" parent="mail_debug" action="action_view_all_messages_short"/>
<menuitem id="mail_debug_subs" name="Subscriptions" parent="mail_debug" action="action_view_subscriptions"/>
<menuitem id="mail_debug_notifs" name="Pushed notif" parent="mail_debug" action="action_view_notifications"/>
</data>
</openerp>

33
addons/mail/res_users.py Normal file
View File

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
##############################################################################
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2009-Today OpenERP SA (<http://www.openerp.com>)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>
#
##############################################################################
from osv import osv
class res_users(osv.osv):
_name = 'res.users'
_inherit = ['res.users', 'mail.thread']
def create(self, cr, uid, data, context=None):
user_id = super(res_users, self).create(cr, uid, data, context=context)
# make user follow itself
self.message_subscribe(cr, uid, [user_id], [user_id], context=context)
return user_id

View File

@ -1,3 +1,5 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_mail_message,mail.message,model_mail_message,,1,0,1,0
access_mail_thread,mail.thread,model_mail_thread,,1,0,1,0
access_mail_subscription,mail.subscription,model_mail_subscription,,1,0,1,0
access_mail_notification,mail.notification,model_mail_notification,,1,0,1,0

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_mail_message mail.message model_mail_message 1 0 1 0
3 access_mail_thread mail.thread model_mail_thread 1 0 1 0
4 access_mail_subscription mail.subscription model_mail_subscription 1 0 1 0
5 access_mail_notification mail.notification model_mail_notification 1 0 1 0

View File

@ -0,0 +1,239 @@
/* ------------------------------ */
/* Wall */
/* ------------------------------ */
.oe_mail_wall {
overflow: auto;
padding: 5px;
}
.oe_mail_wall_search {
width: 55%;
}
/* 2 columns view */
.oe_mail_wall_left {
float: left;
width: 65%;
}
.oe_mail_wall_right {
float: right;
width: 34%;
}
.oe_mail_wall_thread div.oe_mail_thread_act .oe_mail_action_textarea {
height: 20px;
padding: 2px;
}
.oe_mail_thread_subthread div.oe_mail_thread_act .oe_mail_msg_image, .oe_mail_thread_subthread div.oe_mail_thread_display .oe_mail_msg_image {
width: 30px;
height: 30px;
}
.oe_mail_thread_subthread .oe_mail_msg_content, .oe_mail_thread_subthread .oe_mail_msg_content {
margin-left: 40px;
}
.oe_mail_wall_more {
text-align: center;
}
/* ------------------------------ */
/* RecordThread */
/* ------------------------------ */
.oe_mail_recthread {
overflow: auto;
padding: 5px 5px 5px 5px;
}
/* Left-side CSS */
.oe_mail_actions {
margin-bottom: 10px;
}
.oe_mail_followers {
margin-bottom: 10px;
}
.oe_mail_followers_action, .oe_mail_followers_display {
}
/* RecordThread: 2 columns view */
.oe_mail_recthread_left {
float: left;
width: 55%;
}
.oe_mail_recthread_right {
float: right;
width: 25%;
margin-right: 5%;
}
.oe_mail_button_follow, .oe_mail_button_unfollow {
width: 120px;
}
.oe_mail_button_followers {
display: inline;
width: 120px;
}
.oe_mail_followers h4 {
margin: 0 0 8px;
}
/* ------------------------------ */
/* ThreadDisplay */
/* ------------------------------ */
div.oe_mail_thread_act {
white-space: normal;
margin-bottom: 5px;
}
div.oe_mail_thread_display {
white-space: normal;
margin-bottom: 5px;
}
div.oe_mail_thread_subthread {
margin-top: 8px;
padding-left: 5%;
}
div.oe_mail_thread_more {
border-bottom: 1px solid #D2D9E7;
}
div.oe_mail_thread_msg {
padding: 4px;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-o-border-radius: 4px;
-ms-border-radius: 4px;
border-radius: 4px;
}
div.notification {
background: #E0E0E0;
}
div.email {
background: #E0E0E0;
}
div.oe_mail_thread_msg:after, div.oe_mail_thread_act:after {
content: "";
display: block;
clear: both;
}
.oe_mail_msg_content {
margin-left: 60px;
}
.oe_mail_action_textarea {
height: 50px;
padding: 5px;
}
.oe_mail_msg_image {
margin-right: 8px;
width: 45px;
height: 45px;
text-align: center;
overflow: hidden;
-moz-border-radius: 4px;
-webkit-border-radius: 4px;
-o-border-radius: 4px;
-ms-border-radius: 4px;
border-radius: 4px;
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
clip: rect(5px, 40px, 45px, 0px);
}
.oe_mail_msg_p {
padding: 0;
margin: 0;
}
.oe_mail_msg_p_email_header {
border-bottom: 1px solid #D2D9E7;
}
.oe_mail_msg_body a.reduce, .oe_mail_msg_body_short a.expand {
color: #4E43E7;
}
/* ------------------------------ */
/* Styling (should be openerp) */
/* ------------------------------ */
input.oe_mail, textarea.oe_mail {
width: 100%;
padding: 4px;
font-size: 12px;
border: 1px solid #cccccc;
-webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-transition: border linear 0.2s, box-shadow linear 0.2s;
-ms-transition: border linear 0.2s, box-shadow linear 0.2s;
-o-transition: border linear 0.2s, box-shadow linear 0.2s;
transition: border linear 0.2s, box-shadow linear 0.2s;
-moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
border-radius: 3px;
}
input.oe_mail:focus, textarea.oe_mail:focus {
outline: 0;
border-color: rgba(82, 168, 236, 0.8);
-moz-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
-box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6);
}
p.oe_mail_msg {
padding: 0;
margin: 0;
}
.oe_mail_oe_right {
float: right;
margin-right: 8px;
}
.oe_mail_oe_left {
float: left;
margin-right: 8px;
}
.oe_mail_oe_fade {
font-size: 12px;
color: #888888;
}
.oe_mail_oe_bold {
font-weight: bold;
}
a.oe_mail_oe_intlink {
color: #8786b7;
}
.oe_mail_oe_warning, .oe_mail_oe_warning a {
color: #C03000;
}
.oe_mail_oe_space {
margin-left: 15px;
}

View File

@ -0,0 +1,74 @@
.oe_group_vignette {
padding: 8px 0;
min-height: 100px;
}
.oe_group_image, .oe_group_details {
display: inline-block;
vertical-align: top;
}
.oe_group_image {
width: 100px;
height: 100px;
text-align: center;
overflow: hidden;
-moz-border-radius: 3px;
-webkit-border-radius: 3px;
-o-border-radius: 3px;
-ms-border-radius: 3px;
border-radius: 3px;
-moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-o-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.4);
}
.oe_group_photo {
width: 100px;
height: auto;
clip: rect(10px, 100px, 110px, 0px);
}
.oe_group_photo_wide {
height: 100px;
width: auto;
clip: rect(0px, 115px, 100px, 15px);
}
.oe_group_details {
width: 220px;
font-size: 13px;
padding: 2px 5px;
color: #4c4c4c;
min-height: 120px;
}
.oe_group_details h4 {
margin: 0;
font-size: 13px;
}
.oe_group_details h4 a {
color: #4c4c4c;
}
.oe_group_details h4 a:hover {
text-decoration: underline;
}
.oe_group_details ul {
margin: 3px 0 5px;
padding: 0;
list-style: none;
}
.oe_group_details li {
margin: 2px 0;
}
.oe_group_description {
}
.oe_kanban_record div.true {
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1,701 @@
openerp.mail = function(session) {
var mail = session.mail = {};
/* Add ThreadDisplay widget to registry */
session.web.form.widgets.add(
'Thread', 'openerp.mail.Thread');
session.web.page.readonly.add(
'Thread', 'openerp.mail.Thread');
/**
* ThreadDisplay widget: this widget handles the display of a thread of messages.
* Two displays are managed through the [thread_level] parameter that sets
* the level number in the thread:
* - root message
* - - sub message (parent_id = root message)
* - - - sub sub message (parent id = sub message)
* - - sub message (parent_id = root message)
* This widget has 2 ways of initialization, either you give records to be rendered,
* either it will fetch [limit] messages related to [res_model]:[res_id].
*/
mail.Thread = session.web.Widget.extend({
template: 'Thread',
/**
* @param {Object} parent parent
* @param {Object} [params]
* @param {String} [params.res_model] res_model of mail.thread object
* @param {Number} [params.res_id] res_id of record
* @param {Number} [params.parent_id=false] parent_id of message
* @param {Number} [params.uid] user id
* @param {Number} [params.thread_level=0] number of levels in the thread (only 0 or 1 currently)
* @param {Number} [params.msg_more_limit=100] number of character to display before having a "show more" link;
* note that the text will not be truncated if it does not have 110% of
* the parameter (ex: 110 characters needed to be truncated and be displayed
* as a 100-characters message)
* @param {Number} [params.limit=10] maximum number of messages to fetch
* @param {Number} [params.offset=0] offset for fetching messages
* @param {Number} [params.records=null] records to show instead of fetching messages
*/
init: function(parent, params) {
this._super(parent);
this.params = params;
this.params.parent_id = this.params.parent_id || false;
this.params.thread_level = this.params.thread_level || 0;
this.params.msg_more_limit = this.params.msg_more_limit || 100;
this.params.limit = this.params.limit || 2;
this.params.offset = this.params.offset || 0;
this.params.records = this.params.records || null;
/* DataSets and internal vars */
this.ds = new session.web.DataSet(this, this.params.res_model);
this.ds_users = new session.web.DataSet(this, 'res.users');
this.name = 'Unknown record'
this.sorted_comments = {'root_ids': [], 'root_id_msg_list': {}};
/* Display vars */
this.display = {};
this.display.show_post_comment = false;
this.display.show_reply = (this.params.thread_level > 0);
this.display.show_delete = true;
this.display.show_hide = true;
this.display.show_more = (this.params.thread_level == 0);
// not used currently
this.intlinks_mapping = {};
},
start: function() {
var self = this;
this._super.apply(this, arguments);
/* display customization and events */
this.$element.find('p.oe_mail_p_nomore').hide();
if (! this.display.show_post_comment) this.$element.find('div.oe_mail_thread_act').hide();
if (! this.display.show_more) this.$element.find('div.oe_mail_thread_more').hide();
this.$element.find('button.oe_mail_button_more').bind('click', function () { self.do_more(); });
this.$element.find('textarea.oe_mail_action_textarea').bind('keyup', function (event) {
var charCode = (event.which) ? event.which : window.event.keyCode;
if (event.shiftKey && charCode == 13) { this.value = this.value+"\n"; }
else if (charCode == 13) { self.do_comment(); }
});
this.$element.find('div.oe_mail_thread_display').delegate('a.oe_mail_msg_reply', 'click', function (event) {
var act_dom = $(this).parents('div.oe_mail_thread_display').find('div.oe_mail_thread_act:first');
act_dom.toggle();
});
this.$element.find('div.oe_mail_thread_display').delegate('a.intlink', 'click', function (event) {
// lazy implementation: fetch data and try to redirect
if (! event.srcElement.dataset.resModel) return false;
else var res_model = event.srcElement.dataset.resModel;
var res_login = event.srcElement.dataset.resLogin;
var res_id = event.srcElement.dataset.resId;
if ((! res_login) && (! res_id)) return false;
if (! res_id) {
var ds = new session.web.DataSet(self, res_model);
var defer = ds.call('search', [[['login', '=', res_login]]]).then(function (records) {
if (records[0]) {
self.do_action({ type: 'ir.actions.act_window', res_model: res_model, res_id: parseInt(records[0]), views: [[false, 'form']]});
}
else return false;
});
}
else self.do_action({ type: 'ir.actions.act_window', res_model: res_model, res_id: parseInt(res_id), views: [[false, 'form']]});
});
/* get record name */
var name_get_done = self.ds.name_get([this.params.res_id]).then(function (records) { self.name = records[0][1]; });
var display_done = $.when(name_get_done).then(function () {
/* display user, fetch comments */
self.display_current_user();
if (self.params.records) return self.display_comments(self.params.records);
else return self.init_comments();
});
return display_done
},
stop: function () {
//this._super.apply(this, arguments);
},
init_comments: function() {
var self = this;
this.params.offset = 0;
this.sorted_comments = {'root_ids': [], 'root_id_msg_list': {}};
this.$element.find('div.oe_mail_thread_display').empty();
domain = this.get_fetch_domain(this.sorted_comments);
return this.fetch_comments(this.params.limit, this.params.offset, domain).then();
},
fetch_comments: function (limit, offset, domain) {
console.log('fetch comments');
var self = this;
var defer = this.ds.call('message_load', [[this.params.res_id], (limit||this.params.limit), (offset||this.params.offset), (domain||[]), (this.params.thread_level > 0), (this.sorted_comments['root_ids'])]);
$.when(defer).then(function (records) {
if (records.length < self.params.limit) self.display.show_more = false;
self.display_comments(records);
if (self.display.show_more == false) {
self.$element.find('button.oe_mail_button_more:last').hide();
self.$element.find('p.oe_mail_p_nomore:last').show(); }
});
return defer;
},
display_comments: function (records) {
var self = this;
if (this.params.thread_level > 0) {
this.sc = this.sort_comments(records);
}
/* WIP: map matched regexp -> records to browse with name */
//_(records).each(function (record) {
//self.do_check_internal_links(record.body_text);
//});
_(records).each(function (record) {
var sub_msgs = [];
if ((record.parent_id == false || record.parent_id[0] == self.params.parent_id) && self.params.thread_level > 0 ) {
var sub_list = self.sc['root_id_msg_list'][record.id];
_(records).each(function (record) {
if (record.parent_id == false || record.parent_id[0] == self.params.parent_id) return;
if (_.indexOf(_.pluck(sub_list, 'id'), record.id) != -1) {
sub_msgs.push(record);
}
});
self.display_comment(record);
self.thread = new mail.Thread(self, {'res_model': self.params.res_model, 'res_id': self.params.res_id, 'uid': self.params.uid,
'records': sub_msgs, 'thread_level': (self.params.thread_level-1), 'parent_id': record.id});
self.$element.find('div.oe_mail_thread_display:last').append('<div class="oe_mail_thread_subthread"/>');
self.thread.appendTo(self.$element.find('div.oe_mail_thread_subthread:last'));
}
else if (self.params.thread_level == 0) {
self.display_comment(record);
}
});
// update offset for "More" buttons
if (this.params.thread_level == 0) this.params.offset += records.length;
},
/**
* Display a record
*/
display_comment: function (record) {
if (record.type == 'email') { record.mini_url = ('/mail/static/src/img/email_icon.png'); }
else { record.mini_url = this.thread_get_avatar_mini('res.users', 'avatar_mini', record.user_id[0]); }
// body text manipulation
record.body_text = this.do_clean_text(record.body_text);
record.tr_body_text = this.do_truncate_string(record.body_text, this.params.msg_more_limit);
record.body_text = this.do_replace_internal_links(record.body_text);
if (record.tr_body_text) record.tr_body_text = this.do_replace_internal_links(record.tr_body_text);
// render
$(session.web.qweb.render('ThreadMsg', {'record': record, 'res_model': this.params.res_model, 'res_id': this.params.res_id, 'name': this.name,
'display': this.display})).appendTo(this.$element.children('div.oe_mail_thread_display:first'));
// truncated: hide full-text, show summary, add buttons
if (record.tr_body_text) {
var node_body = this.$element.find('span.oe_mail_msg_body:last').append(' <a href="#" class="reduce">[ ... Show less]</a>');
var node_body_short = this.$element.find('span.oe_mail_msg_body_short:last').append(' <a href="#" class="expand">[ ... Show more]</a>');
node_body.hide();
node_body.find('a:last').click(function() { node_body.hide(); node_body_short.show(); return false; });
node_body_short.find('a:last').click(function() { node_body_short.hide(); node_body.show(); return false; });
}
},
/**
* Add records to sorted_comments array
* @param {Array} records records from mail.message sorted by date desc
* @returns {Object} sc sorted_comments: dict {
* 'root_id_list': list or root_ids
* 'root_id_msg_list': {'record_id': [ancestor_ids]}, still sorted by date desc
* 'id_to_root': {'root_id': [records]}, still sorted by date desc
* }
*/
sort_comments: function (records) {
var self = this;
sc = {'root_id_list': [], 'root_id_msg_list': {}, 'id_to_root': {}}
var cur_iter = 0; var max_iter = 10; var modif = true;
/* step1: get roots */
while ( modif && (cur_iter++) < max_iter) {
modif = false;
_(records).each(function (record) {
if ( (record.parent_id == false || record.parent_id[0] == self.params.parent_id) && (_.indexOf(sc['root_id_list'], record.id) == -1)) {
sc['root_id_list'].push(record.id);
sc['root_id_msg_list'][record.id] = [];
self.sorted_comments['root_ids'].push(record.id);
modif = true;
}
else {
if (_.indexOf(sc['root_id_list'], record.parent_id[0]) != -1) {
sc['id_to_root'][record.id] = record.parent_id[0];
modif = true;
}
else if ( sc['id_to_root'][record.parent_id[0]] ) {
sc['id_to_root'][record.id] = sc['id_to_root'][record.parent_id[0]];
modif = true;
}
}
});
}
/* step2: add records */
_(records).each(function (record) {
var root_id = sc['id_to_root'][record.id];
if (! root_id) return;
sc['root_id_msg_list'][root_id].push(record);
//self.sorted_comments['root_id_msg_list'][root_id].push(record.id);
});
return sc;
},
display_current_user: function () {
return this.$element.find('img.oe_mail_msg_image').attr('src', this.thread_get_avatar_mini('res.users', 'avatar_mini', this.params.uid));
},
do_comment: function () {
var comment_node = this.$element.find('textarea');
var body_text = comment_node.val();
comment_node.val('');
return this.ds.call('message_append_note', [[this.params.res_id], 'Reply comment', body_text, this.params.parent_id, 'comment']).then(
this.proxy('init_comments'));
},
/**
* Create a domain to fetch new comments according to
* comment already present in sorted_comments
* @param {Object} sorted_comments (see sort_comments)
* @returns {Array} fetch_domain (OpenERP domain style)
*/
get_fetch_domain: function (sorted_comments) {
var domain = [];
var ids = [];
var dis2 = [];
if (this.params.parent_id) {
domain.push(['id', 'child_of', this.params.parent_id]);
}
_(sorted_comments.root_ids).each(function (id) { // each record
ids.push(id);
dis2.push(id);
});
if (ids.length > 0) {
domain.push('&');
domain.push('!');
domain.push(['id', 'child_of', ids]);
}
if (this.params.parent_id != false) {
dis2.push(this.params.parent_id);
}
if (dis2.length > 0) {
domain.push(['id', 'not in', dis2]);
}
return domain;
},
do_more: function () {
domain = this.get_fetch_domain(this.sorted_comments);
console.log(domain);
console.log(this.params.limit + '-' + this.params.offset + '-' + this.params.parent_id + '-' + this.params.res_id);
//return true;
return this.fetch_comments(this.params.limit, this.params.offset, domain);
},
do_replace_internal_links: function (string) {
var self = this;
/* shortcut to user: @login */
var regex_login = new RegExp(/(^|\s)@(\w*)/g);
var regex_res = regex_login.exec(string);
while (regex_res != null) {
var login = regex_res[2];
string = string.replace(regex_res[0], '<a href="#" class="intlink oe_mail_oe_intlink" data-res-model="res.users" data-res-login = ' + login + '>@' + login + '</a>');
regex_res = regex_login.exec(string);
}
return string;
},
thread_get_avatar_mini: function(model, field, id) {
return this.session.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || '');
},
do_truncate_string: function(string, max_length) {
if (string.length <= (max_length * 1.2)) return false;
else return string.slice(0, max_length);
},
do_clean_text: function (string) {
var html = $('<div/>').text(string.replace(/\s+/g, ' ')).html().replace(new RegExp('&lt;(/)?(b|em)\\s*&gt;', 'gi'), '<$1$2>');
return html;
},
/**
*
* var regex_login = new RegExp(/(^|\s)@(\w*[a-zA-Z_.]+\w*\s)/g);
* var regex_login = new RegExp(/(^|\s)@(\w*)/g);
* var regex_intlink = new RegExp(/(^|\s)#(\w*[a-zA-Z_]+\w*)\.(\w+[a-zA-Z_]+\w*),(\w+)/g);
*/
do_check_internal_links: function(string) {
/* shortcut to user: @login */
var regex_login = new RegExp(/(^|\s)@(\w*)/g);
var regex_res = regex_login.exec(string);
while (regex_res != null) {
var login = regex_res[2];
if (! ('res.users' in this.map_hash)) { this.map_hash['res.users']['name'] = []; }
this.map_hash['res.users']['login'].push(login);
regex_res = regex_login.exec(string);
}
/* internal links: #res.model,name */
var regex_intlink = new RegExp(/(^|\s)#(\w*[a-zA-Z_]+\w*)\.(\w+[a-zA-Z_]+\w*),(\w+)/g);
regex_res = regex_intlink.exec(string);
while (regex_res != null) {
var res_model = regex_res[2] + '.' + regex_res[3];
var res_name = regex_res[4];
if (! (res_model in this.map_hash)) { this.map_hash[res_model]['name'] = []; }
this.map_hash[res_model]['name'].push(res_name);
regex_res = regex_intlink.exec(string);
}
},
});
/* Add ThreadView widget to registry */
session.web.form.widgets.add(
'ThreadView', 'openerp.mail.RecordThread');
session.web.page.readonly.add(
'ThreadView', 'openerp.mail.RecordThread');
/* ThreadView widget: thread of comments */
mail.RecordThread = session.web.form.Field.extend({
// QWeb template to use when rendering the object
form_template: 'RecordThread',
init: function() {
this.is_sub = 0;
this.see_sub = 1;
this._super.apply(this, arguments);
this.thread = null;
/* DataSets */
this.ds = new session.web.DataSet(this, this.view.model);
this.ds_users = new session.web.DataSet(this, 'res.users');
},
start: function() {
var self = this;
this._super.apply(this, arguments);
/* bind and hide buttons */
self.$element.find('button.oe_mail_button_followers').bind('click', function () { self.do_toggle_followers(); });
self.$element.find('button.oe_mail_button_follow').bind('click', function () { self.do_follow(); }).hide();
self.$element.find('button.oe_mail_button_unfollow').bind('click', function () { self.do_unfollow(); }).hide();
},
stop: function () {
this._super.apply(this, arguments);
},
set_value: function() {
console.log('set_value');
var self = this;
this._super.apply(this, arguments);
this.see_sub = 1;
/* hide follow/unfollow/see followers buttons */
this.$element.find('button.oe_mail_button_followers').html('Hide followers')
this.$element.find('button.oe_mail_button_follow').hide();
this.$element.find('button.oe_mail_button_unfollow').hide();
if (! this.view.datarecord.id) { return; }
/* find wich (un)follow buttons to show */
var fetch_fol_done = this.ds.call('message_is_subscriber', [[this.view.datarecord.id]]).then(function (records) {
if (records == true) { self.is_sub = 1; self.$element.find('button.oe_mail_button_unfollow').show(); }
else { self.is_sub = 0; self.$element.find('button.oe_mail_button_follow').show(); }
});
/* fetch subscribers */
var fetch_sub_done = this.fetch_subscribers();
/* create ThreadDisplay widget and render it */
this.$element.find('div.oe_mail_recthread_left').empty();
if (this.thread) this.thread.stop();
this.thread = new mail.Thread(this, {'res_model': this.view.model, 'res_id': this.view.datarecord.id, 'uid': this.session.uid});
this.thread.appendTo(this.$element.find('div.oe_mail_recthread_left'));
return fetch_fol_done && fetch_sub_done;
},
fetch_subscribers: function () {
return this.ds.call('message_get_subscribers', [[this.view.datarecord.id]]).then(this.proxy('display_subscribers'));
},
display_subscribers: function (records) {
var self = this;
var sub_node = this.$element.find('div.oe_mail_followers')
sub_node.empty();
$('<h4/>').html('Followers (' + records.length + ')').appendTo(sub_node);
_(records).each(function (record) {
var mini_url = self.thread_get_avatar_mini('res.users', 'avatar_mini', record.id);
$('<img class="oe_mail_oe_left oe_mail_msg_image" src="' + mini_url + '" title="' + record.name + '" alt="' + record.name + '"/>').appendTo(sub_node);
});
},
do_follow: function () {
this.do_toggle_follow();
return this.ds.call('message_subscribe', [[this.view.datarecord.id]]).when();
},
do_unfollow: function () {
this.do_toggle_follow();
return this.ds.call('message_unsubscribe', [[this.view.datarecord.id]]).when();
},
do_toggle_follow: function () {
this.is_sub = 1 - this.is_sub;
this.$element.find('button.oe_mail_button_unfollow').toggle();
this.$element.find('button.oe_mail_button_follow').toggle();
},
do_toggle_followers: function () {
this.see_sub = 1 - this.see_sub;
if (this.see_sub == 1) { this.$element.find('button.oe_mail_button_followers').html('Hide followers'); }
else { this.$element.find('button.oe_mail_button_followers').html('Display followers'); }
this.$element.find('div.oe_mail_followers').toggle();
},
thread_get_avatar_mini: function(model, field, id) {
id = id || '';
var url = this.session.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + id;
return url;
},
});
/* Add WallView widget to registry */
session.web.client_actions.add('mail.all_feeds', 'session.mail.WallView');
/* WallView widget: a wall of messages */
mail.WallView = session.web.Widget.extend({
template: 'Wall',
/**
* @param {Object} parent parent
* @param {Object} [params]
* @param {Number} [params.limit=20] number of messages to show and fetch
* @param {Number} [params.search_view_id=false] search view id for messages
* @var {Array} sorted_comments records sorted by res_model and res_id
* records.res_model = {res_ids}
* records.res_model.res_id = [records]
*/
init: function (parent, params) {
this._super(parent);
this.params = params || {};
this.params.limit = params.limit || 2;
this.params.search_view_id = params.search_view_id || false;
this.params.thread_level = params.thread_level || 1;
this.params.search = {};
this.params.domain = [];
this.sorted_comments = {'models': {}};
this.display_show_more = true;
/* DataSets */
this.ds_msg = new session.web.DataSet(this, 'mail.message');
this.ds_thread = new session.web.DataSet(this, 'mail.thread');
this.ds_users = new session.web.DataSet(this, 'res.users');
},
start: function() {
var self = this;
this._super.apply(this, arguments);
/* events and buttons */
this.$element.find('button.oe_mail_wall_button_comment').bind('click', function () { self.do_comment(); });
this.$element.find('button.oe_mail_wall_button_more').bind('click', function () { self.do_more(); });
this.$element.find('p.oe_mail_wall_nomore').hide();
/* load mail.message search view */
var search_view_loaded = this.load_search_view(this.params.search_view_id, {}, false);
var search_view_ready = $.when(search_view_loaded).then(function () {
self.searchview.on_search.add(self.do_searchview_search);
});
/* fetch comments */
var comments_ready = this.init_comments(this.params.domain, {}, 0, this.params.limit);
return (search_view_ready && comments_ready);
},
stop: function () {
this._super.apply(this, arguments);
},
/**
* Loads the mail.message search view
* @param {Number} view_id id of the search view to load
* @param {Object} defaults ??
* @param {Boolean} hidden ??
*/
load_search_view: function (view_id, defaults, hidden) {
this.searchview = new session.web.SearchView(this, this.ds_msg, view_id || false, defaults || {}, hidden || false);
return this.searchview.appendTo(this.$element.find('div.oe_mail_wall_search'));
},
/**
* Aggregate the domains, contexts and groupbys in parameter
* with those from search form, and then calls fetch_comments
* to actually fetch comments
* @param {Array} domains
* @param {Array} contexts
* @param {Array} groupbys
*/
do_searchview_search: function(domains, contexts, groupbys) {
var self = this;
this.rpc('/web/session/eval_domain_and_context', {
domains: domains || [],
contexts: contexts || [],
group_by_seq: groupbys || []
}, function (results) {
self.params.search['context'] = results.context;
self.params.search['domain'] = results.domain;
self.params.search['groupby'] = results.group_by;
self.init_comments(self.params.search['domain'], self.params.search['context']);
});
},
/**
* Initializes Wall and calls fetch_comments
* @param {Array} domains
* @param {Array} contexts
* @param {Array} groupbys
* @param {Number} limit number of messages to fetch
*/
init_comments: function(domain, context, offset, limit) {
this.params.domain = [];
this.display_show_more = true;
this.sorted_comments = {'models': {}};
this.$element.find('div.oe_mail_wall_threads').empty();
return this.fetch_comments(domain, context, offset, limit);
},
/**
* Fetches Wall comments (mail.thread.get_pushed_messages)
* @param {Array} domains
* @param {Array} contexts
* @param {Array} groupbys
* @param {Number} limit number of messages to fetch
*/
fetch_comments: function (domain, context, offset, limit) {
var self = this;
var load_res = this.ds_thread.call('get_pushed_messages',
[[this.session.uid], (limit || 2), (offset || 0), (domain || []), true, [false], (context || null)]).then(function (records) {
if (records.length < self.params.limit) self.display_show_more = false;
self.display_comments(records);
if (! self.display_show_more) {
self.$element.find('button.oe_mail_wall_button_more:last').hide();
self.$element.find('p.oe_mail_wall_nomore:last').show();
}
});
return load_res;
},
/**
* @param {Array} records records to show in threads
*/
display_comments: function (records) {
var sorted_comments = this.sort_comments(records);
console.log(sorted_comments);
var self = this;
_(sorted_comments.model_list).each(function (model_name) {
_(sorted_comments.models[model_name].root_ids).each(function (id) {
var records = sorted_comments.models[model_name].msgs[id];
var template = 'WallThreadContainer';
var render_res = session.web.qweb.render(template, {});
$('<div class="oe_mail_wall_thread">').html(render_res).appendTo(self.$element.find('div.oe_mail_wall_threads'));
var thread = new mail.Thread(self, {
'res_model': model_name, 'res_id': records[0]['res_id'], 'uid': self.session.uid, 'records': records,
'parent_id': false, 'thread_level': self.params.thread_level}
);
var thread_displayed = thread.appendTo(self.$element.find('div.oe_mail_wall_thread:last'));
});
});
},
/**
* Add records to sorted_comments array
* @param {Array} records records from mail.message sorted by date desc
* @returns {Object} sc sorted_comments: dict
* sc.model_list = [record.model names]
* sc.models[model_name] = {
* 'root_ids': [root record.ids],
* 'id_to_root': {record.id: root.id, ..},
* 'msgs': {'root_id': [records]}, still sorted by date desc
* }, for each model
*/
sort_comments: function (records) {
var self = this;
var sc = {'model_list': [], 'models': {}}
var cur_iter = 0; var max_iter = 10; var modif = true;
/* step1: get roots */
while ( modif && (cur_iter++) < max_iter) {
modif = false;
_(records).each(function (record) {
if (_.indexOf(sc['model_list'], record.model) == -1) {
sc['model_list'].push(record.model);
sc['models'][record.model] = {'root_ids': [], 'id_to_root': {}, 'msgs': {}};
}
if (! self.sorted_comments['models'][record.model]) {
self.sorted_comments['models'][record.model] = {'root_ids': []};
}
var rmod = sc['models'][record.model];
if (record.parent_id == false && (_.indexOf(rmod['root_ids'], record.id) == -1)) {
rmod['root_ids'].push(record.id);
rmod['msgs'][record.id] = [];
self.sorted_comments['models'][record.model]['root_ids'].push(record.id);
modif = true;
}
else {
if ((_.indexOf(rmod['root_ids'], record.parent_id[0]) != -1) && (! rmod['id_to_root'][record.id])) {
rmod['id_to_root'][record.id] = record.parent_id[0];
modif = true;
}
else if ( rmod['id_to_root'][record.parent_id[0]] && (! rmod['id_to_root'][record.id])) {
rmod['id_to_root'][record.id] = rmod['id_to_root'][record.parent_id[0]];
modif = true;
}
}
});
}
/* step2: add records */
_(records).each(function (record) {
var root_id = sc['models'][record.model]['id_to_root'][record.id];
if (! root_id) root_id = record.id;
sc['models'][record.model]['msgs'][root_id].push(record);
});
return sc;
},
/**
* Create a domain to fetch new comments according to
* comment already present in sorted_comments
* @param {Object} sorted_comments (see sort_comments)
* @returns {Array} fetch_domain (OpenERP domain style)
*/
get_fetch_domain: function (sorted_comments) {
var domain = [];
_(sorted_comments.models).each(function (sc_model, model_name) { //each model
var ids = [];
_(sc_model.root_ids).each(function (id) { // each record
ids.push(id);
});
domain.push('|', ['model', '!=', model_name], '!', ['id', 'child_of', ids]);
});
return domain;
},
/**
* Action: Shows more discussions
*/
do_more: function () {
var domain = this.get_fetch_domain(this.sorted_comments);
return this.fetch_comments(domain);
},
/**
* Action: Posts a comment
*/
do_comment: function () {
var body_text = this.$element.find('textarea.oe_mail_wall_action_textarea').val();
return this.ds_users.call('message_append_note', [[this.session.uid], 'Tweet', body_text, false, 'comment']).then(this.init_comments());
},
/**
* Tools: get avatar mini (TODO: should be moved in some tools ?)
*/
thread_get_mini: function(model, field, id) {
return this.session.prefix + '/web/binary/image?session_id=' + this.session.session_id + '&model=' + model + '&field=' + field + '&id=' + (id || '');
},
});
};
// vim:et fdc=0 fdl=0 foldnestmax=3 fdm=syntax:

View File

@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<template>
<div t-name="Wall" class="oe_mail_wall">
<div class="oe_mail_wall_search">
</div>
<div class="oe_mail_wall_left">
<div class="oe_mail_wall_act">
<div class="oe_mail_wall_tweet">
<textarea class="oe_mail oe_mail_wall_action_textarea" placeholder="Add a personnal message here..."/><br />
<button class="oe_mail_wall_button_comment" type="button">Post comment</button>
</div>
</div>
<div class="oe_mail_wall_threads">
</div>
<div class="oe_mail_wall_more">
<button class="oe_mail_wall_button_more" type="button">See more discussions</button>
<p class="oe_mail_wall_nomore">You have loaded all discussions.</p>
</div>
</div>
<div class="oe_mail_wall_right">
</div>
</div>
<t t-name="WallThreadContainer">
</t>
<div t-name="RecordThread" class="oe_mail_recthread">
<div class="separator horizontal">OpenSocial</div>
<div class="oe_mail_recthread_left">
</div>
<div class="oe_mail_recthread_right">
<div class="oe_mail_actions">
<button type="button" class="oe_mail_button_follow">Follow</button>
<button type="button" class="oe_mail_button_unfollow">Unfollow</button>
<button type="button" class="oe_mail_button_followers">Display followers</button>
</div>
<div class="oe_mail_followers">
<h4>Followers</h4>
<div class="oe_mail_followers_display">
</div>
</div>
</div>
</div>
<div t-name="Thread" class="oe_mail_thread">
<div class="oe_mail_thread_act">
<img class="oe_mail_msg_image oe_mail_oe_left" alt="User img"/>
<div class="oe_mail_msg_content">
<textarea class="oe_mail oe_mail_action_textarea" placeholder="Add your comment here..." onfocus="this.value = '';"/><br />
</div>
</div>
<div class="oe_mail_thread_display"></div>
<div class="oe_mail_thread_more">
<button class="oe_mail_button_more" type="button">Load more messages</button>
<p class="oe_mail_p_nomore">You have loaded all messages in this thread.</p>
</div>
</div>
<div t-name="ThreadMsg" t-attf-class="{record.type} oe_mail_thread_msg">
<img class="oe_mail_msg_image oe_mail_oe_left" t-att-src="record.mini_url"/>
<div class="oe_mail_msg_content">
<t t-if="record.type == 'email'"><t t-call="EmailDisplay" /></t>
<t t-if="record.type == 'notification' || record.type == 'comment'"><t t-call="NoteDisplay" /></t>
</div>
<t t-if="record.type == 'tmp'"><t t-call="ThreadDisplay" /></t>
</div>
<t t-name="NoteDisplay">
<p class="oe_mail_msg">
<a href="#" class="intlink oe_mail_oe_intlink" t-attf-data-res-model='{res_model}' t-attf-data-res-id='{res_id}'><t t-raw="name"/></a>
<span class="oe_mail_msg_body"><t t-raw="record.body_text"/></span>
<t t-if="record.tr_body_text"><span class="oe_mail_msg_body_short"><t t-raw="record.tr_body_text"/></span></t>
<br />
<span class="oe_mail_oe_fade">
<a href="#" class="intlink oe_mail_oe_intlink" data-res-model='res.users' t-attf-data-res-id='{record.user_id[0]}'><t t-raw="record.user_id[1]"/></a>
on <t t-raw="record.date"/>
</span>
<t t-if="display.show_reply"><span class="oe_mail_oe_space"><a href="#" class="oe_mail_msg_reply">Reply</a></span></t>
<t t-if="display.show_delete"><span class="oe_mail_oe_space"><a href="#" class="oe_mail_msg_delete">Delete</a></span></t>
<t t-if="display.show_hide"><span class="oe_mail_oe_space"><a href="#" class="oe_mail_msg_hide">Hide</a></span></t>
</p>
</t>
<t t-name="EmailDisplay">
<p class="oe_mail_msg oe_mail_msg_p_email_header">
<span class="oe_mail_oe_bold">From:</span> <t t-esc="email_from"/> on <span class="oe_mail_oe_fade"><t t-raw="record.date"/></span><br />
<span>To:</span> <t t-sec="mail_to"/><br />
<span>Subject:</span> <t t-sec="subject"/><br />
</p>
<p class="oe_mail_msg_p">
<span class="oe_mail_msg_body"><t t-raw="record.body_text"/></span>
</p>
</t>
</template>

View File

@ -67,6 +67,9 @@ Dashboard for project members that includes:
'test/project_process.yml',
'test/task_process.yml',
],
'css': [
'static/src/css/project_task.css',
],
'installable': True,
'auto_install': False,
'application': True,

View File

@ -344,44 +344,46 @@
<t t-if="record.kanban_state.raw_value === 'done'" t-set="border">oe_kanban_color_green</t>
<div t-attf-class="#{kanban_color(record.color.raw_value)} #{border || ''}">
<div class="oe_kanban_box oe_kanban_color_border">
<table class="oe_kanban_table oe_kanban_box_header oe_kanban_color_bgdark oe_kanban_color_border oe_kanban_draghandle">
<tr>
<td align="left" valign="middle" width="16">
<a t-if="record.priority.raw_value == 1" icon="star-on" type="object" name="set_normal_priority"/>
<a t-if="record.priority.raw_value != 1" icon="star-off" type="object" name="set_high_priority" style="opacity:0.6; filter:alpha(opacity=60);"/>
</td>
<td align="left" valign="middle" class="oe_kanban_title" tooltip="task_details">
<field name="name"/>
</td>
<td valign="top" width="22">
<img t-att-src="kanban_gravatar(record.user_email.value, 22)" class="oe_kanban_gravatar" t-att-title="record.user_id.value"/>
</td>
</tr>
</table>
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger">
<div class="oe_kanban_description">
<t t-esc="kanban_text_ellipsis(record.description.value, 160)"/>
<i t-if="record.date_deadline.raw_value">
<t t-if="record.description.raw_value">, </t>
<field name="date_deadline"/>
</i>
<span class="oe_kanban_project_times" style="white-space: nowrap; padding-left: 5px;">
<t t-set="hours" t-value="record.remaining_hours.raw_value"/>
<t t-set="times" t-value="[
[1, (hours gte 1 and hours lt 2)]
,[2, (hours gte 2 and hours lt 5)]
,[5, (hours gte 5 and hours lt 10)]
,[10, (hours gte 10)]
]"/>
<t t-foreach="times" t-as="time"
><a t-if="!time[1]" t-attf-data-name="set_remaining_time_#{time[0]}"
type="object" class="oe_kanban_button"><t t-esc="time[0]"/></a
><b t-if="time[1]" class="oe_kanban_button oe_kanban_button_active"><t t-esc="Math.round(hours)"/></b
></t>
<a name="do_open" states="draft" string="Validate planned time and open task" type="object" class="oe_kanban_button oe_kanban_button_active">!</a>
</span>
<div class="oe_proj_task_avatar_box">
<img t-att-src="kanban_image('res.users', 'avatar_mini', record.user_id.raw_value[0])" class="oe_kanban_avatar" t-att-title="record.user_id.value"/>
</div>
<div class="oe_proj_task_content_box">
<table class="oe_kanban_table oe_kanban_box_header oe_kanban_color_bgdark oe_kanban_color_border oe_kanban_draghandle">
<tr>
<td align="left" valign="middle" width="16">
<a t-if="record.priority.raw_value == 1" icon="star-on" type="object" name="set_normal_priority"/>
<a t-if="record.priority.raw_value != 1" icon="star-off" type="object" name="set_high_priority" style="opacity:0.6; filter:alpha(opacity=60);"/>
</td>
<td align="left" valign="middle" class="oe_kanban_title" tooltip="task_details">
<field name="name"/>
</td>
</tr>
</table>
<div class="oe_kanban_box_content oe_kanban_color_bglight oe_kanban_box_show_onclick_trigger">
<div class="oe_kanban_description">
<t t-esc="kanban_text_ellipsis(record.description.value, 160)"/>
<i t-if="record.date_deadline.raw_value">
<t t-if="record.description.raw_value">, </t>
<field name="date_deadline"/>
</i>
<span class="oe_kanban_project_times" style="white-space: nowrap; padding-left: 5px;">
<t t-set="hours" t-value="record.remaining_hours.raw_value"/>
<t t-set="times" t-value="[
[1, (hours gte 1 and hours lt 2)]
,[2, (hours gte 2 and hours lt 5)]
,[5, (hours gte 5 and hours lt 10)]
,[10, (hours gte 10)]
]"/>
<t t-foreach="times" t-as="time"
><a t-if="!time[1]" t-attf-data-name="set_remaining_time_#{time[0]}"
type="object" class="oe_kanban_button"><t t-esc="time[0]"/></a
><b t-if="time[1]" class="oe_kanban_button oe_kanban_button_active"><t t-esc="Math.round(hours)"/></b
></t>
<a name="do_open" states="draft" string="Validate planned time and open task" type="object" class="oe_kanban_button oe_kanban_button_active">!</a>
</span>
</div>
<div class="oe_kanban_clear"/>
</div>
<div class="oe_kanban_clear"/>
</div>
<div class="oe_kanban_buttons_set oe_kanban_color_border oe_kanban_color_bglight oe_kanban_box_show_onclick">
<div class="oe_kanban_left">

View File

@ -0,0 +1,21 @@
.openerp .oe_proj_task_content_box {
margin-right: 41px;
}
.openerp .oe_proj_task_avatar_box {
float: right;
width: 40px;
}
.openerp .oe_kanban_avatar {
display: block;
width: 40px;
height: auto;
clip: rect(5px, 40px, 45px, 0px);
}
.openerp .oe_kanban_avatar_wide {
height: 40px;
width: auto;
clip: rect(0px, 45px, 40px, 05px);
}