diff options
| author | Mayur Maheshwari (OpenERP) <mma@tinyerp.com> | 2012-10-10 10:49:17 +0530 |
|---|---|---|
| committer | Mayur Maheshwari (OpenERP) <mma@tinyerp.com> | 2012-10-10 10:49:17 +0530 |
| commit | 79a84ffc5d2afaec71225b0e70dcccb79ecc8239 (patch) | |
| tree | 0e6b2ae62ba8d02d7c1d987b438a02ed7baea07f /addons | |
| parent | 9487c2d9ba9381e04c677d861864e29895f48e8e (diff) | |
| parent | 49b102b54a860e655156fced1940ae6b913a7458 (diff) | |
[MERGE]: Merge with lp:openobject-addons
bzr revid: mma@tinyerp.com-20121010051917-0ep6z3xy0ff92kzo
Diffstat (limited to 'addons')
82 files changed, 2615 insertions, 969 deletions
diff --git a/addons/account/account_invoice.py b/addons/account/account_invoice.py index 71f37180848..c3f507f1787 100644 --- a/addons/account/account_invoice.py +++ b/addons/account/account_invoice.py @@ -1360,6 +1360,7 @@ class account_invoice_line(osv.osv): _columns = { 'name': fields.text('Description', required=True), 'origin': fields.char('Source', size=256, help="Reference of the document that produced this invoice."), + 'sequence': fields.integer('Sequence', help="Gives the sequence of this line when displaying the invoice."), 'invoice_id': fields.many2one('account.invoice', 'Invoice Reference', ondelete='cascade', select=True), 'uos_id': fields.many2one('product.uom', 'Unit of Measure', ondelete='set null'), 'product_id': fields.many2one('product.product', 'Product', ondelete='set null'), diff --git a/addons/account/account_invoice_view.xml b/addons/account/account_invoice_view.xml index 26e18271dc2..3c2922ff141 100644 --- a/addons/account/account_invoice_view.xml +++ b/addons/account/account_invoice_view.xml @@ -458,7 +458,6 @@ <filter domain="[('user_id','=',uid)]" help="My Invoices" icon="terp-personal"/> <field name="partner_id"/> <field name="user_id" string="Salesperson"/> - <field name="journal_id"/> <field name="period_id" string="Period"/> <group expand="0" string="Group By..."> <filter string="Partner" icon="terp-partner" domain="[]" context="{'group_by':'partner_id'}"/> diff --git a/addons/account_analytic_default/i18n/hu.po b/addons/account_analytic_default/i18n/hu.po index 557aa2e66f5..8b863f51679 100644 --- a/addons/account_analytic_default/i18n/hu.po +++ b/addons/account_analytic_default/i18n/hu.po @@ -7,15 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2011-02-01 18:11+0000\n" -"Last-Translator: NOVOTRADE RENDSZERHÁZ ( novotrade.hu ) " -"<openerp@novotrade.hu>\n" +"PO-Revision-Date: 2012-10-09 13:58+0000\n" +"Last-Translator: krnkris <Unknown>\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-08-28 06:22+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: account_analytic_default #: help:account.analytic.default,partner_id:0 @@ -134,12 +133,14 @@ msgstr "Gyűjtőkód alapértelmezések" #. module: account_analytic_default #: sql_constraint:stock.picking:0 msgid "Reference must be unique per Company!" -msgstr "" +msgstr "Minden vállalkozáshoz egyedi hivatkozás kell!" #. module: account_analytic_default #: view:account.analytic.default:0 msgid "Analytical defaults whose end date is greater than today or None" msgstr "" +"A mai napnál későbbi lejáratú vagy nem létező dátumú elemzések " +"alapértelmezései" #. module: account_analytic_default #: help:account.analytic.default,product_id:0 diff --git a/addons/account_budget/i18n/hu.po b/addons/account_budget/i18n/hu.po index c48392c7c8a..35df6ac7dc9 100644 --- a/addons/account_budget/i18n/hu.po +++ b/addons/account_budget/i18n/hu.po @@ -7,15 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 6.0dev\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2011-01-30 16:38+0000\n" -"Last-Translator: NOVOTRADE RENDSZERHÁZ ( novotrade.hu ) " -"<openerp@novotrade.hu>\n" +"PO-Revision-Date: 2012-10-09 13:49+0000\n" +"Last-Translator: krnkris <Unknown>\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-08-28 06:27+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: account_budget #: field:crossovered.budget,creating_user_id:0 @@ -105,6 +104,7 @@ msgid "Percentage" msgstr "Százalék" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:crossovered.budget.report:0 msgid "to" msgstr "-" @@ -138,21 +138,24 @@ msgstr "" #: code:addons/account_budget/account_budget.py:119 #, python-format msgid "The Budget '%s' has no accounts!" -msgstr "" +msgstr "A '%s' költségvetésnek nincs számlaszáma!" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "%" msgstr "%" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Description" msgstr "Leírás" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:crossovered.budget.report:0 msgid "Currency" msgstr "Pénznem" @@ -187,6 +190,7 @@ msgid "Planned Amount" msgstr "Tervezett összeg" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Perc(%)" @@ -199,6 +203,7 @@ msgid "Done" msgstr "Kész" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Practical Amt" @@ -259,7 +264,7 @@ msgstr "Üzleti terv" #. module: account_budget #: view:crossovered.budget:0 msgid "To Approve Budgets" -msgstr "" +msgstr "Költségvetés jóváhagyása" #. module: account_budget #: code:addons/account_budget/account_budget.py:119 @@ -333,6 +338,7 @@ msgid "Account Budget crossvered summary report" msgstr "Üzleti terv összesítő kimutatás" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Theoretical Amt" @@ -359,9 +365,10 @@ msgstr "Nyomtatás" #: view:crossovered.budget:0 #: field:crossovered.budget.lines,theoritical_amount:0 msgid "Theoretical Amount" -msgstr "" +msgstr "Elméleti mennyiség" #. module: account_budget +#: report:account.analytic.account.budget:0 #: field:crossovered.budget.lines,analytic_account_id:0 #: model:ir.model,name:account_budget.model_account_analytic_account msgid "Analytic Account" @@ -373,6 +380,7 @@ msgid "Budget :" msgstr "Üzleti terv :" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Planned Amt" @@ -413,6 +421,7 @@ msgid "Error! You can not create recursive analytic accounts." msgstr "Hiba! Nem hozhat létre rekurzív gyűjtőkódokat." #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Analysis from" @@ -421,7 +430,7 @@ msgstr "Elemzési időszak" #. module: account_budget #: view:crossovered.budget:0 msgid "Draft Budgets" -msgstr "" +msgstr "Előzetes, tervezett költségvetés" #, python-format #~ msgid "The General Budget '%s' has no Accounts!" diff --git a/addons/account_budget/i18n/lo.po b/addons/account_budget/i18n/lo.po index daa39b8a6e6..cbc81b84000 100644 --- a/addons/account_budget/i18n/lo.po +++ b/addons/account_budget/i18n/lo.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2011-01-19 15:50+0000\n" +"PO-Revision-Date: 2012-10-09 16:05+0000\n" "Last-Translator: Brice Muangkhot ສຸພາ ເມືອງໂຄຕ <bmuangkhot@gmail.com>\n" "Language-Team: Lao <lo@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-08-28 06:27+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: account_budget #: field:crossovered.budget,creating_user_id:0 @@ -31,7 +31,7 @@ msgstr "ຮັບຮູ້ເເລັວ" #: model:ir.actions.act_window,name:account_budget.open_budget_post_form #: model:ir.ui.menu,name:account_budget.menu_budget_post_form msgid "Budgetary Positions" -msgstr "ຈຸດງົບປະມານ" +msgstr "ຕໍາແຫນ່ງງົບປະມານ" #. module: account_budget #: report:account.budget:0 @@ -105,6 +105,7 @@ msgid "Percentage" msgstr "ເປີເຊັນ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:crossovered.budget.report:0 msgid "to" msgstr "ຫາ" @@ -126,6 +127,14 @@ msgid "" "revenue per analytic account and monitor its evolution based on the actuals " "realised during that period." msgstr "" +"ງົບປະມານແມ່ນຄາດຄະເນລາຍໄດ້ຂອງບໍລິສັດຂອງທ່ານແລະຄ່າໃຊ້ຈ່າຍຄາດວ່າຈະສໍາລັບໄລຍະເວລາ" +"ໃນອະນາຄົດໄດ້. ມີງົບປະມານ, " +"ບໍລິສັດສາມາດລະມັດລະວັງເບິ່ງວິທີການເງິນຫຼາຍປານເຂົາເຈົ້າໄດ້ຖືກກິນໃນໄລຍະເວລາໃນໄລ" +"ຍະໃດຫນຶ່ງ, ແລະສະວິທີທີ່ດີທີ່ສຸດເພື່ອແບ່ງມັນໃນບັນດາປະເພດຂອງລາຍການຕ່າງໆ. " +"ໂດຍການຮັກສາຕິດຕາມບ່ອນທີ່ເງິນຂອງທ່ານດີ, ທ່ານອາດຈະມີຫນ້ອຍອາດຈະ overspend, " +"ແລະອື່ນໆທີ່ອາດຈະຕອບສະຫນອງໄດ້ຕາມເປົ້າຫມາຍທາງດ້ານການເງິນຂອງທ່ານ. " +"ຄາດງົບປະມານໂດຍ detailing ລາຍຮັບຄາດວ່າຈະຕໍ່ບັນຊີແຍກທາດແລະຕິດຕາມກວດກາຂອງຕົນ " +"evolution ອີງຕາມຕົວຈິງໃນ realized ໃນໄລຍະນັ້ນ." #. module: account_budget #: code:addons/account_budget/account_budget.py:119 @@ -134,18 +143,21 @@ msgid "The Budget '%s' has no accounts!" msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "%" msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Description" msgstr "ຄຳອະທິບາຍ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:crossovered.budget.report:0 msgid "Currency" msgstr "ເງິນຕາ" @@ -170,7 +182,7 @@ msgstr "ເພື່ອຮັບຮູ້" #. module: account_budget #: view:crossovered.budget:0 msgid "Reset to Draft" -msgstr "" +msgstr "ປັບກັບຮ່າງ" #. module: account_budget #: view:account.budget.post:0 @@ -180,6 +192,7 @@ msgid "Planned Amount" msgstr "ຈໍານວນຄາດໝາຍມ້ຽນ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Perc(%)" @@ -192,6 +205,7 @@ msgid "Done" msgstr "ຈົບເເລັວ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Practical Amt" @@ -203,7 +217,7 @@ msgstr "" #: view:crossovered.budget:0 #: field:crossovered.budget.lines,practical_amount:0 msgid "Practical Amount" -msgstr "" +msgstr "ຈໍານວນເງິນປະຕິບັດໄດ້" #. module: account_budget #: field:crossovered.budget,date_to:0 @@ -292,7 +306,7 @@ msgstr "" #. module: account_budget #: selection:crossovered.budget,state:0 msgid "Cancelled" -msgstr "" +msgstr "ຍົກເລີກ" #. module: account_budget #: view:crossovered.budget:0 @@ -326,6 +340,7 @@ msgid "Account Budget crossvered summary report" msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Theoretical Amt" @@ -355,6 +370,7 @@ msgid "Theoretical Amount" msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: field:crossovered.budget.lines,analytic_account_id:0 #: model:ir.model,name:account_budget.model_account_analytic_account msgid "Analytic Account" @@ -366,6 +382,7 @@ msgid "Budget :" msgstr "ງົບປະມານ" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Planned Amt" @@ -406,6 +423,7 @@ msgid "Error! You can not create recursive analytic accounts." msgstr "" #. module: account_budget +#: report:account.analytic.account.budget:0 #: report:account.budget:0 #: report:crossovered.budget.report:0 msgid "Analysis from" diff --git a/addons/account_followup/i18n/ar.po b/addons/account_followup/i18n/ar.po index 567253c4c02..bf1fde2db25 100644 --- a/addons/account_followup/i18n/ar.po +++ b/addons/account_followup/i18n/ar.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 00:35+0000\n" -"PO-Revision-Date: 2009-02-03 06:24+0000\n" -"Last-Translator: <>\n" +"PO-Revision-Date: 2012-10-08 15:59+0000\n" +"Last-Translator: waleed bazaza <waleed_bazaza@yahoo.com>\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-08-28 06:07+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-09 04:51+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: account_followup #: view:account_followup.followup:0 @@ -42,7 +42,7 @@ msgstr "متابعة" #: help:account.followup.print.all,test_print:0 msgid "" "Check if you want to print followups without changing followups level." -msgstr "" +msgstr "اختر هذه الخانة إذا أردت طباعة المتابعات بدون تغيير متوى المتابعات." #. module: account_followup #: model:account_followup.followup.line,description:account_followup.demo_followup_line2 @@ -101,7 +101,7 @@ msgstr "الدليل" #. module: account_followup #: view:account_followup.stat:0 msgid "Follow up Entries with period in current year" -msgstr "" +msgstr "مدخلات المتابعات بفترات في السنة الحالية" #. module: account_followup #: view:account.followup.print.all:0 @@ -314,6 +314,9 @@ msgid "" "\n" "%s" msgstr "" +"جميع رسائل البريد الالكتروني قد أرسلت بنجاح للشركاء:.\n" +"\n" +"%s" #. module: account_followup #: constraint:account_followup.followup.line:0 @@ -362,6 +365,11 @@ msgid "" "\n" "%s" msgstr "" +"\n" +"\n" +"رسالة البريد الالكتروني قد أرسلت للشركاء التاليين.!\n" +"\n" +"%s" #. module: account_followup #: help:account.followup.print,date:0 @@ -503,7 +511,7 @@ msgstr "تقرير المتابعة" #. module: account_followup #: view:account_followup.followup.line:0 msgid "Follow-Up Steps" -msgstr "" +msgstr "خطوات المتابعة" #. module: account_followup #: field:account_followup.stat,period_id:0 @@ -514,7 +522,7 @@ msgstr "فترة" #: code:addons/account_followup/wizard/account_followup_print.py:307 #, python-format msgid "Followup Summary" -msgstr "" +msgstr "خلاصة المتابعة" #. module: account_followup #: view:account.followup.print:0 @@ -535,7 +543,7 @@ msgstr "مستوى اعلى متابعة" #. module: account_followup #: model:ir.actions.act_window,name:account_followup.action_view_account_followup_followup_form msgid "Review Invoicing Follow-Ups" -msgstr "" +msgstr "استعراض متابعة الفوترة" #. module: account_followup #: constraint:account.move.line:0 @@ -595,7 +603,7 @@ msgstr "الوصف" #. module: account_followup #: constraint:account_followup.followup:0 msgid "Only One Followup by Company." -msgstr "" +msgstr "متابعة واحدة غقط من الشركة." #. module: account_followup #: view:account_followup.stat:0 diff --git a/addons/auth_oauth/auth_oauth_data.xml b/addons/auth_oauth/auth_oauth_data.xml index 83e9df2e44d..c07359a062c 100644 --- a/addons/auth_oauth/auth_oauth_data.xml +++ b/addons/auth_oauth/auth_oauth_data.xml @@ -1,6 +1,6 @@ <?xml version="1.0"?> <openerp> - <data> + <data noupdate="1"> <record id="provider_openerp" model="auth.oauth.provider"> <field name="name">OpenERP Accounts</field> diff --git a/addons/auth_reset_password/__init__.py b/addons/auth_reset_password/__init__.py index 28bbdd18161..8341ccd60c7 100644 --- a/addons/auth_reset_password/__init__.py +++ b/addons/auth_reset_password/__init__.py @@ -1 +1,23 @@ -import auth_reset_password +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +############################################################################## + +import controllers +import res_users diff --git a/addons/auth_reset_password/__openerp__.py b/addons/auth_reset_password/__openerp__.py index ccf5ca53a21..e620d515fa1 100644 --- a/addons/auth_reset_password/__openerp__.py +++ b/addons/auth_reset_password/__openerp__.py @@ -1,3 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +############################################################################## + { 'name': 'Reset Password', 'description': """ @@ -9,9 +30,11 @@ Allow users to reset their password from the login page. 'category': 'Authentication', 'website': 'http://www.openerp.com', 'installable': True, - 'depends': ['auth_anonymous', 'email_template'], - 'data': ['auth_reset_password.xml'], + 'depends': ['auth_signup', 'email_template'], + 'data': [ + 'auth_reset_password.xml', + 'res_users_view.xml', + ], 'js': ['static/src/js/reset_password.js'], - 'css': ['static/src/css/reset_password.css'], 'qweb': ['static/src/xml/reset_password.xml'], } diff --git a/addons/auth_reset_password/auth_reset_password.py b/addons/auth_reset_password/auth_reset_password.py deleted file mode 100644 index cef7bc9c212..00000000000 --- a/addons/auth_reset_password/auth_reset_password.py +++ /dev/null @@ -1,130 +0,0 @@ -import base64 -import hashlib -import simplejson -import time -import urlparse - -from openerp.tools import config -from openerp.osv import osv, fields -from openerp import SUPERUSER_ID - -TWENTY_FOUR_HOURS = 24 * 60 * 60 - -def message_sign(data, secret): - src = simplejson.dumps([data, secret], indent=None, separators=(',', ':'), sort_keys=True) - sign = hashlib.sha1(src).hexdigest() - msg = simplejson.dumps([data, sign], indent=None, separators=(',', ':'), sort_keys=True) - # pad message to avoid '=' - pad = (3 - len(msg) % 3) % 3 - msg = msg + " " * pad - msg = base64.urlsafe_b64encode(msg) - return msg, sign - -def message_check(msg, secret): - msg = base64.urlsafe_b64decode(msg) - l = simplejson.loads(msg) - msg_data = l[0] - msg_sign = l[1] - tmp, sign = message_sign(msg_data, secret) - if msg_sign == sign: - return msg_data - -class res_users(osv.osv): - _inherit = 'res.users' - - def _auth_reset_password_secret(self, cr, uid, context=None): - uuid = self.pool.get('ir.config_parameter').get_param(cr, uid, 'database.uuid') - res = { - 'dbname': cr.dbname, - 'uuid': uuid, - 'admin_passwd': config['admin_passwd'] - } - return res - - def _auth_reset_password_host(self, cr, uid, context=None): - return self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', '') - - def _auth_reset_password_link(self, cr, uid, ids, context=None): - assert len(ids) == 1 - host = self._auth_reset_password_host(cr, uid, context) - secret = self._auth_reset_password_secret(cr, uid, context) - msg_src = { - 'time': time.time(), - 'uid': ids[0], - } - msg, sign = message_sign(msg_src, secret) - link = urlparse.urljoin(host, '/login?db=%s&login=anonymous&key=anonymous#action=reset_password&token=%s' % (cr.dbname, msg)) - return link - - def _auth_reset_password_check_token(self, cr, uid, token, context=None): - secret = self._auth_reset_password_secret(cr, uid, context) - data = message_check(token, secret) - if data and (time.time() - data['time'] < TWENTY_FOUR_HOURS): - return data - return None - - def _auth_reset_password_send_email(self, cr, uid, email_to, tpl_name, res_id, context=None): - model, tpl_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'auth_reset_password', tpl_name) - assert model == 'email.template' - - msg_id = self.pool.get(model).send_mail(cr, uid, tpl_id, res_id, force_send=False, context=context) - MailMessage = self.pool.get('mail.message') - MailMessage.write(cr, uid, [msg_id], {'email_to': email_to}, context=context) - MailMessage.send(cr, uid, [msg_id], context=context) - - def send_reset_password_request(self, cr, uid, email, context=None): - # TODO reseting a password knowing only an email is not good enough (email can be shared between multiple logins). - ids = self.pool.get('res.users').search(cr, SUPERUSER_ID, [('user_email', '=', email)], context=context) - if ids: - self._auth_reset_password_send_email(cr, SUPERUSER_ID, email, 'reset_password_email', ids[0], context=context) - return True - #else: - # _m, company_id = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'base', 'main_company') - # self._auth_reset_password_send_email(cr, uid, email, 'email_no_user', company_id, context=context) - return False - -class auth_reset_password(osv.TransientModel): - _name = 'auth.reset_password' - _rec_name = 'password' - _columns = { - 'password': fields.char('Password', size=64), - 'password_confirmation': fields.char('Confirm Password', size=64), - 'token': fields.char('Token', size=128), - 'state': fields.selection([(x, x) for x in 'draft done missmatch error'.split()], required=True), - } - _defaults = { - 'state': 'draft', - } - - def create(self, cr, uid, values, context=None): - # NOTE here, invalid values raises exceptions to avoid storing - # sensitive data into the database (which then are available to anyone) - - pw = values.get('password') - if not pw or pw != values.get('password_confirmation'): - raise osv.except_osv('Error', 'Passwords missmatch') - - Users = self.pool.get('res.users') - data = Users._auth_reset_password_check_token(cr, uid, values.get('token', '')) - if data: - Users.write(cr, SUPERUSER_ID, data['uid'], {'password': pw}, context=context) - else: - raise osv.except_osv('Error', 'Invalid token') - - # Dont store password - values = {'state': 'done'} - return super(auth_reset_password, self).create(cr, uid, values, context) - - def change(self, cr, uid, ids, context=None): - return True - - def onchange_pw(self, cr, uid, ids, password, password_confirmation, context=None): - if password != password_confirmation: - return {'value': {'state': 'missmatch'}} - return {'value': {'state': 'draft'}} - - def onchange_token(self, cr, uid, ids, token, context=None): - Users = self.pool.get('res.users') - if not Users._auth_reset_password_check_token(cr, uid, token, context=context): - return {'value': {'state': 'error'}} - return {} diff --git a/addons/auth_reset_password/auth_reset_password.xml b/addons/auth_reset_password/auth_reset_password.xml index bb75da34ce8..90f08a25899 100644 --- a/addons/auth_reset_password/auth_reset_password.xml +++ b/addons/auth_reset_password/auth_reset_password.xml @@ -7,56 +7,15 @@ <field name="name">Reset Password</field> <field name="model_id" ref="base.model_res_users"/> <field name="email_from"><![CDATA[${object.company_id.name} <${object.company_id.email}>]]></field> - <field name="email_to" eval="False"><!--(set by reset_password module)--></field> + <field name="email_to">${object.email}</field> <field name="subject">Password reset</field> <field name="body_html"><![CDATA[ -<p>A password reset was requested the OpenERP account linked to this email on ${object._auth_reset_password_host()}</p> +<p>A password reset was requested for the OpenERP account linked to this email.</p> -<p>You may change your password following this <a href="${object._auth_reset_password_link()}">link</a>, -or by copy-pasting the following URL in your browser: ${object._auth_reset_password_link()}</p> +<p>You may change your password following <a href="${object.signup_url}">this link</a>.</p> <p>Note: If you did not ask for a password reset, you can safely ignore this email.</p>]]></field> </record> - <!-- TODO get own css --> - <record id="reset_password_wizard_form_view" model="ir.ui.view"> - <field name="name">auth.reset_password.form</field> - <field name="model">auth.reset_password</field> - <field name="arch" type="xml"> - <form string="Reset Password" version="7.0"> - <field name="state" invisible="1"/> - <field name="token" on_change="onchange_token(token)" invisible="1"/> - <group colspan="4" states="draft,missmatch"> - <field name="password" required='1' on_change="onchange_pw(password,password_confirmation)"/> - <field name="password_confirmation" required='1' on_change="onchange_pw(password,password_confirmation)"/> - <group colspan="4" states="missmatch"> - <div>Passwords missmatch</div> - </group> - <group colspan="2" col="1"> - <button string="Change Password" name="change" icon="gtk-dialog-authentication" attrs="{'readonly': [('state', '=', 'missmatch')]}"/> - </group> - </group> - <group colspan="4" states="error" col="1"> - <div>Invalid or expired token</div> - <button special="cancel" string="Close"/> - </group> - <group colspan="4" states="done" col="1"> - <div>Password changed. We sent you an email confirming the password change.</div> - <button special="cancel" string="Close"/> - </group> - </form> - </field> - </record> - - <record id="action_reset" model="ir.actions.act_window"> - <field name="name">Reset Password</field> - <field name="type">ir.actions.act_window</field> - <field name="res_model">auth.reset_password</field> - <field name="view_type">form</field> - <field name="view_mode">form</field> - <field name="target">new</field> - </record> - - </data> </openerp> diff --git a/addons/auth_reset_password/controllers/__init__.py b/addons/auth_reset_password/controllers/__init__.py new file mode 100644 index 00000000000..f40f0ee976e --- /dev/null +++ b/addons/auth_reset_password/controllers/__init__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +############################################################################## + +import main + +# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_reset_password/controllers/main.py b/addons/auth_reset_password/controllers/main.py new file mode 100644 index 00000000000..5185a65c3ce --- /dev/null +++ b/addons/auth_reset_password/controllers/main.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +############################################################################## + +from openerp import SUPERUSER_ID +from openerp.modules.registry import RegistryManager +import openerp.addons.web.common.http as openerpweb + +import werkzeug + +import logging +_logger = logging.getLogger(__name__) + +class Controller(openerpweb.Controller): + _cp_path = '/auth_reset_password' + + @openerpweb.httprequest + def reset_password(self, req, dbname, login): + """ retrieve user, and perform reset password """ + url = '/' + registry = RegistryManager.get(dbname) + with registry.cursor() as cr: + try: + res_users = registry.get('res.users') + res_users.reset_password(cr, SUPERUSER_ID, login) + cr.commit() + message = 'An email has been sent with credentials to reset your password' + except Exception as e: + # signup error + _logger.exception('error when resetting password') + message = e.message + url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(message) + return werkzeug.utils.redirect(url) + +# vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_reset_password/res_users.py b/addons/auth_reset_password/res_users.py new file mode 100644 index 00000000000..b1039031812 --- /dev/null +++ b/addons/auth_reset_password/res_users.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +############################################################################## + +from openerp.osv import osv, fields +from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT + +from datetime import datetime, timedelta + +def now(**kwargs): + dt = datetime.now() + timedelta(**kwargs) + return dt.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + + +class res_users(osv.osv): + _inherit = 'res.users' + + def reset_password(self, cr, uid, login, context=None): + """ retrieve the user corresponding to login (login or email), + and reset their password + """ + user_ids = self.search(cr, uid, [('login', '=', login)], context=context) + if not user_ids: + user_ids = self.search(cr, uid, [('email', '=', login)], context=context) + if len(user_ids) != 1: + raise Exception('Reset password: invalid username or email') + return self.action_reset_password(cr, uid, user_ids, context=context) + + def action_reset_password(self, cr, uid, ids, context=None): + """ create signup token for each user, and send their signup url by email """ + # prepare reset password signup + res_partner = self.pool.get('res.partner') + partner_ids = [user.partner_id.id for user in self.browse(cr, uid, ids, context)] + res_partner.signup_prepare(cr, uid, partner_ids, expiration=now(days=+1), context=context) + + # send email to users with their signup url + template = self.pool.get('ir.model.data').get_object(cr, uid, 'auth_reset_password', 'reset_password_email') + assert template._name == 'email.template' + for user in self.browse(cr, uid, ids, context): + self.pool.get('email.template').send_mail(cr, uid, template.id, user.id, context=context) + + return True diff --git a/addons/auth_reset_password/res_users_view.xml b/addons/auth_reset_password/res_users_view.xml new file mode 100644 index 00000000000..9952162ae65 --- /dev/null +++ b/addons/auth_reset_password/res_users_view.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<openerp> + <data> + + <record id="res_users_form_view" model="ir.ui.view"> + <field name="name">user.form.reset_password</field> + <field name="model">res.users</field> + <field name="inherit_id" ref="base.view_users_form"/> + <field name="arch" type="xml"> + <xpath expr="//sheet/*[1]" position="before"> + <div class="oe_right oe_button_box"> + <button string="Reset Password" type="object" name="action_reset_password"/> + </div> + </xpath> + </field> + </record> + + </data> +</openerp> diff --git a/addons/auth_reset_password/static/src/css/reset_password.css b/addons/auth_reset_password/static/src/css/reset_password.css deleted file mode 100644 index dd590365918..00000000000 --- a/addons/auth_reset_password/static/src/css/reset_password.css +++ /dev/null @@ -1,12 +0,0 @@ -.openerp .oe_login .oe_login_pane { - height: 152px; -} -.openerp .oe_login .oe_login_pane ul.oe_login_switch a { - color: #eeeeee; - margin: 0 8px; -} -.openerp .oe_login .oe_login_pane ul.oe_login_switch a:hover { - text-decoration: underline; -} - - diff --git a/addons/auth_reset_password/static/src/js/reset_password.js b/addons/auth_reset_password/static/src/js/reset_password.js index 4e09b5c5318..13fe2a39ce5 100644 --- a/addons/auth_reset_password/static/src/js/reset_password.js +++ b/addons/auth_reset_password/static/src/js/reset_password.js @@ -1,72 +1,30 @@ openerp.auth_reset_password = function(instance) { var _t = instance.web._t; + instance.web.Login.include({ start: function() { - var $e = this.$el; - $e.find('a.oe_login_switch').click(function() { - $e.find('ul.oe_login_switch').toggle(); - var $m = $e.find('form input[name=is_reset_pw]'); - $m.attr('checked', !$m.is(':checked')); - }); + this.$('a.oe_reset_password').click(this.do_reset_password); return this._super(); }, - on_submit: function(ev) { - if(ev) { + do_reset_password: function(ev) { + if (ev) { ev.preventDefault(); } - - var $e = this.$el; - var db = $e.find("form [name=db]").val(); + var db = this.$("form [name=db]").val(); + var login = this.$("form input[name=login]").val(); if (!db) { - this.do_warn(_t("Login"), _t("No database selected !")); + this.do_warn("Login", "No database selected !"); + return false; + } else if (!login) { + this.do_warn("Login", "Please enter a username or email address.") return false; } - var $m = $e.find('form input[name=is_reset_pw]'); - if ($m.is(':checked')) { - var email = $e.find('form input[name=email]').val(); - return this.do_reset_password(db, email); - } else { - return this._super(ev); - } - }, - do_reset_password: function(db, email) { - var self = this; - instance.session.session_authenticate(db, 'anonymous', 'anonymous', true).pipe(function () { - var func = new instance.web.Model("res.users").get_func("send_reset_password_request"); - return func(email).then(function(res) { - // show message - self.do_notify(_t('Reset Password'), _.str.sprintf(_t('We have sent an email to %s with further instructions'), email), true); - }, function(error, event) { - // no traceback please - event.preventDefault(); - }); - }).fail(function(error, event) { - // cannot log as anonymous or reset_password not installed - self.do_warn(_t('Reset Password'), _.str.sprintf(_t('Reset Password functionnality is not available for database %s'), db), true); - }); - } - }); - - instance.reset_password = {}; - instance.reset_password.ResetPassword = instance.web.Widget.extend({ - init: function(parent, params) { - this._super(parent); - this.token = (params && params.token) || false; - }, - start: function() { - this.do_action({ - name: 'Reset Password', - type: 'ir.actions.act_window', - context: {default_token: this.token}, - res_model: 'auth.reset_password', - target: 'new', - views: [[false, 'form']] - }); + var params = { + dbname : db, + login: login, + }; + var url = "/auth_reset_password/reset_password?" + $.param(params); + window.location = url; } }); - - - instance.web.client_actions.add("reset_password", "instance.reset_password.ResetPassword"); - - }; diff --git a/addons/auth_reset_password/static/src/xml/reset_password.xml b/addons/auth_reset_password/static/src/xml/reset_password.xml index e57334b9c15..841f3906622 100644 --- a/addons/auth_reset_password/static/src/xml/reset_password.xml +++ b/addons/auth_reset_password/static/src/xml/reset_password.xml @@ -1,26 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- vim:fdl=1: ---> +<!-- vim:fdl=1: --> <templates id="template" xml:space="preserve"> <t t-extend="Login"> - <t t-jquery="form ul:first"> - // addClass does not work :( - this.attr('class', (this.attr('class') || '') + ' oe_login_switch'); - </t> <t t-jquery="form ul:first li:last" t-operation="after"> - <li> - <a class="oe_login_switch" href="#">Forgot your password?</a> - </li> - </t> - <t t-jquery="form ul:first" t-operation="after"> - <ul class="oe_login_switch" style="display:none;"> - <li style="display:none;"><input type="checkbox" name="is_reset_pw"/></li> - <li>Email</li> - <li><input type="email" name="email"/></li> - <li><button name="submit">Reset Password</button></li> - <li><a class="oe_login_switch" href="#">< Back</a></li> - </ul> + <li><a class="oe_reset_password" href="#">Reset password</a></li> </t> </t> + </templates> diff --git a/addons/auth_signup/__openerp__.py b/addons/auth_signup/__openerp__.py index 9cfe89cdc1c..9cbeb60638f 100644 --- a/addons/auth_signup/__openerp__.py +++ b/addons/auth_signup/__openerp__.py @@ -34,7 +34,9 @@ Allow users to sign up. 'data': [ 'auth_signup_data.xml', 'res_config.xml', + 'res_users_view.xml', ], 'js': ['static/src/js/auth_signup.js'], + 'css' : ['static/src/css/base.css'], 'qweb': ['static/src/xml/auth_signup.xml'], } diff --git a/addons/auth_signup/controllers/main.py b/addons/auth_signup/controllers/main.py index 821ef44f459..56e5e910212 100644 --- a/addons/auth_signup/controllers/main.py +++ b/addons/auth_signup/controllers/main.py @@ -1,35 +1,63 @@ -import logging - -import werkzeug.urls +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +############################################################################## +from openerp import SUPERUSER_ID from openerp.modules.registry import RegistryManager from openerp.addons.web.controllers.main import login_and_redirect import openerp.addons.web.common.http as openerpweb -from openerp import SUPERUSER_ID +import werkzeug + +import logging _logger = logging.getLogger(__name__) -class OpenIDController(openerpweb.Controller): +class Controller(openerpweb.Controller): _cp_path = '/auth_signup' + @openerpweb.jsonrequest + def retrieve(self, req, dbname, token): + """ retrieve the user info (name, login or email) corresponding to a signup token """ + registry = RegistryManager.get(dbname) + user_info = None + with registry.cursor() as cr: + res_partner = registry.get('res.partner') + user_info = res_partner.signup_retrieve_info(cr, SUPERUSER_ID, token) + return user_info + @openerpweb.httprequest - def signup(self, req, dbname, name, login, password): + def signup(self, req, dbname, token, name, login, password): + """ sign up a user (new or existing), and log it in """ url = '/' registry = RegistryManager.get(dbname) with registry.cursor() as cr: try: - Users = registry.get('res.users') - credentials = Users.auth_signup(cr, SUPERUSER_ID, name, login, password) + res_users = registry.get('res.users') + values = {'name': name, 'login': login, 'password': password} + credentials = res_users.signup(cr, SUPERUSER_ID, values, token) cr.commit() return login_and_redirect(req, *credentials) - except AttributeError: - # auth_signup is not installed - _logger.exception('attribute error when signup') - url = "/#action=auth_signup&error=NA" # Not Available - except Exception: + except Exception as e: # signup error _logger.exception('error when signup') - url = "/#action=auth_signup&error=UE" # Unexcpected Error + url = "/#action=login&error_message=%s" % werkzeug.urls.url_quote(e.message) return werkzeug.utils.redirect(url) # vim:expandtab:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/auth_signup/res_config.py b/addons/auth_signup/res_config.py index 8bfe1fc3ddb..21fce19745c 100644 --- a/addons/auth_signup/res_config.py +++ b/addons/auth_signup/res_config.py @@ -20,22 +20,27 @@ ############################################################################## from openerp.osv import osv, fields +from openerp.tools.safe_eval import safe_eval class base_config_settings(osv.TransientModel): _inherit = 'base.config.settings' _columns = { - 'auth_signup_uninvited': fields.boolean('Allow public users to sign up', help="If unchecked only invited users may sign up"), + 'auth_signup_uninvited': fields.boolean('Allow external users to sign up', help="If unchecked, only invited users may sign up"), 'auth_signup_template_user_id': fields.many2one('res.users', 'Template user for new users created through signup'), } def get_default_auth_signup_template_user_id(self, cr, uid, fields, context=None): icp = self.pool.get('ir.config_parameter') + # we use safe_eval on the result, since the value of the parameter is a nonempty string return { - 'auth_signup_template_user_id': icp.get_param(cr, uid, 'auth_signup.template_user_id', 0) or False + 'auth_signup_uninvited': safe_eval(icp.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')), + 'auth_signup_template_user_id': safe_eval(icp.get_param(cr, uid, 'auth_signup.template_user_id', 'False')), } def set_auth_signup_template_user_id(self, cr, uid, ids, context=None): config = self.browse(cr, uid, ids[0], context=context) icp = self.pool.get('ir.config_parameter') - icp.set_param(cr, uid, 'auth_signup.template_user_id', config.auth_signup_template_user_id.id) + # we store the repr of the values, since the value of the parameter is a required string + icp.set_param(cr, uid, 'auth_signup.allow_uninvited', repr(config.auth_signup_uninvited)) + icp.set_param(cr, uid, 'auth_signup.template_user_id', repr(config.auth_signup_template_user_id.id)) diff --git a/addons/auth_signup/res_config.xml b/addons/auth_signup/res_config.xml index c17634ececb..9d3eb4eaa5a 100644 --- a/addons/auth_signup/res_config.xml +++ b/addons/auth_signup/res_config.xml @@ -14,7 +14,9 @@ </div> <div attrs="{'invisible':[('auth_signup_uninvited','=',False)]}"> <label for="auth_signup_template_user_id"/> - <field name="auth_signup_template_user_id" class="oe_inline" domain="['|',('active','=',0),('active','=',1)]"/> + <field name="auth_signup_template_user_id" class="oe_inline" + attrs="{'required': [('auth_signup_uninvited', '=', True)]}" + domain="['|',('active','=',0),('active','=',1)]"/> </div> </xpath> </field> diff --git a/addons/auth_signup/res_users.py b/addons/auth_signup/res_users.py index 5f6463508b6..7b663bd0e33 100644 --- a/addons/auth_signup/res_users.py +++ b/addons/auth_signup/res_users.py @@ -1,47 +1,205 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2012-today OpenERP SA (<http://www.openerp.com>) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/> +# +############################################################################## + import openerp -from openerp.osv import osv +from openerp.osv import osv, fields from openerp import SUPERUSER_ID +from openerp.tools.misc import DEFAULT_SERVER_DATETIME_FORMAT +from openerp.tools.safe_eval import safe_eval + +import time +import random +import urllib +import urlparse + +def random_token(): + # the token has an entropy of about 120 bits (6 bits/char * 20 chars) + chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + return ''.join(random.choice(chars) for i in xrange(20)) + +def now(): + return time.strftime(DEFAULT_SERVER_DATETIME_FORMAT) + + +class res_partner(osv.Model): + _inherit = 'res.partner' + + def _get_signup_valid(self, cr, uid, ids, name, arg, context=None): + dt = now() + res = {} + for partner in self.browse(cr, uid, ids, context): + res[partner.id] = bool(partner.signup_token) and \ + (not partner.signup_expiration or dt <= partner.signup_expiration) + return res + + def _get_signup_url(self, cr, uid, ids, name, arg, context=None): + """ determine a signup url for a given partner """ + base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url') + + # if required, make sure that every partner without user has a valid signup token + if context and context.get('signup_valid'): + unsigned_ids = [p.id for p in self.browse(cr, uid, ids, context) if not p.user_ids] + self.signup_prepare(cr, uid, unsigned_ids, context=context) + + res = dict.fromkeys(ids, False) + for partner in self.browse(cr, uid, ids, context): + if partner.signup_token: + params = (urllib.quote(cr.dbname), urllib.quote(partner.signup_token)) + res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&token=%s" % params) + elif partner.user_ids: + user = partner.user_ids[0] + params = (urllib.quote(cr.dbname), urllib.quote(user.login)) + res[partner.id] = urlparse.urljoin(base_url, "#action=login&db=%s&login=%s" % params) + return res + + _columns = { + 'signup_token': fields.char(size=24, string='Signup Token'), + 'signup_expiration': fields.datetime(string='Signup Expiration'), + 'signup_valid': fields.function(_get_signup_valid, type='boolean', string='Signup Token is Valid'), + 'signup_url': fields.function(_get_signup_url, type='char', string='Signup URL'), + } + + def action_signup_prepare(self, cr, uid, ids, context=None): + return self.signup_prepare(cr, uid, ids, context=context) + + def signup_prepare(self, cr, uid, ids, expiration=False, context=None): + """ generate a new token for the partners with the given validity, if necessary + :param expiration: the expiration datetime of the token (string, optional) + """ + for partner in self.browse(cr, uid, ids, context): + if expiration or not partner.signup_valid: + token = random_token() + while self._signup_retrieve_partner(cr, uid, token, context=context): + token = random_token() + partner.write({'signup_token': token, 'signup_expiration': expiration}) + return True + + def _signup_retrieve_partner(self, cr, uid, token, + check_validity=False, raise_exception=False, context=None): + """ find the partner corresponding to a token, and possibly check its validity + :param token: the token to resolve + :param check_validity: if True, also check validity + :param raise_exception: if True, raise exception instead of returning False + :return: partner (browse record) or False (if raise_exception is False) + """ + partner_ids = self.search(cr, uid, [('signup_token', '=', token)], context=context) + if not partner_ids: + if raise_exception: + raise Exception("Signup token '%s' is not valid" % token) + return False + partner = self.browse(cr, uid, partner_ids[0], context) + if check_validity and not partner.signup_valid: + if raise_exception: + raise Exception("Signup token '%s' is no longer valid" % token) + return False + return partner + + def signup_retrieve_info(self, cr, uid, token, context=None): + """ retrieve the user info about the token + :return: a dictionary with the user information: + - 'db': the name of the database + - 'token': the token, if token is valid + - 'name': the name of the partner, if token is valid + - 'login': the user login, if the user already exists + - 'email': the partner email, if the user does not exist + """ + partner = self._signup_retrieve_partner(cr, uid, token, raise_exception=True, context=None) + res = {'db': cr.dbname} + if partner.signup_valid: + res['token'] = token + res['name'] = partner.name + if partner.user_ids: + res['login'] = partner.user_ids[0].login + else: + res['email'] = partner.email or '' + return res + + class res_users(osv.Model): _inherit = 'res.users' - def auth_signup_create(self, cr, uid, new_user, context=None): - # new_user: - # login - # email - # name (optional) - # partner_id (optional) - # groups (optional) - # sign (for partner_id and groups) - # - user_template_id = self.pool.get('ir.config_parameter').get_param(cr, uid, 'auth.signup_template_user_id', 0) - if user_template_id: - self.pool.get('res.users').copy(cr, SUPERUSER_ID, user_template_id, new_user, context=context) - else: - self.pool.get('res.users').create(cr, SUPERUSER_ID, new_user, context=context) - - def auth_signup(self, cr, uid, name, login, password, context=None): - r = (cr.dbname, login, password) - res = self.search(cr, uid, [("login", "=", login)]) - if res: - # Existing user - user_id = res[0] - try: - self.check(cr.dbname, user_id, password) - # Same password - except openerp.exceptions.AccessDenied: - # Different password - raise - else: - # New user - new_user = { - 'name': name, - 'login': login, - 'user_email': login, - 'password': password, - 'active': True, - } - self.auth_signup_create(cr, uid, new_user) - return r + def _get_state(self, cr, uid, ids, name, arg, context=None): + return dict((user.id, 'new' if not user.login_date else 'reset' if user.signup_token else 'active') + for user in self.browse(cr, uid, ids, context)) -# + _columns = { + 'state': fields.function(_get_state, string='State', type='selection', + selection=[('new', 'New'), ('active', 'Active'), ('reset', 'Resetting Password')]), + } + + def signup(self, cr, uid, values, token=None, context=None): + """ signup a user, to either: + - create a new user (no token), or + - create a user for a partner (with token, but no user for partner), or + - change the password of a user (with token, and existing user). + :param values: a dictionary with field values + :param token: signup token (optional) + :return: (dbname, login, password) for the signed up user + """ + assert values.get('login') and values.get('password') + result = (cr.dbname, values['login'], values['password']) + + if token: + # signup with a token: find the corresponding partner id + res_partner = self.pool.get('res.partner') + partner = res_partner._signup_retrieve_partner(cr, uid, token, + check_validity=True, raise_exception=True, context=None) + # invalidate signup token + partner.write({'signup_token': False, 'signup_expiration': False}) + if partner.user_ids: + # user exists, modify its password + partner.user_ids[0].write({'password': values['password']}) + else: + # user does not exist: sign up invited user + self._signup_create_user(cr, uid, { + 'name': partner.name, + 'login': values['login'], + 'password': values['password'], + 'email': values['login'], + 'partner_id': partner.id, + }, context=context) + return result + + # sign up an external user + assert values.get('name'), 'Signup: no name given for new user' + self._signup_create_user(cr, uid, { + 'name': values['name'], + 'login': values['login'], + 'password': values['password'], + 'email': values['login'], + }, context=context) + return result + + def _signup_create_user(self, cr, uid, values, context=None): + """ create a new user from the template user """ + ir_config_parameter = self.pool.get('ir.config_parameter') + template_user_id = safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.template_user_id', 'False')) + assert template_user_id and self.exists(cr, uid, template_user_id, context=context), 'Signup: invalid template user' + + # check that uninvited users may sign up + if 'partner_id' not in values: + if not safe_eval(ir_config_parameter.get_param(cr, uid, 'auth_signup.allow_uninvited', 'False')): + raise Exception('Signup is not allowed for uninvited users') + + # create a copy of the template user (attached to a specific partner_id if given) + values['active'] = True + return self.copy(cr, uid, template_user_id, values, context=context) diff --git a/addons/auth_signup/res_users_view.xml b/addons/auth_signup/res_users_view.xml new file mode 100644 index 00000000000..c42e1ab5f41 --- /dev/null +++ b/addons/auth_signup/res_users_view.xml @@ -0,0 +1,19 @@ +<?xml version="1.0" encoding="UTF-8"?> +<openerp> + <data> + + <record id="res_users_form_view" model="ir.ui.view"> + <field name="name">user.form.state</field> + <field name="model">res.users</field> + <field name="inherit_id" ref="base.view_users_form"/> + <field name="arch" type="xml"> + <xpath expr="//sheet" position="before"> + <header> + <field name="state" widget="statusbar"/> + </header> + </xpath> + </field> + </record> + + </data> +</openerp> diff --git a/addons/auth_signup/static/src/css/Makefile b/addons/auth_signup/static/src/css/Makefile new file mode 100644 index 00000000000..d6b4f4b2fc8 --- /dev/null +++ b/addons/auth_signup/static/src/css/Makefile @@ -0,0 +1,3 @@ +base.css: base.sass + sass --trace -t expanded base.sass base.css + diff --git a/addons/auth_signup/static/src/css/base.css b/addons/auth_signup/static/src/css/base.css new file mode 100644 index 00000000000..3f97e7a9301 --- /dev/null +++ b/addons/auth_signup/static/src/css/base.css @@ -0,0 +1,10 @@ +@charset "utf-8"; +.openerp .oe_login .oe_signup_show { + display: none; +} +.openerp .oe_login_signup .oe_signup_show { + display: block !important; +} +.openerp .oe_login_signup .oe_signup_hide { + display: none; +} diff --git a/addons/auth_signup/static/src/css/base.sass b/addons/auth_signup/static/src/css/base.sass new file mode 100644 index 00000000000..f97665d192c --- /dev/null +++ b/addons/auth_signup/static/src/css/base.sass @@ -0,0 +1,13 @@ +@charset "utf-8" + +.openerp + // Regular login form + .oe_login + .oe_signup_show + display: none + // Signup form + .oe_login_signup + .oe_signup_show + display: block !important + .oe_signup_hide + display: none diff --git a/addons/auth_signup/static/src/js/auth_signup.js b/addons/auth_signup/static/src/js/auth_signup.js index 2ab13aa7598..509fafa35cc 100644 --- a/addons/auth_signup/static/src/js/auth_signup.js +++ b/addons/auth_signup/static/src/js/auth_signup.js @@ -5,56 +5,99 @@ openerp.auth_signup = function(instance) { instance.web.Login.include({ start: function() { var self = this; - this.$('a.oe_signup').click(function() { - var dbname = self.$("form [name=db]").val(); - self.do_action({ - type: 'ir.actions.client', - tag: 'auth_signup.signup', - params: {'dbname': dbname}, - target: 'new', - name: 'Sign up' - }); - return true; + var d = this._super(); + + // to switch between the signup and regular login form + this.$('a.oe_signup_signup').click(function() { + self.$el.addClass("oe_login_signup"); + }); + this.$('a.oe_signup_back').click(function() { + self.$el.removeClass("oe_login_signup"); + delete self.params.token; }); - return this._super(); - }, - }); + // if there is an error message in params, show it then forget it + if (self.params.error_message) { + this.show_error(self.params.error_message); + delete self.params.error_message; + } - instance.auth_signup.Signup = instance.web.Widget.extend({ - template: 'auth_signup.signup', - init: function(parent, params) { - this.params = params; - return this._super(); + // in case of a signup, retrieve the user information from the token + if (self.params.db && self.params.token) { + d = self.rpc("/auth_signup/retrieve", {dbname: self.params.db, token: self.params.token}) + .done(self.on_token_loaded) + .fail(self.on_token_failed); + } + return d; }, - start: function() { - var self = this; - this.$('input[name=password_confirmation]').keyup(function() { - var v = $(this).val(); - var $b = self.$('button'); - if (_.isEmpty(v) || self.$('input[name=password]').val() === v) { - $b.removeAttr('disabled'); + on_token_loaded: function(result) { + // select the right the database + this.selected_db = result.db; + this.on_db_loaded({db_list: [result.db]}); + if (result.token) { + // switch to signup mode, set user name and login + this.$el.addClass("oe_login_signup"); + this.$("form input[name=name]").val(result.name).attr("readonly", "readonly"); + if (result.login) { + this.$("form input[name=login]").val(result.login).attr("readonly", "readonly"); } else { - $b.attr('disabled', 'disabled'); + this.$("form input[name=login]").val(result.email); } - }); - - this.$('form').submit(function(ev) { - if(ev) { - ev.preventDefault(); + } else { + // remain in login mode, set login if present + delete this.params.token; + this.$("form input[name=login]").val(result.login || ""); + } + }, + on_token_failed: function(result, ev) { + if (ev) { + ev.preventDefault(); + } + this.show_error("Invalid signup token"); + delete this.params.db; + delete this.params.token; + }, + on_submit: function(ev) { + if (ev) { + ev.preventDefault(); + } + if (this.$el.hasClass("oe_login_signup")) { + // signup user (or reset password) + var db = this.$("form [name=db]").val(); + var name = this.$("form input[name=name]").val(); + var login = this.$("form input[name=login]").val(); + var password = this.$("form input[name=password]").val(); + var confirm_password = this.$("form input[name=confirm_password]").val(); + if (!db) { + this.do_warn("Login", "No database selected !"); + return false; + } else if (!name) { + this.do_warn("Login", "Please enter a name.") + return false; + } else if (!login) { + this.do_warn("Login", "Please enter a username.") + return false; + } else if (!password || !confirm_password) { + this.do_warn("Login", "Please enter a password and confirm it.") + return false; + } else if (password !== confirm_password) { + this.do_warn("Login", "Passwords do not match; please retype them.") + return false; } var params = { - dbname : self.params.dbname, - name: self.$('input[name=name]').val(), - login: self.$('input[name=email]').val(), - password: self.$('input[name=password]').val(), + dbname : db, + token: this.params.token || "", + name: name, + login: login, + password: password, }; var url = "/auth_signup/signup?" + $.param(params); window.location = url; - }); - return this._super(); - } + } else { + // regular login + this._super(ev); + } + }, }); - instance.web.client_actions.add("auth_signup.signup", "instance.auth_signup.Signup"); }; diff --git a/addons/auth_signup/static/src/xml/auth_signup.xml b/addons/auth_signup/static/src/xml/auth_signup.xml index c2eec846d15..e63bd5863b0 100644 --- a/addons/auth_signup/static/src/xml/auth_signup.xml +++ b/addons/auth_signup/static/src/xml/auth_signup.xml @@ -1,28 +1,28 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- vim:fdl=1: ---> +<!-- vim:fdl=1: --> <templates id="template" xml:space="preserve"> -<t t-extend="Login"> - <t t-jquery="form ul:first li:last" t-operation="after"> - <li> - <a class="oe_signup" href="#">Sign Up</a> - </li> + <t t-extend="Login"> + <t t-jquery="form ul:first li:contains('Username')" t-operation="before"> + <li class="oe_signup_show">Name</li> + <li class="oe_signup_show"><input name="name" type="text"/></li> + </t> + <t t-jquery="form ul:first li:contains('Username')" t-operation="replace"> + <li class="oe_signup_hide">Username</li> + <li class="oe_signup_show">Username (Email)</li> + </t> + <t t-jquery="form ul:first li:has(input[name=password])" t-operation="after"> + <li class="oe_signup_show">Confirm Password</li> + <li class="oe_signup_show"><input name="confirm_password" type="password"/></li> + </t> + <t t-jquery="form ul:first li:has(button[name=submit])" t-operation="replace"> + <li class="oe_signup_hide"><button name="submit">Log in</button></li> + <li class="oe_signup_show"><button name="submit">Sign in</button></li> + </t> + <t t-jquery="form ul:first li:last" t-operation="after"> + <li><a class="oe_signup_hide oe_signup_signup" href="#">Sign Up</a></li> + <li><a class="oe_signup_show oe_signup_back" href="#">Back to Login</a></li> + </t> </t> -</t> - -<t t-name="auth_signup.signup"> - <div> - <form> - Name = <input type="text" name="name"/><br/> - Email = <input type="email" name="email"/><br/> - Password = <input type="password" name="password"/><br/> - Confirmation = <input type="password" name="password_confirmation"/><br/> - <button type="submit" disabled="disabled">Signup</button> - </form> - </div> -</t> - - </templates> diff --git a/addons/base_calendar/crm_meeting_view.xml b/addons/base_calendar/crm_meeting_view.xml index 37797a012aa..4df0a8a22b2 100644 --- a/addons/base_calendar/crm_meeting_view.xml +++ b/addons/base_calendar/crm_meeting_view.xml @@ -280,7 +280,7 @@ <field name="arch" type="xml"> <search string="Search Meetings"> <field name="name" string="Meeting" filter_domain="[('name','ilike',self)]"/> - <filter string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter string="My Meetings" help="My Meetings" domain="[('user_id','=',uid)]"/> <field name="user_id"/> diff --git a/addons/crm/crm_lead.py b/addons/crm/crm_lead.py index 7e33f397e8a..48c6491a7c3 100644 --- a/addons/crm/crm_lead.py +++ b/addons/crm/crm_lead.py @@ -26,6 +26,7 @@ from osv import fields, osv import time import tools from tools.translate import _ +from tools import html2plaintext from base.res.res_partner import format_address @@ -812,9 +813,11 @@ class crm_lead(base_stage, format_address, osv.osv): This override updates the document according to the email. """ if custom_values is None: custom_values = {} + + desc = html2plaintext(msg.get('body')) if msg.get('body') else '' custom_values.update({ 'name': msg.get('subject') or _("No Subject"), - 'description': msg.get('body'), + 'description': desc, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'user_id': False, diff --git a/addons/crm/crm_lead_view.xml b/addons/crm/crm_lead_view.xml index f91a8756598..6a152f44b67 100644 --- a/addons/crm/crm_lead_view.xml +++ b/addons/crm/crm_lead_view.xml @@ -356,10 +356,10 @@ <field name="name" string="Lead / Customer" filter_domain="['|','|',('partner_name','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/> <field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike',self)]" /> <field name="create_date"/> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter icon="terp-check" string="New" name="new" help="New Leads" domain="[('state','=','draft')]"/> - <filter icon="terp-camera_test" string="Open" name="open" domain="[('state','=','open')]"/> + <filter icon="terp-camera_test" string="In Progress" name="open" domain="[('state','=','open')]"/> <separator/> <filter string="Unassigned Leads" icon="terp-personal-" domain="[('user_id','=', False)]" help="Unassigned Leads" /> <separator/> @@ -569,10 +569,10 @@ <field name="name" string="Opportunity / Customer" filter_domain="['|','|','|',('partner_id','ilike',self),('partner_name','ilike',self),('email_from','ilike',self),('name', 'ilike', self)]"/> <field name="categ_ids" string="Category" filter_domain="[('categ_ids','ilike', self)]" /> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter icon="terp-check" string="New" help="New Opportunities" name="new" domain="[('state','=','draft')]"/> - <filter icon="terp-camera_test" string="Open" help="Open Opportunities" name="open" domain="[('state','=','open')]"/> + <filter icon="terp-camera_test" string="In Progress" help="Open Opportunities" name="open" domain="[('state','=','open')]"/> <separator/> <filter string="Unassigned Opportunities" icon="terp-personal-" domain="[('user_id','=', False)]" help="Unassigned Opportunities" /> <separator/> @@ -580,9 +580,8 @@ domain="['|', ('section_id.user_id','=',uid), ('section_id.member_ids', 'in', [uid])]" context="{'invisible_section': False}" help="Opportunities that are assigned to either me or one of the sale teams I manage" /> <field name="user_id"/> - <field name="country_id"/> - <field name="partner_id"/> <field name="section_id" context="{'invisible_section': False, 'default_section_id': self}"/> + <field name="partner_id"/> <group expand="0" string="Group By..." colspan="16"> <filter string="Salesperson" icon="terp-personal" domain="[]" context="{'group_by':'user_id'}" /> <filter string="Team" help="Sales Team" icon="terp-personal+" domain="[]" context="{'group_by':'section_id'}"/> diff --git a/addons/crm/res_partner_view.xml b/addons/crm/res_partner_view.xml index 84a0bd4cf49..b7aa334a9c3 100644 --- a/addons/crm/res_partner_view.xml +++ b/addons/crm/res_partner_view.xml @@ -15,31 +15,12 @@ </field> </record> - <record id="view_partners_tree_crm2" model="ir.ui.view"> - <field name="name">view.res.partner.tree.crm.inherited2</field> - <field name="model">res.partner</field> - <field name="inherit_id" ref="base.view_partner_tree"/> - <field eval="18" name="priority"/> - <field name="arch" type="xml"> - <field name="phone" position="after"> - <field name="section_id" completion="1" invisible="context.get('invisible_section', True)"/> - </field> - </field> - </record> - <record id="view_partners_form_crm3" model="ir.ui.view"> <field name="name">view.res.partner.search.crm.inherited3</field> <field name="model">res.partner</field> <field name="inherit_id" ref="base.view_res_partner_filter"/> <field eval="18" name="priority"/> <field name="arch" type="xml"> - <field name="category_id" position="after"> - <field name="section_id" completion="1"/> - <field name="section_id" completion="1" widget="selection" context="{'invisible_section': False}"/> - </field> - <xpath expr="//field[@name='user_id']" position="after"> - <field name="country_id"/> - </xpath> <xpath expr="//group[@string='Group By...']" position="after"> <group string="Display"> <filter string="Show Sales Team" context="{'invisible_section': False}"/> diff --git a/addons/crm_claim/crm_claim.py b/addons/crm_claim/crm_claim.py index cbf3ec9f13a..c88b2fe26af 100644 --- a/addons/crm_claim/crm_claim.py +++ b/addons/crm_claim/crm_claim.py @@ -26,6 +26,7 @@ from osv import fields, osv import time import tools from tools.translate import _ +from tools import html2plaintext CRM_CLAIM_PENDING_STATES = ( crm.AVAILABLE_STATES[2][0], # Cancelled @@ -192,9 +193,10 @@ class crm_claim(base_stage, osv.osv): This override updates the document according to the email. """ if custom_values is None: custom_values = {} + desc = html2plaintext(msg.get('body')) if msg.get('body') else '' custom_values.update({ 'name': msg.get('subject') or _("No Subject"), - 'description': msg.get('body'), + 'description': desc, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), }) diff --git a/addons/crm_helpdesk/crm_helpdesk.py b/addons/crm_helpdesk/crm_helpdesk.py index f36a3b72640..ceffd282aa8 100644 --- a/addons/crm_helpdesk/crm_helpdesk.py +++ b/addons/crm_helpdesk/crm_helpdesk.py @@ -25,6 +25,7 @@ from crm import crm from osv import fields, osv import tools from tools.translate import _ +from tools import html2plaintext CRM_HELPDESK_STATES = ( crm.AVAILABLE_STATES[2][0], # Cancelled @@ -104,9 +105,10 @@ class crm_helpdesk(base_state, base_stage, osv.osv): This override updates the document according to the email. """ if custom_values is None: custom_values = {} + desc = html2plaintext(msg.get('body')) if msg.get('body') else '' custom_values.update({ 'name': msg.get('subject') or _("No Subject"), - 'description': msg.get('body'), + 'description': desc, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'user_id': False, diff --git a/addons/crm_partner_assign/crm_lead_view.xml b/addons/crm_partner_assign/crm_lead_view.xml index cd2e3f08c9a..2cffb6fb899 100644 --- a/addons/crm_partner_assign/crm_lead_view.xml +++ b/addons/crm_partner_assign/crm_lead_view.xml @@ -56,7 +56,7 @@ <filter string="Assigned Partner" icon="terp-personal" domain="[]" context="{'group_by':'partner_assigned_id'}"/> </filter> - <field name="user_id" position="after"> + <field name="partner_id" position="after"> <field name="partner_assigned_id"/> </field> </field> diff --git a/addons/event/event_view.xml b/addons/event/event_view.xml index b46fafe270f..0155846fec3 100644 --- a/addons/event/event_view.xml +++ b/addons/event/event_view.xml @@ -339,7 +339,7 @@ <field name="arch" type="xml"> <search string="Events"> <field name="name" string="Events"/> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter icon="terp-check" string="Unconfirmed" name="draft" domain="[('state','=','draft')]" help="Events in New state"/> <filter icon="terp-camera_test" string="Confirmed" domain="[('state','=','confirm')]" help="Confirmed events"/> @@ -529,7 +529,7 @@ <field name="arch" type="xml"> <search string="Event Registration"> <field name="name" string="Participant" filter_domain="['|','|','|',('name','ilike',self),('partner_id','ilike',self),('email','ilike',self),('origin','ilike',self)]"/> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter icon="terp-check" string="New" name="draft" domain="[('state','=','draft')]" help="Registrations in unconfirmed state"/> <filter icon="terp-camera_test" string="Confirmed" domain="[('state','=','open')]" help="Confirmed registrations"/> diff --git a/addons/hr/hr_view.xml b/addons/hr/hr_view.xml index 69567902763..89c327f4d13 100644 --- a/addons/hr/hr_view.xml +++ b/addons/hr/hr_view.xml @@ -112,8 +112,6 @@ <search string="Employees"> <field name="name" string="Employees"/> <field name="department_id" /> - <field name="job_id"/> - <field name="parent_id"/> <field name="category_ids"/> <group expand="0" string="Group By..."> <filter string="Manager" icon="terp-personal" domain="[]" context="{'group_by':'parent_id'}"/> diff --git a/addons/hr_contract/i18n/mk.po b/addons/hr_contract/i18n/mk.po new file mode 100644 index 00000000000..2a4011c9e0d --- /dev/null +++ b/addons/hr_contract/i18n/mk.po @@ -0,0 +1,271 @@ +# Macedonian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" +"POT-Creation-Date: 2012-02-08 00:36+0000\n" +"PO-Revision-Date: 2012-10-09 08:00+0000\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: Macedonian <mk@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n" +"X-Generator: Launchpad (build 16112)\n" + +#. module: hr_contract +#: field:hr.contract,wage:0 +msgid "Wage" +msgstr "Плата" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Information" +msgstr "Информации" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Trial Period" +msgstr "Пробен Период" + +#. module: hr_contract +#: field:hr.contract,trial_date_start:0 +msgid "Trial Start Date" +msgstr "Пробен Почетен Датум" + +#. module: hr_contract +#: view:hr.employee:0 +msgid "Medical Examination" +msgstr "Систематски Преглед" + +#. module: hr_contract +#: field:hr.employee,vehicle:0 +msgid "Company Vehicle" +msgstr "Службено Возило" + +#. module: hr_contract +#: view:hr.employee:0 +msgid "Miscellaneous" +msgstr "Разно" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Current" +msgstr "Тековен" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Group By..." +msgstr "Групирај По..." + +#. module: hr_contract +#: field:hr.contract,department_id:0 +msgid "Department" +msgstr "Сектор" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Overpassed" +msgstr "Изминат" + +#. module: hr_contract +#: view:hr.contract:0 +#: field:hr.contract,employee_id:0 +#: model:ir.model,name:hr_contract.model_hr_employee +msgid "Employee" +msgstr "Вработен" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Search Contract" +msgstr "Пребарај Договор" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Contracts in progress" +msgstr "Договори во тек" + +#. module: hr_contract +#: field:hr.employee,vehicle_distance:0 +msgid "Home-Work Distance" +msgstr "Дома-Работа Одалеченост" + +#. module: hr_contract +#: view:hr.contract:0 +#: field:hr.employee,contract_ids:0 +#: model:ir.actions.act_window,name:hr_contract.act_hr_employee_2_hr_contract +#: model:ir.actions.act_window,name:hr_contract.action_hr_contract +#: model:ir.ui.menu,name:hr_contract.hr_menu_contract +msgid "Contracts" +msgstr "Договори" + +#. module: hr_contract +#: view:hr.employee:0 +msgid "Personal Info" +msgstr "Лични информации" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Contracts whose end date already passed" +msgstr "Договори чии што краен рок е веќе поминат" + +#. module: hr_contract +#: help:hr.employee,contract_id:0 +msgid "Latest contract of the employee" +msgstr "Последен договор на вработениот" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Job" +msgstr "Работно Место" + +#. module: hr_contract +#: view:hr.contract:0 +#: field:hr.contract,advantages:0 +msgid "Advantages" +msgstr "Придобивки" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Valid for" +msgstr "Валиден за" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Work Permit" +msgstr "Дозвола за Работа" + +#. module: hr_contract +#: field:hr.employee,children:0 +msgid "Number of Children" +msgstr "Број на Деца" + +#. module: hr_contract +#: model:ir.actions.act_window,name:hr_contract.action_hr_contract_type +#: model:ir.ui.menu,name:hr_contract.hr_menu_contract_type +msgid "Contract Types" +msgstr "Типови на Договор" + +#. module: hr_contract +#: constraint:hr.employee:0 +msgid "Error ! You cannot create recursive Hierarchy of Employees." +msgstr "Грешка! Не можете да креирате рекурзивна хиерархија на вработените." + +#. module: hr_contract +#: field:hr.contract,date_end:0 +msgid "End Date" +msgstr "Краен датум" + +#. module: hr_contract +#: help:hr.contract,wage:0 +msgid "Basic Salary of the employee" +msgstr "Основна Плата на вработениот" + +#. module: hr_contract +#: field:hr.contract,name:0 +msgid "Contract Reference" +msgstr "Референца" + +#. module: hr_contract +#: help:hr.employee,vehicle_distance:0 +msgid "In kilometers" +msgstr "во километри" + +#. module: hr_contract +#: view:hr.contract:0 +#: field:hr.contract,notes:0 +msgid "Notes" +msgstr "Забелешки" + +#. module: hr_contract +#: field:hr.contract,permit_no:0 +msgid "Work Permit No" +msgstr "Број на Дозвола за Работа" + +#. module: hr_contract +#: view:hr.contract:0 +#: field:hr.employee,contract_id:0 +#: model:ir.model,name:hr_contract.model_hr_contract +#: model:ir.ui.menu,name:hr_contract.next_id_56 +msgid "Contract" +msgstr "Договор" + +#. module: hr_contract +#: view:hr.contract:0 +#: field:hr.contract,type_id:0 +#: view:hr.contract.type:0 +#: field:hr.contract.type,name:0 +#: model:ir.model,name:hr_contract.model_hr_contract_type +msgid "Contract Type" +msgstr "Тип на Договор" + +#. module: hr_contract +#: view:hr.contract:0 +#: field:hr.contract,working_hours:0 +msgid "Working Schedule" +msgstr "Распоред за Работа" + +#. module: hr_contract +#: view:hr.employee:0 +msgid "Job Info" +msgstr "Информации за Работата" + +#. module: hr_contract +#: field:hr.contract,visa_expire:0 +msgid "Visa Expire Date" +msgstr "Виза Датум на Истекување" + +#. module: hr_contract +#: field:hr.contract,job_id:0 +msgid "Job Title" +msgstr "Звање" + +#. module: hr_contract +#: field:hr.employee,manager:0 +msgid "Is a Manager" +msgstr "Менаџер" + +#. module: hr_contract +#: field:hr.contract,date_start:0 +msgid "Start Date" +msgstr "Почетен датум" + +#. module: hr_contract +#: constraint:hr.contract:0 +msgid "Error! contract start-date must be lower then contract end-date." +msgstr "" +"Грешка! Почетниот датум на договорот мора да биде помал од крајниот датум." + +#. module: hr_contract +#: field:hr.contract,visa_no:0 +msgid "Visa No" +msgstr "Број на Виза" + +#. module: hr_contract +#: field:hr.employee,place_of_birth:0 +msgid "Place of Birth" +msgstr "Место на раѓање" + +#. module: hr_contract +#: view:hr.contract:0 +msgid "Duration" +msgstr "Траење" + +#. module: hr_contract +#: field:hr.employee,medic_exam:0 +msgid "Medical Examination Date" +msgstr "Датум за Систематски Преглед" + +#. module: hr_contract +#: field:hr.contract,trial_date_end:0 +msgid "Trial End Date" +msgstr "Пробен Краен Датум" + +#. module: hr_contract +#: view:hr.contract.type:0 +msgid "Search Contract Type" +msgstr "Пребарај Тип на Договор" diff --git a/addons/hr_expense/hr_expense_view.xml b/addons/hr_expense/hr_expense_view.xml index 388e5262f6e..67d629eee6a 100644 --- a/addons/hr_expense/hr_expense_view.xml +++ b/addons/hr_expense/hr_expense_view.xml @@ -155,11 +155,12 @@ <field name="name" string="Expenses"/> <field name="date"/> <filter icon="terp-document-new" domain="[('state','=','draft')]" string="New" help="New Expense"/> - <filter icon="terp-camera_test" domain="[('state','=','confirm')]" string="To Approve" help="Confirmed Expense"/> + <filter icon="terp-camera_test" domain="[('state','=','confirm')]" string="To Approve" help="Confirmed Expenses"/> <filter icon="terp-dolar" domain="[('state','=','accepted')]" string="To Pay" help="Expenses to Invoice"/> + <separator/> + <filter domain="[('user_id', '=', uid)]" string="My Expenses"/> <field name="employee_id"/> <field name="department_id" string="Department" context="{'invisible_department': False}"/> - <field name="user_id" string="User"/> <group expand="0" string="Group By..."> <filter string="Employee" icon="terp-personal" domain="[]" context="{'group_by':'employee_id'}"/> <filter string="Department" icon="terp-personal+" domain="[]" context="{'group_by':'department_id'}"/> diff --git a/addons/hr_holidays/i18n/mk.po b/addons/hr_holidays/i18n/mk.po new file mode 100644 index 00000000000..2694317ebe9 --- /dev/null +++ b/addons/hr_holidays/i18n/mk.po @@ -0,0 +1,864 @@ +# Macedonian translation for openobject-addons +# Copyright (c) 2012 Rosetta Contributors and Canonical Ltd 2012 +# This file is distributed under the same license as the openobject-addons package. +# FIRST AUTHOR <EMAIL@ADDRESS>, 2012. +# +msgid "" +msgstr "" +"Project-Id-Version: openobject-addons\n" +"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" +"POT-Creation-Date: 2012-02-08 01:37+0100\n" +"PO-Revision-Date: 2012-10-09 08:43+0000\n" +"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"Language-Team: Macedonian <mk@li.org>\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Launchpad-Export-Date: 2012-10-10 04:44+0000\n" +"X-Generator: Launchpad (build 16112)\n" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Blue" +msgstr "Сина" + +#. module: hr_holidays +#: field:hr.holidays,holiday_type:0 +msgid "Allocation Type" +msgstr "Тип на Алокација" + +#. module: hr_holidays +#: selection:hr.employee,current_leave_state:0 +#: selection:hr.holidays,state:0 +msgid "Waiting Second Approval" +msgstr "Се чека на второ одобрување" + +#. module: hr_holidays +#: help:hr.holidays.status,remaining_leaves:0 +msgid "Maximum Leaves Allowed - Leaves Already Taken" +msgstr "Мкасимален број на дозволени отсуства - Отсуствата се веќе земени" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Leaves Management" +msgstr "Менаџирање на Отсуства" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Group By..." +msgstr "Групирај По..." + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Allocation Mode" +msgstr "Мод на Алокација" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: field:hr.holidays,department_id:0 +msgid "Department" +msgstr "Сектор" + +#. module: hr_holidays +#: model:ir.actions.act_window,name:hr_holidays.request_approve_holidays +msgid "Requests Approve" +msgstr "Одобри Барања" + +#. module: hr_holidays +#: selection:hr.employee,current_leave_state:0 +#: selection:hr.holidays,state:0 +msgid "Refused" +msgstr "Одбиени" + +#. module: hr_holidays +#: help:hr.holidays,category_id:0 +msgid "Category of Employee" +msgstr "Категорија на Вработен" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid " Month-1" +msgstr " Месец-1" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Brown" +msgstr "Кафена" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Remaining Days" +msgstr "Останати Денови" + +#. module: hr_holidays +#: selection:hr.holidays,holiday_type:0 +msgid "By Employee" +msgstr "По Вработен" + +#. module: hr_holidays +#: help:hr.holidays,employee_id:0 +msgid "" +"Leave Manager can let this field empty if this leave request/allocation is " +"for every employee" +msgstr "" +"Менаџерот за отсуства може да го остави ова поле празно ако барањето за " +"отсуство/преместување е за секој вработен" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Set to Draft" +msgstr "Подеси на нацрт" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Light Cyan" +msgstr "Светла Цијан" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Light Green" +msgstr "Светло зелено" + +#. module: hr_holidays +#: field:hr.employee,current_leave_id:0 +msgid "Current Leave Type" +msgstr "Тековен Тип на Отсуство" + +#. module: hr_holidays +#: model:ir.actions.act_window,help:hr_holidays.open_ask_holidays +msgid "" +"Leave requests can be recorded by employees and validated by their managers. " +"Once a leave request is validated, it appears automatically in the agenda of " +"the employee. You can define several allowance types (paid holidays, " +"sickness, etc.) and manage allowances per type." +msgstr "" + +#. module: hr_holidays +#: model:ir.actions.report.xml,name:hr_holidays.report_holidays_summary +msgid "Summary Of Leaves" +msgstr "Збир на Отсуства" + +#. module: hr_holidays +#: selection:hr.employee,current_leave_state:0 +#: view:hr.holidays:0 +#: selection:hr.holidays,state:0 +msgid "Approved" +msgstr "Одобрено" + +#. module: hr_holidays +#: field:hr.employee,last_login:0 +msgid "Latest Connection" +msgstr "Последна Конекција" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Refuse" +msgstr "Одбиј" + +#. module: hr_holidays +#: code:addons/hr_holidays/hr_holidays.py:344 +#, python-format +msgid "" +"You cannot validate leaves for employee %s: too few remaining days (%s)." +msgstr "" +"Не можете да валидирате отсуства за вработениот %s: премалку преостанати " +"денови (%s)." + +#. module: hr_holidays +#: help:hr.holidays,state:0 +msgid "" +"The state is set to 'Draft', when a holiday request is created. \n" +"The state is 'Waiting Approval', when holiday request is confirmed by user. " +" \n" +"The state is 'Refused', when holiday request is refused by manager. " +" \n" +"The state is 'Approved', when holiday request is approved by manager." +msgstr "" + +#. module: hr_holidays +#: view:board.board:0 +#: model:ir.actions.act_window,name:hr_holidays.act_hr_employee_holiday_request +#: model:ir.ui.menu,name:hr_holidays.menu_hr_reporting_holidays +#: model:ir.ui.menu,name:hr_holidays.menu_open_ask_holidays +msgid "Leaves" +msgstr "Отсуства" + +#. module: hr_holidays +#: model:ir.model,name:hr_holidays.model_hr_holidays +msgid "Leave" +msgstr "" + +#. module: hr_holidays +#: model:ir.ui.menu,name:hr_holidays.menu_request_approve_holidays +msgid "Leave Requests to Approve" +msgstr "Барања за отсуства за одобрување" + +#. module: hr_holidays +#: model:ir.ui.menu,name:hr_holidays.menu_account_central_journal +msgid "Leaves by Department" +msgstr "Отсуства по Сектор" + +#. module: hr_holidays +#: selection:hr.employee,current_leave_state:0 +#: selection:hr.holidays,state:0 +msgid "Cancelled" +msgstr "Откажано" + +#. module: hr_holidays +#: help:hr.holidays,type:0 +msgid "" +"Choose 'Leave Request' if someone wants to take an off-day. \n" +"Choose 'Allocation Request' if you want to increase the number of leaves " +"available for someone" +msgstr "" + +#. module: hr_holidays +#: help:hr.employee,remaining_leaves:0 +msgid "" +"Total number of legal leaves allocated to this employee, change this value " +"to create allocation/leave requests." +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.status:0 +msgid "Validation" +msgstr "" + +#. module: hr_holidays +#: code:addons/hr_holidays/hr_holidays.py:377 +#, python-format +msgid "Warning !" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.status,color_name:0 +msgid "Color in Report" +msgstr "" + +#. module: hr_holidays +#: model:ir.model,name:hr_holidays.model_hr_holidays_summary_employee +msgid "HR Holidays Summary Report By Employee" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays,manager_id:0 +msgid "This area is automatically filled by the user who validate the leave" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,holiday_status_id:0 +#: field:hr.holidays.remaining.leaves.user,leave_type:0 +#: view:hr.holidays.status:0 +#: field:hr.holidays.status,name:0 +#: field:hr.holidays.summary.dept,holiday_type:0 +#: model:ir.actions.act_window,name:hr_holidays.open_view_holiday_status +#: model:ir.model,name:hr_holidays.model_hr_holidays_status +#: model:ir.ui.menu,name:hr_holidays.menu_open_view_holiday_status +msgid "Leave Type" +msgstr "" + +#. module: hr_holidays +#: code:addons/hr_holidays/hr_holidays.py:199 +#: code:addons/hr_holidays/hr_holidays.py:344 +#, python-format +msgid "Warning!" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Magenta" +msgstr "" + +#. module: hr_holidays +#: code:addons/hr_holidays/wizard/hr_holidays_summary_department.py:44 +#, python-format +msgid "You have to select at least 1 Department. And try again" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.summary.dept,holiday_type:0 +#: selection:hr.holidays.summary.employee,holiday_type:0 +msgid "Confirmed" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.summary.dept,date_from:0 +#: field:hr.holidays.summary.employee,date_from:0 +msgid "From" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Confirm" +msgstr "" + +#. module: hr_holidays +#: code:addons/hr_holidays/hr_holidays.py:384 +#, python-format +msgid "Leave Request for %s" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.status,remaining_leaves:0 +msgid "Remaining Leaves" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: field:hr.holidays,state:0 +msgid "State" +msgstr "" + +#. module: hr_holidays +#: model:ir.model,name:hr_holidays.model_hr_holidays_remaining_leaves_user +msgid "Total holidays by type" +msgstr "" + +#. module: hr_holidays +#: view:hr.employee:0 +#: view:hr.holidays:0 +#: field:hr.holidays,employee_id:0 +#: field:hr.holidays.remaining.leaves.user,name:0 +#: model:ir.model,name:hr_holidays.model_hr_employee +msgid "Employee" +msgstr "" + +#. module: hr_holidays +#: selection:hr.employee,current_leave_state:0 +#: selection:hr.holidays,state:0 +msgid "New" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Type" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Red" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.remaining.leaves.user:0 +msgid "Leaves by Type" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Light Salmon" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Wheat" +msgstr "" + +#. module: hr_holidays +#: constraint:resource.calendar.leaves:0 +msgid "Error! leave start-date must be lower then leave end-date." +msgstr "" + +#. module: hr_holidays +#: code:addons/hr_holidays/hr_holidays.py:382 +#, python-format +msgid "Allocation for %s" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: field:hr.holidays,number_of_days:0 +#: field:hr.holidays,number_of_days_temp:0 +msgid "Number of Days" +msgstr "" + +#. module: hr_holidays +#: code:addons/hr_holidays/hr_holidays.py:377 +#, python-format +msgid "" +"The feature behind the field 'Remaining Legal Leaves' can only be used when " +"there is only one leave type with the option 'Allow to Override Limit' " +"unchecked. (%s Found). Otherwise, the update is ambiguous as we cannot " +"decide on which leave type the update has to be done. \n" +"You may prefer to use the classic menus 'Leave Requests' and 'Allocation " +"Requests' located in 'Human Resources \\ Leaves' to manage the leave days of " +"the employees if the configuration does not allow to use this field." +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.status:0 +msgid "Search Leave Type" +msgstr "" + +#. module: hr_holidays +#: sql_constraint:hr.holidays:0 +msgid "You have to select an employee or a category" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays.status,double_validation:0 +msgid "" +"When selected, the Allocation/Leave Requests for this type require a second " +"validation to be approved." +msgstr "" + +#. module: hr_holidays +#: selection:hr.employee,current_leave_state:0 +#: selection:hr.holidays,state:0 +msgid "Waiting Approval" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.summary.employee,emp:0 +msgid "Employee(s)" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays.status,categ_id:0 +msgid "" +"If you set a meeting type, OpenERP will create a meeting in the calendar " +"once a leave is validated." +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,linked_request_ids:0 +msgid "Linked Requests" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,parent_id:0 +msgid "Parent" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Lavender" +msgstr "" + +#. module: hr_holidays +#: xsl:holidays.summary:0 +#: view:hr.holidays:0 +msgid "Month" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: model:ir.actions.act_window,name:hr_holidays.open_ask_holidays +#: model:ir.ui.menu,name:hr_holidays.menu_open_ask_holidays_new +msgid "Leave Requests" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.status,limit:0 +msgid "Allow to Override Limit" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.summary.employee:0 +#: model:ir.actions.act_window,name:hr_holidays.action_hr_holidays_summary_employee +msgid "Employee's Holidays" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: field:hr.holidays,category_id:0 +msgid "Category" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays.status,max_leaves:0 +msgid "" +"This value is given by the sum of all holidays requests with a positive " +"value." +msgstr "" + +#. module: hr_holidays +#: view:board.board:0 +msgid "All Employee Leaves" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Light Coral" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.summary.dept:0 +#: model:ir.actions.act_window,name:hr_holidays.action_hr_holidays_summary_dept +msgid "Holidays by Department" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Black" +msgstr "" + +#. module: hr_holidays +#: model:ir.actions.act_window,name:hr_holidays.hr_holidays_leaves_assign_legal +msgid "Allocate Leaves for Employees" +msgstr "" + +#. module: hr_holidays +#: field:resource.calendar.leaves,holiday_id:0 +msgid "Holiday" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,case_id:0 +#: field:hr.holidays.status,categ_id:0 +msgid "Meeting" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Ivory" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.summary.dept,holiday_type:0 +#: selection:hr.holidays.summary.employee,holiday_type:0 +msgid "Both Validated and Confirmed" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.status,leaves_taken:0 +msgid "Leaves Already Taken" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,user_id:0 +#: field:hr.holidays.remaining.leaves.user,user_id:0 +msgid "User" +msgstr "" + +#. module: hr_holidays +#: sql_constraint:hr.holidays:0 +msgid "The start date must be before the end date !" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.status,active:0 +msgid "Active" +msgstr "" + +#. module: hr_holidays +#: constraint:hr.employee:0 +msgid "Error ! You cannot create recursive Hierarchy of Employees." +msgstr "" + +#. module: hr_holidays +#: view:hr.employee:0 +#: field:hr.employee,remaining_leaves:0 +msgid "Remaining Legal Leaves" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,manager_id:0 +msgid "First Approval" +msgstr "" + +#. module: hr_holidays +#: model:hr.holidays.status,name:hr_holidays.holiday_status_unpaid +msgid "Unpaid" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: model:ir.actions.act_window,name:hr_holidays.open_company_allocation +#: model:ir.ui.menu,name:hr_holidays.menu_open_company_allocation +msgid "Leaves Summary" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Holidays during last month" +msgstr "" + +#. module: hr_holidays +#: code:addons/hr_holidays/wizard/hr_holidays_summary_department.py:44 +#, python-format +msgid "Error" +msgstr "" + +#. module: hr_holidays +#: view:hr.employee:0 +msgid "Assign Leaves" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Light Blue" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "My Department Leaves" +msgstr "" + +#. module: hr_holidays +#: field:hr.employee,current_leave_state:0 +msgid "Current Leave Status" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,type:0 +msgid "Request Type" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays.status,active:0 +msgid "" +"If the active field is set to false, it will allow you to hide the leave " +"type without removing it." +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.status:0 +msgid "Misc" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "General" +msgstr "" + +#. module: hr_holidays +#: model:hr.holidays.status,name:hr_holidays.holiday_status_comp +msgid "Compensatory Days" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: field:hr.holidays,notes:0 +msgid "Reasons" +msgstr "" + +#. module: hr_holidays +#: model:ir.actions.act_window,name:hr_holidays.action_hr_available_holidays_report +#: model:ir.ui.menu,name:hr_holidays.menu_hr_available_holidays_report_tree +msgid "Leaves Analysis" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.summary.dept:0 +#: view:hr.holidays.summary.employee:0 +msgid "Cancel" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays.status,color_name:0 +msgid "" +"This color will be used in the leaves summary located in Reporting\\Leaves " +"by Departement" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: selection:hr.holidays.summary.dept,holiday_type:0 +#: selection:hr.holidays.summary.employee,holiday_type:0 +msgid "Validated" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: selection:hr.holidays,type:0 +msgid "Allocation Request" +msgstr "" + +#. module: hr_holidays +#: model:ir.model,name:hr_holidays.model_resource_calendar_leaves +msgid "Leave Detail" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,double_validation:0 +#: field:hr.holidays.status,double_validation:0 +msgid "Apply Double Validation" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.summary.dept:0 +#: view:hr.holidays.summary.employee:0 +msgid "Print" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays.status:0 +msgid "Details" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: model:ir.actions.act_window,name:hr_holidays.action_hr_holidays_leaves_by_month +msgid "My Leaves" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays,holiday_type:0 +msgid "By Employee Category" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: selection:hr.holidays,type:0 +msgid "Leave Request" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,name:0 +msgid "Description" +msgstr "" + +#. module: hr_holidays +#: model:hr.holidays.status,name:hr_holidays.holiday_status_cl +msgid "Legal Leaves" +msgstr "" + +#. module: hr_holidays +#: sql_constraint:hr.holidays:0 +msgid "The number of days must be greater than 0 !" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays,holiday_type:0 +msgid "" +"By Employee: Allocation/Request for individual Employee, By Employee " +"Category: Allocation/Request for group of employees in category" +msgstr "" + +#. module: hr_holidays +#: code:addons/hr_holidays/hr_holidays.py:199 +#, python-format +msgid "You cannot delete a leave which is not in draft state !" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Search Leave" +msgstr "Пребарај Отсуство" + +#. module: hr_holidays +#: field:hr.holidays.summary.employee,holiday_type:0 +msgid "Select Holiday Type" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.remaining.leaves.user,no_of_leaves:0 +msgid "Remaining leaves" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.summary.dept,depts:0 +msgid "Department(s)" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,manager_id2:0 +msgid "Second Approval" +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays,date_to:0 +msgid "End Date" +msgstr "" + +#. module: hr_holidays +#: model:hr.holidays.status,name:hr_holidays.holiday_status_sl +msgid "Sick Leaves" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays.status,limit:0 +msgid "" +"If you select this checkbox, the system allows the employees to take more " +"leaves than the available ones for this type." +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays.status,leaves_taken:0 +msgid "" +"This value is given by the sum of all holidays requests with a negative " +"value." +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Violet" +msgstr "" + +#. module: hr_holidays +#: model:ir.actions.act_window,help:hr_holidays.hr_holidays_leaves_assign_legal +msgid "" +"You can assign remaining Legal Leaves for each employee, OpenERP will " +"automatically create and validate allocation requests." +msgstr "" + +#. module: hr_holidays +#: field:hr.holidays.status,max_leaves:0 +msgid "Maximum Allowed" +msgstr "" + +#. module: hr_holidays +#: help:hr.holidays,manager_id2:0 +msgid "" +"This area is automaticly filled by the user who validate the leave with " +"second level (If Leave type need second validation)" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Mode" +msgstr "" + +#. module: hr_holidays +#: model:ir.model,name:hr_holidays.model_hr_holidays_summary_dept +msgid "HR Holidays Summary Report By Department" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Approve" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: field:hr.holidays,date_from:0 +msgid "Start Date" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +#: model:ir.actions.act_window,name:hr_holidays.open_allocation_holidays +#: model:ir.ui.menu,name:hr_holidays.menu_open_allocation_holidays +msgid "Allocation Requests" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Light Yellow" +msgstr "" + +#. module: hr_holidays +#: selection:hr.holidays.status,color_name:0 +msgid "Light Pink" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Manager" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "To Confirm" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "Year" +msgstr "" + +#. module: hr_holidays +#: view:hr.holidays:0 +msgid "To Approve" +msgstr "" diff --git a/addons/hr_recruitment/hr_recruitment.py b/addons/hr_recruitment/hr_recruitment.py index 31959d7e9c7..ccfcae0d26e 100644 --- a/addons/hr_recruitment/hr_recruitment.py +++ b/addons/hr_recruitment/hr_recruitment.py @@ -26,6 +26,7 @@ from base_status.base_stage import base_stage from datetime import datetime from osv import fields, osv from tools.translate import _ +from tools import html2plaintext AVAILABLE_STATES = [ ('draft', 'New'), @@ -327,9 +328,10 @@ class hr_applicant(base_stage, osv.Model): This override updates the document according to the email. """ if custom_values is None: custom_values = {} + desc = html2plaintext(msg.get('body')) if msg.get('body') else '' custom_values.update({ 'name': msg.get('subject') or _("No Subject"), - 'description': msg.get('body'), + 'description': desc, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'user_id': False, diff --git a/addons/hr_recruitment/hr_recruitment_view.xml b/addons/hr_recruitment/hr_recruitment_view.xml index 08e5f31a2d2..d48086f5663 100644 --- a/addons/hr_recruitment/hr_recruitment_view.xml +++ b/addons/hr_recruitment/hr_recruitment_view.xml @@ -208,7 +208,7 @@ <field name="arch" type="xml"> <search string="Search Jobs"> <field name="partner_name" filter_domain="['|','|',('name','ilike',self),('partner_name','ilike',self),('email_from','ilike',self)]" string="Subject / Applicant"/> - <filter string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter string="New" domain="[('state','=','draft')]" help="All Initial Jobs"/> <filter string="In Progress" domain="[('state','=','open')]" help="Open Jobs"/> diff --git a/addons/hr_timesheet_sheet/__openerp__.py b/addons/hr_timesheet_sheet/__openerp__.py index 9aab412d425..fd025491588 100644 --- a/addons/hr_timesheet_sheet/__openerp__.py +++ b/addons/hr_timesheet_sheet/__openerp__.py @@ -65,5 +65,8 @@ The validation can be configured in the company: 'installable': True, 'auto_install': False, 'application': True, + 'js': ['static/src/js/timesheet.js',], + 'css': ['static/src/css/timesheet.css',], + 'qweb': ['static/src/xml/timesheet.xml',], } # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py index cd58f52f959..96cb15ddce1 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet.py +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet.py @@ -27,80 +27,6 @@ from osv import fields, osv from tools.translate import _ import netsvc -class one2many_mod2(fields.one2many): - def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None): - if context is None: - context = {} - - if values is None: - values = {} - - # res6 = {id: date_current, ...} - res6 = dict([(rec['id'], rec['date_current']) - for rec in obj.read(cr, user, ids, ['date_current'], context=context)]) - - dom = [] - for c, id in enumerate(ids): - if id in res6: - if c: # skip first - dom.insert(0 ,'|') - dom.append('&') - dom.append('&') - dom.append(('name', '>=', res6[id])) - dom.append(('name', '<=', res6[id])) - dom.append(('sheet_id', '=', id)) - - ids2 = obj.pool.get(self._obj).search(cr, user, dom, limit=self._limit) - - res = {} - for i in ids: - res[i] = [] - - for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, [self._fields_id], context=context, load='_classic_read'): - if r[self._fields_id]: - res[r[self._fields_id][0]].append(r['id']) - return res - - def set(self, cr, obj, id, field, values, user=None, context=None): - if context is None: - context = {} - - context = context.copy() - context['sheet_id'] = id - return super(one2many_mod2, self).set(cr, obj, id, field, values, user=user, context=context) - - -class one2many_mod(fields.one2many): - def get(self, cr, obj, ids, name, user=None, offset=0, context=None, values=None): - if context is None: - context = {} - - if values is None: - values = {} - - - res5 = obj.read(cr, user, ids, ['date_current'], context=context) - res6 = {} - for r in res5: - res6[r['id']] = r['date_current'] - - ids2 = [] - for id in ids: - dom = [] - if id in res6: - dom = [('date', '=', res6[id]), ('sheet_id', '=', id)] - ids2.extend(obj.pool.get(self._obj).search(cr, user, - dom, limit=self._limit)) - res = {} - for i in ids: - res[i] = [] - for r in obj.pool.get(self._obj)._read_flat(cr, user, ids2, - [self._fields_id], context=context, load='_classic_read'): - if r[self._fields_id]: - res[r[self._fields_id][0]].append(r['id']) - - return res - class hr_timesheet_sheet(osv.osv): _name = "hr_timesheet_sheet.sheet" _inherit = "mail.thread" @@ -111,8 +37,7 @@ class hr_timesheet_sheet(osv.osv): def _total_attendances(self, cr, uid, ids, name, args, context=None): """ Get the total attendance for the timesheets Returns a dict like : - {id: {'date_current': '2011-06-17', - 'total_per_day': {day: timedelta, ...}, + {id: {'total_per_day': {day: timedelta, ...}, }, ... } @@ -122,7 +47,6 @@ class hr_timesheet_sheet(osv.osv): res = {} for sheet_id in ids: sheet = self.browse(cr, uid, sheet_id, context=context) - date_current = sheet.date_current # field attendances_ids of hr_timesheet_sheet.sheet only # returns attendances of timesheet's current date attendance_ids = attendance_obj.search(cr, uid, [('sheet_id', '=', sheet_id)], context=context) @@ -143,20 +67,7 @@ class hr_timesheet_sheet(osv.osv): else: total_attendance[day] += attendance_interval - # if the delta is negative, it means that a sign out is missing - # in a such case, we want to have the time to the end of the day - # for a past date, and the time to now for the current date - if total_attendance[day] < timedelta(0): - if day == date_current: - now = datetime.now() - total_attendance[day] += timedelta(hours=now.hour, - minutes=now.minute, - seconds=now.second) - else: - total_attendance[day] += timedelta(days=1) - - res[sheet_id] = {'date_current': date_current, - 'total_per_day': total_attendance} + res[sheet_id] = {'total_per_day': total_attendance} return res def _total_timesheet(self, cr, uid, ids, name, args, context=None): @@ -210,24 +121,16 @@ class hr_timesheet_sheet(osv.osv): all_attendances_sheet = all_timesheet_attendances[id] - date_current = all_attendances_sheet['date_current'] total_attendances_sheet = all_attendances_sheet['total_per_day'] total_attendances_all_days = sum_all_days(total_attendances_sheet) - total_attendances_day = total_attendances_sheet.get(date_current, timedelta(seconds=0)) total_timesheets_sheet = all_timesheet_lines[id] total_timesheets_all_days = sum_all_days(total_timesheets_sheet) - total_timesheets_day = total_timesheets_sheet.get(date_current, timedelta(seconds=0)) total_difference_all_days = total_attendances_all_days - total_timesheets_all_days - total_difference_day = total_attendances_day - total_timesheets_day res[id]['total_attendance'] = timedelta_to_hours(total_attendances_all_days) res[id]['total_timesheet'] = timedelta_to_hours(total_timesheets_all_days) res[id]['total_difference'] = timedelta_to_hours(total_difference_all_days) - - res[id]['total_attendance_day'] = timedelta_to_hours(total_attendances_day) - res[id]['total_timesheet_day'] = timedelta_to_hours(total_timesheets_day) - res[id]['total_difference_day'] = timedelta_to_hours(total_difference_day) return res def check_employee_attendance_state(self, cr, uid, sheet_id, context=None): @@ -277,44 +180,6 @@ class hr_timesheet_sheet(osv.osv): raise osv.except_osv(_('Warning!'), _('Please verify that the total difference of the sheet is lower than %.2f.') %(di,)) return True - def date_today(self, cr, uid, ids, context=None): - for sheet in self.browse(cr, uid, ids, context=context): - if datetime.today() <= datetime.strptime(sheet.date_from, '%Y-%m-%d'): - self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,}, context=context) - elif datetime.now() >= datetime.strptime(sheet.date_to, '%Y-%m-%d'): - self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,}, context=context) - else: - self.write(cr, uid, [sheet.id], {'date_current': time.strftime('%Y-%m-%d')}, context=context) - return True - - def date_previous(self, cr, uid, ids, context=None): - for sheet in self.browse(cr, uid, ids, context=context): - if datetime.strptime(sheet.date_current, '%Y-%m-%d') <= datetime.strptime(sheet.date_from, '%Y-%m-%d'): - self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,}, context=context) - else: - self.write(cr, uid, [sheet.id], { - 'date_current': (datetime.strptime(sheet.date_current, '%Y-%m-%d') + relativedelta(days=-1)).strftime('%Y-%m-%d'), - }, context=context) - return True - - def date_next(self, cr, uid, ids, context=None): - for sheet in self.browse(cr, uid, ids, context=context): - if datetime.strptime(sheet.date_current, '%Y-%m-%d') >= datetime.strptime(sheet.date_to, '%Y-%m-%d'): - self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,}, context=context) - else: - self.write(cr, uid, [sheet.id], { - 'date_current': (datetime.strptime(sheet.date_current, '%Y-%m-%d') + relativedelta(days=1)).strftime('%Y-%m-%d'), - }, context=context) - return True - - def button_dummy(self, cr, uid, ids, context=None): - for sheet in self.browse(cr, uid, ids, context=context): - if datetime.strptime(sheet.date_current, '%Y-%m-%d') <= datetime.strptime(sheet.date_from, '%Y-%m-%d'): - self.write(cr, uid, [sheet.id], {'date_current': sheet.date_from,}, context=context) - elif datetime.strptime(sheet.date_current, '%Y-%m-%d') >= datetime.strptime(sheet.date_to, '%Y-%m-%d'): - self.write(cr, uid, [sheet.id], {'date_current': sheet.date_to,}, context=context) - return True - def attendance_action_change(self, cr, uid, ids, context=None): hr_employee = self.pool.get('hr.employee') employee_ids = [] @@ -329,14 +194,13 @@ class hr_timesheet_sheet(osv.osv): 'user_id': fields.related('employee_id', 'user_id', type="many2one", relation="res.users", store=True, string="User", required=False, readonly=True),#fields.many2one('res.users', 'User', required=True, select=1, states={'confirm':[('readonly', True)], 'done':[('readonly', True)]}), 'date_from': fields.date('Date from', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}), 'date_to': fields.date('Date to', required=True, select=1, readonly=True, states={'new':[('readonly', False)]}), - 'date_current': fields.date('Current date', required=True, select=1), - 'timesheet_ids' : one2many_mod('hr.analytic.timesheet', 'sheet_id', - 'Timesheet lines', domain=[('date', '=', time.strftime('%Y-%m-%d'))], + 'timesheet_ids' : fields.one2many('hr.analytic.timesheet', 'sheet_id', + 'Timesheet lines', readonly=True, states={ 'draft': [('readonly', False)], 'new': [('readonly', False)]} ), - 'attendances_ids' : one2many_mod2('hr.attendance', 'sheet_id', 'Attendances'), + 'attendances_ids' : fields.one2many('hr.attendance', 'sheet_id', 'Attendances'), 'state' : fields.selection([ ('new', 'New'), ('draft','Open'), @@ -346,9 +210,6 @@ class hr_timesheet_sheet(osv.osv): \n* The \'Confirmed\' state is used for to confirm the timesheet by user. \ \n* The \'Done\' state is used when users timesheet is accepted by his/her senior.'), 'state_attendance' : fields.related('employee_id', 'state', type='selection', selection=[('absent', 'Absent'), ('present', 'Present')], string='Current Status', readonly=True), - 'total_attendance_day': fields.function(_total, method=True, string='Total Attendance', multi="_total"), - 'total_timesheet_day': fields.function(_total, method=True, string='Total Timesheet', multi="_total"), - 'total_difference_day': fields.function(_total, method=True, string='Difference', multi="_total"), 'total_attendance': fields.function(_total, method=True, string='Total Attendance', multi="_total"), 'total_timesheet': fields.function(_total, method=True, string='Total Timesheet', multi="_total"), 'total_difference': fields.function(_total, method=True, string='Difference', multi="_total"), @@ -386,7 +247,6 @@ class hr_timesheet_sheet(osv.osv): _defaults = { 'date_from' : _default_date_from, - 'date_current' : lambda *a: time.strftime('%Y-%m-%d'), 'date_to' : _default_date_to, 'state': 'new', 'employee_id': _default_employee, @@ -406,16 +266,9 @@ class hr_timesheet_sheet(osv.osv): return False return True - def _date_current_check(self, cr, uid, ids, context=None): - for sheet in self.browse(cr, uid, ids, context=context): - if sheet.date_current < sheet.date_from or sheet.date_current > sheet.date_to: - return False - return True - _constraints = [ (_sheet_date, 'You cannot have 2 timesheets that overlaps !\nPlease use the menu \'My Current Timesheet\' to avoid this problem.', ['date_from','date_to']), - (_date_current_check, 'You must select a Current date which is in the timesheet dates !', ['date_current']), ] def action_set_to_draft(self, cr, uid, ids, *args): @@ -498,7 +351,7 @@ class hr_timesheet_line(osv.osv): _columns = { 'sheet_id': fields.function(_sheet, string='Sheet', - type='many2one', relation='hr_timesheet_sheet.sheet', + type='many2one', relation='hr_timesheet_sheet.sheet', ondelete="cascade", store={ 'hr_timesheet_sheet.sheet': (_get_hr_timesheet_sheet, ['employee_id', 'date_from', 'date_to'], 10), 'account.analytic.line': (_get_account_analytic_line, ['user_id', 'date'], 10), @@ -534,6 +387,10 @@ class hr_timesheet_line(osv.osv): raise osv.except_osv(_('Error!'), _('You cannot modify an entry in a confirmed timesheet.')) return True + def multi_on_change_account_id(self, cr, uid, ids, account_ids, context=None): + return dict([(el, self.on_change_account_id(cr, uid, ids, el)) for el in account_ids]) + + hr_timesheet_line() class hr_attendance(osv.osv): diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_demo.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_demo.xml index 42497eb537e..4e0e1becbe9 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_demo.xml +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_demo.xml @@ -6,7 +6,6 @@ <field name="name">Sheet 1</field> <field name="user_id" ref="base.user_root"/> <field name="employee_id" ref="hr.employee_fp" /> - <field eval="time.strftime('%Y-%m-%d')" name="date_current"/> </record> --> </data> diff --git a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml index 9a7416464af..7dd2b9a1b88 100644 --- a/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml +++ b/addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml @@ -71,20 +71,13 @@ </group> </group> <notebook> + <page string="Weekly"> + <widget type="weekly_timesheet"> + </widget> + </page> <page string="Daily"> - <group> - <div> - <button name="button_dummy" class="oe_inline" string="Go to" type="object" icon="terp-gtk-jump-to-ltr"/> : - <field name="date_current" class="oe_inline"/> - </div> - <div align="right"> - <button class="oe_inline" icon="terp-gtk-go-back-ltr" name="date_previous" string="" type="object"/> - <button class="oe_inline" name="date_today" string="Today" type="object" icon="terp-go-today"/> - <button class="oe_inline" icon="terp-gtk-go-back-rtl" name="date_next" string="" type="object"/> - </div> - </group> <group colspan="4" col="3"> - <field context="{'name':date_current,'user_id':user_id}" name="attendances_ids" nolabel="1" groups="base.group_hr_attendance"> + <field context="{'user_id':user_id}" name="attendances_ids" nolabel="1" groups="base.group_hr_attendance"> <tree string="Attendances" editable="bottom"> <field name="name"/> <field name="action"/> @@ -101,9 +94,9 @@ <group col="4"> <field name="state_attendance" groups="base.group_hr_attendance"/> </group> - <field colspan="4" context="{'date':date_current,'user_id':user_id}" domain="[('name','=',date_current)]" name="timesheet_ids" nolabel="1"> + <field colspan="4" context="{'user_id':user_id}" name="timesheet_ids" nolabel="1"> <tree editable="top" string="Timesheet Lines"> - <field invisible="1" name="date"/> + <field name="date"/> <field domain="[('type','in',['normal', 'contract']), ('state', '<>', 'close'),('use_timesheets','=',1)]" name="account_id" on_change="on_change_account_id(account_id)" context="{'default_use_timesheets': 1}"/> <field name="name"/> <field name="unit_amount" on_change="on_change_unit_amount(product_id, unit_amount, False, product_uom_id,journal_id)" widget="float_time"/> diff --git a/addons/hr_timesheet_sheet/report/timesheet_report.py b/addons/hr_timesheet_sheet/report/timesheet_report.py index 3db7ff8feb2..497fe8da117 100644 --- a/addons/hr_timesheet_sheet/report/timesheet_report.py +++ b/addons/hr_timesheet_sheet/report/timesheet_report.py @@ -46,7 +46,6 @@ class timesheet_report(osv.osv): 'department_id':fields.many2one('hr.department','Department',readonly=True), 'date_from': fields.date('Date from',readonly=True,), 'date_to': fields.date('Date to',readonly=True), - 'date_current': fields.date('Current date', required=True), 'state' : fields.selection([ ('new', 'New'), ('draft','Draft'), @@ -62,13 +61,9 @@ class timesheet_report(osv.osv): create or replace view timesheet_report as ( select min(aal.id) as id, - htss.date_current, htss.name, htss.date_from, htss.date_to, - to_char(htss.date_current,'YYYY') as year, - to_char(htss.date_current,'MM') as month, - to_char(htss.date_current, 'YYYY-MM-DD') as day, count(*) as nbr, aal.unit_amount as quantity, aal.amount as cost, @@ -77,18 +72,15 @@ class timesheet_report(osv.osv): (SELECT sum(day.total_difference) FROM hr_timesheet_sheet_sheet AS sheet LEFT JOIN hr_timesheet_sheet_sheet_day AS day - ON (sheet.id = day.sheet_id - AND day.name = sheet.date_current) where sheet.id=htss.id) as total_diff, + ON (sheet.id = day.sheet_id) where sheet.id=htss.id) as total_diff, (SELECT sum(day.total_timesheet) FROM hr_timesheet_sheet_sheet AS sheet LEFT JOIN hr_timesheet_sheet_sheet_day AS day - ON (sheet.id = day.sheet_id - AND day.name = sheet.date_current) where sheet.id=htss.id) as total_timesheet, + ON (sheet.id = day.sheet_id) where sheet.id=htss.id) as total_timesheet, (SELECT sum(day.total_attendance) FROM hr_timesheet_sheet_sheet AS sheet LEFT JOIN hr_timesheet_sheet_sheet_day AS day - ON (sheet.id = day.sheet_id - AND day.name = sheet.date_current) where sheet.id=htss.id) as total_attendance, + ON (sheet.id = day.sheet_id) where sheet.id=htss.id) as total_attendance, aal.to_invoice, aal.general_account_id, htss.user_id, @@ -99,15 +91,11 @@ class timesheet_report(osv.osv): left join hr_analytic_timesheet as hat ON (hat.line_id=aal.id) left join hr_timesheet_sheet_sheet as htss ON (hat.line_id=htss.id) group by - to_char(htss.date_current,'YYYY'), - to_char(htss.date_current,'MM'), - to_char(htss.date_current, 'YYYY-MM-DD'), aal.account_id, htss.date_from, htss.date_to, aal.unit_amount, aal.amount, - htss.date_current, aal.to_invoice, aal.product_id, aal.general_account_id, diff --git a/addons/hr_timesheet_sheet/report/timesheet_report_view.xml b/addons/hr_timesheet_sheet/report/timesheet_report_view.xml index 7b31f72235b..711bbaf2a35 100644 --- a/addons/hr_timesheet_sheet/report/timesheet_report_view.xml +++ b/addons/hr_timesheet_sheet/report/timesheet_report_view.xml @@ -17,7 +17,6 @@ <field name="model">timesheet.report</field> <field name="arch" type="xml"> <tree colors="blue:state == 'draft';black:state in ('confirm','new');gray:state == 'cancel'" string="Timesheet"> - <field name="date_current" invisible="1"/> <field name="name" invisible="1"/> <field name="user_id" invisible="1"/> <field name="date_from" invisible="1"/> @@ -56,7 +55,6 @@ <field name="product_id"/> <field name="department_id"/> <field name="company_id" groups="base.group_multi_company"/> - <field name="date_current"/> <field name="date_to"/> <field name="date_from"/> </group> diff --git a/addons/hr_timesheet_sheet/static/src/css/timesheet.css b/addons/hr_timesheet_sheet/static/src/css/timesheet.css new file mode 100644 index 00000000000..b486f76694d --- /dev/null +++ b/addons/hr_timesheet_sheet/static/src/css/timesheet.css @@ -0,0 +1,79 @@ + +.openerp .oe_timesheet_weekly { + overflow-x: auto; +} + +.openerp .oe_timesheet_weekly table { + width: 100%; +} + +.openerp .oe_timesheet_weekly td { + padding-top: 15px; +} + +.openerp .oe_timesheet_weekly th { + text-align: right; + color: #069; + font-family: 'Helvetica Neue', Arial, Verdana, 'Nimbus Sans L', sans-serif; + font-size: 10px; +} + +.openerp .oe_timesheet_weekly th.oe_timesheet_weekly_date_head { + width: 60px; +} + +.openerp .oe_timesheet_weekly td { + text-align: right; + vertical-align: middle; +} + +.openerp .oe_timesheet_weekly .oe_timesheet_weekly_account { + text-align: left; + padding-right: 30px; +} + +.openerp .oe_timesheet_weekly td input.oe_timesheet_weekly_input { + border: 1px solid #CCC; + padding: 5px 2px !important; + color: #666 !important; + font-size: 14px; + font-weight: bold; + width: 38px; + text-align: right; + min-width: 0 !important; +} + +.openerp .oe_timesheet_weekly td .oe_timesheet_weekly_box { + padding: 5px 2px !important; + color: #666 !important; + font-size: 14px; + font-weight: bold; + width: 38px; + display: inline-block; +} + +.openerp .oe_timesheet_weekly .oe_timesheet_weekly_adding_tot { + display: table; + width: 100%; +} + +.openerp .oe_timesheet_weekly .oe_timesheet_weekly_adding { + display: table-cell; + text-align: left; +} + +.openerp .oe_timesheet_weekly .oe_timesheet_weekly_tottot { + display: table-cell; +} + +.openerp .oe_timesheet_weekly .oe_timesheet_weekly_add_row td { + text-align: left; +} + +.openerp .oe_timesheet_weekly .oe_timesheet_weekly_add_row .oe_form_field_many2one { + display: inline-block; + width: 200px; +} + +.openerp .oe_timesheet_weekly_today { +} diff --git a/addons/hr_timesheet_sheet/static/src/js/timesheet.js b/addons/hr_timesheet_sheet/static/src/js/timesheet.js new file mode 100644 index 00000000000..1be965fd63d --- /dev/null +++ b/addons/hr_timesheet_sheet/static/src/js/timesheet.js @@ -0,0 +1,299 @@ + +openerp.hr_timesheet_sheet = function(instance) { + var QWeb = instance.web.qweb; + var _t = instance.web._t; + + instance.hr_timesheet_sheet.WeeklyTimesheet = instance.web.form.FormWidget.extend(instance.web.form.ReinitializeWidgetMixin, { + init: function() { + this._super.apply(this, arguments); + this.set({ + sheets: [], + date_to: false, + date_from: false, + }); + this.field_manager.on("field_changed:timesheet_ids", this, this.query_sheets); + this.field_manager.on("field_changed:date_from", this, function() { + this.set({"date_from": instance.web.str_to_date(this.field_manager.get_field_value("date_from"))}); + }); + this.field_manager.on("field_changed:date_to", this, function() { + this.set({"date_to": instance.web.str_to_date(this.field_manager.get_field_value("date_to"))}); + }); + this.field_manager.on("field_changed:user_id", this, function() { + this.set({"user_id": this.field_manager.get_field_value("user_id")}); + }); + this.on("change:sheets", this, this.update_sheets); + this.res_o2m_drop = new instance.web.DropMisordered(); + this.render_drop = new instance.web.DropMisordered(); + this.description_line = _t("/"); + }, + query_sheets: function() { + var self = this; + if (self.updating) + return; + var commands = this.field_manager.get_field_value("timesheet_ids"); + this.res_o2m_drop.add(new instance.web.Model(this.view.model).call("resolve_2many_commands", ["timesheet_ids", commands, [], + new instance.web.CompoundContext()])) + .then(function(result) { + self.querying = true; + self.set({sheets: result}); + self.querying = false; + }); + }, + update_sheets: function() { + var self = this; + if (self.querying) + return; + self.updating = true; + self.field_manager.set_values({timesheet_ids: self.get("sheets")}).then(function() { + self.updating = false; + }); + }, + initialize_field: function() { + instance.web.form.ReinitializeWidgetMixin.initialize_field.call(this); + var self = this; + self.on("change:sheets", self, self.initialize_content); + self.on("change:date_to", self, self.initialize_content); + self.on("change:date_from", self, self.initialize_content); + self.on("change:user_id", self, self.initialize_content); + }, + initialize_content: function() { + var self = this; + if (self.setting) + return; + // don't render anything until we have date_to and date_from + if (!self.get("date_to") || !self.get("date_from")) + return; + this.destroy_content(); + + // it's important to use those vars to avoid race conditions + var dates; + var accounts; + var account_names; + var default_get; + return this.render_drop.add(new instance.web.Model("hr.analytic.timesheet").call("default_get", [ + ['account_id','general_account_id', 'journal_id','date','name','user_id','product_id','product_uom_id','to_invoice','amount','unit_amount'], + new instance.web.CompoundContext({'user_id': self.get('user_id')})]).pipe(function(result) { + default_get = result; + // calculating dates + dates = []; + var start = self.get("date_from"); + var end = self.get("date_to"); + while (start <= end) { + dates.push(start); + start = start.clone().addDays(1); + } + // group by account + accounts = _(self.get("sheets")).chain() + .map(function(el) { + // much simpler to use only the id in all cases + if (typeof(el.account_id) === "object") + el.account_id = el.account_id[0]; + return el; + }) + .groupBy("account_id").value(); + + var account_ids = _.map(_.keys(accounts), function(el) { return el === "false" ? false : Number(el) }); + + return new instance.web.Model("hr.analytic.timesheet").call("multi_on_change_account_id", [[], account_ids, + new instance.web.CompoundContext({'user_id': self.get('user_id')})]).pipe(function(accounts_defaults) { + accounts = _(accounts).chain().map(function(lines, account_id) { + account_defaults = _.extend({}, default_get, accounts_defaults[account_id]); + // group by days + account_id = account_id === "false" ? false : Number(account_id); + var index = _.groupBy(lines, "date"); + var days = _.map(dates, function(date) { + var day = {day: date, lines: index[instance.web.date_to_str(date)] || []}; + // add line where we will insert/remove hours + var to_add = _.find(day.lines, function(line) { return line.name === self.description_line }); + if (to_add) { + day.lines = _.without(day.lines, to_add); + day.lines.unshift(to_add); + } else { + day.lines.unshift(_.extend(_.clone(account_defaults), { + name: self.description_line, + unit_amount: 0, + date: instance.web.date_to_str(date), + account_id: account_id, + })); + } + return day; + }); + return {account: account_id, days: days, account_defaults: account_defaults}; + }).value(); + + // we need the name_get of the analytic accounts + return new instance.web.Model("account.analytic.account").call("name_get", [_.pluck(accounts, "account"), + new instance.web.CompoundContext()]).pipe(function(result) { + account_names = {}; + _.each(result, function(el) { + account_names[el[0]] = el[1]; + }); + accounts = _.sortBy(accounts, function(el) { + return account_names[el.account]; + }); + });; + }); + })).pipe(function(result) { + // we put all the gathered data in self, then we render + self.dates = dates; + self.accounts = accounts; + self.account_names = account_names; + self.default_get = default_get; + //real rendering + self.display_data(); + }); + }, + destroy_content: function() { + if (this.dfm) { + this.dfm.destroy(); + this.dfm = undefined; + } + }, + display_data: function() { + var self = this; + self.$el.html(QWeb.render("hr_timesheet_sheet.WeeklyTimesheet", {widget: self})); + _.each(self.accounts, function(account) { + _.each(_.range(account.days.length), function(day_count) { + if (!self.get('effective_readonly')) { + self.get_box(account, day_count).val(self.sum_box(account, day_count)).change(function() { + var num = Number($(this).val()); + if (isNaN(num)) { + $(this).val(self.sum_box(account, day_count)); + } else { + account.days[day_count].lines[0].unit_amount += num - self.sum_box(account, day_count); + self.display_totals(); + self.sync(); + } + }); + } else { + self.get_box(account, day_count).html(self.sum_box(account, day_count)); + } + }); + }); + self.display_totals(); + self.$(".oe_timesheet_weekly_adding button").click(_.bind(this.init_add_account, this)); + }, + init_add_account: function() { + var self = this; + if (self.dfm) + return; + self.$(".oe_timesheet_weekly_add_row").show(); + self.dfm = new instance.web.form.DefaultFieldManager(self); + self.dfm.extend_field_desc({ + account: { + relation: "account.analytic.account", + }, + }); + self.account_m2o = new instance.web.form.FieldMany2One(self.dfm, { + attrs: { + name: "account", + type: "many2one", + domain: [ + ['type','in',['normal', 'contract']], + ['state', '<>', 'close'], + ['use_timesheets','=',1], + ['id', 'not in', _.pluck(self.accounts, "account")], + ], + modifiers: '{"required": true}', + }, + }); + self.account_m2o.prependTo(self.$(".oe_timesheet_weekly_add_row td")); + self.$(".oe_timesheet_weekly_add_row button").click(function() { + var id = self.account_m2o.get_value(); + if (id === false) { + self.dfm.set({display_invalid_fields: true}); + return; + } + var ops = self.generate_o2m_value(); + new instance.web.Model("hr.analytic.timesheet").call("on_change_account_id", [[], id]).pipe(function(res) { + var def = _.extend({}, self.default_get, res.value, { + name: self.description_line, + unit_amount: 0, + date: instance.web.date_to_str(self.dates[0]), + account_id: id, + }); + ops.push(def); + self.set({"sheets": ops}); + }); + }); + }, + get_box: function(account, day_count) { + return this.$('[data-account="' + account.account + '"][data-day-count="' + day_count + '"]'); + }, + get_total: function(account) { + return this.$('[data-account-total="' + account.account + '"]'); + }, + get_day_total: function(day_count) { + return this.$('[data-day-total="' + day_count + '"]'); + }, + get_super_total: function() { + return this.$('.oe_timesheet_weekly_supertotal'); + }, + sum_box: function(account, day_count) { + var line_total = 0; + _.each(account.days[day_count].lines, function(line) { + line_total += line.unit_amount; + }); + return line_total; + }, + display_totals: function() { + var self = this; + var day_tots = _.map(_.range(self.dates.length), function() { return 0 }); + var super_tot = 0; + _.each(self.accounts, function(account) { + var acc_tot = 0; + _.each(_.range(self.dates.length), function(day_count) { + var sum = self.sum_box(account, day_count); + acc_tot += sum; + day_tots[day_count] += sum; + super_tot += sum; + }); + self.get_total(account).html(acc_tot); + }); + _.each(_.range(self.dates.length), function(day_count) { + self.get_day_total(day_count).html(day_tots[day_count]); + }); + self.get_super_total().html(super_tot); + }, + sync: function() { + var self = this; + self.setting = true; + self.set({sheets: this.generate_o2m_value()}); + self.setting = false; + }, + generate_o2m_value: function() { + var self = this; + var ops = []; + + _.each(self.accounts, function(account) { + var auth_keys = _.extend(_.clone(account.account_defaults), { + name: true, unit_amount: true, date: true, account_id:true, + }); + _.each(account.days, function(day) { + _.each(day.lines, function(line) { + if (line.unit_amount !== 0) { + var tmp = _.clone(line); + tmp.id = undefined; + _.each(line, function(v, k) { + if (v instanceof Array) { + tmp[k] = v[0]; + } + }); + // we have to remove some keys, because analytic lines are shitty + _.each(_.keys(tmp), function(key) { + if (auth_keys[key] === undefined) { + tmp[key] = undefined; + } + }); + ops.push(tmp); + } + }); + }); + }); + return ops; + }, + }); + + instance.web.form.custom_widgets.add('weekly_timesheet', 'instance.hr_timesheet_sheet.WeeklyTimesheet'); + +}; diff --git a/addons/hr_timesheet_sheet/static/src/xml/timesheet.xml b/addons/hr_timesheet_sheet/static/src/xml/timesheet.xml new file mode 100644 index 00000000000..0c4ad857d3b --- /dev/null +++ b/addons/hr_timesheet_sheet/static/src/xml/timesheet.xml @@ -0,0 +1,56 @@ +<?xml version="1.0" encoding="UTF-8"?> + +<templates> + <t t-name="hr_timesheet_sheet.WeeklyTimesheet"> + <div class="oe_timesheet_weekly"> + <table> + <tr> + <th> </th> + <t t-foreach="widget.dates" t-as="date"> + <th t-att-class="'oe_timesheet_weekly_date_head' + (Date.compare(date, Date.today()) === 0 ? ' oe_timesheet_weekly_today' : '')"> + <t t-esc="date.toString('ddd')"/><br /> + <t t-esc="date.toString('MMM d')"/> + </th> + </t> + <th class="oe_timesheet_weekly_date_head">TOTAL</th> + </tr> + <tr t-foreach="widget.accounts" t-as="account"> + <td class="oe_timesheet_weekly_account"><t t-esc="widget.account_names[account.account]"/></td> + <t t-set="day_count" t-value="0"/> + <t t-foreach="account.days" t-as="day"> + <td t-att-class="(Date.compare(day.day, Date.today()) === 0 ? 'oe_timesheet_weekly_today' : '')"> + <input t-if="!widget.get('effective_readonly')" class="oe_timesheet_weekly_input" t-att-data-account="account.account" + t-att-data-day-count="day_count" type="text"/> + <span t-if="widget.get('effective_readonly')" t-att-data-account="account.account" + t-att-data-day-count="day_count" class="oe_timesheet_weekly_box"/> + <t t-set="day_count" t-value="day_count + 1"/> + </td> + </t> + <td t-att-data-account-total="account.account"> </td> + </tr> + <tr class="oe_timesheet_weekly_add_row" style="display: none"> + <td t-att-colspan="widget.dates.length + 2"> + <button>Add</button> + </td> + </tr> + <tr> + <td> + <div class="oe_timesheet_weekly_adding_tot"> + <div class="oe_timesheet_weekly_adding"><button>Add Row</button></div> + <div class="oe_timesheet_weekly_tottot"><span>TOTAL</span></div> + </div> + </td> + <t t-set="day_count" t-value="0"/> + <t t-foreach="widget.dates" t-as="date"> + <td t-att-class="(Date.compare(date, Date.today()) === 0 ? 'oe_timesheet_weekly_today' : '')"> + <span class="oe_timesheet_weekly_box" t-att-data-day-total="day_count"> + </span> + <t t-set="day_count" t-value="day_count + 1"/> + </td> + </t> + <td class="oe_timesheet_weekly_supertotal"> </td> + </tr> + </table> + </div> + </t> +</templates>
\ No newline at end of file diff --git a/addons/hr_timesheet_sheet/test/test_hr_timesheet_sheet.yml b/addons/hr_timesheet_sheet/test/test_hr_timesheet_sheet.yml index 070079a4b80..71776af61ba 100644 --- a/addons/hr_timesheet_sheet/test/test_hr_timesheet_sheet.yml +++ b/addons/hr_timesheet_sheet/test/test_hr_timesheet_sheet.yml @@ -16,7 +16,6 @@ I create a timesheet for employee "Quentin Paolinon". - !record {model: hr_timesheet_sheet.sheet, id: hr_timesheet_sheet_sheet_deddk0}: - date_current: !eval time.strftime('%Y-%m-%d') date_from: !eval time.strftime('%Y-%m-01') name: Quentin Paolinon state: new @@ -35,30 +34,6 @@ !assert {model: hr.employee, id: hr.employee_qdp}: - state == 'present' - - I want to check attendance and work of yesterday. I click on <- button. -- - !python {model: hr_timesheet_sheet.sheet}: | - date_prev = self.date_previous(cr, uid, [ref('hr_timesheet_sheet_sheet_deddk0')], None) - assert date_prev == True, "I See Previous Date Timesheet" -- - Then I click on "Today" button to fill today's timesheet. -- - !python {model: hr_timesheet_sheet.sheet}: | - date_to = self.date_today(cr, uid, [ref('hr_timesheet_sheet_sheet_deddk0')], None) - assert date_to == True, "I See Today Date Timesheet" -- - I can also move to next day by clicking on -> button. -- - !python {model: hr_timesheet_sheet.sheet}: | - date_next = self.date_next(cr, uid, [ref('hr_timesheet_sheet_sheet_deddk0')], None) - assert date_next == True, "I See Next Date Timesheet" -- - I want to go to a particular date and see attendance then I select the date and click on "Go to:" button. -- - !python {model: hr_timesheet_sheet.sheet}: | - button_dumy = self.button_dummy(cr, uid, [ref('hr_timesheet_sheet_sheet_deddk0')], None) - assert button_dumy == True, "I See Particular Date Attendance" -- At the time of logout, I create attendance and perform "Sign Out". - !record {model: hr.attendance, id: hr_attendance_2}: diff --git a/addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py b/addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py index a6974fc2ccf..3bd495036d5 100644 --- a/addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py +++ b/addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py @@ -42,7 +42,6 @@ class hr_timesheet_current_open(osv.osv_memory): view_type = 'tree,form' domain = "[('id','in',["+','.join(map(str, ids))+"]),('user_id', '=', uid)]" elif len(ids)==1: - ts.write(cr, uid, ids, {'date_current': time.strftime('%Y-%m-%d')}, context=context) domain = "[('user_id', '=', uid)]" else: domain = "[('user_id', '=', uid)]" diff --git a/addons/mail/data/mail_demo.xml b/addons/mail/data/mail_demo.xml index 1350fd33547..7bda32c002b 100644 --- a/addons/mail/data/mail_demo.xml +++ b/addons/mail/data/mail_demo.xml @@ -42,10 +42,5 @@ This month you also get 250 EUR of eco-vouchers if you have been in the company <field name="type">comment</field> </record> - <record model="ir.config_parameter" id="user_mail_alias"> - <field name="key">mail.catchall.domain</field> - <field name="value">demo.openerp.com</field> - </record> - </data> </openerp> diff --git a/addons/mail/mail_mail.py b/addons/mail/mail_mail.py index e9a569a8192..741184ef7a3 100644 --- a/addons/mail/mail_mail.py +++ b/addons/mail/mail_mail.py @@ -63,10 +63,12 @@ class mail_mail(osv.Model): } def _get_default_from(self, cr, uid, context=None): - cur = self.pool.get('res.users').browse(cr, uid, uid, context=context) - if not cur.alias_domain: - raise osv.except_osv(_('Invalid Action!'), _('Unable to send email, set an alias domain in your server settings.')) - return cur.alias_name + '@' + cur.alias_domain + this = self.pool.get('res.users').browse(cr, uid, uid, context=context) + if this.alias_domain: + return '%s@%s' % (this.alias_name, this.alias_domain) + elif this.email: + return this.email + raise osv.except_osv(_('Invalid Action!'), _("Unable to send email, please configure the sender's email address or alias.")) _defaults = { 'state': 'outgoing', diff --git a/addons/mail/mail_message.py b/addons/mail/mail_message.py index 9c873a31097..7e77b2f2e8c 100644 --- a/addons/mail/mail_message.py +++ b/addons/mail/mail_message.py @@ -180,7 +180,6 @@ class mail_message(osv.Model): fields allow to have the foreign record name without having to check external access rights). """ - child_nbr = len(msg.child_ids) has_voted = False vote_ids = self.pool.get('res.users').name_get(cr, SUPERUSER_ID, [user.id for user in msg.vote_user_ids], context=context) for vote in vote_ids: @@ -194,7 +193,7 @@ class mail_message(osv.Model): try: author_id = self.pool.get('res.partner').name_get(cr, uid, [msg.author_id.id], context=context)[0] is_author = uid == msg.author_id.user_ids[0].id - except (orm.except_orm, osv.except_osv): + except Exception: author_id = False is_author = False try: @@ -502,7 +501,7 @@ class mail_message(osv.Model): def create(self, cr, uid, values, context=None): if not values.get('message_id') and values.get('res_id') and values.get('model'): - values['message_id'] = tools.generate_tracking_message_id('%(model)s-%(res_id)s' % values) + values['message_id'] = tools.generate_tracking_message_id('%(res_id)s-%(model)s' % values) newid = super(mail_message, self).create(cr, uid, values, context) self._notify(cr, SUPERUSER_ID, newid, context=context) return newid diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py index 9f53b4c08b1..529ee8c65cc 100644 --- a/addons/mail/mail_thread.py +++ b/addons/mail/mail_thread.py @@ -39,63 +39,6 @@ _logger = logging.getLogger(__name__) def decode_header(message, header, separator=' '): return separator.join(map(decode, message.get_all(header, []))) -class many2many_reference(fields.many2many): - """ many2many_reference manages many2many fields where one id is found - by a reference-like key (a char column in addition to the foreign id). - The reference_column attribute on the many2many fields is used; - if not defined, ``res_model`` is used. """ - - def _get_query_and_where_params(self, cr, model, ids, values, where_params): - """ Add in where condition like mail_followers.res_model = 'crm.lead' """ - reference_column = self.reference_column if self.reference_column else 'res_model' - values.update(reference_column=reference_column, reference_value=model._name) - query = 'SELECT %(rel)s.%(id2)s, %(rel)s.%(id1)s \ - FROM %(rel)s, %(from_c)s \ - WHERE %(rel)s.%(id1)s IN %%s \ - AND %(rel)s.%(id2)s = %(tbl)s.id \ - AND %(rel)s.%(reference_column)s = \'%(reference_value)s\' \ - %(where_c)s \ - %(order_by)s \ - %(limit)s \ - OFFSET %(offset)d' \ - % values - return query, where_params - - def set(self, cr, model, id, name, values, user=None, context=None): - """ Override to add the reference field in queries. """ - if not values: return - rel, id1, id2 = self._sql_names(model) - obj = model.pool.get(self._obj) - # reference column name: given by attribute or res_model - reference_column = self.reference_column if self.reference_column else 'res_model' - for act in values: - if not (isinstance(act, list) or isinstance(act, tuple)) or not act: - continue - if act[0] == 0: - idnew = obj.create(cr, user, act[2], context=context) - cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, idnew, model._name)) - elif act[0] == 3: - cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND '+reference_column+'=%s', (id, act[1], model._name)) - elif act[0] == 4: - # following queries are in the same transaction - so should be relatively safe - cr.execute('SELECT 1 FROM '+rel+' WHERE '+id1+'=%s AND '+id2+'=%s AND '+reference_column+'=%s', (id, act[1], model._name)) - if not cr.fetchone(): - cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, act[1], model._name)) - elif act[0] == 5: - cr.execute('delete from '+rel+' where '+id1+' = %s AND '+reference_column+'=%s', (id, model._name)) - elif act[0] == 6: - d1, d2,tables = obj.pool.get('ir.rule').domain_get(cr, user, obj._name, context=context) - if d1: - d1 = ' and ' + ' and '.join(d1) - else: - d1 = '' - cr.execute('DELETE FROM '+rel+' WHERE '+id1+'=%s AND '+reference_column+'=%s AND '+id2+' IN (SELECT '+rel+'.'+id2+' FROM '+rel+', '+','.join(tables)+' WHERE '+rel+'.'+id1+'=%s AND '+rel+'.'+id2+' = '+obj._table+'.id '+ d1 +')', [id, model._name, id]+d2) - for act_nbr in act[2]: - cr.execute('INSERT INTO '+rel+' ('+id1+','+id2+','+reference_column+') VALUES (%s,%s,%s)', (id, act_nbr, model._name)) - # cases 1, 2: performs write and unlink -> default implementation is ok - else: - return super(many2many_reference, self).set(cr, model, id, name, values, user, context) - class mail_thread(osv.AbstractModel): ''' mail_thread model is meant to be inherited by any model that needs to @@ -186,6 +129,62 @@ class mail_thread(osv.AbstractModel): res[notif.message_id.res_id] = True return [('id', 'in', res.keys())] + def _get_followers(self, cr, uid, ids, name, arg, context=None): + fol_obj = self.pool.get('mail.followers') + fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)]) + res = dict((res_id, []) for res_id in ids) + for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids): + res[fol.res_id].append(fol.partner_id.id) + return res + + def _set_followers(self, cr, uid, id, name, value, arg, context=None): + partner_obj = self.pool.get('res.partner') + fol_obj = self.pool.get('mail.followers') + + # read the old set of followers, and determine the new set of followers + fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', '=', id)]) + old = set(fol.partner_id.id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids)) + new = set(old) + + for command in value: + if isinstance(command, (int, long)): + new.add(command) + elif command[0] == 0: + new.add(partner_obj.create(cr, uid, command[2], context=context)) + elif command[0] == 1: + partner_obj.write(cr, uid, [command[1]], command[2], context=context) + new.add(command[1]) + elif command[0] == 2: + partner_obj.unlink(cr, uid, [command[1]], context=context) + new.discard(command[1]) + elif command[0] == 3: + new.discard(command[1]) + elif command[0] == 4: + new.add(command[1]) + elif command[0] == 5: + new.clear() + elif command[0] == 6: + new = set(command[2]) + + # remove partners that are no longer followers + fol_ids = fol_obj.search(cr, SUPERUSER_ID, + [('res_model', '=', self._name), ('res_id', '=', id), ('partner_id', 'not in', list(new))]) + fol_obj.unlink(cr, SUPERUSER_ID, fol_ids) + + # add new followers + for partner_id in new - old: + fol_obj.create(cr, SUPERUSER_ID, {'res_model': self._name, 'res_id': id, 'partner_id': partner_id}) + + def _search_followers(self, cr, uid, obj, name, args, context): + fol_obj = self.pool.get('mail.followers') + res = [] + for field, operator, value in args: + assert field == name + fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('partner_id', operator, value)]) + res_ids = [fol.res_id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids)] + res.append(('id', 'in', res_ids)) + return res + _columns = { 'message_is_follower': fields.function(_get_subscription_data, type='boolean', string='Is a Follower', multi='_get_subscription_data,'), @@ -194,9 +193,8 @@ class mail_thread(osv.AbstractModel): help="Holds data about the subtypes. The content of this field "\ "is a structure holding the current model subtypes, and the "\ "current document followed subtypes."), - 'message_follower_ids': many2many_reference('res.partner', - 'mail_followers', 'res_id', 'partner_id', - reference_column='res_model', string='Followers'), + 'message_follower_ids': fields.function(_get_followers, fnct_inv=_set_followers, + fnct_search=_search_followers, type='many2many', obj='res.partner', string='Followers'), 'message_comment_ids': fields.one2many('mail.message', 'res_id', domain=lambda self: [('model', '=', self._name), ('type', 'in', ('comment', 'email'))], string='Comments and emails', @@ -422,12 +420,12 @@ class mail_thread(osv.AbstractModel): model_pool = self.pool.get(model) assert thread_id and hasattr(model_pool, 'message_update') or hasattr(model_pool, 'message_new'), \ "Undeliverable mail with Message-Id %s, model %s does not accept incoming emails" % \ - (msg['message-id'], model) + (msg['message_id'], model) if thread_id and hasattr(model_pool, 'message_update'): model_pool.message_update(cr, user_id, [thread_id], msg, context=context) else: thread_id = model_pool.message_new(cr, user_id, msg, custom_values, context=context) - self.message_post(cr, uid, [thread_id], context=context, **msg) + model_pool.message_post(cr, uid, [thread_id], context=context, **msg) return thread_id def message_new(self, cr, uid, msg_dict, custom_values=None, context=None): @@ -538,7 +536,7 @@ class mail_thread(osv.AbstractModel): field may not be present if missing in original message:: - { 'message-id': msg_id, + { 'message_id': msg_id, 'subject': subject, 'from': from, 'to': to, diff --git a/addons/mail/res_config.py b/addons/mail/res_config.py index ce2f75687ba..91606e1c720 100644 --- a/addons/mail/res_config.py +++ b/addons/mail/res_config.py @@ -19,6 +19,8 @@ # ############################################################################## +import urlparse + from openerp.osv import osv, fields class project_configuration(osv.TransientModel): @@ -31,9 +33,16 @@ class project_configuration(osv.TransientModel): } def get_default_alias_domain(self, cr, uid, ids, context=None): - return {'alias_domain': self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.catchall.domain", context=context)} + alias_domain = self.pool.get("ir.config_parameter").get_param(cr, uid, "mail.catchall.domain", context=context) + if not alias_domain: + domain = self.pool.get("ir.config_parameter").get_param(cr, uid, "web.base.url", context=context) + try: + alias_domain = urlparse.urlsplit(domain).netloc.split(':')[0] + except Exception: + pass + return {'alias_domain': alias_domain} def set_alias_domain(self, cr, uid, ids, context=None): config_parameters = self.pool.get("ir.config_parameter") for record in self.browse(cr, uid, ids, context=context): - config_parameters.set_param(cr, uid, "mail.catchall.domain", record.alias_domain or '', context=context)
\ No newline at end of file + config_parameters.set_param(cr, uid, "mail.catchall.domain", record.alias_domain or '', context=context) diff --git a/addons/mail/res_users.py b/addons/mail/res_users.py index 545198f4c7f..f870604b03a 100644 --- a/addons/mail/res_users.py +++ b/addons/mail/res_users.py @@ -110,9 +110,19 @@ class res_users(osv.Model): alias_pool.unlink(cr, uid, alias_ids, context=context) return res - def message_post(self, cr, uid, thread_id, **kwargs): - partner_id = self.pool.get('res.users').browse(cr, uid, thread_id)[0].partner_id.id - return self.pool.get('res.partner').message_post(cr, uid, partner_id, **kwargs) + def message_post(self, cr, uid, thread_id, context=None, **kwargs): + assert thread_id, "res.users does not support posting global messages" + if context and 'thread_model' in context: + context['thread_model'] = 'res.partner' + if isinstance(thread_id, (list, tuple)): + thread_id = thread_id[0] + partner_id = self.pool.get('res.users').browse(cr, uid, thread_id).partner_id.id + return self.pool.get('res.partner').message_post(cr, uid, partner_id, context=context, **kwargs) + + def message_update(self, cr, uid, ids, msg_dict, update_vals=None, context=None): + partner_id = self.pool.get('res.users').browse(cr, uid, ids)[0].partner_id.id + return self.pool.get('res.partner').message_update(cr, uid, [partner_id], msg_dict, + update_vals=update_vals, context=context) class res_users_mail_group(osv.Model): """ Update of res.users class diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml index b503565bd11..be3bb667628 100644 --- a/addons/mail/security/mail_security.xml +++ b/addons/mail/security/mail_security.xml @@ -7,7 +7,7 @@ <field name="name">Mail.group: access only public and joined groups</field> <field name="model_id" ref="model_mail_group"/> <!-- This rule has to be improved for employee only groups --> - <field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.id]), '&', ('public','=','groups'), ('group_public_id','in', [x.id for x in user.groups_id])]</field> + <field name="domain_force">['|', '|', ('public', '=', 'public'), ('message_follower_ids', 'in', [user.partner_id.id]), '&', ('public','=','groups'), ('group_public_id','in', [g.id for g in user.groups_id])]</field> </record> <!-- diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js index 953b8cd9fc8..6c016c6c679 100644 --- a/addons/mail/static/src/js/mail_followers.js +++ b/addons/mail/static/src/js/mail_followers.js @@ -149,7 +149,6 @@ openerp_mail_followers = function(session, mail) { var self = this; var node_user_list = this.$('ul.oe_mail_followers_display').empty(); this.$('div.oe_mail_recthread_followers h4').html(this.options.title + (records.length>=5 ? ' (' + records.length + ')' : '') ); - console.log(records); for(var i=0; i<records.length&&i<5; i++) { var record=records[i]; record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id); diff --git a/addons/mail/tests/test_mail.py b/addons/mail/tests/test_mail.py index 7ec37d952bc..1fc44ed81af 100644 --- a/addons/mail/tests/test_mail.py +++ b/addons/mail/tests/test_mail.py @@ -194,7 +194,7 @@ class test_mail(TestMailMockups): 'plaintext mail incorrectly parsed') def test_10_many2many_reference_field(self): - """ Tests designed for the many2many_reference field (follower_ids). + """ Tests designed for the many2many function field 'follower_ids'. We will test to perform writes using the many2many commands 0, 3, 4, 5 and 6. """ cr, uid = self.cr, self.uid diff --git a/addons/marketing_campaign/i18n/ar.po b/addons/marketing_campaign/i18n/ar.po index 141a567ac52..7f7ee498416 100644 --- a/addons/marketing_campaign/i18n/ar.po +++ b/addons/marketing_campaign/i18n/ar.po @@ -8,19 +8,19 @@ 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-01-12 05:49+0000\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"PO-Revision-Date: 2012-10-08 15:06+0000\n" +"Last-Translator: kifcaliph <Unknown>\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-08-28 06:38+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-09 04:51+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: marketing_campaign #: view:marketing.campaign:0 msgid "Manual Mode" -msgstr "" +msgstr "النمط اليدوي" #. module: marketing_campaign #: field:marketing.campaign.transition,activity_from_id:0 @@ -31,12 +31,12 @@ msgstr "النشاط السابق" #: code:addons/marketing_campaign/marketing_campaign.py:818 #, python-format msgid "The current step for this item has no email or report to preview." -msgstr "" +msgstr "الخطوة الحالية لهذا الصنف لا تملك بريد إلكتروني أو تقرير للمعاينة" #. module: marketing_campaign #: constraint:marketing.campaign.transition:0 msgid "The To/From Activity of transition must be of the same Campaign " -msgstr "" +msgstr "الإنتقال من / إلى النشاط يجب أن يكون من نفس الحملة " #. module: marketing_campaign #: selection:marketing.campaign.transition,trigger:0 @@ -46,7 +46,7 @@ msgstr "الوقت" #. module: marketing_campaign #: selection:marketing.campaign.activity,type:0 msgid "Custom Action" -msgstr "" +msgstr "تخصيص إجراء" #. module: marketing_campaign #: view:campaign.analysis:0 @@ -63,11 +63,13 @@ msgid "" "reached this point has generated a certain revenue. You can get revenue " "statistics in the Reporting section" msgstr "" +"تعيين الإيرادات المتوقعة إذا كنت ترى أن كل بند من بنود الحملة وصلت الى تلك " +"النقطة وولدت عائد معين. يمكنك الحصول على إحصاءات الإيرادات في قسم التقارير" #. module: marketing_campaign #: field:marketing.campaign.transition,trigger:0 msgid "Trigger" -msgstr "" +msgstr "زر" #. module: marketing_campaign #: field:campaign.analysis,count:0 @@ -77,7 +79,7 @@ msgstr "" #. module: marketing_campaign #: view:marketing.campaign:0 msgid "Campaign Editor" -msgstr "" +msgstr "محرر الحملة" #. module: marketing_campaign #: view:campaign.analysis:0 @@ -106,13 +108,13 @@ msgstr "كائن" #. module: marketing_campaign #: view:marketing.campaign.segment:0 msgid "Sync mode: only records created after last sync" -msgstr "" +msgstr "نمط المزامنة: السجلات فقط التي تم إنشاؤها بعد آخر المزامنة" #. module: marketing_campaign #: model:email.template,body_text:marketing_campaign.email_template_2 msgid "" "Hello, We are happy to announce that you now become our Silver Partner." -msgstr "" +msgstr "مرحبا، ونحن سعداء أن نعلن أنك أصبحت الآن شريكاً فضياً لدينا." #. module: marketing_campaign #: view:marketing.campaign:0 @@ -123,7 +125,7 @@ msgstr "حفظ كمسودة" #. module: marketing_campaign #: field:marketing.campaign.activity,to_ids:0 msgid "Next Activities" -msgstr "" +msgstr "النشاطات التالية" #. module: marketing_campaign #: view:marketing.campaign.segment:0 @@ -133,7 +135,7 @@ msgstr "مزامنة" #. module: marketing_campaign #: sql_constraint:marketing.campaign.transition:0 msgid "The interval must be positive or zero" -msgstr "" +msgstr "الفترة يجب أن تكون إيجابية أو صفر" #. module: marketing_campaign #: code:addons/marketing_campaign/marketing_campaign.py:818 @@ -145,7 +147,7 @@ msgstr "لا معاينة" #: view:marketing.campaign.segment:0 #: field:marketing.campaign.segment,date_run:0 msgid "Launch Date" -msgstr "" +msgstr "تاريخ الإنشاء" #. module: marketing_campaign #: view:campaign.analysis:0 @@ -156,7 +158,7 @@ msgstr "يوم" #. module: marketing_campaign #: view:marketing.campaign.activity:0 msgid "Outgoing Transitions" -msgstr "" +msgstr "التنقلات الصادرة" #. module: marketing_campaign #: view:marketing.campaign.workitem:0 @@ -166,18 +168,18 @@ msgstr "إستعادة" #. module: marketing_campaign #: help:marketing.campaign,object_id:0 msgid "Choose the resource on which you want this campaign to be run" -msgstr "" +msgstr "اختيار المورد الذي تريده ليتم تشغيله لهذه الحملة" #. module: marketing_campaign #: field:marketing.campaign.segment,sync_last_date:0 msgid "Last Synchronization" -msgstr "" +msgstr "التزامن الأخير" #. module: marketing_campaign #: code:addons/marketing_campaign/marketing_campaign.py:214 #, python-format msgid "You can not duplicate a campaign, it's not supported yet." -msgstr "" +msgstr "لا يمكنك تكرار الحملة، انها غير مدعومة حتى الآن." #. module: marketing_campaign #: selection:marketing.campaign.transition,interval_type:0 @@ -189,7 +191,7 @@ msgstr "سنة/سنين" msgid "" "Date on which this segment was synchronized last time (automatically or " "manually)" -msgstr "" +msgstr "التاريخ الذي تزامن هذا القطاع آخر مرة (آليا أو يدويا)" #. module: marketing_campaign #: selection:campaign.analysis,state:0 @@ -216,11 +218,19 @@ msgid "" "Normal - the campaign runs normally and automatically sends all emails and " "reports (be very careful with this mode, you're live!)" msgstr "" +"اختبار - وهو يخلق ويعالج كافة الأنشطة مباشرة (دون انتظار تأخير على التحولات) " +"ولكن لا يرسل رسائل البريد الإلكتروني أو يعد التقارير.\n" +" اختبار في الوقت الحقيقي - وهو يخلق ويعالج جميع الأنشطة بشكل مباشر ولكن لا " +"يرسل رسائل البريد الإلكتروني أو يعد التقارير.\n" +" مع التأكيد اليدوي - يعمل الحملات عادة، ولكن المستخدم لديه للتحقق من صحة كل " +"بنود العمل يدويا.\n" +" طبيعي - يعمل الحملة بشكل طبيعي يرسل تلقائيا جميع رسائل البريد الإلكتروني " +"والتقارير (نكون حذرين للغاية مع هذا الوضع، أنت على الهواء!)" #. module: marketing_campaign #: help:marketing.campaign.segment,date_run:0 msgid "Initial start date of this segment." -msgstr "" +msgstr "تاريخ بدء الأولي لهذا القطاع." #. module: marketing_campaign #: view:campaign.analysis:0 @@ -237,7 +247,7 @@ msgstr "حملة" #. module: marketing_campaign #: model:email.template,subject:marketing_campaign.email_template_3 msgid "Congratulation! You become our Gold Partner." -msgstr "" +msgstr "تهنئة! لقد أصبحت شريكاً ذهبياً الآن." #. module: marketing_campaign #: view:campaign.analysis:0 @@ -251,7 +261,7 @@ msgstr "قطعة" #. module: marketing_campaign #: view:marketing.campaign.activity:0 msgid "Cost / Revenue" -msgstr "" +msgstr "التكلفة/العائد" #. module: marketing_campaign #: help:marketing.campaign.activity,type:0 @@ -313,7 +323,7 @@ msgstr "" #. module: marketing_campaign #: view:campaign.analysis:0 msgid "Marketing Reports" -msgstr "" +msgstr "تقارير التسويق" #. module: marketing_campaign #: selection:marketing.campaign,state:0 @@ -342,7 +352,7 @@ msgstr "" #. module: marketing_campaign #: field:marketing.campaign.segment,sync_mode:0 msgid "Synchronization mode" -msgstr "" +msgstr "نمط التزامن" #. module: marketing_campaign #: view:marketing.campaign:0 @@ -353,7 +363,7 @@ msgstr "" #. module: marketing_campaign #: field:marketing.campaign.activity,from_ids:0 msgid "Previous Activities" -msgstr "" +msgstr "النشاطات السابقة" #. module: marketing_campaign #: help:marketing.campaign.segment,date_done:0 @@ -363,7 +373,7 @@ msgstr "" #. module: marketing_campaign #: view:marketing.campaign.workitem:0 msgid "Marketing Campaign Activities" -msgstr "" +msgstr "نشاطات حملة التسويق" #. module: marketing_campaign #: view:marketing.campaign.workitem:0 @@ -427,7 +437,7 @@ msgstr "" #. module: marketing_campaign #: model:ir.model,name:marketing_campaign.model_marketing_campaign_segment msgid "Campaign Segment" -msgstr "" +msgstr "حملة القطاع" #. module: marketing_campaign #: view:marketing.campaign:0 @@ -456,7 +466,7 @@ msgstr "" #. module: marketing_campaign #: field:marketing.campaign,fixed_cost:0 msgid "Fixed Cost" -msgstr "" +msgstr "التكلفة الثابتة" #. module: marketing_campaign #: model:email.template,subject:marketing_campaign.email_template_2 @@ -466,12 +476,12 @@ msgstr "" #. module: marketing_campaign #: view:marketing.campaign.segment:0 msgid "Newly Modified" -msgstr "" +msgstr "تم التعديل حديثاً" #. module: marketing_campaign #: field:marketing.campaign.transition,interval_nbr:0 msgid "Interval Value" -msgstr "" +msgstr "قيمة الفترة" #. module: marketing_campaign #: field:campaign.analysis,revenue:0 @@ -512,22 +522,22 @@ msgstr "" #. module: marketing_campaign #: model:ir.actions.act_window,name:marketing_campaign.act_marketing_campaing_followup msgid "Campaign Follow-up" -msgstr "" +msgstr "متابعة الحملة" #. module: marketing_campaign #: help:marketing.campaign.activity,email_template_id:0 msgid "The e-mail to send when this activity is activated" -msgstr "" +msgstr "بريد إلكتروني للإرسال عندما يتم تنشيط المنشط" #. module: marketing_campaign #: view:marketing.campaign:0 msgid "Test Mode" -msgstr "" +msgstr "نمط الإختبار" #. module: marketing_campaign #: selection:marketing.campaign.segment,sync_mode:0 msgid "Only records modified after last sync (no duplicates)" -msgstr "" +msgstr "فقط السجلات المعدلة بعد آخر مزامنة (بدون تكرار)" #. module: marketing_campaign #: model:ir.model,name:marketing_campaign.model_ir_actions_report_xml diff --git a/addons/portal/__openerp__.py b/addons/portal/__openerp__.py index d7356fc9ce5..66a736c6048 100644 --- a/addons/portal/__openerp__.py +++ b/addons/portal/__openerp__.py @@ -25,7 +25,6 @@ 'depends': [ 'base', 'share', - 'auth_anonymous', 'auth_signup', ], 'author': 'OpenERP SA', diff --git a/addons/portal/mail_mail.py b/addons/portal/mail_mail.py index a41c28f15e2..f57170ed67b 100644 --- a/addons/portal/mail_mail.py +++ b/addons/portal/mail_mail.py @@ -19,34 +19,22 @@ # ############################################################################## -import tools from osv import osv +import tools - -class mail_mail_portal(osv.Model): - """ Update of mail_mail class, to add the signin URL to notifications. - """ - _name = 'mail.mail' - _inherit = ['mail.mail'] - - def _generate_signin_url(self, cr, uid, partner_id, portal_group_id, key, context=None): - """ Generate the signin url """ - base_url = self.pool.get('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='', context=context) - return base_url + '/login?action=signin&partner_id=%s&group=%s&key=%s' % (partner_id, portal_group_id, key) +class mail_mail(osv.Model): + """ Update of mail_mail class, to add the signin URL to notifications. """ + _inherit = 'mail.mail' def send_get_mail_body(self, cr, uid, mail, partner=None, context=None): - """ Return a specific ir_email body. The main purpose of this method - is to be inherited by Portal, to add a link for signing in, in - each notification email a partner receives. - + """ add a signin link inside the body of a mail.mail :param mail: mail.mail browse_record :param partner: browse_record of the specific recipient partner + :return: the resulting body_html """ + body = super(mail_mail, self).send_get_mail_body(cr, uid, mail, partner, context=context) if partner: - portal_ref = self.pool.get('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal') - portal_id = portal_ref and portal_ref[1] or False - url = self._generate_signin_url(cr, uid, partner.id, portal_id, 1234, context=context) - body = tools.append_content_to_html(mail.body_html, url) - return body - else: - return super(mail_mail_portal, self).send_get_mail_body(cr, uid, mail, partner=partner, context=context) + context = dict(context or {}, signup_valid=True) + partner = self.pool.get('res.partner').browse(cr, uid, partner.id, context) + body = tools.append_content_to_html(body, "Log in our portal at: %s" % partner.signup_url) + return body diff --git a/addons/portal/portal_view.xml b/addons/portal/portal_view.xml index 16e8a86b27e..5b39ea89525 100644 --- a/addons/portal/portal_view.xml +++ b/addons/portal/portal_view.xml @@ -5,7 +5,7 @@ <!-- Top menu item --> <menuitem name="Portal" id="portal_menu" - groups="base.group_no_one,portal.group_portal,auth_anonymous.group_anonymous" + groups="base.group_no_one,portal.group_portal" sequence="20"/> <menuitem name="Our company" id="portal_company" parent="portal_menu" sequence="10"/> diff --git a/addons/portal/tests/test_portal.py b/addons/portal/tests/test_portal.py index 653a05d40aa..83b10b39c28 100644 --- a/addons/portal/tests/test_portal.py +++ b/addons/portal/tests/test_portal.py @@ -33,6 +33,7 @@ class test_portal(test_mail.TestMailMockups): self.mail_group = self.registry('mail.group') self.mail_mail = self.registry('mail.mail') self.mail_message = self.registry('mail.message') + self.mail_invite = self.registry('mail.wizard.invite') self.res_users = self.registry('res.users') self.res_partner = self.registry('res.partner') @@ -41,19 +42,20 @@ class test_portal(test_mail.TestMailMockups): {'name': 'Pigs', 'description': 'Fans of Pigs, unite !'}) # Find Portal group - group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal') - self.group_portal_id = group_portal_ref and group_portal_ref[1] or False + group_portal = self.registry('ir.model.data').get_object(cr, uid, 'portal', 'group_portal') + self.group_portal_id = group_portal.id # Create Chell (portal user) self.user_chell_id = self.res_users.create(cr, uid, {'name': 'Chell Gladys', 'login': 'chell', 'groups_id': [(6, 0, [self.group_portal_id])]}) - self.user_chell = self.res_users.browse(cr, uid, self.user_chell_id) - self.partner_chell_id = self.user_chell.partner_id.id + user_chell = self.res_users.browse(cr, uid, self.user_chell_id) + self.partner_chell_id = user_chell.partner_id.id + + # Set an email address for the user running the tests, used as Sender for outgoing mails + self.res_users.write(cr, uid, uid, {'email': 'test@localhost'}) def test_00_access_rights(self): """ Test basic mail_message and mail_group access rights for portal users. """ cr, uid = self.cr, self.uid - partner_chell_id = self.partner_chell_id - user_chell_id = self.user_chell_id # Prepare group: Pigs (portal) self.mail_group.message_post(cr, uid, self.group_pigs_id, body='Message') @@ -64,56 +66,56 @@ class test_portal(test_mail.TestMailMockups): # ---------------------------------------- # Do: Chell reads Pigs messages, ok because restricted to portal group - message_ids = self.mail_group.read(cr, user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids'] - self.mail_message.read(cr, user_chell_id, message_ids) + message_ids = self.mail_group.read(cr, self.user_chell_id, self.group_pigs_id, ['message_ids'])['message_ids'] + self.mail_message.read(cr, self.user_chell_id, message_ids) + # Do: Chell posts a message on Pigs, crash because can not write on group or is not in the followers - self.assertRaises(except_orm, - self.mail_group.message_post, - cr, user_chell_id, self.group_pigs_id, body='Message') + with self.assertRaises(except_orm): + self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message') + # Do: Chell is added to Pigs followers - self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [partner_chell_id]) + self.mail_group.message_subscribe(cr, uid, [self.group_pigs_id], [self.partner_chell_id]) + # Test: Chell posts a message on Pigs, ok because in the followers - self.mail_group.message_post(cr, user_chell_id, self.group_pigs_id, body='Message') + self.mail_group.message_post(cr, self.user_chell_id, self.group_pigs_id, body='Message') def test_50_mail_invite(self): cr, uid = self.cr, self.uid user_admin = self.res_users.browse(cr, uid, uid) - self.mail_invite = self.registry('mail.wizard.invite') base_url = self.registry('ir.config_parameter').get_param(cr, uid, 'web.base.url', default='') - portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal') - portal_id = portal_ref and portal_ref[1] or False - # 0 - Admin - p_a_id = user_admin.partner_id.id + partner_admin_id = user_admin.partner_id.id # 1 - Bert Tartopoils, with email, should receive emails for comments and emails - p_b_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'}) - - # ---------------------------------------- - # CASE1: generated URL - # ---------------------------------------- - - url = self.mail_mail._generate_signin_url(cr, uid, p_b_id, portal_id, 1234) - self.assertEqual(url, base_url + '/login?action=signin&partner_id=%s&group=%s&key=%s' % (p_b_id, portal_id, 1234), - 'generated signin URL incorrect') + partner_bert_id = self.res_partner.create(cr, uid, {'name': 'Bert Tartopoils', 'email': 'b@b'}) # ---------------------------------------- - # CASE2: invite Bert + # CASE: invite Bert to follow Pigs # ---------------------------------------- - _sent_email_subject = 'Invitation to follow Pigs' - _sent_email_body = append_content_to_html('<div>You have been invited to follow Pigs.</div>', url) - # Do: create a mail_wizard_invite, validate it self._init_mock_build_email() - mail_invite_id = self.mail_invite.create(cr, uid, {'partner_ids': [(4, p_b_id)]}, {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id}) + context = {'default_res_model': 'mail.group', 'default_res_id': self.group_pigs_id} + mail_invite_id = self.mail_invite.create(cr, uid, {'partner_ids': [(4, partner_bert_id)]}, context) self.mail_invite.add_followers(cr, uid, [mail_invite_id]) - group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) # Test: Pigs followers should contain Admin and Bert + group_pigs = self.mail_group.browse(cr, uid, self.group_pigs_id) follower_ids = [follower.id for follower in group_pigs.message_follower_ids] - self.assertEqual(set(follower_ids), set([p_a_id, p_b_id]), 'Pigs followers after invite is incorrect') - # Test: sent email subject, body + self.assertEqual(set(follower_ids), set([partner_admin_id, partner_bert_id]), 'Pigs followers after invite is incorrect') + + # Test: partner must have been prepared for signup + partner_bert = self.res_partner.browse(cr, uid, partner_bert_id) + self.assertTrue(partner_bert.signup_valid, 'partner has not been prepared for signup') + self.assertTrue(base_url in partner_bert.signup_url, 'signup url is incorrect') + self.assertTrue(cr.dbname in partner_bert.signup_url, 'signup url is incorrect') + self.assertTrue(partner_bert.signup_token in partner_bert.signup_url, 'signup url is incorrect') + + # Test: (pretend to) send email and check subject, body self.assertEqual(len(self._build_email_kwargs_list), 1, 'sent email number incorrect, should be only for Bert') for sent_email in self._build_email_kwargs_list: - self.assertEqual(sent_email.get('subject'), _sent_email_subject, 'sent email subject incorrect') - self.assertEqual(sent_email.get('body'), _sent_email_body, 'sent email body incorrect') + self.assertEqual(sent_email.get('subject'), 'Invitation to follow Pigs', + 'subject of invitation email is incorrect') + self.assertTrue('You have been invited to follow Pigs' in sent_email.get('body'), + 'body of invitation email is incorrect') + self.assertTrue(partner_bert.signup_url in sent_email.get('body'), + 'body of invitation email does not contain signup url') diff --git a/addons/portal/wizard/portal_wizard.py b/addons/portal/wizard/portal_wizard.py index 3cc706c2129..c94d6509f63 100644 --- a/addons/portal/wizard/portal_wizard.py +++ b/addons/portal/wizard/portal_wizard.py @@ -30,47 +30,31 @@ from openerp import SUPERUSER_ID from base.res.res_partner import _lang_get _logger = logging.getLogger(__name__) -# welcome/goodbye email sent to portal users +# welcome email sent to portal users # (note that calling '_' has no effect except exporting those strings for translation) WELCOME_EMAIL_SUBJECT = _("Your OpenERP account at %(company)s") WELCOME_EMAIL_BODY = _("""Dear %(name)s, -You have been given access to %(portal)s at %(url)s. +You have been given access to %(portal)s. Your login account data is: Database: %(db)s -User: %(login)s -Password: %(password)s +Username: %(login)s -%(welcome_message)s - --- -OpenERP - Open Source Business Applications -http://www.openerp.com -""") - -GOODBYE_EMAIL_SUBJECT = _("Your OpenERP account at %(company)s") -GOODBYE_EMAIL_BODY = _("""Dear %(name)s, +In order to complete the signin process, click on the following url: +%(url)s -Your access to %(portal)s has been withdrawn. - -%(goodbye_message)s +%(welcome_message)s -- OpenERP - Open Source Business Applications http://www.openerp.com """) -# character sets for passwords, excluding 0, O, o, 1, I, l -_PASSU = 'ABCDEFGHIJKLMNPQRSTUVWXYZ' -_PASSL = 'abcdefghijkmnpqrstuvwxyz' -_PASSD = '23456789' - def random_password(): - # get 3 uppercase letters, 3 lowercase letters, 2 digits, and shuffle them - chars = map(random.choice, [_PASSU] * 3 + [_PASSL] * 3 + [_PASSD] * 2) - random.shuffle(chars) - return ''.join(chars) + # temporary random stuff; user password is reset by signup process + chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' + return ''.join(random.choice(chars) for i in xrange(12)) def extract_email(email): """ extract the email address from a user-friendly email address """ @@ -92,8 +76,6 @@ class wizard(osv.osv_memory): 'user_ids': fields.one2many('portal.wizard.user', 'wizard_id', string='Users'), 'welcome_message': fields.text(string='Invitation Message', help="This text is included in the email sent to new users of the portal."), - 'goodbye_message': fields.text(string='Withdrawal Message', - help="This text is included in the email sent to users withdrawn from the portal."), } def _default_portal(self, cr, uid, context): @@ -164,6 +146,8 @@ class wizard_user(osv.osv_memory): user = self._create_user(cr, SUPERUSER_ID, wizard_user, context) if (not user.active) or (portal not in user.groups_id): user.write({'active': True, 'groups_id': [(4, portal.id)]}) + # prepare for the signup process + user.partner_id.signup_prepare() wizard_user = self.browse(cr, SUPERUSER_ID, wizard_user.id, context) self._send_email(cr, uid, wizard_user, context) else: @@ -174,8 +158,6 @@ class wizard_user(osv.osv_memory): user.write({'groups_id': [(3, portal.id)], 'active': False}) else: user.write({'groups_id': [(3, portal.id)]}) - wizard_user = self.browse(cr, SUPERUSER_ID, wizard_user.id, context) - self._send_email(cr, uid, wizard_user, context) def _retrieve_user(self, cr, uid, wizard_user, context=None): """ retrieve the (possibly inactive) user corresponding to wizard_user.partner_id @@ -208,7 +190,7 @@ class wizard_user(osv.osv_memory): return res_users.browse(cr, uid, user_id, context) def _send_email(self, cr, uid, wizard_user, context=None): - """ send notification email to a new/former portal user + """ send notification email to a new portal user @param wizard_user: browse record of model portal.wizard.user @return: the id of the created mail.mail record """ @@ -219,33 +201,23 @@ class wizard_user(osv.osv_memory): _('You must have an email address in your User Preferences to send emails.')) # determine subject and body in the portal user's language - url = self.pool.get('ir.config_parameter').get_param(cr, SUPERUSER_ID, 'web.base.url', context=this_context) user = self._retrieve_user(cr, SUPERUSER_ID, wizard_user, context) context = dict(this_context or {}, lang=user.lang) data = { 'company': this_user.company_id.name, 'portal': wizard_user.wizard_id.portal_id.name, 'welcome_message': wizard_user.wizard_id.welcome_message or "", - 'goodbye_message': wizard_user.wizard_id.goodbye_message or "", - 'url': url or _("(missing url)"), 'db': cr.dbname, + 'name': user.name, 'login': user.login, - 'password': user.password, - 'name': user.name + 'url': user.signup_url, } - if wizard_user.in_portal: - subject = _(WELCOME_EMAIL_SUBJECT) % data - body = _(WELCOME_EMAIL_BODY) % data - else: - subject = _(GOODBYE_EMAIL_SUBJECT) % data - body = _(GOODBYE_EMAIL_BODY) % data - mail_mail = self.pool.get('mail.mail') mail_values = { 'email_from': this_user.email, 'email_to': user.email, - 'subject': subject, - 'body_html': '<pre>%s</pre>' % body, + 'subject': _(WELCOME_EMAIL_SUBJECT) % data, + 'body_html': '<pre>%s</pre>' % (_(WELCOME_EMAIL_BODY) % data), 'state': 'outgoing', } return mail_mail.create(cr, uid, mail_values, context=this_context) diff --git a/addons/portal/wizard/portal_wizard_view.xml b/addons/portal/wizard/portal_wizard_view.xml index c8e3f30b128..373b4ad8e7a 100644 --- a/addons/portal/wizard/portal_wizard_view.xml +++ b/addons/portal/wizard/portal_wizard_view.xml @@ -27,8 +27,6 @@ <field name="user_ids"/> <field name="welcome_message" placeholder="This text is included in the email sent to new portal users."/> - <field name="goodbye_message" - placeholder="This text is included in the email sent to users withdrawn from the portal."/> <footer> <button string="Apply" name="action_apply" type="object" class="oe_highlight"/> or diff --git a/addons/process/static/src/js/process.js b/addons/process/static/src/js/process.js index c6b9bbd9ef1..c593c1569fc 100644 --- a/addons/process/static/src/js/process.js +++ b/addons/process/static/src/js/process.js @@ -239,9 +239,7 @@ instance.web.ViewManager.include({ title: _t('Process') }); var form_controller = pop.view_form; - pop.on_write_completed.add_last(function() { - self.initialize_process_view(); - }); + pop.on('on_write_complete', self, self.initialize_process_view); } }); }; diff --git a/addons/project/project.py b/addons/project/project.py index 6b02a018293..a47b582be77 100644 --- a/addons/project/project.py +++ b/addons/project/project.py @@ -19,12 +19,14 @@ # ############################################################################## -from base_status.base_stage import base_stage -from datetime import datetime, date +import time from lxml import etree +from datetime import datetime, date + +import tools +from base_status.base_stage import base_stage from osv import fields, osv from openerp.addons.resource.faces import task as Task -import time from tools.translate import _ from openerp import SUPERUSER_ID @@ -1173,11 +1175,10 @@ class task(base_stage, osv.osv): """ Override to updates the document according to the email. """ if custom_values is None: custom_values = {} custom_values.update({ - 'name': subject, + 'name': msg.get('subject'), 'planned_hours': 0.0, - 'subject': msg.get('subject'), }) - return super(project_tasks,self).message_new(cr, uid, msg, custom_values=custom_values, context=context) + return super(task,self).message_new(cr, uid, msg, custom_values=custom_values, context=context) def message_update(self, cr, uid, ids, msg, update_vals=None, context=None): """ Override to update the task according to the email. """ @@ -1202,7 +1203,7 @@ class task(base_stage, osv.osv): act = 'do_%s' % res.group(2).lower() if act: getattr(self,act)(cr, uid, ids, context=context) - return super(project_tasks,self).message_update(cr, uid, msg, update_vals=update_vals, context=context) + return super(task,self).message_update(cr, uid, msg, update_vals=update_vals, context=context) # --------------------------------------------------- # OpenChatter methods and notifications diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml index bd63eec84c5..686ae08ee73 100644 --- a/addons/project/project_view.xml +++ b/addons/project/project_view.xml @@ -163,7 +163,7 @@ <field name="arch" type="xml"> <search string="Search Project"> <field name="complete_name" string="Project Name"/> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter icon="terp-check" string="Open" name="Current" domain="[('state', '=','open')]" help="Open Projects"/> <filter icon="gtk-media-pause" string="Pending" name="Pending" domain="[('state', '=','pending')]" help="Pending Projects"/> @@ -619,7 +619,7 @@ <field name="arch" type="xml"> <search string="Tasks"> <field name="name" string="Tasks"/> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter name="draft" string="New" domain="[('state','=','draft')]" help="New Tasks" icon="terp-check"/> <filter name="open" string="In Progress" domain="[('state','=','open')]" help="In Progress Tasks" icon="terp-camera_test"/> diff --git a/addons/project_issue/project_issue.py b/addons/project_issue/project_issue.py index 2004e1310f0..9e434078d2f 100644 --- a/addons/project_issue/project_issue.py +++ b/addons/project_issue/project_issue.py @@ -27,6 +27,7 @@ from tools.translate import _ import binascii import time import tools +from tools import html2plaintext class project_issue_version(osv.osv): _name = "project.issue.version" @@ -462,9 +463,11 @@ class project_issue(base_stage, osv.osv): if context is None: context = {} context['state_to'] = 'draft' + desc = html2plaintext(msg.get('body')) if msg.get('body') else '' + custom_values.update({ 'name': msg.get('subject') or _("No Subject"), - 'description': msg.get('body'), + 'description': desc, 'email_from': msg.get('from'), 'email_cc': msg.get('cc'), 'user_id': False, @@ -486,7 +489,6 @@ class project_issue(base_stage, osv.osv): if update_vals is None: update_vals = {} # Update doc values according to the message - update_vals['description'] = msg.get('body', '') if msg.get('priority'): update_vals['priority'] = msg.get('priority') # Parse 'body' to find values to update diff --git a/addons/project_issue/project_issue_view.xml b/addons/project_issue/project_issue_view.xml index 3214b93c8d3..4cd9e2c19ff 100644 --- a/addons/project_issue/project_issue_view.xml +++ b/addons/project_issue/project_issue_view.xml @@ -198,7 +198,7 @@ <search string="Issue Tracker Search"> <field name="name" string="Issue" filter_domain="['|', '|',('partner_id','ilike',self),('email_from','ilike',self),('name','ilike',self)]"/> <field name="id"/> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter string="New" icon="terp-document-new" domain="[('state','=','draft')]" help="New Issues"/> <filter string="To Do" domain="[('state','=','open')]" help="To Do Issues" icon="terp-check"/> diff --git a/addons/project_long_term/project_long_term_demo.xml b/addons/project_long_term/project_long_term_demo.xml index bca8231fdaa..fda481d5a11 100644 --- a/addons/project_long_term/project_long_term_demo.xml +++ b/addons/project_long_term/project_long_term_demo.xml @@ -7,6 +7,7 @@ <record id="project.project_project_1" model="project.project"> <field name="resource_calendar_id" ref="resource.timesheet_group1"/> + <field name="use_phases" eval="True"/> </record> <!-- Project Phases --> diff --git a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml index 497ec9e3597..3efd133ffb6 100644 --- a/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml +++ b/addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml @@ -45,7 +45,6 @@ Create a timesheet sheet for HR manager - !record {model: hr_timesheet_sheet.sheet, id: hr_timesheet_sheet_sheet_sheetforhrmanager0}: - date_current: !eval time.strftime('%Y-05-%d') date_from: !eval "'%s-05-01' %(datetime.now().year)" date_to: !eval "'%s-05-31' %(datetime.now().year)" name: Sheet for hr manager diff --git a/addons/purchase/i18n/ar.po b/addons/purchase/i18n/ar.po index 378d405db66..f839347add1 100644 --- a/addons/purchase/i18n/ar.po +++ b/addons/purchase/i18n/ar.po @@ -7,14 +7,14 @@ msgstr "" "Project-Id-Version: OpenERP Server 5.0.4\n" "Report-Msgid-Bugs-To: support@openerp.com\n" "POT-Creation-Date: 2012-02-08 01:37+0100\n" -"PO-Revision-Date: 2012-02-08 02:55+0000\n" +"PO-Revision-Date: 2012-10-08 16:00+0000\n" "Last-Translator: kifcaliph <Unknown>\n" "Language-Team: \n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"X-Launchpad-Export-Date: 2012-09-07 04:56+0000\n" -"X-Generator: Launchpad (build 15914)\n" +"X-Launchpad-Export-Date: 2012-10-09 04:51+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: purchase #: model:process.transition,note:purchase.process_transition_confirmingpurchaseorder0 @@ -45,7 +45,7 @@ msgstr "المقصد" #: code:addons/purchase/purchase.py:236 #, python-format msgid "In order to delete a purchase order, it must be cancelled first!" -msgstr "" +msgstr "من أجل حذف أمر الشراء، يجب الإلغاء أولاً!" #. module: purchase #: help:purchase.report,date:0 @@ -87,7 +87,7 @@ msgstr "" #. module: purchase #: view:purchase.order:0 msgid "Approved purchase order" -msgstr "" +msgstr "الموافقة على أمر الشراء" #. module: purchase #: view:purchase.order:0 field:purchase.order,partner_id:0 @@ -149,7 +149,7 @@ msgstr "لا يوجد قائمة اسعار !" #. module: purchase #: model:ir.model,name:purchase.model_purchase_config_wizard msgid "purchase.config.wizard" -msgstr "" +msgstr "شراء.ضبط.صندوق حوار" #. module: purchase #: view:board.board:0 model:ir.actions.act_window,name:purchase.purchase_draft @@ -249,7 +249,7 @@ msgstr "يوم" #. module: purchase #: selection:purchase.order,invoice_method:0 msgid "Based on generated draft invoice" -msgstr "" +msgstr "على أساس توليد مسودة فاتورة" #. module: purchase #: view:purchase.report:0 @@ -259,7 +259,7 @@ msgstr "ترتيب اليوم" #. module: purchase #: view:board.board:0 msgid "Monthly Purchases by Category" -msgstr "" +msgstr "مشتريات شهرية حسب الفئة" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_purchase_line_product_tree @@ -269,7 +269,7 @@ msgstr "المشتريات" #. module: purchase #: view:purchase.order:0 msgid "Purchase order which are in draft state" -msgstr "" +msgstr "أمر الشراء التي هي في حالة مسودة" #. module: purchase #: view:purchase.order:0 @@ -392,7 +392,7 @@ msgstr "حركة مخزن" #: code:addons/purchase/purchase.py:419 #, python-format msgid "You must first cancel all invoices related to this purchase order." -msgstr "" +msgstr "يجب عليك أولا إلغاء جميع الفواتير المتعلقة بهذا أمر الشراء." #. module: purchase #: field:purchase.report,dest_address_id:0 @@ -436,13 +436,13 @@ msgstr "تم التحقق من الصلاحية عن طريق" #. module: purchase #: view:purchase.report:0 msgid "Order in last month" -msgstr "" +msgstr "طلب في الشهر الماضي" #. module: purchase #: code:addons/purchase/purchase.py:412 #, python-format msgid "You must first cancel all receptions related to this purchase order." -msgstr "" +msgstr "يجب عليك أولا إلغاء جميع حفلات الاستقبال المتعلقة بهذا أمر الشراء." #. module: purchase #: selection:purchase.order.line,state:0 @@ -467,7 +467,7 @@ msgstr "توضح انه قد تم عمل الاختيار" #. module: purchase #: view:purchase.order:0 msgid "Purchase orders which are in exception state" -msgstr "" +msgstr "أوامر الشراء التي هي في حالة الاستثناء" #. module: purchase #: report:purchase.order:0 field:purchase.report,validator:0 @@ -506,7 +506,7 @@ msgstr "تأكيد" #: model:ir.ui.menu,name:purchase.menu_action_picking_tree4_picking_to_invoice #: selection:purchase.order,invoice_method:0 msgid "Based on receptions" -msgstr "" +msgstr "استناداً على ما تم استقباله" #. module: purchase #: constraint:res.company:0 @@ -541,7 +541,7 @@ msgstr "" #. module: purchase #: view:purchase.order:0 msgid "Purchase order which are in the exception state" -msgstr "" +msgstr "أمر الشراء التي هي في حالة استثناء" #. module: purchase #: model:ir.actions.act_window,help:purchase.action_stock_move_report_po @@ -597,7 +597,7 @@ msgstr "السعر الإجمالي" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_import_create_supplier_installer msgid "Create or Import Suppliers" -msgstr "" +msgstr "إنشاء أو استيراد الموردون" #. module: purchase #: view:stock.picking:0 @@ -662,7 +662,7 @@ msgstr "" #. module: purchase #: report:purchase.order:0 msgid "Purchase Order Confirmation N°" -msgstr "" +msgstr "تأكيد أمر الشراء ن°" #. module: purchase #: model:ir.actions.act_window,help:purchase.action_purchase_order_report_all @@ -748,7 +748,7 @@ msgstr "الاستقبالات" #: code:addons/purchase/purchase.py:285 #, python-format msgid "You cannot confirm a purchase order without any lines." -msgstr "" +msgstr "لا يمكنك تأكيد أمر الشراء دون أية أسطر." #. module: purchase #: model:ir.actions.act_window,help:purchase.action_invoice_pending @@ -784,7 +784,7 @@ msgstr "يناير" #. module: purchase #: model:ir.actions.server,name:purchase.ir_actions_server_edi_purchase msgid "Auto-email confirmed purchase orders" -msgstr "" +msgstr "البريد التلقائي لتأكيد طلبات الشراء" #. module: purchase #: model:process.transition,name:purchase.process_transition_approvingpurchaseorder0 @@ -840,7 +840,7 @@ msgstr "دمج امر الشراء" #. module: purchase #: view:purchase.report:0 msgid "Order in current month" -msgstr "" +msgstr "طلب في الشهر الحالي" #. module: purchase #: view:purchase.report:0 field:purchase.report,delay_pass:0 @@ -881,7 +881,7 @@ msgstr "خطوط الاوامر الكلية للمستخدم لكل شهر" #. module: purchase #: view:purchase.order:0 msgid "Approved purchase orders" -msgstr "" +msgstr "تأكيد أوامر الشراء" #. module: purchase #: view:purchase.report:0 field:purchase.report,month:0 @@ -891,7 +891,7 @@ msgstr "شهر" #. module: purchase #: model:email.template,subject:purchase.email_template_edi_purchase msgid "${object.company_id.name} Order (Ref ${object.name or 'n/a' })" -msgstr "" +msgstr "${object.company_id.name} أمر (Ref ${object.name or 'n/a' })" #. module: purchase #: report:purchase.quotation:0 @@ -932,7 +932,7 @@ msgstr "ـكون هذه القائمة المختارة التي تم جمعها #. module: purchase #: view:stock.picking:0 msgid "Is a Back Order" -msgstr "" +msgstr "طلب عودة" #. module: purchase #: model:process.node,note:purchase.process_node_invoiceafterpacking0 @@ -984,7 +984,7 @@ msgstr "" #. module: purchase #: selection:purchase.config.wizard,default_method:0 msgid "Pre-Generate Draft Invoices based on Purchase Orders" -msgstr "" +msgstr "قبل انشاء مسودة فاتورة على طلبات الشراء" #. module: purchase #: model:ir.actions.act_window,name:purchase.action_view_purchase_line_invoice @@ -1010,7 +1010,7 @@ msgstr "عرض النتيجة" #. module: purchase #: selection:purchase.config.wizard,default_method:0 msgid "Based on Purchase Order Lines" -msgstr "" +msgstr "اعتماداً على سطور طلب الشراء" #. module: purchase #: help:purchase.order,amount_untaxed:0 @@ -1130,7 +1130,7 @@ msgstr "مرشحات مفصلة..." #. module: purchase #: view:purchase.config.wizard:0 msgid "Invoicing Control on Purchases" -msgstr "" +msgstr "التحكم في الفاتورة عند الشراء" #. module: purchase #: code:addons/purchase/wizard/purchase_order_group.py:48 diff --git a/addons/purchase/purchase_view.xml b/addons/purchase/purchase_view.xml index 7702d182b09..bfff06f5eaf 100644 --- a/addons/purchase/purchase_view.xml +++ b/addons/purchase/purchase_view.xml @@ -323,7 +323,7 @@ <field name="arch" type="xml"> <search string="Search Purchase Order"> <field name="name" string="Reference"/> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter icon="terp-document-new" name="draft" string="Quotations" domain="[('state','=','draft')]" help="Purchase orders which are in draft state"/> <filter icon="terp-check" name="approved" string="Purchase Orders" domain="[('state','not in',('draft','cancel'))]" help="Approved purchase orders"/> diff --git a/addons/sale/sale.py b/addons/sale/sale.py index 34f3c536917..ed7a2835425 100644 --- a/addons/sale/sale.py +++ b/addons/sale/sale.py @@ -793,6 +793,7 @@ class sale_order_line(osv.osv): _('There is no Fiscal Position defined or Income category account defined for default properties of Product categories.')) res = { 'name': line.name, + 'sequence': line.sequence, 'origin': line.order_id.name, 'account_id': account_id, 'price_unit': pu, diff --git a/addons/sale/sale_view.xml b/addons/sale/sale_view.xml index 5505a6a88b5..4323261a19d 100644 --- a/addons/sale/sale_view.xml +++ b/addons/sale/sale_view.xml @@ -337,7 +337,7 @@ <field name="arch" type="xml"> <search string="Search Sales Order"> <field name="name" string="Sales Order" filter_domain="['|',('name','ilike',self),('client_order_ref','ilike',self)]"/> - <filter icon="terp-mail-message-new" string="Inbox" help="Unread messages" name="message_unread" domain="[('message_unread','=',True)]"/> + <filter icon="terp-mail-message-new" string="Unread Messages" name="message_unread" domain="[('message_unread','=',True)]"/> <separator/> <filter icon="terp-document-new" string="Quotations" name="draft" domain="[('state','in',('draft','sent'))]" help="Sales Order that haven't yet been confirmed"/> <filter icon="terp-check" string="Sales" name="sales" domain="[('state','in',('manual','progress'))]"/> diff --git a/addons/share/i18n/ar.po b/addons/share/i18n/ar.po index 28215c8d748..c6fba54885c 100644 --- a/addons/share/i18n/ar.po +++ b/addons/share/i18n/ar.po @@ -8,14 +8,14 @@ msgstr "" "Project-Id-Version: openobject-addons\n" "Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n" "POT-Creation-Date: 2012-02-08 01:37+0100\n" -"PO-Revision-Date: 2012-01-13 21:14+0000\n" -"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" +"PO-Revision-Date: 2012-10-08 15:06+0000\n" +"Last-Translator: almodhesh <Unknown>\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-08-28 06:34+0000\n" -"X-Generator: Launchpad (build 15864)\n" +"X-Launchpad-Export-Date: 2012-10-09 04:51+0000\n" +"X-Generator: Launchpad (build 16112)\n" #. module: share #: field:share.wizard,embed_option_title:0 @@ -25,7 +25,7 @@ msgstr "عرض العنوان" #. module: share #: view:share.wizard:0 msgid "Access granted!" -msgstr "" +msgstr "منح الوصول!" #. module: share #: field:share.wizard,user_type:0 @@ -50,13 +50,15 @@ msgstr "مشاركة" #. module: share #: field:share.wizard,share_root_url:0 msgid "Share Access URL" -msgstr "" +msgstr "مشاركة الوصول URL" #. module: share #: code:addons/share/wizard/share_wizard.py:782 #, python-format msgid "You may use your current login (%s) and password to view them.\n" msgstr "" +"يمكنك استخدام معلومات تسجيل الدخول الخاصة بك الحالية(%s) وكلمة المرور " +"لعرضها.\n" #. module: share #: code:addons/share/wizard/share_wizard.py:601 @@ -83,24 +85,25 @@ msgstr "" #. module: share #: field:share.wizard,embed_url:0 field:share.wizard.result.line,share_url:0 msgid "Share URL" -msgstr "" +msgstr "مشاركة URL" #. module: share #: code:addons/share/wizard/share_wizard.py:776 #, python-format msgid "These are your credentials to access this protected area:\n" -msgstr "" +msgstr "هذا هو اعتمادك للوصول إلى هذه المناطق المحمية\n" #. module: share #: code:addons/share/wizard/share_wizard.py:643 #, python-format msgid "You must be a member of the Share/User group to use the share wizard" msgstr "" +"يجب أن تكون عضوا في المجموعة مشاركة / المستخدم لاستخدام مشاركة صندوق الحوار" #. module: share #: view:share.wizard:0 msgid "Access info" -msgstr "" +msgstr "معلومات الولوج" #. module: share #: view:share.wizard:0 @@ -111,12 +114,12 @@ msgstr "حصة" #: code:addons/share/wizard/share_wizard.py:551 #, python-format msgid "(Duplicated for modified sharing permissions)" -msgstr "" +msgstr "(تكرار للحصول على أذونات مشاركة تعديل)" #. module: share #: help:share.wizard,domain:0 msgid "Optional domain for further data filtering" -msgstr "" +msgstr "المجال اختياري لمزيد من بيانات التصفية" #. module: share #: sql_constraint:res.users:0 @@ -159,7 +162,7 @@ msgstr "إغلاق" #: code:addons/share/wizard/share_wizard.py:640 #, python-format msgid "Action and Access Mode are required to create a shared access" -msgstr "" +msgstr "نمط الإجراء والوصول لإنشاء مشاركة الوصول" #. module: share #: view:share.wizard:0 @@ -167,6 +170,7 @@ msgid "" "Please select the action that opens the screen containing the data you want " "to share." msgstr "" +"الرجاء اختيار الإجراء الذي يفتح شاشة تحتوي على البيانات التي تريد مشاركتها." #. module: share #: code:addons/share/wizard/share_wizard.py:781 @@ -174,7 +178,7 @@ msgstr "" msgid "" "The documents have been automatically added to your current OpenERP " "documents.\n" -msgstr "" +msgstr "المستندات تمت إضافتها بشكل تلقائي لمستندات اوبن اي ار بي الخاصة بك\n" #. module: share #: view:share.wizard:0 @@ -184,7 +188,7 @@ msgstr "إلغاء" #. module: share #: field:res.groups,share:0 msgid "Share Group" -msgstr "" +msgstr "مشاركة المجموعة" #. module: share #: code:addons/share/wizard/share_wizard.py:763 @@ -197,12 +201,12 @@ msgstr "لا بدّ من ذكر عنوان بريد إلكتروني" msgid "" "Optionally, you may specify an additional domain restriction that will be " "applied to the shared data." -msgstr "" +msgstr "اختيارياً، اختيار محدودية مجال لتطبيقها على المعلومات المشاركة." #. module: share #: help:share.wizard,name:0 msgid "Title for the share (displayed to users as menu and shortcut name)" -msgstr "" +msgstr "عنوان المشاركة (تعرض للمستخدمين كقائمة واسم مختصر)" #. module: share #: view:share.wizard:0 @@ -212,7 +216,7 @@ msgstr "خيارات" #. module: share #: view:res.groups:0 msgid "Regular groups only (no share groups" -msgstr "" +msgstr "المجموعات المنتظمة فقط( لا مجموعات مشاركة)" #. module: share #: code:addons/share/wizard/share_wizard.py:787 @@ -222,27 +226,32 @@ msgid "" "Sales, HR, etc.)\n" "It is open source and can be found on http://www.openerp.com." msgstr "" +"OpenERP هي قوية ومجموعة من تطبيقات الأعمال سهل الاستعمال(إدارة علاقات " +"العملاء,المبيعات, موارد بشرية, الخ.)\n" +"هي مفتوحة المصدر ويمكنك الوصول إليها عن طريق هذا الرابط " +"http://www.openerp.com." #. module: share #: field:share.wizard,action_id:0 msgid "Action to share" -msgstr "" +msgstr "إجراء للمشاركة" #. module: share #: view:share.wizard:0 msgid "Optional: include a personal message" -msgstr "" +msgstr "اختياري: تضمين رسالة خاصة" #. module: share #: field:res.users,share:0 msgid "Share User" -msgstr "" +msgstr "مشاركة المستخدم" #. module: share #: code:addons/share/wizard/share_wizard.py:647 #, python-format msgid "Please indicate the emails of the persons to share with, one per line" msgstr "" +"يرجى الإشارة إلى رسائل البريد الإلكتروني للأشخاص للمشاركة معه، واحد في كل سطر" #. module: share #: field:share.wizard,embed_code:0 field:share.wizard.result.line,user_id:0 @@ -252,13 +261,13 @@ msgstr "مجهول" #. module: share #: help:res.groups,share:0 msgid "Group created to set access rights for sharing data with some users." -msgstr "" +msgstr "إنشاء مجموعة لتعيين حقوق الوصول لتبادل البيانات مع بعض المستخدمين." #. module: share #: help:share.wizard,action_id:0 msgid "" "The action that opens the screen containing the data you wish to share." -msgstr "" +msgstr "الإجراء الذي يفتح شاشة تحتوي على البيانات التي ترغب في مشاركتها." #. module: share #: constraint:res.users:0 @@ -270,12 +279,12 @@ msgstr "" #: code:addons/share/wizard/share_wizard.py:526 #, python-format msgid "(Copy for sharing)" -msgstr "" +msgstr "(نسخة من المشاركة)" #. module: share #: field:share.wizard.result.line,newly_created:0 msgid "Newly created" -msgstr "" +msgstr "منشأة حديثاً" #. module: share #: code:addons/share/wizard/share_wizard.py:616 @@ -292,7 +301,7 @@ msgstr "" #. module: share #: help:share.wizard,share_root_url:0 msgid "Main access page for users that are granted shared access" -msgstr "" +msgstr "صفحة الوصول الرئيسية للمستخدمين لضمان مشاركة الوصول" #. module: share #: sql_constraint:res.groups:0 @@ -343,12 +352,12 @@ msgstr "ملخّص" #: code:addons/share/wizard/share_wizard.py:493 #, python-format msgid "Copied access for sharing" -msgstr "" +msgstr "نسخ الوصول للمشاركة" #. module: share #: model:ir.actions.act_window,name:share.action_share_wizard_step1 msgid "Share your documents" -msgstr "" +msgstr "مشاركة المستندات الخاصة بك" #. module: share #: view:share.wizard:0 @@ -370,22 +379,22 @@ msgstr "share.wizard.result.line" #. module: share #: help:share.wizard,user_type:0 msgid "Select the type of user(s) you would like to share data with." -msgstr "" +msgstr "استخدم نوع المستخدم الذي تود مشاركة المعلومات معه" #. module: share #: field:share.wizard,view_type:0 msgid "Current View Type" -msgstr "" +msgstr "نوع عرض الحالي" #. module: share #: selection:share.wizard,access_mode:0 msgid "Can view" -msgstr "" +msgstr "امكانية المشاهدة" #. module: share #: selection:share.wizard,access_mode:0 msgid "Can edit" -msgstr "" +msgstr "امكانية التعديل" #. module: share #: help:share.wizard,message:0 @@ -528,3 +537,10 @@ msgstr "مشاركة مع..." #~ msgid "Share with these people (one e-mail per line)" #~ msgstr "المشاركة مع هؤلاء الناس (بريد إلكتروني واحد في كل سطر)" + +#~ msgid "" +#~ "An optional personal message, to be included in the e-mail notification." +#~ msgstr "رسالة اختيارية شخصية، ليتم تضمينها في اخطار البريد الإلكتروني." + +#~ msgid "Use this link" +#~ msgstr "استخدم هذا الرابط" |
