summaryrefslogtreecommitdiff
path: root/addons
diff options
context:
space:
mode:
authorMayur Maheshwari (OpenERP) <mma@tinyerp.com>2012-10-10 10:49:17 +0530
committerMayur Maheshwari (OpenERP) <mma@tinyerp.com>2012-10-10 10:49:17 +0530
commit79a84ffc5d2afaec71225b0e70dcccb79ecc8239 (patch)
tree0e6b2ae62ba8d02d7c1d987b438a02ed7baea07f /addons
parent9487c2d9ba9381e04c677d861864e29895f48e8e (diff)
parent49b102b54a860e655156fced1940ae6b913a7458 (diff)
[MERGE]: Merge with lp:openobject-addons
bzr revid: mma@tinyerp.com-20121010051917-0ep6z3xy0ff92kzo
Diffstat (limited to 'addons')
-rw-r--r--addons/account/account_invoice.py1
-rw-r--r--addons/account/account_invoice_view.xml1
-rw-r--r--addons/account_analytic_default/i18n/hu.po13
-rw-r--r--addons/account_budget/i18n/hu.po27
-rw-r--r--addons/account_budget/i18n/lo.po32
-rw-r--r--addons/account_followup/i18n/ar.po28
-rw-r--r--addons/auth_oauth/auth_oauth_data.xml2
-rw-r--r--addons/auth_reset_password/__init__.py24
-rw-r--r--addons/auth_reset_password/__openerp__.py29
-rw-r--r--addons/auth_reset_password/auth_reset_password.py130
-rw-r--r--addons/auth_reset_password/auth_reset_password.xml47
-rw-r--r--addons/auth_reset_password/controllers/__init__.py24
-rw-r--r--addons/auth_reset_password/controllers/main.py52
-rw-r--r--addons/auth_reset_password/res_users.py59
-rw-r--r--addons/auth_reset_password/res_users_view.xml19
-rw-r--r--addons/auth_reset_password/static/src/css/reset_password.css12
-rw-r--r--addons/auth_reset_password/static/src/js/reset_password.js74
-rw-r--r--addons/auth_reset_password/static/src/xml/reset_password.xml21
-rw-r--r--addons/auth_signup/__openerp__.py2
-rw-r--r--addons/auth_signup/controllers/main.py56
-rw-r--r--addons/auth_signup/res_config.py11
-rw-r--r--addons/auth_signup/res_config.xml4
-rw-r--r--addons/auth_signup/res_users.py238
-rw-r--r--addons/auth_signup/res_users_view.xml19
-rw-r--r--addons/auth_signup/static/src/css/Makefile3
-rw-r--r--addons/auth_signup/static/src/css/base.css10
-rw-r--r--addons/auth_signup/static/src/css/base.sass13
-rw-r--r--addons/auth_signup/static/src/js/auth_signup.js121
-rw-r--r--addons/auth_signup/static/src/xml/auth_signup.xml44
-rw-r--r--addons/base_calendar/crm_meeting_view.xml2
-rw-r--r--addons/crm/crm_lead.py5
-rw-r--r--addons/crm/crm_lead_view.xml11
-rw-r--r--addons/crm/res_partner_view.xml19
-rw-r--r--addons/crm_claim/crm_claim.py4
-rw-r--r--addons/crm_helpdesk/crm_helpdesk.py4
-rw-r--r--addons/crm_partner_assign/crm_lead_view.xml2
-rw-r--r--addons/event/event_view.xml4
-rw-r--r--addons/hr/hr_view.xml2
-rw-r--r--addons/hr_contract/i18n/mk.po271
-rw-r--r--addons/hr_expense/hr_expense_view.xml5
-rw-r--r--addons/hr_holidays/i18n/mk.po864
-rw-r--r--addons/hr_recruitment/hr_recruitment.py4
-rw-r--r--addons/hr_recruitment/hr_recruitment_view.xml2
-rw-r--r--addons/hr_timesheet_sheet/__openerp__.py3
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet.py163
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet_demo.xml1
-rw-r--r--addons/hr_timesheet_sheet/hr_timesheet_sheet_view.xml21
-rw-r--r--addons/hr_timesheet_sheet/report/timesheet_report.py18
-rw-r--r--addons/hr_timesheet_sheet/report/timesheet_report_view.xml2
-rw-r--r--addons/hr_timesheet_sheet/static/src/css/timesheet.css79
-rw-r--r--addons/hr_timesheet_sheet/static/src/js/timesheet.js299
-rw-r--r--addons/hr_timesheet_sheet/static/src/xml/timesheet.xml56
-rw-r--r--addons/hr_timesheet_sheet/test/test_hr_timesheet_sheet.yml25
-rw-r--r--addons/hr_timesheet_sheet/wizard/hr_timesheet_current.py1
-rw-r--r--addons/mail/data/mail_demo.xml5
-rw-r--r--addons/mail/mail_mail.py10
-rw-r--r--addons/mail/mail_message.py5
-rw-r--r--addons/mail/mail_thread.py124
-rw-r--r--addons/mail/res_config.py13
-rw-r--r--addons/mail/res_users.py16
-rw-r--r--addons/mail/security/mail_security.xml2
-rw-r--r--addons/mail/static/src/js/mail_followers.js1
-rw-r--r--addons/mail/tests/test_mail.py2
-rw-r--r--addons/marketing_campaign/i18n/ar.po80
-rw-r--r--addons/portal/__openerp__.py1
-rw-r--r--addons/portal/mail_mail.py34
-rw-r--r--addons/portal/portal_view.xml2
-rw-r--r--addons/portal/tests/test_portal.py76
-rw-r--r--addons/portal/wizard/portal_wizard.py60
-rw-r--r--addons/portal/wizard/portal_wizard_view.xml2
-rw-r--r--addons/process/static/src/js/process.js4
-rw-r--r--addons/project/project.py15
-rw-r--r--addons/project/project_view.xml4
-rw-r--r--addons/project_issue/project_issue.py6
-rw-r--r--addons/project_issue/project_issue_view.xml2
-rw-r--r--addons/project_long_term/project_long_term_demo.xml1
-rw-r--r--addons/project_timesheet/test/worktask_entry_to_timesheetline_entry.yml1
-rw-r--r--addons/purchase/i18n/ar.po52
-rw-r--r--addons/purchase/purchase_view.xml2
-rw-r--r--addons/sale/sale.py1
-rw-r--r--addons/sale/sale_view.xml2
-rw-r--r--addons/share/i18n/ar.po78
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="#">&lt; 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', '&lt;&gt;', '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]), '&amp;', ('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]), '&amp;', ('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 "استخدم هذا الرابط"