\n"
+" "
#. module: crm
#: field:crm.opportunity2phonecall,contact_name:0
@@ -445,16 +462,16 @@ msgid "#Opportunities"
msgstr "#商机"
#. module: crm
-#: code:addons/crm/crm_lead.py:624
+#: code:addons/crm/crm_lead.py:640
#, python-format
msgid ""
"Please select more than one element (lead or opportunity) from the list view."
-msgstr ""
+msgstr "请从列表视图中选择一个以上元素(线索或机会)。"
#. module: crm
#: view:crm.lead:0
msgid "Leads that are assigned to one of the sale teams I manage, or to me"
-msgstr ""
+msgstr "分配给我锁管理的某个销售队伍,或给我的线索。"
#. module: crm
#: field:crm.lead,partner_address_email:0
@@ -473,6 +490,16 @@ msgid ""
" \n"
" "
msgstr ""
+"
\n"
+" "
#~ msgid "My Draft "
#~ msgstr "我的草稿 "
diff --git a/addons/crm_partner_assign/__openerp__.py b/addons/crm_partner_assign/__openerp__.py
index 37a8c5a3d6b..e0f1d78558a 100644
--- a/addons/crm_partner_assign/__openerp__.py
+++ b/addons/crm_partner_assign/__openerp__.py
@@ -38,7 +38,7 @@ You can also use the geolocalization without using the GPS coordinates.
""",
'author': 'OpenERP SA',
'depends': ['crm', 'account', 'portal'],
- 'demo': ['res_partner_demo.xml'],
+ 'demo': ['res_partner_demo.xml', 'crm_lead_demo.xml'],
'data': [
'security/ir.model.access.csv',
'res_partner_view.xml',
diff --git a/addons/crm_partner_assign/crm_lead_demo.xml b/addons/crm_partner_assign/crm_lead_demo.xml
new file mode 100644
index 00000000000..6631131d054
--- /dev/null
+++ b/addons/crm_partner_assign/crm_lead_demo.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+ lead
+ Specifications and price of your phones
+ Steve Martinez
+
+
+ Reseller
+
+ Edinburgh
+
+
+
+ 2
+
+
+
+ Hi,
+
+ Please, can you give me more details about your phones, including their specifications and their prices.
+
+ Regards,
+ Steve
+
+
+
+
+
\ No newline at end of file
diff --git a/addons/crm_partner_assign/i18n/zh_CN.po b/addons/crm_partner_assign/i18n/zh_CN.po
index af764d72747..d69d52029fc 100644
--- a/addons/crm_partner_assign/i18n/zh_CN.po
+++ b/addons/crm_partner_assign/i18n/zh_CN.po
@@ -8,14 +8,14 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
"POT-Creation-Date: 2012-12-21 17:05+0000\n"
-"PO-Revision-Date: 2012-12-14 13:50+0000\n"
-"Last-Translator: 盈通 ccdos \n"
+"PO-Revision-Date: 2013-07-23 06:22+0000\n"
+"Last-Translator: fanvil \n"
"Language-Team: Chinese (Simplified) \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-03-16 05:47+0000\n"
-"X-Generator: Launchpad (build 16532)\n"
+"X-Launchpad-Export-Date: 2013-07-24 05:31+0000\n"
+"X-Generator: Launchpad (build 16700)\n"
#. module: crm_partner_assign
#: field:crm.lead.report.assign,delay_close:0
@@ -37,7 +37,7 @@ msgstr "计划收入"
msgid ""
"Message type: email for email message, notification for system message, "
"comment for other messages such as user replies"
-msgstr ""
+msgstr "消息类型:Email 用于 邮件消息, 通知用户系统消息,评论用于其他消息,例如用户回复。"
#. module: crm_partner_assign
#: field:crm.lead.report.assign,nbr:0
@@ -53,7 +53,7 @@ msgstr "分组..."
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,body:0
msgid "Automatically sanitized HTML contents"
-msgstr ""
+msgstr "自动整理HTML内容"
#. module: crm_partner_assign
#: view:crm.lead:0
@@ -68,7 +68,7 @@ msgstr "geolocalization定位"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,starred:0
msgid "Starred"
-msgstr ""
+msgstr "加星号的邮件"
#. module: crm_partner_assign
#: view:crm.lead.forward.to.partner:0
@@ -80,7 +80,7 @@ msgstr "正文"
msgid ""
"Email address of the sender. This field is set when no matching partner is "
"found for incoming emails."
-msgstr ""
+msgstr "发送者的Email地址。当收取的email没有对应的合作伙伴时,此字段被设置"
#. module: crm_partner_assign
#: view:crm.partner.report.assign:0
@@ -111,7 +111,7 @@ msgstr "公司"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,notification_ids:0
msgid "Notifications"
-msgstr ""
+msgstr "通知"
#. module: crm_partner_assign
#: field:crm.lead.report.assign,date_assign:0
@@ -123,7 +123,7 @@ msgstr "业务伙伴日期"
#: view:crm.partner.report.assign:0
#: view:res.partner:0
msgid "Salesperson"
-msgstr ""
+msgstr "销售员"
#. module: crm_partner_assign
#: selection:crm.lead.report.assign,priority:0
@@ -170,7 +170,7 @@ msgstr "指派的geolocalization"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_crm_lead_forward_to_partner
msgid "Email composition wizard"
-msgstr ""
+msgstr "Email撰写向导"
#. module: crm_partner_assign
#: field:crm.partner.report.assign,turnover:0
@@ -192,7 +192,7 @@ msgstr "为线索指定一个业务伙伴的概率(0表示没指派)"
#. module: crm_partner_assign
#: view:res.partner:0
msgid "Partner Activation"
-msgstr ""
+msgstr "激活合作伙伴"
#. module: crm_partner_assign
#: selection:crm.lead.forward.to.partner,type:0
@@ -203,7 +203,7 @@ msgstr "系统通知"
#: code:addons/crm_partner_assign/wizard/crm_forward_to_partner.py:74
#, python-format
msgid "Lead forward"
-msgstr ""
+msgstr "转发的线索"
#. module: crm_partner_assign
#: field:crm.lead.report.assign,probability:0
@@ -284,7 +284,7 @@ msgstr "最低"
#. module: crm_partner_assign
#: view:crm.partner.report.assign:0
msgid "Date Invoice"
-msgstr ""
+msgstr "发票排程"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,template_id:0
@@ -309,7 +309,7 @@ msgstr "创建日期"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_res_partner_activation
msgid "res.partner.activation"
-msgstr ""
+msgstr "res.partner.activation"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,parent_id:0
@@ -344,7 +344,7 @@ msgstr "7月"
#. module: crm_partner_assign
#: view:crm.partner.report.assign:0
msgid "Date Review"
-msgstr ""
+msgstr "回顾排程"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@@ -356,12 +356,12 @@ msgstr "阶段"
#: view:crm.lead.report.assign:0
#: field:crm.lead.report.assign,state:0
msgid "Status"
-msgstr ""
+msgstr "状态"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,to_read:0
msgid "To read"
-msgstr ""
+msgstr "查看"
#. module: crm_partner_assign
#: code:addons/crm_partner_assign/wizard/crm_forward_to_partner.py:74
@@ -416,12 +416,12 @@ msgstr "到期天数"
#: help:crm.lead.forward.to.partner,notified_partner_ids:0
msgid ""
"Partners that have a notification pushing this message in their mailboxes"
-msgstr ""
+msgstr "推送此通知消息进他们邮箱的合作伙伴"
#. module: crm_partner_assign
#: selection:crm.lead.forward.to.partner,type:0
msgid "Comment"
-msgstr ""
+msgstr "评论"
#. module: crm_partner_assign
#: field:res.partner,partner_weight:0
@@ -449,7 +449,7 @@ msgstr "12月"
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,vote_user_ids:0
msgid "Users that voted for this message"
-msgstr ""
+msgstr "投票给这条消息的用户"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@@ -465,13 +465,13 @@ msgstr "开启日期"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,child_ids:0
msgid "Child Messages"
-msgstr ""
+msgstr "子信息"
#. module: crm_partner_assign
#: field:crm.partner.report.assign,date_review:0
#: field:res.partner,date_review:0
msgid "Latest Partner Review"
-msgstr ""
+msgstr "最新合作伙伴评论"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,subject:0
@@ -481,17 +481,17 @@ msgstr "主题"
#. module: crm_partner_assign
#: view:crm.lead.forward.to.partner:0
msgid "or"
-msgstr ""
+msgstr "或"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,body:0
msgid "Contents"
-msgstr ""
+msgstr "内容"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,vote_user_ids:0
msgid "Votes"
-msgstr ""
+msgstr "投票"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@@ -501,13 +501,13 @@ msgstr "#商机"
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,starred:0
msgid "Current user has a starred notification linked to this message"
-msgstr ""
+msgstr "当前用户用 星号 提醒关联到这条消息"
#. module: crm_partner_assign
#: field:crm.partner.report.assign,date_partnership:0
#: field:res.partner,date_partnership:0
msgid "Partnership Date"
-msgstr ""
+msgstr "合作关系日期"
#. module: crm_partner_assign
#: view:crm.lead:0
@@ -661,7 +661,7 @@ msgstr ""
#. module: crm_partner_assign
#: field:crm.partner.report.assign,period_id:0
msgid "Invoice Period"
-msgstr ""
+msgstr "发票期间"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_res_partner_grade
@@ -682,7 +682,7 @@ msgstr "附件"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,record_name:0
msgid "Message Record Name"
-msgstr ""
+msgstr "消息记录名称"
#. module: crm_partner_assign
#: field:res.partner.activation,sequence:0
@@ -696,7 +696,7 @@ msgstr "序列"
msgid ""
"Cannot contact geolocation servers. Please make sure that your internet "
"connection is up and running (%s)."
-msgstr ""
+msgstr "无法连接地理信息服务器。请确保你的互联网链接畅通(%s)。"
#. module: crm_partner_assign
#: selection:crm.lead.report.assign,month:0
@@ -722,7 +722,7 @@ msgstr "开启"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,subtype_id:0
msgid "Subtype"
-msgstr ""
+msgstr "子类型"
#. module: crm_partner_assign
#: field:res.partner,date_localization:0
@@ -737,12 +737,12 @@ msgstr "当前的"
#. module: crm_partner_assign
#: model:ir.model,name:crm_partner_assign.model_crm_lead
msgid "Lead/Opportunity"
-msgstr ""
+msgstr "线索/商机"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,notified_partner_ids:0
msgid "Notified partners"
-msgstr ""
+msgstr "已通知的合作伙伴"
#. module: crm_partner_assign
#: view:crm.lead.forward.to.partner:0
@@ -773,7 +773,7 @@ msgstr "可能收入"
#: field:res.partner,activation:0
#: view:res.partner.activation:0
msgid "Activation"
-msgstr ""
+msgstr "激活"
#. module: crm_partner_assign
#: view:crm.lead:0
@@ -784,12 +784,12 @@ msgstr "指定的业务伙伴"
#. module: crm_partner_assign
#: field:res.partner,grade_id:0
msgid "Partner Level"
-msgstr ""
+msgstr "合作伙伴级别"
#. module: crm_partner_assign
#: help:crm.lead.forward.to.partner,to_read:0
msgid "Current user has an unread notification linked to this message"
-msgstr ""
+msgstr "当前用户有关联到这条消息的未读的提醒"
#. module: crm_partner_assign
#: selection:crm.lead.report.assign,type:0
@@ -815,7 +815,7 @@ msgstr "名称"
#: model:ir.actions.act_window,name:crm_partner_assign.res_partner_activation_act
#: model:ir.ui.menu,name:crm_partner_assign.res_partner_activation_config_mi
msgid "Partner Activations"
-msgstr ""
+msgstr "合作伙伴激活"
#. module: crm_partner_assign
#: view:crm.lead.report.assign:0
@@ -872,7 +872,7 @@ msgstr "客户关系管理 线索报表"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,composition_mode:0
msgid "Composition mode"
-msgstr ""
+msgstr "写作模式"
#. module: crm_partner_assign
#: field:crm.lead.forward.to.partner,model:0
diff --git a/addons/crm_todo/crm_todo_view.xml b/addons/crm_todo/crm_todo_view.xml
index 851c2abfba4..96d00e76932 100644
--- a/addons/crm_todo/crm_todo_view.xml
+++ b/addons/crm_todo/crm_todo_view.xml
@@ -16,12 +16,8 @@
-
-
-
-
-
-
+
+
diff --git a/addons/google_drive/i18n/pt_BR.po b/addons/google_drive/i18n/pt_BR.po
index 574eb5abc0c..be9806e4c66 100644
--- a/addons/google_drive/i18n/pt_BR.po
+++ b/addons/google_drive/i18n/pt_BR.po
@@ -8,20 +8,19 @@ msgstr ""
"Project-Id-Version: openobject-addons\n"
"Report-Msgid-Bugs-To: FULL NAME \n"
"POT-Creation-Date: 2013-06-27 16:03+0000\n"
-"PO-Revision-Date: 2012-12-07 22:47+0000\n"
-"Last-Translator: Fábio Martinelli - http://zupy.com.br "
-"\n"
+"PO-Revision-Date: 2013-07-20 22:32+0000\n"
+"Last-Translator: Claudio de Araujo Santos \n"
"Language-Team: Brazilian Portuguese \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
-"X-Launchpad-Export-Date: 2013-06-28 05:42+0000\n"
-"X-Generator: Launchpad (build 16681)\n"
+"X-Launchpad-Export-Date: 2013-07-22 05:34+0000\n"
+"X-Generator: Launchpad (build 16696)\n"
#. module: google_drive
#: model:ir.ui.menu,name:google_drive.menu_google_drive_config
msgid "Google Drive configuration"
-msgstr ""
+msgstr "Configuração do Google Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:48
@@ -40,6 +39,13 @@ msgid ""
"in your Google Drive and in OpenERP attachment will be named\n"
" 'Agrolait_SO0001_Sales'."
msgstr ""
+"O nome do documento em anexo pode usar dados fixos ou variáveis. Para "
+"distinguir entre documentos\n"
+" Google Drive, usar palavras fixos e campos. "
+"Por exemplo, no exemplo acima, se escreveu Agrolait_% (nome) s_Sales\n"
+" no campo Nome da unidade do Google, o "
+"documento em seu Google Drive e em OpenERP anexo será nomeado\n"
+" 'Agrolait_SO0001_Sales \"."
#. module: google_drive
#: view:google.drive.config:0
@@ -47,16 +53,18 @@ msgid ""
"- If filter is not specified, link of google document will appear in "
"\"More\" option for all users for all opportunities."
msgstr ""
+"- Se o filtro não for especificado, ligação de documento google aparecerá na "
+"opção \"Mais\" para todos os usuários de todas as oportunidades."
#. module: google_drive
#: view:google.drive.config:0
msgid "To create a new filter:"
-msgstr ""
+msgstr "Para criar um novo filtro:"
#. module: google_drive
#: model:ir.model,name:google_drive.model_base_config_settings
msgid "base.config.settings"
-msgstr ""
+msgstr "base.config.settings"
#. module: google_drive
#: model:ir.actions.act_window,help:google_drive.action_google_drive_users_config
@@ -74,17 +82,30 @@ msgid ""
" \n"
" "
msgstr ""
+"
\n"
+" Clique para adicionar um novo modelo.\n"
+" P>\n"
+"
\n"
+" Vincular seus próprios modelos Google Drive para "
+"qualquer registro de OpenERP. Se você tem documentos específicos que você "
+"realmente deseja que seu colaborador preencher, por exemplo, usar uma "
+"planilha para controlar a qualidade do seu produto ou rever a lista de "
+"entrega para cada ordem em um país estrangeiro, ... É muito fácil de "
+"gerenciá-los, associá-los a OpenERP e usá-los para colaborar com seus "
+"empregados.\n"
+" P>\n"
+" "
#. module: google_drive
#: code:addons/google_drive/google_drive.py:150
#, python-format
msgid "Incorrect URL!"
-msgstr ""
+msgstr "URL incorreto!"
#. module: google_drive
#: view:base.config.settings:0
msgid "Configure your templates"
-msgstr ""
+msgstr "Configure seus modelos"
#. module: google_drive
#: help:google.drive.config,name_template:0
@@ -92,6 +113,8 @@ msgid ""
"Choose how the new google drive will be named, on google side. Eg. "
"gdoc_%(field_name)s"
msgstr ""
+"Escolha como a nova unidade google será nomeado, no lado google. Por "
+"exemplo. gdoc_% (field_name) s"
#. module: google_drive
#: view:google.drive.config:0
@@ -99,6 +122,8 @@ msgid ""
"- Go to the OpenERP document you want to filter. For instance, go to "
"Opportunities and search on Sales Department."
msgstr ""
+"- Vá para o documento OpenERP você deseja filtrar. Por exemplo, ir para "
+"Oportunidades e de pesquisa no Departamento de Vendas."
#. module: google_drive
#: view:google.drive.config:0
@@ -106,6 +131,8 @@ msgid ""
"- In this \"Search\" view, select the option \"Save Current Filter\", enter "
"the name (Ex: Sales Department)"
msgstr ""
+"- Neste ponto de vista \"Pesquisar\", selecione a opção \"Salvar Filtro "
+"Atual\", digite o nome (Ex: Departamento de Vendas)"
#. module: google_drive
#: view:google.drive.config:0
@@ -114,6 +141,9 @@ msgid ""
"\"More\" options will appear for all users in opportunities of Sales "
"Department."
msgstr ""
+"- Se você selecionar \"Compartilhar com todos os usuários\", ligação de "
+"documento google na opção \"Mais\" será exibido para todos os usuários em "
+"oportunidades do Departamento de Vendas."
#. module: google_drive
#: view:google.drive.config:0
@@ -122,23 +152,28 @@ msgid ""
"\"More\" options will not appear for other users in opportunities of Sales "
"Department."
msgstr ""
+"- Se você não selecionar \"Compartilhar com todos os usuários\", link do "
+"documento google em \"More\" opções não aparecerá para outros usuários em "
+"oportunidades do Departamento de Vendas."
#. module: google_drive
#: code:addons/google_drive/google_drive.py:48
#, python-format
msgid "At least one key cannot be found in your Google Drive name pattern"
msgstr ""
+"Pelo menos uma chave não pode ser encontrado em seu padrão de nome de Google "
+"Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:150
#, python-format
msgid "Please enter a valid Google Document URL."
-msgstr ""
+msgstr "Por favor insira um URL Documento Google válida."
#. module: google_drive
#: field:google.drive.config,google_drive_client_id:0
msgid "Google Client "
-msgstr ""
+msgstr "Google Cliente "
#. module: google_drive
#: view:google.drive.config:0
@@ -146,43 +181,46 @@ msgid ""
"https://docs.google.com/document/d/1vOtpJK9scIQz6taD9tJRIETWbEw3fSiaQHArsJYcu"
"a4/edit"
msgstr ""
+"https://docs.google.com/document/d/1vOtpJK9scIQz6taD9tJRIETWbEw3fSiaQHArsJYcu"
+"a4/edit"
#. module: google_drive
#: field:google.drive.config,filter_id:0
msgid "Filter"
-msgstr ""
+msgstr "Filtro"
#. module: google_drive
#: field:google.drive.config,name_template:0
msgid "Google Drive Name Pattern"
-msgstr ""
+msgstr "Nome Padrão Google Drive"
#. module: google_drive
#: help:base.config.settings,google_drive_uri:0
msgid "The URL to generate the authorization code from Google"
-msgstr ""
+msgstr "O URL para gerar o código de autorização do Google"
#. module: google_drive
#: model:ir.filters,name:google_drive.filter_partner
msgid "Customer"
-msgstr ""
+msgstr "Cliente"
#. module: google_drive
#: field:google.drive.config,google_drive_resource_id:0
msgid "Resource Id"
-msgstr ""
+msgstr "ID Recurso"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:91
#, python-format
msgid "The Google Template cannot be found. Maybe it has been deleted."
msgstr ""
+"O modelo do Google não pode ser encontrado. Talvez tenha sido excluído."
#. module: google_drive
#: model:ir.actions.act_window,name:google_drive.action_google_drive_users_config
#: model:ir.ui.menu,name:google_drive.menu_google_drive_model_config
msgid "Google Drive Templates"
-msgstr ""
+msgstr "Templates do Google Drive"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:81
@@ -191,23 +229,25 @@ msgid ""
"Something went wrong during the token generation. Please request again an "
"authorization code in %(menu:base_setup.menu_general_configuration)s."
msgstr ""
+"Algo deu errado durante a geração de token. Por favor, solicite novamente um "
+"código de autorização em%(menu:base_setup.menu_general_configuration)s."
#. module: google_drive
#: code:addons/google_drive/google_drive.py:124
#, python-format
msgid "Google Drive Error!"
-msgstr ""
+msgstr "Google Drive Error!"
#. module: google_drive
#: field:base.config.settings,google_drive_uri:0
msgid "URI"
-msgstr ""
+msgstr "URL"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:124
#, python-format
msgid "Creating google drive may only be done by one at a time."
-msgstr ""
+msgstr "Criação de unidade google só pode ser feito por um de cada vez."
#. module: google_drive
#: field:google.drive.config,model:0
@@ -218,38 +258,40 @@ msgstr "Modelo"
#. module: google_drive
#: view:google.drive.config:0
msgid "Google Drive Configuration"
-msgstr ""
+msgstr "Configuração do acionamento Google"
#. module: google_drive
#: field:google.drive.config,name:0
msgid "Template Name"
-msgstr ""
+msgstr "Nome do modelo"
#. module: google_drive
#: constraint:google.drive.config:0
msgid ""
"Model of selected filter is not matching with model of current template."
msgstr ""
+"Modelo de filtro selecionado não está combinando com o modelo do modelo "
+"atual."
#. module: google_drive
#: field:google.drive.config,google_drive_template_url:0
msgid "Template URL"
-msgstr ""
+msgstr "Modelo de URL"
#. module: google_drive
#: view:base.config.settings:0
msgid "and paste it here"
-msgstr ""
+msgstr "e cole-o aqui"
#. module: google_drive
#: field:base.config.settings,google_drive_authorization_code:0
msgid "Authorization Code"
-msgstr ""
+msgstr "Código de Autorização"
#. module: google_drive
#: model:ir.model,name:google_drive.model_google_drive_config
msgid "Google Drive templates config"
-msgstr ""
+msgstr "Google Drive templates de configuração"
#. module: google_drive
#: code:addons/google_drive/google_drive.py:64
@@ -258,3 +300,6 @@ msgid ""
"You haven't configured 'Authorization Code' generated from google, Please "
"generate and configure it in %(menu:base_setup.menu_general_configuration)s."
msgstr ""
+"Você não configurou \"Código de Autorização\" gerado a partir do Google, por "
+"favor, gerar e configurá-lo em "
+"%(menu:base_setup.menu_general_configuration)s."
diff --git a/addons/hr/hr.py b/addons/hr/hr.py
index 4849d3a7d8b..3a99d5c8b60 100644
--- a/addons/hr/hr.py
+++ b/addons/hr/hr.py
@@ -51,9 +51,9 @@ class hr_employee_category(osv.osv):
_name = "hr.employee.category"
_description = "Employee Category"
_columns = {
- 'name': fields.char("Category", size=64, required=True),
+ 'name': fields.char("Employee Tag", size=64, required=True),
'complete_name': fields.function(_name_get_fnc, type="char", string='Name'),
- 'parent_id': fields.many2one('hr.employee.category', 'Parent Category', select=True),
+ 'parent_id': fields.many2one('hr.employee.category', 'Parent Employee Tag', select=True),
'child_ids': fields.one2many('hr.employee.category', 'parent_id', 'Child Categories'),
'employee_ids': fields.many2many('hr.employee', 'employee_category_rel', 'category_id', 'emp_id', 'Employees'),
}
@@ -129,7 +129,7 @@ class hr_job(osv.osv):
}
_sql_constraints = [
- ('name_company_uniq', 'unique(name, company_id)', 'The name of the job position must be unique per company!'),
+ ('name_company_uniq', 'unique(name, company_id, department_id)', 'The name of the job position must be unique per department in company!'),
]
@@ -156,6 +156,8 @@ class hr_employee(osv.osv):
_inherits = {'resource.resource': "resource_id"}
_inherit = ['mail.thread']
+ _mail_post_access = 'read'
+
def _get_image(self, cr, uid, ids, name, args, context=None):
result = dict.fromkeys(ids, False)
for obj in self.browse(cr, uid, ids, context=context):
@@ -324,22 +326,6 @@ class hr_employee(osv.osv):
(_check_recursion, 'Error! You cannot create recursive hierarchy of Employee(s).', ['parent_id']),
]
- # ---------------------------------------------------
- # Mail gateway
- # ---------------------------------------------------
-
- def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
- """ mail.message document permission rule: can post a new message if can read
- because of portal document. """
- if not model_obj:
- model_obj = self
- employee_ids = model_obj.search(cr, uid, [('user_id', '=', uid)], context=context)
- if employee_ids and operation == 'create':
- model_obj.check_access_rights(cr, uid, 'read')
- model_obj.check_access_rule(cr, uid, mids, 'read', context=context)
- else:
- return super(hr_employee, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
-
class hr_department(osv.osv):
_description = "Department"
diff --git a/addons/hr_holidays/hr_holidays.py b/addons/hr_holidays/hr_holidays.py
index f2be4844d27..f9eee9cd243 100644
--- a/addons/hr_holidays/hr_holidays.py
+++ b/addons/hr_holidays/hr_holidays.py
@@ -79,7 +79,7 @@ class hr_holidays_status(osv.osv):
'categ_id': fields.many2one('crm.meeting.type', 'Meeting Type',
help='Once a leave is validated, OpenERP will create a corresponding meeting of this type in the calendar.'),
'color_name': fields.selection([('red', 'Red'),('blue','Blue'), ('lightgreen', 'Light Green'), ('lightblue','Light Blue'), ('lightyellow', 'Light Yellow'), ('magenta', 'Magenta'),('lightcyan', 'Light Cyan'),('black', 'Black'),('lightpink', 'Light Pink'),('brown', 'Brown'),('violet', 'Violet'),('lightcoral', 'Light Coral'),('lightsalmon', 'Light Salmon'),('lavender', 'Lavender'),('wheat', 'Wheat'),('ivory', 'Ivory')],'Color in Report', required=True, help='This color will be used in the leaves summary located in Reporting\Leaves by Department.'),
- 'limit': fields.boolean('Allow to Override Limit', help='If you select this check box, the system allows the employees to take more leaves than the available ones for this type and take them into account for the "Remaining Legal Leaves" defined on the employee form.'),
+ 'limit': fields.boolean('Allow to Override Limit', help='If you select this check box, the system allows the employees to take more leaves than the available ones for this type and will not take them into account for the "Remaining Legal Leaves" defined on the employee form.'),
'active': fields.boolean('Active', help="If the active field is set to false, it will allow you to hide the leave type without removing it."),
'max_leaves': fields.function(_user_left_days, string='Maximum Allowed', help='This value is given by the sum of all holidays requests with a positive value.', multi='user_left_days'),
'leaves_taken': fields.function(_user_left_days, string='Leaves Already Taken', help='This value is given by the sum of all holidays requests with a negative value.', multi='user_left_days'),
diff --git a/addons/hr_timesheet/test/test_hr_timesheet.yml b/addons/hr_timesheet/test/test_hr_timesheet.yml
index 59d688f183b..2a2101ff186 100644
--- a/addons/hr_timesheet/test/test_hr_timesheet.yml
+++ b/addons/hr_timesheet/test/test_hr_timesheet.yml
@@ -35,7 +35,7 @@
new_id = self.create(cr, uid, {'account_id': ref('account.analytic_nebula'),'analytic_amount': 7.0,
'date': (datetime.now()+timedelta(1)).strftime('%Y-%m-%d %H:%M:%S') ,
'date_start': time.strftime('%Y-%m-%d %H:%M:%S'), 'info': 'Create Yaml for hr module',
- 'name': 'Quentin Paolino', 'server_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'state': 'action'})
+ 'name': 'Quentin Paolino', 'server_date': time.strftime('%Y-%m-%d %H:%M:%S'), 'state': 'present'})
self.sign_out_result(cr, uid, [new_id], context)
-
My work for this project "Sednacom" is over and I stop working by clicking on "Stop Work" button of this wizard.
diff --git a/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py b/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py
index d0cbdc78c31..87425b9cb3f 100644
--- a/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py
+++ b/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out.py
@@ -32,8 +32,8 @@ class hr_so_project(osv.osv_memory):
'date_start': fields.datetime('Starting Date', readonly=True),
'date': fields.datetime('Closing Date'),
'analytic_amount': fields.float('Minimum Analytic Amount'),
- 'name': fields.char('Employees name', size=32, required=True, readonly=True),
- 'state': fields.related('emp_id', 'state', string='Current Status', type='char', required=True, readonly=True),
+ 'name': fields.char('Employee\'s Name', size=32, required=True, readonly=True),
+ 'state': fields.related('emp_id', 'state', string='Current Status', type='selection', selection=[('present', 'Present'), ('absent', 'Absent')], required=True, readonly=True),
'server_date': fields.datetime('Current Date', required=True, readonly=True),
'emp_id': fields.many2one('hr.employee', 'Employee ID')
}
@@ -109,8 +109,8 @@ class hr_si_project(osv.osv_memory):
_name = 'hr.sign.in.project'
_description = 'Sign In By Project'
_columns = {
- 'name': fields.char('Employees name', size=32, readonly=True),
- 'state': fields.related('emp_id', 'state', string='Current Status', type='char', required=True, readonly=True),
+ 'name': fields.char('Employee\'s Name', size=32, readonly=True),
+ 'state': fields.related('emp_id', 'state', string='Current Status', type='selection', selection=[('present', 'Present'), ('absent', 'Absent')], required=True, readonly=True),
'date': fields.datetime('Starting Date'),
'server_date': fields.datetime('Current Date', readonly=True),
'emp_id': fields.many2one('hr.employee', 'Employee ID')
diff --git a/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out_view.xml b/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out_view.xml
index ea8d37ad454..c91b794b1f1 100644
--- a/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out_view.xml
+++ b/addons/hr_timesheet/wizard/hr_timesheet_sign_in_out_view.xml
@@ -43,7 +43,7 @@
- Sign in / Sign out by project
+ Sign in / Sign out by Projecthr.sign.in.projectformtree,form
@@ -67,7 +67,7 @@
-
+
@@ -82,7 +82,7 @@
- Sign in / Sign out by project
+ Sign in / Sign out by Projecthr.sign.out.projectformtree,form
diff --git a/addons/l10n_multilang/i18n/zh_CN.po b/addons/l10n_multilang/i18n/zh_CN.po
new file mode 100644
index 00000000000..bb1f5830977
--- /dev/null
+++ b/addons/l10n_multilang/i18n/zh_CN.po
@@ -0,0 +1,169 @@
+# Chinese (Simplified) translation for openobject-addons
+# Copyright (c) 2013 Rosetta Contributors and Canonical Ltd 2013
+# This file is distributed under the same license as the openobject-addons package.
+# FIRST AUTHOR , 2013.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: openobject-addons\n"
+"Report-Msgid-Bugs-To: FULL NAME \n"
+"POT-Creation-Date: 2012-02-08 01:06+0000\n"
+"PO-Revision-Date: 2013-07-24 12:29+0000\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Chinese (Simplified) \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Launchpad-Export-Date: 2013-07-25 05:13+0000\n"
+"X-Generator: Launchpad (build 16700)\n"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_fiscal_position_template
+msgid "Template for Fiscal Position"
+msgstr "财务结构模板"
+
+#. module: l10n_multilang
+#: sql_constraint:account.account:0
+msgid "The code of the account must be unique per company !"
+msgstr "该科目的代码,每家公司必须是唯一的!"
+
+#. module: l10n_multilang
+#: constraint:account.account.template:0
+msgid ""
+"Configuration Error!\n"
+"You can not define children to an account with internal type different of "
+"\"View\"! "
+msgstr ""
+"配置错误! \n"
+"上级科目必须是“视图”类型的科目! "
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_analytic_journal
+msgid "Analytic Journal"
+msgstr "辅助核算类型"
+
+#. module: l10n_multilang
+#: constraint:account.account.template:0
+msgid "Error ! You can not create recursive account templates."
+msgstr "错误!您不能创建递归的科目模板。"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_journal
+msgid "Journal"
+msgstr "分录序时薄"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_chart_template
+msgid "Templates for Account Chart"
+msgstr "科目一览表模板"
+
+#. module: l10n_multilang
+#: sql_constraint:account.tax:0
+msgid "The description must be unique per company!"
+msgstr "此说明必须是每个公司唯一的"
+
+#. module: l10n_multilang
+#: constraint:account.tax.code.template:0
+msgid "Error ! You can not create recursive Tax Codes."
+msgstr "错误!您不能创建递归的税编码。"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_tax_template
+msgid "account.tax.template"
+msgstr "account.tax.template"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_tax
+msgid "account.tax"
+msgstr "account.tax"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_account
+msgid "Account"
+msgstr "科目"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_wizard_multi_charts_accounts
+msgid "wizard.multi.charts.accounts"
+msgstr "wizard.multi.charts.accounts"
+
+#. module: l10n_multilang
+#: constraint:account.journal:0
+msgid ""
+"Configuration error! The currency chosen should be shared by the default "
+"accounts too."
+msgstr "设置错误!所选币种应与默认科目共享。"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_account_template
+msgid "Templates for Accounts"
+msgstr "科目模板"
+
+#. module: l10n_multilang
+#: help:account.chart.template,spoken_languages:0
+msgid ""
+"State here the languages for which the translations of templates could be "
+"loaded at the time of installation of this localization module and copied in "
+"the final object when generating them from templates. You must provide the "
+"language codes separated by ';'"
+msgstr ""
+
+#. module: l10n_multilang
+#: constraint:account.account:0
+msgid "Error ! You can not create recursive accounts."
+msgstr "错误!您不能创建递归的科目"
+
+#. module: l10n_multilang
+#: constraint:account.account:0
+msgid ""
+"Configuration Error! \n"
+"You can not select an account type with a deferral method different of "
+"\"Unreconciled\" for accounts with internal type \"Payable/Receivable\"! "
+msgstr ""
+"配置错误!\n"
+"对于内部类型是‘应收/应付’的会计科目,你需要选择结转方式是‘未核销’的科目类型。 "
+
+#. module: l10n_multilang
+#: sql_constraint:account.journal:0
+msgid "The name of the journal must be unique per company !"
+msgstr "每个公司的日记账名称必须唯一!"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_analytic_account
+msgid "Analytic Account"
+msgstr "辅助核算项目"
+
+#. module: l10n_multilang
+#: sql_constraint:account.journal:0
+msgid "The code of the journal must be unique per company !"
+msgstr "每个公司的日记账代码必须唯一!"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_fiscal_position
+msgid "Fiscal Position"
+msgstr "财务结构"
+
+#. module: l10n_multilang
+#: constraint:account.account:0
+msgid ""
+"Configuration Error! \n"
+"You can not define children to an account with internal type different of "
+"\"View\"! "
+msgstr ""
+"配置错误! \n"
+"您不能给 非“视图”类型的科目定义一个子科目。 "
+
+#. module: l10n_multilang
+#: constraint:account.analytic.account:0
+msgid "Error! You can not create recursive analytic accounts."
+msgstr "错误! 你不能创建递归的辅助核算项目"
+
+#. module: l10n_multilang
+#: model:ir.model,name:l10n_multilang.model_account_tax_code_template
+msgid "Tax Code Template"
+msgstr "税编码模板"
+
+#. module: l10n_multilang
+#: field:account.chart.template,spoken_languages:0
+msgid "Spoken Languages"
+msgstr "交流语言"
diff --git a/addons/mail/doc/changelog.rst b/addons/mail/doc/changelog.rst
new file mode 100644
index 00000000000..f6674bb42ad
--- /dev/null
+++ b/addons/mail/doc/changelog.rst
@@ -0,0 +1,11 @@
+.. _changelog:
+
+Changelog
+=========
+
+`trunk (saas-2)`
+----------------
+
+ - added ``_mail_post_access`` attribute that specifies the access right that
+ should have the user in order to post a new message on a given model. Values
+ are ``read`` (portal documents), ``write`` (default value), ``unlink`` or ``create``.
\ No newline at end of file
diff --git a/addons/mail/doc/index.rst.inc b/addons/mail/doc/index.rst.inc
index 4b01d3ec7bd..7f56666336b 100644
--- a/addons/mail/doc/index.rst.inc
+++ b/addons/mail/doc/index.rst.inc
@@ -12,3 +12,12 @@ Mail Module documentation topics
mail_partner
mail_state
mail_subtype
+
+
+Changelog
+'''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ changelog.rst
diff --git a/addons/mail/mail_thread.py b/addons/mail/mail_thread.py
index 2744e6df231..306e5fd5905 100644
--- a/addons/mail/mail_thread.py
+++ b/addons/mail/mail_thread.py
@@ -70,6 +70,7 @@ class mail_thread(osv.AbstractModel):
_name = 'mail.thread'
_description = 'Email Thread'
_mail_flat_thread = True
+ _mail_post_access = 'write'
# Automatic logging system if mail installed
# _track = {
@@ -156,12 +157,26 @@ class mail_thread(osv.AbstractModel):
res[id]['message_summary'] = "9 %d %s" % (title, res[id].pop('message_unread_count'), _("New"))
return res
- def _get_subscription_data(self, cr, uid, ids, name, args, context=None):
+ def read_followers_data(self, cr, uid, follower_ids, context=None):
+ result = []
+ technical_group = self.pool.get('ir.model.data').get_object(cr, uid, 'base', 'group_no_one')
+ for follower in self.pool.get('res.partner').browse(cr, uid, follower_ids, context=context):
+ is_editable = uid in map(lambda x: x.id, technical_group.users)
+ is_uid = uid in map(lambda x: x.id, follower.user_ids)
+ data = (follower.id,
+ follower.name,
+ {'is_editable': is_editable, 'is_uid': is_uid},
+ )
+ result.append(data)
+ return result
+
+ def _get_subscription_data(self, cr, uid, ids, name, args, user_pid=None, context=None):
""" Computes:
- message_subtype_data: data about document subtypes: which are
available, which are followed if any """
res = dict((id, dict(message_subtype_data='')) for id in ids)
- user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
+ if user_pid is None:
+ user_pid = self.pool.get('res.users').read(cr, uid, uid, ['partner_id'], context=context)['partner_id'][0]
# find current model subtypes, add them to a dictionary
subtype_obj = self.pool.get('mail.message.subtype')
@@ -495,12 +510,22 @@ class mail_thread(osv.AbstractModel):
access rule on the document, for portal document such as issues. """
if not model_obj:
model_obj = self
- if operation in ['create', 'write', 'unlink']:
- model_obj.check_access_rights(cr, uid, 'write')
- model_obj.check_access_rule(cr, uid, mids, 'write', context=context)
+ if hasattr(self, '_mail_post_access'):
+ create_allow = self._mail_post_access
else:
- model_obj.check_access_rights(cr, uid, operation)
- model_obj.check_access_rule(cr, uid, mids, operation, context=context)
+ create_allow = 'write'
+
+ if operation in ['write', 'unlink']:
+ check_operation = 'write'
+ elif operation == 'create' and create_allow in ['create', 'read', 'write', 'unlink']:
+ check_operation = create_allow
+ elif operation == 'create':
+ check_operation = 'write'
+ else:
+ check_operation = operation
+
+ model_obj.check_access_rights(cr, uid, check_operation)
+ model_obj.check_access_rule(cr, uid, mids, check_operation, context=context)
def _get_formview_action(self, cr, uid, id, model=None, context=None):
""" Return an action to open the document. This method is meant to be
@@ -1401,9 +1426,9 @@ class mail_thread(osv.AbstractModel):
# Followers API
#------------------------------------------------------
- def message_get_subscription_data(self, cr, uid, ids, context=None):
+ def message_get_subscription_data(self, cr, uid, ids, user_pid=None, context=None):
""" Wrapper to get subtypes data. """
- return self._get_subscription_data(cr, uid, ids, None, None, context=context)
+ return self._get_subscription_data(cr, uid, ids, None, None, user_pid=user_pid, context=context)
def message_subscribe_users(self, cr, uid, ids, user_ids=None, subtype_ids=None, context=None):
""" Wrapper on message_subscribe, using users. If user_ids is not
diff --git a/addons/mail/security/mail_security.xml b/addons/mail/security/mail_security.xml
index c369e96102b..5384e2aba95 100644
--- a/addons/mail/security/mail_security.xml
+++ b/addons/mail/security/mail_security.xml
@@ -14,11 +14,22 @@
mail.followers: read and write its own entries
+ [('partner_id', '=', user.partner_id.id)]
+
+
+ mail.followers: read and write others entries
+
+
+ []
+
+
+
+
mail.notification: read and write its own entries
diff --git a/addons/mail/static/src/css/mail.css b/addons/mail/static/src/css/mail.css
index a0c60e548b6..fd869d4a6ba 100644
--- a/addons/mail/static/src/css/mail.css
+++ b/addons/mail/static/src/css/mail.css
@@ -635,11 +635,10 @@
}
.openerp .oe_followers .oe_partner {
height: 32px;
- margin-right: 8px;
+ margin-right: 24px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
- margin-right: 10px;
}
.openerp .oe_followers .oe_partner img{
width: 32px;
@@ -652,7 +651,18 @@
right: 0px;
line-height: 20px;
}
-
+.openerp .oe_followers .oe_edit_subtype{
+ cursor: pointer;
+ position: absolute;
+ right: 12px;
+ line-height: 20px;
+}
+.openerp .oe_followers .oe_partner .oe_hidden{
+ display: none;
+}
+.openerp.ui-dialog .ui-dialog-titlebar .ui-dialog-title{
+ padding-right: 20px;
+}
.openerp .oe_followers .oe_show_more{
cursor: pointer;
}
diff --git a/addons/mail/static/src/js/mail_followers.js b/addons/mail/static/src/js/mail_followers.js
index f812844194b..a0dad1fcdbc 100644
--- a/addons/mail/static/src/js/mail_followers.js
+++ b/addons/mail/static/src/js/mail_followers.js
@@ -29,7 +29,6 @@ openerp_mail_followers = function(session, mail) {
this.displayed_limit = this.node.attrs.displayed_nb || 10;
this.displayed_nb = this.displayed_limit;
this.ds_model = new session.web.DataSetSearch(this, this.view.model);
- this.ds_follow = new session.web.DataSetSearch(this, this.field.relation);
this.ds_users = new session.web.DataSetSearch(this, 'res.users');
this.value = [];
@@ -74,10 +73,33 @@ openerp_mail_followers = function(session, mail) {
this.$el.on('click', '.oe_subtype_list input', self.do_update_subscription);
// event: click on 'invite' button, that opens the invite wizard
this.$('.oe_invite').on('click', self.on_invite_follower);
+ // event: click on 'edit_subtype(pencil)' button to edit subscription
+ this.$el.on('click', '.oe_edit_subtype', self.on_edit_subtype);
this.$el.on('click', '.oe_remove_follower', self.on_remove_follower);
this.$el.on('click', '.oe_show_more', self.on_show_more_followers)
},
+ on_edit_subtype: function(event) {
+ var self = this;
+ var $currentTarget = $(event.currentTarget);
+ var user_pid = $currentTarget.data('id');
+ $('div.oe_edit_actions').remove();
+ self.$dialog = new session.web.dialog($('
'), {
+ modal: true,
+ width: 'auto',
+ height: 'auto',
+ title: _t('Edit Subscription of ') + $currentTarget.siblings('a').text(),
+ buttons: [
+ { text: _t("Apply"), click: function() {
+ self.do_update_subscription(event, user_pid);
+ $(this).dialog("close");
+ }},
+ { text: _t("Cancel"), click: function() { $(this).dialog("close"); }}
+ ],
+ });
+ return self.fetch_subtypes(user_pid);
+ },
+
on_invite_follower: function (event) {
var self = this;
var action = {
@@ -130,7 +152,7 @@ openerp_mail_followers = function(session, mail) {
fetch_followers: function (value_) {
this.value = value_ || {};
- return this.ds_follow.call('read', [this.value, ['name', 'user_ids']])
+ return this.ds_model.call('read_followers_data', [this.value])
.then(this.proxy('display_followers'), this.proxy('fetch_generic'))
.then(this.proxy('display_buttons'))
.then(this.proxy('fetch_subtypes'));
@@ -147,6 +169,7 @@ openerp_mail_followers = function(session, mail) {
self.message_is_follower = (_.indexOf(self.value, pid) != -1);
}).then(self.proxy('display_generic'));
},
+
_format_followers: function(count){
var str = '';
if(count <= 0){
@@ -158,6 +181,7 @@ openerp_mail_followers = function(session, mail) {
}
return str;
},
+
/* Display generic info about follower, for people not having access to res_partner */
display_generic: function () {
var self = this;
@@ -168,16 +192,32 @@ openerp_mail_followers = function(session, mail) {
/** Display the followers */
display_followers: function (records) {
var self = this;
+ this.message_is_follower = false;
this.followers = records || this.followers;
- this.message_is_follower = this.set_is_follower(this.followers);
// clean and display title
var node_user_list = this.$('.oe_follower_list').empty();
this.$('.oe_follower_title').html(this._format_followers(this.followers.length));
// truncate number of displayed followers
var truncated = this.followers.slice(0, this.displayed_nb);
_(truncated).each(function (record) {
- record.avatar_url = mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record.id);
- $(session.web.qweb.render('mail.followers.partner', {'record': record, 'widget': self})).appendTo(node_user_list);
+ partner = {
+ 'id': record[0],
+ 'name': record[1],
+ 'is_uid': record[2]['is_uid'],
+ 'is_editable': record[2]['is_editable'],
+ 'avatar_url': mail.ChatterUtils.get_image(self.session, 'res.partner', 'image_small', record[0]),
+ }
+ if (partner.is_uid) {
+ self.message_is_follower = partner.is_uid;
+ }
+ $(session.web.qweb.render('mail.followers.partner', {'record': partner, 'widget': self})).appendTo(node_user_list);
+ // On mouse-enter it will show the edit_subtype pencil.
+ if (partner.is_editable) {
+ self.$('.oe_follower_list').on('mouseenter mouseleave', function(e) {
+ self.$('.oe_edit_subtype').toggleClass('oe_hidden', e.type == 'mouseleave');
+ self.$('.oe_follower_list').find('.oe_partner').toggleClass('oe_partner_name', e.type == 'mouseenter');
+ });
+ }
});
// FVA note: be sure it is correctly translated
if (truncated.length < this.followers.length) {
@@ -185,12 +225,6 @@ openerp_mail_followers = function(session, mail) {
}
},
- /** Computes whether the current user is in the followers */
- set_is_follower: function (records) {
- var user_ids = _.pluck(_.pluck(records, 'user_ids'), 0);
- return _.indexOf(user_ids, this.session.uid) != -1;
- },
-
display_buttons: function () {
if (this.message_is_follower) {
this.$('button.oe_follower').removeClass('oe_notfollow').addClass('oe_following');
@@ -206,25 +240,37 @@ openerp_mail_followers = function(session, mail) {
},
/** Fetch subtypes, only if current user is follower */
- fetch_subtypes: function () {
+ fetch_subtypes: function (user_pid) {
var self = this;
- var subtype_list_ul = this.$('.oe_subtype_list').empty();
- if (! this.message_is_follower) return;
+ var dialog = false;
+ if (user_pid) {
+ dialog = true;
+ } else {
+ var subtype_list_ul = this.$('.oe_subtype_list').empty();
+ if (! this.message_is_follower) return;
+ }
var id = this.view.datarecord.id;
- this.ds_model.call('message_get_subscription_data', [[id], new session.web.CompoundContext(this.build_context(), {})])
- .then(function (data) {self.display_subtypes(data, id);});
+ this.ds_model.call('message_get_subscription_data', [[id], user_pid, new session.web.CompoundContext(this.build_context(), {})])
+ .then(function (data) {self.display_subtypes(data, id, dialog);});
},
/** Display subtypes: {'name': default, followed} */
- display_subtypes:function (data, id) {
+ display_subtypes:function (data, id, dialog) {
var self = this;
- var $list = this.$('.oe_subtype_list');
+ if (dialog) {
+ var $list = self.$dialog;
+ }
+ else {
+ var $list = this.$('.oe_subtype_list');
+ }
$list.empty().hide();
var records = data[this.view.datarecord.id || this.view.dataset.ids[0]].message_subtype_data;
+ this.records_length = $.map(records, function(value, index) { return index; }).length;
+ if (this.records_length > 1) { self.display_followers(); }
_(records).each(function (record, record_name) {
record.name = record_name;
record.followed = record.followed || undefined;
- $(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo( self.$('.oe_subtype_list') );
+ $(session.web.qweb.render('mail.followers.subtype', {'record': record})).appendTo($list);
});
if (_.size(records) > 1) {
$list.show();
@@ -241,35 +287,49 @@ openerp_mail_followers = function(session, mail) {
});
},
- do_unfollow: function () {
+ do_unfollow: function (user_pid) {
if (confirm(_t("Warning! \nYou won't be notified of any email or discussion on this document. Do you really want to unfollow this document ?"))) {
_(this.$('.oe_msg_subtype_check')).each(function (record) {
$(record).attr('checked',false);
});
+ var action_unsubscribe = 'message_unsubscribe_users';
+ var follower_ids = [this.session.uid];
+ if (user_pid) {
+ action_unsubscribe = 'message_unsubscribe';
+ follower_ids = [user_pid];
+ }
var context = new session.web.CompoundContext(this.build_context(), {});
- return this.ds_model.call('message_unsubscribe_users', [[this.view.datarecord.id], [this.session.uid], context])
- .then(this.proxy('read_value'));
+ return this.ds_model.call(action_unsubscribe, [[this.view.datarecord.id], follower_ids, context])
+ .then(this.proxy('read_value'));
}
return false;
},
- do_update_subscription: function (event) {
+ do_update_subscription: function (event, user_pid) {
var self = this;
+ var action_subscribe = 'message_subscribe_users';
+ var follower_ids = [this.session.uid];
+ var oe_action = this.$('.oe_actions input[type="checkbox"]');
+ if (user_pid) {
+ action_subscribe = 'message_subscribe';
+ follower_ids = [user_pid];
+ oe_action = $('.oe_edit_actions input[type="checkbox"]');
+ }
var checklist = new Array();
- _(this.$('.oe_actions input[type="checkbox"]')).each(function (record) {
+ _(oe_action).each(function (record) {
if ($(record).is(':checked')) {
checklist.push(parseInt($(record).data('id')));
}
});
if (!checklist.length) {
- if (!this.do_unfollow()) {
+ if (!this.do_unfollow(user_pid)) {
$(event.target).attr("checked", "checked");
}
} else {
var context = new session.web.CompoundContext(this.build_context(), {});
- return this.ds_model.call('message_subscribe_users', [[this.view.datarecord.id], [this.session.uid], checklist, context])
+ return this.ds_model.call(action_subscribe, [[this.view.datarecord.id], follower_ids, checklist, context])
.then(this.proxy('read_value'));
}
},
diff --git a/addons/mail/static/src/xml/mail_followers.xml b/addons/mail/static/src/xml/mail_followers.xml
index 5ad3ff09bb3..a936b8237d6 100644
--- a/addons/mail/static/src/xml/mail_followers.xml
+++ b/addons/mail/static/src/xml/mail_followers.xml
@@ -31,6 +31,7 @@
+ &X
diff --git a/addons/pad_project/project_task.xml b/addons/pad_project/project_task.xml
index 24efbc9dad0..c8cbbe1c626 100644
--- a/addons/pad_project/project_task.xml
+++ b/addons/pad_project/project_task.xml
@@ -6,7 +6,7 @@
-
+
diff --git a/addons/portal_project/tests/test_access_rights.py b/addons/portal_project/tests/test_access_rights.py
index a7d56cb3895..1b98a140b68 100644
--- a/addons/portal_project/tests/test_access_rights.py
+++ b/addons/portal_project/tests/test_access_rights.py
@@ -19,35 +19,17 @@
#
##############################################################################
+from openerp.addons.project.tests.test_project_base import TestProjectBase
from openerp.osv.orm import except_orm
-from openerp.tests import common
from openerp.tools import mute_logger
-class TestPortalProject(common.TransactionCase):
+class TestPortalProjectBase(TestProjectBase):
def setUp(self):
- super(TestPortalProject, self).setUp()
+ super(TestPortalProjectBase, self).setUp()
cr, uid = self.cr, self.uid
- # Useful models
- self.project_project = self.registry('project.project')
- self.project_task = self.registry('project.task')
- self.res_users = self.registry('res.users')
- self.res_partner = self.registry('res.partner')
-
- # Find Employee group
- group_employee_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'base', 'group_user')
- self.group_employee_id = group_employee_ref and group_employee_ref[1] or False
-
- # Find Project User group
- group_project_user_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_user')
- self.group_project_user_id = group_project_user_ref and group_project_user_ref[1] or False
-
- # Find Project Manager group
- group_project_manager_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'project', 'group_project_manager')
- self.group_project_manager_id = group_project_manager_ref and group_project_manager_ref[1] or False
-
# Find Portal group
group_portal_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_portal')
self.group_portal_id = group_portal_ref and group_portal_ref[1] or False
@@ -56,62 +38,39 @@ class TestPortalProject(common.TransactionCase):
group_anonymous_ref = self.registry('ir.model.data').get_object_reference(cr, uid, 'portal', 'group_anonymous')
self.group_anonymous_id = group_anonymous_ref and group_anonymous_ref[1] or False
- # Test users to use through the various tests
- self.user_alfred_id = self.res_users.create(cr, uid, {
- 'name': 'Alfred EmployeeUser',
- 'login': 'alfred',
- 'alias_name': 'alfred',
- 'groups_id': [(6, 0, [self.group_employee_id, self.group_project_user_id])]
- })
- self.user_bert_id = self.res_users.create(cr, uid, {
- 'name': 'Bert Nobody',
- 'login': 'bert',
- 'alias_name': 'bert',
- 'groups_id': [(6, 0, [])]
- })
- self.user_chell_id = self.res_users.create(cr, uid, {
- 'name': 'Chell Portal',
- 'login': 'chell',
- 'alias_name': 'chell',
- 'groups_id': [(6, 0, [self.group_portal_id])]
- })
- self.user_donovan_id = self.res_users.create(cr, uid, {
- 'name': 'Donovan Anonymous',
- 'login': 'donovan',
- 'alias_name': 'donovan',
- 'groups_id': [(6, 0, [self.group_anonymous_id])]
- })
- self.user_ernest_id = self.res_users.create(cr, uid, {
- 'name': 'Ernest Manager',
- 'login': 'ernest',
- 'alias_name': 'ernest',
- 'groups_id': [(6, 0, [self.group_project_manager_id])]
- })
+ # # Test users to use through the various tests
+ self.user_portal_id = self.res_users.create(cr, uid, {
+ 'name': 'Chell Portal',
+ 'login': 'chell',
+ 'alias_name': 'chell',
+ 'groups_id': [(6, 0, [self.group_portal_id])]
+ })
+ self.user_anonymous_id = self.res_users.create(cr, uid, {
+ 'name': 'Donovan Anonymous',
+ 'login': 'donovan',
+ 'alias_name': 'donovan',
+ 'groups_id': [(6, 0, [self.group_anonymous_id])]
+ })
# Test 'Pigs' project
- self.project_pigs_id = self.project_project.create(cr, uid,
- {'name': 'Pigs', 'privacy_visibility': 'public'},
- {'mail_create_nolog': True})
+ self.project_pigs_id = self.project_project.create(cr, uid, {
+ 'name': 'Pigs', 'privacy_visibility': 'public'}, {'mail_create_nolog': True})
# Various test tasks
- self.task_1_id = self.project_task.create(cr, uid,
- {'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_2_id = self.project_task.create(cr, uid,
- {'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_3_id = self.project_task.create(cr, uid,
- {'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_4_id = self.project_task.create(cr, uid,
- {'name': 'Test4', 'user_id': self.user_alfred_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_5_id = self.project_task.create(cr, uid,
- {'name': 'Test5', 'user_id': self.user_chell_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.task_6_id = self.project_task.create(cr, uid,
- {'name': 'Test6', 'user_id': self.user_donovan_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
+ self.task_1_id = self.project_task.create(cr, uid, {
+ 'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_2_id = self.project_task.create(cr, uid, {
+ 'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_3_id = self.project_task.create(cr, uid, {
+ 'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_4_id = self.project_task.create(cr, uid, {
+ 'name': 'Test4', 'user_id': self.user_projectuser_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_5_id = self.project_task.create(cr, uid, {
+ 'name': 'Test5', 'user_id': self.user_portal_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.task_6_id = self.project_task.create(cr, uid, {
+ 'name': 'Test6', 'user_id': self.user_anonymous_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+
+class TestPortalProject(TestPortalProjectBase):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_project_access_rights(self):
""" Test basic project access rights, for project and portal_project """
@@ -122,53 +81,47 @@ class TestPortalProject(common.TransactionCase):
# ----------------------------------------
# Do: Alfred reads project -> ok (employee ok public)
- self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_2_id, self.task_3_id, self.task_4_id, self.task_5_id, self.task_6_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: project user cannot see all tasks of a public project')
+ 'access rights: project user cannot see all tasks of a public project')
# Test: all project tasks readable
- self.project_task.read(cr, self.user_alfred_id, task_ids, ['name'])
+ self.project_task.read(cr, self.user_projectuser_id, task_ids, ['name'])
# Test: all project tasks writable
- self.project_task.write(cr, self.user_alfred_id, task_ids, {'description': 'TestDescription'})
+ self.project_task.write(cr, self.user_projectuser_id, task_ids, {'description': 'TestDescription'})
# Do: Bert reads project -> crash, no group
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_bert_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Test: no project task visible
- self.assertRaises(except_orm, self.project_task.search,
- cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Test: no project task readable
- self.assertRaises(except_orm, self.project_task.read,
- cr, self.user_bert_id, task_ids, ['name'])
+ self.assertRaises(except_orm, self.project_task.read, cr, self.user_none_id, task_ids, ['name'])
# Test: no project task writable
- self.assertRaises(except_orm, self.project_task.write,
- cr, self.user_bert_id, task_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_task.write, cr, self.user_none_id, task_ids, {'description': 'TestDescription'})
# Do: Chell reads project -> ok (portal ok public)
- self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: project user cannot see all tasks of a public project')
+ 'access rights: project user cannot see all tasks of a public project')
# Test: all project tasks readable
- self.project_task.read(cr, self.user_chell_id, task_ids, ['name'])
+ self.project_task.read(cr, self.user_portal_id, task_ids, ['name'])
# Test: no project task writable
- self.assertRaises(except_orm, self.project_task.write,
- cr, self.user_chell_id, task_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_task.write, cr, self.user_portal_id, task_ids, {'description': 'TestDescription'})
# Do: Donovan reads project -> ok (anonymous ok public)
- self.project_project.read(cr, self.user_donovan_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: anonymous user cannot see all tasks of a public project')
+ 'access rights: anonymous user cannot see all tasks of a public project')
# Test: all project tasks readable
- self.project_task.read(cr, self.user_donovan_id, task_ids, ['name'])
+ self.project_task.read(cr, self.user_anonymous_id, task_ids, ['name'])
# Test: no project task writable
- self.assertRaises(except_orm, self.project_task.write,
- cr, self.user_donovan_id, task_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_task.write, cr, self.user_anonymous_id, task_ids, {'description': 'TestDescription'})
# ----------------------------------------
# CASE2: portal project
@@ -176,39 +129,36 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'portal'})
# Do: Alfred reads project -> ok (employee ok public)
- self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: project user cannot see all tasks of a portal project')
+ 'access rights: project user cannot see all tasks of a portal project')
# Do: Bert reads project -> crash, no group
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_bert_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Test: no project task searchable
- self.assertRaises(except_orm, self.project_task.search,
- cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ self.assertRaises(except_orm, self.project_task.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Data: task follower
- self.project_task.message_subscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id])
+ self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id])
# Do: Chell reads project -> ok (portal ok public)
- self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: only followed project tasks visible + assigned
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: portal user should see the followed tasks of a portal project')
+ 'access rights: portal user should see the followed tasks of a portal project')
# Do: Donovan reads project -> ko (anonymous ko portal)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_donovan_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of a portal project')
# Data: task follower cleaning
- self.project_task.message_unsubscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id])
+ self.project_task.message_unsubscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id])
# ----------------------------------------
# CASE3: employee project
@@ -216,29 +166,26 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'employees'})
# Do: Alfred reads project -> ok (employee ok employee)
- self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: all project tasks visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_2_id, self.task_3_id, self.task_4_id, self.task_5_id, self.task_6_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: project user cannot see all tasks of an employees project')
+ 'access rights: project user cannot see all tasks of an employees project')
# Do: Bert reads project -> crash, no group
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_bert_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Do: Chell reads project -> ko (portal ko employee)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_chell_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name'])
# Test: no project task visible + assigned
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: portal user should not see tasks of an employees project, even if assigned')
# Do: Donovan reads project -> ko (anonymous ko employee)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_donovan_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of an employees project')
# ----------------------------------------
@@ -247,54 +194,49 @@ class TestPortalProject(common.TransactionCase):
self.project_project.write(cr, uid, [pigs_id], {'privacy_visibility': 'followers'})
# Do: Alfred reads project -> ko (employee ko followers)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_alfred_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_4_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: employee user should not see tasks of a not-followed followers project, only assigned')
+ 'access rights: employee user should not see tasks of a not-followed followers project, only assigned')
# Do: Bert reads project -> crash, no group
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_bert_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_none_id, pigs_id, ['name'])
# Do: Chell reads project -> ko (portal ko employee)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_chell_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_portal_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: portal user should not see tasks of a not-followed followers project, only assigned')
+ 'access rights: portal user should not see tasks of a not-followed followers project, only assigned')
# Do: Donovan reads project -> ko (anonymous ko employee)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_donovan_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
# Test: no project task visible
- task_ids = self.project_task.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertFalse(task_ids, 'access rights: anonymous user should not see tasks of a followers project')
# Data: subscribe Alfred, Chell and Donovan as follower
- self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_alfred_id, self.user_chell_id, self.user_donovan_id])
- self.project_task.message_subscribe_users(cr, self.user_alfred_id, [self.task_1_id, self.task_3_id], [self.user_chell_id, self.user_alfred_id])
+ self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
+ self.project_task.message_subscribe_users(cr, self.user_projectuser_id, [self.task_1_id, self.task_3_id], [self.user_portal_id, self.user_projectuser_id])
# Do: Alfred reads project -> ok (follower ok followers)
- self.project_project.read(cr, self.user_alfred_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_projectuser_id, pigs_id, ['name'])
# Test: followed + assigned tasks visible
- task_ids = self.project_task.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_4_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: employee user should not see followed + assigned tasks of a follower project')
+ 'access rights: employee user should not see followed + assigned tasks of a follower project')
# Do: Chell reads project -> ok (follower ok follower)
- self.project_project.read(cr, self.user_chell_id, pigs_id, ['name'])
+ self.project_project.read(cr, self.user_portal_id, pigs_id, ['name'])
# Test: followed + assigned tasks visible
- task_ids = self.project_task.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ task_ids = self.project_task.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_task_ids = set([self.task_1_id, self.task_3_id, self.task_5_id])
self.assertEqual(set(task_ids), test_task_ids,
- 'access rights: employee user should not see followed + assigned tasks of a follower project')
+ 'access rights: employee user should not see followed + assigned tasks of a follower project')
# Do: Donovan reads project -> ko (anonymous ko follower even if follower)
- self.assertRaises(except_orm, self.project_project.read,
- cr, self.user_donovan_id, pigs_id, ['name'])
+ self.assertRaises(except_orm, self.project_project.read, cr, self.user_anonymous_id, pigs_id, ['name'])
diff --git a/addons/portal_project_issue/portal_project_issue_view.xml b/addons/portal_project_issue/portal_project_issue_view.xml
index 2750edf300f..c4f1e0aca57 100644
--- a/addons/portal_project_issue/portal_project_issue_view.xml
+++ b/addons/portal_project_issue/portal_project_issue_view.xml
@@ -26,7 +26,7 @@
-
+
diff --git a/addons/portal_project_issue/tests/test_access_rights.py b/addons/portal_project_issue/tests/test_access_rights.py
index 28d337a4965..a7721b489a0 100644
--- a/addons/portal_project_issue/tests/test_access_rights.py
+++ b/addons/portal_project_issue/tests/test_access_rights.py
@@ -19,40 +19,36 @@
#
##############################################################################
-from openerp.addons.portal_project.tests.test_access_rights import TestPortalProject
+from openerp.addons.portal_project.tests.test_access_rights import TestPortalProjectBase
from openerp.osv.orm import except_orm
from openerp.tools import mute_logger
-class TestPortalIssueProject(TestPortalProject):
+class TestPortalProjectBase(TestPortalProjectBase):
def setUp(self):
- super(TestPortalIssueProject, self).setUp()
+ super(TestPortalProjectBase, self).setUp()
cr, uid = self.cr, self.uid
# Useful models
self.project_issue = self.registry('project.issue')
# Various test issues
- self.issue_1_id = self.project_issue.create(cr, uid,
- {'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_2_id = self.project_issue.create(cr, uid,
- {'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_3_id = self.project_issue.create(cr, uid,
- {'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_4_id = self.project_issue.create(cr, uid,
- {'name': 'Test4', 'user_id': self.user_alfred_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_5_id = self.project_issue.create(cr, uid,
- {'name': 'Test5', 'user_id': self.user_chell_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
- self.issue_6_id = self.project_issue.create(cr, uid,
- {'name': 'Test6', 'user_id': self.user_donovan_id, 'project_id': self.project_pigs_id},
- {'mail_create_nolog': True})
+ self.issue_1_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test1', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_2_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test2', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_3_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test3', 'user_id': False, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_4_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test4', 'user_id': self.user_projectuser_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_5_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test5', 'user_id': self.user_portal_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+ self.issue_6_id = self.project_issue.create(cr, uid, {
+ 'name': 'Test6', 'user_id': self.user_anonymous_id, 'project_id': self.project_pigs_id}, {'mail_create_nolog': True})
+
+class TestPortalIssue(TestPortalProjectBase):
@mute_logger('openerp.addons.base.ir.ir_model', 'openerp.osv.orm')
def test_00_project_access_rights(self):
""" Test basic project access rights, for project and portal_project """
@@ -64,42 +60,38 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok public)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_2_id, self.issue_3_id, self.issue_4_id, self.issue_5_id, self.issue_6_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of a public project')
+ 'access rights: project user cannot see all issues of a public project')
# Test: all project issues readable
- self.project_issue.read(cr, self.user_alfred_id, issue_ids, ['name'])
+ self.project_issue.read(cr, self.user_projectuser_id, issue_ids, ['name'])
# Test: all project issues writable
- self.project_issue.write(cr, self.user_alfred_id, issue_ids, {'description': 'TestDescription'})
+ self.project_issue.write(cr, self.user_projectuser_id, issue_ids, {'description': 'TestDescription'})
# Do: Bert reads project -> crash, no group
# Test: no project issue visible
- self.assertRaises(except_orm, self.project_issue.search,
- cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Test: no project issue readable
- self.assertRaises(except_orm, self.project_issue.read,
- cr, self.user_bert_id, issue_ids, ['name'])
+ self.assertRaises(except_orm, self.project_issue.read, cr, self.user_none_id, issue_ids, ['name'])
# Test: no project issue writable
- self.assertRaises(except_orm, self.project_issue.write,
- cr, self.user_bert_id, issue_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_issue.write, cr, self.user_none_id, issue_ids, {'description': 'TestDescription'})
# Do: Chell reads project -> ok (portal ok public)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of a public project')
+ 'access rights: project user cannot see all issues of a public project')
# Test: all project issues readable
- self.project_issue.read(cr, self.user_chell_id, issue_ids, ['name'])
+ self.project_issue.read(cr, self.user_portal_id, issue_ids, ['name'])
# Test: no project issue writable
- self.assertRaises(except_orm, self.project_issue.write,
- cr, self.user_chell_id, issue_ids, {'description': 'TestDescription'})
+ self.assertRaises(except_orm, self.project_issue.write, cr, self.user_portal_id, issue_ids, {'description': 'TestDescription'})
# Do: Donovan reads project -> ok (anonymous ok public)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_donovan_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_anonymous_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of a public project')
+ 'access rights: project user cannot see all issues of a public project')
# ----------------------------------------
# CASE2: portal project
@@ -108,27 +100,26 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok public)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of a portal project')
+ 'access rights: project user cannot see all issues of a portal project')
# Do: Bert reads project -> crash, no group
# Test: no project issue searchable
- self.assertRaises(except_orm, self.project_issue.search,
- cr, self.user_bert_id, [('project_id', '=', pigs_id)])
+ self.assertRaises(except_orm, self.project_issue.search, cr, self.user_none_id, [('project_id', '=', pigs_id)])
# Data: issue follower
- self.project_issue.message_subscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id])
+ self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id])
# Do: Chell reads project -> ok (portal ok public)
# Test: only followed project issues visible + assigned
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: portal user should see the followed issues of a portal project')
+ 'access rights: portal user should see the followed issues of a portal project')
# Data: issue follower cleaning
- self.project_issue.message_unsubscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id])
+ self.project_issue.message_unsubscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id])
# ----------------------------------------
# CASE3: employee project
@@ -137,14 +128,14 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ok (employee ok employee)
# Test: all project issues visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_2_id, self.issue_3_id, self.issue_4_id, self.issue_5_id, self.issue_6_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: project user cannot see all issues of an employees project')
+ 'access rights: project user cannot see all issues of an employees project')
# Do: Chell reads project -> ko (portal ko employee)
# Test: no project issue visible + assigned
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
self.assertFalse(issue_ids, 'access rights: portal user should not see issues of an employees project, even if assigned')
# ----------------------------------------
@@ -154,32 +145,32 @@ class TestPortalIssueProject(TestPortalProject):
# Do: Alfred reads project -> ko (employee ko followers)
# Test: no project issue visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_4_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: employee user should not see issues of a not-followed followers project, only assigned')
+ 'access rights: employee user should not see issues of a not-followed followers project, only assigned')
# Do: Chell reads project -> ko (portal ko employee)
# Test: no project issue visible
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: portal user should not see issues of a not-followed followers project, only assigned')
+ 'access rights: portal user should not see issues of a not-followed followers project, only assigned')
# Data: subscribe Alfred, Chell and Donovan as follower
- self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_alfred_id, self.user_chell_id, self.user_donovan_id])
- self.project_issue.message_subscribe_users(cr, self.user_alfred_id, [self.issue_1_id, self.issue_3_id], [self.user_chell_id, self.user_alfred_id])
+ self.project_project.message_subscribe_users(cr, uid, [pigs_id], [self.user_projectuser_id, self.user_portal_id, self.user_anonymous_id])
+ self.project_issue.message_subscribe_users(cr, self.user_projectuser_id, [self.issue_1_id, self.issue_3_id], [self.user_portal_id, self.user_projectuser_id])
# Do: Alfred reads project -> ok (follower ok followers)
# Test: followed + assigned issues visible
- issue_ids = self.project_issue.search(cr, self.user_alfred_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_projectuser_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_4_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: employee user should not see followed + assigned issues of a follower project')
+ 'access rights: employee user should not see followed + assigned issues of a follower project')
# Do: Chell reads project -> ok (follower ok follower)
# Test: followed + assigned issues visible
- issue_ids = self.project_issue.search(cr, self.user_chell_id, [('project_id', '=', pigs_id)])
+ issue_ids = self.project_issue.search(cr, self.user_portal_id, [('project_id', '=', pigs_id)])
test_issue_ids = set([self.issue_1_id, self.issue_3_id, self.issue_5_id])
self.assertEqual(set(issue_ids), test_issue_ids,
- 'access rights: employee user should not see followed + assigned issues of a follower project')
+ 'access rights: employee user should not see followed + assigned issues of a follower project')
diff --git a/addons/product/pricelist.py b/addons/product/pricelist.py
index 6fc926980dd..649b721eb46 100644
--- a/addons/product/pricelist.py
+++ b/addons/product/pricelist.py
@@ -376,11 +376,26 @@ class product_pricelist_item(osv.osv):
result.append((-2, _('Supplier Prices on the product form')))
return result
+# Added default function to fetch the Price type Based on Pricelist type.
+ def _get_default_base(self, cr, uid, fields, context=None):
+ product_price_type_obj = self.pool.get('product.price.type')
+ if fields.get('type') == 'purchase':
+ product_price_type_ids = product_price_type_obj.search(cr, uid, [('field', '=', 'standard_price')], context=context)
+ elif fields.get('type') == 'sale':
+ product_price_type_ids = product_price_type_obj.search(cr, uid, [('field','=','list_price')], context=context)
+ else:
+ return -1
+ if not product_price_type_ids:
+ return False
+ else:
+ pricetype = product_price_type_obj.browse(cr, uid, product_price_type_ids, context=context)[0]
+ return pricetype.id
+
_name = "product.pricelist.item"
_description = "Pricelist item"
_order = "sequence, min_quantity desc"
_defaults = {
- 'base': lambda *a: -1,
+ 'base': _get_default_base,
'min_quantity': lambda *a: 0,
'sequence': lambda *a: 5,
'price_discount': lambda *a: 0,
diff --git a/addons/product/pricelist_view.xml b/addons/product/pricelist_view.xml
index a1b996b891b..784a14228d5 100644
--- a/addons/product/pricelist_view.xml
+++ b/addons/product/pricelist_view.xml
@@ -152,7 +152,7 @@
-
+
diff --git a/addons/project/__openerp__.py b/addons/project/__openerp__.py
index c0a516cf555..f5ba24c41cb 100644
--- a/addons/project/__openerp__.py
+++ b/addons/project/__openerp__.py
@@ -40,7 +40,6 @@
],
'depends': [
'base_setup',
- 'base_status',
'product',
'analytic',
'board',
@@ -66,7 +65,6 @@ Dashboard / Reports for Project Management will include:
'data': [
'security/project_security.xml',
'wizard/project_task_delegate_view.xml',
- 'wizard/project_task_reevaluate_view.xml',
'security/ir.model.access.csv',
'project_data.xml',
'project_view.xml',
@@ -79,9 +77,6 @@ Dashboard / Reports for Project Management will include:
],
'demo': ['project_demo.xml'],
'test': [
- 'test/project_demo.yml',
- 'test/project_process.yml',
- 'test/task_process.yml',
],
'installable': True,
'auto_install': False,
diff --git a/addons/project/board_project_view.xml b/addons/project/board_project_view.xml
index 931c661d211..3a14f7b6274 100644
--- a/addons/project/board_project_view.xml
+++ b/addons/project/board_project_view.xml
@@ -16,7 +16,6 @@
-
@@ -26,7 +25,7 @@
project.taskformtree,form
- [('user_id','=',uid),('state','not in',('cancel','done'))]
+ [('user_id', '=', uid), ('stage_id.fold', '!=', True)]
diff --git a/addons/project/doc/changelog.rst b/addons/project/doc/changelog.rst
new file mode 100644
index 00000000000..ba5b242f1c0
--- /dev/null
+++ b/addons/project/doc/changelog.rst
@@ -0,0 +1,16 @@
+.. _changelog:
+
+Changelog
+=========
+
+`trunk (saas-2)`
+----------------
+
+- Stage/state update
+
+ - ``project.task``: removed inheritance from ``base_stage`` class and removed
+ ``state`` field. Added ``date_last_stage_update`` field holding last stage_id
+ modification. Updated reports.
+ - ``project.task.type``: removed ``state`` field.
+
+- Removed ``project.task.reevaluate`` wizard.
diff --git a/addons/project/doc/index.rst b/addons/project/doc/index.rst
new file mode 100644
index 00000000000..118b8e7006b
--- /dev/null
+++ b/addons/project/doc/index.rst
@@ -0,0 +1,22 @@
+=====================
+Project DevDoc
+=====================
+
+Project module documentation
+===================================
+
+Documentation topics
+''''''''''''''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ stage_status.rst
+
+Changelog
+'''''''''
+
+.. toctree::
+ :maxdepth: 1
+
+ changelog.rst
diff --git a/addons/project/doc/stage_status.rst b/addons/project/doc/stage_status.rst
new file mode 100644
index 00000000000..cc2a64c2fbf
--- /dev/null
+++ b/addons/project/doc/stage_status.rst
@@ -0,0 +1,55 @@
+.. _stage_status:
+
+Stage and Status
+================
+
+.. versionchanged:: 8.0 saas-2 state/stage cleaning
+
+Stage
++++++
+
+This revision removed the concept of state on project.task objects. The ``state``
+field has been totally removed and replaced by stages, using ``stage_id``. The
+following models are impacted:
+
+ - ``project.task`` now use only stages. However a convention still exists about
+ 'New' stage. A task is consdered as ``new`` when it has the following
+ properties:
+
+ - ``stage_id and stage_id.sequence = 1``
+
+ - ``project.task.type`` do not have any ``state`` field anymore.
+ - ``project.task.report`` do not have any ``state`` field anymore.
+
+By default a newly created task is in a new stage. It means that it will
+fetch the stage having ``sequence = 1``. Stage mangement is done using the
+kanban view or the clikable statusbar. It is not done using buttons anymore.
+
+Stage analysis
+++++++++++++++
+
+Stage analysis can be performed using the newly introduced ``date_last_stage_update``
+datetime field. This field is updated everytime ``stage_id`` is updated.
+
+``project.task.report`` model also uses the ``date_last_stage_update`` field.
+This allows to group and analyse the time spend in the various stages.
+
+Open / Assignation date
++++++++++++++++++++++++
+
+The ``date_open`` field meaning has been updated. It is now set when the ``user_id``
+(responsible) is set. It is therefore the assignation date.
+
+Subtypes
+++++++++
+
+The following subtypes are triggered on ``project.task``:
+
+ - ``mt_task_new``: new tasks. Condition: ``obj.stage_id and obj.stage_id.sequence == 1``
+ - ``mt_task_stage``: stage changed. Condition: ``obj.stage_id and obj.stage_id.sequence != 1``
+ - ``mt_task_assigned``: user assigned. condition: ``obj.user_id and obj.user_id.id``
+ - ``mt_task_blocked``: kanban state blocked. Condition: ``obj.kanban_state == 'blocked'``
+
+
+Those subtypes are also available on the ``project.project`` model and are used
+for the auto subscription.
diff --git a/addons/project/project.py b/addons/project/project.py
index 6bd17f98e92..d09e3476a96 100644
--- a/addons/project/project.py
+++ b/addons/project/project.py
@@ -25,13 +25,10 @@ import time
from openerp import SUPERUSER_ID
from openerp import tools
+from openerp.addons.resource.faces import task as Task
from openerp.osv import fields, osv
from openerp.tools.translate import _
-from openerp.addons.base_status.base_stage import base_stage
-from openerp.addons.resource.faces import task as Task
-
-_TASK_STATE = [('draft', 'New'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done'), ('cancelled', 'Cancelled')]
class project_task_type(osv.osv):
_name = 'project.task.type'
@@ -44,33 +41,18 @@ class project_task_type(osv.osv):
'case_default': fields.boolean('Default for New Projects',
help="If you check this field, this stage will be proposed by default on each new project. It will not assign this stage to existing projects."),
'project_ids': fields.many2many('project.project', 'project_task_type_rel', 'type_id', 'project_id', 'Projects'),
- 'state': fields.selection(_TASK_STATE, 'Related Status', required=True,
- help="The status of your document is automatically changed regarding the selected stage. " \
- "For example, if a stage is related to the status 'Close', when your document reaches this stage, it is automatically closed."),
'fold': fields.boolean('Folded by Default',
help="This stage is not visible, for example in status bar or kanban view, when there are no records in that stage to display."),
}
- def _get_default_project_id(self, cr, uid, ctx={}):
- proj = ctx.get('default_project_id', False)
- if type(proj) is int:
- return [proj]
- return proj
+
_defaults = {
'sequence': 1,
- 'state': 'open',
'fold': False,
'case_default': False,
- 'project_ids': _get_default_project_id
+ 'project_ids': lambda self, cr, uid, ctx=None: self.pool['project.task']._get_default_project_id(cr, uid, context=ctx),
}
_order = 'sequence'
-def short_name(name):
- """Keep first word(s) of name to make it small enough
- but distinctive"""
- if not name: return name
- # keep 7 chars + end of the last word
- keep_words = name[:7].strip().split()
- return ' '.join(name.split()[:len(keep_words)])
class project(osv.osv):
_name = "project.project"
@@ -99,9 +81,9 @@ class project(osv.osv):
def onchange_partner_id(self, cr, uid, ids, part=False, context=None):
partner_obj = self.pool.get('res.partner')
- if not part:
- return {'value':{}}
val = {}
+ if not part:
+ return {'value': val}
if 'pricelist_id' in self.fields_get(cr, uid, context=context):
pricelist = partner_obj.read(cr, uid, part, ['property_product_pricelist'], context=context)
pricelist_id = pricelist.get('property_product_pricelist', False) and pricelist.get('property_product_pricelist')[0] or False
@@ -152,11 +134,13 @@ class project(osv.osv):
cr.execute("""
SELECT project_id, COALESCE(SUM(planned_hours), 0.0),
COALESCE(SUM(total_hours), 0.0), COALESCE(SUM(effective_hours), 0.0)
- FROM project_task WHERE project_id IN %s AND state <> 'cancelled'
+ FROM project_task
+ LEFT JOIN project_task_type ON project_task.stage_id = project_task_type.id
+ WHERE project_task.project_id IN %s AND project_task_type.fold = False
GROUP BY project_id
""", (tuple(child_parent.keys()),))
# aggregate results into res
- res = dict([(id, {'planned_hours':0.0,'total_hours':0.0,'effective_hours':0.0}) for id in ids])
+ res = dict([(id, {'planned_hours':0.0, 'total_hours':0.0, 'effective_hours':0.0}) for id in ids])
for id, planned, total, effective in cr.fetchall():
# add the values specific to id to all parent projects of id in the result
while id:
@@ -253,22 +237,22 @@ class project(osv.osv):
'planned_hours': fields.function(_progress_rate, multi="progress", string='Planned Time', help="Sum of planned hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'effective_hours': fields.function(_progress_rate, multi="progress", string='Time Spent', help="Sum of spent hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'total_hours': fields.function(_progress_rate, multi="progress", string='Total Time', help="Sum of total hours of all tasks related to this project and its child projects.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'progress_rate': fields.function(_progress_rate, multi="progress", string='Progress', type='float', group_operator="avg", help="Percent of tasks closed according to the total of tasks todo.",
store = {
'project.project': (_get_project_and_parents, ['tasks', 'parent_id', 'child_ids'], 10),
- 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'state'], 20),
+ 'project.task': (_get_projects_from_tasks, ['planned_hours', 'remaining_hours', 'work_ids', 'stage_id'], 20),
}),
'resource_calendar_id': fields.many2one('resource.calendar', 'Working Time', help="Timetable working hours to adjust the gantt diagram report", states={'close':[('readonly',True)]} ),
'type_ids': fields.many2many('project.task.type', 'project_task_type_rel', 'project_id', 'type_id', 'Tasks Stages', states={'close':[('readonly',True)], 'cancelled':[('readonly',True)]}),
@@ -323,26 +307,19 @@ class project(osv.osv):
]
def set_template(self, cr, uid, ids, context=None):
- res = self.setActive(cr, uid, ids, value=False, context=context)
- return res
+ return self.setActive(cr, uid, ids, value=False, context=context)
def set_done(self, cr, uid, ids, context=None):
- task_obj = self.pool.get('project.task')
- task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', 'not in', ('cancelled', 'done'))])
- task_obj.case_close(cr, uid, task_ids, context=context)
- return self.write(cr, uid, ids, {'state':'close'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'close'}, context=context)
def set_cancel(self, cr, uid, ids, context=None):
- task_obj = self.pool.get('project.task')
- task_ids = task_obj.search(cr, uid, [('project_id', 'in', ids), ('state', '!=', 'done')])
- task_obj.case_cancel(cr, uid, task_ids, context=context)
- return self.write(cr, uid, ids, {'state':'cancelled'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'cancelled'}, context=context)
def set_pending(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state':'pending'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'pending'}, context=context)
def set_open(self, cr, uid, ids, context=None):
- return self.write(cr, uid, ids, {'state':'open'}, context=context)
+ return self.write(cr, uid, ids, {'state': 'open'}, context=context)
def reset_project(self, cr, uid, ids, context=None):
return self.setActive(cr, uid, ids, value=True, context=context)
@@ -459,8 +436,6 @@ class project(osv.osv):
if project.user_id and (project.user_id.id not in u_ids):
u_ids.append(project.user_id.id)
for task in project.tasks:
- if task.state in ('done','cancelled'):
- continue
if task.user_id and (task.user_id.id not in u_ids):
u_ids.append(task.user_id.id)
calendar_id = project.resource_calendar_id and project.resource_calendar_id.id or False
@@ -525,7 +500,7 @@ def Project():
for project in projects:
project_gantt = getattr(projects_gantt, 'Project_%d' % (project.id,))
for task in project.tasks:
- if task.state in ('done', 'cancelled'):
+ if task.stage_id and task.stage_id.fold:
continue
p = getattr(project_gantt, 'Task_%d' % (task.id,))
@@ -540,16 +515,13 @@ def Project():
}, context=context)
return True
- # ------------------------------------------------
- # OpenChatter methods and notifications
- # ------------------------------------------------
-
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
# Prevent double project creation when 'use_tasks' is checked + alias management
create_context = dict(context, project_creation_in_progress=True,
- alias_model_name=vals.get('alias_model', 'project.task'), alias_parent_model_name=self._name)
+ alias_model_name=vals.get('alias_model', 'project.task'),
+ alias_parent_model_name=self._name)
if vals.get('type', False) not in ('template', 'contract'):
vals['type'] = 'contract'
@@ -566,22 +538,23 @@ def Project():
vals.update(alias_model_id=model_ids[0])
return super(project, self).write(cr, uid, ids, vals, context=context)
-class task(base_stage, osv.osv):
+
+class task(osv.osv):
_name = "project.task"
_description = "Task"
_date_name = "date_start"
_inherit = ['mail.thread', 'ir.needaction_mixin']
+ _mail_post_access = 'read'
_track = {
- 'state': {
- 'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.state in ['new', 'draft'],
- 'project.mt_task_started': lambda self, cr, uid, obj, ctx=None: obj.state == 'open',
- 'project.mt_task_closed': lambda self, cr, uid, obj, ctx=None: obj.state == 'done',
- },
'stage_id': {
- 'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.state not in ['new', 'draft', 'done', 'open'],
+ 'project.mt_task_new': lambda self, cr, uid, obj, ctx=None: obj.stage_id and obj.stage_id.sequence == 1,
+ 'project.mt_task_stage': lambda self, cr, uid, obj, ctx=None: obj.stage_id.sequence != 1,
},
- 'kanban_state': { # kanban state: tracked, but only block subtype
+ 'user_id': {
+ 'project.mt_task_assigned': lambda self, cr, uid, obj, ctx=None: obj.user_id and obj.user_id.id,
+ },
+ 'kanban_state': {
'project.mt_task_blocked': lambda self, cr, uid, obj, ctx=None: obj.kanban_state == 'blocked',
},
}
@@ -593,7 +566,7 @@ class task(base_stage, osv.osv):
project = self.pool.get('project.project').browse(cr, uid, project_id, context=context)
if project and project.partner_id:
return project.partner_id.id
- return super(task, self)._get_default_partner(cr, uid, context=context)
+ return False
def _get_default_project_id(self, cr, uid, context=None):
""" Gives default section by checking if present in the context """
@@ -602,7 +575,7 @@ class task(base_stage, osv.osv):
def _get_default_stage_id(self, cr, uid, context=None):
""" Gives default stage_id """
project_id = self._get_default_project_id(cr, uid, context=context)
- return self.stage_find(cr, uid, [], project_id, [('state', '=', 'draft')], context=context)
+ return self.stage_find(cr, uid, [], project_id, [('sequence', '=', '1')], context=context)
def _resolve_project_id_from_context(self, cr, uid, context=None):
""" Returns ID of project based on the value of 'default_project_id'
@@ -679,17 +652,18 @@ class task(base_stage, osv.osv):
res[task.id]['progress'] = 0.0
if (task.remaining_hours + hours.get(task.id, 0.0)):
res[task.id]['progress'] = round(min(100.0 * hours.get(task.id, 0.0) / res[task.id]['total_hours'], 99.99),2)
- if task.state in ('done','cancelled'):
+ # TDE CHECK: if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
res[task.id]['progress'] = 100.0
return res
def onchange_remaining(self, cr, uid, ids, remaining=0.0, planned=0.0):
if remaining and not planned:
- return {'value':{'planned_hours': remaining}}
+ return {'value': {'planned_hours': remaining}}
return {}
def onchange_planned(self, cr, uid, ids, planned=0.0, effective=0.0):
- return {'value':{'remaining_hours': planned - effective}}
+ return {'value': {'remaining_hours': planned - effective}}
def onchange_project(self, cr, uid, id, project_id, context=None):
if project_id:
@@ -698,6 +672,12 @@ class task(base_stage, osv.osv):
return {'value': {'partner_id': project.partner_id.id}}
return {}
+ def onchange_user_id(self, cr, uid, ids, user_id, context=None):
+ vals = {}
+ if user_id:
+ vals['date_start'] = fields.datetime.now()
+ return {'value': vals}
+
def duplicate_task(self, cr, uid, map_ids, context=None):
for new in map_ids.values():
task = self.browse(cr, uid, new, context)
@@ -755,13 +735,6 @@ class task(base_stage, osv.osv):
'sequence': fields.integer('Sequence', select=True, help="Gives the sequence order when displaying a list of tasks."),
'stage_id': fields.many2one('project.task.type', 'Stage', track_visibility='onchange',
domain="[('project_ids', '=', project_id)]"),
- 'state': fields.related('stage_id', 'state', type="selection", store=True,
- selection=_TASK_STATE, string="Status", readonly=True,
- help='The status is set to \'Draft\', when a case is created.\
- If the case is in progress the status is set to \'Open\'.\
- When the case is over, the status is set to \'Done\'.\
- If the case needs to be reviewed then the status is \
- set to \'Pending\'.'),
'categ_ids': fields.many2many('project.category', string='Tags'),
'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State',
track_visibility='onchange',
@@ -774,6 +747,7 @@ class task(base_stage, osv.osv):
'date_start': fields.datetime('Starting Date',select=True),
'date_end': fields.datetime('Ending Date',select=True),
'date_deadline': fields.date('Deadline',select=True),
+ 'date_last_stage_update': fields.datetime('Last Stage Update', select=True),
'project_id': fields.many2one('project.project', 'Project', ondelete='set null', select="1", track_visibility='onchange'),
'parent_ids': fields.many2many('project.task', 'project_task_parent_rel', 'task_id', 'parent_id', 'Parent Tasks'),
'child_ids': fields.many2many('project.task', 'project_task_parent_rel', 'parent_id', 'task_id', 'Delegated Tasks'),
@@ -790,7 +764,7 @@ class task(base_stage, osv.osv):
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours'], 10),
'project.task.work': (_get_task, ['hours'], 10),
}),
- 'progress': fields.function(_hours_get, string='Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
+ 'progress': fields.function(_hours_get, string='Working Time Progress (%)', multi='hours', group_operator="avg", help="If the task has a progress of 99.99% you should close the task if it's finished or reevaluate the time",
store = {
'project.task': (lambda self, cr, uid, ids, c={}: ids, ['work_ids', 'remaining_hours', 'planned_hours','state'], 10),
'project.task.work': (_get_task, ['hours'], 10),
@@ -813,6 +787,7 @@ class task(base_stage, osv.osv):
_defaults = {
'stage_id': _get_default_stage_id,
'project_id': _get_default_project_id,
+ 'date_last_stage_update': lambda *a: fields.datetime.now(),
'kanban_state': 'normal',
'priority': '2',
'progress': 0,
@@ -940,7 +915,7 @@ class task(base_stage, osv.osv):
section_ids.append(task.project_id.id)
search_domain = []
if section_ids:
- search_domain = [('|')] * (len(section_ids)-1)
+ search_domain = [('|')] * (len(section_ids) - 1)
for section_id in section_ids:
search_domain.append(('project_ids', '=', section_id))
search_domain += list(domain)
@@ -957,82 +932,10 @@ class task(base_stage, osv.osv):
for task in tasks:
if task.child_ids:
for child in task.child_ids:
- if child.state in ['draft', 'open', 'pending']:
+ if child.stage_id and not child.stage_id.fold:
raise osv.except_osv(_("Warning!"), _("Child task still open.\nPlease cancel or complete child task first."))
return True
- def action_close(self, cr, uid, ids, context=None):
- """ This action closes the task
- """
- task_id = len(ids) and ids[0] or False
- self._check_child_task(cr, uid, ids, context=context)
- if not task_id: return False
- return self.do_close(cr, uid, [task_id], context=context)
-
- def do_close(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_close. """
- return self.case_close(cr, uid, ids, context=context)
-
- def case_close(self, cr, uid, ids, context=None):
- """ Closes Task """
- if not isinstance(ids, list): ids = [ids]
- for task in self.browse(cr, uid, ids, context=context):
- vals = {}
- project = task.project_id
- for parent_id in task.parent_ids:
- if parent_id.state in ('pending','draft'):
- reopen = True
- for child in parent_id.child_ids:
- if child.id != task.id and child.state not in ('done','cancelled'):
- reopen = False
- if reopen:
- self.do_reopen(cr, uid, [parent_id.id], context=context)
- # close task
- vals['remaining_hours'] = 0.0
- if not task.date_end:
- vals['date_end'] = fields.datetime.now()
- self.case_set(cr, uid, [task.id], 'done', vals, context=context)
- return True
-
- def do_reopen(self, cr, uid, ids, context=None):
- for task in self.browse(cr, uid, ids, context=context):
- project = task.project_id
- self.case_set(cr, uid, [task.id], 'open', {}, context=context)
- return True
-
- def do_cancel(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_cancel. """
- return self.case_cancel(cr, uid, ids, context=context)
-
- def case_cancel(self, cr, uid, ids, context=None):
- tasks = self.browse(cr, uid, ids, context=context)
- self._check_child_task(cr, uid, ids, context=context)
- for task in tasks:
- self.case_set(cr, uid, [task.id], 'cancelled', {'remaining_hours': 0.0}, context=context)
- return True
-
- def do_open(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_open. """
- return self.case_open(cr, uid, ids, context=context)
-
- def case_open(self, cr, uid, ids, context=None):
- if not isinstance(ids,list): ids = [ids]
- return self.case_set(cr, uid, ids, 'open', {'date_start': fields.datetime.now()}, context=context)
-
- def do_draft(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_draft. """
- return self.case_draft(cr, uid, ids, context=context)
-
- def case_draft(self, cr, uid, ids, context=None):
- return self.case_set(cr, uid, ids, 'draft', {}, context=context)
-
- def do_pending(self, cr, uid, ids, context=None):
- """ Compatibility when changing to case_pending. """
- return self.case_pending(cr, uid, ids, context=context)
-
- def case_pending(self, cr, uid, ids, context=None):
- return self.case_set(cr, uid, ids, 'pending', {}, context=context)
-
def _delegate_task_attachments(self, cr, uid, task_id, delegated_task_id, context=None):
attachment = self.pool.get('ir.attachment')
attachment_ids = attachment.search(cr, uid, [('res_model', '=', self._name), ('res_id', '=', task_id)], context=context)
@@ -1053,6 +956,7 @@ class task(base_stage, osv.osv):
delegated_task_id = self.copy(cr, uid, task.id, {
'name': delegate_data['name'],
'project_id': delegate_data['project_id'] and delegate_data['project_id'][0] or False,
+ 'stage_id': delegate_data.get('stage_id') and delegate_data.get('stage_id')[0] or False,
'user_id': delegate_data['user_id'] and delegate_data['user_id'][0] or False,
'planned_hours': delegate_data['planned_hours'] or 0.0,
'parent_ids': [(6, 0, [task.id])],
@@ -1067,16 +971,12 @@ class task(base_stage, osv.osv):
'planned_hours': delegate_data['planned_hours_me'] + (task.effective_hours or 0.0),
'name': newname,
}, context=context)
- if delegate_data['state'] == 'pending':
- self.do_pending(cr, uid, [task.id], context=context)
- elif delegate_data['state'] == 'done':
- self.do_close(cr, uid, [task.id], context=context)
delegated_tasks[task.id] = delegated_task_id
return delegated_tasks
def set_remaining_time(self, cr, uid, ids, remaining_time=1.0, context=None):
for task in self.browse(cr, uid, ids, context=context):
- if (task.state=='draft') or (task.planned_hours==0.0):
+ if (task.stage_id and task.stage_id.sequence == 1) or (task.planned_hours == 0.0):
self.write(cr, uid, [task.id], {'planned_hours': remaining_time}, context=context)
self.write(cr, uid, ids, {'remaining_hours': remaining_time}, context=context)
return True
@@ -1111,17 +1011,25 @@ class task(base_stage, osv.osv):
'planned_hours': task.planned_hours,
'kanban_state': task.kanban_state,
'type_id': task.stage_id.id,
- 'state': task.state,
'user_id': task.user_id.id
}, context=context)
return True
+ # ------------------------------------------------
+ # CRUD overrides
+ # ------------------------------------------------
+
def create(self, cr, uid, vals, context=None):
if context is None:
context = {}
+
+ # for default stage
if vals.get('project_id') and not context.get('default_project_id'):
context['default_project_id'] = vals.get('project_id')
+ # user_id change: update date_start
+ if vals.get('user_id'):
+ vals['date_start'] = fields.datetime.now()
# context: no_log, because subtype already handle this
create_context = dict(context, mail_create_nolog=True)
@@ -1129,25 +1037,30 @@ class task(base_stage, osv.osv):
self._store_history(cr, uid, [task_id], context=context)
return task_id
- # Overridden to reset the kanban_state to normal whenever
- # the stage (stage_id) of the task changes.
def write(self, cr, uid, ids, vals, context=None):
if isinstance(ids, (int, long)):
ids = [ids]
+
+ # stage change: update date_last_stage_update
+ if 'stage_id' in vals:
+ vals['date_last_stage_update'] = fields.datetime.now()
+ # user_id change: update date_start
+ if vals.get('user_id'):
+ vals['date_start'] = fields.datetime.now()
+
+ # Overridden to reset the kanban_state to normal whenever
+ # the stage (stage_id) of the task changes.
if vals and not 'kanban_state' in vals and 'stage_id' in vals:
new_stage = vals.get('stage_id')
vals_reset_kstate = dict(vals, kanban_state='normal')
for t in self.browse(cr, uid, ids, context=context):
- #TO FIX:Kanban view doesn't raise warning
- #stages = [stage.id for stage in t.project_id.type_ids]
- #if new_stage not in stages:
- #raise osv.except_osv(_('Warning!'), _('Stage is not defined in the project.'))
write_vals = vals_reset_kstate if t.stage_id != new_stage else vals
super(task, self).write(cr, uid, [t.id], write_vals, context=context)
result = True
else:
result = super(task, self).write(cr, uid, ids, vals, context=context)
- if ('stage_id' in vals) or ('remaining_hours' in vals) or ('user_id' in vals) or ('state' in vals) or ('kanban_state' in vals):
+
+ if any(item in vals for item in ['stage_id', 'remaining_hours', 'user_id', 'kanban_state']):
self._store_history(cr, uid, ids, context=context)
return result
@@ -1163,7 +1076,7 @@ class task(base_stage, osv.osv):
result = ""
ident = ' '*ident
for task in tasks:
- if task.state in ('done','cancelled'):
+ if task.stage_id and task.stage_id.fold:
continue
result += '''
%sdef Task_%s():
@@ -1194,17 +1107,6 @@ class task(base_stage, osv.osv):
return [task.project_id.message_get_reply_to()[0] if task.project_id else False
for task in self.browse(cr, uid, ids, context=context)]
- def check_mail_message_access(self, cr, uid, mids, operation, model_obj=None, context=None):
- """ mail.message document permission rule: can post a new message if can read
- because of portal document. """
- if not model_obj:
- model_obj = self
- if operation == 'create':
- model_obj.check_access_rights(cr, uid, 'read')
- model_obj.check_access_rule(cr, uid, mids, 'read', context=context)
- else:
- return super(task, self).check_mail_message_access(cr, uid, mids, operation, model_obj=model_obj, context=context)
-
def message_new(self, cr, uid, msg, custom_values=None, context=None):
""" Override to updates the document according to the email. """
if custom_values is None: custom_values = {}
@@ -1233,24 +1135,10 @@ class task(base_stage, osv.osv):
update_vals[field] = float(res.group(2).lower())
except (ValueError, TypeError):
pass
- elif match.lower() == 'state' \
- and res.group(2).lower() in ['cancel','close','draft','open','pending']:
- act = 'do_%s' % res.group(2).lower()
if act:
getattr(self,act)(cr, uid, ids, context=context)
return super(task,self).message_update(cr, uid, ids, msg, update_vals=update_vals, context=context)
- def project_task_reevaluate(self, cr, uid, ids, context=None):
- if self.pool.get('res.users').has_group(cr, uid, 'project.group_time_work_estimation_tasks'):
- return {
- 'view_type': 'form',
- "view_mode": 'form',
- 'res_model': 'project.task.reevaluate',
- 'type': 'ir.actions.act_window',
- 'target': 'new',
- }
- return self.do_reopen(cr, uid, ids, context=context)
-
class project_work(osv.osv):
_name = "project.task.work"
_description = "Project Task Work"
@@ -1371,7 +1259,7 @@ class project_task_history(osv.osv):
def _get_date(self, cr, uid, ids, name, arg, context=None):
result = {}
for history in self.browse(cr, uid, ids, context=context):
- if history.state in ('done','cancelled'):
+ if history.type_id and history.type_id.fold:
result[history.id] = history.date
continue
cr.execute('''select
@@ -1405,14 +1293,13 @@ class project_task_history(osv.osv):
_columns = {
'task_id': fields.many2one('project.task', 'Task', ondelete='cascade', required=True, select=True),
'type_id': fields.many2one('project.task.type', 'Stage'),
- 'state': fields.selection([('draft', 'New'), ('cancelled', 'Cancelled'),('open', 'In Progress'),('pending', 'Pending'), ('done', 'Done')], 'Status'),
- 'kanban_state': fields.selection([('normal', 'Normal'),('blocked', 'Blocked'),('done', 'Ready for next stage')], 'Kanban State', required=False),
+ 'kanban_state': fields.selection([('normal', 'Normal'), ('blocked', 'Blocked'), ('done', 'Ready for next stage')], 'Kanban State', required=False),
'date': fields.date('Date', select=True),
'end_date': fields.function(_get_date, string='End Date', type="date", store={
'project.task.history': (_get_related_date, None, 20)
}),
- 'remaining_hours': fields.float('Remaining Time', digits=(16,2)),
- 'planned_hours': fields.float('Planned Time', digits=(16,2)),
+ 'remaining_hours': fields.float('Remaining Time', digits=(16, 2)),
+ 'planned_hours': fields.float('Planned Time', digits=(16, 2)),
'user_id': fields.many2one('res.users', 'Responsible'),
}
_defaults = {
@@ -1442,7 +1329,7 @@ class project_task_history_cumulative(osv.osv):
SELECT
h.id AS history_id,
h.date+generate_series(0, CAST((coalesce(h.end_date, DATE 'tomorrow')::date - h.date) AS integer)-1) AS date,
- h.task_id, h.type_id, h.user_id, h.kanban_state, h.state,
+ h.task_id, h.type_id, h.user_id, h.kanban_state,
greatest(h.remaining_hours, 1) AS remaining_hours, greatest(h.planned_hours, 1) AS planned_hours,
t.project_id
FROM
diff --git a/addons/project/project_data.xml b/addons/project/project_data.xml
index 7bf1a623a2c..0f48dfe9a2b 100644
--- a/addons/project/project_data.xml
+++ b/addons/project/project_data.xml
@@ -30,51 +30,43 @@
1Analysis
- draft
-
+
- 2
+ 10Specification
- pending
- 2
+ 11Design
- open
- 3
+ 12Development
- open
- 4
+ 13Testing
- open
- 5
+ 14Merge
- open
- 100
+ 20Done
- done
- 200
+ 30Cancelled
- cancelled
@@ -86,11 +78,11 @@
Task created
-
- Task Started
+
+ Task Assignedproject.task
- Task started
+ Task AssignedTask Blocked
@@ -98,12 +90,6 @@
Task blocked
-
- Task Done
- project.task
-
- Task closed
- Stage Changedproject.task
@@ -118,11 +104,11 @@
project_id
-
- Task Started
+
+ Task Assignedproject.project
-
+ project_id
@@ -131,12 +117,6 @@
project_id
-
- Task Done
- project.project
-
- project_id
- Task Stage Changedproject.project
diff --git a/addons/project/project_demo.xml b/addons/project/project_demo.xml
index b7e1524c2c8..12e373642fd 100644
--- a/addons/project/project_demo.xml
+++ b/addons/project/project_demo.xml
@@ -225,7 +225,6 @@
ref('project.project_category_04')])]"/>
-
@@ -237,7 +236,6 @@
6
-
@@ -248,7 +246,6 @@
Design Use Cases
-
@@ -282,7 +279,6 @@
Set target for all deparments
-
@@ -293,7 +289,6 @@
Integration of core components
-
@@ -315,7 +310,6 @@
- 42.0
@@ -325,7 +319,6 @@
Create new components
- 14.0
@@ -337,7 +330,6 @@
- 12.0
@@ -371,7 +363,6 @@
- 20
@@ -382,7 +373,6 @@
Data importation + Doc
- 20
diff --git a/addons/project/project_view.xml b/addons/project/project_view.xml
index 33842a9a19c..37836b0e07a 100644
--- a/addons/project/project_view.xml
+++ b/addons/project/project_view.xml
@@ -18,29 +18,26 @@
+
+
-
+
-
-
-
-
-
-
-
-
+
+ help="Show only tasks having a deadline"/>
+
+
-
+
@@ -138,6 +135,7 @@